Files
scummvm-cursorfix/engines/scumm/he/basketball/collision/bball_collision_cylinder.cpp
2026-02-02 04:50:13 +01:00

1131 lines
36 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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<const CCollisionBox *>(&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<const CCollisionCylinder *>(&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<const ICollisionObject *>(&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