/* 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/basketball.h"
#include "scumm/he/basketball/court.h"
#include "scumm/he/basketball/obstacle_avoidance.h"
#include "common/array.h"
#include "common/stack.h"
#include "common/queue.h"
#include "common/std/set.h"
namespace Scumm {
float Basketball::getAvoidanceDistance(const U32Circle &playerMarker, const CCollisionPlayer &obstacle) {
// Figure out how close we want to buzz the obstacle...
float passByDistance = (playerMarker.radius + obstacle.radius + OBSTACLE_AVOIDANCE_DISTANCE);
// Figure out how close we are to the obstacle...
float currentDistance = (playerMarker.center - obstacle.center).magnitude();
// Use the smaller of the two...
float avoidanceDistance = MIN(currentDistance, passByDistance);
return avoidanceDistance;
}
CCollisionPlayer *Basketball::detectObstacle(const U32Circle &playerMarker,
int playerID,
const U32FltPoint2D &targetLocation,
bool targetIsObstacle,
U32FltPoint2D *intersection,
CBBallCourt *court) {
// Create a ray whose origin is at the player's center, and whose direction
// is the player's target vector...
U32Ray2D poRay;
poRay._origin = playerMarker.center;
poRay._direction = (targetLocation - poRay._origin);
// Cycle through all potential obstacles and see which obstacle is closest...
CCollisionPlayer *finalObstacle = nullptr;
float minDistance = (float)0x7FFFFFFF;
for (int i = 0; i <= LAST_PLAYER; ++i) {
// Get a pointer to the current obstacle...
CCollisionPlayer *currentObstacle = court->getPlayerPtr(i);
// Make sure we are not checking ourselves or a bench player...
if ((currentObstacle->_objectID != playerID) &&
(currentObstacle->_playerIsInGame)) {
U32Circle obstacleMarker;
obstacleMarker.center = currentObstacle->center;
obstacleMarker.radius = getAvoidanceDistance(playerMarker, *currentObstacle);
if (poRay.nearIntersection(obstacleMarker, intersection)) {
float obstacleDist = (*intersection - playerMarker.center).magnitude();
float alertDistance = MIN(AVOIDANCE_LOOK_AHEAD_DISTANCE, poRay._direction.magnitude());
// See if this obstacle is in our way...
if (obstacleDist < alertDistance) {
// See if the target is within the obstacle...
float otDist = (targetLocation - currentObstacle->center).magnitude();
if ((otDist > obstacleMarker.radius) || targetIsObstacle) {
// See if this obstacle is the closest one yet...
if (obstacleDist < minDistance) {
finalObstacle = currentObstacle;
minDistance = obstacleDist;
}
}
}
}
}
}
return finalObstacle;
}
bool Basketball::avoidObstacle(const U32Circle &playerMarker, const U32FltPoint2D &targetLocation, const CCollisionPlayer &obstacle, ERevDirection whichDirection, U32FltPoint2D *newTarget) {
double sinTheta;
double theta;
// Get a vector from the player center to the obstacle center...
U32FltVector2D poVector = obstacle.center - playerMarker.center;
// Get a vector from the target center to the obstacle center...
U32FltVector2D toVector = obstacle.center - targetLocation;
// Figure out how close we want to buzz the obstacle...
float avoidanceDistance = getAvoidanceDistance(playerMarker, obstacle);
avoidanceDistance += AVOIDANCE_SAFETY_DISTANCE;
// Figure out if we are avoinding the obstacle in the clockwise or
// counter-clockwise direction...
if (whichDirection == kNone) {
whichDirection = getAvoidanceDirection(playerMarker, targetLocation, obstacle);
}
// Figure out how many radians of roatation away from the center of the
// obstacle (using our center as the origin) we need to aim for in order to
// perfectly buzz the obstacle...
if (poVector.magnitude() == 0) {
sinTheta = 0;
} else {
sinTheta = avoidanceDistance / poVector.magnitude();
if ((sinTheta < -MAX_AVOIDANCE_ANGLE_SIN) || (MAX_AVOIDANCE_ANGLE_SIN < sinTheta)) {
sinTheta = MAX_AVOIDANCE_ANGLE_SIN;
}
}
theta = asin(sinTheta);
// Create a vector that points in the direction we must now travel...
poVector.rotate(whichDirection, theta);
// This vector will take us as close as we want to go to the obstacle, but
// we don't know how far in that direction we need to travel. To get to the,
// target we need to buzz the obstacle once, continue on a little bit, then
// turn and buzz the obstacle again. Right now we are going to calculate
// that second buzz path...
// We are caluclating from the target to the obstacle, so swap the direction...
whichDirection = (whichDirection == kClockwise) ? kCounterClockwise : kClockwise;
if (toVector.magnitude() == 0) {
sinTheta = 0;
} else {
sinTheta = avoidanceDistance / toVector.magnitude();
if (sinTheta < -MAX_AVOIDANCE_ANGLE_SIN) {
sinTheta = -MAX_AVOIDANCE_ANGLE_SIN;
} else if (sinTheta > MAX_AVOIDANCE_ANGLE_SIN) {
sinTheta = MAX_AVOIDANCE_ANGLE_SIN;
}
}
theta = asin(sinTheta);
// Create a vector that points in the direction we must travel after our
// initial pass-by of the target...
toVector.rotate(whichDirection, theta);
// Where the player and target vector's meet is where we should aim for...
U32Ray2D poRay;
poRay._origin = playerMarker.center;
poRay._direction = poVector;
U32Ray2D toRay;
toRay._origin = targetLocation;
toRay._direction = toVector;
return poRay.intersection(toRay, newTarget);
}
ERevDirection Basketball::getAvoidanceDirection(const U32Circle &playerMarker, const U32FltPoint2D &targetLocation, const CCollisionPlayer &obstacle) {
U32FltVector3D obstacleVector = obstacle.center - playerMarker.center;
U32FltVector3D targetVector = targetLocation - playerMarker.center;
U32FltVector3D crossVector = obstacleVector.cross(targetVector);
return (crossVector.z > 0) ? kCounterClockwise : kClockwise;
}
bool Basketball::getPathDistance(U32Circle *playerMarker, int playerID, Common::Stack *targetStack, ERevDirection lastTurn, float *pathDistance, Common::Queue *wayPointQueue, Std::set *obstacleSet, CBBallCourt *court) {
U32FltPoint2D intersection;
// See if we're going for the final target...
bool shootingForFinalTarget = (targetStack->size() == 1);
U32FltPoint2D ¤tTarget = targetStack->top();
// See if there is an obstacle between us and the target...
CCollisionPlayer *obstacle = detectObstacle(*playerMarker, playerID, currentTarget, !shootingForFinalTarget, &intersection, court);
if (obstacle) {
// Make sure we haven't run into this obstacle already...
Std::set::const_iterator obstacleIt = obstacleSet->find(obstacle->_objectID);
if (obstacleIt != obstacleSet->end()) {
if (targetStack->size() != 1) {
targetStack->pop();
}
return false;
}
// Recursively call getPathDistance for both the left and right paths
// around the current obstacle...
ERevDirection turnDirection = getBestPath(*playerMarker, playerID, targetStack,
obstacle, lastTurn, pathDistance,
wayPointQueue, obstacleSet, court);
if (turnDirection == kNone) {
if (targetStack->size() != 1) {
targetStack->pop();
}
return false;
} else {
return true;
}
} else {
// Get the distance between the player and the target...
float targetDistance = (currentTarget - playerMarker->center).magnitude();
// Add that distance to the current total for this path...
*pathDistance += targetDistance;
// Advance the player to the target...
playerMarker->center = currentTarget;
// See if this gets us to the final destination...
if (shootingForFinalTarget) {
return true;
} else {
wayPointQueue->push(currentTarget);
targetStack->pop();
// Keep on going for the final target...
return getPathDistance(playerMarker, playerID, targetStack,
kNone, pathDistance, wayPointQueue,
obstacleSet, court);
}
}
}
void Basketball::pushTargetOutOfObstacle(const U32Circle &playerMarker, const CCollisionPlayer &obstacle, Common::Stack *targetStack) {
// Get the effective radius of the obstacle...
float avoidanceDistance = getAvoidanceDistance(playerMarker, obstacle);
// See if the target is within that radius...
if ((targetStack->top() - obstacle.center).magnitude() < avoidanceDistance) {
// Make sure this isn't the final target...
if (targetStack->size() == 1) {
warning("Basketball::pushTargetOutOfObstacle(): Should not be calling this function on the final target");
}
// Make a ray from the player to the target...
U32Ray2D playerRay;
playerRay._origin = playerMarker.center;
playerRay._direction = targetStack->top() - playerMarker.center;
// Make a circle around the obstacle...
U32Circle obstacleCircle;
obstacleCircle.center = obstacle.center;
obstacleCircle.radius = avoidanceDistance;
// Find the farthest intersection of the ray and the circle...
U32FltPoint2D newTarget;
if (playerRay.farIntersection(obstacleCircle, &newTarget)) {
targetStack->pop();
targetStack->push(newTarget);
} else {
warning("Basketball::pushTargetOutOfObstacle(): Unable to intersect the player ray with the obstacle circle");
}
}
}
ERevDirection Basketball::getBestPath(const U32Circle &playerMarker, int playerID, Common::Stack *targetStack, CCollisionPlayer *obstacle,
ERevDirection lastTurn, float *distance, Common::Queue *wayPointQueue, Std::set *obstacleSet, CBBallCourt *court) {
U32Circle tempLocation;
float leftDistance = 0;
float rightDistance = 0;
bool leftPath = false;
bool rightPath = false;
Common::Queue leftWayPointQueue;
Common::Queue rightWayPointQueue;
// Add this obstacle to the obstacle set...
obstacleSet->insert(obstacle->_objectID);
// Clear out any unnecessary interim targets...
while (targetStack->size() != 1) {
targetStack->pop();
}
// Get the avoidance path to the left...
if (kClockwise != lastTurn) {
U32FltPoint2D leftTarget;
if (avoidObstacle(playerMarker, targetStack->top(), *obstacle, kCounterClockwise, &leftTarget)) {
tempLocation = playerMarker;
targetStack->push(leftTarget);
leftWayPointQueue = *wayPointQueue;
leftPath = getPathDistance(&tempLocation, playerID, targetStack,
kCounterClockwise, &leftDistance, &leftWayPointQueue,
obstacleSet, court);
}
}
// Get the avoidance path to the right...
if (kCounterClockwise != lastTurn) {
U32FltPoint2D rightTarget;
if (avoidObstacle(playerMarker, targetStack->top(),
*obstacle, kClockwise, &rightTarget)) {
tempLocation = playerMarker;
targetStack->push(rightTarget);
rightWayPointQueue = *wayPointQueue;
rightPath = getPathDistance(&tempLocation, playerID, targetStack,
kClockwise, &rightDistance, &rightWayPointQueue,
obstacleSet, court);
}
}
// See which path is better
if (leftPath && rightPath) {
*distance = MIN(leftDistance, rightDistance);
if (*distance == leftDistance) {
*wayPointQueue = Common::move(leftWayPointQueue);
return kCounterClockwise;
} else {
*wayPointQueue = Common::move(rightWayPointQueue);
return kClockwise;
}
} else if (leftPath && !rightPath) {
*distance = leftDistance;
*wayPointQueue = Common::move(leftWayPointQueue);
return kCounterClockwise;
} else if (!leftPath && rightPath) {
*distance = rightDistance;
*wayPointQueue = Common::move(rightWayPointQueue);
return kClockwise;
} else {
*distance = 0;
return kNone;
}
}
int LogicHEBasketball::u32_userGetAvoidancePath(int playerID, const U32FltPoint2D &targetLocation, EAvoidanceType type) {
U32FltPoint2D newTarget;
U32FltPoint2D intersection;
// Get a pointer to the source player...
CCollisionPlayer *player = _vm->_basketball->_court->getPlayerPtr(playerID);
// Make sure the player isn't already at its target...
if (player->center == targetLocation) {
newTarget = targetLocation;
} else {
// Extract the player's current location...
U32Circle playerMarker;
playerMarker.center = player->center;
playerMarker.radius = player->radius;
// See if an obstacle was found...
CCollisionPlayer *obstacle = _vm->_basketball->detectObstacle(playerMarker, player->_objectID,
targetLocation, false, &intersection,
_vm->_basketball->_court);
if (obstacle) {
if (type == kMultipleObject) {
Common::Queue wayPointQueue;
Common::Stack targetStack;
Std::set obstacleSet;
targetStack.push(targetLocation);
float pathDistance;
ERevDirection turnDirection = _vm->_basketball->getBestPath(playerMarker, player->_objectID, &targetStack,
obstacle, kNone, &pathDistance, &wayPointQueue, &obstacleSet,
_vm->_basketball->_court);
targetStack.pop();
if (!targetStack.empty()) {
warning("LogicHEBasketball::u32_userGetAvoidancePath(): It doesn't look like we calculated things out to the final target.");
}
if (wayPointQueue.empty()) {
assert(turnDirection == kNone);
// We were unable to find a valid path to the target,
// so treat this like a single obstacle...
if (!_vm->_basketball->avoidObstacle(playerMarker, targetLocation, *obstacle, kNone, &newTarget)) {
warning("LogicHEBasketball::u32_userGetAvoidancePath(): Unable to go around the primary obstacle");
newTarget = targetLocation;
}
} else {
newTarget = wayPointQueue.front();
}
} else {
if (!_vm->_basketball->avoidObstacle(playerMarker, targetLocation, *obstacle, kNone, &newTarget)) {
warning("LogicHEBasketball::u32_userGetAvoidancePath(): Unable to go around the primary obstacle");
newTarget = targetLocation;
}
}
} else {
newTarget = targetLocation;
}
}
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(newTarget.x));
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(newTarget.y));
return 1;
}
} // End of namespace Scumm