/* * The Mana World Server * Copyright 2004 The Mana World Development Team * * This file is part of The Mana World. * * The Mana World is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or any later version. * * The Mana World is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with The Mana World; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "game-server/collisiondetection.hpp" #include #include "point.h" #include "utils/mathutils.h" #define D_TO_R 0.0174532925 // PI / 180 #define R_TO_D 57.2957795 // 180 / PI // Tests to see if pos is between s1 degree and s2 #define test_degrees(pos,s1,s2) (pos > s1 && pos < s2) || (s1 > s2 && !(pos < s1 && pos > s2)) bool Collision::circleWithCirclesector(const Point &circlePos, int circleRadius, const Point &secPos, int secRadius, float secAngle, float secSize) { float targetAngle; // Calculate distance int distX = circlePos.x - secPos.x; int distY = circlePos.y - secPos.y; float invDist = utils::math::fastInvSqrt(distX * distX + distY * distY); float dist = 1.0f / invDist; // If out of range we can't hit it if (dist > secRadius + circleRadius) { return false; } // If we are standing in it we hit it in any case if (dist < circleRadius) { return true; } // Calculate target angle if (distX > 0) { targetAngle = asin(-distY * invDist); } else { if (distY < 0) { targetAngle = M_PI - asin(-distY * invDist); } else { targetAngle = -M_PI - asin(-distY * invDist); } } // Calculate difference from segment angle float targetDiff = fabs(targetAngle - secAngle); if (targetDiff > M_PI) { targetDiff = fabs(targetDiff - M_PI * 2); } // Add hit circle secSize += asin(circleRadius * invDist) * 2; return (targetDiff < secSize * 0.5f); } bool Collision::diskWithCircleSector(const Point &diskCenter, int diskRadius, const Point §orCenter, int sectorRadius, int halfTopAngle, int placeAngle) { float r1 = sectorRadius, r2 = diskRadius; float dx = sectorCenter.x - diskCenter.x, dy = sectorCenter.y - diskCenter.y; // d^2 = dx^2 + dy^2 float d = ((dx * dx) + (dy * dy)); // d^2 < r2^2 if (d < r2 * r2) return true; // We are right on top of each other // d^2 > r1^2 + r2^2 if (d > ((r1+r2) * (r1+r2))) return false; // The two circles do not touch float s1 = placeAngle - halfTopAngle, s2 = placeAngle + halfTopAngle; if (s1 >= 360) s1 -= 360; if (s1 < 0) s1 += 360; if (s2 >= 360) s2 -= 360; if (s2 < 0) s2 += 360; // Is the center point of circle 2 within circle 1? if (d < r1 * r1) { // Circle 2 degrees in respect to circle 1 float c2dc1 = atan2(dy,dx) * R_TO_D; if (c2dc1 < 0) c2dc1 += 360; if (test_degrees(c2dc1, s1, s2)) return true; // Since we are well within range, we might be // Too close, so we need to make sure two circles intersect d = sqrt(d); r1 = d; } else { d = sqrt(d); } float a = ((r1*r1) - (r2*r2) + (d*d)) / (2.0 * d); float axd = (a * dx) / d, ayd = (a * dy) / d, h = sqrt((r1*r1) - (a*a)); float ix1 = axd + ((h * dx) / d), iy1 = ayd - ((h * dy) / d), ix2 = axd - ((h * dx) / d), iy2 = ayd + ((h * dy) / d); float idc1 = atan2(iy1,ix1) * R_TO_D; if (idc1 < 0) idc1 += 360; if (test_degrees(idc1, s1, s2)) return true; idc1 = atan2(iy2,ix2) * R_TO_D; if (idc1 < 0) idc1 += 360; if (test_degrees(idc1, s1, s2)) return true; // If we got to this point, it must be false return false; } /** * Collision of a Disk with a Circle-Sector * * For a detailled explanation of this function please see: * http://wiki.themanaworld.org/index.php/Collision_determination * bool Collision::diskWithCircleSector2(const Point &diskCenter, int diskRadius, const Point §orCenter, int sectorRadius, int halfTopAngle, int placeAngle) { // Converting the radii to float float R = (float) sectorRadius; float Rp = (float) diskRadius; // Transform to the primary coordinate system float Px = diskCenter.x - sectorCenter.x; float Py = diskCenter.y - sectorCenter.y; // The values of the trigonomic functions (only have to be computed once) float sinAlpha = utils::math::cachedSin(halfTopAngle); float cosAlpha = utils::math::cachedCos(halfTopAngle); float sinBeta = utils::math::cachedSin(placeAngle); float cosBeta = utils::math::cachedCos(placeAngle); ** * This bounding circle implementation can be used up and until a * half-top-angle of +/- 85 degrees. The bounding circle becomes * infinitly large at 90 degrees. Above about 60 degrees a bounding * half-circle with radius R becomes more efficient. * (The additional check for a region 1 collision can then be scrapped.) * // Calculating the coordinates of the disk's center in coordinate system 4 float Px1 = Px * cosBeta + Py * sinBeta; float Py1 = Py * cosBeta - Px * sinBeta; // Check for an intersection with the bounding circle // (>) : touching is accepted if ((cosAlpha * Px1 * Px1 + cosAlpha * Py1 * Py1 - Px1 * R) > (Rp * Rp * cosAlpha + Rp * R)) return false; // Check for a region 4 collision if ((Px*Px + Py*Py) <= (Rp*Rp)) return true; // Calculating the coordinates of the disk's center in coordinate system 1 Px1 = Px * (cosAlpha * cosBeta + sinAlpha * sinBeta) + Py * (cosAlpha * sinBeta - sinAlpha * cosBeta); Py1 = Py * (cosAlpha * cosBeta + sinAlpha * sinBeta) - Px * (cosAlpha * sinBeta - sinAlpha * cosBeta); // Check if P is in region 5 (using coordinate system 1) if ((Px1 >= 0.0f) && (Px1 <= R) && (Py1 <= 0.0f)) { // Return true on intersection, false otherwise // (>=) : touching is accepted return (Py1 >= -1.0f * Rp); } // Check if P is in region 3 (using coordinate system 1) if ((Px1 > R) && (Py1 <= 0.0f)) { // Calculating the vector from point A to the disk center float distAx = Px - R * (cosAlpha * cosBeta + sinAlpha * sinBeta); float distAy = Py - R * (cosAlpha * sinBeta - sinAlpha * cosBeta); // Check for a region 3 collision return ((distAx * distAx + distAy * distAy) <= Rp * Rp); } // Discard, if P is in region 4 (was previously checked) if ((Px1 < 0.0f) && (Py1 <= 0.0f)) return false; float tan2Alpha = utils::math::cachedTan(2 * halfTopAngle); // Check if P is in region 1 (using coordinate system 1) if ((Px1 >= 0.0f) && (Py1 >= 0.0f) && (Py1 <= Px1 * tan2Alpha)) { // Return true on intersection, false otherwise // (<=) : touching is accepted return ((Px * Px + Py * Py) <= (R * R + Rp * Rp + 2.0f * R * Rp)); } // Calculating the coordinates of the disk's center in coordinate system 3 Px1 = Px * (cosAlpha * cosBeta - sinAlpha * sinBeta) + Py * (sinAlpha * cosBeta + cosAlpha * sinBeta); Py1 = Py * (cosAlpha * cosBeta - sinAlpha * sinBeta) - Px * (sinAlpha * cosBeta + cosAlpha * sinBeta); // Discard, if P is in region 4 (was previously checked) if ((Px1 < 0.0f) && (Py1 >= 0.0f)) return false; // Check if P is in region 6 (using coordinate system 3) if ((Px1 >= 0.0f) && (Px1 <= R) && (Py1 >= 0.0f)) { // Return true on intersection, false otherwise // (<=) : touching is accepted return (Py1 <= Rp); } // Check if P is in region 2 (using coordinate system 3) if ((Px1 > R) && (Py1 <= 0.0f)) { // Calculating the vector from point B to the disk center float distBx = Px - R * (cosAlpha * cosBeta - sinAlpha * sinBeta); float distBy = Py - R * (cosAlpha * sinBeta + sinAlpha * cosBeta); // Check for a region 2 collision return ((distBx * distBx + distBy * distBy) <= (Rp * Rp)); } // The intersection with the bounding circle is in region 4, // but the disk and circle sector don't intersect. return false; } */ bool Collision::CircleWithCircle(const Point ¢er1, int radius1, const Point ¢er2, int radius2) { int distx = center1.x - center2.x; int disty = center1.y - center2.y; double dist = sqrt((distx * distx) + (disty * disty)); return (dist < radius1 + radius2); } bool Collision::rectWithRect(const Point &pos1, const Point &size1, const Point &pos2, const Point &size2) { return( pos1.x < (pos2.x + size2.x) //left edge 1 left of right edge 2 && (pos1.x + size1.x) > pos2.x //right edge 1 right of left edge 2 && pos1.y < (pos2.y + size2.y) //upper edge 1 above lower edge 2 && (pos1.y + size1.y) > pos2.y //lower edge 1 under upper edge 2 ); }