diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs index bba9fdea3..c143a6f95 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs @@ -39,19 +39,34 @@ public class FlipperColliderInspector : ColliderInspector(coll.Entity); var flipperHitData = GetComponent(coll.Entity); var flipperTricksData = GetComponent(coll.Entity); - + // do liveCatch - check before collision + FlipperCollider.LiveCatch( + ref ballData, ref collEvent, ref flipperTricksData, in flipperMaterialData, timeMsec + ); ((FlipperCollider*)collider)->Collide( ref ballData, ref collEvent, ref flipperMovementData, ref events, in ballEntity, in flipperTricksData,in flipperMaterialData, in flipperVelocityData, in flipperHitData, timeMsec diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 271a39c56..43696584c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -14,6 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using NLog; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; using Unity.Entities; @@ -35,6 +36,10 @@ internal struct FlipperCollider : ICollider public ColliderBounds Bounds { get; private set; } + public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + #region Setup + public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float startRadius, float endRadius, float startAngle, float endAngle, ColliderInfo info) : this() { var bounds = hitCircleBase.Bounds; @@ -143,6 +148,8 @@ public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray insideOfs, @@ -574,7 +581,7 @@ private float HitTestFlipperEnd(ref CollisionEventData collEvent, ref FlipperHit // hit limits ??? if (contactAng >= angleMax && angleSpeed > 0 || contactAng <= angleMin && angleSpeed < 0) { - angleSpeed = 0; // rotation stopped + angleSpeed = 0f; // rotation stopped } // Unit Tangent vector velocity of contact point(rotate normal right) @@ -717,6 +724,56 @@ private void GetRelativeVelocity(in float3 normal, in BallData ball, in FlipperM #endregion + #region LiveCatch + + public static void LiveCatch(ref BallData ball, ref CollisionEventData collEvent, ref FlipperTricksData tricks, in FlipperStaticData matData, uint msec ) { + if (!tricks.UseFlipperLiveCatch) + return; + var normalSpeed = math.dot(collEvent.HitNormal, ball.Velocity) * -1f; + // Vector from position of the flipper ball to ball + var flipperToBall = ball.Position - matData.Position; + var hitTangent = Math.CrossZ(1f, collEvent.HitNormal); + var ballPosition = math.dot(hitTangent, flipperToBall); + //Logger.Info("BallPosition = {0}", ballPosition); + if (math.abs(ballPosition) > tricks.LiveCatchDistanceMax) { + //Logger.Info("BallPosition = {0} -> no calculation", ballPosition); + return; + } + if (math.abs(ballPosition) < tricks.LiveCatchDistanceMin) { + //Logger.Info("BallPosition = {0} -> no calculation", ballPosition); + return; + } + // only test for LiveCatch if Ballspeed is greater as set Minimal Speed (default = 6) + // different to nFozzys implementation we calculate all speeds based on the angle of the flipper, not y direction. + if (normalSpeed >= tricks.LiveCatchMinimalBallSpeed) { + float catchTime = (float)(msec - tricks.FlipperAngleEndTime * 1000); + if (catchTime <= tricks.LiveCatchFullTime){ + // we have a live catch, so stop the ball for now. + ball.Velocity += normalSpeed * collEvent.HitNormal; + // do we have some bounce + // as a difference to the nFozzy implementation, we don't deal with hard-coded speeds, but multiplier to current speed against the flipper. + var liveCatchBounceMultiplier = tricks.LiveCatchMinimalBounceSpeedMultiplier; + //Logger.Info("We have a live catch"); + if (catchTime > tricks.LiveCatchPerfectTime) { + // but it's imperfect, so we have add some bounce + // example: hit after 10 msecs, fulltime is 16, perfect time is 8, should be (10-8)/(16-8)*inaccuracySpeedMultiplier + liveCatchBounceMultiplier = (catchTime - tricks.LiveCatchPerfectTime) / (tricks.LiveCatchFullTime - tricks.LiveCatchPerfectTime) * (tricks.LiveCatchInaccurateBounceSpeedMultiplier-tricks.LiveCatchMinimalBounceSpeedMultiplier) + tricks.LiveCatchMinimalBounceSpeedMultiplier; + + } + //Logger.Info("Bounce Multiplicator is {0}, catchtime {1}", liveCatchBounceMultiplier, catchTime); + ball.Velocity -= collEvent.HitNormal * normalSpeed * liveCatchBounceMultiplier; + ball.AngularMomentum.x = 0; + ball.AngularMomentum.y = 0; + + } + //Logger.Info("LiveCatchTest - Ball with y-speed {0}, at CollisionTime: {1}, livecatchTime is {2}, difference is {3} msecs", ball.Velocity.y, msec, tricks.FlipperAngleEndTime * 1000, tricks.FlipperAngleEndTime * 1000 - msec); + //Logger.Info("LiveCatchTest - normalspeed = {0}, catchTime = {1}", normalSpeed, catchTime); + + } + } + + #endregion + #region Collision public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref FlipperMovementData movementData, @@ -786,7 +843,7 @@ public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref Fli * 0 = no falloff, 1 = half the COR at 1 m/s (18.53 speed units) */ var epsilon = Math.ElasticityWithFalloff(_header.Material.Elasticity, _header.Material.ElasticityFalloff, bnv); - if (tricks.useFlipperTricksPhysics) + if (tricks.UseFlipperTricksPhysics) epsilon *= tricks.ElasticityMultiplier; var pv1 = angResp / matData.Inertia; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 1ab0172ea..4261caf7f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -16,7 +16,6 @@ // ReSharper disable InconsistentNaming -using System; using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Flipper; @@ -79,7 +78,7 @@ public class FlipperColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter); - #region Flipper_Tricks + #region FlipperTricks /// /// If set, apply Flipper Tricks Physics (nFozzy/RothBauerW) /// @@ -120,6 +119,44 @@ public class FlipperColliderComponent : ColliderComponent + /// If set, apply Live Catch (nFozzy/RothBauerW) + /// + #endregion + [Tooltip("The nFozzy's LiveCatch Physics")] + public bool useFlipperLiveCatch = false; + + [Min(0f)] + [Tooltip("Minimum distance in vp units from flipper base live catch dampening will occur")] + public float LiveCatchDistanceMin = 40f; + + [Min(0f)] + [Tooltip("Maxium distance in vp units from flipper base live catch dampening will occur")] + public float LiveCatchDistanceMax = 100f; + + [Min(0f)] + [Tooltip("Minimal ball speed for live catch")] + public float LiveCatchMinimalBallSpeed = 6f; + + [Unit("ms")] + [Min(0f)] + [Tooltip("Maximum Time in for (perfect or imperfect) live catch")] + public float LiveCatchFullTime = 16; + + [Unit("ms")] + [Min(0f)] + [Tooltip("Maximum Time for a perfect live catch")] + public float LiveCatchPerfectTime = 8; + + [Min(0f)] + [Tooltip("Minimum bounce speed multiplier for a live catch (0 allows perfect live catches)")] + public float LiveCatchMinmalBounceSpeedMultiplier = 0.1f; + + [Min(0f)] + [Tooltip("Maximum bounce speed multiplier for an inaccurate live catch")] + public float LiveCatchInaccurateBounceSpeedMultiplier = 1.0f; + protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) => new FlipperApi(gameObject, entity, player); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index e2b59df78..496791714 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -581,7 +581,7 @@ private FlipperTricksData GetFlipperTricksData(FlipperColliderComponent collider { return new FlipperTricksData { - useFlipperTricksPhysics = colliderComponent.useFlipperTricksPhysics, + UseFlipperTricksPhysics = colliderComponent.useFlipperTricksPhysics, SOSRampUp = colliderComponent.SOSRampUp, SOSEM = colliderComponent.SOSEM, EOSReturn = colliderComponent.EOSReturn, @@ -592,7 +592,16 @@ private FlipperTricksData GetFlipperTricksData(FlipperColliderComponent collider AngleEnd = staticData.AngleEnd, TorqueDamping = staticData.TorqueDamping, TorqueDampingAngle = staticData.TorqueDampingAngle, - RampUpSpeed = staticData.RampUpSpeed, + RampUpSpeed = staticData.RampUpSpeed, + + UseFlipperLiveCatch = colliderComponent.useFlipperLiveCatch, + LiveCatchDistanceMin = colliderComponent.LiveCatchDistanceMin, // vp units from base + LiveCatchDistanceMax = colliderComponent.LiveCatchDistanceMax, // vp units from base + LiveCatchMinimalBallSpeed = colliderComponent.LiveCatchMinimalBallSpeed, + LiveCatchPerfectTime = colliderComponent.LiveCatchPerfectTime, + LiveCatchFullTime = colliderComponent.LiveCatchFullTime, + LiveCatchInaccurateBounceSpeedMultiplier = colliderComponent.LiveCatchInaccurateBounceSpeedMultiplier, + LiveCatchMinimalBounceSpeedMultiplier = colliderComponent.LiveCatchMinmalBounceSpeedMultiplier, //initialize OriginalAngleEnd = staticData.AngleEnd, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs index a59133b74..bdd63878f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs @@ -50,8 +50,9 @@ protected override void OnUpdate() var dTime = _simulateCycleSystemGroup.HitTime; var events = _eventQueue.AsParallelWriter(); var marker = PerfMarker; + var currentTime = Time.ElapsedTime; - Entities.WithName("FlipperDisplacementJob").ForEach((Entity entity, ref FlipperMovementData state, in FlipperTricksData tricks,in FlipperStaticData data) => { + Entities.WithName("FlipperDisplacementJob").ForEach((Entity entity, ref FlipperMovementData state, ref FlipperTricksData tricks, in FlipperStaticData data) => { marker.Begin(); @@ -76,6 +77,10 @@ protected override void OnUpdate() var handleEvent = false; + if (state.Angle == tricks.AngleEnd) { + tricks.FlipperAngleEndTime = currentTime; + } + if (state.Angle >= angleMax) { // hit stop? if (state.AngleSpeed > 0) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs index 6037c82e1..69442df92 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs @@ -38,8 +38,12 @@ internal struct FlipperTricksData : IComponentData public bool WasInContact; + // time used for live Catch + public double FlipperAngleEndTime; + // externals - public bool useFlipperTricksPhysics; + // Flipper Tricks + public bool UseFlipperTricksPhysics; public float SOSRampUp; public float SOSEM; public float EOSReturn; @@ -47,5 +51,16 @@ internal struct FlipperTricksData : IComponentData public float EOSANew; public float EOSRampup; public float Overshoot; + + // Live Catch + public bool UseFlipperLiveCatch; + public float LiveCatchDistanceMin; // vp units from base + public float LiveCatchDistanceMax; // vp units from base + public float LiveCatchMinimalBallSpeed; + public float LiveCatchPerfectTime; + public float LiveCatchFullTime; + public float LiveCatchMinimalBounceSpeedMultiplier; + public float LiveCatchInaccurateBounceSpeedMultiplier; + } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs index cf696bc45..8c9670477 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs @@ -48,7 +48,7 @@ protected override void OnUpdate() desiredTorque *= -data.ReturnRatio; } - if (tricks.useFlipperTricksPhysics) { + if (tricks.UseFlipperTricksPhysics) { // check if solenoid was just activated or deactivated if (solenoid.Value != tricks.lastSolState) { @@ -71,15 +71,11 @@ protected override void OnUpdate() } // hold coil is weaker - float eosAngle; - if (tricks.useFlipperTricksPhysics) - eosAngle = math.radians(tricks.TorqueDampingAngle); - else - eosAngle = math.radians(tricks.TorqueDampingAngle); + float eosAngle = math.radians(tricks.TorqueDampingAngle); if (math.abs(mState.Angle - tricks.AngleEnd) < eosAngle) { // fade in/out damping, depending on angle to end var lerp = math.pow(math.abs(mState.Angle - tricks.AngleEnd) / eosAngle, 4); - if (tricks.useFlipperTricksPhysics) + if (tricks.UseFlipperTricksPhysics) desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); else desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); @@ -132,7 +128,7 @@ protected override void OnUpdate() mState.AngleSpeed = mState.AngularMomentum / data.Inertia; vState.AngularAcceleration = torque / data.Inertia; - if (tricks.useFlipperTricksPhysics) + if (tricks.UseFlipperTricksPhysics) { // Flippertricks, case 3 (OnFlipperDown) and 4 (OnFlipperUpResting) if (!tricks.WasInContact && vState.IsInContact)