/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program 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 3 of the License, or * (at your option) any later version. * * This program 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 this program. If not, see . * */ #include "scumm/he/intern_he.h" #include "scumm/he/basketball/collision/bball_collision.h" #include "scumm/he/basketball/collision/bball_collision_support_obj.h" #include "scumm/he/basketball/collision/bball_collision_object.h" #include "scumm/he/basketball/collision/bball_collision_sphere.h" #include "scumm/he/basketball/collision/bball_collision_box.h" #include "scumm/he/basketball/collision/bball_collision_cylinder.h" #include "scumm/he/basketball/collision/bball_collision_stack.h" #include "scumm/he/basketball/collision/bball_collision_node.h" #include "scumm/he/basketball/collision/bball_collision_tree.h" namespace Scumm { float CCollisionCylinder::getDimensionDistance(const CCollisionBox &targetObject, EDimension dimension) const { if (center[dimension] < targetObject.minPoint[dimension]) { return (center[dimension] - targetObject.minPoint[dimension]); } else if (center[dimension] > targetObject.maxPoint[dimension]) { return (center[dimension] - targetObject.maxPoint[dimension]); } else { return 0; } } float CCollisionCylinder::getDimensionDistance(const CCollisionSphere &targetObject, EDimension dimension) const { float centerDistance = center[dimension] - targetObject.center[dimension]; if (dimension == Z_INDEX) { if (centerDistance < -(targetObject.radius)) { return (centerDistance + targetObject.radius); } else if (centerDistance > (targetObject.radius)) { return (centerDistance - targetObject.radius); } else { return 0; } } else { return centerDistance; } } float CCollisionCylinder::getDimensionDistance(const CCollisionCylinder &targetObject, EDimension dimension) const { float centerDistance = center[dimension] - targetObject.center[dimension]; if (dimension == Z_INDEX) { if (centerDistance < -(targetObject.height / 2)) { return (centerDistance + (targetObject.height / 2)); } else if (centerDistance > (targetObject.height / 2)) { return (centerDistance - (targetObject.height / 2)); } else { return 0; } } else { return centerDistance; } } float CCollisionCylinder::getObjectDistance(const CCollisionSphere &targetObject) const { U32Distance3D distance; distance.x = getDimensionDistance(targetObject, X_INDEX); distance.y = getDimensionDistance(targetObject, Y_INDEX); distance.z = getDimensionDistance(targetObject, Z_INDEX); float xyDistance = distance.xyMagnitude() - radius - targetObject.radius; if (xyDistance < 0) xyDistance = 0; float zDistance = fabs(distance.z) - (height / 2) - targetObject.radius; if (zDistance < 0) zDistance = 0; float totalDistance = sqrt((xyDistance * xyDistance) + (zDistance * zDistance)); return (totalDistance); } float CCollisionCylinder::getObjectDistance(const CCollisionBox &targetObject) const { U32Distance3D distance; distance.x = getDimensionDistance(targetObject, X_INDEX); distance.y = getDimensionDistance(targetObject, Y_INDEX); distance.z = getDimensionDistance(targetObject, Z_INDEX); float xyDistance = distance.xyMagnitude() - radius; if (xyDistance < 0) xyDistance = 0; float zDistance = fabs(distance.z) - (height / 2); if (zDistance < 0) zDistance = 0; float totalDistance = sqrt((xyDistance * xyDistance) + (zDistance * zDistance)); return (totalDistance); } float CCollisionCylinder::getObjectDistance(const CCollisionCylinder &targetObject) const { U32Distance3D distance; distance.x = getDimensionDistance(targetObject, X_INDEX); distance.y = getDimensionDistance(targetObject, Y_INDEX); distance.z = getDimensionDistance(targetObject, Z_INDEX); float xyDistance = distance.xyMagnitude() - radius - targetObject.radius; if (xyDistance < 0) xyDistance = 0; float zDistance = fabs(distance.z) - (height / 2) - (targetObject.height / 2); if (zDistance < 0) zDistance = 0; float totalDistance = sqrt((xyDistance * xyDistance) + (zDistance * zDistance)); return (totalDistance); } bool CCollisionCylinder::testObjectIntersection(const CCollisionSphere &targetObject, U32Distance3D *distance) const { // Get the distance between the ball and the cylinder... distance->x = getDimensionDistance(targetObject, X_INDEX); distance->y = getDimensionDistance(targetObject, Y_INDEX); distance->z = getDimensionDistance(targetObject, Z_INDEX); if (distance->xyMagnitude() < (radius + targetObject.radius)) { return (fabs(distance->z) < (height / 2)); } else { return false; } } bool CCollisionCylinder::testObjectIntersection(const CCollisionBox &targetObject, U32Distance3D *distance) const { // Get the distance between the ball and the cylinder... distance->x = getDimensionDistance(targetObject, X_INDEX); distance->y = getDimensionDistance(targetObject, Y_INDEX); distance->z = getDimensionDistance(targetObject, Z_INDEX); ; if (distance->xyMagnitude() < radius) { return (fabs(distance->z) < (height / 2)); } else { return false; } } bool CCollisionCylinder::testObjectIntersection(const CCollisionCylinder &targetObject, U32Distance3D *distance) const { // Get the distance between the ball and the cylinder... distance->x = getDimensionDistance(targetObject, X_INDEX); distance->y = getDimensionDistance(targetObject, Y_INDEX); distance->z = getDimensionDistance(targetObject, Z_INDEX); if (distance->xyMagnitude() < (radius + targetObject.radius)) { return (fabs(distance->z) < (height / 2)); } else { return false; } } float CCollisionCylinder::getPenetrationTime(const CCollisionBox &targetObject, const U32Distance3D &distance, EDimension dimension) const { float collisionDepth; if (dimension == Z_INDEX) { if (distance[dimension] > 0) { collisionDepth = (height / 2) - distance[dimension]; } else if (distance[dimension] < 0) { collisionDepth = -(height / 2) - distance[dimension]; } else { collisionDepth = 0; } } else { if (distance[dimension] > 0) { collisionDepth = radius - distance[dimension]; } else if (distance[dimension] < 0) { collisionDepth = -radius - distance[dimension]; } else { return 0; } } float tFinal = (_velocity[dimension] == 0) ? 0 : (collisionDepth / -_velocity[dimension]); return tFinal; } float CCollisionCylinder::getPenetrationTime(const CCollisionCylinder &targetObject, const U32Distance3D &distance, EDimension dimension) const { float collisionDepth; if (dimension == Z_INDEX) { if (distance[dimension] > 0) { collisionDepth = (height / 2) - distance[dimension]; } else if (distance[dimension] < 0) { collisionDepth = (height / 2) + distance[dimension]; } else { collisionDepth = 0; } } else { if (distance[dimension] > 0) { collisionDepth = radius + targetObject.radius - distance[dimension]; } else if (distance[dimension] < 0) { collisionDepth = -radius - targetObject.radius - distance[dimension]; } else { collisionDepth = 0; } } float tFinal = (_velocity[dimension] == 0) ? 0 : (collisionDepth / -_velocity[dimension]); return tFinal; } bool CCollisionCylinder::validateCollision(const CCollisionCylinder &targetObject, U32Distance3D *distance) { float zCollisionTime = getPenetrationTime(targetObject, *distance, Z_INDEX); if (((zCollisionTime > 1) || (zCollisionTime == 0)) && (_velocity.xyMagnitude() == 0) && (_velocity.z != 0)) { forceOutOfObject(targetObject, distance); return _ignore; } else { // If a player raises their shields and another player is standing inside // the shields, that player should be pushed out... if ((_velocity.magnitude() == 0) && (_shieldRadius == 0) && (targetObject._shieldRadius != 0)) { return true; } else { // See if we're hitting the top or bottom of this... if (((distance->z > 0) && (_velocity.z <= 0)) || ((distance->z < 0) && (_velocity.z >= 0))) { return true; } else { // Create a vector from the center of the target cylinder to the center of // this cylinder... U32FltVector2D centerVector = targetObject.center - center; float aMag = _velocity.magnitude(); float bMag = centerVector.magnitude(); float aDotb = centerVector * _velocity; if (aMag == 0) { // If this object isn't moving, this can't be a valid collision... return false; } if (bMag == 0) { // bMag is 0, it is possible that this sphere penetrated too far into the target // object. If this is the case, we'll go ahead and validate the collision... return true; } double angleCosine = aDotb / (aMag * bMag); if (angleCosine > 0) { return true; } else { return false; } } } } } bool CCollisionCylinder::backOutOfObject(const CCollisionBox &targetObject, U32Distance3D *distance, float *timeUsed) { // See if this is an object we can simply step up onto... if (((height / 2) - distance->z) <= MAX_STEP_HEIGHT) { center.z += ((height / 2) - distance->z); return true; } else if (_movementType == kCircular) { // Since we are moving with a circular motion, try circling out of the object... if (circleOutOfObject(targetObject, distance, timeUsed)) { return true; } else { // If we aren't able to circle out of the object, back out in a straight line... _movementType = kStraight; return backStraightOutOfObject(targetObject, distance, timeUsed); } } else { _movementType = kStraight; return backStraightOutOfObject(targetObject, distance, timeUsed); } } bool CCollisionCylinder::backOutOfObject(const CCollisionCylinder &targetObject, U32Distance3D *distance, float *timeUsed) { if (_velocity.magnitude() == 0) { forceOutOfObject(targetObject, distance); return true; } else if ((_movementType == kCircular) && (&targetObject != _revCenter)) { // Since we are moving with a circular motion, try circling out of the object... if (circleOutOfObject(targetObject, distance, timeUsed)) { return true; } else { // If we aren't able to circle out of the object, back out in a straight line... _movementType = kStraight; return backStraightOutOfObject(targetObject, distance, timeUsed); } } else { _movementType = kStraight; return backStraightOutOfObject(targetObject, distance, timeUsed); } } bool CCollisionCylinder::backStraightOutOfObject(const ICollisionObject &targetObject, U32Distance3D *distance, float *timeUsed) { if (_velocity.magnitude() == 0) return true; U32FltPoint3D startPosition = center; int loopCounter = 0; while (testObjectIntersection(targetObject, distance)) { float collisionTimes[3]; collisionTimes[X_INDEX] = getPenetrationTime(targetObject, *distance, X_INDEX); collisionTimes[Y_INDEX] = getPenetrationTime(targetObject, *distance, Y_INDEX); collisionTimes[Z_INDEX] = getPenetrationTime(targetObject, *distance, Z_INDEX); Std::sort(collisionTimes, collisionTimes + Z_INDEX + 1); float collisionTime = COLLISION_SMALL_TIME_INCREMENT; if (collisionTimes[2] > 0) collisionTime = collisionTimes[2]; if (collisionTimes[1] > 0) collisionTime = collisionTimes[1]; if (collisionTimes[0] > 0) collisionTime = collisionTimes[0]; *timeUsed += collisionTime; // If we take too long to back out, something is wrong, // so restore the object to an ok state... if ((*timeUsed > COLLISION_BACK_OUT_TIME_LIMIT) && (*timeUsed != collisionTime)) { warning("CCollisionCylinder::backStraightOutOfObject(): It took too long for one object to back out of another. Ignore and U32 will attempt to correct."); center = startPosition; restore(); return false; } center.x -= (collisionTime * _velocity.x); center.y -= (collisionTime * _velocity.y); center.z -= (collisionTime * _velocity.z); // Make doubly sure we don't loop forever... if (++loopCounter > 500) return false; } return true; } bool CCollisionCylinder::getCornerIntersection(const CCollisionBox &targetObject, const U32Distance3D &distance, U32FltPoint2D *intersection) { float centerDistance = (center - _revCenterPt).xyMagnitude(); U32BoundingBox testBox = targetObject; U32FltPoint2D boxCorner; // Get the corner that we think we've collided with... if (distance.x < 0) { boxCorner.x = targetObject.minPoint.x; testBox.maxPoint.x += centerDistance; } else if (distance.x > 0) { boxCorner.x = targetObject.maxPoint.x; testBox.minPoint.x -= centerDistance; } else { return false; } if (distance.y < 0) { boxCorner.y = targetObject.minPoint.y; testBox.maxPoint.y += centerDistance; } else if (distance.y > 0) { boxCorner.y = targetObject.maxPoint.y; testBox.maxPoint.y -= centerDistance; } else { return false; } U32FltPoint2D point1; U32FltPoint2D point2; int pointCount = getEquidistantPoint(boxCorner, radius, _revCenterPt, centerDistance, &point1, &point2); switch (pointCount) { case 0: return false; break; case 1: *intersection = point1; break; case 2: if (!testBox.isPointWithin(point1)) { *intersection = point1; } else if (!testBox.isPointWithin(point2)) { *intersection = point2; } else { return false; } break; } return true; } bool CCollisionCylinder::circleOutOfObject(const CCollisionBox &targetObject, U32Distance3D *distance, float *timeUsed) { bool validXIntercept = true; bool validYIntercept = true; // If this object isn't moving, push it out of the collision... if (_velocity.xyMagnitude() == 0) { forceOutOfObject(targetObject, distance); testObjectIntersection(targetObject, distance); return false; } // Keep track of where we started... U32FltPoint3D oldCenter = center; // Get the distance between the center of this cylinder and the point it is // revolving around... float centerDistance = (center - _revCenterPt).xyMagnitude(); // Find out in which direction we are going to revolve around the target point... ERevDirection revDirection = (getRevDirection() == kClockwise) ? kCounterClockwise : kClockwise; // Find where this cylinder would touch the box in the x dimension... U32FltPoint2D xIntercept; if (distance->y < 0) { xIntercept.y = targetObject.minPoint.y - radius; float yDist = xIntercept.y - _revCenterPt.y; float xInterceptSquared = centerDistance * centerDistance - yDist * yDist; if (xInterceptSquared < 0) { validXIntercept = getCornerIntersection(targetObject, *distance, &xIntercept); } else { if (revDirection == kClockwise) { xIntercept.x = sqrt(xInterceptSquared) + _revCenterPt.x; } else { xIntercept.x = -sqrt(xInterceptSquared) + _revCenterPt.x; } } } else if (distance->y > 0) { xIntercept.y = targetObject.maxPoint.y + radius; float yDist = xIntercept.y - _revCenterPt.y; float xInterceptSquared = centerDistance * centerDistance - yDist * yDist; if (xInterceptSquared < 0) { validXIntercept = getCornerIntersection(targetObject, *distance, &xIntercept); } else { if (revDirection == kClockwise) { xIntercept.x = -sqrt(xInterceptSquared) + _revCenterPt.x; } else { xIntercept.x = sqrt(xInterceptSquared) + _revCenterPt.x; } } } else { validXIntercept = false; } // If we found a valid place to back out to, try going there... if (validXIntercept) { center.x = xIntercept.x; center.y = xIntercept.y; forceOutOfObject(targetObject, distance); testObjectIntersection(targetObject, distance); return true; } // Find where this cylinder would touch the box in the y dimension... U32FltPoint2D yIntercept; if (distance->x < 0) { yIntercept.x = targetObject.minPoint.x - radius; float xDist = yIntercept.x - _revCenterPt.x; float yInterceptSquared = centerDistance * centerDistance - xDist * xDist; if (yInterceptSquared < 0) { validYIntercept = getCornerIntersection(targetObject, *distance, &yIntercept); } else { if (revDirection == kClockwise) { yIntercept.y = -sqrt(yInterceptSquared) + _revCenterPt.y; } else { yIntercept.y = sqrt(yInterceptSquared) + _revCenterPt.y; } } } else if (distance->x > 0) { yIntercept.x = targetObject.maxPoint.x + radius; float xDist = yIntercept.x - _revCenterPt.x; float yInterceptSquared = centerDistance * centerDistance - xDist * xDist; if (yInterceptSquared < 0) { validYIntercept = getCornerIntersection(targetObject, *distance, &yIntercept); } else { if (revDirection == kClockwise) { yIntercept.y = sqrt(yInterceptSquared) + _revCenterPt.y; } else { yIntercept.y = -sqrt(yInterceptSquared) + _revCenterPt.y; } } } else { validYIntercept = false; } // If we found a valid place to back out to, try going there... if (validYIntercept) { center.x = yIntercept.x; center.y = yIntercept.y; forceOutOfObject(targetObject, distance); testObjectIntersection(targetObject, distance); return true; } // If we get here, we weren't able to circle out... center = oldCenter; testObjectIntersection(targetObject, distance); return false; } bool CCollisionCylinder::circleOutOfObject(const CCollisionCylinder &targetObject, U32Distance3D *distance, float *timeUsed) { // Get the distance from the target cylinder that we want to be... float targetDistance = radius + targetObject.radius; // Get the distance from the revolution center that we want to be... U32FltVector2D sourceVector = center - _revCenterPt; float revDistance = sourceVector.magnitude(); // Find out in which direction we are going to revolve around the target point... ERevDirection revDirection = (getRevDirection() == kClockwise) ? kCounterClockwise : kClockwise; U32FltPoint2D point1; U32FltPoint2D point2; int points = getEquidistantPoint(targetObject.center, targetDistance, _revCenterPt, revDistance, &point1, &point2); switch (points) { case 0: warning("CCollisionCylinder::circleOutOfObject(): Could not find point of intersection."); return false; break; case 1: center.x = point1.x; center.y = point1.y; break; case 2: U32FltVector2D vector1 = point1 - _revCenterPt; U32FltVector2D vector2 = point2 - _revCenterPt; ERevDirection direction1 = (sourceVector.getRevDirection(vector1)); ERevDirection direction2 = (sourceVector.getRevDirection(vector2)); if (direction1 == direction2) { warning("CCollisionCylinder::circleOutOfObject(): Both directions are the same. That's weird."); } else if (direction1 == revDirection) { center.x = point1.x; center.y = point1.y; } else { center.x = point2.x; center.y = point2.y; } break; } testObjectIntersection(targetObject, distance); forceOutOfObject(targetObject, distance); return true; } bool CCollisionCylinder::nudgeObject(const CCollisionBox &targetObject, U32Distance3D *distance, float *timeUsed) { float tFinal = 0; // To nudge the cylinder precisely against the box, we need to calculate when the // square root of the sum of the squared distances between the sphere and the three // planes equals the radius of the sphere. // Here we will construct a quadratic equation to solve for time. double a = 0; double b = 0; double c = -(radius * radius); // See if we are standing on the object... if ((distance->z == (height / 2)) && (distance->xyMagnitude() == 0)) { return true; } // Only do calculations for the x and y dimensions, as we will handle z separately... for (int i = X_INDEX; i <= Y_INDEX; ++i) { EDimension dim = (EDimension)i; // If the ball is already within the boundaries of the box in a certain dimension, // we don't want to include that dimension in the equation... if ((*distance)[dim] != 0) { a += (_velocity[dim] * _velocity[dim]); b += (2 * _velocity[dim] * (*distance)[dim]); c += ((*distance)[dim] * (*distance)[dim]); } } if (((b * b) < (4 * a * c)) || (a == 0)) { tFinal = -getPenetrationTime(targetObject, *distance, Z_INDEX); assert(tFinal >= 0); } else { // Now we have two answer candidates, and we want the smallest // of the two that is greater than 0... double t1 = (-b + sqrt(b * b - 4 * a * c)) / (2 * a); double t2 = (-b - sqrt(b * b - 4 * a * c)) / (2 * a); double tXY = 0; if ((0 <= t1) && (t1 <= t2)) { tXY = t1; } else if ((0 <= t2) && (t2 <= t1)) { tXY = t2; } float tZ = -getPenetrationTime(targetObject, *distance, Z_INDEX); tFinal = ((0 < tZ) && (tZ < tXY)) ? tZ : tXY; } // Update the position of the ball... center.x += _velocity.x * tFinal; center.y += _velocity.y * tFinal; center.z += _velocity.z * tFinal; *timeUsed -= tFinal; testObjectIntersection(targetObject, distance); return true; } bool CCollisionCylinder::nudgeObject(const CCollisionCylinder &targetObject, U32Distance3D *distance, float *timeUsed) { float collisionTimeFinal; U32FltVector3D xyVelocity = _velocity; xyVelocity.z = 0; // Find the distance between the two centers that would indicate an exact collision... float intersectionDist = radius + targetObject.radius; // Set up a vector pointing from the center of this sphere to the // center of the target cylinder... U32FltVector3D centerVector; centerVector.x = targetObject.center.x - center.x; centerVector.y = targetObject.center.y - center.y; float centerDistance = centerVector.xyMagnitude(); if (centerDistance > intersectionDist) { // Project the center vector onto the velocity vector. // This is the distance along the velocity vector from the center // of the sphere to the point that is parallel with the center // of the cylinder... float parallelDistance = centerVector.projectScalar(xyVelocity); // Find the distance between the center of the target object to the // point that that is distance2 units along the the velocity vector... parallelDistance = ((centerDistance * centerDistance) >= (parallelDistance * parallelDistance)) ? parallelDistance : COPY_SIGN(centerDistance, parallelDistance); // Make sure we don't try to sqrt by negative number... if (parallelDistance > centerDistance) { warning("CCollisionCylinder::nudgeObject(): Tried to sqrt by negative number."); centerDistance = parallelDistance; } float perdistance = sqrt(centerDistance * centerDistance - parallelDistance * parallelDistance); // Now we need to find the point along the velocity vector where the distance // between that point and the center of the target cylinder equals intersectionDist. // We calculate using distance 2, distance3 and intersectionDist... perdistance = ((intersectionDist * intersectionDist) >= (perdistance * perdistance)) ? perdistance : COPY_SIGN(intersectionDist, perdistance); // Make sure we don't try to sqrt by negative number... if (perdistance > intersectionDist) { warning("CCollisionCylinder::nudgeObject(): Tried to sqrt by negative number."); intersectionDist = perdistance; } float xyCollisionDist = parallelDistance - sqrt(intersectionDist * intersectionDist - perdistance * perdistance); collisionTimeFinal = (_velocity.xyMagnitude() == 0) ? 0 : (xyCollisionDist / _velocity.xyMagnitude()); } else { collisionTimeFinal = -getPenetrationTime(targetObject, *distance, Z_INDEX); } center.x += collisionTimeFinal * _velocity.x; center.y += collisionTimeFinal * _velocity.y; center.z += collisionTimeFinal * _velocity.z; *timeUsed -= collisionTimeFinal; testObjectIntersection(targetObject, distance); return true; } float CCollisionCylinder::getPointOfCollision(const CCollisionBox &targetBox, U32Distance3D distance, EDimension dimension) const { if (dimension == Z_INDEX) { if ((distance[dimension] + (height / 2)) < 0) { return targetBox.minPoint[dimension]; } else if ((distance[dimension] - (height / 2)) > 0) { return targetBox.maxPoint[dimension]; } else { return center[dimension]; } } else { if (distance[dimension] < 0) { return targetBox.minPoint[dimension]; } else if (distance[dimension] > 0) { return targetBox.maxPoint[dimension]; } else { return center[dimension]; } } } ERevDirection CCollisionCylinder::getRevDirection() const { if (_movementType != kCircular) warning("CCollisionCylinder::getRevDirection(): We can't get a revolution direction if we aren't moving in a circular fashion."); U32FltVector3D targetVector = _revCenterPt - center; if ((_velocity.xyMagnitude() != 0) && (targetVector.xyMagnitude() != 0)) { U32FltVector3D vector1 = targetVector; U32FltVector3D vector2 = _velocity; vector1.z = 0; vector2.z = 0; U32FltVector3D vector3 = vector1.cross(vector2); return (vector3.z > 0) ? kClockwise : kCounterClockwise; } else { warning("CCollisionCylinder::getRevDirection(): Division by zero attempted, ignoring..."); return kNone; } } void CCollisionCylinder::forceOutOfObject(const ICollisionObject &targetObject, U32Distance3D *distance) { U32FltVector3D targetVector; U32FltPoint3D intersectionPoint; const CCollisionBox *pTargetBox = nullptr; const CCollisionCylinder *pTargetCylinder = nullptr; float targetHeight; float targetRadius; U32FltPoint3D targetCenter; if (_ignore) return; switch (targetObject._objectShape) { case kBox: pTargetBox = static_cast(&targetObject); for (int i = X_INDEX; i <= Y_INDEX; ++i) { EDimension dim = (EDimension)i; if (center[dim] < pTargetBox->minPoint[dim]) { intersectionPoint[dim] = pTargetBox->minPoint[dim]; } else if (center[dim] > pTargetBox->maxPoint[dim]) { intersectionPoint[dim] = pTargetBox->maxPoint[dim]; } else { intersectionPoint[dim] = center[dim]; } } targetVector = (center - intersectionPoint); targetVector.z = 0; targetVector = targetVector.normalize() * radius; center.x = intersectionPoint.x + targetVector.x; center.y = intersectionPoint.y + targetVector.y; testObjectIntersection(targetObject, distance); break; case kCylinder: pTargetCylinder = static_cast(&targetObject); targetHeight = pTargetCylinder->height; targetRadius = pTargetCylinder->radius; targetCenter = pTargetCylinder->center; // Handle the case where two players are at the exact same spot... if (!((distance->x != 0) || (distance->y != 0))) warning("CCollisionCylinder::forceOutOfObject(): These two cylinders have the same center, so we don't know which direction to push this one out."); if (distance->xyMagnitude() == 0) { distance->x = 1; } if (distance->z < (targetHeight / 2)) { targetVector.x = distance->x; targetVector.y = distance->y; targetVector.z = 0; targetVector = targetVector.normalize() * (radius + targetRadius); center.x = targetCenter.x + targetVector.x; center.y = targetCenter.y + targetVector.y; testObjectIntersection(targetObject, distance); } break; default: return; } } U32FltPoint3D CCollisionCylinder::findNearestPoint(const U32FltPoint3D &testPoint) const { U32FltPoint3D cylinderPoint; if (testPoint.z <= center.z - (height / 2)) { cylinderPoint.x = testPoint.x; cylinderPoint.y = testPoint.y; cylinderPoint.z = center.z - (height / 2); } else if (testPoint.z >= center.z + (height / 2)) { cylinderPoint.x = testPoint.x; cylinderPoint.y = testPoint.y; cylinderPoint.z = center.z + (height / 2); } else { U32FltVector3D centerVector = testPoint - center; centerVector = centerVector.normalize() * radius; cylinderPoint.x = center.x + centerVector.x; cylinderPoint.y = center.y + centerVector.y; cylinderPoint.z = testPoint.z; } return cylinderPoint; } void CCollisionCylinder::handleCollision(const CCollisionBox &targetBox, float *timeUsed, U32Distance3D *distance, bool advanceObject) { // Handle collisions in the z direction. // If we're on something, make sure we aren't going up or down... if (((distance->z >= (height / 2)) && (_velocity.z < 0)) || ((distance->z <= -(height / 2)) && (_velocity.z > 0))) { _velocity.z = 0; } // Handle collisions in the xy direction... if (_movementType == kCircular) { _velocity.x = 0; _velocity.y = 0; } else { U32FltPoint2D collisionPoint; collisionPoint.x = getPointOfCollision(targetBox, *distance, X_INDEX); collisionPoint.y = getPointOfCollision(targetBox, *distance, Y_INDEX); U32FltVector2D targetVector; targetVector.x = collisionPoint.x - center.x; targetVector.y = collisionPoint.y - center.y; // Project the velocity vector onto that vector. // This will give us the component of the velocity vector that collides directly // with the target object. This part of the velocity vector will be lost in the // collision... _velocity -= _velocity.projectVector(targetVector); } if (advanceObject) { center.x += (_velocity.x * *timeUsed); center.y += (_velocity.y * *timeUsed); center.z += (_velocity.z * *timeUsed); *timeUsed = 0; } return; } void CCollisionCylinder::handleCollision(const CCollisionCylinder &targetCylinder, float *timeUsed, U32Distance3D *distance, bool advanceObject) { // Handle collisions in the z direction. // If we're on something, make sure we aren't going up or down... if (((distance->z >= (height / 2)) && (_velocity.z < 0)) || ((distance->z <= -(height / 2)) && (_velocity.z > 0))) { _velocity.z = 0; } // Handle collisions in the xy direction... if (_movementType == kCircular) { // If we are moving in a circular motion, then we must have hit at // least 2 objects this frame, so we stop our movement altogether... _velocity.x = 0; _velocity.y = 0; *timeUsed = 1; } else { // Find the vector from the center of this object to the center of // the collision object... U32FltVector2D targetVector = targetCylinder.center - center; // Project the velocity vector onto that vector. // This will give us the component of the velocity vector that collides directly // with the target object. This part of the velocity vector will be lost in the // collision... _velocity -= _velocity.projectVector(targetVector); // If we are moving forward, do so, then update the vector... if (advanceObject) { float finalMagnitude = _velocity.xyMagnitude(); if (finalMagnitude != 0) { // Revolve this cylinder around the targer cylinder... _movementType = kCircular; _revCenter = dynamic_cast(&targetCylinder); _revCenterPt = targetCylinder.center; // Find out in which direction we are going to revolve around the target cylinder... ERevDirection revDirection = getRevDirection(); // Find the diameter of the circle we are moving around... double movementDiameter = 2 * BBALL_M_PI * (targetCylinder.radius + radius); // Find how many radians around the circle we will go... double movementRadians = 2 * BBALL_M_PI * ((finalMagnitude * MAX(*timeUsed, (float)1)) / movementDiameter) * revDirection; center.x = (distance->x * cos(movementRadians) - distance->y * sin(movementRadians)) + targetCylinder.center.x; center.y = (distance->x * sin(movementRadians) + distance->y * cos(movementRadians)) + targetCylinder.center.y; testObjectIntersection(targetCylinder, distance); // Adjust for rounding errors in pi by pushing this cylinder straight out // or in so that it is in contact in just one location with the target // cylinder... forceOutOfObject(targetCylinder, distance); // Calculate the final vector. The final vector should be tangent to the // target cylinder at the point of intersection... _velocity.x = targetVector.y; _velocity.y = targetVector.x; _velocity.x = _velocity.x / targetVector.magnitude(); _velocity.y = _velocity.y / targetVector.magnitude(); _velocity.x *= finalMagnitude; _velocity.y *= finalMagnitude; if (revDirection == kClockwise) { _velocity.x *= -1; } else { _velocity.y *= -1; } } // Update the z position... center.z += _velocity.z * *timeUsed; *timeUsed = 0; } else { // Just update the vector... float finalMagnitude = _velocity.xyMagnitude(); ERevDirection revDirection = getRevDirection(); _velocity.x = targetVector.y; _velocity.y = targetVector.x; _velocity.x = _velocity.x / targetVector.magnitude(); _velocity.y = _velocity.y / targetVector.magnitude(); _velocity.x *= finalMagnitude; _velocity.y *= finalMagnitude; if (revDirection == kClockwise) { _velocity.x *= -1; } else { _velocity.y *= -1; } } } } void CCollisionCylinder::handleCollisions(CCollisionObjectVector *pCollisionVector, float *timeUsed, bool advanceObject) { if (pCollisionVector->size() > 1) { _velocity.x = 0; _velocity.y = 0; return; } ICollisionObject::handleCollisions(pCollisionVector, timeUsed, advanceObject); } bool CCollisionCylinder::isOnObject(const CCollisionBox &targetObject, const U32Distance3D &distance) const { return ((distance.z == (height / 2)) && (distance.xyMagnitude() < radius)); } bool CCollisionCylinder::isOnObject(const CCollisionSphere &targetObject, const U32Distance3D &distance) const { return ((distance.z == (height / 2)) && (distance.xyMagnitude() < (radius + targetObject.radius))); } bool CCollisionCylinder::isOnObject(const CCollisionCylinder &targetObject, const U32Distance3D &distance) const { return ((distance.z == (height / 2)) && (distance.xyMagnitude() < (radius + targetObject.radius))); } U32BoundingBox CCollisionCylinder::getBoundingBox() const { U32BoundingBox outBox; outBox.minPoint.x = center.x - radius; outBox.minPoint.y = center.y - radius; outBox.minPoint.z = center.z - height; outBox.maxPoint.x = center.x + radius; outBox.maxPoint.y = center.y + radius; outBox.maxPoint.z = center.z + height; return outBox; } U32BoundingBox CCollisionCylinder::getBigBoundingBox() const { U32BoundingBox outBox; float xyVelocity = _velocity.xyMagnitude(); outBox.minPoint.x = center.x - (radius + xyVelocity); outBox.minPoint.y = center.y - (radius + xyVelocity); outBox.minPoint.z = center.z - (height + _velocity.z); outBox.maxPoint.x = center.x + (radius + xyVelocity); outBox.maxPoint.y = center.y + (radius + xyVelocity); outBox.maxPoint.z = center.z + (height + _velocity.z); return outBox; } void CCollisionCylinder::save() { _positionSaved = true; _safetyPoint = center; _safetyVelocity = _velocity; } void CCollisionCylinder::restore() { if (_positionSaved) { if (_safetyVelocity.magnitude() != 0) { debug("CCollisionCylinder::Restore(): Restoring"); center = _safetyPoint; _velocity.x = 0; _velocity.y = 0; _velocity.z = 0; } } else { warning("CCollisionCylinder::Restore(): No save point."); } } int CCollisionCylinder::getEquidistantPoint(U32FltPoint2D inPoint1, float distance1, U32FltPoint2D inPoint2, float distance2, U32FltPoint2D *outPoint1, U32FltPoint2D *outPoint2) { double distDiff = (distance2 * distance2) - (distance1 * distance1); double xDiff = (inPoint1.x * inPoint1.x) - (inPoint2.x * inPoint2.x); double yDiff = (inPoint1.y * inPoint1.y) - (inPoint2.y * inPoint2.y); double k1 = 0; double k2 = 0; if (xDiff != 0) { k1 = (distDiff + xDiff + yDiff) / (2 * (inPoint1.x - inPoint2.x)); k2 = (inPoint2.y - inPoint1.y) / (inPoint1.x - inPoint2.x); } double a = (k2 * k2) + 1; double b = (2 * k1 * k2) - (2 * k2 * inPoint1.x) - (2 * inPoint1.y); double c = (inPoint1.x * inPoint1.x) + (inPoint1.y * inPoint1.y) + (k1 * k1) - (2 * k1 * inPoint1.x) - (distance1 * distance1); if (((b * b) < (4 * a * c)) || (a == 0)) { return 0; } else { outPoint1->y = (-b + sqrt(b * b - 4 * a * c)) / (2 * a); outPoint2->y = (-b - sqrt(b * b - 4 * a * c)) / (2 * a); outPoint1->x = k1 + (k2 * outPoint1->y); outPoint2->x = k1 + (k2 * outPoint2->y); if (*outPoint1 == *outPoint2) { return 1; } else { return 2; } } } } // End of namespace Scumm