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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class BumperColliderInspector : ColliderInspector<BumperData, BumperCompo
private SerializedProperty _thresholdProperty;
private SerializedProperty _forceProperty;
private SerializedProperty _scatterProperty;
private SerializedProperty _isKinematicProperty;

protected override void OnEnable()
{
Expand All @@ -37,6 +38,7 @@ protected override void OnEnable()
_forceProperty = serializedObject.FindProperty(nameof(BumperColliderComponent.Force));
_scatterProperty = serializedObject.FindProperty(nameof(BumperColliderComponent.Scatter));
_hitEventProperty = serializedObject.FindProperty(nameof(BumperColliderComponent.HitEvent));
_isKinematicProperty = serializedObject.FindProperty(nameof(BumperColliderComponent._isKinematic));
}

public override void OnInspectorGUI()
Expand All @@ -47,6 +49,7 @@ public override void OnInspectorGUI()

BeginEditing();

PropertyField(_isKinematicProperty, "Movable");
PropertyField(_hitEventProperty, "Has Hit Event");
PropertyField(_forceProperty);
PropertyField(_thresholdProperty, "Hit Threshold");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class PrimitiveColliderInspector : ColliderInspector<PrimitiveData, Primi
private SerializedProperty _collisionReductionFactorProperty;
private SerializedProperty _overwritePhysicsProperty;
private SerializedProperty _physicsMaterialProperty;
private SerializedProperty _isKinematicProperty;

protected override void OnEnable()
{
Expand All @@ -50,6 +51,7 @@ protected override void OnEnable()
_elasticityFalloffProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.ElasticityFalloff));
_frictionProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.Friction));
_scatterProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent.Scatter));
_isKinematicProperty = serializedObject.FindProperty(nameof(PrimitiveColliderComponent._isKinematic));
}

public override void OnInspectorGUI()
Expand All @@ -62,6 +64,7 @@ public override void OnInspectorGUI()

OnPreInspectorGUI();

PropertyField(_isKinematicProperty, "Movable");
PropertyField(_hitEventProperty, "Has Hit Event");
PropertyField(_thresholdProperty, "Hit Threshold");
PropertyField(_collisionReductionFactorProperty, "Reduce Polygons By");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ public static void RotationAroundAxis(this float3x3 m, float3 axis, float rSin,
m.c2.z = axis.z * axis.z + rCos * (1.0f - axis.z * axis.z);
}

public static float3 GetScale(this float4x4 m)
{
return new float3(
math.length(new float3(m.c0.x, m.c1.x, m.c2.x)),
math.length(new float3(m.c0.y, m.c1.y, m.c2.y)),
math.length(new float3(m.c0.z, m.c1.z, m.c2.z))
);
}

public static Vertex3D ToVertex3D(this Vector3 vector)
{
return new Vertex3D(vector.x, vector.y, vector.z);
Expand Down
30 changes: 23 additions & 7 deletions VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati
// it's okay to have this code outside of the inner loop, as the ball hitrects already include the maximum distance they can travel in that timespan
var ballOctree = PhysicsDynamicBroadPhase.CreateOctree(ref state.Balls, in playfieldBounds);

// create octree of kinematic-to-ball collision
PhysicsKinematicBroadPhase.TransformColliders(ref state);
var kineticOctree = PhysicsKinematicBroadPhase.CreateOctree(ref state.KinematicColliders, in playfieldBounds);

while (dTime > 0) {

var hitTime = dTime; // begin time search from now ... until delta ends
Expand All @@ -63,9 +67,21 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati
continue;
}

// hit testing
// init contacts and event
ball.CollisionEvent.ClearCollider(hitTime); // search upto current hit time

// hit testing (overlappingColliders is cleared in broad phase)
PhysicsStaticBroadPhase.FindOverlaps(in state.Octree, in ball, ref overlappingColliders);
PhysicsStaticNarrowPhase.FindNextCollision(hitTime, ref ball, ref overlappingColliders, ref _contacts, ref state);
PhysicsStaticNarrowPhase.FindNextCollision(ref state.Colliders, ref ball, ref overlappingColliders, ref _contacts, ref state);

PhysicsStaticBroadPhase.FindOverlaps(in kineticOctree, in ball, ref overlappingColliders);
PhysicsStaticNarrowPhase.FindNextCollision(ref state.KinematicColliders, ref ball, ref overlappingColliders, ref _contacts, ref state);

// no negative time allowed
if (ball.CollisionEvent.HitTime < 0) {
ball.CollisionEvent.ClearCollider();
}

PhysicsDynamicBroadPhase.FindOverlaps(in ballOctree, in ball, ref overlappingColliders, ref state.Balls);
PhysicsDynamicNarrowPhase.FindNextCollision(ref ball, ref overlappingColliders, ref _contacts, ref state);

Expand All @@ -87,31 +103,31 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati
using (var enumerator = state.FlipperStates.GetEnumerator()) {
while (enumerator.MoveNext()) {
ref var flipperState = ref enumerator.Current.Value;
FlipperDisplacementPhysics.UpdateDisplacement(flipperState.ItemId, ref flipperState.Movement,
FlipperDisplacementPhysics.UpdateDisplacement(enumerator.Current.Key, ref flipperState.Movement,
ref flipperState.Tricks, in flipperState.Static, hitTime, ref state.EventQueue);
}
}
// gates
using (var enumerator = state.GateStates.GetEnumerator()) {
while (enumerator.MoveNext()) {
ref var gateState = ref enumerator.Current.Value;
GateDisplacementPhysics.UpdateDisplacement(gateState.ItemId, ref gateState.Movement, in gateState.Static,
GateDisplacementPhysics.UpdateDisplacement(enumerator.Current.Key, ref gateState.Movement, in gateState.Static,
hitTime, ref state.EventQueue);
}
}
// plunger
using (var enumerator = state.PlungerStates.GetEnumerator()) {
while (enumerator.MoveNext()) {
ref var plungerState = ref enumerator.Current.Value;
PlungerDisplacementPhysics.UpdateDisplacement(plungerState.ItemId, ref plungerState.Movement, ref plungerState.Collider,
PlungerDisplacementPhysics.UpdateDisplacement(enumerator.Current.Key, ref plungerState.Movement, ref plungerState.Collider,
in plungerState.Static, hitTime, ref state.EventQueue);
}
}
// spinners
using (var enumerator = state.SpinnerStates.GetEnumerator()) {
while (enumerator.MoveNext()) {
ref var spinnerState = ref enumerator.Current.Value;
SpinnerDisplacementPhysics.UpdateDisplacement(spinnerState.ItemId, ref spinnerState.Movement, in spinnerState.Static,
SpinnerDisplacementPhysics.UpdateDisplacement(enumerator.Current.Key, ref spinnerState.Movement, in spinnerState.Static,
hitTime, ref state.EventQueue);
}
}
Expand All @@ -128,7 +144,7 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati
// dynamic collision
PhysicsDynamicCollision.Collide(hitTime, ref ball, ref state);

// static collision
// static & kinematic collision
PhysicsStaticCollision.Collide(hitTime, ref ball, ref state);
}
}
Expand Down
66 changes: 63 additions & 3 deletions VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ public class PhysicsEngine : MonoBehaviour
[NonSerialized] private InsideOfs _insideOfs;
[NonSerialized] private NativeOctree<int> _octree;
[NonSerialized] private NativeColliders _colliders;
[NonSerialized] private NativeColliders _kinematicColliders;
[NonSerialized] private NativeColliders _kinematicCollidersAtIdentity;
[NonSerialized] private NativeParallelHashMap<int, NativeColliderIds> _kinematicColliderLookups;
[NonSerialized] private NativeArray<PhysicsEnv> _physicsEnv = new(1, Allocator.Persistent);
[NonSerialized] private NativeQueue<EventData> _eventQueue = new(Allocator.Persistent);

Expand All @@ -67,6 +70,8 @@ public class PhysicsEngine : MonoBehaviour
#region Transforms

[NonSerialized] private readonly Dictionary<int, Transform> _transforms = new();
[NonSerialized] private NativeParallelHashMap<int, float4x4> _kinematicTransforms = new(0, Allocator.Persistent);
[NonSerialized] private NativeParallelHashMap<int, float4x4> _updatedKinematicTransforms = new(0, Allocator.Persistent);
[NonSerialized] private readonly Dictionary<int, SkinnedMeshRenderer[]> _skinnedMeshRenderers = new();

#endregion
Expand All @@ -75,6 +80,8 @@ public class PhysicsEngine : MonoBehaviour
[NonSerialized] private readonly List<ScheduledAction> _scheduledActions = new();

[NonSerialized] private Player _player;
[NonSerialized] private IKinematicColliderComponent[] _kinematicColliderComponents;


private static ulong NowUsec => (ulong)(Time.timeAsDouble * 1000000);

Expand Down Expand Up @@ -166,6 +173,7 @@ private void Awake()
_player = GetComponentInParent<Player>();
_insideOfs = new InsideOfs(Allocator.Persistent);
_physicsEnv[0] = new PhysicsEnv(NowUsec, GetComponentInChildren<PlayfieldComponent>(), GravityStrength);
_kinematicColliderComponents = GetComponentsInChildren<IKinematicColliderComponent>();
}

private void Start()
Expand All @@ -175,15 +183,27 @@ private void Start()
var colliderItems = GetComponentsInChildren<ICollidableComponent>();
Debug.Log($"Found {colliderItems.Length} collidable items.");
var colliders = new ColliderReference(Allocator.TempJob);
var kinematicColliders = new ColliderReference(Allocator.TempJob, true);
foreach (var colliderItem in colliderItems) {
if (!colliderItem.IsCollidable) {
_disabledCollisionItems.Add(colliderItem.ItemId);
}
colliderItem.GetColliders(_player, ref colliders, 0);
colliderItem.GetColliders(_player, ref colliders, ref kinematicColliders, 0);
}

// allocate colliders
_colliders = new NativeColliders(ref colliders, Allocator.Persistent);
_kinematicColliders = new NativeColliders(ref kinematicColliders, Allocator.Persistent);

// get kinetic collider matrices
foreach (var coll in _kinematicColliderComponents) {
_kinematicTransforms[coll.ItemId] = coll.TransformationMatrix;
}
_kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent);

// create identity kinematic colliders
kinematicColliders.TransformToIdentity(_kinematicTransforms);
_kinematicCollidersAtIdentity = new NativeColliders(ref kinematicColliders, Allocator.Persistent);

// create octree
var elapsedMs = sw.Elapsed.TotalMilliseconds;
Expand All @@ -209,6 +229,21 @@ private void Start()

private void Update()
{
// check for updated kinematic transforms
_updatedKinematicTransforms.Clear();
foreach (var coll in _kinematicColliderComponents) {
if (!coll.IsKinematic) { // kinematic enabled?
continue;
}
var lastTransformationMatrix = _kinematicTransforms[coll.ItemId];
var currTransformationMatrix = coll.TransformationMatrix;
if (lastTransformationMatrix.Equals(currTransformationMatrix)) {
continue;
}
_updatedKinematicTransforms.Add(coll.ItemId, currTransformationMatrix);
_kinematicTransforms[coll.ItemId] = currTransformationMatrix;
}

// prepare job
var events = _eventQueue.AsParallelWriter();
var updatePhysics = new PhysicsUpdateJob {
Expand All @@ -217,6 +252,10 @@ private void Update()
PhysicsEnv = _physicsEnv,
Octree = _octree,
Colliders = _colliders,
KinematicColliders = _kinematicColliders,
KinematicCollidersAtIdentity = _kinematicCollidersAtIdentity,
KinematicColliderLookups = _kinematicColliderLookups,
UpdatedKinematicTransforms = _updatedKinematicTransforms,
InsideOfs = _insideOfs,
Events = events,
Balls = _ballStates,
Expand All @@ -236,8 +275,9 @@ private void Update()
};

var env = _physicsEnv[0];
var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref events, ref _insideOfs, ref _ballStates,
ref _bumperStates, ref _dropTargetStates, ref _flipperStates, ref _gateStates,
var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref _kinematicColliders,
ref _kinematicCollidersAtIdentity, ref _updatedKinematicTransforms, ref _kinematicColliderLookups, ref events,
ref _insideOfs, ref _ballStates, ref _bumperStates, ref _dropTargetStates, ref _flipperStates, ref _gateStates,
ref _hitTargetStates, ref _kickerStates, ref _plungerStates, ref _spinnerStates,
ref _surfaceStates, ref _triggerStates, ref _disabledCollisionItems, ref _swapBallCollisionHandling);

Expand Down Expand Up @@ -402,6 +442,15 @@ private void OnDestroy()
}
_triggerStates.Dispose();
_disabledCollisionItems.Dispose();
_kinematicTransforms.Dispose();
_updatedKinematicTransforms.Dispose();
using (var enumerator = _kinematicColliderLookups.GetEnumerator()) {
while (enumerator.MoveNext()) {
enumerator.Current.Value.Dispose();
}
}
_kinematicColliderLookups.Dispose();

}

#endregion
Expand All @@ -417,5 +466,16 @@ public ScheduledAction(ulong scheduleAt, Action action)
Action = action;
}
}

public ICollider[] GetKinematicColliders(int itemId)
{
ref var colliderIds = ref _kinematicColliderLookups.GetValueByRef(itemId);
var colliders = new ICollider[colliderIds.Length];
for (var i = 0; i < colliderIds.Length; i++) {
var colliderId = colliderIds[i];
colliders[i] = _kinematicColliders[colliderId];
}
return colliders;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Visual Pinball Engine
// Copyright (C) 2023 freezy and VPE Team
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

using NativeTrees;
using Unity.Collections;
using Unity.Profiling;
using VisualPinball.Unity.Collections;

namespace VisualPinball.Unity
{
public static class PhysicsKinematicBroadPhase
{
private static readonly ProfilerMarker PerfMarkerBallOctree = new("CreateKinematicOctree");

internal static void TransformColliders(ref PhysicsState state)
{
using var enumerator = state.UpdatedKinematicTransforms.GetEnumerator();
while (enumerator.MoveNext()) {
ref var matrix = ref enumerator.Current.Value;
var itemId = enumerator.Current.Key;

ref var colliderLookups = ref state.KinematicColliderLookups.GetValueByRef(itemId);
for (var i = 0; i < colliderLookups.Length; i++) {
state.Transform(colliderLookups[i], matrix);
}
}
}

internal static NativeOctree<int> CreateOctree(ref NativeColliders kinematicColliders, in AABB playfieldBounds)
{
PerfMarkerBallOctree.Begin();
var octree = new NativeOctree<int>(playfieldBounds, 1024, 10, Allocator.TempJob);

for (var i = 0; i < kinematicColliders.Length; i++) {
octree.Insert(i, kinematicColliders.GetAabb(i));
}

PerfMarkerBallOctree.End();
return octree;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading