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

255 lines
12 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/logic_he.h"
#include "scumm/he/basketball/geo_translations.h"
namespace Scumm {
int LogicHEBasketball::u32_userInitScreenTranslations() {
// Find the angle between the left and bottom baseline in the court image...
_courtAngle = atan(TRANSLATED_MAX_Y / (double)TRANSLATED_MAX_START_X);
// The relationship between the location in the game world and the location in pixels
// from the bottom of the court can be described by the parametric equation:
//
// y = [(x-c)^(1/2) - c^(1/2)] / a^(1/2)
//
// Here, x is worldPoint.y and y is pixelsFromBottom. This is where we calculate the constant
// coefficients a, b, and c.
_yTranslationA = (float)((((MAX_WORLD_Y / 2.0) * TRANSLATED_MAX_Y) - (MAX_WORLD_Y * (float)TRANSLATED_MID_Y)) / ((TRANSLATED_MID_Y * TRANSLATED_MID_Y * (float)TRANSLATED_MAX_Y) - (TRANSLATED_MAX_Y * TRANSLATED_MAX_Y * (float)TRANSLATED_MID_Y)));
assert(_yTranslationA != 0);
_yTranslationB = (MAX_WORLD_Y / (float)TRANSLATED_MAX_Y) - (_yTranslationA * TRANSLATED_MAX_Y);
_yTranslationC = (_yTranslationB * _yTranslationB) / (4.0 * _yTranslationA);
assert(_yTranslationC != 0);
// The relationship between the location in pixels from the bottom of the court and
// the location in world points can be described by the parametric equation:
//
// y = ax^2 + bx +c
//
// Here, x is pixelsFromBottom and y is worldPoint.y. This is where we calculate the constant
// coefficients a, b, and c.
_yRevTranslationA = (((MAX_WORLD_Y / (float)2) * TRANSLATED_MAX_Y) - (MAX_WORLD_Y * (float)TRANSLATED_MID_Y)) / ((TRANSLATED_MID_Y * TRANSLATED_MID_Y * (float)TRANSLATED_MAX_Y) - (TRANSLATED_MAX_Y * TRANSLATED_MAX_Y * (float)TRANSLATED_MID_Y));
_yRevTranslationB = (MAX_WORLD_Y / (float)TRANSLATED_MAX_Y) - (_yRevTranslationA * TRANSLATED_MAX_Y);
_yRevTranslationC = 0;
// As you move up the screen, the number of world points per screen pixel increases
// parametrically. Vice versa for moving down the screen. It may be desirable to
// have a top and bottom cutoff point. So there will be a point above the court where
// the point to pixel ratio stops increasing and a point below the court where the point
// to pixel ratio stops decreasing. Here, the corresponding world points are calculated.
_topScalingPointCutoff = _vm->_basketball->u32FloatToInt(
_yRevTranslationA * TOP_SCALING_PIXEL_CUTOFF * TOP_SCALING_PIXEL_CUTOFF +
_yRevTranslationB * TOP_SCALING_PIXEL_CUTOFF +
_yRevTranslationC);
_bottomScalingPointCutoff = _vm->_basketball->u32FloatToInt(
_yRevTranslationA * BOTTOM_SCALING_PIXEL_CUTOFF * BOTTOM_SCALING_PIXEL_CUTOFF +
_yRevTranslationB * BOTTOM_SCALING_PIXEL_CUTOFF +
_yRevTranslationC);
assert(_topScalingPointCutoff >= MAX_WORLD_Y);
assert(_bottomScalingPointCutoff <= 0);
return 1;
}
static U32FltPoint2D worldToScreenTranslation(const U32FltPoint3D &worldPoint, LogicHEBasketball *logic) {
U32FltPoint2D screenPoint; // The point on the screen that corresponds to worldPoint
float courtWidth; // The width of the court in pixels at the at the current y location
float xOffset; // The number of pixels from the left side of the screen to the court at the current y location
float pixelsFromBottom;
float zToYOffset; // Given the current world z coordinate, how many pixels in the y direction should the object be moved
float a, c; // The constant coefficients for the parametric equation which describes the relation between world y coordinates and screen y coordinates
float slope; // The derivative of the above mentioned equation
assert(MAX_WORLD_X != 0);
assert(MAX_WORLD_Y != 0);
assert(TRANSLATED_MAX_START_X != 0);
// Let's find y...
a = logic->_yTranslationA;
c = logic->_yTranslationC;
// Normally, the game world coordinates compress as you move further up the screen.
// This adds to the illusion of depth perception. Sometimes it may be desirable to stop
// this compression after a certain point on the screen. This 'if block' handles that
// case...
if (worldPoint.y < logic->_bottomScalingPointCutoff) {
slope = 1 / (2 * sqrt(a * logic->_bottomScalingPointCutoff + a * c));
pixelsFromBottom = slope * (worldPoint.y - logic->_bottomScalingPointCutoff) + BOTTOM_SCALING_PIXEL_CUTOFF;
} else if (worldPoint.y < logic->_topScalingPointCutoff) {
slope = 1 / (2 * sqrt(a * worldPoint.y + a * c));
pixelsFromBottom = (sqrt(worldPoint.y + c) - sqrt(c)) / sqrt(a);
} else {
slope = 1 / (2 * sqrt(a * logic->_topScalingPointCutoff + a * c));
pixelsFromBottom = slope * (worldPoint.y - logic->_topScalingPointCutoff) + TOP_SCALING_PIXEL_CUTOFF;
}
screenPoint.y = BB_SCREEN_SIZE_Y - COURT_Y_OFFSET - pixelsFromBottom;
// Let's find x...
if (pixelsFromBottom < BOTTOM_SCALING_PIXEL_CUTOFF) {
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (BOTTOM_SCALING_PIXEL_CUTOFF / tan(logic->_courtAngle)));
xOffset = (tan((BBALL_M_PI / 2.0) - logic->_courtAngle) * BOTTOM_SCALING_PIXEL_CUTOFF) + COURT_X_OFFSET;
} else if (pixelsFromBottom < TOP_SCALING_PIXEL_CUTOFF) {
courtWidth = (TRANSLATED_NEAR_MAX_X - (2.0 * (pixelsFromBottom / tan(logic->_courtAngle))));
xOffset = (tan((BBALL_M_PI / 2.0) - logic->_courtAngle) * pixelsFromBottom) + COURT_X_OFFSET;
} else {
// Find the width of the court in pixels at the current y coordinate...
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (TOP_SCALING_PIXEL_CUTOFF / tan(logic->_courtAngle)));
// Find the number of pixels beetween the left side of the screen and the beginning of the court at the current y coordinate...
xOffset = tan(((BBALL_M_PI / 2.0) - logic->_courtAngle) * TOP_SCALING_PIXEL_CUTOFF) + COURT_X_OFFSET;
}
// Find the screen x based on the world x and y...
screenPoint.x = (worldPoint.x * courtWidth / MAX_WORLD_X) + xOffset;
// Now factor in world z to the screen y coordinate...
zToYOffset = (courtWidth / MAX_WORLD_X) * worldPoint.z;
screenPoint.y -= zToYOffset;
return screenPoint;
}
int LogicHEBasketball::u32_userWorldToScreenTranslation(const U32FltPoint3D &worldPoint) {
U32FltPoint2D screenPoint = worldToScreenTranslation(worldPoint, this);
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(screenPoint.x));
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(screenPoint.y));
return 1;
}
int LogicHEBasketball::u32_userScreenToWorldTranslation(const U32FltPoint2D &screenPoint) {
U32FltPoint2D worldPoint; // The point in the game world that corresponds to screenPoint
float courtWidth; // The width of the court in pixels at the at the current y location
float xOffset; // The number of pixels from the left side of the screen to the court at the current y location
float pixelsFromBottom;
float a, b, c; // The constant coefficients for the parametric equation which describes the relation between screen y coordinates and world y coordinates
float slope; // The derivative of the above mentioned equation
assert(TRANSLATED_MAX_START_X != 0);
assert(TRANSLATED_NEAR_MAX_X != 0);
assert(TRANSLATED_FAR_MAX_X != 0);
assert(TRANSLATED_MAX_Y != 0);
// Let's find y...
a = _yRevTranslationA;
b = _yRevTranslationB;
c = _yRevTranslationC;
pixelsFromBottom = BB_SCREEN_SIZE_Y - COURT_Y_OFFSET - screenPoint.y;
// Normally, the game world coordinates compress as you move further up the screen.
// This adds to the illusion of depth perception. Sometimes it may be desirable to stop
// this compression after a certain point on the screen. This 'if block' handles that
// case...
if (pixelsFromBottom < BOTTOM_SCALING_PIXEL_CUTOFF) {
slope = (2 * a * BOTTOM_SCALING_PIXEL_CUTOFF + b);
worldPoint.y = slope * (pixelsFromBottom - BOTTOM_SCALING_PIXEL_CUTOFF) + _bottomScalingPointCutoff;
} else if (pixelsFromBottom < TOP_SCALING_PIXEL_CUTOFF) {
worldPoint.y = a * pixelsFromBottom * pixelsFromBottom + b * pixelsFromBottom + c;
} else {
slope = (2 * a * TOP_SCALING_PIXEL_CUTOFF + b);
worldPoint.y = slope * (pixelsFromBottom - TOP_SCALING_PIXEL_CUTOFF) + _topScalingPointCutoff;
}
// -Let's find x...
if (pixelsFromBottom < BOTTOM_SCALING_PIXEL_CUTOFF) {
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (BOTTOM_SCALING_PIXEL_CUTOFF / tan(_courtAngle)));
xOffset = (tan((BBALL_M_PI / 2.0) - _courtAngle) * BOTTOM_SCALING_PIXEL_CUTOFF) + COURT_X_OFFSET;
} else if (pixelsFromBottom < TOP_SCALING_PIXEL_CUTOFF) {
// Find the width of the court in pixels at the current y coordinate...
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (pixelsFromBottom / tan(_courtAngle)));
// Find the number of pixels beetween the left side of the screen and the beginning of the court at the current y coordinate...
xOffset = (tan((BBALL_M_PI / 2.0) - _courtAngle) * pixelsFromBottom) + COURT_X_OFFSET;
} else {
// Find the width of the court in pixels at the current y coordinate...
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (TOP_SCALING_PIXEL_CUTOFF / tan(_courtAngle)));
// Find the number of pixels beetween the left side of the screen and the beginning of the court at the current y coordinate...
xOffset = (tan((BBALL_M_PI / 2.0) - _courtAngle) * TOP_SCALING_PIXEL_CUTOFF) + COURT_X_OFFSET;
}
// Find the world x based on the screen x and y...
assert(courtWidth != 0);
worldPoint.x = (screenPoint.x - xOffset) * MAX_WORLD_X / courtWidth;
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(worldPoint.x));
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(worldPoint.y));
return 1;
}
int LogicHEBasketball::u32_userGetCourtDimensions() {
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(MAX_WORLD_X));
writeScummVar(_vm1->VAR_U32_USER_VAR_B, _vm->_basketball->u32FloatToInt(MAX_WORLD_Y));
writeScummVar(_vm1->VAR_U32_USER_VAR_C, _vm->_basketball->u32FloatToInt(BASKET_X));
writeScummVar(_vm1->VAR_U32_USER_VAR_D, _vm->_basketball->u32FloatToInt(BASKET_Y));
writeScummVar(_vm1->VAR_U32_USER_VAR_E, _vm->_basketball->u32FloatToInt(BASKET_Z));
return 1;
}
int LogicHEBasketball::u32_userComputePointsForPixels(int pixels, int screenYPos) {
float points;
float pixelsFromBottom;
float courtWidth;
float courtAngle;
courtAngle = atan(TRANSLATED_MAX_Y / (double)TRANSLATED_MAX_START_X);
pixelsFromBottom = BB_SCREEN_SIZE_Y - COURT_Y_OFFSET - screenYPos;
if (pixelsFromBottom < 0) {
courtWidth = TRANSLATED_NEAR_MAX_X;
// FIXME: Remove duplicated condition branch?
#if 0
} else if (pixelsFromBottom < TRANSLATED_MAX_Y) {
// Find the width of the court in pixels at the current y coordinate...
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (pixelsFromBottom / tan(courtAngle)));
#endif
} else {
// Find the width of the court in pixels at the current y coordinate...
courtWidth = TRANSLATED_NEAR_MAX_X - (2.0 * (pixelsFromBottom / tan(courtAngle)));
}
points = (MAX_WORLD_X / courtWidth) * pixels;
writeScummVar(_vm1->VAR_U32_USER_VAR_A, _vm->_basketball->u32FloatToInt(points));
return 1;
}
} // End of namespace Scumm