FT collision part2: added most of the FT collision system.

Aka "World's most expensive flippers".
This is an aggregate of collision-related changes made during 3DPB->FT transition.
The most important part is in flipper collision - a shift from monolithic iterative solver in TFlipperEdge::EdgeCollision to a distributed non-iterative solver.
Both 3DPB and FT data sets use FT collision, keeping two collision systems does not make much sense.
From user perspective, FT/3DPB systems should not have any major differences.
This commit is contained in:
Muzychenko Andrey 2023-03-04 17:31:23 +03:00
parent 466c875f8a
commit ba470e8727
16 changed files with 489 additions and 396 deletions

View File

@ -344,10 +344,11 @@ void DebugOverlay::DrawEdge(TEdgeSegment* edge)
auto flip = dynamic_cast<TFlipperEdge*>(edge);
if (flip)
{
flip->set_control_points(pb::time_now);
if (flip->ControlPointDirtyFlag)
flip->set_control_points(flip->CurrentAngle);
DrawLineType(flip->lineA);
DrawLineType(flip->lineB);
DrawLineType(flip->LineA);
DrawLineType(flip->LineB);
DrawCicleType(flip->circlebase);
DrawCicleType(flip->circleT1);
}

View File

@ -29,6 +29,7 @@ TBall::TBall(TPinballTable* table) : TPinballComponent(table, -1, false)
Direction.X = 0.0;
Position.X = 0.0;
Position.Y = 0.0;
HasGroupFlag = false;
ListBitmap = new std::vector<SpriteData>();
@ -81,11 +82,19 @@ void TBall::Repaint()
void TBall::not_again(TEdgeSegment* edge)
{
if (EdgeCollisionCount < 5)
if (EdgeCollisionCount < 16)
{
Collisions[EdgeCollisionCount] = edge;
++EdgeCollisionCount;
}
else
{
for (int i = 0; i < 8; i++)
Collisions[i] = Collisions[i + 8];
Collisions[8] = edge;
EdgeCollisionCount = 9;
}
EdgeCollisionResetFlag = true;
}
bool TBall::already_hit(TEdgeSegment* edge)
@ -119,15 +128,15 @@ int TBall::Message(MessageCode code, float value)
return 0;
}
void TBall::throw_ball(TBall* ball, vector3* direction, float angleMult, float speedMult1, float speedMult2)
void TBall::throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2)
{
ball->CollisionComp = nullptr;
ball->Direction = *direction;
CollisionComp = nullptr;
Direction = *direction;
float rnd = RandFloat();
float angle = (1.0f - (rnd + rnd)) * angleMult;
maths::RotateVector(ball->Direction, angle);
maths::RotateVector(Direction, angle);
rnd = RandFloat();
ball->Speed = (1.0f - (rnd + rnd)) * (speedMult1 * speedMult2) + speedMult1;
Speed = (1.0f - (rnd + rnd)) * (speedMult1 * speedMult2) + speedMult1;
}
vector2 TBall::get_coordinates()
@ -138,5 +147,6 @@ vector2 TBall::get_coordinates()
void TBall::Disable()
{
ActiveFlag = false;
AsEdgeCollisionFlag = true;
SpriteSet(-1);
}

View File

@ -15,11 +15,10 @@ public :
int Message(MessageCode code, float value) override;
vector2 get_coordinates() override;
void Disable();
static void throw_ball(TBall* ball, vector3* direction, float angleMult, float speedMult1,
float speedMult2);
void throw_ball(vector3* direction, float angleMult, float speedMult1, float speedMult2);
vector3 Position{};
vector3 PrevPosition{};
vector3 Direction{};
float Speed;
float RayMaxDistance;
@ -28,10 +27,15 @@ public :
vector2 RampFieldForce{};
TCollisionComponent* CollisionComp;
int CollisionMask;
TEdgeSegment* Collisions[5]{};
TEdgeSegment* Collisions[16]{};
int EdgeCollisionCount;
bool EdgeCollisionResetFlag{};
vector3 CollisionOffset{};
int CollisionFlag;
float Offset;
bool HasGroupFlag;
int SomeCounter1 = 0;
int time_ticks1{}, time_ticks2{};
float VisualZArray[50]{};
bool AsEdgeCollisionFlag{};
};

View File

@ -6,6 +6,7 @@
#include "loader.h"
#include "pb.h"
#include "render.h"
#include "TBall.h"
#include "TFlipperEdge.h"
#include "timer.h"
#include "TPinballTable.h"
@ -84,15 +85,17 @@ int TFlipper::Message(MessageCode code, float value)
code = MessageCode::TFlipperRetract;
}
MessageField = FlipperEdge->SetMotion(code, value);
MessageField = FlipperEdge->SetMotion(code);
break;
case MessageCode::PlayerChanged:
case MessageCode::Reset:
if (MessageField)
{
FlipperEdge->CurrentAngle = 0;
FlipperEdge->set_control_points(0);
MessageField = 0;
FlipperEdge->SetMotion(MessageCode::Reset, value);
UpdateSprite(0);
FlipperEdge->SetMotion(MessageCode::Reset);
UpdateSprite();
}
break;
default: break;
@ -110,11 +113,11 @@ void TFlipper::Collision(TBall* ball, vector2* nextPosition, vector2* direction,
{
}
void TFlipper::UpdateSprite(float timeNow)
void TFlipper::UpdateSprite()
{
int bmpCountSub1 = ListBitmap->size() - 1;
auto newBmpIndex = static_cast<int>(floor(FlipperEdge->flipper_angle(timeNow) / FlipperEdge->AngleMax * bmpCountSub1 + 0.5f));
auto newBmpIndex = static_cast<int>(floor(FlipperEdge->CurrentAngle / FlipperEdge->AngleMax * bmpCountSub1 + 0.5f));
newBmpIndex = Clamp(newBmpIndex, 0, bmpCountSub1);
if (BmpIndex == newBmpIndex)
return;
@ -122,3 +125,87 @@ void TFlipper::UpdateSprite(float timeNow)
BmpIndex = newBmpIndex;
SpriteSet(BmpIndex);
}
int TFlipper::GetFlipperAngleDistance(float dt, float* dst) const
{
if (!MessageField)
return 0;
auto deltaAngle = FlipperEdge->flipper_angle_delta(dt);
auto distance = std::fabs(std::ceil(FlipperEdge->DistanceDiv * deltaAngle * FlipperEdge->InvT1Radius));
if (distance > 3.0f)
distance = 3.0f;
if (distance >= 2.0f)
{
*dst = deltaAngle / distance;
return static_cast<int>(distance);
}
*dst = deltaAngle;
return 1;
}
void TFlipper::FlipperCollision(float deltaAngle)
{
if (!MessageField)
return;
ray_type ray{}, rayDst{};
ray.MinDistance = 0.002f;
auto deltaAngleNeg = -deltaAngle;
bool collisionFlag = false;
for (auto ball : pb::MainTable->BallList)
{
if ((FlipperEdge->CollisionGroup & ball->CollisionMask) != 0 &&
FlipperEdge->YMax >= ball->Position.Y && FlipperEdge->YMin <= ball->Position.Y &&
FlipperEdge->XMax >= ball->Position.X && FlipperEdge->XMin <= ball->Position.X)
{
if (FlipperEdge->ControlPointDirtyFlag)
FlipperEdge->set_control_points(FlipperEdge->CurrentAngle);
ray.CollisionMask = ball->CollisionMask;
ray.Origin = ball->Position;
float sin, cos;
auto ballPosRot = ray.Origin;
maths::SinCos(deltaAngleNeg, sin, cos);
maths::RotatePt(ballPosRot, sin, cos, FlipperEdge->RotOrigin);
ray.Direction.X = ballPosRot.X - ray.Origin.X;
ray.Direction.Y = ballPosRot.Y - ray.Origin.Y;
ray.MaxDistance = maths::normalize_2d(ray.Direction);
auto distance = maths::distance_to_flipper(FlipperEdge, ray, rayDst);
if (distance < 1e9f)
{
FlipperEdge->NextBallPosition = ball->Position;
FlipperEdge->CollisionDirection = rayDst.Direction;
FlipperEdge->EdgeCollision(ball, distance);
collisionFlag = true;
}
}
}
if (collisionFlag)
{
auto angleAdvance = deltaAngle / (std::fabs(FlipperEdge->MoveSpeed) * 5.0f);
FlipperEdge->CurrentAngle -= angleAdvance;
FlipperEdge->AngleRemainder += std::fabs(angleAdvance);
if (FlipperEdge->AngleRemainder <= 0.0001f)
{
FlipperEdge->CurrentAngle = FlipperEdge->AngleDst;
FlipperEdge->FlipperFlag = MessageCode::TFlipperNull;
MessageField = 0;
}
FlipperEdge->ControlPointDirtyFlag = true;
}
else
{
FlipperEdge->CurrentAngle += deltaAngle;
FlipperEdge->AngleRemainder -= std::fabs(deltaAngle);
if (FlipperEdge->AngleRemainder <= 0.0001f)
{
FlipperEdge->CurrentAngle = FlipperEdge->AngleDst;
FlipperEdge->FlipperFlag = MessageCode::TFlipperNull;
MessageField = 0;
}
FlipperEdge->ControlPointDirtyFlag = true;
}
}

View File

@ -13,7 +13,9 @@ public:
void port_draw() override;
void Collision(TBall* ball, vector2* nextPosition, vector2* direction, float distance,
TEdgeSegment* edge) override;
void UpdateSprite(float timeNow);
void UpdateSprite();
int GetFlipperAngleDistance(float dt, float* dst) const;
void FlipperCollision(float deltaAngle);
int BmpIndex;
TFlipperEdge* FlipperEdge;

View File

@ -8,9 +8,11 @@
#include "TTableLayer.h"
TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table,
vector3* origin, vector3* vecT1, vector3* vecT2, float extendTime, float retractTime,
float collMult, float elasticity, float smoothness): TEdgeSegment(collComp, activeFlag, collisionGroup)
TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup,
TPinballTable* table,
vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed,
float collMult, float elasticity, float smoothness): TEdgeSegment(
collComp, activeFlag, collisionGroup)
{
vector3 crossProd{}, vecOriginT1{}, vecOriginT2{};
@ -52,22 +54,22 @@ TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsi
// 3DPB and FT have different formats for flipper speed:
// 3DPB: Time it takes for flipper to go from source to destination, in sec.
// FT: Flipper movement speed, in radians per sec.
if (pb::FullTiltMode)
if (!pb::FullTiltMode)
{
auto angleMax = std::abs(AngleMax);
retractTime = angleMax / retractTime;
extendTime = angleMax / extendTime;
retractSpeed = angleMax / retractSpeed;
extendSpeed = angleMax / extendSpeed;
}
ExtendTime = extendTime;
RetractTime = retractTime;
ExtendSpeed = extendSpeed;
RetractSpeed = retractSpeed;
const vector2 perpOriginT1Cc = { -vecOriginT1.Y , vecOriginT1.X };
const vector2 perpOriginT1Cc = {-vecOriginT1.Y, vecOriginT1.X};
A2Src.X = perpOriginT1Cc.X * CirclebaseRadius + origin->X;
A2Src.Y = perpOriginT1Cc.Y * CirclebaseRadius + origin->Y;
A1Src.X = perpOriginT1Cc.X * CircleT1Radius + vecT1->X;
A1Src.Y = perpOriginT1Cc.Y * CircleT1Radius + vecT1->Y;
const vector2 perpOriginT1C = { vecOriginT1.Y , -vecOriginT1.X };
const vector2 perpOriginT1C = {vecOriginT1.Y, -vecOriginT1.X};
B1Src.X = perpOriginT1C.X * CirclebaseRadius + origin->X;
B1Src.Y = perpOriginT1C.Y * CirclebaseRadius + origin->Y;
B2Src.X = perpOriginT1C.X * CircleT1Radius + vecT1->X;
@ -82,249 +84,94 @@ TFlipperEdge::TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsi
auto dx = vecT1->X - RotOrigin.X;
auto dy = vecT1->Y - RotOrigin.Y;
auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z;
DistanceDiv = distance1;
DistanceDivSq = distance1 * distance1;
InvT1Radius = 1.0f / CircleT1Radius * 1.5f;
float minMoveTime = std::min(ExtendTime, RetractTime);
auto distance = maths::Distance(*vecT1, *vecT2);
CollisionTimeAdvance = minMoveTime / (distance / CircleT1Radius + distance / CircleT1Radius);
EdgeCollisionFlag = 0;
InputTime = 0.0;
CollisionFlag1 = 0;
AngleStopTime = 0.0;
AngleAdvanceTime = 0.0;
if (AngleMax <= 0.0f)
{
ExtendSpeed = -ExtendSpeed;
}
else
{
RetractSpeed = -RetractSpeed;
}
set_control_points(CurrentAngle);
}
void TFlipperEdge::port_draw()
{
set_control_points(InputTime);
set_control_points(CurrentAngle);
}
float TFlipperEdge::FindCollisionDistance(ray_type* ray)
{
auto ogRay = ray;
ray_type dstRay{}, srcRay{};
ray_type dstRay{};
if (ControlPointDirtyFlag)
set_control_points(CurrentAngle);
auto distance = maths::distance_to_flipper(this, *ray, dstRay);
if (distance >= 1e9f)
return 1e9f;
if (ogRay->TimeNow > AngleStopTime)
{
FlipperFlag = MessageCode::TFlipperNull;
}
if (EdgeCollisionFlag == 0)
{
if (FlipperFlag == MessageCode::TFlipperNull)
{
CollisionFlag1 = 0;
CollisionFlag2 = 0;
set_control_points(ogRay->TimeNow);
auto ballInside = is_ball_inside(ogRay->Origin.X, ogRay->Origin.Y);
srcRay.MinDistance = ogRay->MinDistance;
if (ballInside == 0)
{
srcRay.Direction = ogRay->Direction;
srcRay.MaxDistance = ogRay->MaxDistance;
srcRay.Origin = ogRay->Origin;
auto distance = maths::distance_to_flipper(this, srcRay, dstRay);
if (distance == 0.0f)
{
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
}
else
{
NextBallPosition = dstRay.Origin;
}
CollisionDirection = dstRay.Direction;
return distance;
}
if (maths::Distance_Squared(ogRay->Origin, RotOrigin) >= CirclebaseRadiusMSq)
{
if (maths::Distance_Squared(ogRay->Origin, T1) >= CircleT1RadiusMSq)
{
srcRay.Direction.Y = lineB.PerpendicularC.Y;
srcRay.Direction.X = lineB.PerpendicularC.X;
if (ballInside == 4)
{
srcRay.Direction.Y = lineA.PerpendicularC.Y;
srcRay.Direction.X = lineA.PerpendicularC.X;
}
srcRay.Direction.X = -srcRay.Direction.X;
srcRay.Direction.Y = -srcRay.Direction.Y;
}
else
{
srcRay.Direction.X = T1.X - ogRay->Origin.X;
srcRay.Direction.Y = T1.Y - ogRay->Origin.Y;
maths::normalize_2d(srcRay.Direction);
}
}
else
{
srcRay.Direction.X = RotOrigin.X - ogRay->Origin.X;
srcRay.Direction.Y = RotOrigin.Y - ogRay->Origin.Y;
maths::normalize_2d(srcRay.Direction);
}
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
if (maths::distance_to_flipper(this, srcRay, dstRay) >= 1e+09f)
{
srcRay.Direction.X = RotOrigin.X - ogRay->Origin.X;
srcRay.Direction.Y = RotOrigin.Y - ogRay->Origin.Y;
maths::normalize_2d(srcRay.Direction);
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
if (maths::distance_to_flipper(this, srcRay, dstRay) >= 1e+09f)
{
return 1e+09;
}
}
NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
auto posX = ogRay->Origin.X;
auto posY = ogRay->Origin.Y;
auto posXAdvance = ogRay->Direction.X * CollisionTimeAdvance;
auto posYAdvance = ogRay->Direction.Y * CollisionTimeAdvance;
auto rayMaxDistance = ogRay->MaxDistance * CollisionTimeAdvance;
auto timeNow = ogRay->TimeNow;
auto stopTime = ogRay->TimeDelta + ogRay->TimeNow;
while (timeNow < stopTime)
{
set_control_points(timeNow);
auto ballInside = is_ball_inside(posX, posY);
if (ballInside != 0)
{
vector2* linePtr;
if (FlipperFlag == MessageCode::TFlipperExtend && ballInside != 5)
{
linePtr = &lineA.PerpendicularC;
srcRay.Direction.Y = lineA.PerpendicularC.Y;
srcRay.Direction.X = lineA.PerpendicularC.X;
}
else
{
if (FlipperFlag != MessageCode::TFlipperRetract || ballInside == 4)
{
CollisionFlag1 = 0;
CollisionFlag2 = 1;
srcRay.Direction.X = RotOrigin.X - posX;
srcRay.Direction.Y = RotOrigin.Y - posY;
maths::normalize_2d(srcRay.Direction);
srcRay.Origin.X = posX - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = posY - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
if (maths::distance_to_flipper(this, srcRay, dstRay) >= 1e+09f)
{
NextBallPosition.X = posX;
NextBallPosition.Y = posY;
CollisionDirection.X = -srcRay.Direction.X;
CollisionDirection.Y = -srcRay.Direction.Y;
return 0.0;
}
NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
linePtr = &lineB.PerpendicularC;
srcRay.Direction.Y = lineB.PerpendicularC.Y;
srcRay.Direction.X = lineB.PerpendicularC.X;
}
CollisionLinePerp = *linePtr;
CollisionFlag2 = 0;
CollisionFlag1 = 1;
srcRay.Direction.X = -srcRay.Direction.X;
srcRay.Direction.Y = -srcRay.Direction.Y;
srcRay.MinDistance = 0.002f;
srcRay.Origin.X = ogRay->Origin.X - srcRay.Direction.X * 5.0f;
srcRay.Origin.Y = ogRay->Origin.Y - srcRay.Direction.Y * 5.0f;
srcRay.MaxDistance = ogRay->MaxDistance + 10.0f;
auto distance = maths::distance_to_flipper(this, srcRay, dstRay);
CollisionDirection = dstRay.Direction;
if (distance >= 1e+09f)
{
return 1e+09;
}
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
return 0.0;
}
srcRay.Direction = ogRay->Direction;
srcRay.MinDistance = ogRay->MinDistance;
srcRay.Origin = ogRay->Origin;
srcRay.MaxDistance = rayMaxDistance;
auto distance = maths::distance_to_flipper(this, srcRay, dstRay);
if (distance < 1e+09f)
{
NextBallPosition = dstRay.Origin;
NextBallPosition.X -= srcRay.Direction.X * 1e-05f;
NextBallPosition.Y -= srcRay.Direction.Y * 1e-05f;
vector2* linePtr;
if (FlipperFlag == MessageCode::TFlipperRetract)
{
linePtr = &lineB.PerpendicularC;
CollisionFlag1 = AngleMax <= 0.0f;
}
else
{
CollisionFlag1 = AngleMax > 0.0f;
linePtr = &lineA.PerpendicularC;
}
CollisionLinePerp = *linePtr;
CollisionDirection = dstRay.Direction;
return distance;
}
timeNow = timeNow + CollisionTimeAdvance;
posX = posX + posXAdvance;
posY = posY + posYAdvance;
}
}
else
{
EdgeCollisionFlag = 0;
}
return 1e+09;
NextBallPosition = dstRay.Origin;
CollisionDirection = dstRay.Direction;
return distance;
}
void TFlipperEdge::EdgeCollision(TBall* ball, float distance)
{
EdgeCollisionFlag = 1;
if (FlipperFlag == MessageCode::TFlipperNull || !CollisionFlag2 || CollisionFlag1)
if (FlipperFlag == MessageCode::TFlipperNull)
{
float boost = 0.0;
if (CollisionFlag1)
maths::basic_collision(
ball,
&NextBallPosition,
&CollisionDirection,
Elasticity,
Smoothness,
1e9f,
0);
return;
}
auto someProduct = (NextBallPosition.Y - T1.Y) * (RotOrigin.X - T1.X) -
(NextBallPosition.X - T1.X) * (RotOrigin.Y - T1.Y);
bool someFlag = false;
if (someProduct <= 0)
{
if (AngleMax > 0)
someFlag = true;
}
else if (AngleMax <= 0)
{
someFlag = true;
}
if (FlipperFlag == MessageCode::TFlipperRetract)
{
someFlag ^= true;
CollisionLinePerp = LineB.PerpendicularC;
}
else
{
CollisionLinePerp = LineA.PerpendicularC;
}
auto dx = NextBallPosition.X - RotOrigin.X;
auto dy = NextBallPosition.Y - RotOrigin.Y;
auto distanceSq = dy * dy + dx * dx;
if (someFlag)
{
float boost = 0;
if (circlebase.RadiusSq * 1.01f < distanceSq)
{
float dx = NextBallPosition.X - RotOrigin.X;
float dy = NextBallPosition.Y - RotOrigin.Y;
float distanceSq = dy * dy + dx * dx;
if (circlebase.RadiusSq * 1.01f < distanceSq)
{
float v11;
float v20 = sqrt(distanceSq / DistanceDivSq) * (fabs(AngleMax) / AngleAdvanceTime);
float dot1 = maths::DotProduct(CollisionLinePerp, CollisionDirection);
if (dot1 >= 0.0f)
v11 = dot1 * v20;
else
v11 = 0.0;
boost = v11 * CollisionMult;
}
auto v21 = std::fabs(MoveSpeed) * std::sqrt(distanceSq / DistanceDivSq);
auto dot1 = maths::DotProduct(CollisionLinePerp, CollisionDirection);
if (dot1 >= 0)
boost = CollisionMult * dot1 * v21;
}
float threshold = boost <= 0.0f ? 1000000000.0f : -1.0f;
auto threshold = boost <= 0.0f ? 1e9f : -1.0f;
maths::basic_collision(
ball,
&NextBallPosition,
@ -333,18 +180,14 @@ void TFlipperEdge::EdgeCollision(TBall* ball, float distance)
Smoothness,
threshold,
boost);
return;
}
float elasticity;
float dx = NextBallPosition.X - RotOrigin.X;
float dy = NextBallPosition.Y - RotOrigin.Y;
float distanceSq = dy * dy + dx * dx;
if (circlebase.RadiusSq * 1.01f < distanceSq)
elasticity = (1.0f - sqrt(distanceSq / DistanceDivSq)) * Elasticity;
else
elasticity = Elasticity;
maths::basic_collision(ball, &NextBallPosition, &CollisionDirection, elasticity, Smoothness, 1000000000.0, 0.0);
{
auto elasticity = Elasticity;
if (circlebase.RadiusSq * 1.01f < distanceSq)
elasticity = (1.0f - std::sqrt(distanceSq / DistanceDivSq)) * Elasticity;
maths::basic_collision(ball, &NextBallPosition, &CollisionDirection, elasticity, Smoothness, 1e9f, 0.0);
}
}
void TFlipperEdge::place_in_grid(RectF* aabb)
@ -360,12 +203,18 @@ void TFlipperEdge::place_in_grid(RectF* aabb)
}
TTableLayer::edges_insert_square(yMin, xMin, yMax, xMax, this, nullptr);
auto offset = 1.0f / InvT1Radius + pb::ball_min_smth;
XMin = xMin - offset;
YMin = yMin - offset;
XMax = xMax + offset;
YMax = yMax + offset;
}
void TFlipperEdge::set_control_points(float timeNow)
void TFlipperEdge::set_control_points(float angle)
{
float sin, cos;
maths::SinCos(flipper_angle(timeNow), sin, cos);
maths::SinCos(angle, sin, cos);
A1 = A1Src;
A2 = A2Src;
B1 = B1Src;
@ -376,83 +225,48 @@ void TFlipperEdge::set_control_points(float timeNow)
maths::RotatePt(T1, sin, cos, RotOrigin);
maths::RotatePt(B1, sin, cos, RotOrigin);
maths::RotatePt(B2, sin, cos, RotOrigin);
maths::line_init(lineA, A1.X, A1.Y, A2.X, A2.Y);
maths::line_init(lineB, B1.X, B1.Y, B2.X, B2.Y);
maths::line_init(LineA, A1.X, A1.Y, A2.X, A2.Y);
maths::line_init(LineB, B1.X, B1.Y, B2.X, B2.Y);
circlebase = {RotOrigin, CirclebaseRadiusSq};
circleT1 = {T1, CircleT1RadiusSq};
ControlPointDirtyFlag = false;
}
float TFlipperEdge::flipper_angle(float timeNow)
float TFlipperEdge::flipper_angle_delta(float timeDelta)
{
// When not moving, flipper is at destination angle.
if (FlipperFlag == MessageCode::TFlipperNull)
return AngleDst;
return 0.0f;
// How much time it takes to go from source to destination angle, in sec.
auto arcDuration = std::abs((AngleDst - AngleSrc) / AngleMax * AngleAdvanceTime);
// How close the flipper is to destination, in [0, 1] range.
auto t = arcDuration >= 0.0000001f ? (timeNow - InputTime) / arcDuration : 1.0f;
t = Clamp(t, 0.0f, 1.0f);
// Result = linear interpolation between source and destination angle.
return AngleSrc + t * (AngleDst - AngleSrc);
const auto deltaAngle = MoveSpeed * timeDelta;
if (std::fabs(deltaAngle) > AngleRemainder)
return AngleDst - CurrentAngle;
return deltaAngle;
}
int TFlipperEdge::is_ball_inside(float x, float y)
{
vector2 testPoint{};
float dx = RotOrigin.X - x;
float dy = RotOrigin.Y - y;
if (((A2.X - A1.X) * (y - A1.Y) - (A2.Y - A1.Y) * (x - A1.X) >= 0.0f &&
(B1.X - A2.X) * (y - A2.Y) - (B1.Y - A2.Y) * (x - A2.X) >= 0.0f &&
(B2.X - B1.X) * (y - B1.Y) - (B2.Y - B1.Y) * (x - B1.X) >= 0.0f &&
(A1.X - B2.X) * (y - B2.Y) - (A1.Y - B2.Y) * (x - B2.X) >= 0.0f) ||
dy * dy + dx * dx <= CirclebaseRadiusSq ||
(T1.Y - y) * (T1.Y - y) + (T1.X - x) * (T1.X - x) < CircleT1RadiusSq)
{
float flipperLR = AngleMax < 0.0f ? -1.0f : 1.0f;
if (FlipperFlag == MessageCode::TFlipperExtend)
testPoint = AngleMax < 0.0f ? B1 : B2;
else if (FlipperFlag == MessageCode::TFlipperRetract)
testPoint = AngleMax < 0.0f ? A2 : A1;
else
testPoint = T1;
if (((y - testPoint.Y) * (RotOrigin.X - testPoint.X) -
(x - testPoint.X) * (RotOrigin.Y - testPoint.Y)) * flipperLR < 0.0f)
return 4;
return 5;
}
return 0;
}
int TFlipperEdge::SetMotion(MessageCode code, float value)
int TFlipperEdge::SetMotion(MessageCode code)
{
switch (code)
{
case MessageCode::TFlipperExtend:
AngleSrc = flipper_angle(value);
AngleRemainder = std::fabs(AngleMax - CurrentAngle);
AngleDst = AngleMax;
AngleAdvanceTime = ExtendTime;
MoveSpeed = ExtendSpeed;
break;
case MessageCode::TFlipperRetract:
AngleSrc = flipper_angle(value);
AngleRemainder = std::fabs(CurrentAngle);
AngleDst = 0.0f;
AngleAdvanceTime = RetractTime;
MoveSpeed = RetractSpeed;
break;
case MessageCode::Reset:
AngleSrc = 0.0f;
AngleRemainder = 0.0f;
AngleDst = 0.0f;
break;
default: break;
}
if (AngleSrc == AngleDst)
if (AngleRemainder == 0.0f)
code = MessageCode::TFlipperNull;
InputTime = value;
FlipperFlag = code;
AngleStopTime = AngleAdvanceTime + InputTime;
return static_cast<int>(code);
}

View File

@ -9,16 +9,15 @@ class TFlipperEdge : public TEdgeSegment
{
public:
TFlipperEdge(TCollisionComponent* collComp, char* activeFlag, unsigned int collisionGroup, TPinballTable* table,
vector3* origin, vector3* vecT1, vector3* vecT2, float extendTime, float retractTime, float collMult,
vector3* origin, vector3* vecT1, vector3* vecT2, float extendSpeed, float retractSpeed, float collMult,
float elasticity, float smoothness);
void port_draw() override;
float FindCollisionDistance(ray_type* ray) override;
void EdgeCollision(TBall* ball, float distance) override;
void place_in_grid(RectF* aabb) override;
void set_control_points(float timeNow);
float flipper_angle(float timeNow);
int is_ball_inside(float x, float y);
int SetMotion(MessageCode code, float value);
void set_control_points(float angle);
float flipper_angle_delta(float timeDelta);
int SetMotion(MessageCode code);
MessageCode FlipperFlag{};
float Elasticity;
@ -31,10 +30,9 @@ public:
float CirclebaseRadiusMSq;
float CircleT1RadiusMSq;
float AngleMax;
float AngleSrc{};
float AngleRemainder{};
float AngleDst;
int CollisionFlag1;
int CollisionFlag2{};
float CurrentAngle{};
vector2 CollisionLinePerp{};
vector2 A1Src{};
vector2 A2Src{};
@ -43,17 +41,16 @@ public:
float CollisionMult;
vector2 T1Src{};
vector2 T2Src{};
float DistanceDivSq;
float CollisionTimeAdvance;
float DistanceDiv, DistanceDivSq;
vector2 CollisionDirection{};
int EdgeCollisionFlag;
float InputTime;
float AngleStopTime;
float AngleAdvanceTime;
float ExtendTime;
float RetractTime;
float ExtendSpeed;
float RetractSpeed;
float MoveSpeed;
vector2 NextBallPosition{};
vector2 A1, A2, B1, B2, T1;
line_type lineA, lineB;
line_type LineA, LineB;
circle_type circlebase, circleT1;
float InvT1Radius;
float YMin, YMax, XMin, XMax;
bool ControlPointDirtyFlag{};
};

View File

@ -80,6 +80,7 @@ void THole::Collision(TBall* ball, vector2* nextPosition, vector2* direction, fl
ball->Position.X = Circle.Center.X;
ball->Position.Y = Circle.Center.Y;
ball->Direction.Z = 0.0;
ball->AsEdgeCollisionFlag = true;
// Ramp hole has no delay in FT.
auto captureTime = pb::FullTiltMode ? 0 : 0.5f;
@ -91,6 +92,10 @@ void THole::Collision(TBall* ball, vector2* nextPosition, vector2* direction, fl
control::handler(MessageCode::ControlBallCaptured, this);
}
}
else
{
DefaultCollision(ball, nextPosition, direction);
}
}
int THole::FieldEffect(TBall* ball, vector2* vecDst)

View File

@ -104,7 +104,8 @@ void TKickout::Collision(TBall* ball, vector2* nextPosition, vector2* direction,
ball->Position.X = Circle.Center.X;
ball->Position.Y = Circle.Center.Y;
OriginalBallZ = ball->Position.Z;
ball->Position.Z = CollisionBallSetZ;
ball->Position.Z = CollisionBallSetZ;
ball->AsEdgeCollisionFlag = true;
if (PinballTable->TiltLockFlag)
{
Message(MessageCode::TKickoutRestartTimer, 0.1f);
@ -115,6 +116,13 @@ void TKickout::Collision(TBall* ball, vector2* nextPosition, vector2* direction,
control::handler(MessageCode::ControlCollision, this);
}
}
else
{
ball->Position.X = nextPosition->X;
ball->Position.Y = nextPosition->Y;
ball->RayMaxDistance -= distance;
ball->not_again(edge);
}
}
int TKickout::FieldEffect(TBall* ball, vector2* dstVec)
@ -144,7 +152,7 @@ void TKickout::TimerExpired(int timerId, void* caller)
{
loader::play_sound(kick->HardHitSoundId, kick->Ball, "TKickout2");
kick->Ball->Position.Z = kick->OriginalBallZ;
TBall::throw_ball(kick->Ball, &kick->BallThrowDirection, kick->ThrowAngleMult, kick->ThrowSpeedMult1,
kick->Ball->throw_ball(&kick->BallThrowDirection, kick->ThrowAngleMult, kick->ThrowSpeedMult1,
kick->ThrowSpeedMult2);
kick->ActiveFlag = 0;
kick->Ball = nullptr;

View File

@ -631,6 +631,9 @@ TBall* TPinballTable::AddBall(float x, float y)
ball->Position.X = x;
ball->Position.Y = y;
ball->PrevPosition = ball->Position;
ball->SomeCounter1 = 0;
ball->time_ticks1 = ball->time_ticks2 = pb::time_ticks;
return ball;
}

View File

@ -88,7 +88,8 @@ void TSink::TimerExpired(int timerId, void* caller)
{
auto ball = table->AddBall(sink->BallPosition.X, sink->BallPosition.Y);
assertm(ball, "Failure to create ball in sink");
TBall::throw_ball(ball, &sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1,
ball->AsEdgeCollisionFlag = true;
ball->throw_ball(&sink->BallThrowDirection, sink->ThrowAngleMult, sink->ThrowSpeedMult1,
sink->ThrowSpeedMult2);
if (sink->SoundIndex3)
loader::play_sound(sink->SoundIndex3, ball, "TSink2");

View File

@ -3,6 +3,7 @@
#include "midi.h"
#include "pb.h"
#include "TBall.h"
#include "TBlocker.h"
#include "TBumper.h"
#include "TComponentGroup.h"
@ -1062,6 +1063,36 @@ void control::cheat_bump_rank()
}
}
void control::BallThrowOrDisable(TBall& ball, int dt)
{
if (!CheckBallInControlBounds(ball, *flip1) &&
!CheckBallInControlBounds(ball, *flip2) &&
!CheckBallInControlBounds(ball, *plunger))
{
if (ball.SomeCounter1 <= 20)
{
vector3 throwDir{0.0f, -1.0f, 0.0f};
ball.throw_ball(&throwDir, 90.0f, 1.0f, 0.0f);
}
else
{
ball.Disable();
TableG->MultiballCount--;
plunger->Message(MessageCode::PlungerRelaunchBall, 0);
}
}
}
bool control::CheckBallInControlBounds(const TBall& ball, const TCollisionComponent& cmp)
{
auto offset = TableG->CollisionCompOffset / 2.0f;
return ball.ActiveFlag &&
ball.Position.X >= cmp.AABB.XMin - offset &&
ball.Position.X <= cmp.AABB.XMax + offset &&
ball.Position.Y >= cmp.AABB.YMin - offset &&
ball.Position.Y <= cmp.AABB.YMax + offset;
}
int control::SpecialAddScore(int score)
{
int prevFlag1 = TableG->ScoreSpecial3Flag;

View File

@ -1,5 +1,7 @@
#pragma once
class TCollisionComponent;
class TBall;
enum class MessageCode;
class TSink;
class TLight;
@ -87,6 +89,8 @@ public:
static void table_set_multiball(float time);
static void table_bump_ball_sink_lock();
static void table_set_replay(float value);
static void BallThrowOrDisable(TBall& ball, int dt);
static bool CheckBallInControlBounds(const TBall& ball, const TCollisionComponent& cmp);
static void cheat_bump_rank();
static int SpecialAddScore(int score);
static int AddRankProgress(int rank);

View File

@ -217,6 +217,11 @@ float maths::magnitude(const vector3& vec)
return result;
}
float maths::magnitudeSq(const vector2& vec)
{
return vec.X * vec.X + vec.Y * vec.Y;
}
void maths::vector_add(vector2& vec1Dst, const vector2& vec2)
{
vec1Dst.X += vec2.X;
@ -312,7 +317,7 @@ float maths::distance_to_flipper(TFlipperEdge* flipper, const ray_type& ray1, ra
{
auto distance = 1000000000.0f;
auto distanceType = FlipperIntersect::none;
auto newDistance = ray_intersect_line(ray1, flipper->lineA);
auto newDistance = ray_intersect_line(ray1, flipper->LineA);
if (newDistance < distance)
{
distance = newDistance;
@ -330,7 +335,7 @@ float maths::distance_to_flipper(TFlipperEdge* flipper, const ray_type& ray1, ra
distance = newDistance;
distanceType = FlipperIntersect::circleT1;
}
newDistance = ray_intersect_line(ray1, flipper->lineB);
newDistance = ray_intersect_line(ray1, flipper->LineB);
if (newDistance < distance)
{
distance = newDistance;
@ -340,12 +345,12 @@ float maths::distance_to_flipper(TFlipperEdge* flipper, const ray_type& ray1, ra
switch (distanceType)
{
case FlipperIntersect::lineA:
ray2.Direction = flipper->lineA.PerpendicularC;
ray2.Origin = flipper->lineA.RayIntersect;
ray2.Direction = flipper->LineA.PerpendicularC;
ray2.Origin = flipper->LineA.RayIntersect;
break;
case FlipperIntersect::lineB:
ray2.Direction = flipper->lineB.PerpendicularC;
ray2.Origin = flipper->lineB.RayIntersect;
ray2.Direction = flipper->LineB.PerpendicularC;
ray2.Origin = flipper->LineB.RayIntersect;
break;
case FlipperIntersect::circlebase:
case FlipperIntersect::circleT1:

View File

@ -114,6 +114,7 @@ public:
static void cross(const vector3& vec1, const vector3& vec2, vector3& dstVec);
static float cross(const vector2& vec1, const vector2& vec2);
static float magnitude(const vector3& vec);
static float magnitudeSq(const vector2& vec);
static void vector_add(vector2& vec1Dst, const vector2& vec2);
static vector2 vector_sub(const vector2& vec1, const vector2& vec2);
static vector3 vector_sub(const vector3& vec1, const vector3& vec2);

View File

@ -324,13 +324,45 @@ void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls)
for (auto ball : MainTable->BallList)
{
if (!ball->ActiveFlag || ball->HasGroupFlag || ball->CollisionComp || ball->Speed >= 0.8f)
{
if (ball->SomeCounter1 > 0)
{
vector2 dxy{ball->Position.X - ball->PrevPosition.X, ball->Position.Y - ball->PrevPosition.Y};
auto offsetX2 = ball->Offset * 2.0f;
if (offsetX2 * offsetX2 < maths::magnitudeSq(dxy))
ball->SomeCounter1 = 0;
}
ball->time_ticks1 = ball->time_ticks2 = time_ticks;
}
else if (time_ticks - ball->time_ticks2 > 500)
{
vector2 dxy{ball->Position.X - ball->PrevPosition.X, ball->Position.Y - ball->PrevPosition.Y};
auto offsetD2 = ball->Offset / 2.0f;
ball->PrevPosition = ball->Position;
if (offsetD2 * offsetD2 < maths::magnitudeSq(dxy))
ball->SomeCounter1 = 0;
else
ball->SomeCounter1++;
control::BallThrowOrDisable(*ball, time_ticks - ball->time_ticks1);
}
}
int distanceCoefArray[20]{-1};
float distanceArray[20]{}, distanceArrayX[20]{}, distanceArrayY[20]{};
int minDistanceCoef = -1;
for (auto index = 0u; index < MainTable->BallList.size(); index++)
{
auto ball = MainTable->BallList[index];
if (ball->ActiveFlag != 0)
{
auto collComp = ball->CollisionComp;
if (collComp)
ball->TimeDelta = timeDelta;
if (ball->TimeDelta > 0.01f && ball->Speed < 0.8f)
ball->TimeDelta = 0.01f;
ball->AsEdgeCollisionFlag = false;
if (ball->CollisionComp)
{
ball->TimeDelta = timeDelta;
collComp->FieldEffect(ball, &vec1);
ball->CollisionComp->FieldEffect(ball, &vec1);
}
else
{
@ -345,23 +377,107 @@ void pb::timed_frame(float timeNow, float timeDelta, bool drawBalls)
ball->Direction.Y = ball->Speed * ball->Direction.Y;
maths::vector_add(ball->Direction, vec2);
ball->Speed = maths::normalize_2d(ball->Direction);
}
if (ball->Speed > ball_speed_limit)
ball->Speed = ball_speed_limit;
auto timeDelta2 = timeDelta;
auto timeNow2 = timeNow;
for (auto index = 10; timeDelta2 > 0.000001f && index; --index)
{
auto time = collide(timeNow2, timeDelta2, ball);
timeDelta2 -= time;
timeNow2 += time;
distanceArray[index] = ball->Speed * ball->TimeDelta;
auto distanceCoef = static_cast<int>(std::ceil(distanceArray[index] * ball_inv_smth)) - 1;
distanceCoefArray[index] = distanceCoef;
if (distanceCoef >= 0)
{
distanceArrayX[index] = ball->Direction.X * ball_min_smth;
distanceArrayY[index] = ball->Direction.Y * ball_min_smth;
if (distanceCoef > minDistanceCoef)
minDistanceCoef = distanceCoef;
}
}
}
}
}
for (auto flipper : MainTable->FlipperList)
float deltaAngle[4]{};
for (auto index = 0u; index < MainTable->FlipperList.size(); index++)
{
flipper->UpdateSprite(timeNow);
auto distanceCoef = MainTable->FlipperList[index]->GetFlipperAngleDistance(timeDelta, &deltaAngle[index]) - 1;
distanceArrayY[index] = static_cast<float>(distanceCoef);
if (distanceCoef > minDistanceCoef)
minDistanceCoef = distanceCoef;
}
ray_type ray{};
ray.MinDistance = 0.002f;
for (auto index4 = 0; index4 <= minDistanceCoef; index4++)
{
for (auto index5 = 0u; index5 < MainTable->BallList.size(); index5++)
{
auto ball = MainTable->BallList[index5];
if (!ball->AsEdgeCollisionFlag && index4 <= distanceCoefArray[index5])
{
float distanceSum = 0.0f;
ray.CollisionMask = ball->CollisionMask;
ball->TimeNow = timeNow;
if (ball_min_smth > 0.0f)
{
while (true)
{
ray.Origin = ball->Position;
ray.Direction = ball->Direction;
if (index4 >= distanceCoefArray[index5])
{
ray.MaxDistance = distanceArray[index5] - distanceCoefArray[index5] * ball_min_smth;
}
else
{
ray.MaxDistance = ball_min_smth;
}
ray.TimeNow = ball->TimeNow;
TEdgeSegment* edge = nullptr;
auto distance = TTableLayer::edge_manager->FindCollisionDistance(&ray, ball, &edge);
if (distance > 0.0f)
{
// Todo: ball to ball collision
//distance = ball_to_ball_collision();
}
if (ball->EdgeCollisionResetFlag)
{
ball->EdgeCollisionResetFlag = false;
}
else
{
ball->EdgeCollisionCount = 0;
ball->EdgeCollisionResetFlag = true;
}
if (distance >= 1e9f)
{
ball->Position.X += ray.MaxDistance * ray.Direction.X;
ball->Position.Y += ray.MaxDistance * ray.Direction.Y;
break;
}
edge->EdgeCollision(ball, distance);
if (distance > 0.0f && !ball->AsEdgeCollisionFlag)
{
distanceSum += distance;
if (distanceSum < ball_min_smth)
continue;
}
break;
}
}
}
}
for (auto index = 0u; index < MainTable->FlipperList.size(); index++)
{
if (distanceArrayY[index] >= index4)
MainTable->FlipperList[index]->FlipperCollision(deltaAngle[index]);
}
}
for (const auto flipper : MainTable->FlipperList)
{
flipper->UpdateSprite();
}
if (drawBalls)
@ -423,7 +539,7 @@ void pb::InputUp(GameInput input)
{
if (game_mode != GameModes::InGame || winmain::single_step || demo_mode)
return;
const auto bindings = options::MapGameInput(input);
for (const auto binding : bindings)
{
@ -454,7 +570,7 @@ void pb::InputUp(GameInput input)
void pb::InputDown(GameInput input)
{
if (options::WaitingForInput())
if (options::WaitingForInput())
{
options::InputDown(input);
return;
@ -475,31 +591,31 @@ void pb::InputDown(GameInput input)
for (const auto binding : bindings)
{
switch (binding)
{
case GameBindings::LeftFlipper:
MainTable->Message(MessageCode::LeftFlipperInputPressed, time_now);
break;
case GameBindings::RightFlipper:
MainTable->Message(MessageCode::RightFlipperInputPressed, time_now);
break;
case GameBindings::Plunger:
MainTable->Message(MessageCode::PlungerInputPressed, time_now);
break;
case GameBindings::LeftTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_right();
break;
case GameBindings::RightTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_left();
break;
case GameBindings::BottomTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_up();
break;
default: break;
}
{
case GameBindings::LeftFlipper:
MainTable->Message(MessageCode::LeftFlipperInputPressed, time_now);
break;
case GameBindings::RightFlipper:
MainTable->Message(MessageCode::RightFlipperInputPressed, time_now);
break;
case GameBindings::Plunger:
MainTable->Message(MessageCode::PlungerInputPressed, time_now);
break;
case GameBindings::LeftTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_right();
break;
case GameBindings::RightTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_left();
break;
case GameBindings::BottomTableBump:
if (!MainTable->TiltLockFlag)
nudge::nudge_up();
break;
default: break;
}
}
if (cheat_mode && input.Type == InputTypes::Keyboard)
{
@ -510,12 +626,12 @@ void pb::InputDown(GameInput input)
MainTable->MultiballCount++;
break;
case 'h':
{
high_score_struct entry{ {0}, 1000000000 };
strncpy(entry.Name, get_rc_string(Msg::STRING127), sizeof entry.Name - 1);
high_score::show_and_set_high_score_dialog({ entry, 1 });
break;
}
{
high_score_struct entry{{0}, 1000000000};
strncpy(entry.Name, get_rc_string(Msg::STRING127), sizeof entry.Name - 1);
high_score::show_and_set_high_score_dialog({entry, 1});
break;
}
case 'r':
control::cheat_bump_rank();
break;
@ -580,20 +696,24 @@ void pb::end_game()
int position = high_score::get_score_position(scores[i]);
if (position >= 0)
{
high_score_struct entry{ {0}, scores[i] };
high_score_struct entry{{0}, scores[i]};
const char* playerName;
switch(scoreIndex[i])
switch (scoreIndex[i])
{
default:
case 0: playerName = get_rc_string(Msg::STRING127); break;
case 1: playerName = get_rc_string(Msg::STRING128); break;
case 2: playerName = get_rc_string(Msg::STRING129); break;
case 3: playerName = get_rc_string(Msg::STRING130); break;
default:
case 0: playerName = get_rc_string(Msg::STRING127);
break;
case 1: playerName = get_rc_string(Msg::STRING128);
break;
case 2: playerName = get_rc_string(Msg::STRING129);
break;
case 3: playerName = get_rc_string(Msg::STRING130);
break;
}
strncpy(entry.Name, playerName, sizeof entry.Name - 1);
high_score::show_and_set_high_score_dialog({ entry, -1 });
high_score::show_and_set_high_score_dialog({entry, -1});
}
}
}