diff --git a/CHANGELOG.md b/CHANGELOG.md index 533e0fae3..89602f467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ Built with Unity 2021.2. ### Fixed +- Remaining ball spinning issue should now be solved ([#397](https://github.com/freezy/VisualPinball.Engine/pull/397)). - Finally, ball rotation is rendered correctly ([#386](https://github.com/freezy/VisualPinball.Engine/pull/386)). - Ball stuttering when rolling over dropped target ([#375](https://github.com/freezy/VisualPinball.Engine/pull/375)). - Plunger disappearing due to too small bounding box. diff --git a/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs b/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs index f7bca76bf..6580f35c0 100644 --- a/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs @@ -89,7 +89,7 @@ private Mesh GetMesh(float playfieldHeight, float meshHeight, int detailLevel, f SplineVertex sv = new SplineVertex(_data.DragPoints, (int)(_data.Thickness+0.5), detailLevel, splineAccuracy, margin: margin, loop: false); var height = playfieldHeight + meshHeight; - // hack - Component has to get edited. --- and TODO: Thickness should become a float. + var standheight = _data.Standheight; // dont lat the Collider be higher than the visible mesh, just shift the top of the MWG. if (createHitShape) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs index 4b9ca5c04..fac52f9f5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs @@ -80,7 +80,6 @@ public void BallManualRoll(in Entity entity, in float3 targetWorldPosition) float3 target = _worldToLocal.MultiplyPoint(targetWorldPosition); var ballData = _entityManager.GetComponentData(entity); ballData.Velocity = float3.zero; - ballData.AngularVelocity = float3.zero; ballData.AngularMomentum = float3.zero; ballData.IsFrozen = false; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs index e64f1c14e..b40a60c86 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs @@ -27,44 +27,29 @@ internal struct BallData : IComponentData public float3 EventPosition; // m_lastEventPos public float3 Velocity; - /// - /// AngularVelocity - german: Winkelgeschwindigkeit - - /// * Set to 0 at Manual Roll - /// (in BallManualRoll(in Entity entity, in float3 targetWorldPosition) - /// (which is not used anywhere in this Project, but is at least used in Ravarcade's ImGui Physics Debugger - Addon) - /// * Is set to zero At RotatorComponent. Possibly an error and should be AngularVelocity - /// (in UpdateRotation(float angleDeg)) - /// * Calculated from AngularMomentum / inertia - /// (In BallDisplacementSystem.OnUpdate()) - /// (Where Inertia is a "constant" based on radius and mass (2/5 m r^2)) - /// * Used to get tangential velocity due to rotation when rolling / colliding on surfaces (alsways added to normal velocity) - /// (in BallData.SurfaceVelocity(in BallData ball, in float3 surfP)) - /// - public float3 AngularVelocity; /// /// AngularMomentum - german: drehimpuls, Impulsmomemt /// * Set to 0 at Manual Roll /// (in BallManualRoll(in Entity entity, in float3 targetWorldPosition) - /// * Set to 0 at every new ball + /// * Set to 0 at every new ball /// (in Ballmanager.CreateEntity(GameObject ballGo, int id, in float3 worldPos, in float3 localPos, in float3 localVel, in float scale, in float mass, in float radius, in Entity kickerEntity) /// * Set to 0 in KickerApi, KickerCollider and RotatorComponent /// (in several places) - /// * Calculated when a survace applies an impulse, it applies it to velocity (div by mass) and to angMom fully. + /// * Calculated when a survace applies an impulse, it applies it to angMom fully. /// (ApplySurfaceImpulse(in float3 rotI, in float3 impulse)) /// (Where rotI seems to be the Rotation impulse and impulse is the (non angular)velocity (makes sense to divide by mass) /// (angularMomenmtom = rotI;) - /// * used to calculate Angular Velocity (ball.AngularVelocity = ball.AngularMomentum / inertia;) - /// (in BalldisplacementSystem.OnUpdate()) - /// * used to add and thus calculate Orientation + /// * used to add and thus calculate Orientation /// (in BalldisplacementSystem.OnUpdate()) /// skewSymmetricMatrix is created from the Angular Momentum divided by Inertia /// The original orientation is multiplied with the skewSymmetric Matrix /// and added to the old Orientation to form new orientation - /// + /// /// public float3 AngularMomentum; - public float3x3 Orientation; + + public float3x3 BallOrientation; + public float3x3 BallOrientationForUnity; public float Radius; public float Mass; public bool IsFrozen; @@ -89,7 +74,8 @@ public Aabb Aabb { } } - public BallColliderBounds Bounds(Entity entity) { + public BallColliderBounds Bounds(Entity entity) + { var vl = math.length(Velocity) + Radius + 0.05f; // 0.05f = paranoia return new BallColliderBounds(entity, new Aabb( Position.x - vl, @@ -109,11 +95,11 @@ public float CollisionRadiusSqr { } /// - /// Calculates Moment of Inertia for a Ball + /// Calculates Moment of Inertia for a Ball /// https://en.wikipedia.org/wiki/Moment_of_inertia#Examples_2 /// public float Inertia => 2.0f / 5.0f * Radius * Radius * Mass; - + public float InvMass => 1f / Mass; public void ApplySurfaceImpulse(in float3 rotI, in float3 impulse) @@ -125,14 +111,40 @@ public void ApplySurfaceImpulse(in float3 rotI, in float3 impulse) public static float3 SurfaceVelocity(in BallData ball, in float3 surfP) { // linear velocity plus tangential velocity due to rotation - return ball.Velocity + math.cross(ball.AngularVelocity, surfP); + return ball.Velocity + math.cross(ball.AngularMomentum / ball.Inertia, surfP); + /* + This was (from freezy's first implementation): + return ball.Velocity + math.cross(ball.AngularVelocity, surfP); + (angular velocity should not be used to calculate the surface velocity, since angVel is the imapct of the ball from a collider, not the angular "speed" of a ball. + Only after all collision-calculations angVel is set to AngMom / inertia) + + Original code from VPX + Vertex3Ds Ball::SurfaceVelocity(const Vertex3Ds& surfP) const + { + return m_d.m_vel + CrossProduct(m_angularmomentum / Inertia(), surfP); // linear velocity plus tangential velocity due to rotation + } + */ } public static float3 SurfaceAcceleration(in BallData ball, in float3 surfP, in float3 gravity) { + var currentAngularVelocity = ball.AngularMomentum / ball.Inertia; + // if we had any external torque, we would have to add "(deriv. of ang.Vel.) x surfP" here return gravity / ball.Mass // linear acceleration - + math.cross(ball.AngularVelocity, math.cross(ball.AngularVelocity, surfP)); // centripetal acceleration + + math.cross(currentAngularVelocity, math.cross(currentAngularVelocity, surfP)); // centripetal acceleration + + /* This was (from freezy's first implementation): + return gravity / ball.Mass // linear acceleration + + math.cross(ball.angularVelocity, math.cross(ball.angularVelocity, surfP)); // centripetal acceleration + * Original Code: + const Vertex3Ds angularvelocity = m_angularmomentum / Inertia(); + // if we had any external torque, we would have to add "(deriv. of ang.vel.) x surfP" here + return g_pplayer->m_gravity/m_d.m_mass // linear acceleration + + CrossProduct(angularvelocity, CrossProduct(angularvelocity, surfP)); // centripetal acceleration + + the angular velocity used here is not the angular velocity that the ball has (which is more like an angular impulse which is added to the angMom). + */ } public static void SetOutsideOf(ref DynamicBuffer insideOfs, in Entity entity) @@ -147,7 +159,7 @@ public static void SetOutsideOf(ref DynamicBuffer ins public static void SetInsideOf(ref DynamicBuffer insideOfs, Entity entity) { - insideOfs.Add(new BallInsideOfBufferElement {Value = entity}); + insideOfs.Add(new BallInsideOfBufferElement { Value = entity }); } public static bool IsOutsideOf(in DynamicBuffer insideOfs, in Entity entity) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs index 594fc8ffc..49d418746 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs @@ -17,6 +17,7 @@ using Unity.Entities; using Unity.Mathematics; using Unity.Profiling; +using UnityEngine; namespace VisualPinball.Unity { @@ -52,50 +53,50 @@ protected override void OnUpdate() var inertia = ball.Inertia; var mat3 = CreateSkewSymmetric(ball.AngularMomentum / inertia); - var addedOrientation = math.mul(ball.Orientation, mat3); + var addedOrientation = math.mul(ball.BallOrientation, mat3); addedOrientation *= dTime; - ball.Orientation += addedOrientation; - math.orthonormalize(ball.Orientation); - - // after Orthonormalization, the orientation vectors also have to be normalized - this is not done by othonomalize, since the skew matrix creates quite lengthy vectors. - // in fact, they dont have to be normalized, but just shortened, so we can abs-add the x, y and z together and just divide by the sum. - // This saves three sqrts in the game loop per ball. - float lengthX, lengthY, lengthZ; - /* Correct normalization would be: - * lengthX = math.sqrt(ball.Orientation.c0.x * ball.Orientation.c0.x + ball.Orientation.c0.y * ball.Orientation.c0.y + ball.Orientation.c0.z * ball.Orientation.c0.z); - * lengthY = math.sqrt(ball.Orientation.c1.x * ball.Orientation.c1.x + ball.Orientation.c1.y * ball.Orientation.c1.y + ball.Orientation.c1.z * ball.Orientation.c1.z); - * lengthZ = math.sqrt(ball.Orientation.c2.x * ball.Orientation.c2.x + ball.Orientation.c2.y * ball.Orientation.c2.y + ball.Orientation.c2.z * ball.Orientation.c2.z); - */ - lengthX = math.abs(ball.Orientation.c0.x) + math.abs(ball.Orientation.c0.y) + math.abs(ball.Orientation.c0.z); - lengthY = math.abs(ball.Orientation.c1.x) + math.abs(ball.Orientation.c1.y) + math.abs(ball.Orientation.c1.z); - lengthZ = math.abs(ball.Orientation.c2.x) + math.abs(ball.Orientation.c2.y) + math.abs(ball.Orientation.c2.z); - if (lengthX != 0f) - { - ball.Orientation.c0.x /= lengthX; - ball.Orientation.c0.y /= lengthX; - ball.Orientation.c0.z /= lengthX; - } - if (lengthY != 0f) - { - ball.Orientation.c1.x /= lengthY; - ball.Orientation.c1.y /= lengthY; - ball.Orientation.c1.z /= lengthY; - } - if (lengthZ != 0f) - { - ball.Orientation.c2.x /= lengthZ; - ball.Orientation.c2.y /= lengthZ; - ball.Orientation.c2.z /= lengthZ; - } + ball.BallOrientation += addedOrientation; + + // do the same for Unity's ball Orientation (where z (and z only rotation) has to be flipped), + // which (maybe??) can't be done after skew matrix operations (or we don't know how)) + // If we flip an exis in the matrix, we always flip two rotations. + var AngMomFlippedZ = new float3(ball.AngularMomentum.x, ball.AngularMomentum.y, -ball.AngularMomentum.z); + mat3 = CreateSkewSymmetric(AngMomFlippedZ / inertia); + addedOrientation = math.mul(ball.BallOrientationForUnity, mat3); + addedOrientation *= dTime; - ball.AngularVelocity = ball.AngularMomentum / inertia; + ball.BallOrientationForUnity += addedOrientation; + + VPOrthonormalize(ref ball.BallOrientation); + VPOrthonormalize(ref ball.BallOrientationForUnity); marker.End(); }).Run(); } + private static void VPOrthonormalize(ref float3x3 orientation) + { + Vector3 vX = new Vector3(orientation.c0.x, orientation.c1.x, orientation.c2.x); + Vector3 vY = new Vector3(orientation.c0.y, orientation.c1.y, orientation.c2.y); + Vector3 vZ = Vector3.Cross(vX, vY); + vX = Vector3.Normalize(vX); + vZ = Vector3.Normalize(vZ); + vY = Vector3.Cross(vZ, vX); + + orientation.c0.x = vX.x; + orientation.c0.y = vY.x; + orientation.c0.z = vZ.x; + orientation.c1.x = vX.y; + orientation.c1.y = vY.y; + orientation.c1.z = vZ.y; + orientation.c2.x = vX.z; + orientation.c2.y = vY.z; + orientation.c2.z = vZ.z; + + } + private static float3x3 CreateSkewSymmetric(in float3 pv3D) { return new float3x3( diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs index 2d1325b36..467f528f0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs @@ -113,7 +113,8 @@ public void CreateEntity(GameObject ballGo, int id, in float3 worldPos, in float Radius = radius, Mass = mass, Velocity = localVel, - Orientation = float3x3.identity, + BallOrientation = float3x3.identity, + BallOrientationForUnity = float3x3.identity, RingCounterOldPos = 0, AngularMomentum = float3.zero }); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs index 5a343a223..c755e21a4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs @@ -61,53 +61,72 @@ protected override void OnUpdate() var ballTransform = _player.Balls[entity].transform; ballTransform.localPosition = new Vector3(ball.Position.x, ball.Position.y, zHeight); + var or = ball.BallOrientationForUnity; + + var VPX = new Vector3(or.c0.x, or.c1.x, or.c2.x); + var VPY = new Vector3(or.c0.y, or.c1.y, or.c2.y); + var VPZ = new Vector3(or.c0.z, or.c1.z, or.c2.z); - var or = ball.Orientation; - var vpright = new Vector3(or.c0.x, or.c1.x, or.c2.x); - var vpfront = new Vector3(or.c0.y, or.c1.y, or.c2.y); - var vptop = new Vector3(or.c0.z, or.c1.z, or.c2.z); // Debug.Log("c0: (" + or.c0.x + ", " + or.c0.y + ", " + or.c0.z + ")"); // Debug.Log("c1: (" + or.c1.x + ", " + or.c1.y + ", " + or.c1.z + ")"); // Debug.Log("c2: (" + or.c2.x + ", " + or.c2.y + ", " + or.c2.z + ")"); - Vector3.OrthoNormalize(ref vptop, ref vpfront, ref vpright); - var unitytop = new Vector3(vptop.x, vptop.z, vptop.y); - var unityfront = new Vector3(vpfront.x, vpfront.z, vpfront.y); - Vector3.OrthoNormalize(ref unitytop, ref unityfront); + // for security reasons, so that we don't get NaN, NaN, NaN, NaN erroro, when vectors are not fully orthonormalized because of skewMatrix operation + Vector3.OrthoNormalize(ref VPZ, ref VPY, ref VPX); - // Following is the transistion from VP-Physics Ball Orientation to the Unity Ball-Orientation. - // following statements: when looking at the backglass: - // The problem here is, that we have - // a right handed universe in VP (X->R, Y->F, Z->U) and - // a left handed universe in Unity (X->R, Y->U, Z->F) - // The other problem is, that Unity likes quaternions and VP uses Orientation matrices. - // I THINK!!!: - // where x via C0 to C2 describes the right vector, - // and y the front vector and z the top vector + Quaternion q = Quaternion.LookRotation(VPZ, VPY); - // So we have to transform between these. Not only that the Column-wise Vectors is hard to understand, - // but also the transition from one universe to another is hard. + // flip Z axis + q = FlipZAxis(q); - //very old transformation (looks strange) (freezy) - //ballTransform.localRotation = Quaternion.LookRotation(or.c2, or.c1); - //1st iteration by (looks strange, but less strange) (cupiii) - //ballTransform.localRotation = Quaternion.LookRotation(new Vector3(or.c0.x*-1, or.c1.x*-1, or.c2.x), new Vector3(or.c0.z*-1, or.c1.z*-1, or.c2.z)); - //newest iteration (hopefully correct)) + ballTransform.localRotation = q; - // Better Ways than skew matrix: - // https://gamedev.stackexchange.com/questions/108920/applying-angular-velocity-to-quaternion - // https://stackoverflow.com/questions/23503151/how-to-update-quaternion-based-on-3d-gyro-data - // https://stackoverflow.com/questions/12053895/converting-angular-velocity-to-quaternion-in-opencv + marker.End(); - // also implementing VP's "Orthonormalize" Code - although it does not really orthonormalize could give a performance benefit: - // https://github.com/vpinball/vpinball/blob/be08b04d61096272df97bd45e6f0682043228a73/math/matrix.h#L208 + }).Run(); - ballTransform.localRotation = Quaternion.LookRotation(unityfront, unitytop); + static Quaternion FlipZAxis(Quaternion q) + { + // which actually flips x and y axis visually... + return new Quaternion(q.x, q.y, -q.z, -q.w); + } + + /* + * I let these two in here, just in case we need them. + + static float3x3 transpose(float3x3 or) + { + float3x3 or2; + or2.c0.x = or.c0.x; + or2.c0.y = or.c1.x; + or2.c0.z = or.c2.x; + or2.c1.x = or.c0.y; + or2.c1.y = or.c1.y; + or2.c1.z = or.c2.y; + or2.c2.x = or.c0.z; + or2.c2.y = or.c1.z; + or2.c2.z = or.c2.z; + return or2; + } + + static Quaternion QuaternionFromMatrix(Matrix4x4 m) + { + // Adapted from: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + Quaternion q = new Quaternion(); + q.w = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] + m[1, 1] + m[2, 2])) / 2; + q.x = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] - m[1, 1] - m[2, 2])) / 2; + q.y = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] + m[1, 1] - m[2, 2])) / 2; + q.z = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] - m[1, 1] + m[2, 2])) / 2; + q.x *= Mathf.Sign(q.x * (m[2, 1] - m[1, 2])); + q.y *= Mathf.Sign(q.y * (m[0, 2] - m[2, 0])); + q.z *= Mathf.Sign(q.z * (m[1, 0] - m[0, 1])); + return q; + } + + */ - marker.End(); - }).Run(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs index 99d3ac104..df780afb4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs @@ -122,7 +122,6 @@ public void UpdateRotation(float angleDeg) ); ballData.Velocity = float3.zero; ballData.AngularMomentum = float3.zero; - ballData.AngularVelocity = float3.zero; EntityManager.SetComponentData(ballEntity, ballData); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index b29aac14e..77ff753bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -234,6 +234,9 @@ public static void Collide(ref BallData ball, ref CollisionEventData collEvent, var dot = (ball.Velocity.x - collEvent.HitVelocity.x) * collEvent.HitNormal.x + (ball.Velocity.y - collEvent.HitVelocity.y) * collEvent.HitNormal.y; + // HACK to stop the ball from spinning. + ball.AngularMomentum.z *= 0.6f; + // nearly receding ... make sure of conditions if (dot >= -PhysicsConstants.LowNormVel) {