Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ public void BallManualRoll(in Entity entity, in float3 targetWorldPosition)
float3 target = _worldToLocal.MultiplyPoint(targetWorldPosition);
var ballData = _entityManager.GetComponentData<BallData>(entity);
ballData.Velocity = float3.zero;
ballData.AngularVelocity = float3.zero;
ballData.AngularMomentum = float3.zero;
ballData.IsFrozen = false;

Expand Down
68 changes: 40 additions & 28 deletions VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,44 +27,29 @@ internal struct BallData : IComponentData
public float3 EventPosition; // m_lastEventPos
public float3 Velocity;

/// <summary>
/// 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))
/// </summary>
public float3 AngularVelocity;
/// <summary>
/// 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
///
///
/// </summary>
public float3 AngularMomentum;
public float3x3 Orientation;

public float3x3 BallOrientation;
public float3x3 BallOrientationForUnity;
public float Radius;
public float Mass;
public bool IsFrozen;
Expand All @@ -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,
Expand All @@ -109,11 +95,11 @@ public float CollisionRadiusSqr {
}

/// <summary>
/// Calculates Moment of Inertia for a Ball
/// Calculates Moment of Inertia for a Ball
/// https://en.wikipedia.org/wiki/Moment_of_inertia#Examples_2
/// </summary>
public float Inertia => 2.0f / 5.0f * Radius * Radius * Mass;

public float InvMass => 1f / Mass;

public void ApplySurfaceImpulse(in float3 rotI, in float3 impulse)
Expand All @@ -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<BallInsideOfBufferElement> insideOfs, in Entity entity)
Expand All @@ -147,7 +159,7 @@ public static void SetOutsideOf(ref DynamicBuffer<BallInsideOfBufferElement> ins

public static void SetInsideOf(ref DynamicBuffer<BallInsideOfBufferElement> insideOfs, Entity entity)
{
insideOfs.Add(new BallInsideOfBufferElement {Value = entity});
insideOfs.Add(new BallInsideOfBufferElement { Value = entity });
}

public static bool IsOutsideOf(in DynamicBuffer<BallInsideOfBufferElement> insideOfs, in Entity entity)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Unity.Entities;
using Unity.Mathematics;
using Unity.Profiling;
using UnityEngine;

namespace VisualPinball.Unity
{
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down