#include "pch.h" #include "TFlipperEdge.h" #include "TLine.h" #include "TPinballTable.h" #include "TTableLayer.h" float TFlipperEdge::flipper_sin_angle, TFlipperEdge::flipper_cos_angle; vector2 TFlipperEdge::A1, TFlipperEdge::A2, TFlipperEdge::B1, TFlipperEdge::B2, TFlipperEdge::T1; line_type TFlipperEdge::lineA, TFlipperEdge::lineB; circle_type TFlipperEdge::circlebase, TFlipperEdge::circleT1; 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) { vector3 crossProd{}, vecDir1{}, vecDir2{}; Elasticity = elasticity; Smoothness = smoothness; ExtendTime = extendTime; RetractTime = retractTime; CollisionMult = collMult; T1Src = static_cast(*vecT1); T2Src = static_cast(*vecT2); RotOrigin.X = origin->X; RotOrigin.Y = origin->Y; CirclebaseRadius = origin->Z + table->CollisionCompOffset; CirclebaseRadiusMSq = CirclebaseRadius * 1.01f * (CirclebaseRadius * 1.01f); CirclebaseRadiusSq = CirclebaseRadius * CirclebaseRadius; CircleT1Radius = vecT1->Z + table->CollisionCompOffset; CircleT1RadiusMSq = CircleT1Radius * 1.01f * (CircleT1Radius * 1.01f); CircleT1RadiusSq = CircleT1Radius * CircleT1Radius; vecDir1.X = vecT1->X - origin->X; vecDir1.Y = vecT1->Y - origin->Y; vecDir1.Z = 0.0; maths::normalize_2d(vecDir1); vecDir2.X = vecT2->X - origin->X; vecDir2.Y = vecT2->Y - origin->Y; vecDir2.Z = 0.0; maths::normalize_2d(vecDir2); AngleMax = acos(maths::DotProduct(vecDir1, vecDir2)); maths::cross(vecDir1, vecDir2, crossProd); if (crossProd.Z < 0.0f) AngleMax = -AngleMax; FlipperFlag = 0; Angle1 = 0.0; auto dirX1 = vecDir1.X; auto dirY1 = -vecDir1.Y; A2Src.X = dirY1 * CirclebaseRadius + origin->X; A2Src.Y = dirX1 * CirclebaseRadius + origin->Y; A1Src.X = dirY1 * CircleT1Radius + vecT1->X; A1Src.Y = dirX1 * CircleT1Radius + vecT1->Y; dirX1 = -dirX1; dirY1 = -dirY1; B1Src.X = dirY1 * CirclebaseRadius + origin->X; B1Src.Y = dirX1 * CirclebaseRadius + origin->Y; B2Src.X = dirY1 * CircleT1Radius + vecT1->X; B2Src.Y = dirX1 * CircleT1Radius + vecT1->Y; if (AngleMax < 0.0f) { std::swap(A1Src, B1Src); std::swap(A2Src, B2Src); } auto dx = vecT1->X - RotOrigin.X; auto dy = vecT1->Y - RotOrigin.Y; auto distance1 = sqrt(dy * dy + dx * dx) + table->CollisionCompOffset + vecT1->Z; DistanceDivSq = distance1 * distance1; float minMoveTime = std::min(ExtendTime, RetractTime); auto distance = maths::Distance(*vecT1, *vecT2); CollisionTimeAdvance = minMoveTime / (distance / CircleT1Radius + distance / CircleT1Radius); TFlipperEdge::place_in_grid(); EdgeCollisionFlag = 0; InputTime = 0.0; CollisionFlag1 = 0; AngleStopTime = 0.0; AngleMult = 0.0; } void TFlipperEdge::port_draw() { set_control_points(InputTime); build_edges_in_motion(); } float TFlipperEdge::FindCollisionDistance(ray_type* ray) { auto ogRay = ray; ray_type dstRay{}, srcRay{}; if (ogRay->TimeNow > AngleStopTime) { FlipperFlag = 0; } if (EdgeCollisionFlag == 0) { if (FlipperFlag == 0) { CollisionFlag1 = 0; CollisionFlag2 = 0; set_control_points(ogRay->TimeNow); build_edges_in_motion(); 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(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(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(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); build_edges_in_motion(); auto ballInside = is_ball_inside(posX, posY); if (ballInside != 0) { vector2* linePtr; if (FlipperFlag == 1 && ballInside != 5) { linePtr = &lineA.PerpendicularC; srcRay.Direction.Y = lineA.PerpendicularC.Y; srcRay.Direction.X = lineA.PerpendicularC.X; } else { if (FlipperFlag != 2 || 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(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(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(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 == 2) { 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; } void TFlipperEdge::EdgeCollision(TBall* ball, float distance) { EdgeCollisionFlag = 1; if (!FlipperFlag || !CollisionFlag2 || CollisionFlag1) { float boost = 0.0; if (CollisionFlag1) { 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) / AngleMult); float dot1 = maths::DotProduct(CollisionLinePerp, CollisionDirection); if (dot1 >= 0.0f) v11 = dot1 * v20; else v11 = 0.0; boost = v11 * CollisionMult; } } float threshold = boost <= 0.0f ? 1000000000.0f : -1.0f; maths::basic_collision( ball, &NextBallPosition, &CollisionDirection, Elasticity, 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); } void TFlipperEdge::place_in_grid() { float x0 = RotOrigin.X - CirclebaseRadius; float y0 = RotOrigin.Y - CirclebaseRadius; float x1 = RotOrigin.X + CirclebaseRadius; float y1 = RotOrigin.Y + CirclebaseRadius; float v2 = T1Src.X - CircleT1Radius; if (v2 < x0) x0 = v2; float v3 = T1Src.Y - CircleT1Radius; if (v3 < y0) y0 = v3; float v4 = T1Src.X + CircleT1Radius; if (v4 > x1) x1 = v4; float v5 = T1Src.Y + CircleT1Radius; if (v5 > y1) y1 = v5; float v6 = T2Src.X - CircleT1Radius; if (v6 < x0) x0 = v6; float v7 = T2Src.Y - CircleT1Radius; if (v7 < y0) y0 = v7; float v8 = T2Src.X + CircleT1Radius; if (v8 > x1) x1 = v8; float v9 = T2Src.Y + CircleT1Radius; if (v9 > y1) y1 = v9; TTableLayer::edges_insert_square(y0, x0, y1, x1, this, nullptr); } void TFlipperEdge::set_control_points(float timeNow) { maths::SinCos(flipper_angle(timeNow), flipper_sin_angle, flipper_cos_angle); A1 = A1Src; A2 = A2Src; B1 = B1Src; B2 = B2Src; T1 = T1Src; maths::RotatePt(A1, flipper_sin_angle, flipper_cos_angle, RotOrigin); maths::RotatePt(A2, flipper_sin_angle, flipper_cos_angle, RotOrigin); maths::RotatePt(T1, flipper_sin_angle, flipper_cos_angle, RotOrigin); maths::RotatePt(B1, flipper_sin_angle, flipper_cos_angle, RotOrigin); maths::RotatePt(B2, flipper_sin_angle, flipper_cos_angle, RotOrigin); } void TFlipperEdge::build_edges_in_motion() { 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.RadiusSq = CirclebaseRadiusSq; circlebase.Center.X = RotOrigin.X; circlebase.Center.Y = RotOrigin.Y; circleT1.RadiusSq = CircleT1RadiusSq; circleT1.Center.X = T1.X; circleT1.Center.Y = T1.Y; } float TFlipperEdge::flipper_angle(float timeNow) { if (!FlipperFlag) return Angle1; float angle = (Angle1 - Angle2) / AngleMax * AngleMult; if (angle < 0.0f) angle = -angle; if (angle >= 0.0000001f) angle = (timeNow - InputTime) / angle; else angle = 1.0; angle = std::min(1.0f, std::max(angle, 0.0f)); if (FlipperFlag == 2) angle = 1.0f - angle; return angle * AngleMax; } 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 == 1) testPoint = AngleMax < 0.0f ? B1 : B2; else if (FlipperFlag == 2) 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; } void TFlipperEdge::SetMotion(int code, float value) { switch (code) { case 1: Angle2 = flipper_angle(value); Angle1 = AngleMax; AngleMult = ExtendTime; break; case 2: Angle2 = flipper_angle(value); Angle1 = 0.0; AngleMult = RetractTime; break; case 1024: FlipperFlag = 0; Angle1 = 0.0; return; default: break; } if (!FlipperFlag) InputTime = value; FlipperFlag = code; AngleStopTime = AngleMult + InputTime; }