SpaceCadetPinball/SpaceCadetPinball/maths.cpp

385 lines
10 KiB
C++

#include "pch.h"
#include "maths.h"
#include "TBall.h"
#include "TFlipperEdge.h"
// Performs AABB merge, creating rect that is just large enough to contain both source rects.
void maths::enclosing_box(const rectangle_type& rect1, const rectangle_type& rect2, rectangle_type& dstRect)
{
auto xPos = rect1.XPosition, width = rect1.Width;
if (rect2.XPosition < rect1.XPosition)
{
xPos = rect2.XPosition;
width += rect1.XPosition - rect2.XPosition;
}
auto yPos = rect1.YPosition, height = rect1.Height;
if (rect2.YPosition < rect1.YPosition)
{
yPos = rect2.YPosition;
height += rect1.YPosition - rect2.YPosition;
}
auto xEnd2 = rect2.XPosition + rect2.Width;
if (xEnd2 > xPos + width)
width = xEnd2 - xPos;
auto yEnd2 = rect2.YPosition + rect2.Height;
if (yEnd2 > yPos + height)
height = yEnd2 - yPos;
dstRect.XPosition = xPos;
dstRect.YPosition = yPos;
dstRect.Width = width;
dstRect.Height = height;
}
// Creates rect that represents an intersection of rect1 and rect2.
// Return true when intersection exists.
bool maths::rectangle_clip(const rectangle_type& rect1, const rectangle_type& rect2, rectangle_type* dstRect)
{
auto xEnd2 = rect2.XPosition + rect2.Width;
if (rect2.XPosition >= rect1.XPosition + rect1.Width || rect1.XPosition >= xEnd2)
return 0;
auto yEnd2 = rect2.YPosition + rect2.Height;
if (rect2.YPosition >= rect1.YPosition + rect1.Height || rect1.YPosition >= yEnd2)
return 0;
auto xPos = rect1.XPosition, width = rect1.Width;
if (rect1.XPosition < rect2.XPosition)
{
xPos = rect2.XPosition;
width += rect1.XPosition - rect2.XPosition;
}
auto yPos = rect1.YPosition, height = rect1.Height;
if (rect1.YPosition < rect2.YPosition)
{
yPos = rect2.YPosition;
height += rect1.YPosition - rect2.YPosition;
}
if (xPos + width > xEnd2)
width = xEnd2 - xPos;
if (yPos + height > yEnd2)
height = yEnd2 - yPos;
if (width == 0 || height == 0)
return false;
if (dstRect)
{
dstRect->XPosition = xPos;
dstRect->YPosition = yPos;
dstRect->Width = width;
dstRect->Height = height;
}
return true;
}
// Returns the distance from ray origin to the first ray-circle intersection point.
float maths::ray_intersect_circle(const ray_type& ray, const circle_type& circle)
{
// O - ray origin
// D - ray direction
// C - circle center
// R - circle radius
// L, C - O, vector between O and C
auto L = vector_sub(circle.Center, ray.Origin);
// Tca, L dot D, projection of L on D
float Tca = DotProduct(L, ray.Direction);
if (Tca < 0.0f) // No intersection if Tca is negative
return 1000000000.0f;
// L dot L, distance from ray origin to circle center
float LMagSq = DotProduct(L, L);
// Thc^2 = rad^2 - d^2; d = sqrt(L dot L - Tca * Tca)
float ThcSq = circle.RadiusSq - LMagSq + Tca * Tca;
// T0 = Tca - Thc, distance from origin to first intersection
// If ray origin is inside of the circle, then T0 is negative
if (LMagSq < circle.RadiusSq)
return Tca - sqrt(ThcSq);
// No intersection if ThcSq is negative, that is if d > rad
if (ThcSq < 0.0f)
return 1000000000.0f;
// T0 should be positive and less that max ray distance
float T0 = Tca - sqrt(ThcSq);
if (T0 < 0.0f || T0 > ray.MaxDistance)
return 1000000000.0f;
return T0;
}
float maths::normalize_2d(vector2& vec)
{
float mag = sqrt(vec.X * vec.X + vec.Y * vec.Y);
if (mag != 0.0f)
{
vec.X /= mag;
vec.Y /= mag;
}
return mag;
}
void maths::line_init(line_type& line, float x0, float y0, float x1, float y1)
{
line.Origin = { x0, y0 };
line.End = { x1, y1 };
line.Direction.X = x1 - x0;
line.Direction.Y = y1 - y0;
normalize_2d(line.Direction);
// Clockwise perpendicular to the line direction vector
line.PerpendicularC = { line.Direction.Y, -line.Direction.X };
auto lineStart = x0, lineEnd = x1;
if (std::abs(line.Direction.X) < 0.000000001f)
{
line.Direction.X = 0.0;
lineStart = y0;
lineEnd = y1;
}
line.MinCoord = std::min(lineStart, lineEnd);
line.MaxCoord = std::max(lineStart, lineEnd);
}
// Returns the distance from ray origin to the ray-line segment intersection point.
// Stores ray-line intersection point in line.RayIntersect
float maths::ray_intersect_line(const ray_type& ray, line_type& line)
{
// V1 vector between ray origin and line origin
// V2 ray direction
// V3 line perpendicular clockwise
auto v1 = vector_sub(ray.Origin, line.Origin);
auto v2 = line.Direction;
auto v3 = vector2{ -ray.Direction.Y, ray.Direction.X };
// Project line on ray perpendicular, no intersection if ray is pointing away from the line
auto v2DotV3 = DotProduct(v2, v3);
if (v2DotV3 < 0.0f)
{
// Distance to the intersect point: (V2 X V1) / (V2 dot V3)
auto distance = cross(v2, v1) / v2DotV3;
if (distance >= -ray.MinDistance && distance <= ray.MaxDistance)
{
line.RayIntersect.X = distance * ray.Direction.X + ray.Origin.X;
line.RayIntersect.Y = distance * ray.Direction.Y + ray.Origin.Y;
// Check if intersection point is inside line segment
auto testPoint = line.Direction.X != 0.0f ? line.RayIntersect.X : line.RayIntersect.Y;
if (testPoint >= line.MinCoord && testPoint <= line.MaxCoord)
{
return distance;
}
}
}
return 1000000000.0;
}
void maths::cross(const vector3& vec1, const vector3& vec2, vector3& dstVec)
{
dstVec.X = vec2.Z * vec1.Y - vec2.Y * vec1.Z;
dstVec.Y = vec2.X * vec1.Z - vec1.X * vec2.Z;
dstVec.Z = vec1.X * vec2.Y - vec2.X * vec1.Y;
}
float maths::cross(const vector2& vec1, const vector2& vec2)
{
return vec1.X * vec2.Y - vec1.Y * vec2.X;
}
float maths::magnitude(const vector3& vec)
{
float result;
auto magSq = vec.X * vec.X + vec.Y * vec.Y + vec.Z * vec.Z;
if (magSq == 0.0f)
result = 0.0;
else
result = sqrt(magSq);
return result;
}
void maths::vector_add(vector2& vec1Dst, const vector2& vec2)
{
vec1Dst.X += vec2.X;
vec1Dst.Y += vec2.Y;
}
vector2 maths::vector_sub(const vector2& vec1, const vector2& vec2)
{
return { vec1.X - vec2.X, vec1.Y - vec2.Y };
}
vector2 maths::vector_mul(const vector2& vec1, float val)
{
return { vec1.X * val, vec1.Y * val };
}
float maths::basic_collision(TBall* ball, vector2* nextPosition, vector2* direction, float elasticity, float smoothness,
float threshold, float boost)
{
ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y;
auto proj = -DotProduct(*direction, ball->Acceleration);
if (proj < 0)
{
proj = -proj;
}
else
{
float dx1 = proj * direction->X;
float dy1 = proj * direction->Y;
ball->Acceleration.X = (dx1 + ball->Acceleration.X) * smoothness + dx1 * elasticity;
ball->Acceleration.Y = (dy1 + ball->Acceleration.Y) * smoothness + dy1 * elasticity;
normalize_2d(ball->Acceleration);
}
float projSpeed = proj * ball->Speed;
float newSpeed = ball->Speed - (1.0f - elasticity) * projSpeed;
ball->Speed = newSpeed;
if (projSpeed >= threshold)
{
ball->Acceleration.X = newSpeed * ball->Acceleration.X + direction->X * boost;
ball->Acceleration.Y = newSpeed * ball->Acceleration.Y + direction->Y * boost;
ball->Speed = normalize_2d(ball->Acceleration);
}
return projSpeed;
}
float maths::Distance_Squared(const vector2& vec1, const vector2& vec2)
{
auto dx = vec1.X - vec2.X;
auto dy = vec1.Y - vec2.Y;
return dy * dy + dx * dx;
}
float maths::DotProduct(const vector2& vec1, const vector2& vec2)
{
return vec1.X * vec2.X + vec1.Y * vec2.Y;
}
float maths::Distance(const vector2& vec1, const vector2& vec2)
{
return sqrt(Distance_Squared(vec1, vec2));
}
void maths::SinCos(float angle, float& sinOut, float& cosOut)
{
sinOut = sin(angle);
cosOut = cos(angle);
}
void maths::RotatePt(vector2& point, float sin, float cos, const vector2& origin)
{
auto xOffset = point.X - origin.X;
auto yOffset = point.Y - origin.Y;
point.X = xOffset * cos - yOffset * sin + origin.X;
point.Y = xOffset * sin + yOffset * cos + origin.Y;
}
// Return the distance from ray1 origin to the intersection point with the closest flipper feature.
// Sets ray2 origin to intersection point, direction to collision direction
float maths::distance_to_flipper(const ray_type& ray1, ray_type& ray2)
{
auto distance = 1000000000.0f;
auto distanceType = FlipperIntersect::none;
auto newDistance = ray_intersect_line(ray1, TFlipperEdge::lineA);
if (newDistance < distance)
{
distance = newDistance;
distanceType = FlipperIntersect::lineA;
}
newDistance = ray_intersect_circle(ray1, TFlipperEdge::circlebase);
if (newDistance < distance)
{
distance = newDistance;
distanceType = FlipperIntersect::circlebase;
}
newDistance = ray_intersect_circle(ray1, TFlipperEdge::circleT1);
if (newDistance < distance)
{
distance = newDistance;
distanceType = FlipperIntersect::circleT1;
}
newDistance = ray_intersect_line(ray1, TFlipperEdge::lineB);
if (newDistance < distance)
{
distance = newDistance;
distanceType = FlipperIntersect::lineB;
}
switch (distanceType)
{
case FlipperIntersect::lineA:
ray2.Direction = TFlipperEdge::lineA.PerpendicularC;
ray2.Origin = TFlipperEdge::lineA.RayIntersect;
break;
case FlipperIntersect::lineB:
ray2.Direction = TFlipperEdge::lineB.PerpendicularC;
ray2.Origin = TFlipperEdge::lineB.RayIntersect;
break;
case FlipperIntersect::circlebase:
case FlipperIntersect::circleT1:
ray2.Origin.X = distance * ray1.Direction.X + ray1.Origin.X;
ray2.Origin.Y = distance * ray1.Direction.Y + ray1.Origin.Y;
ray2.Direction = vector_sub(ray2.Origin, distanceType == FlipperIntersect::circlebase ?
TFlipperEdge::circlebase.Center : TFlipperEdge::circleT1.Center);
normalize_2d(ray2.Direction);
break;
case FlipperIntersect::none:
default:
break;
}
return distance;
}
void maths::RotateVector(vector2& vec, float angle)
{
float s = sin(angle), c = cos(angle);
vec.X = c * vec.X - s * vec.Y;
vec.Y = s * vec.X + c * vec.Y;
/* Error in the original, should be:
* auto newX = c * vec.X - s * vec.Y;
* vec.Y = s * vec.X + c * vec.Y;
* vec.X = newX;
*/
// Original code rotates the point on a figure eight curve.
// Luckily, it is never used with angle always set to 0.
}
void maths::find_closest_edge(ramp_plane_type* planes, int planeCount, wall_point_type* wall, vector2& lineEnd,
vector2& lineStart)
{
float distance = 1000000000.0f;
for (auto index = 0; index < planeCount; index++)
{
auto& plane = planes[index];
vector2* pointOrder[4] = { &plane.V1, &plane.V2, &plane.V3, &plane.V1 };
for (auto pt = 0; pt < 3; pt++)
{
auto& point1 = *pointOrder[pt], point2 = *pointOrder[pt + 1];
auto newDistance = Distance(wall->Pt0, point1) + Distance(wall->Pt1, point2);
if (newDistance < distance)
{
distance = newDistance;
lineEnd = point1;
lineStart = point2;
}
}
}
}