From 17ac7fbff96dcb73d6cd621625f23c423f2e0752 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 5 Nov 2023 13:20:23 +0100 Subject: [PATCH 001/208] doc: Update changelog. --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188acefb0..684744feb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,11 @@ ## Unreleased -Built with Unity 2022.3.0 +Built with Unity 2022.3.x ### Added +- Kinematic collisions ([#460](https://github.com/freezy/VisualPinball.Engine/pull/460)) - Flipper tricks by nFozzy ([#436](https://github.com/freezy/VisualPinball.Engine/pull/436)) - Asset Library now has thumbnails. - Documentation for score reels. From b01e39773e4bf65faedb6788b5385769e3c2c307 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 5 Nov 2023 21:45:39 +0100 Subject: [PATCH 002/208] transcol: Make rubber colliders transformable. --- .../Physics/Collider/Line3DCollider.cs | 6 ++++++ .../Physics/Collider/PointCollider.cs | 6 ++++++ .../Physics/Collider/TriangleCollider.cs | 6 ++++++ .../VisualPinball.Unity/VPT/Rubber/RubberApi.cs | 8 +++++++- .../VPT/Rubber/RubberColliderGenerator.cs | 14 ++++++++------ 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index 77745a7f6..568f3c79e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -148,5 +148,11 @@ public void Transform(Line3DCollider line3D, float4x4 matrix) math.mul(matrix, new float4(line3D._v2, 1f)).xyz ); } + + public Line3DCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index 2110c4be7..fcac60baa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -144,5 +144,11 @@ public void Transform(PointCollider point, float4x4 matrix) { P = math.mul(matrix, new float4(point.P, 1f)).xyz; } + + public PointCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index 7a9d2ecbf..c21bc4a56 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -190,5 +190,11 @@ public void Transform(TriangleCollider triangle, float4x4 matrix) Rgv2 = math.mul(matrix, new float4(triangle.Rgv2, 1f)).xyz; _normal = math.normalizesafe(math.cross(Rgv2 - Rgv0, Rgv1 - Rgv0)); } + + public TriangleCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index b4c0b0d2c..117bbfad4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Rubber; @@ -46,7 +47,12 @@ internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var colliderGenerator = new RubberColliderGenerator(this, new RubberMeshGenerator(MainComponent)); + var worldToLocal = (float4x4)MainComponent.transform.worldToLocalMatrix; + var colliderGenerator = new RubberColliderGenerator( + this, + new RubberMeshGenerator(MainComponent), + math.mul(math.mul(Physics.WorldToVpx, math.inverse(worldToLocal)), Physics.VpxToWorld) + ); colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs index f5786261b..5a623923a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using Unity.Collections; +using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Rubber; @@ -25,11 +26,13 @@ public class RubberColliderGenerator { private readonly IApiColliderGenerator _api; private readonly RubberMeshGenerator _meshGenerator; + private readonly float4x4 _matrix; - public RubberColliderGenerator(RubberApi rubberApi, RubberMeshGenerator meshGenerator) + public RubberColliderGenerator(RubberApi rubberApi, RubberMeshGenerator meshGenerator, float4x4 matrix) { _api = rubberApi; _meshGenerator = meshGenerator; + _matrix = matrix; } internal void GenerateColliders(float playfieldHeight, float hitHeight, int detailLevel, ref ColliderReference colliders, float margin) @@ -44,7 +47,7 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, int deta var rg1 = mesh.Vertices[mesh.Indices[i + 2]].ToUnityFloat3(); var rg2 = mesh.Vertices[mesh.Indices[i + 1]].ToUnityFloat3(); - colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); + colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()).Transform(_matrix)); GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i], mesh.Indices[i + 2], ref colliders); GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], ref colliders); @@ -53,19 +56,18 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, int deta // add collision vertices foreach (var mv in mesh.Vertices) { - colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo())); + colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo()).Transform(_matrix)); } addedEdges.Dispose(); } - private void GenerateHitEdge(Mesh mesh, ref EdgeSet addedEdges, int i, int j, - ref ColliderReference colliders) + private void GenerateHitEdge(Mesh mesh, ref EdgeSet addedEdges, int i, int j, ref ColliderReference colliders) { if (addedEdges.ShouldAddHitEdge(i, j)) { var v1 = mesh.Vertices[i].ToUnityFloat3(); var v2 = mesh.Vertices[j].ToUnityFloat3(); - colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo()).Transform(_matrix)); } } } From 42c92f7aa332ede3e7b1a72a6086358627743040 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Nov 2023 23:39:05 +0100 Subject: [PATCH 003/208] kinematic: Add support for rubbers. --- .../DragPoint/DragPointsSceneViewHandler.cs | 15 ++++++++++----- .../VPT/Rubber/RubberColliderInspector.cs | 3 +++ .../VPT/Rubber/RubberInspector.cs | 5 ++++- .../VisualPinball.Unity/VPT/Rubber/RubberApi.cs | 7 ++++++- .../VPT/Rubber/RubberColliderComponent.cs | 14 +++++++++++++- .../VPT/Rubber/RubberComponent.cs | 6 ++++++ 6 files changed, 42 insertions(+), 8 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index 297af2334..e6d5a375f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -16,12 +16,12 @@ using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEditor; using UnityEngine; using UnityEngine.Profiling; using VisualPinball.Engine.Math; using Color = UnityEngine.Color; -using Matrix4x4 = UnityEngine.Matrix4x4; using Quaternion = UnityEngine.Quaternion; using Vector3 = UnityEngine.Vector3; @@ -49,6 +49,8 @@ public class DragPointsSceneViewHandler public Color CurveSlingShotColor { get; set; } = Color.red; + private float4x4 _matrix; + public DragPointsSceneViewHandler(DragPointsHandler handler) { _handler = handler; @@ -59,6 +61,8 @@ public void OnSceneGUI() if (_handler == null) { return; } + + _matrix = math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix); DisplayCurve(); DisplayControlPoints(); @@ -121,14 +125,14 @@ private void DisplayCurve() } } Profiler.EndSample(); - + // close loop if needed Profiler.BeginSample("Close Loops"); if (_handler.DragPointInspector.PointsAreLooping) { curveVerticesByDragPoint[_handler.ControlPoints.Count - 1].Add(curveVerticesByDragPoint[0][0]); } Profiler.EndSample(); - + // construct full path Profiler.BeginSample("Construct full path"); _pathPoints.Clear(); @@ -177,7 +181,7 @@ private void DisplayCurve() // Render Curve with correct color regarding drag point properties & find curve section where the curve traveller is _handler.CurveTravellerControlPointIdx = -1; var minDist = float.MaxValue; - Handles.matrix = Matrix4x4.identity; + Handles.matrix = _matrix; foreach (var controlPoint in _handler.ControlPoints) { Profiler.BeginSample("Compute Segments"); var segments = curveVerticesByDragPoint[controlPoint.Index].Select(cp => cp.TranslateToWorld()).ToArray(); @@ -218,10 +222,11 @@ private void DisplayCurve() /// private void DisplayControlPoints() { + var matrix = math.mul(math.mul(Physics.WorldToVpx,math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix)), Physics.VpxToWorld); Profiler.BeginSample("DisplayControlPoints"); // Render Control Points and check traveler distance from CP var distToCPoint = Mathf.Infinity; - Handles.matrix = Matrix4x4.identity; + Handles.matrix = _matrix; var style = new GUIStyle { alignment = TextAnchor.MiddleCenter, }; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberColliderInspector.cs index 8ee777099..d062fa809 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberColliderInspector.cs @@ -33,6 +33,7 @@ public class RubberColliderInspector : ColliderInspector + public class RubberColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -51,10 +52,21 @@ public class RubberColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.RubberApi ?? new RubberApi(gameObject, player, physicsEngine); + + #region IKinematicColliderComponent + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index ecc4f402e..6b425e3ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -24,7 +24,9 @@ using System; using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEngine; +using UnityEngine.UIElements; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Rubber; @@ -104,6 +106,10 @@ private void Awake() public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); + public float4x4 TransformationMatrix + => math.mul(math.mul(Physics.WorldToVpx, math.inverse(transform.worldToLocalMatrix)), Physics.VpxToWorld); + + #endregion #region Conversion From b2ae30002d20e8ae6437cd325f391258a80a6b63 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Nov 2023 23:39:43 +0100 Subject: [PATCH 004/208] physics: Better error message when we run out of ball bits in InsideOfs. --- VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs index 9ab3877c3..0a545ea05 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs @@ -119,7 +119,8 @@ private int GetBitIndex(int ballId) indices.Dispose(); return i; } - throw new IndexOutOfRangeException(); + using var ballIds = _bitLookup.GetKeyArray(Allocator.Temp); + throw new IndexOutOfRangeException($"Bit index in InsideOfs is full, currently stored ball IDs: {string.Join(", ", ballIds)}"); } private bool TryGetBallId(int bitIndex, out int ballId) From 51a2bcaa0e7982c0c9b298bf801ccde3c5ac9d25 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 7 Nov 2023 20:18:35 +0100 Subject: [PATCH 005/208] fix: Remove ball from InsideOfs when destroyed. --- .../VisualPinball.Unity/Game/InsideOfs.cs | 54 +++++++++++++++---- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 1 + 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs index 0a545ea05..bde4d56b9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs @@ -8,13 +8,26 @@ namespace VisualPinball.Unity { internal struct InsideOfs : IDisposable { + /** + * Stores at which position in the bit array(s) a ball is tracked.
+ * + * Key: Ball ID + * Value: Index in the bit array + */ private NativeParallelHashMap _bitLookup; + + /** + * Stores which balls are inside of an item.
+ * + * Key: Item ID + * Value: A bit array of ball IDs, up to 64 balls. + */ private NativeParallelHashMap _insideOfs; public InsideOfs(Allocator allocator) { - _bitLookup = new NativeParallelHashMap(64, allocator); - _insideOfs = new NativeParallelHashMap(64, allocator); + _bitLookup = new NativeParallelHashMap(8, allocator); + _insideOfs = new NativeParallelHashMap(16, allocator); } internal void SetInsideOf(int itemId, int ballId) @@ -27,6 +40,22 @@ internal void SetInsideOf(int itemId, int ballId) bits.SetBits(GetBitIndex(ballId), true); } + internal void SetOutsideOfAll(int ballId) // aka ball destroyed + { + if (!_bitLookup.ContainsKey(ballId)) { + return; + } + var bitIndex = _bitLookup[ballId]; + using (var enumerator = _insideOfs.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var ballIndices = ref enumerator.Current.Value; + ballIndices.SetBits(bitIndex, false); + } + } + + _bitLookup.Remove(ballId); + } + internal void SetOutsideOf(int itemId, int ballId) { if (!_insideOfs.ContainsKey(itemId)) { @@ -92,14 +121,16 @@ private void ClearItems(int itemId) private void ClearBitIndex(int ballId) { - var maps = _insideOfs.GetValueArray(Allocator.Temp); var index = GetBitIndex(ballId); - foreach (var ballIndices in maps) { - if (!ballIndices.IsSet(index)) { - continue; + // for each item bitfield, check if the ball is in there. if not, remove the bit index + using (var enumerator = _insideOfs.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var ballIndices = ref enumerator.Current.Value; + if (!ballIndices.IsSet(index)) { + continue; + } + return; } - maps.Dispose(); - return; } _bitLookup.Remove(ballId); } @@ -110,15 +141,16 @@ private int GetBitIndex(int ballId) return _bitLookup[ballId]; } - var indices = _bitLookup.GetValueArray(Allocator.Temp); + var bitArrayIndices = _bitLookup.GetValueArray(Allocator.Temp); // todo don't copy but ref for (var i = 0; i < 64; i++) { - if (indices.Contains(i)) { + if (bitArrayIndices.Contains(i)) { continue; } _bitLookup[ballId] = i; - indices.Dispose(); + bitArrayIndices.Dispose(); return i; } + bitArrayIndices.Dispose(); using var ballIds = _bitLookup.GetKeyArray(Allocator.Temp); throw new IndexOutOfRangeException($"Bit index in InsideOfs is full, currently stored ball IDs: {string.Join(", ", ballIds)}"); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 426614100..d181a2697 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -149,6 +149,7 @@ internal Transform UnregisterBall(int ballId) var transform = _transforms[ballId]; _transforms.Remove(ballId); _ballStates.Ref.Remove(ballId); + _insideOfs.SetOutsideOfAll(ballId); return transform; } From 340783b2a81bbe92b4593f1c9842bbfa66f4a0b2 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 7 Nov 2023 22:44:13 +0100 Subject: [PATCH 006/208] physics: Move kinematic collider transformation out of outer loop. --- .../VisualPinball.Unity/Game/PhysicsCycle.cs | 6 +----- ...PhysicsKinematicBroadPhase.cs => PhysicsKinematics.cs} | 5 ++++- ...ematicBroadPhase.cs.meta => PhysicsKinematics.cs.meta} | 0 .../VisualPinball.Unity/Game/PhysicsUpdateJob.cs | 8 +++++++- 4 files changed, 12 insertions(+), 7 deletions(-) rename VisualPinball.Unity/VisualPinball.Unity/Game/{PhysicsKinematicBroadPhase.cs => PhysicsKinematics.cs} (88%) rename VisualPinball.Unity/VisualPinball.Unity/Game/{PhysicsKinematicBroadPhase.cs.meta => PhysicsKinematics.cs.meta} (100%) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs index d9418478e..79f3e080e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs @@ -37,7 +37,7 @@ public PhysicsCycle(Allocator a) _contacts = new NativeList(a); } - internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref NativeParallelHashSet overlappingColliders, float dTime) + internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref NativeParallelHashSet overlappingColliders, ref NativeOctree kineticOctree, float dTime) { PerfMarker.Begin(); var staticCounts = PhysicsConstants.StaticCnts; @@ -46,10 +46,6 @@ 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 using var ballOctree = PhysicsDynamicBroadPhase.CreateOctree(ref state.Balls, in playfieldBounds); - // create octree of kinematic-to-ball collision - PhysicsKinematicBroadPhase.TransformColliders(ref state); - using var kineticOctree = PhysicsKinematicBroadPhase.CreateOctree(ref state.KinematicColliders, in playfieldBounds); - while (dTime > 0) { var hitTime = dTime; // begin time search from now ... until delta ends diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematicBroadPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematicBroadPhase.cs rename to VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs index 04b30ead8..4aa1b9ae3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematicBroadPhase.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs @@ -21,12 +21,14 @@ namespace VisualPinball.Unity { - public static class PhysicsKinematicBroadPhase + public static class PhysicsKinematics { + private static readonly ProfilerMarker PerfMarkerTransform = new("TransformColliders"); private static readonly ProfilerMarker PerfMarkerBallOctree = new("CreateKinematicOctree"); internal static void TransformColliders(ref PhysicsState state) { + PerfMarkerTransform.Begin(); using var enumerator = state.UpdatedKinematicTransforms.GetEnumerator(); while (enumerator.MoveNext()) { ref var matrix = ref enumerator.Current.Value; @@ -37,6 +39,7 @@ internal static void TransformColliders(ref PhysicsState state) state.Transform(colliderLookups[i], matrix); } } + PerfMarkerTransform.End(); } internal static NativeOctree CreateOctree(ref NativeColliders kinematicColliders, in AABB playfieldBounds) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematicBroadPhase.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematicBroadPhase.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index ae51e4cbf..a5173925a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -67,6 +67,10 @@ public void Execute() ref SurfaceStates, ref TriggerStates, ref DisabledCollisionItems, ref SwapBallCollisionHandling); using var cycle = new PhysicsCycle(Allocator.Temp); + // create octree of kinematic-to-ball collision. should be okay here, since static colliders don't transform more than once per frame. + PhysicsKinematics.TransformColliders(ref state); + var kineticOctree = PhysicsKinematics.CreateOctree(ref state.KinematicColliders, in PlayfieldBounds); + while (env.CurPhysicsFrameTime < InitialTimeUsec) // loop here until current (real) time matches the physics (simulated) time { env.TimeMsec = (uint)((env.CurPhysicsFrameTime - env.StartTimeUsec) / 1000); @@ -112,7 +116,7 @@ public void Execute() #endregion // primary physics loop - cycle.Simulate(ref state, in PlayfieldBounds, ref OverlappingColliders, physicsDiffTime); + cycle.Simulate(ref state, in PlayfieldBounds, ref OverlappingColliders, ref kineticOctree, physicsDiffTime); // ball trail, keep old pos of balls using (var enumerator = state.Balls.GetEnumerator()) { @@ -123,6 +127,8 @@ public void Execute() #region Animation + // todo it should be enough to calculate animations only once per frame + // bumper using (var enumerator = BumperStates.GetEnumerator()) { while (enumerator.MoveNext()) { From 52c1dff82f62fca78ab22cf4daa0a551573d23fc Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 7 Nov 2023 22:44:50 +0100 Subject: [PATCH 007/208] rubbers: Transform relative to playfield, not to world. --- .../VisualPinball.Unity/VPT/Rubber/RubberApi.cs | 6 ++++-- .../VPT/Rubber/RubberComponent.cs | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index d1fbbb9c8..241aef855 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -47,11 +47,13 @@ internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var worldToLocal = (float4x4)MainComponent.transform.worldToLocalMatrix; + var playfield = MainComponent.GetComponentInParent(); + var playfieldToWorld = (float4x4)playfield.transform.localToWorldMatrix; + var localToPlayfield = math.inverse(math.mul(MainComponent.transform.worldToLocalMatrix, playfieldToWorld)); var colliderGenerator = new RubberColliderGenerator( this, new RubberMeshGenerator(MainComponent), - math.mul(math.mul(Physics.WorldToVpx, math.inverse(worldToLocal)), Physics.VpxToWorld) + math.mul(math.mul(Physics.WorldToVpx, localToPlayfield), Physics.VpxToWorld) ); if (ColliderComponent.IsKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index 6b425e3ee..a17afcc85 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -59,6 +59,9 @@ public class RubberComponent : MainRenderableComponent, [NonSerialized] private Vertex3D[] _scalingDragPoints; + [NonSerialized] + private float4x4 _playfieldToWorld; + #endregion #region IRubberData @@ -100,15 +103,23 @@ private void Awake() RegisterPhysics(physicsEngine); } + private void Start() + { + var playfield = GetComponentInParent(); + _playfieldToWorld = playfield.transform.localToWorldMatrix; + } + #endregion #region Transformation public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationMatrix - => math.mul(math.mul(Physics.WorldToVpx, math.inverse(transform.worldToLocalMatrix)), Physics.VpxToWorld); - + public float4x4 TransformationMatrix => math.mul( + math.mul(Physics.WorldToVpx, + math.inverse(math.mul(transform.worldToLocalMatrix, _playfieldToWorld)) + ), + Physics.VpxToWorld); #endregion From 4d27eb91d6f42f21711c1b46dfd87647112ececa Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 8 Nov 2023 00:07:58 +0100 Subject: [PATCH 008/208] jobs: Fix memory leak. --- .../VisualPinball.Unity/Physics/Collider/ColliderReference.cs | 1 + VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 5ca5b4aa2..23eb5b43c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -81,6 +81,7 @@ public void Dispose() } } _itemIdToColliderIds.Dispose(); + Lookups.Dispose(); } public int Count => Lookups.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 6136a0ca7..17a81e83a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -178,7 +178,6 @@ private void OnDrawGizmos() colliders.Dispose(); kinematicColliders.Dispose(); } - } } if (ShowColliderOctree) { From c779251bdf9d76fe863df46f4f1174b3d793ab97 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 9 Nov 2023 00:24:39 +0100 Subject: [PATCH 009/208] transcol: Make dragpoint gizmos transformable. --- .../DragPoint/DragPointsHandler.cs | 27 ++++++++++++------- .../DragPoint/DragPointsSceneViewHandler.cs | 16 ++++++----- .../VisualPinball.Unity/Game/PhysicsState.cs | 2 +- .../Physics/Collider/PointCollider.cs | 2 +- .../VisualPinball.Unity/Physics/Physics.cs | 20 +++++++++++++- .../VPT/IKinematicColliderComponent.cs | 4 +++ .../VPT/Rubber/RubberApi.cs | 1 - .../VPT/Rubber/RubberComponent.cs | 6 +---- 8 files changed, 54 insertions(+), 24 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index f93b926c2..ee5553a5e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEditor; using UnityEngine; using VisualPinball.Engine.Math; @@ -95,6 +96,7 @@ public class DragPointsHandler private Vector3 _startPos; private readonly Dictionary _startPosZ = new(); private float[] _startTopBottomZ; + private readonly Matrix4x4 _playfieldToWorld; /// /// Every DragPointsInspector instantiates this to manage its curve handling. @@ -106,6 +108,8 @@ public DragPointsHandler(IMainRenderableComponent mainComponent, IDragPointsInsp { MainComponent = mainComponent; DragPointInspector = dragPointsInspector; + var playfield = mainComponent.gameObject.GetComponentInParent(); + _playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : Matrix4x4.identity; Transform = mainComponent.gameObject.transform; @@ -311,7 +315,7 @@ public void OnSceneGUI(Event evt, DragPointPositionChange onChange = null) } if (SelectedControlPoints.Count > 0) { - + // set start positions since clicked if (evt.type == EventType.MouseDown) { _startPos = _centerSelected; @@ -321,13 +325,18 @@ public void OnSceneGUI(Event evt, DragPointPositionChange onChange = null) _startPosZ[cp.DragPointId] = cp.DragPoint.Center.Z; } } - + + // convert from vpx space to vpx space transformed + var matrix = MainComponent.gameObject.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + var centerSelected = matrix.MultiplyPoint(_centerSelected); + var startPos = matrix.MultiplyPoint(_startPos); + // get new pos since last frame EditorGUI.BeginChangeCheck(); - var newHandlePos = HandlesUtils.HandlePosition(Transform.GetComponentInParent(), _centerSelected, DragPointInspector.HandleType); + var newHandlePos = (float3)HandlesUtils.HandlePosition(Transform.GetComponentInParent(), centerSelected, DragPointInspector.HandleType); if (EditorGUI.EndChangeCheck()) { - var delta = newHandlePos - _centerSelected; - var deltaZ = newHandlePos.z - _startPos.z; + var delta = newHandlePos - centerSelected; + var deltaZ = newHandlePos.z - startPos.z; Undo.RecordObject(MainComponent as MonoBehaviour, "move Drag Points"); foreach (var controlPoint in SelectedControlPoints) { @@ -356,11 +365,11 @@ private void OnSceneLayout() if (controlPoint.IsSelected && !controlPoint.DragPoint.IsLocked) { SelectedControlPoints.Add(controlPoint); } - + HandleUtility.AddControl( controlPoint.ControlId, HandleUtility.DistanceToCircle( - controlPoint.EditorPositionWorld, + MainComponent.gameObject.transform.localToWorldMatrix.MultiplyPoint(controlPoint.EditorPositionWorld), controlPoint.HandleSize ) ); @@ -373,8 +382,8 @@ private void OnSceneLayout() //Setup PositionHandle if some control points are selected if (SelectedControlPoints.Count > 0) { _centerSelected = Vector3.zero; - foreach (var sCp in SelectedControlPoints) { - _centerSelected += sCp.EditorPositionVpx; + foreach (var controlPoint in SelectedControlPoints) { + _centerSelected += controlPoint.EditorPositionVpx; } _centerSelected /= SelectedControlPoints.Count; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index e6d5a375f..0c6aa4015 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -22,8 +22,6 @@ using UnityEngine.Profiling; using VisualPinball.Engine.Math; using Color = UnityEngine.Color; -using Quaternion = UnityEngine.Quaternion; -using Vector3 = UnityEngine.Vector3; namespace VisualPinball.Unity.Editor { @@ -83,7 +81,7 @@ private void DisplayCurve() // Display Curve & handle curve traveller if (_handler.ControlPoints.Count > 1) { - + Profiler.BeginSample("Transform Points"); var dragPointsVpx = new DragPointData[_handler.ControlPoints.Count]; for (var i = 0; i < _handler.ControlPoints.Count; i++) { @@ -163,14 +161,14 @@ private void DisplayCurve() _curveTravellerMoved = false; if (_pathPoints.Count > 1) { Profiler.BeginSample("Convert Points"); - var points = _pathPoints.ToArray(); + var points = _pathPoints.Select(p => (Vector3)_matrix.MultiplyPoint(p)).ToArray(); Profiler.EndSample(); Profiler.BeginSample("Calculate closest"); var newPos = HandleUtility.ClosestPointToPolyLine(points); Profiler.EndSample(); Profiler.BeginSample("Calculate if moved"); if ((newPos - _handler.CurveTravellerPosition).magnitude >= HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius * CurveTravellerSizeRatio * 0.1f) { - _handler.CurveTravellerPosition = newPos; + _handler.CurveTravellerPosition = math.inverse(_matrix).MultiplyPoint(newPos); _curveTravellerMoved = true; } Profiler.EndSample(); @@ -250,7 +248,13 @@ private void DisplayControlPoints() // curve traveller is not overlapping a control point, we can draw it. if (distToCPoint > HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius) { Handles.color = Color.grey; - Handles.SphereHandleCap(_handler.CurveTravellerControlId, _handler.CurveTravellerPosition, Quaternion.identity, HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius * CurveTravellerSizeRatio, EventType.Repaint); + Handles.SphereHandleCap( + _handler.CurveTravellerControlId, + _handler.CurveTravellerPosition, + Quaternion.identity, + HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius * CurveTravellerSizeRatio, + EventType.Repaint + ); _handler.CurveTravellerVisible = true; if (EditorWindow.mouseOverWindow && _curveTravellerMoved) { HandleUtility.Repaint(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index fa05b9638..659cba0da 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -29,7 +29,7 @@ internal struct PhysicsState internal NativeColliders Colliders; internal NativeColliders KinematicColliders; internal NativeColliders KinematicCollidersAtIdentity; - internal NativeParallelHashMap UpdatedKinematicTransforms; + internal NativeParallelHashMap UpdatedKinematicTransforms; // transformations of the items, in vpx space. internal NativeParallelHashMap KinematicColliderLookups; internal NativeQueue.ParallelWriter EventQueue; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index fcac60baa..a7e154c36 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -142,7 +142,7 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrit public void Transform(PointCollider point, float4x4 matrix) { - P = math.mul(matrix, new float4(point.P, 1f)).xyz; + P = matrix.MultiplyPoint(point.P); } public PointCollider Transform(float4x4 matrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index e5f53c5cd..35ab7082a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -60,12 +60,30 @@ public static class Physics /// /// Use this on matrices that are generated for VPX-space transformations that you want to apply to a mesh that - /// has already been transformed to world-space. + /// has already been transformed to world-space. /// /// VPX-space matrix that is supposed to be applied to a VPX-space mesh /// Matrix that with the same transformation to be applied to a mesh converted to world-space. public static Matrix4x4 TransformVpxInWorld(this Matrix4x4 m) => math.mul(math.mul(VpxToWorld, m), WorldToVpx); + //public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.mul(matrix, new float4(p, 1f)).xyz; + public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.transform(matrix, p); + + /// + /// Returns the transformation matrix of an item in VPX space.
+ /// + /// You basically give the world-to-local transformation matrix of the item, and you'll get the + /// transformation in VPX space (i.e. relative to the playfield, however it's transformed). + ///
+ /// World-to-local transformation matrix of the item. + /// Local-to-World transformation matrix of the playfield. + /// Transformation matrix of the item in VPX space. + public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) => math.mul( + math.mul(Physics.WorldToVpx, + math.inverse(math.mul(worldToLocal, playfieldToWorld)) + ), + Physics.VpxToWorld); + #endregion #region Translation diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs index 203149523..5c0b6e69f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs @@ -22,6 +22,10 @@ public interface IKinematicColliderComponent { public bool IsKinematic { get; } public int ItemId { get; } + + /// + /// Transformation matrix of the collider in the scene, in VPX space. + /// public float4x4 TransformationMatrix { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index 241aef855..e43ece615 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -60,7 +60,6 @@ protected override void CreateColliders(ref ColliderReference colliders, } else { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } - } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index a17afcc85..25a98d705 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -115,11 +115,7 @@ private void Start() public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationMatrix => math.mul( - math.mul(Physics.WorldToVpx, - math.inverse(math.mul(transform.worldToLocalMatrix, _playfieldToWorld)) - ), - Physics.VpxToWorld); + public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion From 3efd67ec2b0ed08ac0d256774ee95ebc11643963 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 10 Nov 2023 00:25:11 +0100 Subject: [PATCH 010/208] jobs: Fix two more memory leaks. --- .../VisualPinball.Unity/Game/PhysicsCycle.cs | 3 +++ .../VisualPinball.Unity/Game/PhysicsEngine.cs | 2 ++ VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs | 6 +----- .../VisualPinball.Unity/Game/PhysicsUpdateJob.cs | 1 + 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs index 79f3e080e..14308f335 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs @@ -169,6 +169,9 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati state.SwapBallCollisionHandling = !state.SwapBallCollisionHandling; } + + ballOctree.Dispose(); + PerfMarker.End(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index d181a2697..5319aafa7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -410,6 +410,8 @@ private void Update() } #endregion + + overlappingColliders.Dispose(); } private void OnDestroy() diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs index 0d6d446bb..edca08c04 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs @@ -21,7 +21,7 @@ namespace VisualPinball.Unity { - public struct PhysicsEnv : IDisposable + public struct PhysicsEnv { public readonly float3 Gravity; public readonly ulong StartTimeUsec; @@ -39,9 +39,5 @@ public PhysicsEnv(ulong startTimeUsec, PlayfieldComponent playfield, float gravi Random = new Random((uint)UnityEngine.Random.Range(1, 100000)); Gravity = playfield.PlayfieldGravity(gravityStrength); } - - public void Dispose() - { - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index a5173925a..e341dad75 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -181,6 +181,7 @@ public void Execute() } PhysicsEnv[0] = env; + kineticOctree.Dispose(); } } } From 5672850d50ada68f4edfd9737a40d79cabb88d12 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 11 Nov 2023 16:15:47 +0100 Subject: [PATCH 011/208] dragpoints: Use tranformation matrix to render handles. --- .../DragPoint/DragPointsHandler.cs | 28 +++++++++---------- .../DragPoint/DragPointsInspectorHelper.cs | 1 + .../DragPoint/DragPointsSceneViewHandler.cs | 13 +++++---- .../Utils/HandlesUtils.cs | 9 +++--- .../VisualPinball.Unity/Physics/Physics.cs | 4 +-- 5 files changed, 28 insertions(+), 27 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index ee5553a5e..a3209865f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -96,7 +96,6 @@ public class DragPointsHandler private Vector3 _startPos; private readonly Dictionary _startPosZ = new(); private float[] _startTopBottomZ; - private readonly Matrix4x4 _playfieldToWorld; /// /// Every DragPointsInspector instantiates this to manage its curve handling. @@ -108,8 +107,6 @@ public DragPointsHandler(IMainRenderableComponent mainComponent, IDragPointsInsp { MainComponent = mainComponent; DragPointInspector = dragPointsInspector; - var playfield = mainComponent.gameObject.GetComponentInParent(); - _playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : Matrix4x4.identity; Transform = mainComponent.gameObject.transform; @@ -326,17 +323,16 @@ public void OnSceneGUI(Event evt, DragPointPositionChange onChange = null) } } - // convert from vpx space to vpx space transformed - var matrix = MainComponent.gameObject.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - var centerSelected = matrix.MultiplyPoint(_centerSelected); - var startPos = matrix.MultiplyPoint(_startPos); - // get new pos since last frame EditorGUI.BeginChangeCheck(); - var newHandlePos = (float3)HandlesUtils.HandlePosition(Transform.GetComponentInParent(), centerSelected, DragPointInspector.HandleType); + var newHandlePos = HandlesUtils.HandlePosition( + _centerSelected, + Transform.localToWorldMatrix, + DragPointInspector.HandleType + ); if (EditorGUI.EndChangeCheck()) { - var delta = newHandlePos - centerSelected; - var deltaZ = newHandlePos.z - startPos.z; + var delta = newHandlePos - _centerSelected; + var deltaZ = newHandlePos.z - _startPos.z; Undo.RecordObject(MainComponent as MonoBehaviour, "move Drag Points"); foreach (var controlPoint in SelectedControlPoints) { @@ -388,11 +384,13 @@ private void OnSceneLayout() _centerSelected /= SelectedControlPoints.Count; } + Handles.matrix = Matrix4x4.identity; if (CurveTravellerVisible) { - HandleUtility.AddControl(CurveTravellerControlId, - HandleUtility.DistanceToCircle(Handles.matrix.MultiplyPoint(CurveTravellerPosition), - HandleUtility.GetHandleSize(CurveTravellerPosition) * ControlPoint.ScreenRadius * - _sceneViewHandler.CurveTravellerSizeRatio * 0.5f)); + HandleUtility.AddControl( + CurveTravellerControlId, + HandleUtility.DistanceToCircle( + CurveTravellerPosition, + HandleUtility.GetHandleSize(CurveTravellerPosition) * ControlPoint.ScreenRadius * _sceneViewHandler.CurveTravellerSizeRatio * 0.5f)); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs index d66f31694..fd1348fa5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs @@ -290,6 +290,7 @@ public void OnSceneGUI(ItemInspector inspector) DragPointsHandler.OnSceneGUI(Event.current, OnDragPointPositionChange); // right mouse button clicked? + Handles.matrix = Matrix4x4.identity; if (Event.current.type == EventType.MouseDown && Event.current.button == 1) { var nearestControlPoint = DragPointsHandler.ControlPoints.Find(cp => cp.ControlId == HandleUtility.nearestControl); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index 0c6aa4015..511752662 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -47,7 +47,7 @@ public class DragPointsSceneViewHandler public Color CurveSlingShotColor { get; set; } = Color.red; - private float4x4 _matrix; + private float4x4 _worldToLocalMatrix; public DragPointsSceneViewHandler(DragPointsHandler handler) { @@ -60,7 +60,7 @@ public void OnSceneGUI() return; } - _matrix = math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix); + _worldToLocalMatrix = math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix); DisplayCurve(); DisplayControlPoints(); @@ -160,15 +160,16 @@ private void DisplayCurve() Profiler.BeginSample("Handle Traveller"); _curveTravellerMoved = false; if (_pathPoints.Count > 1) { + Handles.matrix = Matrix4x4.identity; Profiler.BeginSample("Convert Points"); - var points = _pathPoints.Select(p => (Vector3)_matrix.MultiplyPoint(p)).ToArray(); + var points = _pathPoints.Select(p => (Vector3)_worldToLocalMatrix.MultiplyPoint(p)).ToArray(); Profiler.EndSample(); Profiler.BeginSample("Calculate closest"); var newPos = HandleUtility.ClosestPointToPolyLine(points); Profiler.EndSample(); Profiler.BeginSample("Calculate if moved"); if ((newPos - _handler.CurveTravellerPosition).magnitude >= HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius * CurveTravellerSizeRatio * 0.1f) { - _handler.CurveTravellerPosition = math.inverse(_matrix).MultiplyPoint(newPos); + _handler.CurveTravellerPosition = math.inverse(_worldToLocalMatrix).MultiplyPoint(newPos); _curveTravellerMoved = true; } Profiler.EndSample(); @@ -179,7 +180,7 @@ private void DisplayCurve() // Render Curve with correct color regarding drag point properties & find curve section where the curve traveller is _handler.CurveTravellerControlPointIdx = -1; var minDist = float.MaxValue; - Handles.matrix = _matrix; + Handles.matrix = _worldToLocalMatrix; foreach (var controlPoint in _handler.ControlPoints) { Profiler.BeginSample("Compute Segments"); var segments = curveVerticesByDragPoint[controlPoint.Index].Select(cp => cp.TranslateToWorld()).ToArray(); @@ -224,7 +225,7 @@ private void DisplayControlPoints() Profiler.BeginSample("DisplayControlPoints"); // Render Control Points and check traveler distance from CP var distToCPoint = Mathf.Infinity; - Handles.matrix = _matrix; + Handles.matrix = _worldToLocalMatrix; var style = new GUIStyle { alignment = TextAnchor.MiddleCenter, }; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs index b69e22765..714266e7c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs @@ -24,17 +24,18 @@ public static class HandlesUtils /// /// Returns the position of the moved object in VPX space. /// - /// Reference to parent playfield /// Original position in VPX space + /// The local-to-world matrix of the item /// Allowed position type /// /// /// Moved position in VPX space. - public static Vector3 HandlePosition(PlayfieldComponent playfield, Vector3 position, ItemDataTransformType type, float handleSize = 0.2f, float snap = 0.0f) + public static Vector3 HandlePosition(Vector3 position, Matrix4x4 localToWorld, ItemDataTransformType type, float handleSize = 0.2f, float snap = 0.0f) { + var pos = position.TranslateToWorld(); - Handles.matrix = playfield == null ? Matrix4x4.identity : playfield.transform.localToWorldMatrix; - + Handles.matrix = localToWorld; + switch (type) { case ItemDataTransformType.TwoD: { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 35ab7082a..f331ba998 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -79,10 +79,10 @@ public static class Physics /// Local-to-World transformation matrix of the playfield. /// Transformation matrix of the item in VPX space. public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) => math.mul( - math.mul(Physics.WorldToVpx, + math.mul(WorldToVpx, math.inverse(math.mul(worldToLocal, playfieldToWorld)) ), - Physics.VpxToWorld); + VpxToWorld); #endregion From 88480126176a155d73739134ab2d63b98f086d3d Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 12 Nov 2023 20:52:02 +0100 Subject: [PATCH 012/208] dragpoints: Fix traveller menu and point index. --- .../DragPoint/DragPointsHandler.cs | 6 ++-- .../DragPoint/DragPointsSceneViewHandler.cs | 32 ++++++++----------- .../VisualPinball.Unity/Physics/Physics.cs | 22 ++++++++++++- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index a3209865f..66f1fb3a5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; using UnityEditor; using UnityEngine; using VisualPinball.Engine.Math; @@ -156,7 +155,7 @@ public void AddDragPointOnTraveller() }; var newIdx = CurveTravellerControlPointIdx + 1; - var dragPointPosition = CurveTravellerPosition.TranslateToVpx(); + var dragPointPosition = CurveTravellerPosition.TranslateToVpx(Transform); dragPointPosition.z = 0; dragPoint.Center = dragPointPosition.ToVertex3D(); var dragPoints = DragPointInspector.DragPoints.ToList(); @@ -355,6 +354,8 @@ private void OnSceneLayout() SelectedControlPoints.Clear(); _center = Vector3.zero; + Handles.matrix = Matrix4x4.identity; + //Setup Screen positions & controlID for control points (in case of modification of drag points coordinates outside) foreach (var controlPoint in ControlPoints) { _center += controlPoint.AbsolutePosition; @@ -384,7 +385,6 @@ private void OnSceneLayout() _centerSelected /= SelectedControlPoints.Count; } - Handles.matrix = Matrix4x4.identity; if (CurveTravellerVisible) { HandleUtility.AddControl( CurveTravellerControlId, diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index 511752662..b60cd1803 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -35,7 +35,7 @@ public class DragPointsSceneViewHandler /// /// Curve points in world space /// - private readonly List _pathPoints = new List(); + private readonly List _pathPoints = new(); private bool _curveTravellerMoved = false; @@ -47,7 +47,7 @@ public class DragPointsSceneViewHandler public Color CurveSlingShotColor { get; set; } = Color.red; - private float4x4 _worldToLocalMatrix; + private Transform Transform => _handler.MainComponent.gameObject.transform; public DragPointsSceneViewHandler(DragPointsHandler handler) { @@ -60,8 +60,6 @@ public void OnSceneGUI() return; } - _worldToLocalMatrix = math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix); - DisplayCurve(); DisplayControlPoints(); } @@ -152,7 +150,7 @@ private void DisplayCurve() segments = newPath; } foreach (var segment in segments) { - _pathPoints.Add(segment.TranslateToWorld()); + _pathPoints.Add(segment.TranslateToWorld(Transform)); } } Profiler.EndSample(); @@ -161,15 +159,12 @@ private void DisplayCurve() _curveTravellerMoved = false; if (_pathPoints.Count > 1) { Handles.matrix = Matrix4x4.identity; - Profiler.BeginSample("Convert Points"); - var points = _pathPoints.Select(p => (Vector3)_worldToLocalMatrix.MultiplyPoint(p)).ToArray(); - Profiler.EndSample(); Profiler.BeginSample("Calculate closest"); - var newPos = HandleUtility.ClosestPointToPolyLine(points); + var newPos = HandleUtility.ClosestPointToPolyLine(_pathPoints.ToArray()); Profiler.EndSample(); Profiler.BeginSample("Calculate if moved"); if ((newPos - _handler.CurveTravellerPosition).magnitude >= HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius * CurveTravellerSizeRatio * 0.1f) { - _handler.CurveTravellerPosition = math.inverse(_worldToLocalMatrix).MultiplyPoint(newPos); + _handler.CurveTravellerPosition = newPos; _curveTravellerMoved = true; } Profiler.EndSample(); @@ -180,7 +175,7 @@ private void DisplayCurve() // Render Curve with correct color regarding drag point properties & find curve section where the curve traveller is _handler.CurveTravellerControlPointIdx = -1; var minDist = float.MaxValue; - Handles.matrix = _worldToLocalMatrix; + Handles.matrix = Transform.localToWorldMatrix; foreach (var controlPoint in _handler.ControlPoints) { Profiler.BeginSample("Compute Segments"); var segments = curveVerticesByDragPoint[controlPoint.Index].Select(cp => cp.TranslateToWorld()).ToArray(); @@ -196,7 +191,7 @@ private void DisplayCurve() Profiler.EndSample(); Profiler.BeginSample("Calculate closes point"); var closestToPath = HandleUtility.ClosestPointToPolyLine(segments); - var dist = (closestToPath - _handler.CurveTravellerPosition).magnitude; + var dist = (closestToPath - (Vector3)math.inverse(Transform.localToWorldMatrix).MultiplyPoint(_handler.CurveTravellerPosition)).magnitude; if (dist < minDist) { minDist = dist; _handler.CurveTravellerControlPointIdx = controlPoint.Index; @@ -224,8 +219,8 @@ private void DisplayControlPoints() var matrix = math.mul(math.mul(Physics.WorldToVpx,math.inverse(_handler.MainComponent.gameObject.transform.worldToLocalMatrix)), Physics.VpxToWorld); Profiler.BeginSample("DisplayControlPoints"); // Render Control Points and check traveler distance from CP - var distToCPoint = Mathf.Infinity; - Handles.matrix = _worldToLocalMatrix; + var distToControlPoint = Mathf.Infinity; + Handles.matrix = Transform.localToWorldMatrix; var style = new GUIStyle { alignment = TextAnchor.MiddleCenter, }; @@ -240,14 +235,15 @@ private void DisplayControlPoints() var pos = controlPoint.EditorPositionWorld; var handleSize = controlPoint.HandleSize; Handles.SphereHandleCap(-1, pos, Quaternion.identity, handleSize, EventType.Repaint); - Handles.Label(pos - (Vector3.right * handleSize - Vector3.forward * handleSize * 2f) * 0.1f, $"{i}", style); - var dist = Vector3.Distance(_handler.CurveTravellerPosition, controlPoint.EditorPositionWorld); - distToCPoint = Mathf.Min(distToCPoint, dist); + Handles.Label(pos, $"{i}", style); + var dist = Vector3.Distance(_handler.CurveTravellerPosition, Handles.matrix.MultiplyPoint(controlPoint.EditorPositionWorld)); + distToControlPoint = Mathf.Min(distToControlPoint, dist); } + Handles.matrix = Matrix4x4.identity; if (!_handler.MainComponent.IsLocked) { // curve traveller is not overlapping a control point, we can draw it. - if (distToCPoint > HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius) { + if (distToControlPoint > HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius) { Handles.color = Color.grey; Handles.SphereHandleCap( _handler.CurveTravellerControlId, diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index f331ba998..f147521ea 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -90,9 +90,29 @@ public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 world public static float3 TranslateToVpx(this float3 worldVector) => math.transform(WorldToVpx, worldVector); public static Vector3 TranslateToVpx(this Vector3 worldVector) => math.transform(WorldToVpx, worldVector); - + + /// + /// Translates a world vector with a given transformation into VPX space, independent of the playfield's transform. + /// + /// This is useful in the editor. + /// + /// World position + /// Transformation of the item. + /// Transformed position in VPX space. + public static Vector3 TranslateToVpx(this Vector3 worldVector, Transform transform) => transform.worldToLocalMatrix.MultiplyPoint(worldVector).TranslateToVpx(); + public static float3 TranslateToWorld(this float3 vpxVector) => math.transform(VpxToWorld, vpxVector); public static Vector3 TranslateToWorld(this Vector3 vpxVector) => math.transform(VpxToWorld, vpxVector); + + /// + /// Translates a VPX vector with a given world transformation into world space, independent of the playfield's transform. + /// + /// This is useful in the editor. + /// + /// VPX position + /// Transformation of the item. + /// Transformed position in world space. + public static Vector3 TranslateToWorld(this Vector3 vpxVector, Transform transform) => transform.localToWorldMatrix.MultiplyPoint(vpxVector.TranslateToWorld()); public static Vector3 TranslateToWorld(float vpxX, float vpxY, float vpxZ) => TranslateToWorld(new float3(vpxX, vpxY, vpxZ)); #endregion From e00a7924b1f31585c05719fb0c5b57b07cc76257 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 12 Nov 2023 21:20:09 +0100 Subject: [PATCH 013/208] dragpoints: Save EditorPositionWorld in transformed world position. --- .../VisualPinball.Unity.Editor/DragPoint/ControlPoint.cs | 2 +- .../VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs | 2 +- .../DragPoint/DragPointsSceneViewHandler.cs | 4 ++-- .../DragPoint/IDragPointsInspector.cs | 3 +++ .../VPT/Light/LightInsertMeshInspector.cs | 1 + .../VPT/MetalWireGuide/MetalWireGuideInspector.cs | 2 ++ .../VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs | 2 ++ .../VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs | 2 ++ .../VPT/Surface/SurfaceInspector.cs | 2 ++ .../VPT/Trigger/TriggerInspector.cs | 1 + 10 files changed, 17 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/ControlPoint.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/ControlPoint.cs index 31c58d241..5f169a3c9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/ControlPoint.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/ControlPoint.cs @@ -48,7 +48,7 @@ public class ControlPoint /// /// Position in world space /// - public Vector3 EditorPositionWorld => EditorPositionVpx.TranslateToWorld(); + public Vector3 EditorPositionWorld => EditorPositionVpx.TranslateToWorld(_dragPointsInspector.Transform); public float HandleSize => HandleUtility.GetHandleSize(EditorPositionWorld) * ScreenRadius; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index 66f1fb3a5..f6bfd48a4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -366,7 +366,7 @@ private void OnSceneLayout() HandleUtility.AddControl( controlPoint.ControlId, HandleUtility.DistanceToCircle( - MainComponent.gameObject.transform.localToWorldMatrix.MultiplyPoint(controlPoint.EditorPositionWorld), + controlPoint.EditorPositionWorld, controlPoint.HandleSize ) ); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index b60cd1803..1e1f77794 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -220,7 +220,7 @@ private void DisplayControlPoints() Profiler.BeginSample("DisplayControlPoints"); // Render Control Points and check traveler distance from CP var distToControlPoint = Mathf.Infinity; - Handles.matrix = Transform.localToWorldMatrix; + Handles.matrix = Matrix4x4.identity; var style = new GUIStyle { alignment = TextAnchor.MiddleCenter, }; @@ -236,7 +236,7 @@ private void DisplayControlPoints() var handleSize = controlPoint.HandleSize; Handles.SphereHandleCap(-1, pos, Quaternion.identity, handleSize, EventType.Repaint); Handles.Label(pos, $"{i}", style); - var dist = Vector3.Distance(_handler.CurveTravellerPosition, Handles.matrix.MultiplyPoint(controlPoint.EditorPositionWorld)); + var dist = Vector3.Distance(_handler.CurveTravellerPosition, controlPoint.EditorPositionWorld); distToControlPoint = Mathf.Min(distToControlPoint, dist); } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs index a2cc703ec..5cc92e471 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System.Collections.Generic; +using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Unity.Editor; @@ -50,6 +51,8 @@ public enum DragPointExposure /// public interface IDragPointsInspector { + Transform Transform { get; } + /// /// Access to the drag point data /// diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs index 607a905f7..ba0a6f97b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs @@ -27,6 +27,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(LightInsertMeshComponent)), CanEditMultipleObjects] public class LightInsertMeshInspector : MeshInspector, IDragPointsInspector { + public Transform Transform => null; // not needed for lights, IDragpointInspector will probably be removed. private SerializedProperty _insertHeightProperty; private SerializedProperty _positionZProperty; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs index 6c4196d8a..23631a40b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs @@ -28,6 +28,8 @@ namespace VisualPinball.Unity.Editor public class MetalWireGuideInspector : MainInspector, IDragPointsInspector { + public Transform Transform => MainComponent.transform; + private SerializedProperty _heightProperty; private SerializedProperty _thicknessProperty; private SerializedProperty _rotationProperty; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs index 80b809943..75bc1b7a1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs @@ -28,6 +28,8 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(RampComponent)), CanEditMultipleObjects] public class RampInspector : MainInspector, IDragPointsInspector { + public Transform Transform => MainComponent.transform; + private bool _foldoutGeometry = true; private static readonly string[] RampTypeLabels = { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs index ba8da0362..27e643811 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs @@ -28,6 +28,8 @@ namespace VisualPinball.Unity.Editor public class RubberInspector : MainInspector, IDragPointsInspector { + public Transform Transform => MainComponent.transform; + private SerializedProperty _heightProperty; private SerializedProperty _thicknessProperty; private SerializedProperty _rotationProperty; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs index a5456dd59..16b6b3f62 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs @@ -28,6 +28,8 @@ namespace VisualPinball.Unity.Editor public class SurfaceInspector : MainInspector, IDragPointsInspector { + public Transform Transform => MainComponent.transform; + private SerializedProperty _heightTopProperty; private SerializedProperty _heightBottomProperty; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs index 2ead2fbf4..dc2442085 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs @@ -27,6 +27,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(TriggerComponent)), CanEditMultipleObjects] public class TriggerInspector : MainInspector, IDragPointsInspector { + public Transform Transform => MainComponent.transform; private SerializedProperty _positionProperty; private SerializedProperty _scaleProperty; From ab6701671f6ca2770c3b62eaa118d914c6ee78f2 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 12 Nov 2023 22:15:49 +0100 Subject: [PATCH 014/208] dragpoints: Add center pivot button. --- .../DragPoint/DragPointsHandler.cs | 18 ++++++++++++++++++ .../DragPoint/DragPointsInspectorHelper.cs | 7 ++++++- .../VPT/Rubber/RubberInspector.cs | 4 ---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs index f6bfd48a4..da83adb1b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsHandler.cs @@ -233,6 +233,24 @@ public void ReverseDragPoints() RebuildControlPoints(); } + public void CenterPivot() + { + var transform = Transform; + var centerVpx = Vector3.zero; + foreach (var dragPoint in DragPointInspector.DragPoints) { + centerVpx += dragPoint.Center.ToUnityVector3(); + } + centerVpx /= DragPointInspector.DragPoints.Length; + + Undo.RecordObjects(new[] { MainComponent as Object, transform }, $"Center pivot point of {MainComponent.name}"); + transform.Translate(centerVpx.TranslateToWorld(transform) - transform.position); + foreach (var dragPoint in DragPointInspector.DragPoints) { + dragPoint.Center -= centerVpx.ToVertex3D(); + } + + MainComponent.RebuildMeshes(); + } + /// /// Updates the lock status on all drag points to the given value. /// diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs index fd1348fa5..c2904d95a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs @@ -36,7 +36,7 @@ public class DragPointsInspectorHelper /// /// If true, a list of the drag points is displayed in the inspector. /// - private bool _foldoutControlPoints = true; + private bool _foldoutControlPoints; /// /// stored vector during the copy/paste process @@ -211,6 +211,11 @@ public void OnInspectorGUI(ItemInspector inspector) return; } + GUILayout.Space(10); + if (GUILayout.Button("Center Origin")) { + DragPointsHandler.CenterPivot(); + } + _foldoutControlPoints = EditorGUILayout.BeginFoldoutHeaderGroup(_foldoutControlPoints, "Drag Points"); if (_foldoutControlPoints) { EditorGUI.indentLevel++; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs index 27e643811..c75805695 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs @@ -66,10 +66,6 @@ public override void OnInspectorGUI() PropertyField(_heightProperty, rebuildMesh: true); PropertyField(_thicknessProperty, rebuildMesh: true); - // GUILayout.Space(10); - // if (GUILayout.Button("Center Origin")) { - // } - DragPointsHelper.OnInspectorGUI(this); base.OnInspectorGUI(); From 1bd68d694ba9dc396d9663e2da1b1d3f87d23535 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 13 Nov 2023 22:54:11 +0100 Subject: [PATCH 015/208] editor: Add transform and vpe icon. --- .../Assets/Editor/Icons/large_blue/vpe.png | Bin 0 -> 18015 bytes .../Editor/Icons/large_blue/vpe.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/large_gray/vpe.png | Bin 0 -> 18025 bytes .../Editor/Icons/large_gray/vpe.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/large_green/vpe.png | Bin 0 -> 18019 bytes .../Editor/Icons/large_green/vpe.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/large_orange/vpe.png | Bin 0 -> 18017 bytes .../Editor/Icons/large_orange/vpe.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/small_blue/vpe.png | Bin 0 -> 1155 bytes .../Editor/Icons/small_blue/vpe.png.meta | 127 ++++++++++++++++++ .../Editor/Icons/small_gray/transform.png | Bin 0 -> 1083 bytes .../Icons/small_gray/transform.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/small_gray/vpe.png | Bin 0 -> 1155 bytes .../Editor/Icons/small_gray/vpe.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/small_green/vpe.png | Bin 0 -> 1157 bytes .../Editor/Icons/small_green/vpe.png.meta | 127 ++++++++++++++++++ .../Editor/Icons/small_orange/transform.png | Bin 0 -> 1082 bytes .../Icons/small_orange/transform.png.meta | 127 ++++++++++++++++++ .../Assets/Editor/Icons/small_orange/vpe.png | Bin 0 -> 1156 bytes .../Editor/Icons/small_orange/vpe.png.meta | 127 ++++++++++++++++++ 20 files changed, 1270 insertions(+) create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_blue/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_blue/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_gray/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_gray/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_green/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_green/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_orange/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/large_orange/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_gray/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_green/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_green/vpe.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/transform.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/transform.png.meta create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/vpe.png create mode 100644 VisualPinball.Unity/Assets/Editor/Icons/small_orange/vpe.png.meta diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_blue/vpe.png b/VisualPinball.Unity/Assets/Editor/Icons/large_blue/vpe.png new file mode 100644 index 0000000000000000000000000000000000000000..61f46dd25c81f60a560f154bca8e524431d50588 GIT binary patch literal 18015 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 ZnRDLQ*dMQ-2Wm%8KQ(jufoU4u{{wvy@aO;l literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_gray/vpe.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/large_gray/vpe.png.meta new file mode 100644 index 000000000..2ad31f959 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/large_gray/vpe.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 1618158c1f388fc4f8589f51d7e32464 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_green/vpe.png b/VisualPinball.Unity/Assets/Editor/Icons/large_green/vpe.png new file mode 100644 index 0000000000000000000000000000000000000000..2ef2c0a527fbcf95568d095f5308a1021f47462b GIT binary patch literal 18019 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 literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/large_orange/vpe.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/large_orange/vpe.png.meta new file mode 100644 index 000000000..65641d4b5 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/large_orange/vpe.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: e97f7f3a14c6c3c4fa0ede9868f3d6de +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 512 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png new file mode 100644 index 0000000000000000000000000000000000000000..7233cd7b9183d43648eb3825c403ff04bb2c0702 GIT binary patch literal 1155 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+9I14-?iy0W${Xm$pP}SUmfq}); z)5S3)qBZHy|Nr)f7qIj2@BmdZ{QsXMCA2OjApydXlHyAxg`JeSTi2?;+q zfU5rgKa^o>8uPz>^^UBZ3n@&gR!s{Y{<3G!*mZrmmxzHu4huw4m~o>PL(b9A+QVP# zxmtW$#MFPUeBr?c^fNESu0sVHCRL6L8+jiHwOa~3nfNoLM1eyFXk-*rKI*#jVu|R& zr~JKPF%7pQ-zfpT&AnSs9E%Z_L7A!WmMXE90I+fqS9bVN9owYA2N$La%(4E@=>wXMCf0& zn@3lAqp0-)7k{rxPOYpDQ>AVI69>c(N~^S)dbK7jzQDM$)oPtdKa#tao@ZILQG;=T zmW1tIA$AD~2}qzQWiD9)4DEpJ4AI6sk`J|$f_*hxw1_4n)461hD9kJ?Guz8cFuY8h=Uao5Vsdz z@Yy8LZnC29`0W#FoN$RnB@=kAYj`uOrmC^Q4PSI(;e{2h{FVjR8>{?4cEW63RB=Pl zdwc6IRy$Z2F1q$Z<4j?Qc3%J}ZGe-;qAur!&dm#=I@K1QgePq&smtbYA4^F&KStsp biU4K?$1jg6a#}>^fPCrc>gTe~DWM4foe!ws literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png.meta new file mode 100644 index 000000000..926514a43 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/vpe.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: b43fe976aa6b9a14a82505b12b7954cc +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png new file mode 100644 index 0000000000000000000000000000000000000000..f9c8553a24a85843364e996d1a6faa9107751bd1 GIT binary patch literal 1083 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+9I14-?iy0W${Xm$pP}SUmfr0t0 zr;B4qL~GKY|Nrd|FJR~4;Q^{-`2RmiN@!h5LIQ*%CB>IYh@G@>S)z8>y3B-xgo1`5 zdjkUl6PK3um<2Zy*w{dh0GhcdYF+%sqO*)ZfohY7{R-{DZz2$qURTyf>{MguR(5Qd z-`JSQbW{vz22^91l<5KG_Or}OKzd#TcA9Bu+{t6EYDJdk`WzPDK1eV*pl9Z5u z+O^C%Q~WPn@uFqU@(+LtT#yxTOGrq_E$UPaQeA8a_fyvMyrZW~nSeSMOq;^Q%zT(F zYpW|z7t~xSsl5Sk2QFICxxVz0+SgSaWP9vA;bt9r9iYPAWU%7D zGslAE!2u@@E|QgIC=LDwjLc^WaqPx0{~nV38gReqlA<9WOJld-Lq&~R^%atzJFoCF zaWCX^`wF-DP}BpUR<$+ykuo1 z8`oOX}L#X2~tXGtvcKrQc}tOaLb6vmdI+D Z8Rkyh)3pCW?m1A>^>p=fS?83{1OO3?c%A?N literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png.meta new file mode 100644 index 000000000..31092d380 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/transform.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: dd30a3584befd2041b9b71d9de589292 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_gray/vpe.png b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/vpe.png new file mode 100644 index 0000000000000000000000000000000000000000..de8002fd5ed805bcd435e6a6cc7eea278736ad30 GIT binary patch literal 1155 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+9I14-?iy0W${Xm$pP}SUmfq}); z)5S3)qBZHy|Nr)f7qIj2@BmdZ{QsXMCA2OjApydXlHyAxg`E_+vB;N^nYsCZLIJl? z#eo9{HYh6DJYkNQ(AdZbF+OHr(pM%nwzeOR4{bs|Ol)ja1gc131_7`FCf8c#kb(`2 zbwD*=wZqnVCIA7%HB90^fC7w`+v)_Y#8~W%3=AY75>2VgIDsDEXTRaNgTbDe*%@M2 zQ}{xldQJvbQ@*Rhk`fXRIA^yVXlQIaD1W$yt*slP*Jr#Y2toN#}>aR=KJKamOD+(`^qk|xYt!Vu7KVsS=7!VeCh zf&c#>%CKD1mV;eWvt<^%jGM0&2n-HEh@vp#MlFV%qoIF+k|G>4 zIduKL1z(&9^aGDH#94<5G)$@-7dG-f5Nfv+dNT26NQnZ6Ob-WtBvd}?y7OX*=)$M` zypYkk4+M50H9;VZ*7q2?HNy$%lk#v=+aC5LW{9t@5V zRgz{3<<<(jP^`^i;nvdLa7Izyz`y`viPDub%-1(ZI52N{(P$|&C1itTk+P}GNno@> zrRP~zZPZ{~ zpe13uSBPCg0-6q#GMA(rJ!QHek8xX?gwcVkDsvpN7PIleFTaP;|w)&-Lq zVvavxfhT*V$~T=3+m`~<=|iSX456n2+1Nli2pFMC#}_Vm#PviXht~;RT;f0_`R%`i|c|p~eZ9SX45B=emYBvudgu8{F_kCl+2<;mU7WaJ{k04`e6I z) zv5t+c?d$A+uBrw=0C5eI_z$2!qfE3thb%XfyrhH#)Xhz)%Q%4^;Ag+#xP!qSXgI{K zrtpP8^{fp|Qthugf$|?(e4P^x960cxKhci)uq(t+r|)o7#vVVVaAPG3^_+b{{khsT6|i> z)PJvh;lT#pYkk4+M50H9;VZ*7q2?HNy$?VKbvgnqff{jJ^k8s|sFE~O zD7RMFg<@?Ei(iJi%rl+sfbj-VqIBg9^YzUU4$NC#G+GKx3E5y-q--j45*S!e`5QZL z14Bxr=x4+dHK1ng1K}Rdmn}W`BpVh@*|OyfT-~vSKV%kh!i(cuc#eKlVMfSSGBC|L z*O4{dX)@E%K48vCfI2062}@LrtjYqOE6Y@`IsbtBYEg^?h62=4xroq|6kPQywJIMK~$&OLPS(bNnJLF2ZofC^J63q cq7YzaDAaml6}scrG>|VnUHx3vIVCg!0K^@tMF0Q* literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_green/vpe.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_green/vpe.png.meta new file mode 100644 index 000000000..a06472132 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_green/vpe.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 7da818d71a10f304da8bf3f79f4b35d7 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_orange/transform.png b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/transform.png new file mode 100644 index 0000000000000000000000000000000000000000..ab9f1f376fe0f3c851d12a2bc86ea2dc42422ddf GIT binary patch literal 1082 zcmeAS@N?(olHy`uVBq!ia0vp^S|H591|*LjJ{b+9I14-?iy0W${Xm$pP}SUmfr0sr zr;B4qL~GKY|Nrd|FJR~4;Q^{-`2RmiN@!h5LIQ*%CB>IYh@G@>Sz>S-!j1Y?pK1}aUz)&cpk2|%ei$m|3$M`k`fXyn~-FeIqN?XSZ;%m zU9`+OQ~WQ&smq+@9{?4&z_kMva07!xZc(Rdkm_PX0|TfVv!1`pT|F1%q!p@5fllAh z7?m3cbQ0WudjsH{MJrnWZ@bDTQl#+5VaG3lbtYUv?0H|g!{wPw*LfnMOi9~iAs>rr z$KQ!9jxP%t#Yz>9pLA|@$dtHZ>lo-RU;wjQ>EyzIZpXiiDrCHPCSHDeJ8U;^#=J(x zOEPC*p`fJoYT-V!(abY+ja9ofE=4q;y{}h*&J(>1IIKFSbZx-$6!)g-%E==8Cs8caww z7s@nSB@K)pNUSJrU&r3s$E3+>p>YJ53>sHmcbb%&3`_Zko&`*J|8eQc&ILZsdCAVe zn9%quEwq8zlmTu4&`DPq7d+ySZQ$V(S!dVc+ZS@YiHTWc?*g9=K8KC1Ry;>wVSFgu zrm;&Z;3Lx_N4uDY^>*sw4&4R;zgVZ}yNej+AUBCBC$ XFpJ!OF{d|q3dpyfu6{1-oD!MYnej| zHZaz)v9*28kA5p80R#}eOyWO)0uFs^X_e#3DGgFQ2| zGsLK-@P$D2Ob(8{&R<>F*w`u#EmJf|NJ#i_++cp=LnW|QrxyA|i=NoshP4N?%(9NC1a3yKN%q0u~4JQ_7 zBqaRc0GoFx!**}(*2AGOrd;9jOsQ5)4{}$}Wj49{I#`uULc)v*qA1L`QHvqxXz1VD zVb-ipYEIpg|Av&PFf%hBhWhAGfrd$yfSf$j*$(bwr7LHcuWycUVBYeg(Nbti$Og+IWmB1x zzzBvIp>$(s_3bQnuAMbHLBg%8`9&XSFIo6fMx~v{AwNZm{ftG~rULkgP0#V9b@+5b)?271ywUTW-1*V+p6K<_&WJY*7{6RAFf`FsX zFSIV0)DUz00Si3YD^;-YImG|m);X!iwxQU}m&|Nk%Qa$e}%ydbJmZ6P8mrKB#K k!vjM~%K0%82T=wvGps%HEKKkNw;ITop00i_>zopr0A|{rGXMYp literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_orange/vpe.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/vpe.png.meta new file mode 100644 index 000000000..b2a1ee7ff --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/vpe.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: f9328c24715571d4c97003e350c8541c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + flipGreenChannel: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMipmapLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 2 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + swizzle: 50462976 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + mipmapLimitGroupName: + pSDRemoveMatte: 0 + userData: + assetBundleName: + assetBundleVariant: From 1b641173ffe4f10e43c65acdf75418781e33bfcd Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 00:15:26 +0100 Subject: [PATCH 016/208] ramps: Make them transformable and kinematic. --- .../VPT/Ramp/RampColliderInspector.cs | 7 ++- .../Extensions/MathExtensions.cs | 21 +++++++ .../Physics/Collider/ColliderReference.cs | 35 ++++++++++++ .../VisualPinball.Unity/VPT/CollidableApi.cs | 8 +++ .../VisualPinball.Unity/VPT/Ramp/RampApi.cs | 12 ++-- .../VPT/Ramp/RampColliderComponent.cs | 14 ++++- .../VPT/Ramp/RampColliderGenerator.cs | 56 ++++++++----------- .../VPT/Ramp/RampComponent.cs | 12 ++++ .../VPT/Rubber/RubberApi.cs | 5 +- .../VPT/Rubber/RubberComponent.cs | 3 +- 10 files changed, 128 insertions(+), 45 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampColliderInspector.cs index 0da9803f4..419316f85 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampColliderInspector.cs @@ -26,6 +26,8 @@ public class RampColliderInspector : ColliderInspector Add(collider.Transform(matrix)); + internal int Add(Line3DCollider collider) { collider.Id = Lookups.Length; @@ -248,6 +250,7 @@ internal int Add(SpinnerCollider collider) return collider.Id; } + internal int Add(TriangleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(TriangleCollider collider) { collider.Id = Lookups.Length; @@ -268,6 +271,38 @@ internal int Add(PlaneCollider collider) #endregion + #region Add non-transformable + + internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) + { + if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { + Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info).Transform(matrix)); + } else { + Add(new LineZCollider(xy, zLow, zHigh, info)); + } + + } + + internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix, ColliderType type = ColliderType.Line) + { + if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { + var p1 = new float3(v1.xy, zLow); + var p2 = new float3(v1.xy, zHigh); + var p3 = new float3(v2.xy, zLow); + var p4 = new float3(v2.xy, zHigh); + + // todo check orientation. + Add(new TriangleCollider(p1, p2, p3, info).Transform(matrix)); + Add(new TriangleCollider(p3, p2, p4, info).Transform(matrix)); + + } else { + Add(new LineCollider(v1, v2, zLow, zHigh, info, type)); + } + } + + #endregion + + public ICollider[] ToArray() { var array = new ICollider[Lookups.Length]; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index fcbfa695c..77645dde2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.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 Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT; @@ -67,6 +68,13 @@ public ColliderInfo GetColliderInfo(ItemType itemType) }; } + protected float4x4 GetTransformationWithinPlayfield() + { + var playfield = MainComponent.GetComponentInParent(); + var playfieldToWorld = playfield ? (float4x4)playfield.transform.localToWorldMatrix : float4x4.identity; + return MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(playfieldToWorld); + } + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index 3e93b9e0f..5c8092e5d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -61,11 +61,15 @@ void IApi.OnDestroy() protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); + if (ColliderComponent.IsKinematic) { + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); + } else { + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + } + } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index abf566dc1..03dbd33e5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -16,13 +16,14 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Ramp; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Ramp Collider")] - public class RampColliderComponent : ColliderComponent + public class RampColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -56,6 +57,17 @@ public class RampColliderComponent : ColliderComponent [Tooltip("When hit, add a random angle between 0 and this value to the trajectory.")] public float Scatter; + [Tooltip("If set, transforming this object will transform the colliders as well.")] + public bool _isKinematic; + + #endregion + + #region IKinematicColliderComponent + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + #endregion public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction, scatterAngleDeg: Scatter, overwrite: OverwritePhysics); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index 1c6c5b543..2dd0475cb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -29,13 +29,15 @@ public class RampColliderGenerator private readonly IRampData _data; private readonly RampMeshGenerator _meshGenerator; private readonly RampColliderComponent _colliderComponent; + private readonly float4x4 _matrix; - public RampColliderGenerator(RampApi rampApi, IRampData data, RampColliderComponent colliderComponent) + public RampColliderGenerator(RampApi rampApi, IRampData data, RampColliderComponent colliderComponent, float4x4 matrix) { _api = rampApi; _data = data; _colliderComponent = colliderComponent; _meshGenerator = new RampMeshGenerator(data); + _matrix = matrix; } internal void GenerateColliders(float tableHeight, ref ColliderReference colliders, float margin = 0f) @@ -57,20 +59,16 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide pv2 = rgvLocal[i].ToUnityFloat2(); pv3 = rgvLocal[i + 1].ToUnityFloat2(); - GenerateWallLineSeg(pv2, pv3, i > 0,rgHeight1[i], - rgHeight1[i + 1], wallHeightRight, ref colliders); - GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[i], - rgHeight1[i + 1], wallHeightRight, ref colliders); + GenerateWallLineSeg(pv2, pv3, i > 0,rgHeight1[i], rgHeight1[i + 1], wallHeightRight, ref colliders); + GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[i], rgHeight1[i + 1], wallHeightRight, ref colliders); // add joints at start and end of right wall if (i == 0) { - colliders.Add(new LineZCollider(pv2, rgHeight1[0], rgHeight1[0] + wallHeightRight, - _api.GetColliderInfo())); + colliders.AddLineZ(pv2, rgHeight1[0], rgHeight1[0] + wallHeightRight, _api.GetColliderInfo(), _matrix); } if (i == vertexCount - 2) { - colliders.Add(new LineZCollider(pv3, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightRight, - _api.GetColliderInfo())); + colliders.AddLineZ(pv3, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightRight, _api.GetColliderInfo(), _matrix); } } } @@ -81,20 +79,16 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide pv2 = rgvLocal[vertexCount + i].ToUnityFloat2(); pv3 = rgvLocal[vertexCount + i + 1].ToUnityFloat2(); - GenerateWallLineSeg(pv2, pv3, i > 0, rgHeight1[vertexCount - i - 2], - rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); - GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[vertexCount - i - 2], - rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); + GenerateWallLineSeg(pv2, pv3, i > 0, rgHeight1[vertexCount - i - 2], rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); + GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[vertexCount - i - 2], rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); // add joints at start and end of left wall if (i == 0) { - colliders.Add(new LineZCollider(pv2, rgHeight1[vertexCount - 1], - rgHeight1[vertexCount - 1] + wallHeightLeft, _api.GetColliderInfo())); + colliders.AddLineZ(pv2, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightLeft, _api.GetColliderInfo(), _matrix); } if (i == vertexCount - 2) { - colliders.Add(new LineZCollider(pv3, rgHeight1[0], rgHeight1[0] + wallHeightLeft, - _api.GetColliderInfo())); + colliders.AddLineZ(pv3, rgHeight1[0], rgHeight1[0] + wallHeightLeft, _api.GetColliderInfo(), _matrix); } } } @@ -124,7 +118,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide // add joint for starting edge of ramp if (i == 0) { - colliders.Add(new Line3DCollider(rg0, rg1, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(rg0, rg1, _api.GetColliderInfo()), _matrix); } // add joint for left edge @@ -133,7 +127,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide // degenerate triangles happen if width is 0 at some point if (!TriangleCollider.IsDegenerate(rg0, rg1, rg2)) { var ph3dPoly = new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()); - colliders.Add(ph3dPoly); + colliders.Add(ph3dPoly, _matrix); CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, ref colliders); ph3dPolyOld = ph3dPoly; @@ -146,11 +140,11 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide rg2 = new float3(pv4.x, pv4.y, rgHeight1[i + 1] + margin); // add joint for right edge - colliders.Add(new Line3DCollider(rg1, rg2, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(rg1, rg2, _api.GetColliderInfo()), _matrix); if (!TriangleCollider.IsDegenerate(rg0, rg1, rg2)) { var ph3dPoly = new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()); - colliders.Add(ph3dPoly); + colliders.Add(ph3dPoly, _matrix); CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, ref colliders); ph3dPolyOld = ph3dPoly; @@ -162,7 +156,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide // add joint for final edge of ramp var v1 = new float3(pv4.x, pv4.y, rgHeight1[vertexCount - 1]); var v2 = new float3(pv3.x, pv3.y, rgHeight1[vertexCount - 1]); - colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo()), _matrix); } // add outside bottom, @@ -182,7 +176,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide var rg2 = new float3(pv3.x, pv3.y, rgHeight1[i + 1]); if (!TriangleCollider.IsDegenerate(rg0, rg1, rg2)) { - colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); + colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()), _matrix); } // right ramp triangle, order CW @@ -191,7 +185,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide rg2 = new float3(pv1.x, pv1.y, rgHeight1[i]); if (!TriangleCollider.IsDegenerate(rg0, rg1, rg2)) { - colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); + colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()), _matrix); } } } @@ -210,28 +204,24 @@ private float2 GetWallHeights() } } - private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float height1, float height2, float wallHeight, - ref ColliderReference colliders) + private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float height1, float height2, float wallHeight, ref ColliderReference colliders) { //!! Hit-walls are still done via 2D line segments with only a single lower and upper border, so the wall will always reach below and above the actual ramp -between- two points of the ramp // Thus, subdivide until at some point the approximation error is 'subtle' enough so that one will usually not notice (i.e. dependent on ball size) if (height2 - height1 > 2.0 * PhysicsConstants.PhysSkin) { //!! use ballsize GenerateWallLineSeg(pv1, (pv1 + pv2) * 0.5f, pv3Exists, height1, (height1 + height2) * 0.5f, wallHeight, ref colliders); - GenerateWallLineSeg((pv1 + pv2) * 0.5f, pv2, true, (height1 + height2) * 0.5f, height2, wallHeight, ref colliders); } else { - colliders.Add(new LineCollider(pv1, pv2, height1, height2 + wallHeight, - _api.GetColliderInfo())); + colliders.AddLine(pv1, pv2, height1, height2 + wallHeight, _api.GetColliderInfo(), _matrix); if (pv3Exists) { - colliders.Add(new LineZCollider(pv1, height1, height2 + wallHeight, _api.GetColliderInfo())); + colliders.AddLineZ(pv1, height1, height2 + wallHeight, _api.GetColliderInfo(), _matrix); } } } - private void CheckJoint(bool isOldSet, in TriangleCollider ph3d1, in TriangleCollider ph3d2, - ref ColliderReference colliders) + private void CheckJoint(bool isOldSet, in TriangleCollider ph3d1, in TriangleCollider ph3d2, ref ColliderReference colliders) { if (isOldSet) { // may be null in case of degenerate triangles var jointNormal = math.cross(ph3d1.Normal(), ph3d2.Normal()); @@ -241,7 +231,7 @@ private void CheckJoint(bool isOldSet, in TriangleCollider ph3d1, in TriangleCol } // By convention of the calling function, points 1 [0] and 2 [1] of the second polygon will // be the common-edge points - colliders.Add(new Line3DCollider(ph3d2.Rgv0, ph3d2.Rgv1, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(ph3d2.Rgv0, ph3d2.Rgv1, _api.GetColliderInfo()), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index d9b60f264..9dfe575b4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -25,6 +25,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -84,6 +85,9 @@ public class RampComponent : MainRenderableComponent, [SerializeField] private DragPointData[] _dragPoints; + [NonSerialized] + private float4x4 _playfieldToWorld; + #endregion #region IRampData @@ -135,6 +139,12 @@ private void Awake() RegisterPhysics(physicsEngine); } + private void Start() + { + var playfield = GetComponentInParent(); + _playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : float4x4.identity; + } + #endregion #region Transformation @@ -202,6 +212,8 @@ public override void UpdateVisibility() } } + public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index e43ece615..e97d9068c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -47,13 +47,10 @@ internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var playfield = MainComponent.GetComponentInParent(); - var playfieldToWorld = (float4x4)playfield.transform.localToWorldMatrix; - var localToPlayfield = math.inverse(math.mul(MainComponent.transform.worldToLocalMatrix, playfieldToWorld)); var colliderGenerator = new RubberColliderGenerator( this, new RubberMeshGenerator(MainComponent), - math.mul(math.mul(Physics.WorldToVpx, localToPlayfield), Physics.VpxToWorld) + GetTransformationWithinPlayfield() ); if (ColliderComponent.IsKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index 25a98d705..f75961d30 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -26,7 +26,6 @@ using System.Linq; using Unity.Mathematics; using UnityEngine; -using UnityEngine.UIElements; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Rubber; @@ -106,7 +105,7 @@ private void Awake() private void Start() { var playfield = GetComponentInParent(); - _playfieldToWorld = playfield.transform.localToWorldMatrix; + _playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : float4x4.identity; } #endregion From 2fe2f27d00ee3027915b512e772e98fc88331b0c Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 20:14:16 +0100 Subject: [PATCH 017/208] rubbers: Use new API for adding transformed colliders. --- .../Physics/Collider/ColliderReference.cs | 1 + .../VPT/Rubber/RubberColliderGenerator.cs | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index fbbf9747e..2c1b503a6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -232,6 +232,7 @@ internal int Add(PlungerCollider collider) return collider.Id; } + internal int Add(PointCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(PointCollider collider) { collider.Id = Lookups.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs index 5a623923a..9ae6910bc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.VPT; @@ -47,7 +46,7 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, int deta var rg1 = mesh.Vertices[mesh.Indices[i + 2]].ToUnityFloat3(); var rg2 = mesh.Vertices[mesh.Indices[i + 1]].ToUnityFloat3(); - colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()).Transform(_matrix)); + colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()), _matrix); GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i], mesh.Indices[i + 2], ref colliders); GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], ref colliders); @@ -56,7 +55,7 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, int deta // add collision vertices foreach (var mv in mesh.Vertices) { - colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo()).Transform(_matrix)); + colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo()), _matrix); } addedEdges.Dispose(); @@ -67,7 +66,7 @@ private void GenerateHitEdge(Mesh mesh, ref EdgeSet addedEdges, int i, int j, re if (addedEdges.ShouldAddHitEdge(i, j)) { var v1 = mesh.Vertices[i].ToUnityFloat3(); var v2 = mesh.Vertices[j].ToUnityFloat3(); - colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo()).Transform(_matrix)); + colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo()), _matrix); } } } From 974247f7be207705de566ba12824c17b30c3168c Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 21:02:04 +0100 Subject: [PATCH 018/208] kinematics: Fix orientation when triangulating line colliders. --- .../Physics/Collider/ColliderReference.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 2c1b503a6..9b36b0c3e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -292,9 +292,8 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf var p3 = new float3(v2.xy, zLow); var p4 = new float3(v2.xy, zHigh); - // todo check orientation. - Add(new TriangleCollider(p1, p2, p3, info).Transform(matrix)); - Add(new TriangleCollider(p3, p2, p4, info).Transform(matrix)); + Add(new TriangleCollider(p1, p3, p2, info).Transform(matrix)); + Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); } else { Add(new LineCollider(v1, v2, zLow, zHigh, info, type)); From fa2bd559a6d8b5cfc28e9bd5c4b024e28c9e372f Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 21:04:06 +0100 Subject: [PATCH 019/208] refactor: Give the matrix a more specific name. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 4 ++-- .../VisualPinball.Unity/Game/Player.cs | 2 ++ .../VPT/Bumper/BumperColliderComponent.cs | 2 +- .../VisualPinball.Unity/VPT/CollidableApi.cs | 7 +------ .../VPT/IKinematicColliderComponent.cs | 2 +- .../VPT/MainRenderableComponent.cs | 3 +++ .../VPT/Playfield/PlayfieldApi.cs | 2 +- .../VPT/Primitive/PrimitiveColliderComponent.cs | 2 +- .../VPT/Ramp/RampColliderComponent.cs | 2 +- .../VisualPinball.Unity/VPT/Ramp/RampComponent.cs | 15 +++++++-------- .../VPT/Rubber/RubberColliderComponent.cs | 2 +- .../VPT/Trigger/TriggerColliderGenerator.cs | 2 +- 12 files changed, 22 insertions(+), 23 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 5319aafa7..a3c86b627 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -199,7 +199,7 @@ private void Start() // get kinetic collider matrices foreach (var coll in _kinematicColliderComponents) { - _kinematicTransforms.Ref[coll.ItemId] = coll.TransformationMatrix; + _kinematicTransforms.Ref[coll.ItemId] = coll.TransformationWithinPlayfield; } _kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent); @@ -238,7 +238,7 @@ private void Update() continue; } var lastTransformationMatrix = _kinematicTransforms.Ref[coll.ItemId]; - var currTransformationMatrix = coll.TransformationMatrix; + var currTransformationMatrix = coll.TransformationWithinPlayfield; if (lastTransformationMatrix.Equals(currTransformationMatrix)) { continue; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs index ea2a10457..4e8b8c599 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs @@ -55,6 +55,8 @@ public class Player : MonoBehaviour public event EventHandler OnBallCreated; public event EventHandler OnBallDestroyed; + public float4x4 PlayfieldToWorldMatrix => PlayfieldComponent.transform.localToWorldMatrix; + [HideInInspector] [SerializeField] public string debugUiId; [Tooltip("When enabled, update the switch, coil, lamp and wire manager windows in the editor (slower performance)")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 34b60b586..96993583c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -51,7 +51,7 @@ public class BumperColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index 77645dde2..194443a60 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs @@ -68,12 +68,7 @@ public ColliderInfo GetColliderInfo(ItemType itemType) }; } - protected float4x4 GetTransformationWithinPlayfield() - { - var playfield = MainComponent.GetComponentInParent(); - var playfieldToWorld = playfield ? (float4x4)playfield.transform.localToWorldMatrix : float4x4.identity; - return MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(playfieldToWorld); - } + protected float4x4 GetTransformationWithinPlayfield() => MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs index 5c0b6e69f..2c66c5bbe 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs @@ -26,6 +26,6 @@ public interface IKinematicColliderComponent /// /// Transformation matrix of the collider in the scene, in VPX space. /// - public float4x4 TransformationMatrix { get; } + public float4x4 TransformationWithinPlayfield { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index b13f99eb4..d43c31b3d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -43,6 +43,9 @@ public abstract class MainRenderableComponent : MainComponent, protected abstract Type ColliderComponentType { get; } + [NonSerialized] + public Player Player; + /// /// Returns all child mesh components linked to this data. /// diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs index d5fed05ef..79e4aef01 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs @@ -39,7 +39,7 @@ protected override void CreateColliders(ref ColliderReference colliders, if (meshComp && !meshComp.AutoGenerate) { var mf = GameObject.GetComponent(); if (mf && mf.sharedMesh) { - ColliderUtils.GenerateCollidersFromMesh(mf.sharedMesh.ToVpMesh().TransformToVpx(), info, ref colliders); + ColliderUtils.GenerateCollidersFromMesh(mf.sharedMesh.ToVpMesh().TransformToVpx(), info, ref colliders, float4x4.identity); } else { Debug.LogWarning($"Could not find mesh filter on playfield {GameObject.name}"); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index b30d404d4..0032eb643 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -66,7 +66,7 @@ public class PrimitiveColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index 03dbd33e5..907c9b1e2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -66,7 +66,7 @@ public class RampColliderComponent : ColliderComponent, public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 9dfe575b4..0c06e85c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -85,9 +85,6 @@ public class RampComponent : MainRenderableComponent, [SerializeField] private DragPointData[] _dragPoints; - [NonSerialized] - private float4x4 _playfieldToWorld; - #endregion #region IRampData @@ -131,24 +128,26 @@ public class RampComponent : MainRenderableComponent, private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - RampApi = new RampApi(gameObject, player, physicsEngine); + RampApi = new RampApi(gameObject, Player, physicsEngine); - player.Register(RampApi, this); + Player.Register(RampApi, this); RegisterPhysics(physicsEngine); } private void Start() { - var playfield = GetComponentInParent(); - _playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : float4x4.identity; + _playfieldToWorld = Player.PlayfieldToWorldMatrix; } #endregion #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public float Height(Vector2 pos) { var vVertex = new RampMeshGenerator(this).GetCentralCurve(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 23d916fcf..8e260fbbd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -65,7 +65,7 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationMatrix => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index 2bda3eeb8..4d2060b8d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -69,7 +69,7 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) rgv[i] = vVertex[i]; rgv3D[i] = new float3(rgv[i].X, rgv[i].Y, height + (float)(PhysicsConstants.PhysSkin * 2.0)); } - ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders); + ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, float4x4.identity); // todo adapt for (var i = 0; i < count; i++) { var pv2 = rgv[i < count - 1 ? i + 1 : 0]; From cbd1bf30e552675523b1ede58994bb3f87fdbbcf Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 21:04:31 +0100 Subject: [PATCH 020/208] surfaces: Make them transformable and kinematic. --- .../VPT/Surface/SurfaceColliderInspector.cs | 3 +++ .../Physics/Collider/ColliderUtils.cs | 16 +++++++------- .../VPT/Surface/SurfaceApi.cs | 12 +++++++---- .../VPT/Surface/SurfaceColliderComponent.cs | 14 ++++++++++++- .../VPT/Surface/SurfaceColliderGenerator.cs | 21 +++++++++++-------- .../VPT/Surface/SurfaceComponent.cs | 18 +++++++++++++--- 6 files changed, 59 insertions(+), 25 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs index 14d746913..98eb0c182 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs @@ -27,6 +27,7 @@ public class SurfaceColliderInspector : ColliderInspector ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { if (MainComponent.DragPoints.Length == 0) { return; } - var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); + if (ColliderComponent.IsKinematic) { + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); + + } else { + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + } } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 26f38178c..68bf072b7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -16,13 +16,14 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Surface; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Surface Collider")] - public class SurfaceColliderComponent : ColliderComponent + public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -63,10 +64,21 @@ public class SurfaceColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.SurfaceApi ?? new SurfaceApi(gameObject, player, physicsEngine); + + #region IKinematicColliderComponent + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index 4c1ec83d9..31c5dc80b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -27,12 +27,14 @@ public class SurfaceColliderGenerator private readonly IApiColliderGenerator _api; private readonly SurfaceComponent _component; private readonly SurfaceColliderComponent _colliderComponent; + private readonly float4x4 _matrix; - public SurfaceColliderGenerator(SurfaceApi surfaceApi, SurfaceComponent component, SurfaceColliderComponent colliderComponent) + public SurfaceColliderGenerator(SurfaceApi surfaceApi, SurfaceComponent component, SurfaceColliderComponent colliderComponent, float4x4 matrix) { _api = surfaceApi; _component = component; _colliderComponent = colliderComponent; + _matrix = matrix; } internal void GenerateColliders(float playfieldHeight, ref ColliderReference colliders, float margin = 0f) @@ -59,10 +61,10 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col GenerateLinePolys(pv2, pv3, playfieldHeight, ref colliders); } - ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders); + ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders, _matrix); if (rgv3Db != null) { - ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders); + ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders, _matrix); } } @@ -75,29 +77,30 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield var top = _component.HeightTop + playfieldHeight; if (!pv1.IsSlingshot) { - colliders.Add(new LineCollider(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo())); + colliders.AddLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); } else { + // todo colliders.Add(new LineSlingshotCollider(_colliderComponent.SlingshotForce, pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo())); } if (_component.HeightBottom != 0) { // add lower edge as a line - colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo()), _matrix); } // add upper edge as a line - colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); // create vertical joint between the two line segments - colliders.Add(new LineZCollider(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo())); + colliders.AddLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); // add upper and lower end points of line if (_component.HeightBottom != 0) { - colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo())); + colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo()), _matrix); } - colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo())); + colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo()), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index f1f7d031a..251d14e1e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -70,24 +71,35 @@ public class SurfaceComponent : MainRenderableComponent, ISurfaceCo private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - SurfaceApi = new SurfaceApi(gameObject, player, physicsEngine); + SurfaceApi = new SurfaceApi(gameObject, Player, physicsEngine); - player.Register(SurfaceApi, this); + Player.Register(SurfaceApi, this); if (GetComponentInChildren()) { RegisterPhysics(physicsEngine); } } + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public float Height(Vector2 _) => HeightTop + PlayfieldHeight; public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); + public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + + #endregion #region Conversion From 4eef801052435a59c9bace28e7f18068e80135f5 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 14 Nov 2023 21:24:00 +0100 Subject: [PATCH 021/208] fix: Translate line and line-z colliders when static. --- .../Extensions/MathExtensions.cs | 9 +++------ .../Physics/Collider/ColliderReference.cs | 4 ++-- .../Physics/Collider/LineCollider.cs | 16 ++++++++++++++-- .../Physics/Collider/LineZCollider.cs | 15 +++++++++++++-- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index dbf2ed573..49b90e7f6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -67,6 +67,8 @@ public static float3 GetScale(this float4x4 m) ); } + public static float3 GetTranslation(this float4x4 m) => new(m.c3.x, m.c3.y, m.c3.z); + public static bool IsPureTranslationMatrix(this float4x4 matrix) { // check scaling (diagonal elements) @@ -80,12 +82,7 @@ public static bool IsPureTranslationMatrix(this float4x4 matrix) return false; } - // Check translation (last column) - if (matrix.c3 is { x: 0.0f, y: 0.0f, z: 0.0f, w: 1.0f }) { - return true; - } - - return false; + return true; } public static Vertex3D ToVertex3D(this Vector3 vector) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 9b36b0c3e..434782241 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -279,7 +279,7 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info).Transform(matrix)); } else { - Add(new LineZCollider(xy, zLow, zHigh, info)); + Add(new LineZCollider(xy, zLow, zHigh, info).Transform(matrix)); } } @@ -296,7 +296,7 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); } else { - Add(new LineCollider(v1, v2, zLow, zHigh, info, type)); + Add(new LineCollider(v1, v2, zLow, zHigh, info, type).Transform(matrix)); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index 9ace858cb..e9cc78730 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -35,8 +35,8 @@ public int Id public float2 V2; public float2 Normal; - public readonly float ZLow; - public readonly float ZHigh; + public float ZLow; + public float ZHigh; private float _length; internal ItemType ItemType => Header.ItemType; @@ -229,5 +229,17 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite } #endregion + + public LineCollider Transform(float4x4 matrix) + { + var t = matrix.GetTranslation(); + + V1 += t.xy; + V2 += t.xy; + ZLow += t.z; + ZHigh += t.z; + + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 13e6c73bd..61a814b51 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -31,8 +31,8 @@ public int Id public ColliderHeader Header; public float2 XY; - private readonly float _zLow; - private readonly float _zHigh; + private float _zLow; + private float _zHigh; public float XyY { set => XY.y = value; } @@ -161,5 +161,16 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite } #endregion + + public LineZCollider Transform(float4x4 matrix) + { + var t = matrix.GetTranslation(); + + XY += t.xy; + _zLow += t.z; + _zHigh += t.z; + + return this; + } } } From f09687eb55a604c5ad2bf16a5b9d70bc1278810f Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 15 Nov 2023 23:45:29 +0100 Subject: [PATCH 022/208] spinners: Make them transformable and kinematic. --- .../VPT/Spinner/SpinnerColliderInspector.cs | 3 ++ .../Extensions/MathExtensions.cs | 2 + .../VisualPinball.Unity/Game/PhysicsState.cs | 5 ++- .../Game/PhysicsStaticCollision.cs | 2 +- .../Physics/Collider/CircleCollider.cs | 26 ++++++++--- .../Physics/Collider/ColliderReference.cs | 7 +++ .../Physics/Collider/LineCollider.cs | 4 +- .../VPT/Bumper/BumperColliderComponent.cs | 2 +- .../VPT/Bumper/BumperComponent.cs | 4 +- .../Primitive/PrimitiveColliderComponent.cs | 2 +- .../VPT/Primitive/PrimitiveComponent.cs | 2 +- .../VPT/Ramp/RampColliderComponent.cs | 2 +- .../VPT/Ramp/RampComponent.cs | 3 +- .../VPT/Rubber/RubberColliderComponent.cs | 2 +- .../VPT/Rubber/RubberComponent.cs | 2 +- .../VPT/Spinner/SpinnerApi.cs | 13 ++++-- .../VPT/Spinner/SpinnerCollider.cs | 45 +++++++++++-------- .../VPT/Spinner/SpinnerColliderComponent.cs | 14 +++++- .../VPT/Spinner/SpinnerColliderGenerator.cs | 42 ++++++++++------- .../VPT/Spinner/SpinnerComponent.cs | 20 ++++++--- .../VPT/Surface/SurfaceColliderComponent.cs | 2 +- .../VPT/Surface/SurfaceComponent.cs | 3 +- 22 files changed, 142 insertions(+), 65 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerColliderInspector.cs index 424c34b8c..08e976630 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerColliderInspector.cs @@ -22,11 +22,13 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(SpinnerColliderComponent)), CanEditMultipleObjects] public class SpinnerColliderInspector : ColliderInspector { + private SerializedProperty _isKinematicProperty; private SerializedProperty _elasticityProperty; protected override void OnEnable() { base.OnEnable(); + _isKinematicProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent._isKinematic)); _elasticityProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent.Elasticity)); } @@ -40,6 +42,7 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); + PropertyField(_isKinematicProperty, "Movable"); PropertyField(_elasticityProperty, updateTransforms: true); base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index 49b90e7f6..307ecc93f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -69,6 +69,8 @@ public static float3 GetScale(this float4x4 m) public static float3 GetTranslation(this float4x4 m) => new(m.c3.x, m.c3.y, m.c3.z); + public static float3 GetRotationVector(this float4x4 matrix) => new quaternion(matrix).ToEuler(); + public static bool IsPureTranslationMatrix(this float4x4 matrix) { // check scaling (diagonal elements) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 659cba0da..f2539c78c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -95,7 +95,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native internal ref PlungerState GetPlungerState(int colliderId) => ref PlungerStates.GetValueByRef(Colliders.GetItemId(colliderId)); - internal ref SpinnerState GetSpinnerState(int colliderId) => ref SpinnerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref SpinnerState GetSpinnerState(int colliderId, ref NativeColliders colliders) => ref SpinnerStates.GetValueByRef(colliders.GetItemId(colliderId)); internal ref TriggerState GetTriggerState(int colliderId) => ref TriggerStates.GetValueByRef(Colliders.GetItemId(colliderId)); @@ -136,6 +136,9 @@ internal void Transform(int colliderId, float4x4 matrix) case ColliderType.Triangle: KinematicColliders.Triangle(colliderId).Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); break; + case ColliderType.Spinner: + KinematicColliders.Spinner(colliderId).Transform(KinematicCollidersAtIdentity.Spinner(colliderId), matrix); + break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index ba579f193..41aa4c6be 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -115,7 +115,7 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.Spinner: - ref var spinnerState = ref state.GetSpinnerState(colliderId); + ref var spinnerState = ref state.GetSpinnerState(colliderId, ref colliders); SpinnerCollider.Collide(in ball, ref ball.CollisionEvent, ref spinnerState.Movement, in spinnerState.Static); break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 85e8743c9..0e52a313f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -33,10 +33,10 @@ public int Id public float2 Center; public float Radius; - private readonly float _zHigh; - private readonly float _zLow; + private float _zHigh; + private float _zLow; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + public ColliderBounds Bounds => new(Header.ItemId, Header.Id, new Aabb( Center.x - Radius, Center.x + Radius, Center.y - Radius, @@ -224,9 +224,23 @@ public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Ran public void Transform(CircleCollider circle, float4x4 matrix) { - var size = matrix.GetScale(); - Center = math.mul(matrix, new float4(circle.Center, 0f, 1f)).xy; - Radius = size.x / 2 * BumperComponent.DataMeshScale; + var s = matrix.GetScale(); + var t = matrix.GetTranslation(); + // + Center = circle.Center + t.xy; + Radius = circle.Radius * s.x; + _zHigh = circle._zHigh * s.z; + _zLow = circle._zLow * s.z; + + // var size = matrix.GetScale(); + // Center = math.mul(matrix, new float4(circle.Center, 0f, 1f)).xy; + // Radius = size.x / 2 * BumperComponent.DataMeshScale; + } + + public CircleCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 434782241..434bb8edd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -115,6 +115,10 @@ public void TransformToIdentity(NativeParallelHashMap itemIdToTra ref var triangleCollider = ref TriangleColliders.GetElementAsRef(lookup.Index); triangleCollider.Transform(TriangleColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Spinner: + ref var spinnerCollider = ref SpinnerColliders.GetElementAsRef(lookup.Index); + spinnerCollider.Transform(SpinnerColliders[lookup.Index], math.inverse(matrix)); + break; } } } @@ -158,6 +162,8 @@ private void TrackReference(int itemId, int colliderId) _itemIdToColliderIds[itemId].Add(colliderId); } + internal int Add(CircleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); + internal int Add(CircleCollider collider) { collider.Id = Lookups.Length; @@ -242,6 +248,7 @@ internal int Add(PointCollider collider) return collider.Id; } + internal int Add(SpinnerCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(SpinnerCollider collider) { collider.Id = Lookups.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index e9cc78730..89bafce1c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -234,8 +234,8 @@ public LineCollider Transform(float4x4 matrix) { var t = matrix.GetTranslation(); - V1 += t.xy; - V2 += t.xy; + V1 = matrix.MultiplyPoint(new float3(V1, 0)).xy; + V2 = matrix.MultiplyPoint(new float3(V2, 0)).xy; ZLow += t.z; ZHigh += t.z; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 96993583c..12881d017 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -51,7 +51,7 @@ public class BumperColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index b5c66b8db..5d4f5af28 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -146,10 +146,10 @@ public override void UpdateTransforms() t.localEulerAngles = new Vector3(0, Orientation, 0); } - public float4x4 TransformationMatrix { + public float4x4 TransformationWithinPlayfield { get { - var scaleMatrix = float4x4.Scale(new float3(Radius * 2f, Radius * 2f, HeightScale) / DataMeshScale); var transMatrix = float4x4.Translate(new float3(Position.x, Position.y, PositionZ)); + var scaleMatrix = float4x4.Scale(new float3(Radius * 2f, Radius * 2f, HeightScale) / DataMeshScale); var rotMatrix = float4x4.RotateZ(math.radians(Orientation)); return math.mul(transMatrix, math.mul(rotMatrix, scaleMatrix)); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index 0032eb643..dd106ec44 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -66,7 +66,7 @@ public class PrimitiveColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 85ad3bd4a..c5ec1e61f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -95,7 +95,7 @@ public float2 RotatedPosition { } } - public float4x4 TransformationMatrix { + public float4x4 TransformationWithinPlayfield { get { var scaleMatrix = float4x4.Scale(Size); var transMatrix = float4x4.Translate(new float3(Position.x, Position.y, Position.z + PlayfieldHeight)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index 907c9b1e2..a5a2aa3df 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -66,7 +66,7 @@ public class RampColliderComponent : ColliderComponent, public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 0c06e85c6..674b59d83 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -211,7 +211,8 @@ public override void UpdateVisibility() } } - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + public float4x4 TransformationWithinPlayfield + => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 8e260fbbd..0cd2f263c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -65,7 +65,7 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index f75961d30..d5986580a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -114,7 +114,7 @@ private void Start() public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs index 9019cfec0..9b3f4082c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Spinner; @@ -82,11 +83,15 @@ public SpinnerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : b protected override bool FireHitEvents => true; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent); - colliderGenerator.GenerateColliders(MainComponent.HeightOnPlayfield, ref colliders); + var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); + var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent, matrix); + if (ColliderComponent._isKinematic) { + colliderGenerator.GenerateColliders(ref kinematicColliders); + } else { + colliderGenerator.GenerateColliders(ref colliders); + } } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index 05f57fad2..d905ebeb4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Collections; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.Common; namespace VisualPinball.Unity @@ -35,32 +35,31 @@ public int Id public ColliderHeader Header; - public readonly LineCollider LineSeg0; - public readonly LineCollider LineSeg1; + public LineCollider LineSeg0; + public LineCollider LineSeg1; public ColliderBounds Bounds { get; private set; } - public SpinnerCollider(SpinnerComponent component, float height, ColliderInfo info) : this() + public SpinnerCollider(ColliderInfo info) : this() { Header.Init(info, ColliderType.Spinner); - var halfLength = component.Length * 0.5f; - - var radAngle = math.radians(component.Rotation); - var sn = math.sin(radAngle); - var cs = math.cos(radAngle); + const float halfLength = 40f; + // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct + // position, we generate them at the origin and then transform them later. var v1 = new float2( - component.Position.x - cs * (halfLength + PhysicsConstants.PhysSkin), // through the edge of the - component.Position.y - sn * (halfLength + PhysicsConstants.PhysSkin) // spinner + - (halfLength + PhysicsConstants.PhysSkin), // through the edge of the + 0 // spinner ); var v2 = new float2( - component.Position.x + cs * (halfLength + PhysicsConstants.PhysSkin), // oversize by the ball radius - component.Position.y + sn * (halfLength + PhysicsConstants.PhysSkin) // this will prevent clipping + halfLength + PhysicsConstants.PhysSkin, // oversize by the ball radius + 0 // this will prevent clipping ); - LineSeg0 = new LineCollider(v1, v2, height, height + 2.0f * PhysicsConstants.PhysSkin, info); - LineSeg1 = new LineCollider(v2, v1, height, height + 2.0f * PhysicsConstants.PhysSkin, info); + // todo probably broke surface + LineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, info); + LineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, info); Bounds = LineSeg0.Bounds; } @@ -69,9 +68,6 @@ public SpinnerCollider(SpinnerComponent component, float height, ColliderInfo in public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { - // todo - // if (!m_enabled) return -1.0f; - var hitTime = LineCollider.HitTestBasic(ref collEvent, ref insideOfs, in LineSeg0, in ball, dTime, false, true, false); // any face, lateral, non-rigid if (hitTime >= 0.0f) { // signal the Collide() function that the hit is on the front or back side @@ -127,5 +123,18 @@ public static void Collide(in BallState ball, ref CollisionEventData collEvent, } #endregion + + public void Transform(SpinnerCollider collider, float4x4 matrix) + { + LineSeg0 = collider.LineSeg0.Transform(matrix); + LineSeg1 = collider.LineSeg1.Transform(matrix); + Bounds = collider.LineSeg0.Bounds; + } + + public SpinnerCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index bd90929bb..2e56f4aa7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -16,13 +16,14 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Spinner; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Spinner Collider")] - public class SpinnerColliderComponent : ColliderComponent + public class SpinnerColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -35,5 +36,16 @@ public class SpinnerColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.SpinnerApi ?? new SpinnerApi(gameObject, player, physicsEngine); + + #region IKinematicColliderComponent + + [Tooltip("If set, transforming this object will transform the colliders as well.")] + public bool _isKinematic; + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs index edd7f5dbb..22840104a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs @@ -14,8 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Mathematics; +using VisualPinball.Engine.Common; namespace VisualPinball.Unity { @@ -23,46 +23,56 @@ public class SpinnerColliderGenerator { private readonly SpinnerApi _api; private readonly SpinnerComponent _component; + private readonly float4x4 _matrix; - public SpinnerColliderGenerator(SpinnerApi spinnerApi, SpinnerComponent component) + public SpinnerColliderGenerator(SpinnerApi spinnerApi, SpinnerComponent component, float4x4 matrix) { _api = spinnerApi; _component = component; + _matrix = matrix; } - internal void GenerateColliders(float height, ref ColliderReference colliders) + internal void GenerateColliders(ref ColliderReference colliders) { - colliders.Add(new SpinnerCollider(_component, height - _component.Height, _api.GetColliderInfo())); + colliders.Add(new SpinnerCollider(_api.GetColliderInfo()), _matrix); if (_component.ShowBracket) { - GenerateBracketColliders(height, ref colliders); + GenerateBracketColliders(ref colliders); } } - private void GenerateBracketColliders(float height, ref ColliderReference colliders) + private void GenerateBracketColliders(ref ColliderReference colliders) { const float h = 30.0f; + var t = _matrix.GetTranslation(); + var r = _matrix.GetRotationVector(); + var s = _matrix.GetScale(); + + // extract dimensions from translation matrix + var length = s.x * 80f; // 80 = size at scale 1 + var rotationRad = r.z; + var height = t.z - PhysicsConstants.PhysSkin; + /*add a hit shape for the bracket if shown, just in case if the bracket spinner height is low enough so the ball can hit it*/ - var halfLength = _component.Length * 0.5f + _component.Length * 0.1875f; - var radAngle = math.radians(_component.Rotation); - var sn = math.sin(radAngle); - var cs = math.cos(radAngle); + var halfLength = length * 0.5f + length * 0.1875f; + var sn = math.sin(rotationRad); + var cs = math.cos(rotationRad); colliders.Add(new CircleCollider( - new float2(_component.Position.x + cs * halfLength, _component.Position.y + sn * halfLength), - _component.Length * 0.075f, + new float2(cs * halfLength, sn * halfLength), + length * 0.075f, height, height + h, _api.GetColliderInfo() - )); + ), _matrix); colliders.Add(new CircleCollider( - new float2(_component.Position.x - cs * halfLength, _component.Position.y - sn * halfLength), - _component.Length * 0.075f, + new float2( - cs * halfLength, - sn * halfLength), + length * 0.075f, height, height + h, _api.GetColliderInfo() - )); + ), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index f3e3718df..179e85091 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -96,14 +96,19 @@ public class SpinnerComponent : MainRenderableComponent, private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - SpinnerApi = new SpinnerApi(gameObject, player, physicsEngine); + SpinnerApi = new SpinnerApi(gameObject, Player, physicsEngine); - player.Register(SpinnerApi, this); + Player.Register(SpinnerApi, this); RegisterPhysics(physicsEngine); } + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Wiring @@ -120,6 +125,9 @@ private void Awake() #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); @@ -134,12 +142,14 @@ public override void UpdateTransforms() t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, HeightOnPlayfield); // scale - t.localScale = Physics.ScaleToWorld(Length, Length, Length); + t.localScale = new float3(Length / 80f); // rotation - t.localEulerAngles = Physics.RotateToWorld(0, 0, Rotation); + t.localRotation = quaternion.RotateY(math.radians(Rotation)); } + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 68bf072b7..c56f5de31 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -77,7 +77,7 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 251d14e1e..481b56d2c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -97,7 +97,8 @@ private void Start() public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + public float4x4 TransformationWithinPlayfield + => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion From f9001f09d925fc4d39d959750cc5d26466faff4e Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 16 Nov 2023 23:57:06 +0100 Subject: [PATCH 023/208] spinners: Fix bracket collider transformation. --- .../Physics/Collider/CircleCollider.cs | 5 ++--- .../VPT/Spinner/SpinnerColliderGenerator.cs | 19 ++++++------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 0e52a313f..6f94b9881 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -225,9 +225,8 @@ public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Ran public void Transform(CircleCollider circle, float4x4 matrix) { var s = matrix.GetScale(); - var t = matrix.GetTranslation(); - // - Center = circle.Center + t.xy; + + Center = matrix.MultiplyPoint(new float3(circle.Center, 0)).xy; Radius = circle.Radius * s.x; _zHigh = circle._zHigh * s.z; _zLow = circle._zLow * s.z; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs index 22840104a..6cf661190 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs @@ -42,24 +42,17 @@ internal void GenerateColliders(ref ColliderReference colliders) private void GenerateBracketColliders(ref ColliderReference colliders) { - const float h = 30.0f; - - var t = _matrix.GetTranslation(); - var r = _matrix.GetRotationVector(); - var s = _matrix.GetScale(); + const float h = 30.0f + PhysicsConstants.PhysSkin; // extract dimensions from translation matrix - var length = s.x * 80f; // 80 = size at scale 1 - var rotationRad = r.z; - var height = t.z - PhysicsConstants.PhysSkin; + const float length = 80f; // 80 = size at scale 1 + var height = 0; /*add a hit shape for the bracket if shown, just in case if the bracket spinner height is low enough so the ball can hit it*/ - var halfLength = length * 0.5f + length * 0.1875f; - var sn = math.sin(rotationRad); - var cs = math.cos(rotationRad); + const float halfLength = length * 0.5f + length * 0.1875f; colliders.Add(new CircleCollider( - new float2(cs * halfLength, sn * halfLength), + new float2(halfLength, 0), length * 0.075f, height, height + h, @@ -67,7 +60,7 @@ private void GenerateBracketColliders(ref ColliderReference colliders) ), _matrix); colliders.Add(new CircleCollider( - new float2( - cs * halfLength, - sn * halfLength), + new float2( -halfLength, 0), length * 0.075f, height, height + h, From bdd9fa082dd029b5dd72b2864c0fc1cad18b1fbb Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 17 Nov 2023 00:07:33 +0100 Subject: [PATCH 024/208] fix: Normal when transforming line collider. --- .../VisualPinball.Unity/Physics/Collider/CircleCollider.cs | 5 ----- .../VisualPinball.Unity/Physics/Collider/LineCollider.cs | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 6f94b9881..a4a649aea 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -225,15 +225,10 @@ public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Ran public void Transform(CircleCollider circle, float4x4 matrix) { var s = matrix.GetScale(); - Center = matrix.MultiplyPoint(new float3(circle.Center, 0)).xy; Radius = circle.Radius * s.x; _zHigh = circle._zHigh * s.z; _zLow = circle._zLow * s.z; - - // var size = matrix.GetScale(); - // Center = math.mul(matrix, new float4(circle.Center, 0f, 1f)).xy; - // Radius = size.x / 2 * BumperComponent.DataMeshScale; } public CircleCollider Transform(float4x4 matrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index 89bafce1c..a3527f81a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -239,6 +239,8 @@ public LineCollider Transform(float4x4 matrix) ZLow += t.z; ZHigh += t.z; + CalcNormal(); + return this; } } From ca4afd599bfc6a2d6f3962e077f0f856a2391fe7 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 18 Nov 2023 01:03:34 +0100 Subject: [PATCH 025/208] bumper: Fix transformation. --- .../VPT/Bumper/BumperApi.cs | 18 ++++++++------- .../VPT/Bumper/BumperComponent.cs | 23 ++++++++++--------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index b8eeffd50..5880014a1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Bumper; using System.Collections.Generic; @@ -117,17 +118,18 @@ void IApiCoil.OnCoil(bool enabled) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { + var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var height = MainComponent.PositionZ; - var switchCollider = new CircleCollider(MainComponent.Position, MainComponent.Radius, height, - height + MainComponent.HeightScale, GetColliderInfo(), ColliderType.Bumper); - var rigidCollider = new CircleCollider(MainComponent.Position, MainComponent.Radius * 0.5f, height, - height + MainComponent.HeightScale, GetColliderInfo(), ColliderType.Circle); + var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, + height + 100f, GetColliderInfo(), ColliderType.Bumper); + var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, + height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent.IsKinematic) { - switchColliderId = kinematicColliders.Add(switchCollider); - kinematicColliders.Add(rigidCollider); + switchColliderId = kinematicColliders.Add(switchCollider, matrix); + kinematicColliders.Add(rigidCollider, matrix); } else { - switchColliderId = colliders.Add(switchCollider); - colliders.Add(rigidCollider); + switchColliderId = colliders.Add(switchCollider, matrix); + colliders.Add(rigidCollider, matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 5d4f5af28..25d34ab89 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -90,16 +90,21 @@ public class BumperComponent : MainRenderableComponent, private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - BumperApi = new BumperApi(gameObject, player, physicsEngine); + BumperApi = new BumperApi(gameObject, Player, physicsEngine); - player.Register(BumperApi, this); + Player.Register(BumperApi, this); if (GetComponentInChildren()) { RegisterPhysics(physicsEngine); } } + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Wiring @@ -127,6 +132,9 @@ private void Awake() #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); @@ -146,14 +154,7 @@ public override void UpdateTransforms() t.localEulerAngles = new Vector3(0, Orientation, 0); } - public float4x4 TransformationWithinPlayfield { - get { - var transMatrix = float4x4.Translate(new float3(Position.x, Position.y, PositionZ)); - var scaleMatrix = float4x4.Scale(new float3(Radius * 2f, Radius * 2f, HeightScale) / DataMeshScale); - var rotMatrix = float4x4.RotateZ(math.radians(Orientation)); - return math.mul(transMatrix, math.mul(rotMatrix, scaleMatrix)); - } - } + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion From 8822a5b72a5e3a458843bca9d29dd7a29a85fee0 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 19 Nov 2023 01:07:27 +0100 Subject: [PATCH 026/208] gates: Make them transformable and kinematic. --- .../VPT/Gate/GateColliderInspector.cs | 3 ++ .../VisualPinball.Unity/Game/PhysicsState.cs | 5 ++- .../Game/PhysicsStaticCollision.cs | 2 +- .../Physics/Collider/ColliderReference.cs | 5 +++ .../VisualPinball.Unity/Physics/README.md | 2 - .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 11 ++++- .../VPT/Gate/GateCollider.cs | 17 +++++++- .../VPT/Gate/GateColliderComponent.cs | 14 +++++- .../VPT/Gate/GateColliderGenerator.cs | 43 ++++++++++--------- .../VPT/Gate/GateComponent.cs | 21 ++++++--- 10 files changed, 88 insertions(+), 35 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs index d6ceac1c4..197ab4711 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs @@ -24,6 +24,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(GateColliderComponent)), CanEditMultipleObjects] public class GateColliderInspector : ColliderInspector { + private SerializedProperty _isKinematicProperty; private SerializedProperty _angleMinProperty; private SerializedProperty _angleMaxProperty; private SerializedProperty _elasticityProperty; @@ -36,6 +37,7 @@ protected override void OnEnable() { base.OnEnable(); + _isKinematicProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent._isKinematic)); _angleMinProperty = serializedObject.FindProperty(nameof(GateColliderComponent._angleMin)); _angleMaxProperty = serializedObject.FindProperty(nameof(GateColliderComponent._angleMax)); _elasticityProperty = serializedObject.FindProperty(nameof(GateColliderComponent.Elasticity)); @@ -53,6 +55,7 @@ public override void OnInspectorGUI() BeginEditing(); + PropertyField(_isKinematicProperty, "Movable"); PropertyField(_angleMinProperty, "Close Angle"); PropertyField(_angleMaxProperty, "Open Angle"); PropertyField(_elasticityProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index f2539c78c..f9008d377 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -111,7 +111,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native internal ref BumperState GetBumperState(int colliderId, ref NativeColliders col) => ref BumperStates.GetValueByRef(col.GetItemId(colliderId)); - internal ref GateState GetGateState(int colliderId) => ref GateStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref GateState GetGateState(int colliderId, ref NativeColliders colliders) => ref GateStates.GetValueByRef(colliders.GetItemId(colliderId)); internal ref SurfaceState GetSurfaceState(int colliderId) => ref SurfaceStates.GetValueByRef(Colliders.GetItemId(colliderId)); @@ -139,6 +139,9 @@ internal void Transform(int colliderId, float4x4 matrix) case ColliderType.Spinner: KinematicColliders.Spinner(colliderId).Transform(KinematicCollidersAtIdentity.Spinner(colliderId), matrix); break; + case ColliderType.Gate: + KinematicColliders.Gate(colliderId).Transform(KinematicCollidersAtIdentity.Gate(colliderId), matrix); + break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 41aa4c6be..25c2eca40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -97,7 +97,7 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.Gate: - ref var gateState = ref state.GetGateState(colliderId); + ref var gateState = ref state.GetGateState(colliderId, ref colliders); GateCollider.Collide(ref ball, ref ball.CollisionEvent, ref gateState.Movement, ref state.EventQueue, in collHeader, in gateState.Static); break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 434bb8edd..a37b69fa4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -119,6 +119,10 @@ public void TransformToIdentity(NativeParallelHashMap itemIdToTra ref var spinnerCollider = ref SpinnerColliders.GetElementAsRef(lookup.Index); spinnerCollider.Transform(SpinnerColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Gate: + ref var gateCollider = ref GateColliders.GetElementAsRef(lookup.Index); + gateCollider.Transform(GateColliders[lookup.Index], math.inverse(matrix)); + break; } } } @@ -182,6 +186,7 @@ internal int Add(FlipperCollider collider) return collider.Id; } + internal int Add(GateCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(GateCollider collider) { collider.Id = Lookups.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index 255edd5d2..fa705b46e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -117,5 +117,3 @@ The only dynamic collision in Visual Pinball is collision between balls. For this, another KD-Tree is re-created on each physics cycle. Then, during phase 3.1, balls are not only checked against static objects, but also against other balls. - - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index 13eca7981..90b00025b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -96,12 +96,19 @@ public void Lift(float speed, float angleDeg) #endregion #region Collider Generation + protected override bool FireHitEvents => true; + protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) { - var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref colliders); + var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); + var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, matrix); + if (ColliderComponent._isKinematic) { + colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref kinematicColliders); + } else { + colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref colliders); + } } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 28bda1dcd..ddd4cfc4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -34,8 +34,8 @@ public int Id public ColliderHeader Header; - public readonly LineCollider LineSeg0; - public readonly LineCollider LineSeg1; + public LineCollider LineSeg0; + public LineCollider LineSeg1; public ColliderBounds Bounds { get; private set; } @@ -48,6 +48,19 @@ public GateCollider(in LineCollider lineSeg0, in LineCollider lineSeg1, Collider Bounds = LineSeg0.Bounds; } + public void Transform(GateCollider collider, float4x4 matrix) + { + LineSeg0 = collider.LineSeg0.Transform(matrix); + LineSeg1 = collider.LineSeg1.Transform(matrix); + Bounds = collider.LineSeg0.Bounds; + } + + public GateCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + #region Narrowphase public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index ef8bd0d53..b28449b90 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -17,13 +17,14 @@ // ReSharper disable InconsistentNaming using System.ComponentModel; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Gate; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Gate Collider")] - public class GateColliderComponent : ColliderComponent, IGateColliderData + public class GateColliderComponent : ColliderComponent, IGateColliderData, IKinematicColliderComponent { #region Data @@ -68,5 +69,16 @@ public class GateColliderComponent : ColliderComponent, protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.GateApi ?? new GateApi(gameObject, player, physicsEngine); + + #region IKinematicColliderComponent + + [Tooltip("If set, transforming this object will transform the colliders as well.")] + public bool _isKinematic; + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 8e00197f2..5c55e1731 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT; @@ -27,12 +26,14 @@ internal class GateColliderGenerator private readonly IGateData _data; private readonly IGateColliderData _collData; private readonly GateApi _api; + private readonly float4x4 _matrix; - internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderData collData) + internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderData collData, float4x4 matrix) { _api = gateApi; _data = data; _collData = collData; + _matrix = matrix; } internal void GenerateColliders(float height, ref ColliderReference colliders) // var height = table.GetSurfaceHeight(_data.Surface, _data.Center.X, _data.Center.Y); @@ -47,47 +48,47 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat var radAngle = math.radians(_data.Rotation); var tangent = new float2(math.cos(radAngle), math.sin(radAngle)); - GenerateGateCollider(ref colliders, height, radAngle); - GenerateLineCollider(ref colliders, height, tangent); + GenerateGateCollider(ref colliders, height); + GenerateLineCollider(ref colliders, height); if (_data.ShowBracket) { GenerateBracketColliders(ref colliders, height, tangent); } } - private void GenerateGateCollider(ref ColliderReference colliders, float height, float radAngle) + private void GenerateGateCollider(ref ColliderReference colliders, float height) { - var halfLength = _data.Length * 0.5f; - var sn = math.sin(radAngle); - var cs = math.cos(radAngle); + // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct + // position, we generate them at the origin and then transform them later. + + const float halfLength = 50f; var v1 = new float2( - _data.PosX - cs * (halfLength + PhysicsConstants.PhysSkin), - _data.PosY - sn * (halfLength + PhysicsConstants.PhysSkin) + -(halfLength + PhysicsConstants.PhysSkin), + 0 ); var v2 = new float2( - _data.PosX + cs * (halfLength + PhysicsConstants.PhysSkin), - _data.PosY + sn * (halfLength + PhysicsConstants.PhysSkin) + halfLength + PhysicsConstants.PhysSkin, + 0 ); - var lineSeg0 = new LineCollider(v1, v2, height, height + 2.0f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); - var lineSeg1 = new LineCollider(v2, v1, height, height + 2.0f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); + var lineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); - colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo())); + colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); } - private void GenerateLineCollider(ref ColliderReference colliders, float height, float2 tangent) + private void GenerateLineCollider(ref ColliderReference colliders, float height) { if (_collData.TwoWay) { return; } // oversize by the ball's radius to prevent the ball from clipping through - var halfLength = _data.Length * 0.5f; - var center = new float2(_data.PosX, _data.PosY); - var rgv0 = center + (halfLength + PhysicsConstants.PhysSkin) * tangent; - var rgv1 = center - (halfLength + PhysicsConstants.PhysSkin) * tangent; + const float halfLength = 50f; + var rgv0 = new float2(halfLength + PhysicsConstants.PhysSkin, 0f); + var rgv1 = new float2(-halfLength + PhysicsConstants.PhysSkin, 0f); var info = _api.GetColliderInfo(ItemType.Invalid); // hack to not treat this line seg as gate - colliders.Add(new LineCollider(rgv0, rgv1, height, height + 2.0f * PhysicsConstants.PhysSkin, info)); //!! = ball diameter + colliders.AddLine(rgv0, rgv1, -2f * PhysicsConstants.PhysSkin, 0, info, _matrix); //!! = ball diameter } private void GenerateBracketColliders(ref ColliderReference colliders, float height, float2 tangent) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 5cb9ec8f6..7f31e0c31 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -111,16 +111,21 @@ public bool ShowBracket { get { private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - GateApi = new GateApi(gameObject, player, physicsEngine); + GateApi = new GateApi(gameObject, Player, physicsEngine); - player.Register(GateApi, this); + Player.Register(GateApi, this); if (GetComponent()) { RegisterPhysics(physicsEngine); } } + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Wiring @@ -139,6 +144,9 @@ private void Awake() #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); @@ -152,12 +160,15 @@ public override void UpdateTransforms() t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, Position.z + PositionZ); // scale - t.localScale = Physics.ScaleToWorld(Length, Length, Length); + t.localScale = new float3(Length * 0.01f); // rotation - t.localEulerAngles = Physics.RotateToWorld(0, 0, Rotation); + t.localRotation = quaternion.RotateY(math.radians(Rotation)); } + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + + #endregion #region Conversion From 4f3fc99199c2b9237a56624fd4eb871062bc3833 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 19 Nov 2023 23:07:55 +0100 Subject: [PATCH 027/208] gates: Update collision height. --- .../VPT/Gate/GateColliderGenerator.cs | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 5c55e1731..07922ff19 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -48,14 +48,14 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat var radAngle = math.radians(_data.Rotation); var tangent = new float2(math.cos(radAngle), math.sin(radAngle)); - GenerateGateCollider(ref colliders, height); - GenerateLineCollider(ref colliders, height); + GenerateGateCollider(ref colliders); + GenerateLineCollider(ref colliders); if (_data.ShowBracket) { - GenerateBracketColliders(ref colliders, height, tangent); + GenerateBracketColliders(ref colliders, height); } } - private void GenerateGateCollider(ref ColliderReference colliders, float height) + private void GenerateGateCollider(ref ColliderReference colliders) { // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct // position, we generate them at the origin and then transform them later. @@ -70,13 +70,13 @@ private void GenerateGateCollider(ref ColliderReference colliders, float height) 0 ); - var lineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); - var lineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); + var lineSeg0 = new LineCollider(v1, v2, 0, 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, 0, 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); } - private void GenerateLineCollider(ref ColliderReference colliders, float height) + private void GenerateLineCollider(ref ColliderReference colliders) { if (_collData.TwoWay) { return; @@ -91,23 +91,22 @@ private void GenerateLineCollider(ref ColliderReference colliders, float height) colliders.AddLine(rgv0, rgv1, -2f * PhysicsConstants.PhysSkin, 0, info, _matrix); //!! = ball diameter } - private void GenerateBracketColliders(ref ColliderReference colliders, float height, float2 tangent) + private void GenerateBracketColliders(ref ColliderReference colliders, float height) { - var center = new float2(_data.PosX, _data.PosY); - var halfLength = _data.Length * 0.5f; + var halfLength = 50f; colliders.Add(new CircleCollider( - center + tangent * halfLength, + new float2(halfLength, 0), 1f, - height, - height + _data.Height, + 0, + 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate )); colliders.Add(new CircleCollider( - center - tangent * halfLength, + new float2(-halfLength, 0), 1f, - height, - height + _data.Height, + 0, + 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate )); } From 3258ac4f87404f6296fc3b1d950f7d8514160ee5 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 20 Nov 2023 23:11:20 +0100 Subject: [PATCH 028/208] gates: Move gate rotation to component. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 14 ++++++++-- .../VPT/AnimationComponent.cs | 1 + .../VPT/Gate/GateComponent.cs | 23 +++++++++++++--- .../VPT/Gate/GateWireAnimationComponent.cs | 16 ++++++++++- .../VPT/IRotatableAnimationComponent.cs | 27 +++++++++++++++++++ .../VPT/IRotatableAnimationComponent.cs.meta | 3 +++ 6 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index a3c86b627..437b1b60a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -74,6 +74,8 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); + [NonSerialized] private readonly Dictionary _rotatableComponent = new(); + #endregion [NonSerialized] private readonly Queue _inputActions = new(); @@ -122,6 +124,7 @@ internal void Register(T item) where T : MonoBehaviour var itemId = go.GetInstanceID(); _transforms.TryAdd(itemId, go.transform); + // states switch (item) { case BallComponent c: if (!_ballStates.Ref.ContainsKey(itemId)) { @@ -142,6 +145,11 @@ internal void Register(T item) where T : MonoBehaviour case SurfaceComponent c: _surfaceStates.Ref[itemId] = c.CreateState(); break; case TriggerComponent c: _triggerStates.Ref[itemId] = c.CreateState(); break; } + + // animations + if (item is IRotatableAnimationComponent rotatableComponent) { + _rotatableComponent.TryAdd(itemId, rotatableComponent); + } } internal Transform UnregisterBall(int ballId) @@ -373,8 +381,10 @@ private void Update() using (var enumerator = _gateStates.Ref.GetEnumerator()) { while (enumerator.MoveNext()) { ref var gateState = ref enumerator.Current.Value; - var gateTransform = _transforms[gateState.WireItemId]; - gateTransform.localRotation = quaternion.RotateX(-gateState.Movement.Angle); + var component = _rotatableComponent[enumerator.Current.Key]; + component.OnRotationUpdated(gateState.Movement.Angle); +// var gateTransform = _transforms[gateState.WireItemId]; +// gateTransform.localRotation = quaternion.RotateX(-gateState.Movement.Angle); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs index a5700fba1..45e37be59 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs @@ -27,6 +27,7 @@ public abstract class AnimationComponent : SubComponent, - IGateData, ISwitchDeviceComponent, IOnSurfaceComponent + IGateData, ISwitchDeviceComponent, IOnSurfaceComponent, IRotatableAnimationComponent { #region Data @@ -107,6 +106,9 @@ public bool ShowBracket { get { #region Runtime + [NonSerialized] + private IRotatableAnimationComponent[] _animatedComponents; + public GateApi GateApi { get; private set; } private void Awake() @@ -119,6 +121,10 @@ private void Awake() if (GetComponent()) { RegisterPhysics(physicsEngine); } + + _animatedComponents = GetComponentsInChildren() + .Select(gwa => gwa as IRotatableAnimationComponent) + .ToArray(); } private void Start() @@ -351,5 +357,16 @@ public override Vector3 GetEditorPosition() => Surface != null public override void SetEditorScale(Vector3 scale) => _length = scale.x; #endregion + + #region IRotatableAnimationComponent + + public void OnRotationUpdated(float angleRad) + { + foreach (var animatedComponent in _animatedComponents) { + animatedComponent.OnRotationUpdated(angleRad); + } + } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs index b5857e119..fb19eeabb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs @@ -14,11 +14,25 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.VPT.Gate; namespace VisualPinball.Unity { - public class GateWireAnimationComponent : AnimationComponent + public class GateWireAnimationComponent : AnimationComponent, IRotatableAnimationComponent { + private float min = float.MaxValue; + private float max = float.MinValue; + + public void OnRotationUpdated(float angleRad) + { + min = math.min(angleRad, min); + max = math.max(angleRad, max); + + Debug.Log($"Rotate: {angleRad} ({math.degrees(angleRad)}) [{math.degrees(min)} - {math.degrees(max)}]"); + + transform.localRotation = quaternion.RotateX(-angleRad); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs new file mode 100644 index 000000000..bdee830cd --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs @@ -0,0 +1,27 @@ +// 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 . + +namespace VisualPinball.Unity +{ + /// + /// Components implementing this interface will get angle updates from the physics + /// engine, and can either relay them to their children or perform their own rotation. + /// + public interface IRotatableAnimationComponent + { + void OnRotationUpdated(float angleRad); + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs.meta new file mode 100644 index 000000000..ca0b114bf --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IRotatableAnimationComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d1ec4eb068c44a748a0db7e41ae3b659 +timeCreated: 1700432085 \ No newline at end of file From aa856b0a57095d3c24027bb0e801668019e3b06b Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 21 Nov 2023 20:36:00 +0100 Subject: [PATCH 029/208] spinners: Move rotations to components. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 6 +-- .../VPT/Gate/GateWireAnimationComponent.cs | 3 +- .../VPT/Spinner/SpinnerComponent.cs | 22 ++++++++- .../Spinner/SpinnerLeverAnimationComponent.cs | 48 +++++++++++++++++++ .../SpinnerLeverAnimationComponent.cs.meta | 3 ++ .../Spinner/SpinnerPlateAnimationComponent.cs | 7 ++- 6 files changed, 81 insertions(+), 8 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 437b1b60a..19beca6b7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -383,8 +383,6 @@ private void Update() ref var gateState = ref enumerator.Current.Value; var component = _rotatableComponent[enumerator.Current.Key]; component.OnRotationUpdated(gateState.Movement.Angle); -// var gateTransform = _transforms[gateState.WireItemId]; -// gateTransform.localRotation = quaternion.RotateX(-gateState.Movement.Angle); } } @@ -402,8 +400,8 @@ private void Update() using (var enumerator = _spinnerStates.Ref.GetEnumerator()) { while (enumerator.MoveNext()) { ref var spinnerState = ref enumerator.Current.Value; - var spinnerTransform = _transforms[spinnerState.AnimationItemId]; - spinnerTransform.localRotation = quaternion.RotateX(-spinnerState.Movement.Angle); + var component = _rotatableComponent[enumerator.Current.Key]; + component.OnRotationUpdated(spinnerState.Movement.Angle); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs index fb19eeabb..33cb4b4ae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateWireAnimationComponent.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using Unity.Mathematics; -using UnityEngine; using VisualPinball.Engine.VPT.Gate; namespace VisualPinball.Unity @@ -30,7 +29,7 @@ public void OnRotationUpdated(float angleRad) min = math.min(angleRad, min); max = math.max(angleRad, max); - Debug.Log($"Rotate: {angleRad} ({math.degrees(angleRad)}) [{math.degrees(min)} - {math.degrees(max)}]"); + // Debug.Log($"Rotate: {angleRad} ({math.degrees(angleRad)}) [{math.degrees(min)} - {math.degrees(max)}]"); transform.localRotation = quaternion.RotateX(-angleRad); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 179e85091..a69773076 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Common; @@ -35,7 +36,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Spinner")] public class SpinnerComponent : MainRenderableComponent, - ISwitchDeviceComponent, IOnSurfaceComponent + ISwitchDeviceComponent, IOnSurfaceComponent, IRotatableAnimationComponent { #region Data @@ -92,6 +93,9 @@ public class SpinnerComponent : MainRenderableComponent, #region Runtime + [NonSerialized] + private IRotatableAnimationComponent[] _animatedComponents; + public SpinnerApi SpinnerApi { get; private set; } private void Awake() @@ -102,6 +106,11 @@ private void Awake() Player.Register(SpinnerApi, this); RegisterPhysics(physicsEngine); + + _animatedComponents = GetComponentsInChildren() + .Select(gwa => gwa as IRotatableAnimationComponent) + .Concat(GetComponentsInChildren().Select(gwa => gwa as IRotatableAnimationComponent)) + .ToArray(); } private void Start() @@ -326,5 +335,16 @@ public bool ShowBracket { public override void SetEditorScale(Vector3 scale) => Length = scale.x; #endregion + + #region IRotatableAnimationComponent + + public void OnRotationUpdated(float angleRad) + { + foreach (var animatedComponent in _animatedComponents) { + animatedComponent.OnRotationUpdated(angleRad); + } + } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs new file mode 100644 index 000000000..ba1e883d9 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs @@ -0,0 +1,48 @@ +// 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 . + +// ReSharper disable InconsistentNaming + +using Unity.Mathematics; +using UnityEngine; +using VisualPinball.Engine.VPT.Spinner; + +namespace VisualPinball.Unity +{ + public class SpinnerLeverAnimationComponent : AnimationComponent, IRotatableAnimationComponent + { + [Tooltip("Shifts the lever angle by the given amount of degrees.")] + [Range(-90f, 90f)] + public float Shift = 15f; + + [Tooltip("Start angle of the movement")] + [Range(-90f, 90f)] + public float MinAngle = -1.56f; + + [Tooltip("End angle of the movement")] + [Range(-90f, 90f)] + public float MaxAngle = 13.83f; + + public void OnRotationUpdated(float angleRad) + { + angleRad = math.radians((math.degrees(angleRad) + Shift) % 360f); + var a = math.abs(angleRad - math.PI); + var pos = math.sin(math.smoothstep(0, math.PI, a)); + var leverAngleDeg = math.lerp(MinAngle, MaxAngle, pos); + transform.localRotation = quaternion.RotateX(math.radians(leverAngleDeg)); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs.meta new file mode 100644 index 000000000..245473ded --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerLeverAnimationComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b4422fe9bba04f85b3e263fab66e0d02 +timeCreated: 1700519112 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerPlateAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerPlateAnimationComponent.cs index addded44a..7182e3754 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerPlateAnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerPlateAnimationComponent.cs @@ -14,11 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using Unity.Mathematics; using VisualPinball.Engine.VPT.Spinner; namespace VisualPinball.Unity { - public class SpinnerPlateAnimationComponent : AnimationComponent + public class SpinnerPlateAnimationComponent : AnimationComponent, IRotatableAnimationComponent { + public void OnRotationUpdated(float angleRad) + { + transform.localRotation = quaternion.RotateX(-angleRad); + } } } From 5320d923aa8e9069cc6dea9aca413bdcafe13c64 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 22 Nov 2023 21:56:06 +0100 Subject: [PATCH 030/208] math: Add debug print extensions. --- .../VisualPinball.Unity/Extensions/MathExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index 307ecc93f..82c7a6112 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -201,8 +201,11 @@ public static float3 ToEuler(this quaternion quaternion) { var sinYCosP = +2.0 * (q.w * q.z + q.x * q.y); var cosYCosP = +1.0 - 2.0 * (q.y * q.y + q.z * q.z); res.z = math.atan2(sinYCosP, cosYCosP); - return (float3) res; } + + public static string ToDebugString(this float4x4 m) => $"{((Matrix4x4)m).ToString()}\nt: {m.GetTranslation()}\nr: {math.degrees(m.GetRotationVector())}\ns: {m.GetScale()}"; + + public static string ToDebugString(this Matrix4x4 m) => ((float4x4)m).ToDebugString(); } } From cda4905306c39fad2f932141369ad04bd68a25e6 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 23 Nov 2023 22:13:47 +0100 Subject: [PATCH 031/208] physics: Use matrix on flipper collision by transforming the ball before testing and colliding. --- .../VisualPinball.Unity/Game/PhysicsState.cs | 15 +++++- .../Game/PhysicsStaticCollision.cs | 16 +++++- .../Physics/Collision/Aabb.cs | 50 +++++++++++++++++-- .../Physics/Collision/CollisionEventData.cs | 6 +++ .../VisualPinball.Unity/Physics/Physics.cs | 22 ++++++-- .../VisualPinball.Unity/VPT/Ball/BallState.cs | 14 ++++++ .../VPT/Flipper/FlipperApi.cs | 13 +++-- .../VPT/Flipper/FlipperCollider.cs | 24 ++++----- 8 files changed, 132 insertions(+), 28 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index f9008d377..5d8a0161f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -205,8 +205,19 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt case ColliderType.Flipper: ref var flipperState = ref GetFlipperState(colliderId); - return colliders.Flipper(colliderId).HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, - in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ball, ball.CollisionEvent.HitTime); + ref var flipperCollider = ref colliders.Flipper(colliderId); + var ballTransformed = ball; + ballTransformed.Transform(math.inverse(flipperCollider.Matrix)); + + var hitTime = flipperCollider.HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, + in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ballTransformed, ball.CollisionEvent.HitTime); + + if (hitTime > 0) { + // transform hit normal back to world space + newCollEvent.Transform(flipperCollider.Matrix); + } + + return hitTime; case ColliderType.Plunger: ref var plungerState = ref GetPlungerState(colliderId); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 25c2eca40..dc0b85532 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -16,6 +16,7 @@ // ReSharper disable ConvertIfStatementToSwitchStatement +using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Unity.Collections; @@ -90,10 +91,23 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r case ColliderType.Flipper: ref var flipperState = ref state.GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); - flipperCollider.Collide(ref ball, ref ball.CollisionEvent, ref flipperState.Movement, + + var ballTransformed = ball; + ballTransformed.Transform(math.inverse(flipperCollider.Matrix)); + + var colEventTransformed = ball.CollisionEvent; + colEventTransformed.Transform(math.inverse(flipperCollider.Matrix)); + + flipperCollider.Collide(ref ballTransformed, ref colEventTransformed, ref flipperState.Movement, ref state.EventQueue, in ball.Id, in flipperState.Tricks, in flipperState.Static, in flipperState.Velocity, in flipperState.Hit, state.Env.TimeMsec ); + + colEventTransformed.Transform(flipperCollider.Matrix); + ballTransformed.Transform(flipperCollider.Matrix); + ball = ballTransformed; + ball.CollisionEvent = colEventTransformed; + break; case ColliderType.Gate: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs index a98e46524..a107b0642 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs @@ -123,19 +123,63 @@ public readonly bool Equals(Aabb a) a.ZHigh == ZHigh; } - public override readonly bool Equals(object obj) + public Aabb Transform(float4x4 m) + { + var t = m.GetTranslation(); + var translateOnly = float4x4.Translate(new float3(t.x, t.y, 0)); + var p1 = translateOnly.MultiplyPoint(new float3(Left, Top, ZHigh)); + var p2 = translateOnly.MultiplyPoint(new float3(Right, Top, ZHigh)); + var p3 = translateOnly.MultiplyPoint(new float3(Left, Bottom, ZHigh)); + var p4 = translateOnly.MultiplyPoint(new float3(Right, Bottom, ZHigh)); + var p5 = translateOnly.MultiplyPoint(new float3(Left, Top, ZLow)); + var p6 = translateOnly.MultiplyPoint(new float3(Right, Top, ZLow)); + var p7 = translateOnly.MultiplyPoint(new float3(Left, Bottom, ZLow)); + var p8 = translateOnly.MultiplyPoint(new float3(Right, Bottom, ZLow)); + + //return new Aabb(Left, Right, Top, Bottom, ZLow, ZHigh); + //return new Aabb(Left, Right, Top, Bottom, ZLow, ZHigh); + // todo optimize, use min(float3) instead of min(float) + return new Aabb( + min(p1.x, p2.x, p3.x, p4.x, p5.x, p6.x, p7.x, p8.x), + max(p1.x, p2.x, p3.x, p4.x, p5.x, p6.x, p7.x, p8.x), + min(p1.y, p2.y, p3.y, p4.y, p5.y, p6.y, p7.y, p8.y), + max(p1.y, p2.y, p3.y, p4.y, p5.y, p6.y, p7.y, p8.y), + min(p1.z, p2.z, p3.z, p4.z, p5.z, p6.z, p7.z, p8.z), + max(p1.z, p2.z, p3.z, p4.z, p5.z, p6.z, p7.z, p8.z) + ); + } + + private static float min(params float[] values) + { + var min = float.MaxValue; + foreach (var value in values) { + min = math.min(min, value); + } + return min; + } + + private static float max(params float[] values) + { + var max = float.MinValue; + foreach (var value in values) { + max = math.max(max, value); + } + return max; + } + + public readonly override bool Equals(object obj) { if (obj is Aabb) return Equals(obj); return false; } - public override readonly string ToString() + public readonly override string ToString() { return $"Aabb {Left} → {Right} | {Top} ↘ {Bottom} | {ZLow} ↑ {ZHigh}"; } - public override readonly int GetHashCode() + public readonly override int GetHashCode() { return HashCode.Combine(Right, Left, Bottom, Top, ZLow, ZHigh); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs index eb332dda6..13ad6e451 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs @@ -64,5 +64,11 @@ public bool HasCollider() { return ColliderId > -1 || BallId != 0; } + + public void Transform(float4x4 matrix) + { + HitNormal = matrix.MultiplyVector(HitNormal); + HitVelocity = matrix.MultiplyVector(new float3(HitVelocity, 0)).xy; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index f147521ea..a57051bc8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.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 System; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; @@ -68,6 +69,8 @@ public static class Physics //public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.mul(matrix, new float4(p, 1f)).xyz; public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.transform(matrix, p); + // todo optimize + public static float3 MultiplyVector(this float4x4 matrix, float3 p) => ((Matrix4x4)matrix).MultiplyVector(p); /// /// Returns the transformation matrix of an item in VPX space.
@@ -78,12 +81,23 @@ public static class Physics /// World-to-local transformation matrix of the item. /// Local-to-World transformation matrix of the playfield. /// Transformation matrix of the item in VPX space. - public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) => math.mul( - math.mul(WorldToVpx, - math.inverse(math.mul(worldToLocal, playfieldToWorld)) - ), + [Obsolete("use (and test) LocalToWorldTranslateWithinPlayfield()")] + public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) + => math.mul( + math.mul(WorldToVpx, + math.inverse(math.mul(worldToLocal, playfieldToWorld)) + ), VpxToWorld); + public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) + => math.mul( + math.mul(WorldToVpx, + math.mul(worldToPlayfield, localToWorldMatrix) + ), + VpxToWorld + ); + + #endregion #region Translation diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs index efef6c68b..0d3e2624f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs @@ -142,5 +142,19 @@ public override string ToString() { return $"Ball{Id} ({Position.x}/{Position.y})"; } + + public void Transform(float4x4 matrix) + { + Position = matrix.MultiplyPoint(Position); + EventPosition = matrix.MultiplyPoint(EventPosition); + Velocity = matrix.MultiplyVector(Velocity); + AngularMomentum = matrix.MultiplyVector(AngularMomentum); + + //BallOrientation = math.mul(matrix, BallOrientation) + //BallOrientationForUnity; + + OldVelocity = matrix.MultiplyVector(OldVelocity); + //CollisionEvent.Transform(matrix); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 25cae1a33..65afe2b3a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -227,17 +227,21 @@ protected override void CreateColliders(ref ColliderReference colliders, var height = MainComponent.PositionZ; var baseRadius = math.max(MainComponent.BaseRadius, 0.01f); var hitCircleBase = new CircleCollider( - MainComponent.Position, + float2.zero, // flipper collision is always done through the center and a matrix baseRadius, height, height + MainComponent.Height, GetColliderInfo() ); + var playfield = MainComponent.GetComponentInParent(); + var worldToPlayfield = playfield ? playfield.transform.worldToLocalMatrix : Matrix4x4.identity; // check which side we are at var multiplicator = 0.0f; - if (ColliderComponent.useFlipperTricksPhysics) - multiplicator = MainComponent.StartAngle > MainComponent.EndAngle ? -1f : 1f; // usually: -1f = left flipper + if (ColliderComponent.useFlipperTricksPhysics) { + multiplicator = + MainComponent.StartAngle > MainComponent.EndAngle ? -1f : 1f; // usually: -1f = left flipper + } // and add ColliderComponent.Overshoot to the endangle so that the bounding box is correctly calculated colliders.Add( @@ -248,7 +252,8 @@ protected override void CreateColliders(ref ColliderReference colliders, MainComponent.EndRadius, MainComponent.StartAngle, MainComponent.EndAngle + ColliderComponent.Overshoot * multiplicator, - GetColliderInfo() + GetColliderInfo(), + MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield) ) ); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 043725ccf..be6bb0335 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -27,27 +27,25 @@ internal struct FlipperCollider : ICollider public int Id { get => Header.Id; - set { - Header.Id = value; - var bounds = Bounds; - bounds.ColliderId = value; - Bounds = bounds; - } + set => Header.Id = value; } public ColliderHeader Header; + public float4x4 Matrix; private readonly CircleCollider _hitCircleBase; private readonly float _zLow; private readonly float _zHigh; - public ColliderBounds Bounds { get; set; } + public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, _bounds.Transform(Matrix)); public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + private readonly Aabb _bounds; #region Setup - public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float startRadius, float endRadius, float startAngle, float endAngle, ColliderInfo info) : this() + public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float startRadius, float endRadius, + float startAngle, float endAngle, ColliderInfo info, float4x4 matrix) : this() { var bounds = hitCircleBase.Bounds; Header.Init(info, ColliderType.Flipper); @@ -76,7 +74,10 @@ public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 90f); aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 180f); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb); + var l = flipperRadius * 1.2f; + _bounds = new Aabb(-l, l, -l, l, -l, l); + + Matrix = matrix; } private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float2 c, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) @@ -148,11 +149,6 @@ public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in FlipperMovementState movementState, in FlipperTricksData tricks, in FlipperStaticData matData, in BallState ball, float dTime) { - // todo - // if (!_data.IsEnabled) { - // return -1.0f; - // } - var lastFace = hitData.LastHitFace; // for effective computing, adding a last face hit value to speed calculations From 9b525671506d7a74dc389ed36aea7aa0f7cefa6a Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 24 Nov 2023 23:15:42 +0100 Subject: [PATCH 032/208] physics: Ignore Y-rotation when computing translation matrix for flipper. --- .../VisualPinball.Unity/Physics/Physics.cs | 5 ++++- .../VisualPinball.Unity/VPT/Flipper/FlipperApi.cs | 2 +- .../VPT/Flipper/FlipperComponent.cs | 14 +++++++++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index a57051bc8..f0c08dd1a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -89,7 +89,10 @@ public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 world ), VpxToWorld); - public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) + + public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) => LocalToWorldTranslateWithinPlayfield(localToWorldMatrix, worldToPlayfield); + + public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorldMatrix, float4x4 worldToPlayfield) => math.mul( math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorldMatrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 65afe2b3a..142769b85 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -253,7 +253,7 @@ protected override void CreateColliders(ref ColliderReference colliders, MainComponent.StartAngle, MainComponent.EndAngle + ColliderComponent.Overshoot * multiplicator, GetColliderInfo(), - MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield) + MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield) ) ); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 3ee02ad77..1c282a593 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -169,12 +169,24 @@ public override void UpdateTransforms() var t = transform; // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); + //t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); // rotation t.localEulerAngles = new Vector3(0, _startAngle, 0); } + public float4x4 LocalToWorldPhysicsMatrix + { + get + { + var t = transform; + var m = t.localToWorldMatrix; + var r = t.localRotation.eulerAngles; + //return float4x4.TRS(t.position, quaternion.identity, ((float4x4)m).GetScale()); + return math.mul(m, math.inverse(float4x4.RotateY(math.radians(r.y)))); + } + } + private FlipperApi _flipperApi; public float _originalRotateZ; From 91f661891cc697b304faf1a6cb9d936f617dd849 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 25 Nov 2023 14:19:13 +0100 Subject: [PATCH 033/208] physics: Add new interface for non-transformable colliders. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 10 ++++-- .../VPT/Flipper/FlipperColliderComponent.cs | 12 ++++++- .../VPT/ICollidableComponent.cs | 16 +++++++++ .../ICollidableNonTransformableComponent.cs | 33 +++++++++++++++++++ ...ollidableNonTransformableComponent.cs.meta | 3 ++ 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 19beca6b7..4cd3bacd7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -190,6 +190,8 @@ private void Start() { // create static octree var sw = Stopwatch.StartNew(); + var playfield = GetComponentInChildren(); + var colliderItems = GetComponentsInChildren(); Debug.Log($"Found {colliderItems.Length} collidable items."); var colliders = new ColliderReference(Allocator.Temp); @@ -198,6 +200,9 @@ private void Start() if (!colliderItem.IsCollidable) { _disabledCollisionItems.Ref.Add(colliderItem.ItemId); } + if (colliderItem is ICollidableNonTransformableComponent nonTransformable) { + var matrix = nonTransformable.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix); + } colliderItem.GetColliders(_player, ref colliders, ref kinematicColliders, 0); } @@ -217,9 +222,8 @@ private void Start() // create octree var elapsedMs = sw.Elapsed.TotalMilliseconds; - var playfieldBounds = GetComponentInChildren().Bounds; - _playfieldBounds = GetComponentInChildren().Bounds; - _octree = new NativeOctree(playfieldBounds, 1024, 10, Allocator.Persistent); + _playfieldBounds = playfield.Bounds; + _octree = new NativeOctree(_playfieldBounds, 1024, 10, Allocator.Persistent); sw.Restart(); var populateJob = new PhysicsPopulateJob { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 1a23f397d..59aaba107 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -16,6 +16,7 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Flipper; @@ -23,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Flipper Collider")] [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] - public class FlipperColliderComponent : ColliderComponent + public class FlipperColliderComponent : ColliderComponent, ICollidableNonTransformableComponent { #region Data @@ -160,5 +161,14 @@ public class FlipperColliderComponent : ColliderComponent MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); + + float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + + void ICollidableNonTransformableComponent.GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, + float4x4 translateWithinPlayfieldMatrix, float margin) + { + InstantiateColliderApi(player, null).CreateColliders(ref colliders, ref kinematicColliders, margin); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 76e47e4fb..086d9efe1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -18,8 +18,24 @@ namespace VisualPinball.Unity { public interface ICollidableComponent { + /// + /// Generates the colliders. + /// + /// + /// + /// + /// internal void GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin); + + /// + /// The unique identifier of the main item. + /// internal int ItemId { get; } + + /// + /// Returns whether this specific item is set to collidable, i.e. whether can it ever be + /// collided with during gameplay. + /// internal bool IsCollidable { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs new file mode 100644 index 000000000..f4e781205 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs @@ -0,0 +1,33 @@ +// 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 . + +using Unity.Mathematics; + +namespace VisualPinball.Unity +{ + public interface ICollidableNonTransformableComponent : ICollidableComponent + { + /// + /// The translation matrix, that will be applied in reverse to the ball + /// for hit testing and collision. + /// + /// The playfield's worldToLocal matrix. + /// + internal float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); + + internal void GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta new file mode 100644 index 000000000..8f6d8b9b4 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f11252e97bdd48619151f104f22693d3 +timeCreated: 1701003509 \ No newline at end of file From 1ac5b88b6eb0c062510f755b01495c748f9fb676 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 26 Nov 2023 15:04:26 +0100 Subject: [PATCH 034/208] physics: Move transformation matrix for non-transformable colliders into physics engine. --- .../VPT/TransformInspector.cs | 14 +++++----- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 16 +++++++++--- .../VisualPinball.Unity/Game/PhysicsState.cs | 10 +++++-- .../Game/PhysicsStaticCollision.cs | 10 ++++--- .../Game/PhysicsUpdateJob.cs | 5 +++- .../VPT/Bumper/BumperApi.cs | 2 +- .../VisualPinball.Unity/VPT/CollidableApi.cs | 8 +++--- .../VPT/ColliderComponent.cs | 26 +++++++++++-------- .../VPT/Flipper/FlipperApi.cs | 6 ++--- .../VPT/Flipper/FlipperCollider.cs | 5 ++-- .../VPT/Flipper/FlipperColliderComponent.cs | 6 ----- .../VPT/Flipper/FlipperComponent.cs | 1 - .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 2 +- .../VPT/HitTarget/DropTargetApi.cs | 3 ++- .../VPT/HitTarget/HitTargetApi.cs | 3 ++- .../VisualPinball.Unity/VPT/IApi.cs | 5 +++- .../VPT/ICollidableComponent.cs | 7 ++++- .../ICollidableNonTransformableComponent.cs | 2 -- .../VPT/Kicker/KickerApi.cs | 2 +- .../VPT/MetalWireGuide/MetalWireGuideApi.cs | 3 ++- .../VPT/Playfield/PlayfieldApi.cs | 2 +- .../VPT/Plunger/PlungerApi.cs | 3 ++- .../VPT/Primitive/PrimitiveApi.cs | 4 ++- .../VisualPinball.Unity/VPT/Ramp/RampApi.cs | 4 ++- .../VPT/Rubber/RubberApi.cs | 2 +- .../VPT/Spinner/SpinnerApi.cs | 3 ++- .../VPT/Surface/SurfaceApi.cs | 4 ++- .../VPT/Trigger/TriggerApi.cs | 3 ++- 28 files changed, 97 insertions(+), 64 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs index 26650e05f..746c1efd2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity.Editor { - [CustomEditor(typeof(Transform))] + //[CustomEditor(typeof(Transform))] [CanEditMultipleObjects] public class TransformInspector : UnityEditor.Editor { @@ -258,12 +258,12 @@ private void HandleRotationTool() private void HandleMoveTool() { - var handlePos = _primaryItem.GetEditorPosition(); - EditorGUI.BeginChangeCheck(); - handlePos = HandlesUtils.HandlePosition(_transform.GetComponentInParent(), handlePos, _primaryItem.EditorPositionType); - if (EditorGUI.EndChangeCheck()) { - FinishMove(handlePos); - } + // var handlePos = _primaryItem.GetEditorPosition(); + // EditorGUI.BeginChangeCheck(); + // handlePos = HandlesUtils.HandlePosition(_transform.GetComponentInParent(), handlePos, _primaryItem.EditorPositionType); + // if (EditorGUI.EndChangeCheck()) { + // FinishMove(handlePos); + // } } private void HandleScaleTool() diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 4cd3bacd7..dd9ea33bf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -72,6 +72,7 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private readonly Dictionary _transforms = new(); [NonSerialized] private LazyInit> _kinematicTransforms = new(() => new(0, Allocator.Persistent)); [NonSerialized] private LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private LazyInit> _nonTransformableColliderMatrices = new(() => new(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); [NonSerialized] private readonly Dictionary _rotatableComponent = new(); @@ -200,10 +201,15 @@ private void Start() if (!colliderItem.IsCollidable) { _disabledCollisionItems.Ref.Add(colliderItem.ItemId); } - if (colliderItem is ICollidableNonTransformableComponent nonTransformable) { - var matrix = nonTransformable.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix); + + var translateWithinPlayfieldMatrix = colliderItem is ICollidableNonTransformableComponent nonTransformableColliderItem + ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix) + : float4x4.identity; + if (colliderItem is ICollidableNonTransformableComponent) { + _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; } - colliderItem.GetColliders(_player, ref colliders, ref kinematicColliders, 0); + + colliderItem.GetColliders(_player, this, ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0); } // allocate colliders @@ -272,6 +278,7 @@ private void Update() KinematicCollidersAtIdentity = _kinematicCollidersAtIdentity, KinematicColliderLookups = _kinematicColliderLookups, UpdatedKinematicTransforms = _updatedKinematicTransforms.Ref, + NonTransformableColliderMatrices = _nonTransformableColliderMatrices.Ref, InsideOfs = _insideOfs, Events = events, Balls = _ballStates.Ref, @@ -292,7 +299,8 @@ private void Update() var env = _physicsEnv.Ref[0]; var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref _kinematicColliders, - ref _kinematicCollidersAtIdentity, ref _updatedKinematicTransforms.Ref, ref _kinematicColliderLookups, ref events, + ref _kinematicCollidersAtIdentity, ref _updatedKinematicTransforms.Ref, ref _nonTransformableColliderMatrices.Ref, + ref _kinematicColliderLookups, ref events, ref _insideOfs, ref _ballStates.Ref, ref _bumperStates.Ref, ref _dropTargetStates.Ref, ref _flipperStates.Ref, ref _gateStates.Ref, ref _hitTargetStates.Ref, ref _kickerStates.Ref, ref _plungerStates.Ref, ref _spinnerStates.Ref, ref _surfaceStates.Ref, ref _triggerStates.Ref, ref _disabledCollisionItems.Ref, ref _swapBallCollisionHandling); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 5d8a0161f..7b9bb8511 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -30,6 +30,7 @@ internal struct PhysicsState internal NativeColliders KinematicColliders; internal NativeColliders KinematicCollidersAtIdentity; internal NativeParallelHashMap UpdatedKinematicTransforms; // transformations of the items, in vpx space. + internal NativeParallelHashMap NonTransformableColliderMatrices; internal NativeParallelHashMap KinematicColliderLookups; internal NativeQueue.ParallelWriter EventQueue; @@ -51,6 +52,7 @@ internal struct PhysicsState public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref NativeColliders colliders, ref NativeColliders kinematicColliders, ref NativeColliders kinematicCollidersAtIdentity, ref NativeParallelHashMap updatedKinematicTransforms, + ref NativeParallelHashMap nonTransformableColliderMatrices, ref NativeParallelHashMap kinematicColliderLookups, ref NativeQueue.ParallelWriter eventQueue, ref InsideOfs insideOfs, ref NativeParallelHashMap balls, ref NativeParallelHashMap bumperStates, ref NativeParallelHashMap dropTargetStates, @@ -66,6 +68,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native KinematicColliders = kinematicColliders; KinematicCollidersAtIdentity = kinematicCollidersAtIdentity; UpdatedKinematicTransforms = updatedKinematicTransforms; + NonTransformableColliderMatrices = nonTransformableColliderMatrices; KinematicColliderLookups = kinematicColliderLookups; EventQueue = eventQueue; InsideOfs = insideOfs; @@ -119,6 +122,8 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native #region Transform + internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId) => ref NonTransformableColliderMatrices.GetValueByRef(Colliders.GetItemId(colliderId)); + internal void Transform(int colliderId, float4x4 matrix) { switch (GetColliderType(ref KinematicColliders, colliderId)) @@ -206,15 +211,16 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt case ColliderType.Flipper: ref var flipperState = ref GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); + ref var matrix = ref GetNonTransformableColliderMatrix(colliderId); var ballTransformed = ball; - ballTransformed.Transform(math.inverse(flipperCollider.Matrix)); + ballTransformed.Transform(math.inverse(matrix)); var hitTime = flipperCollider.HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ballTransformed, ball.CollisionEvent.HitTime); if (hitTime > 0) { // transform hit normal back to world space - newCollEvent.Transform(flipperCollider.Matrix); + newCollEvent.Transform(matrix); } return hitTime; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index dc0b85532..76eb458d1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -91,20 +91,22 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r case ColliderType.Flipper: ref var flipperState = ref state.GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); + ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId); + var matrixInv = math.inverse(matrix); var ballTransformed = ball; - ballTransformed.Transform(math.inverse(flipperCollider.Matrix)); + ballTransformed.Transform(matrixInv); var colEventTransformed = ball.CollisionEvent; - colEventTransformed.Transform(math.inverse(flipperCollider.Matrix)); + colEventTransformed.Transform(matrixInv); flipperCollider.Collide(ref ballTransformed, ref colEventTransformed, ref flipperState.Movement, ref state.EventQueue, in ball.Id, in flipperState.Tricks, in flipperState.Static, in flipperState.Velocity, in flipperState.Hit, state.Env.TimeMsec ); - colEventTransformed.Transform(flipperCollider.Matrix); - ballTransformed.Transform(flipperCollider.Matrix); + colEventTransformed.Transform(matrix); + ballTransformed.Transform(matrix); ball = ballTransformed; ball.CollisionEvent = colEventTransformed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index e341dad75..6b279a82c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -38,6 +38,8 @@ internal struct PhysicsUpdateJob : IJob public NativeColliders KinematicColliders; public NativeColliders KinematicCollidersAtIdentity; public NativeParallelHashMap UpdatedKinematicTransforms; + public NativeParallelHashMap NonTransformableColliderMatrices; + public NativeParallelHashMap KinematicColliderLookups; public InsideOfs InsideOfs; public NativeQueue.ParallelWriter Events; @@ -61,7 +63,8 @@ public void Execute() { var env = PhysicsEnv[0]; var state = new PhysicsState(ref env, ref Octree, ref Colliders, ref KinematicColliders, - ref KinematicCollidersAtIdentity, ref UpdatedKinematicTransforms, ref KinematicColliderLookups, ref Events, + ref KinematicCollidersAtIdentity, ref UpdatedKinematicTransforms, ref NonTransformableColliderMatrices, + ref KinematicColliderLookups, ref Events, ref InsideOfs, ref Balls, ref BumperStates, ref DropTargetStates, ref FlipperStates, ref GateStates, ref HitTargetStates, ref KickerStates, ref PlungerStates, ref SpinnerStates, ref SurfaceStates, ref TriggerStates, ref DisabledCollisionItems, ref SwapBallCollisionHandling); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 5880014a1..0e601443e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -116,7 +116,7 @@ void IApiCoil.OnCoil(bool enabled) protected override float HitThreshold => ColliderComponent.Threshold; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var height = MainComponent.PositionZ; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index 194443a60..71b4e29ce 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs @@ -42,14 +42,16 @@ protected CollidableApi(GameObject go, Player player, PhysicsEngine physicsEngin protected virtual bool FireHitEvents => false; protected virtual float HitThreshold => 0; - protected abstract void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin); + protected abstract void CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); - void IApiColliderGenerator.CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) + void IApiColliderGenerator.CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { if (!ColliderComponent) { return; } - CreateColliders(ref colliders, ref kinematicColliders, margin); + CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, margin); } ColliderInfo IApiColliderGenerator.GetColliderInfo() => GetColliderInfo(MainComponent.ItemType); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 17a81e83a..90818e501 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -26,7 +26,6 @@ using UnityEditor; using UnityEngine; using UnityEngine.Profiling; -using UnityEngine.UIElements; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Flipper; using Mesh = UnityEngine.Mesh; @@ -67,7 +66,7 @@ public abstract class ColliderComponent : SubComponent _colliderMesh != null && !_collidersDirty; + private bool HasCachedColliders => false;// _colliderMesh != null && !_collidersDirty; private void Start() { @@ -139,27 +138,32 @@ private void OnDrawGizmos() Gizmos.matrix = ltw * (Matrix4x4)Physics.VpxToWorld; Handles.matrix = Gizmos.matrix; + var translateWithinPlayfieldMatrix = this is ICollidableNonTransformableComponent nonTransformableColliderItem + ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(math.inverse(ltw)) + : float4x4.identity; + var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { if (Application.isPlaying && IsKinematic) { if (!_physicsEngine) { - _physicsEngine = GetComponentInParent(); + _physicsEngine = GetComponentInParent(); // todo cache } var colliders = _physicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); if (showColliders) { _colliderMesh = GenerateColliderMesh(colliders); - _collidersDirty = false; + //_collidersDirty = false; } } else { + var api = InstantiateColliderApi(player, null); var colliders = new ColliderReference(Allocator.Temp); var kinematicColliders = new ColliderReference(Allocator.Temp, true); try { - api.CreateColliders(ref colliders, ref kinematicColliders, 0.1f); + api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); if (showColliders) { _colliderMesh = IsKinematic @@ -186,9 +190,9 @@ private void OnDrawGizmos() var colliders = new ColliderReference(Allocator.TempJob); var kinematicColliders = new ColliderReference(Allocator.TempJob, true); try { - api.CreateColliders(ref colliders, ref kinematicColliders, 0.1f); + api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); - var playfieldBounds = GetComponentInChildren().Bounds; + var playfieldBounds = GetComponentInParent().Bounds; var octree = new NativeOctree(playfieldBounds, 32, 10, Allocator.Persistent); var nativeColliders = new NativeColliders(ref colliders, Allocator.TempJob); var populateJob = new PhysicsPopulateJob { @@ -532,10 +536,10 @@ private static void DrawAabb(Aabb aabb, bool isSelected) #endregion - void ICollidableComponent.GetColliders(Player player, ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) - => InstantiateColliderApi(player, null) - .CreateColliders(ref colliders, ref kinematicColliders, margin); + void ICollidableComponent.GetColliders(Player player, PhysicsEngine physicsEngine, ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + => InstantiateColliderApi(player, physicsEngine) + .CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, margin); int ICollidableComponent.ItemId => MainComponent.gameObject.GetInstanceID(); bool ICollidableComponent.IsCollidable => isActiveAndEnabled; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 142769b85..22bfebc66 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -222,7 +222,7 @@ void IApiCollidable.OnCollide(int ballId, float hit) #region Collider Generation protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var height = MainComponent.PositionZ; var baseRadius = math.max(MainComponent.BaseRadius, 0.01f); @@ -233,8 +233,6 @@ protected override void CreateColliders(ref ColliderReference colliders, height + MainComponent.Height, GetColliderInfo() ); - var playfield = MainComponent.GetComponentInParent(); - var worldToPlayfield = playfield ? playfield.transform.worldToLocalMatrix : Matrix4x4.identity; // check which side we are at var multiplicator = 0.0f; @@ -253,7 +251,7 @@ protected override void CreateColliders(ref ColliderReference colliders, MainComponent.StartAngle, MainComponent.EndAngle + ColliderComponent.Overshoot * multiplicator, GetColliderInfo(), - MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield) + translateWithinPlayfieldMatrix ) ); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index be6bb0335..83ca5b520 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -32,12 +32,11 @@ public int Id public ColliderHeader Header; - public float4x4 Matrix; private readonly CircleCollider _hitCircleBase; private readonly float _zLow; private readonly float _zHigh; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, _bounds.Transform(Matrix)); + public ColliderBounds Bounds { get; private set; } public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private readonly Aabb _bounds; @@ -77,7 +76,7 @@ public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float var l = flipperRadius * 1.2f; _bounds = new Aabb(-l, l, -l, l, -l, l); - Matrix = matrix; + Bounds = new ColliderBounds(Header.ItemId, Header.Id, _bounds.Transform(matrix)); } private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float2 c, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 59aaba107..97d7e83c3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -164,11 +164,5 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - - void ICollidableNonTransformableComponent.GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, - float4x4 translateWithinPlayfieldMatrix, float margin) - { - InstantiateColliderApi(player, null).CreateColliders(ref colliders, ref kinematicColliders, margin); - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 1c282a593..87f5ebaf3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -182,7 +182,6 @@ public float4x4 LocalToWorldPhysicsMatrix var t = transform; var m = t.localToWorldMatrix; var r = t.localRotation.eulerAngles; - //return float4x4.TRS(t.position, quaternion.identity, ((float4x4)m).GetScale()); return math.mul(m, math.inverse(float4x4.RotateY(math.radians(r.y)))); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index 90b00025b..e309e79c2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -100,7 +100,7 @@ public void Lift(float speed, float angleDeg) protected override bool FireHitEvents => true; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, matrix); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs index 1764216ce..e8f3dabdb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -103,7 +104,7 @@ private void SetIsDropped(bool isDropped) protected override float HitThreshold => ColliderComponent.Threshold; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent); colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 65b8c2b21..9c4871871 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -60,7 +61,7 @@ internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override float HitThreshold => ColliderComponent.Threshold; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new HitTargetColliderGenerator(this, MainComponent, MainComponent); colliderGenerator.GenerateColliders(ref colliders); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs index 9db4834e1..69bf86656 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; @@ -42,8 +43,10 @@ public interface IApiColliderGenerator ///
/// List to add colliders to. /// List to add kinematic colliders to. + /// /// - void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin); + void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, + float4x4 translateWithinPlayfieldMatrix, float margin); /// /// Computes collider info based on the component data. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 086d9efe1..6c8b7fec4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using Unity.Mathematics; + namespace VisualPinball.Unity { public interface ICollidableComponent @@ -22,10 +24,13 @@ public interface ICollidableComponent /// Generates the colliders. /// /// + /// /// /// + /// /// - internal void GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin); + internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); /// /// The unique identifier of the main item. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs index f4e781205..58c01a347 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs @@ -27,7 +27,5 @@ public interface ICollidableNonTransformableComponent : ICollidableComponent /// The playfield's worldToLocal matrix. /// internal float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); - - internal void GetColliders(Player player, ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index b78b51141..f3c655dde 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -225,7 +225,7 @@ private void KickXYZ(float angle, float speed, float inclination, float x, float #region Collider Generation protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var height = MainComponent.PositionZ; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs index 9b04d3b79..ab1ab1774 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.MetalWireGuide; @@ -44,7 +45,7 @@ internal MetalWireGuideApi(GameObject go, Player player, PhysicsEngine physicsEn protected override float HitThreshold => 2.0f; // hard coded threshold for now protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent)); colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs index 79e4aef01..c2268259c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs @@ -30,7 +30,7 @@ internal PlayfieldApi(GameObject go, Player player, PhysicsEngine physicsEngine) #region Collider Generation protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var info = ((IApiColliderGenerator)this).GetColliderInfo(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 328d9f430..f59125699 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using Unity.Mathematics; using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.VPT.Plunger; @@ -143,7 +144,7 @@ private IApiCoil Coil(string deviceItem) #region Collider Generation protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo())); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs index ecea5bf90..b42e2e5dc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Primitive; @@ -43,7 +44,8 @@ internal PrimitiveApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent, MainComponent); if (ColliderComponent._isKinematic) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index 5c8092e5d..699c7c5b1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Ramp; @@ -61,7 +62,8 @@ void IApi.OnDestroy() protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); if (ColliderComponent.IsKinematic) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index e97d9068c..914572665 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -45,7 +45,7 @@ internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override float HitThreshold => 2.0f; // hard coded threshold for now protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new RubberColliderGenerator( this, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs index 9b3f4082c..a61c37707 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs @@ -83,7 +83,8 @@ public SpinnerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : b protected override bool FireHitEvents => true; - protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent, matrix); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index 0662b25e8..38ba7c172 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Surface; @@ -47,7 +48,8 @@ internal SurfaceApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { if (MainComponent.DragPoints.Length == 0) { return; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs index a867681e5..8c2895db3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Trigger; @@ -64,7 +65,7 @@ internal TriggerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override bool FireHitEvents => true; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float margin) + ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var meshComponent = GameObject.GetComponent(); var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent); From f0b038fed95aad30eabaf9a51e3485851984bfbb Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 26 Nov 2023 15:27:58 +0100 Subject: [PATCH 035/208] physics: Move ball transformation higher up to make it reusable. --- .../VisualPinball.Unity/Game/PhysicsState.cs | 18 +++--------- .../Game/PhysicsStaticCollision.cs | 28 ++++++++----------- .../Game/PhysicsStaticNarrowPhase.cs | 21 +++++++++++++- .../VisualPinball.Unity/VPT/Ball/BallState.cs | 2 +- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 7b9bb8511..be0cfb6f1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -122,7 +122,8 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native #region Transform - internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId) => ref NonTransformableColliderMatrices.GetValueByRef(Colliders.GetItemId(colliderId)); + internal bool HasNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) => NonTransformableColliderMatrices.ContainsKey(colliders.GetItemId(colliderId)); + internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) => ref NonTransformableColliderMatrices.GetValueByRef(colliders.GetItemId(colliderId)); internal void Transform(int colliderId, float4x4 matrix) { @@ -211,19 +212,8 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt case ColliderType.Flipper: ref var flipperState = ref GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); - ref var matrix = ref GetNonTransformableColliderMatrix(colliderId); - var ballTransformed = ball; - ballTransformed.Transform(math.inverse(matrix)); - - var hitTime = flipperCollider.HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, - in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ballTransformed, ball.CollisionEvent.HitTime); - - if (hitTime > 0) { - // transform hit normal back to world space - newCollEvent.Transform(matrix); - } - - return hitTime; + return flipperCollider.HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, + in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ball, ball.CollisionEvent.HitTime); case ColliderType.Plunger: ref var plungerState = ref GetPlungerState(colliderId); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 76eb458d1..086d3e7f9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -45,6 +45,12 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r if (CollidesWithItem(ref colliders, ref collHeader, ref ball, ref state)) { return; } + + if (state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); + ball.Transform(math.inverse(matrix)); + } + switch (state.GetColliderType(ref colliders, colliderId)) { case ColliderType.Circle: @@ -91,25 +97,10 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r case ColliderType.Flipper: ref var flipperState = ref state.GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); - ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId); - var matrixInv = math.inverse(matrix); - - var ballTransformed = ball; - ballTransformed.Transform(matrixInv); - - var colEventTransformed = ball.CollisionEvent; - colEventTransformed.Transform(matrixInv); - - flipperCollider.Collide(ref ballTransformed, ref colEventTransformed, ref flipperState.Movement, + flipperCollider.Collide(ref ball, ref ball.CollisionEvent, ref flipperState.Movement, ref state.EventQueue, in ball.Id, in flipperState.Tricks, in flipperState.Static, in flipperState.Velocity, in flipperState.Hit, state.Env.TimeMsec ); - - colEventTransformed.Transform(matrix); - ballTransformed.Transform(matrix); - ball = ballTransformed; - ball.CollisionEvent = colEventTransformed; - break; case ColliderType.Gate: @@ -147,6 +138,11 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; } + if (state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); + ball.Transform(matrix); + } + // remove trial hit object pointer ball.CollisionEvent.ClearCollider(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs index 211b16a7d..e23ae533a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs @@ -17,6 +17,7 @@ // ReSharper disable ForCanBeConvertedToForeach using Unity.Collections; +using Unity.Mathematics; using Unity.Profiling; namespace VisualPinball.Unity @@ -41,8 +42,26 @@ ref PhysicsState state if (!state.IsColliderActive(ref colliders, overlappingColliderId)) { continue; } + + float newTime; var newCollEvent = new CollisionEventData(); - var newTime = state.HitTest(ref colliders, overlappingColliderId, ref ball, ref newCollEvent, ref contacts); + + if (state.HasNonTransformableColliderMatrix(overlappingColliderId, ref colliders)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(overlappingColliderId, ref colliders); + var ballTransformed = ball; + ballTransformed.Transform(math.inverse(matrix)); + + newTime = state.HitTest(ref colliders, overlappingColliderId, ref ballTransformed, ref newCollEvent, ref contacts); + + if (newTime > 0) { + // transform hit normal back to world space + newCollEvent.Transform(matrix); + } + + } else { + newTime = state.HitTest(ref colliders, overlappingColliderId, ref ball, ref newCollEvent, ref contacts); + } + SaveCollisions(ref ball, ref newCollEvent, ref contacts, overlappingColliderId, newTime, colliders.KinematicColliders); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs index 0d3e2624f..0f32281b4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs @@ -154,7 +154,7 @@ public void Transform(float4x4 matrix) //BallOrientationForUnity; OldVelocity = matrix.MultiplyVector(OldVelocity); - //CollisionEvent.Transform(matrix); + CollisionEvent.Transform(matrix); } } } From 344a76ad5042f85f72edc1c98f0641aac4061d4a Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 26 Nov 2023 21:53:01 +0100 Subject: [PATCH 036/208] physics: Add ball transformation to contacts. --- .../VisualPinball.Unity/Game/PhysicsCycle.cs | 6 +++- .../Physics/Collision/ContactPhysics.cs | 32 +++++++++++++------ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs index 14308f335..f04de9ee2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs @@ -151,7 +151,11 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati for (var i = 0; i < _contacts.Length; i++) { ref var contact = ref _contacts.GetElementAsRef(i); ref var ball = ref state.Balls.GetValueByRef(contact.BallId); - ContactPhysics.Update(ref contact, ref ball, ref state, hitTime); + if (contact.CollEvent.IsKinematic) { + ContactPhysics.Update(ref contact, ref ball, ref state, ref state.KinematicColliders, hitTime); + } else { + ContactPhysics.Update(ref contact, ref ball, ref state, ref state.Colliders, hitTime); + } } PerfMarkerContacts.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs index d43c0d46d..5868e0be1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs @@ -14,27 +14,41 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using Unity.Mathematics; + namespace VisualPinball.Unity { internal static class ContactPhysics { - internal static void Update(ref ContactBufferElement contact, ref BallState ball, ref PhysicsState state, float hitTime) + internal static void Update(ref ContactBufferElement contact, ref BallState ball, ref PhysicsState state, ref NativeColliders colliders, float hitTime) { ref var collEvent = ref contact.CollEvent; if (collEvent.ColliderId > -1) { // collide with static collider - var collHeader = collEvent.IsKinematic - ? ref state.GetColliderHeader(ref state.KinematicColliders, collEvent.ColliderId) - : ref state.GetColliderHeader(ref state.Colliders, collEvent.ColliderId); + + var gravity = state.Env.Gravity; + if (state.HasNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders); + var matrixInv = math.inverse(matrix); + ball.Transform(matrixInv); + collEvent.Transform(matrixInv); + gravity = matrixInv.MultiplyVector(gravity); + } + + ref var collHeader = ref state.GetColliderHeader(ref colliders, collEvent.ColliderId); if (collHeader.Type == ColliderType.Flipper) { - var flipperCollider = collEvent.IsKinematic - ? ref state.KinematicColliders.Flipper(collEvent.ColliderId) - : ref state.Colliders.Flipper(collEvent.ColliderId); + ref var flipperCollider = ref colliders.Flipper(collEvent.ColliderId); ref var flipperState = ref state.GetFlipperState(collEvent.ColliderId); - flipperCollider.Contact(ref ball, ref flipperState.Movement, in collEvent, - in flipperState.Static, in flipperState.Velocity, hitTime, in state.Env.Gravity); + flipperCollider.Contact(ref ball, ref flipperState.Movement, in collEvent, in flipperState.Static, in flipperState.Velocity, hitTime, in gravity); } else { Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in state.Env.Gravity); } + + if (state.HasNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders); + ball.Transform(matrix); + collEvent.Transform(matrix); + } + } else if (collEvent.BallId != 0) { // collide with ball var collHeader = collEvent.IsKinematic ? ref state.GetColliderHeader(ref state.KinematicColliders, contact.CollEvent.ColliderId) From 4d5fe051712ac14638aa11b6638aa5618ec99e97 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 26 Nov 2023 22:53:34 +0100 Subject: [PATCH 037/208] flipper: Read position and start angle from transformation matrix instead of data. --- .../VPT/Flipper/FlipperInspector.cs | 23 ++++++-- .../Patcher/Tables/Rock.cs | 5 +- .../VPT/Flipper/FlipperCollider.cs | 5 +- .../VPT/Flipper/FlipperComponent.cs | 59 ++++++++++++------- 4 files changed, 59 insertions(+), 33 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs index f52655c88..b848528aa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs @@ -17,6 +17,7 @@ // ReSharper disable AssignmentInConditionalExpression using UnityEditor; +using UnityEngine; using VisualPinball.Engine.VPT.Flipper; namespace VisualPinball.Unity.Editor @@ -27,8 +28,6 @@ public class FlipperInspector : MainInspector private bool _foldoutBaseGeometry = true; private bool _foldoutRubberGeometry = true; - private SerializedProperty _positionProperty; - private SerializedProperty _startAngleProperty; private SerializedProperty _endAngleProperty; private SerializedProperty _surfaceProperty; private SerializedProperty _isEnabledProperty; @@ -46,8 +45,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(FlipperComponent.Position)); - _startAngleProperty = serializedObject.FindProperty(nameof(FlipperComponent._startAngle)); _endAngleProperty = serializedObject.FindProperty(nameof(FlipperComponent.EndAngle)); _surfaceProperty = serializedObject.FindProperty(nameof(FlipperComponent._surface)); _isEnabledProperty = serializedObject.FindProperty(nameof(FlipperComponent.IsEnabled)); @@ -72,8 +69,22 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_startAngleProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the flipper on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Flipper Position"); + MainComponent.Position = newPos; + } + + // start angle + EditorGUI.BeginChangeCheck(); + var newAngle = EditorGUILayout.Slider(new GUIContent("Start Angle", "Angle of the flipper in start position (not flipped)"), MainComponent.StartAngle, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Flipper Start Angle"); + MainComponent.StartAngle = newAngle; + } + PropertyField(_endAngleProperty); PropertyField(_surfaceProperty); PropertyField(_isEnabledProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Rock.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Rock.cs index f2b614c30..2f87e1c91 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Rock.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Rock.cs @@ -257,9 +257,8 @@ public void ReparentFlipper(FlipperComponent flipper, GameObject go, ref GameObj PatcherUtil.Reparent(go, parent); PatcherUtil.Hide(go.GetComponentInChildren().gameObject); - flipper.Position.x = 0; - flipper.Position.y = 0; - flipper._startAngle = 0; + flipper.Position = Vector2.zero; + flipper.StartAngle = 0; } [NameMatch("sw40")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 83ca5b520..1482f95ce 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -39,7 +39,6 @@ public int Id public ColliderBounds Bounds { get; private set; } public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private readonly Aabb _bounds; #region Setup @@ -74,9 +73,9 @@ public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 180f); var l = flipperRadius * 1.2f; - _bounds = new Aabb(-l, l, -l, l, -l, l); + aabb = new Aabb(-l, l, -l, l, -l, l); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, _bounds.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb.Transform(matrix)); } private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float2 c, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 87f5ebaf3..ed969b6c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -43,15 +43,33 @@ public class FlipperComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the flipper on the playfield.")] - public Vector2 Position; + public Vector2 Position { + get + { + var pos = transform.localPosition; + var posVpx = pos.TranslateToVpx(); + return new Vector2(posVpx.x, posVpx.y); + } + set + { + var posVpx = new Vector3(value.x, value.y, 0); + var pos = posVpx.TranslateToWorld(); + var t = transform; + t.localPosition = new Vector3(pos.x, t.localPosition.y, pos.z); + } + } + public float PosX => Position.x; public float PosY => Position.y; - [Range(-180f, 180f)] - [Tooltip("Angle of the flipper in start position (not flipped)")] - public float _startAngle = 121.0f; - public float StartAngle => _startAngle; + public float StartAngle { + get => transform.localEulerAngles.y; + set { + var t = transform; + var e = t.localEulerAngles; + t.localEulerAngles = new Vector3(e.x, value, e.z); + } + } [Range(-180f, 180f)] [Tooltip("Angle of the flipper in end position (flipped)")] @@ -172,7 +190,7 @@ public override void UpdateTransforms() //t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); // rotation - t.localEulerAngles = new Vector3(0, _startAngle, 0); + t.localEulerAngles = new Vector3(0, StartAngle, 0); } public float4x4 LocalToWorldPhysicsMatrix @@ -191,7 +209,7 @@ public float4x4 LocalToWorldPhysicsMatrix public float RotateZ { set { - _startAngle = _originalRotateZ + value; + StartAngle = _originalRotateZ + value; _flipperApi.StartAngle = _originalRotateZ + value; UpdateTransforms(); } @@ -200,8 +218,7 @@ public float RotateZ { public float2 RotatedPosition { get => new(Position.x, Position.y); set { - Position.x = value.x; - Position.y = value.y; + Position = value; UpdateTransforms(); } } @@ -216,7 +233,7 @@ public override IEnumerable SetData(FlipperData data) // transforms Position = data.Center.ToUnityVector2(); - _startAngle = data.StartAngle > 180f ? data.StartAngle - 360f : data.StartAngle; + StartAngle = data.StartAngle > 180f ? data.StartAngle - 360f : data.StartAngle; // geometry _height = data.Height; @@ -278,7 +295,7 @@ public override FlipperData CopyDataTo(FlipperData data, string[] materialNames, // name and transforms data.Name = name; data.Center = Position.ToVertex2D(); - data.StartAngle = _startAngle; + data.StartAngle = StartAngle; data.Surface = Surface != null ? Surface.name : string.Empty; // geometry @@ -323,7 +340,7 @@ public override void CopyFromObject(GameObject go) var flipperComponent = go.GetComponent(); if (flipperComponent != null) { Position = flipperComponent.Position; - _startAngle = flipperComponent._startAngle; + StartAngle = flipperComponent.StartAngle; EndAngle = flipperComponent.EndAngle; Surface = flipperComponent.Surface; IsDualWound = flipperComponent.IsDualWound; @@ -354,8 +371,8 @@ public override Vector3 GetEditorPosition() => Surface != null public override void SetEditorPosition(Vector3 pos) => Position = ((float3)pos).xy; public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(_startAngle, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => _startAngle = ClampDegrees(rot.x); + public override Vector3 GetEditorRotation() => new Vector3(StartAngle, 0f, 0f); + public override void SetEditorRotation(Vector3 rot) => StartAngle = ClampDegrees(rot.x); public override ItemDataTransformType EditorScaleType => ItemDataTransformType.None; @@ -386,7 +403,7 @@ protected void OnDrawGizmosSelected() // Draw arc arrow List arrow = new List(); float start = -90F; - float end = -90F + EndAngle - _startAngle; + float end = -90F + EndAngle - StartAngle; if (IsLeft) { (start, end) = (end, start); } @@ -413,7 +430,7 @@ protected void OnDrawGizmosSelected() #region Flipper Correction - private bool IsLeft => EndAngle < _startAngle; + private bool IsLeft => EndAngle < StartAngle; //! Add a circle arc on a given polygon (used for enclosing poygon) public static void AddPolyArc(List poly, Vector3 center, float radius, float angleFrom, float angleTo, float stepSize = 1F, float height = 0f) @@ -448,7 +465,7 @@ public static void AddPolyArc(List poly, Vector3 center, float radius, public List GetEnclosingPolygon(float margin = 0.0F, float stepSize = 5F, float height = 0f) { - var swing = EndAngle - _startAngle; + var swing = EndAngle - StartAngle; swing = Mathf.Abs(swing); List ret = new List(); // TODO: caching @@ -491,7 +508,7 @@ public List GetEnclosingPolygon(float margin = 0.0F, float stepSize = 5 private void Awake() { - _originalRotateZ = _startAngle; + _originalRotateZ = StartAngle; var player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); FlipperApi = new FlipperApi(gameObject, player, physicsEngine); @@ -582,7 +599,7 @@ internal FlipperStaticData GetMaterialData(FlipperColliderComponent colliderComp var endRadius = math.max(_endRadius, 0.01f); // radius of flipper end flipperRadius = math.max(flipperRadius, 0.01f); // radius of flipper arc, center-to-center radius - var angleStart = math.radians(_startAngle); + var angleStart = math.radians(StartAngle); var angleEnd = math.radians(EndAngle); if (angleEnd == angleStart) { @@ -683,7 +700,7 @@ private void SetupFlipperCorrection(FlipperColliderComponent colliderComponent) // p = ta.transform.InverseTransformPoint(transform.TransformPoint(poly[i])) // this is basically (ta.transform.worldToLocalMatrix * transform.localToWorldMatrix) // but I couldn't get this transformation correctly from our current transforms. - // using Matrix4x4.Rotate(quaternion.Euler(new float3(0, 0, -_startAngle))) and transforming + // using Matrix4x4.Rotate(quaternion.Euler(new float3(0, 0, -StartAngle))) and transforming // to localPos was close, but not close enough. var flipperToPlayfield = new Matrix4x4( new Vector4(-0.50754f, 0.86163f, 0, 0), From 6c647f3f24ad735ead6698e67bead0d63869375e Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 27 Nov 2023 21:50:42 +0100 Subject: [PATCH 038/208] physics: Transform contact gravity for all collider types. --- .../VisualPinball.Unity/Physics/Collision/ContactPhysics.cs | 2 +- VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs index 5868e0be1..30546e060 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs @@ -40,7 +40,7 @@ internal static void Update(ref ContactBufferElement contact, ref BallState ball ref var flipperState = ref state.GetFlipperState(collEvent.ColliderId); flipperCollider.Contact(ref ball, ref flipperState.Movement, in collEvent, in flipperState.Static, in flipperState.Velocity, hitTime, in gravity); } else { - Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in state.Env.Gravity); + Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in gravity); } if (state.HasNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index f0c08dd1a..440402826 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -89,9 +89,6 @@ public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 world ), VpxToWorld); - - public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) => LocalToWorldTranslateWithinPlayfield(localToWorldMatrix, worldToPlayfield); - public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorldMatrix, float4x4 worldToPlayfield) => math.mul( math.mul(WorldToVpx, From eb7923bcfecd2c33f67c1cd430fd278c3131b7b8 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 27 Nov 2023 22:04:09 +0100 Subject: [PATCH 039/208] physics: Fix debug physics collider gizmo for non-transformable items. --- .../VPT/ColliderComponent.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 90818e501..5400b417e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -134,14 +134,20 @@ private void OnDrawGizmos() Profiler.EndSample(); return; } - var ltw = GetComponentInParent().transform.localToWorldMatrix; - Gizmos.matrix = ltw * (Matrix4x4)Physics.VpxToWorld; - Handles.matrix = Gizmos.matrix; + var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; + // todo optimize var translateWithinPlayfieldMatrix = this is ICollidableNonTransformableComponent nonTransformableColliderItem - ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(math.inverse(ltw)) + ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(math.inverse(playfieldToWorld)) + : float4x4.identity; + + var translateFullyWithinPlayfieldMatrix = this is ICollidableNonTransformableComponent + ? ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)) : float4x4.identity; + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; + Handles.matrix = Gizmos.matrix; + var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { @@ -513,15 +519,16 @@ private void AddFlipperCollider(List vertices, List normals, L return; } - var t = transform; - var ltp = Matrix4x4.TRS(t.localPosition.TranslateToVpx(), quaternion.Euler(0, 0, math.radians(flipperComponent.StartAngle)), t.localScale); +// var t = transform; +// var ltp = Matrix4x4.TRS(t.localPosition.TranslateToVpx(), quaternion.Euler(0, 0, math.radians(flipperComponent.StartAngle)), t.localScale); var startIdx = vertices.Count; - var mesh = new FlipperMeshGenerator(flipperComponent) - .GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f); + var mesh = new FlipperMeshGenerator(flipperComponent).GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f); for (var i = 0; i < mesh.Vertices.Length; i++) { var vertex = mesh.Vertices[i]; - vertices.Add(ltp.MultiplyPoint(vertex.ToUnityFloat3())); - normals.Add(ltp.MultiplyPoint(vertex.ToUnityNormalVector3())); + // vertices.Add(ltp.MultiplyPoint(vertex.ToUnityFloat3())); + // normals.Add(ltp.MultiplyVector(vertex.ToUnityNormalVector3())); + vertices.Add(vertex.ToUnityFloat3()); + normals.Add(vertex.ToUnityNormalVector3()); } indices.AddRange(mesh.Indices.Select(n => startIdx + n)); } From 90d01974927eeb7f8962aef55eeca4eed818a2df Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 27 Nov 2023 23:01:08 +0100 Subject: [PATCH 040/208] editor: Fix debug gizmos for AABBs and collider bodies. --- VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs | 3 +++ .../VisualPinball.Unity/VPT/ColliderComponent.cs | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 440402826..1cec53bfd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -89,6 +89,9 @@ public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 world ), VpxToWorld); + public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) + => LocalToWorldTranslateWithinPlayfield((float4x4)localToWorldMatrix, worldToPlayfield); + public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorldMatrix, float4x4 worldToPlayfield) => math.mul( math.mul(WorldToVpx, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 5400b417e..7e69cba77 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -179,6 +179,7 @@ private void OnDrawGizmos() } if (ShowAabbs) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; for (var i = 0; i < colliders.Count; i++) { var col = colliders[i]; DrawAabb(col.Bounds.Aabb, i == SelectedCollider); @@ -216,6 +217,9 @@ private void OnDrawGizmos() if (showColliders) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; + Handles.matrix = Gizmos.matrix; + var color = Application.isPlaying && IsKinematic ? Color.magenta : IsKinematic ? new Color(0, 1, 1) : Color.green; From 8ab24fca1c5ce23c30039410a027208aab79f738 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 27 Nov 2023 23:01:38 +0100 Subject: [PATCH 041/208] trigger: Make kinetic. --- .../VPT/Trigger/TriggerColliderInspector.cs | 4 +++- .../VPT/Trigger/TriggerApi.cs | 2 +- .../VPT/Trigger/TriggerColliderComponent.cs | 14 +++++++++++++- .../VPT/Trigger/TriggerColliderGenerator.cs | 13 +++++++------ .../VPT/Trigger/TriggerComponent.cs | 17 ++++++++++++++--- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerColliderInspector.cs index 9e3371849..c45242b20 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerColliderInspector.cs @@ -24,13 +24,14 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(TriggerColliderComponent)), CanEditMultipleObjects] public class TriggerColliderInspector : ColliderInspector { + private SerializedProperty _isKinematicProperty; private SerializedProperty _hitHeightProperty; private SerializedProperty _hitCircleRadiusProperty; protected override void OnEnable() { base.OnEnable(); - + _isKinematicProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent._isKinematic)); _hitHeightProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent.HitHeight)); _hitCircleRadiusProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent.HitCircleRadius)); } @@ -45,6 +46,7 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); + PropertyField(_isKinematicProperty, "Movable"); PropertyField(_hitHeightProperty, updateColliders: true); var meshComponent = (target as TriggerColliderComponent)!.GetComponent(); if (meshComponent && meshComponent.IsCircle) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs index 8c2895db3..e098a7151 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs @@ -68,7 +68,7 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var meshComponent = GameObject.GetComponent(); - var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent); + var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent, GetTransformationWithinPlayfield()); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index 1d78689b8..32e77e891 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Trigger Collider")] - public class TriggerColliderComponent : ColliderComponent + public class TriggerColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -48,10 +48,22 @@ public class TriggerColliderComponent : ColliderComponent GetPhysicsMaterialData(); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.TriggerApi ?? new TriggerApi(gameObject, player, physicsEngine); + + + #region IKinematicColliderComponent + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index 4d2060b8d..c088d9059 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.Math; @@ -28,15 +27,17 @@ public class TriggerColliderGenerator private readonly TriggerComponent _component; private readonly TriggerMeshComponent _meshComponent; private readonly TriggerColliderComponent _colliderComponent; + private readonly float4x4 _matrix; private bool IsRound => _meshComponent && _meshComponent.Shape is TriggerShape.TriggerStar or TriggerShape.TriggerButton; - public TriggerColliderGenerator(TriggerApi triggerApi, TriggerComponent component, TriggerColliderComponent colliderComponent, TriggerMeshComponent meshComponent) + public TriggerColliderGenerator(TriggerApi triggerApi, TriggerComponent component, TriggerColliderComponent colliderComponent, TriggerMeshComponent meshComponent, float4x4 matrix) { _api = triggerApi; _component = component; _meshComponent = meshComponent; _colliderComponent = colliderComponent; + _matrix = matrix; } internal void GenerateColliders(ref ColliderReference colliders) @@ -53,7 +54,7 @@ private void GenerateRoundHitObjects(ref ColliderReference colliders) { var height = _component.PositionZ; colliders.Add(new CircleCollider(_component.Center, _colliderComponent.HitCircleRadius, height, height + _colliderComponent.HitHeight, - _api.GetColliderInfo(), ColliderType.TriggerCircle)); + _api.GetColliderInfo(), ColliderType.TriggerCircle), _matrix); } private void GenerateCurvedHitObjects(ref ColliderReference colliders) @@ -69,7 +70,7 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) rgv[i] = vVertex[i]; rgv3D[i] = new float3(rgv[i].X, rgv[i].Y, height + (float)(PhysicsConstants.PhysSkin * 2.0)); } - ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, float4x4.identity); // todo adapt + ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, _matrix); for (var i = 0; i < count; i++) { var pv2 = rgv[i < count - 1 ? i + 1 : 0]; @@ -79,8 +80,8 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) } private void AddLineSeg(float2 pv1, float2 pv2, float height, ref ColliderReference colliders) { - colliders.Add(new LineCollider(pv1, pv2, height, height + math.max(_colliderComponent.HitHeight - 8.0f, 0f), - _api.GetColliderInfo(), ColliderType.TriggerLine)); + colliders.AddLine(pv1, pv2, height, height + math.max(_colliderComponent.HitHeight - 8.0f, 0f), + _api.GetColliderInfo(), _matrix, ColliderType.TriggerLine); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 2205b7d63..c3d8f607a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -86,16 +86,22 @@ public class TriggerComponent : MainRenderableComponent, private void Awake() { - var player = GetComponentInParent(); + Player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); - TriggerApi = new TriggerApi(gameObject, player, physicsEngine); + TriggerApi = new TriggerApi(gameObject, Player, physicsEngine); - player.Register(TriggerApi, this); + Player.Register(TriggerApi, this); if (GetComponentInChildren()) { RegisterPhysics(physicsEngine); } } + + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Wiring @@ -112,6 +118,9 @@ private void Awake() #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public Vector2 Center => Position; public void OnSurfaceUpdated() => UpdateTransforms(); @@ -132,6 +141,8 @@ public override void UpdateTransforms() t.localEulerAngles = new Vector3(0, Rotation, 0); } + public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + #endregion #region Conversion From d1b02ec27a52a44deef4e6775e23fd8c2b8ba460 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 27 Nov 2023 23:14:13 +0100 Subject: [PATCH 042/208] refactor: Move ball transformation code into separate methods. --- .../Game/PhysicsStaticCollision.cs | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 086d3e7f9..862dbb2c3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -38,19 +38,36 @@ internal static void Collide(float hitTime, ref BallState ball, ref PhysicsState } } + private static void TransformBallIntoColliderSpace(ref NativeColliders colliders, ref BallState ball, ref PhysicsState state, int colliderId) + { + if (!state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + return; + } + ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); + ball.Transform(math.inverse(matrix)); + } + + private static void TransformBallFromColliderSpace(ref NativeColliders colliders, ref BallState ball, ref PhysicsState state, int colliderId) + { + if (!state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + return; + } + ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); + ball.Transform(matrix); + } + private static void Collide(ref NativeColliders colliders, ref BallState ball, ref PhysicsState state) { var colliderId = ball.CollisionEvent.ColliderId; var collHeader = state.GetColliderHeader(ref colliders, colliderId); + + TransformBallIntoColliderSpace(ref colliders, ref ball, ref state, colliderId); + if (CollidesWithItem(ref colliders, ref collHeader, ref ball, ref state)) { + TransformBallFromColliderSpace(ref colliders, ref ball, ref state, colliderId); return; } - if (state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { - ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); - ball.Transform(math.inverse(matrix)); - } - switch (state.GetColliderType(ref colliders, colliderId)) { case ColliderType.Circle: @@ -138,10 +155,7 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; } - if (state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { - ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); - ball.Transform(matrix); - } + TransformBallFromColliderSpace(ref colliders, ref ball, ref state, colliderId); // remove trial hit object pointer ball.CollisionEvent.ClearCollider(); From 3debc922d261b9a4bd0a2226ece6011e7329079e Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 28 Nov 2023 22:54:27 +0100 Subject: [PATCH 043/208] debug: Add ball velocity visualization. --- .../Physics/Collision/CollisionEventData.cs | 2 +- .../VPT/Ball/BallComponent.cs | 41 +++++++++++++++++-- .../VisualPinball.Unity/VPT/Ball/BallState.cs | 2 +- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs index 13ad6e451..41b7ef67e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs @@ -18,7 +18,7 @@ namespace VisualPinball.Unity { - internal struct CollisionEventData + public struct CollisionEventData { public float HitTime; public float3 HitNormal; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs index 6a91da5ca..935f87798 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs @@ -29,12 +29,10 @@ public class BallComponent : MonoBehaviour public float3 Velocity; public bool IsFrozen; - internal BallState CreateState() { var pos = transform.localPosition.TranslateToVpx(); - return new BallState - { + return new BallState { Id = Id, IsFrozen = IsFrozen, Position = new float3(pos.x, pos.y, math.round(pos.z*100000) / 100000), @@ -47,5 +45,42 @@ internal BallState CreateState() AngularMomentum = float3.zero }; } + +#if UNITY_EDITOR + private PhysicsEngine _physicsEngine; + private float4x4 _playfieldToWorld; + + private void Awake() + { + _physicsEngine = GetComponentInParent(); + _playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; + UnityEditor.SceneView.duringSceneGui += DrawPhysicsDebug; + } + + private void OnDestroy() + { + UnityEditor.SceneView.duringSceneGui -= DrawPhysicsDebug; + } + + private void DrawPhysicsDebug(UnityEditor.SceneView sceneView) + { + ref var ballState = ref _physicsEngine.BallState(Id); + DrawArrow( + _playfieldToWorld.MultiplyPoint(ballState.Position.TranslateToWorld()), + _playfieldToWorld.MultiplyVector((ballState.Velocity * 10).TranslateToWorld()), + Color.white, + 0.01f + ); + } + + private static void DrawArrow(Vector3 pos, Vector3 direction, Color color, float arrowHeadLength = 0.025f, float arrowHeadAngle = 20.0f) + { + Debug.DrawRay(pos, direction); + var right = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180+arrowHeadAngle,0) * new Vector3(0,0,1); + var left = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180-arrowHeadAngle,0) * new Vector3(0,0,1); + Debug.DrawRay(pos + direction, right * arrowHeadLength, color); + Debug.DrawRay(pos + direction, left * arrowHeadLength, color); + } +#endif } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs index 0f32281b4..506e38a37 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs @@ -18,7 +18,7 @@ namespace VisualPinball.Unity { - internal struct BallState + public struct BallState { public int Id; public float3 Position; From d1dbbbb64fc68551e5f6374a5fb88383a6843716 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 29 Nov 2023 22:55:26 +0100 Subject: [PATCH 044/208] debug: Add ToString() to colliders. --- .../VisualPinball.Unity/Physics/Collider/CircleCollider.cs | 2 ++ .../Physics/Collider/LineSlingshotCollider.cs | 2 ++ .../VisualPinball.Unity/Physics/Collider/LineZCollider.cs | 2 ++ .../VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs | 2 ++ .../VisualPinball.Unity/VPT/Gate/GateCollider.cs | 3 +++ .../VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs | 2 ++ .../VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs | 2 ++ 7 files changed, 15 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index a4a649aea..43389ebdc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -236,5 +236,7 @@ public CircleCollider Transform(float4x4 matrix) Transform(this, matrix); return this; } + + public override string ToString() => $"CircleCollider[{Header.ItemId}] ({Center.x}/{Center.y}) {_zLow} -> {_zHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index 57886a1a5..8586adb07 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -143,5 +143,7 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite } #endregion + + public override string ToString() => $"LineSlingshotCollider[{Header.ItemId}] ({V1.x}/{V1.y}@{ZLow}) -> ({V2.x}/{V2.y}@{ZHigh}) at ({Normal.x}/{Normal.y}), len: {_length}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 61a814b51..2dcdc67ab 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -172,5 +172,7 @@ public LineZCollider Transform(float4x4 matrix) return this; } + + public override string ToString() => $"LineZCollider[{Header.ItemId}] ({XY.x}/{XY.y}) {_zLow} -> {_zHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 1482f95ce..3ca64c645 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -914,5 +914,7 @@ public void Collide(ref BallState ball, ref CollisionEventData collEvent, ref Fl } #endregion + + public override string ToString() => $"FlipperCollider[{Header.ItemId}] ({_hitCircleBase.Center.x}/{_hitCircleBase.Center.y}) {_zLow} -> {_zHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index ddd4cfc4a..46415cc7d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -123,5 +123,8 @@ public static void Collide(ref BallState ball, ref CollisionEventData collEvent, } #endregion + + public override string ToString() => $"GateCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; + } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index 0300ede24..96c940fd2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -314,5 +314,7 @@ public static void Collide(ref BallState ball, ref CollisionEventData collEvent, } #endregion + + public override string ToString() => $"LineSlingshotCollider[{Header.ItemId}] {LineSegBase.ToString()} | {JointBase0.ToString()} | {JointBase1.ToString()}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index d905ebeb4..b57b99e59 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -136,5 +136,7 @@ public SpinnerCollider Transform(float4x4 matrix) Transform(this, matrix); return this; } + + public override string ToString() => $"SpinnerCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; } } From 32b67ba2f187c37a37129ad4dd94d2cef23d6b2b Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 30 Nov 2023 23:17:29 +0100 Subject: [PATCH 045/208] fix: Matrix rotation when projecting Line3D collider. --- .../VisualPinball.Unity/Extensions/MathExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index 82c7a6112..d2c72b55d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -43,7 +43,7 @@ public static void NormalizeSafe(this ref float3 v) } } - public static void RotationAroundAxis(this float3x3 m, float3 axis, float rSin, float rCos) + public static void RotationAroundAxis(ref this float3x3 m, float3 axis, float rSin, float rCos) { m.c0.x = axis.x * axis.x + rCos * (1.0f - axis.x * axis.x); m.c0.y = axis.x * axis.y * (1.0f - rCos) - axis.z * rSin; From 035acb00f6511ca72e981c31ea7d8c244f57e5f6 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 1 Dec 2023 23:19:12 +0100 Subject: [PATCH 046/208] fix: NPE when a wire is not connected to anything. --- VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs index e424ac41e..b0675a696 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs @@ -47,7 +47,7 @@ public class WirePlayer private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); internal Dictionary WireStatuses { get; } = new Dictionary(); - internal IApiWireDeviceDest WireDevice(IWireableComponent c) => _wireDevices.ContainsKey(c) ? _wireDevices[c] : null; + internal IApiWireDeviceDest WireDevice(IWireableComponent c) => c != null && _wireDevices.ContainsKey(c) ? _wireDevices[c] : null; internal void RegisterWireDevice(IWireableComponent component, IApiWireDeviceDest wireDeviceApi) => _wireDevices[component] = wireDeviceApi; #region Lifecycle From 137a7b7e27586a976a2756cafd392281d341f8d0 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 2 Dec 2023 23:20:32 +0100 Subject: [PATCH 047/208] fix: Ball fall-through on triangulated poly colliders. --- .../VisualPinball.Unity/Physics/Collider/ColliderUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index f985ae513..cfc521c38 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -46,7 +46,7 @@ public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, r } var mesh = new Mesh(triangulatedVerts, outputIndices); - GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, true); + GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false); } public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false) From a67ffcbc5e513846a966a5559e9979cc0bb9589f Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 3 Dec 2023 23:22:07 +0100 Subject: [PATCH 048/208] chore: Cleanup and doc. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 16 ++++++++++++++++ .../Physics/Event/EventData.cs | 4 ++-- .../VisualPinball.Unity/VPT/Gate/GateCollider.cs | 5 ----- .../VPT/Trigger/TriggerCollider.cs | 1 - 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index dd9ea33bf..adee33129 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -70,8 +70,24 @@ public class PhysicsEngine : MonoBehaviour #region Transforms [NonSerialized] private readonly Dictionary _transforms = new(); + + /// + /// Last transforms of kinematic items, so we can detect changes. + /// [NonSerialized] private LazyInit> _kinematicTransforms = new(() => new(0, Allocator.Persistent)); + + /// + /// The transforms of the kinematic items that have changes since the last frame. + /// [NonSerialized] private LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); + + /// + /// The current matrix to the ball will be transformed to, if it collides with a non-transformable collider. + /// This changes as the non-transformable collider collider transforms (it's called non-transformable as in + /// not transformable by the physics engine, but it can be transformed by the game). + /// + /// todo save inverse matrix, too + /// [NonSerialized] private LazyInit> _nonTransformableColliderMatrices = new(() => new(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs index 47e2c5b71..12dcdaf58 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs @@ -50,7 +50,7 @@ public EventData(EventId eventId, int itemId, int ballId, float floatParam, bool public EventData(EventId eventId, int itemId, bool groupEvent = false) : this() { - this.EventId = eventId; + EventId = eventId; ItemId = itemId; BallId = 0; GroupEvent = groupEvent; @@ -58,7 +58,7 @@ public EventData(EventId eventId, int itemId, bool groupEvent = false) : this() public EventData(EventId eventId, int itemId, float floatParam, bool groupEvent = false) : this() { - this.EventId = eventId; + EventId = eventId; ItemId = itemId; BallId = 0; FloatParam = floatParam; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 46415cc7d..7c89ead18 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -65,11 +65,6 @@ public GateCollider Transform(float4x4 matrix) public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { - // todo - // if (!this.isEnabled) { - // return -1.0; - // } - var hitTime = LineCollider.HitTestBasic(ref collEvent, ref insideOfs, in LineSeg0, in ball, dTime, false, true, false); // any face, lateral, non-rigid if (hitTime >= 0) { // signal the Collide() function that the hit is on the front or back side diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs index 470e14c63..8bacd7e2e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; From 2c34e776700ef88f4cfabe3f41a920eaacbea321 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 4 Dec 2023 23:22:59 +0100 Subject: [PATCH 049/208] debug: Add hit normal to ball debug gizmo --- .../VisualPinball.Unity/VPT/Ball/BallComponent.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs index 935f87798..3330d4705 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs @@ -65,17 +65,27 @@ private void OnDestroy() private void DrawPhysicsDebug(UnityEditor.SceneView sceneView) { ref var ballState = ref _physicsEngine.BallState(Id); + + // velocity DrawArrow( _playfieldToWorld.MultiplyPoint(ballState.Position.TranslateToWorld()), _playfieldToWorld.MultiplyVector((ballState.Velocity * 10).TranslateToWorld()), Color.white, 0.01f ); + + // hit hormal + DrawArrow( + _playfieldToWorld.MultiplyPoint(ballState.Position.TranslateToWorld()), + _playfieldToWorld.MultiplyVector((ballState.CollisionEvent.HitNormal * 100).TranslateToWorld()), + ballState.CollisionEvent.HitFlag ? Color.red : Color.yellow, + 0.01f + ); } private static void DrawArrow(Vector3 pos, Vector3 direction, Color color, float arrowHeadLength = 0.025f, float arrowHeadAngle = 20.0f) { - Debug.DrawRay(pos, direction); + Debug.DrawRay(pos, direction, color); var right = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180+arrowHeadAngle,0) * new Vector3(0,0,1); var left = Quaternion.LookRotation(direction) * Quaternion.Euler(0,180-arrowHeadAngle,0) * new Vector3(0,0,1); Debug.DrawRay(pos + direction, right * arrowHeadLength, color); From 5dbe92f1bf8cc61d5bec2a9fcf7d09ab8d630877 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 5 Dec 2023 23:51:51 +0100 Subject: [PATCH 050/208] refactor: Ditch ColliderType.TriggerLine in favor of ItemType.Trigger. --- .../VisualPinball.Unity/Game/PhysicsState.cs | 9 +++++---- .../VisualPinball.Unity/Game/PhysicsStaticCollision.cs | 1 - .../Physics/Collider/ColliderReference.cs | 6 +++--- .../VisualPinball.Unity/Physics/Collider/LineCollider.cs | 4 ++-- .../Physics/Collision/ColliderType.cs | 1 - .../VPT/Trigger/TriggerColliderGenerator.cs | 2 +- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index be0cfb6f1..7060fd1bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -174,6 +174,11 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt ball.CollisionEvent.HitTime); case ColliderType.Line: + ref var lineCollider = ref colliders.Line(colliderId); + if (lineCollider.ItemType == ItemType.Trigger) { + return colliders.Line(colliderId).HitTestBasic(ref newCollEvent, ref InsideOfs, in ball, + ball.CollisionEvent.HitTime, false, false, false); + } return colliders.Line(colliderId).HitTest(ref newCollEvent, ref InsideOfs, in ball, ball.CollisionEvent.HitTime); @@ -205,10 +210,6 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt return colliders.Circle(colliderId).HitTestBasicRadius(ref newCollEvent, ref InsideOfs, in ball, ball.CollisionEvent.HitTime, false, false, false); - case ColliderType.TriggerLine: - return colliders.Line(colliderId).HitTestBasic(ref newCollEvent, ref InsideOfs, in ball, - ball.CollisionEvent.HitTime, false, false, false); - case ColliderType.Flipper: ref var flipperState = ref GetFlipperState(colliderId); ref var flipperCollider = ref colliders.Flipper(colliderId); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 862dbb2c3..3ee1c1afa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -144,7 +144,6 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.TriggerCircle: - case ColliderType.TriggerLine: TriggerCollide(ref ball, ref state, in collHeader); break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index a37b69fa4..3417b4068 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -293,10 +293,10 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl } else { Add(new LineZCollider(xy, zLow, zHigh, info).Transform(matrix)); } - } - internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix, ColliderType type = ColliderType.Line) + internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix + ) { if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { var p1 = new float3(v1.xy, zLow); @@ -308,7 +308,7 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); } else { - Add(new LineCollider(v1, v2, zLow, zHigh, info, type).Transform(matrix)); + Add(new LineCollider(v1, v2, zLow, zHigh, info).Transform(matrix)); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index a3527f81a..ced7bf051 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -56,9 +56,9 @@ public int Id ZHigh )); - public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, ColliderType type = ColliderType.Line) : this() + public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info) : this() { - Header.Init(info, type); + Header.Init(info, ColliderType.Line); V1 = v1; V2 = v2; ZLow = zLow; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderType.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderType.cs index 540d14dd4..6e02c768a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderType.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderType.cs @@ -34,6 +34,5 @@ public enum ColliderType Spinner, Triangle, TriggerCircle, - TriggerLine } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index c088d9059..ce0d8066a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -81,7 +81,7 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) private void AddLineSeg(float2 pv1, float2 pv2, float height, ref ColliderReference colliders) { colliders.AddLine(pv1, pv2, height, height + math.max(_colliderComponent.HitHeight - 8.0f, 0f), - _api.GetColliderInfo(), _matrix, ColliderType.TriggerLine); + _api.GetColliderInfo(), _matrix); } } } From cd4c019b425c404128d23f204ec73e95fa2cb1e0 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 6 Dec 2023 00:29:34 +0100 Subject: [PATCH 051/208] surface: Make non-transformable. --- .../Physics/Collider/Collider.cs | 1 - .../Physics/Collider/ColliderReference.cs | 16 +++++- .../Physics/Collider/ColliderUtils.cs | 37 ++++++++++--- .../Physics/Collider/Line3DCollider.cs | 10 ++++ .../Physics/Collider/LineCollider.cs | 55 +++++++++++++++---- .../Physics/Collider/LineZCollider.cs | 40 +++++++++++--- .../Physics/Collider/PointCollider.cs | 18 +++--- .../Physics/Collider/TriangleCollider.cs | 37 ++++++++++--- .../Physics/Collision/Aabb.cs | 11 +++- .../VPT/ICollidableComponent.cs | 2 +- .../VPT/Surface/SurfaceApi.cs | 2 +- .../VPT/Surface/SurfaceColliderComponent.cs | 6 +- .../VPT/Surface/SurfaceColliderGenerator.cs | 16 +++--- .../VPT/Trigger/TriggerColliderGenerator.cs | 2 +- 14 files changed, 192 insertions(+), 61 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs index 7cc531caa..541ef975f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs @@ -54,7 +54,6 @@ public unsafe ColliderBounds Bounds() { return ((FlipperCollider*) collider)->Bounds; case ColliderType.Gate: return ((GateCollider*) collider)->Bounds; - case ColliderType.TriggerLine: case ColliderType.Line: return ((LineCollider*) collider)->Bounds; case ColliderType.Line3D: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 3417b4068..7c53addbe 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -196,6 +196,7 @@ internal int Add(GateCollider collider) return collider.Id; } + internal int AddNonTransformable(Line3DCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(Line3DCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(Line3DCollider collider) @@ -243,6 +244,7 @@ internal int Add(PlungerCollider collider) return collider.Id; } + internal int AddNonTransformable(PointCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(PointCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(PointCollider collider) { @@ -263,6 +265,7 @@ internal int Add(SpinnerCollider collider) return collider.Id; } + internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(TriangleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(TriangleCollider collider) { @@ -295,8 +298,17 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl } } - internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix - ) + internal void AddNonTransformableLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) + { + Add(new LineZCollider(xy, zLow, zHigh, info).TransformAabb(matrix)); + } + + internal void AddNonTransformableLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) + { + Add(new LineCollider(v1, v2, zLow, zHigh, info).TransformAabb(matrix)); + } + + internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) { if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { var p1 = new float3(v1.xy, zLow); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index cfc521c38..68fe55a62 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -28,7 +28,7 @@ public static class ColliderUtils private static readonly ProfilerMarker PerfMarker1 = new("ColliderUtils.GenerateCollidersFromMesh.ICollider"); private static readonly ProfilerMarker PerfMarker2 = new("ColliderUtils.GenerateCollidersFromMesh.NativeArray"); - public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix) + public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool isNonTransformableParent) { var inputVerts = new float2[rgv.Length]; @@ -46,10 +46,10 @@ public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, r } var mesh = new Mesh(triangulatedVerts, outputIndices); - GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false); + GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false, isNonTransformableParent); } - public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false) + public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false, bool isNonTransformableParent = false) { PerfMarker1.Begin(); var addedEdges = EdgeSet.Get(Allocator.TempJob); @@ -66,18 +66,33 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref C var rgv1 = mesh.Vertices[i1].GetVertex().ToUnityFloat3(); var rgv2 = mesh.Vertices[i2].GetVertex().ToUnityFloat3(); - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); + if (isNonTransformableParent) { + colliders.AddNonTransformable(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); + } else { + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); + } if (!onlyTriangles) { - if (addedEdges.ShouldAddHitEdge(i0, i1)) { - colliders.Add(new Line3DCollider(rgv0, rgv2, info), matrix); + if (isNonTransformableParent) { + colliders.AddNonTransformable(new Line3DCollider(rgv0, rgv2, info), matrix); + } else { + colliders.Add(new Line3DCollider(rgv0, rgv2, info), matrix); + } } if (addedEdges.ShouldAddHitEdge(i1, i2)) { - colliders.Add(new Line3DCollider(rgv2, rgv1, info), matrix); + if (isNonTransformableParent) { + colliders.AddNonTransformable(new Line3DCollider(rgv2, rgv1, info), matrix); + } else { + colliders.Add(new Line3DCollider(rgv2, rgv1, info), matrix); + } } if (addedEdges.ShouldAddHitEdge(i2, i0)) { - colliders.Add(new Line3DCollider(rgv1, rgv0, info), matrix); + if (isNonTransformableParent) { + colliders.AddNonTransformable(new Line3DCollider(rgv1, rgv0, info), matrix); + } else { + colliders.Add(new Line3DCollider(rgv1, rgv0, info), matrix); + } } } } @@ -86,7 +101,11 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref C // add collision vertices if (!onlyTriangles) { foreach (var vertex in mesh.Vertices) { - colliders.Add(new PointCollider(vertex.ToUnityFloat3(), info), matrix); + if (isNonTransformableParent) { + colliders.AddNonTransformable(new PointCollider(vertex.ToUnityFloat3(), info), matrix); + } else { + colliders.Add(new PointCollider(vertex.ToUnityFloat3(), info), matrix); + } } } PerfMarker1.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index 568f3c79e..9a55c7778 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -154,5 +154,15 @@ public Line3DCollider Transform(float4x4 matrix) Transform(this, matrix); return this; } + + public Line3DCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(_v1); + var p2 = matrix.MultiplyPoint(_v2); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); + + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index ced7bf051..5e96601ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -42,19 +42,23 @@ public int Id internal ItemType ItemType => Header.ItemType; private int ItemId => Header.ItemId; - public float V1y { set => V1.y = value; } - public float V2y { set => V2.y = value; } - + public float V1y { + set { + V1.y = value; + CalculateBounds(); + } + } + + public float V2y { + set { + V2.y = value; + CalculateBounds(); + } + } + public override string ToString() => $"LineCollider[{Header.ItemId}] ({V1.x}/{V1.y}@{ZLow}) -> ({V2.x}/{V2.y}@{ZHigh}) at ({Normal.x}/{Normal.y}), len: {_length}"; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( - math.min(V1.x, V2.x), - math.max(V1.x, V2.x), - math.min(V1.y, V2.y), - math.max(V1.y, V2.y), - ZLow, - ZHigh - )); + public ColliderBounds Bounds { get; private set; } public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info) : this() { @@ -64,6 +68,7 @@ public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo ZLow = zLow; ZHigh = zHigh; CalcNormal(); + CalculateBounds(); } public void CalcNormal() @@ -240,8 +245,36 @@ public LineCollider Transform(float4x4 matrix) ZHigh += t.z; CalcNormal(); + CalculateBounds(); + + return this; + } + + public LineCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(new float3(V1, ZLow)); + var p2 = matrix.MultiplyPoint(new float3(V1, ZHigh)); + var p3 = matrix.MultiplyPoint(new float3(V2, ZLow)); + var p4 = matrix.MultiplyPoint(new float3(V2, ZHigh)); + + var min = math.min(p1, math.min(p2, math.min(p3, p4))); + var max = math.max(p1, math.max(p2, math.max(p3, p4))); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); return this; } + + private void CalculateBounds() + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + math.min(V1.x, V2.x), + math.max(V1.x, V2.x), + math.min(V1.y, V2.y), + math.max(V1.y, V2.y), + ZLow, + ZHigh + )); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 2dcdc67ab..34f86248f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -34,16 +34,14 @@ public int Id private float _zLow; private float _zHigh; - public float XyY { set => XY.y = value; } + public float XyY { + set { + XY.y = value; + CalculateBounds(); + } + } - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb ( - XY.x, - XY.x, - XY.y, - XY.y, - _zLow, - _zHigh - )); + public ColliderBounds Bounds { get; private set; } public LineZCollider(float2 xy, float zLow, float zHigh, ColliderInfo info) : this() { @@ -51,6 +49,7 @@ public LineZCollider(float2 xy, float zLow, float zHigh, ColliderInfo info) : th XY = xy; _zLow = zLow; _zHigh = zHigh; + CalculateBounds(); } #region Narrowphase @@ -169,10 +168,33 @@ public LineZCollider Transform(float4x4 matrix) XY += t.xy; _zLow += t.z; _zHigh += t.z; + CalculateBounds(); return this; } + public LineZCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(new float3(XY, _zLow)); + var p2 = matrix.MultiplyPoint(new float3(XY, _zHigh)); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); + + return this; + } + + private void CalculateBounds() + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb ( + XY.x, + XY.x, + XY.y, + XY.y, + _zLow, + _zHigh + )); + } + public override string ToString() => $"LineZCollider[{Header.ItemId}] ({XY.x}/{XY.y}) {_zLow} -> {_zHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index a7e154c36..bf465f298 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -32,19 +32,13 @@ public int Id public float3 P; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( - P.x, - P.x, - P.y, - P.y, - P.z, - P.z - )); + public ColliderBounds Bounds { get; private set; } public PointCollider(float3 p, ColliderInfo info) : this() { Header.Init(info, ColliderType.Point); P = p; + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(P, P)); } #region Narrowphase @@ -143,6 +137,7 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrit public void Transform(PointCollider point, float4x4 matrix) { P = matrix.MultiplyPoint(point.P); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(P, P)); } public PointCollider Transform(float4x4 matrix) @@ -150,5 +145,12 @@ public PointCollider Transform(float4x4 matrix) Transform(this, matrix); return this; } + + public PointCollider TransformAabb(float4x4 matrix) + { + var p = matrix.MultiplyPoint(P); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(p, p)); + return this; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index c21bc4a56..b637a75b5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -40,14 +40,7 @@ public int Id public override string ToString() => $"TriangleCollider[{Header.ItemId}] ({Rgv0.x}/{Rgv0.y}/{Rgv0.z}), ({Rgv1.x}/{Rgv1.y}/{Rgv1.z}), ({Rgv2.x}/{Rgv2.y}/{Rgv2.z}) at ({_normal.x}/{_normal.y/_normal.z})"; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( - math.min(Rgv0.x, math.min(Rgv1.x, Rgv2.x)), - math.max(Rgv0.x, math.max(Rgv1.x, Rgv2.x)), - math.min(Rgv0.y, math.min(Rgv1.y, Rgv2.y)), - math.max(Rgv0.y, math.max(Rgv1.y, Rgv2.y)), - math.min(Rgv0.z, math.min(Rgv1.z, Rgv2.z)), - math.max(Rgv0.z, math.max(Rgv1.z, Rgv2.z)) - )); + public ColliderBounds Bounds { get; private set; } public TriangleCollider(float3 rgv0, float3 rgv1, float3 rgv2, ColliderInfo info) : this() { @@ -55,6 +48,7 @@ public TriangleCollider(float3 rgv0, float3 rgv1, float3 rgv2, ColliderInfo info Rgv0 = rgv0; Rgv1 = rgv1; Rgv2 = rgv2; + CalculateBounds(); var e0 = rgv2 - rgv0; var e1 = rgv1 - rgv0; @@ -189,6 +183,7 @@ public void Transform(TriangleCollider triangle, float4x4 matrix) Rgv1 = math.mul(matrix, new float4(triangle.Rgv1, 1f)).xyz; Rgv2 = math.mul(matrix, new float4(triangle.Rgv2, 1f)).xyz; _normal = math.normalizesafe(math.cross(Rgv2 - Rgv0, Rgv1 - Rgv0)); + CalculateBounds(); } public TriangleCollider Transform(float4x4 matrix) @@ -196,5 +191,31 @@ public TriangleCollider Transform(float4x4 matrix) Transform(this, matrix); return this; } + + public TriangleCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(Rgv0); + var p2 = matrix.MultiplyPoint(Rgv1); + var p3 = matrix.MultiplyPoint(Rgv2); + + var min = math.min(p1, math.min(p2, p3)); + var max = math.max(p1, math.max(p2, p3)); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + + return this; + } + + private void CalculateBounds() + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + math.min(Rgv0.x, math.min(Rgv1.x, Rgv2.x)), + math.max(Rgv0.x, math.max(Rgv1.x, Rgv2.x)), + math.min(Rgv0.y, math.min(Rgv1.y, Rgv2.y)), + math.max(Rgv0.y, math.max(Rgv1.y, Rgv2.y)), + math.min(Rgv0.z, math.min(Rgv1.z, Rgv2.z)), + math.max(Rgv0.z, math.max(Rgv1.z, Rgv2.z)) + )); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs index a107b0642..32b161027 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs @@ -50,11 +50,20 @@ public Aabb(float left, float right, float top, float bottom, float zLow, float Right = right; Top = top; Bottom = bottom; - ZLow = 0; ZLow = zLow; ZHigh = zHigh; } + public Aabb(float3 min, float3 max) + { + Left = min.x; + Right = max.x; + Top = min.y; + Bottom = max.y; + ZLow = min.z; + ZHigh = max.z; + } + public void Clear() { Left = float.MaxValue; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 6c8b7fec4..57d3b9fd9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -35,7 +35,7 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// /// The unique identifier of the main item. /// - internal int ItemId { get; } + public int ItemId { get; } /// /// Returns whether this specific item is set to collidable, i.e. whether can it ever be diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index 38ba7c172..d07ad036f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -54,7 +54,7 @@ protected override void CreateColliders(ref ColliderReference colliders, if (MainComponent.DragPoints.Length == 0) { return; } - var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); + var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); if (ColliderComponent.IsKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index c56f5de31..2ce0e48b7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Surface Collider")] - public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent + public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent { #region Data @@ -77,6 +77,10 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); + + float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index 31c5dc80b..1dd09ea90 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -61,10 +61,10 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col GenerateLinePolys(pv2, pv3, playfieldHeight, ref colliders); } - ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders, _matrix); + ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders, _matrix, true); if (rgv3Db != null) { - ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders, _matrix); + ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders, _matrix, true); } } @@ -77,7 +77,7 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield var top = _component.HeightTop + playfieldHeight; if (!pv1.IsSlingshot) { - colliders.AddLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.AddNonTransformableLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); } else { // todo @@ -86,21 +86,21 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield if (_component.HeightBottom != 0) { // add lower edge as a line - colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo()), _matrix); + colliders.AddNonTransformable(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo()), _matrix); } // add upper edge as a line - colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); + colliders.AddNonTransformable(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); // create vertical joint between the two line segments - colliders.AddLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.AddNonTransformableLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); // add upper and lower end points of line if (_component.HeightBottom != 0) { - colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo()), _matrix); + colliders.AddNonTransformable(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo()), _matrix); } - colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo()), _matrix); + colliders.AddNonTransformable(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo()), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index ce0d8066a..bfd05d939 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -70,7 +70,7 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) rgv[i] = vVertex[i]; rgv3D[i] = new float3(rgv[i].X, rgv[i].Y, height + (float)(PhysicsConstants.PhysSkin * 2.0)); } - ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, _matrix); + ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, _matrix, false); for (var i = 0; i < count; i++) { var pv2 = rgv[i < count - 1 ? i + 1 : 0]; From e2c01d36c79b50b7442e72f7fe6fc82da40448e0 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 7 Dec 2023 23:12:41 +0100 Subject: [PATCH 052/208] primitive: Replace transformation data with native transform. --- .../VPT/Primitive/PrimitiveInspector.cs | 54 +----- .../Tables/CreatureFromTheBlackLagoon.cs | 6 +- .../Patcher/Tables/Goldorak.cs | 7 +- .../Patcher/Tables/JurassicPark.cs | 2 +- .../Patcher/Tables/Terminator2.cs | 8 +- .../Patcher/Tables/TomAndJerry.cs | 2 +- .../VPT/Primitive/PrimitiveComponent.cs | 164 ++++-------------- 7 files changed, 52 insertions(+), 191 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs index 7ebc8219a..7ecbff9e6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs @@ -26,42 +26,15 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(PrimitiveComponent)), CanEditMultipleObjects] public class PrimitiveInspector : MainInspector { - private SerializedProperty _positionProperty; - private SerializedProperty _rotationProperty; - private SerializedProperty _sizeProperty; - private SerializedProperty _translationProperty; - private SerializedProperty _objectRotationProperty; - - protected override void OnEnable() - { - base.OnEnable(); - - _positionProperty = serializedObject.FindProperty(nameof(PrimitiveComponent.Position)); - _rotationProperty = serializedObject.FindProperty(nameof(PrimitiveComponent.Rotation)); - _sizeProperty = serializedObject.FindProperty(nameof(PrimitiveComponent.Size)); - _translationProperty = serializedObject.FindProperty(nameof(PrimitiveComponent.Translation)); - _objectRotationProperty = serializedObject.FindProperty(nameof(PrimitiveComponent.ObjectRotation)); - } - public override void OnInspectorGUI() { - if (HasErrors()) { - return; + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the primitive on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Primitive Position"); + MainComponent.Position = newPos; } - - BeginEditing(); - - OnPreInspectorGUI(); - - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_rotationProperty, updateTransforms: true); - PropertyField(_sizeProperty, updateTransforms: true); - PropertyField(_translationProperty, updateTransforms: true); - PropertyField(_objectRotationProperty, updateTransforms: true); - - base.OnInspectorGUI(); - - EndEditing(); } [MenuItem("GameObject/Visual Pinball/Make Primitive", true, 20)] @@ -78,19 +51,12 @@ public static void MakePrimitive() if (!mf) { continue; } - var pc = go.AddComponent(); - pc.Position = go.transform.localPosition; - pc.Rotation = go.transform.localEulerAngles; - pc.Size = go.transform.localScale; var mc = go.AddComponent(); mc.UseLegacyMesh = false; var cc = go.AddComponent(); cc.enabled = true; - - // var cte = go.AddComponent(); - // cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; } } @@ -104,10 +70,7 @@ public static bool MakeColliderValidation() public static void MakeCollider() { foreach (var go in Selection.gameObjects) { - var pc = go.AddComponent(); - pc.Position = go.transform.localPosition; - pc.Rotation = go.transform.localEulerAngles; - pc.Size = go.transform.localScale; + go.AddComponent(); var mc = go.AddComponent(); mc.UseLegacyMesh = false; @@ -115,9 +78,6 @@ public static void MakeCollider() var cc = go.AddComponent(); cc.enabled = true; - - // var cte = go.AddComponent(); - // cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/CreatureFromTheBlackLagoon.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/CreatureFromTheBlackLagoon.cs index 737050f28..5e99fb06a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/CreatureFromTheBlackLagoon.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/CreatureFromTheBlackLagoon.cs @@ -31,10 +31,8 @@ public void ReparentFlippers(PrimitiveComponent flipper, GameObject gameObject, { PatcherUtil.Reparent(gameObject, parent); - flipper.Position.x = 0; - flipper.Position.y = 0; - - flipper.ObjectRotation.z = 0; + flipper.Position = Vector2.zero; + // flipper.ObjectRotation.z = 0; } [NameMatch("batleftshadow")] diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Goldorak.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Goldorak.cs index 2686fdeb2..a062be444 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Goldorak.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Goldorak.cs @@ -31,11 +31,10 @@ public void ReparentFlippers(PrimitiveComponent flipper, GameObject gameObject, { PatcherUtil.Reparent(gameObject, parent); - flipper.Position.x = 0; - flipper.Position.y = 0; + flipper.Position = Vector2.zero; - // // rotation is set in the original data, reparenting caused the flippers to be rotated wrong => fixing the rotation - flipper.Rotation.y = 0; + // rotation is set in the original data, reparenting caused the flippers to be rotated wrong => fixing the rotation + // flipper.Rotation.y = 0; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/JurassicPark.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/JurassicPark.cs index 4d8e3c1f0..7d90b7197 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/JurassicPark.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/JurassicPark.cs @@ -66,7 +66,7 @@ public void ReparentFlippers(PrimitiveComponent primitive, GameObject gameObject { PatcherUtil.Reparent(gameObject, parent); primitive.Position = Vector3.zero; - primitive.ObjectRotation.z = 0; + // primitive.ObjectRotation.z = 0; } [NameMatch("PLeftFlipper")] diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs index 527636440..0be3774c2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/Terminator2.cs @@ -253,10 +253,8 @@ public void ReparentFlippers(PrimitiveComponent flipper, GameObject gameObject, { PatcherUtil.Reparent(gameObject, parent); - flipper.Position.x = 0; - flipper.Position.y = 0; - - flipper.ObjectRotation.z = 0; + flipper.Position = Vector2.zero; + // flipper.ObjectRotation.z = 0; } [NameMatch("LeftFlipper")] @@ -432,7 +430,7 @@ public void SetupCannon(GameObject primitiveGo, PrimitiveComponent cannonComp) new MechMark(MechMarkSwitchType.EnableBetween, "Gun Mark", "gun_mark_switch", 98, 105), }; - rotatorComp.Target = cannonComp; + //rotatorComp.Target = cannonComp; rotatorComp.RotateWith = new IRotatableComponent[] { playfieldGo.transform.Find("Kickers/sw31").GetComponent(), }; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/TomAndJerry.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/TomAndJerry.cs index 5e67a6975..391b6fe25 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/TomAndJerry.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Patcher/Tables/TomAndJerry.cs @@ -103,7 +103,7 @@ public void ReparentFlippers(PrimitiveComponent primitive, GameObject gameObject PatcherUtil.Reparent(gameObject, parent); primitive.Position = Vector3.zero; - primitive.Rotation.y = 0; + // primitive.Rotation.y = 0; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index c5ec1e61f..4bb5ae3c9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -29,31 +29,19 @@ using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Primitive; using VisualPinball.Engine.VPT.Table; -using MathF = VisualPinball.Engine.Math.MathF; using Mesh = VisualPinball.Engine.VPT.Mesh; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Primitive")] - public class PrimitiveComponent : MainRenderableComponent, IMeshGenerator, - IRotatableComponent + public class PrimitiveComponent : MainRenderableComponent, IMeshGenerator { #region Data - [Tooltip("Position of the primitive on the playfield.")] - public Vector3 Position = Vector3.zero; - - [Tooltip("Rotation of the primitive in the playfield coordinate system.")] - public Vector3 Rotation = Vector3.zero; - - [Tooltip("Scaling of the primitive.")] - public Vector3 Size = Vector3.one; - - [Tooltip("Translation of the primitive.")] - public Vector3 Translation = Vector3.zero; - - [Tooltip("Rotation of the primitive after being translated.")] - public Vector3 ObjectRotation = Vector3.zero; + public Vector3 Position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } #endregion @@ -62,7 +50,7 @@ public class PrimitiveComponent : MainRenderableComponent, IMeshG public override ItemType ItemType => ItemType.Primitive; public override string ItemName => "Primitive"; - public override PrimitiveData InstantiateData() => new PrimitiveData(); + public override PrimitiveData InstantiateData() => new(); public override bool HasProceduralMesh => false; @@ -73,43 +61,7 @@ public class PrimitiveComponent : MainRenderableComponent, IMeshG #region Transformation - public override void UpdateTransforms() - { - base.UpdateTransforms(); - transform.SetFromMatrix(GetTransformationMatrix().ToUnityMatrix().TransformVpxInWorld()); - } - - public float _originalRotateZ; - public float RotateZ { - set { - ObjectRotation.z = _originalRotateZ + value; - UpdateTransforms(); - } - } - public float2 RotatedPosition { - get => new(Position.x, Position.y); - set { - Position.x = value.x; - Position.y = value.y; - UpdateTransforms(); - } - } - - public float4x4 TransformationWithinPlayfield { - get { - var scaleMatrix = float4x4.Scale(Size); - var transMatrix = float4x4.Translate(new float3(Position.x, Position.y, Position.z + PlayfieldHeight)); - var rotTransMatrix = math.mul( - float4x4.EulerZYX(math.radians(Rotation)), - float4x4.Translate(Translation) - ); - rotTransMatrix = math.mul( - float4x4.EulerZYX(math.radians(ObjectRotation)), - rotTransMatrix - ); - return math.mul(transMatrix, math.mul(rotTransMatrix, scaleMatrix)); - } - } + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.TransformWorldInVpx(); #endregion @@ -120,11 +72,22 @@ public override IEnumerable SetData(PrimitiveData data) var updatedComponents = new List { this }; // transforms - Position = data.Position.ToUnityVector3(); - Size = data.Size.ToUnityFloat3(); - Rotation = new Vector3(data.RotAndTra[0], data.RotAndTra[1], data.RotAndTra[2]); - Translation = new Vector3(data.RotAndTra[3], data.RotAndTra[4], data.RotAndTra[5]); - ObjectRotation = new Vector3(data.RotAndTra[6], data.RotAndTra[7], data.RotAndTra[8]); + var position = data.Position.ToUnityVector3(); + var size = data.Size.ToUnityFloat3(); + var rotation = new Vector3(data.RotAndTra[0], data.RotAndTra[1], data.RotAndTra[2]); + var translation = new Vector3(data.RotAndTra[3], data.RotAndTra[4], data.RotAndTra[5]); + var objectRotation = new Vector3(data.RotAndTra[6], data.RotAndTra[7], data.RotAndTra[8]); + + var scaleMatrix = float4x4.Scale(size); + var transMatrix = float4x4.Translate(new float3(position.x, position.y, position.z + PlayfieldHeight)); + var rotTransMatrix = math.mul( + float4x4.EulerZYX(math.radians(objectRotation)), + math.mul( + float4x4.EulerZYX(math.radians(rotation)), + float4x4.Translate(translation) + )); + var transformationWithinPlayfieldMatrix = math.mul(transMatrix, math.mul(rotTransMatrix, scaleMatrix)); + transform.SetFromMatrix(((Matrix4x4)transformationWithinPlayfieldMatrix).TransformVpxInWorld()); // mesh var meshComponent = GetComponent(); @@ -178,13 +141,15 @@ public override IEnumerable SetReferencedData(PrimitiveData data, public override PrimitiveData CopyDataTo(PrimitiveData data, string[] materialNames, string[] textureNames, bool forExport) { // name and transforms + var t = transform; data.Name = name; data.Position = Position.ToVertex3D(); - data.Size = Size.ToVertex3D(); + data.Size = t.localScale.ToVertex3D(); + var vpxRotation = t.localEulerAngles.TranslateToVpx(); data.RotAndTra = new[] { - Rotation.x, Rotation.y, Rotation.z, - Translation.x, Translation.y, Translation.z, - ObjectRotation.x, ObjectRotation.y, ObjectRotation.z, + vpxRotation.x, vpxRotation.y, vpxRotation.z, + 0, 0, 0, + 0, 0, 0, }; // materials @@ -238,19 +203,12 @@ public override PrimitiveData CopyDataTo(PrimitiveData data, string[] materialNa public override void CopyFromObject(GameObject go) { var primitiveComponent = go.GetComponent(); - if (primitiveComponent != null) { - - Position = primitiveComponent.Position; - Rotation = primitiveComponent.Rotation; - Size = primitiveComponent.Size; - Translation = primitiveComponent.Translation; - ObjectRotation = primitiveComponent.ObjectRotation; + var targetTransform = transform; + var sourceTransform = primitiveComponent != null ? primitiveComponent.transform : go.transform; - } else { - Position = go.transform.localPosition.TranslateToVpx(); - Rotation = go.transform.localEulerAngles; - Size = go.transform.localScale; - } + targetTransform.localPosition = sourceTransform.localPosition; + targetTransform.localRotation = sourceTransform.localRotation; + targetTransform.localScale = sourceTransform.localScale; UpdateTransforms(); } @@ -263,8 +221,6 @@ public override void CopyFromObject(GameObject go) private void Awake() { - _originalRotateZ = ObjectRotation.z; - var player = GetComponentInParent(); var physicsEngine = GetComponentInParent(); PrimitiveApi = new PrimitiveApi(gameObject, player, physicsEngine); @@ -281,59 +237,9 @@ private void Awake() public Matrix3D GetTransformationMatrix() { - // scale matrix - var scaleMatrix = new Matrix3D(); - scaleMatrix.SetScaling(Size.x, Size.y, Size.z); - - // translation matrix - var tableHeight = PlayfieldHeight; - var transMatrix = new Matrix3D(); - transMatrix.SetTranslation(Position.x, Position.y, Position.z + tableHeight); - - // translation + rotation matrix - var rotTransMatrix = new Matrix3D(); - rotTransMatrix.SetTranslation(Translation.x, Translation.y, Translation.z); - - var tempMatrix = new Matrix3D(); - tempMatrix.RotateZMatrix(MathF.DegToRad(Rotation.z)); - rotTransMatrix.Multiply(tempMatrix); - tempMatrix.RotateYMatrix(MathF.DegToRad(Rotation.y)); - rotTransMatrix.Multiply(tempMatrix); - tempMatrix.RotateXMatrix(MathF.DegToRad(Rotation.x)); - rotTransMatrix.Multiply(tempMatrix); - - tempMatrix.RotateZMatrix(MathF.DegToRad(ObjectRotation.z)); - rotTransMatrix.Multiply(tempMatrix); - tempMatrix.RotateYMatrix(MathF.DegToRad(ObjectRotation.y)); - rotTransMatrix.Multiply(tempMatrix); - tempMatrix.RotateXMatrix(MathF.DegToRad(ObjectRotation.x)); - rotTransMatrix.Multiply(tempMatrix); - - var fullMatrix = scaleMatrix.Clone(); - fullMatrix.Multiply(rotTransMatrix); - fullMatrix.Multiply(transMatrix); // fullMatrix = Smatrix * RTmatrix * Tmatrix - scaleMatrix.SetScaling(1.0f, 1.0f, 1.0f); - fullMatrix.Multiply(scaleMatrix); - - return fullMatrix; + throw new Exception("deprecated"); } #endregion - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorPosition() => Position; - public override void SetEditorPosition(Vector3 pos) => Position = pos; - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorRotation() => Rotation; - public override void SetEditorRotation(Vector3 rot) => Rotation = rot; - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorScale() => Size; - public override void SetEditorScale(Vector3 scale) => Size = scale; - - #endregion } } From 08ce7b13641073dcd5ffb546d4bb0d088a7ddaf2 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 8 Dec 2023 21:34:36 +0100 Subject: [PATCH 053/208] primitive: Fix collision. --- VisualPinball.Engine/Math/Matrix3D.cs | 2 +- .../VisualPinball.Unity/Physics/Physics.cs | 22 ++++++++++++++++++- .../VPT/MainRenderableComponent.cs | 4 ++++ .../Primitive/PrimitiveColliderGenerator.cs | 2 +- .../VPT/Primitive/PrimitiveComponent.cs | 8 +++---- 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/VisualPinball.Engine/Math/Matrix3D.cs b/VisualPinball.Engine/Math/Matrix3D.cs index cc624d237..b037b3128 100644 --- a/VisualPinball.Engine/Math/Matrix3D.cs +++ b/VisualPinball.Engine/Math/Matrix3D.cs @@ -37,7 +37,7 @@ public class Matrix3D public Tuple Column3 => new Tuple(_31, _32, _33, _34); public Tuple Column4 => new Tuple(_41, _42, _43, _44); - public Matrix3D Set(float[] m) + public Matrix3D Set(params float[] m) { _matrix[0][0] = m[0]; _matrix[1][0] = m[1]; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 1cec53bfd..33197f6ac 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -55,6 +55,7 @@ public static class Physics #region Transformation public static Matrix3D TransformToVpx(this Matrix3D vpx) => WorldToVpx.ToVpMatrix().Multiply(vpx); + public static float4x4 TransformToVpx(this float4x4 vpx) => math.mul(vpx, WorldToVpx); public static Mesh TransformToWorld(this Mesh mesh) => mesh?.Transform(VpxToWorld.ToVpMatrix()); public static Mesh TransformToVpx(this Mesh mesh) => mesh?.Transform(WorldToVpx.ToVpMatrix()); @@ -66,6 +67,7 @@ public static class Physics /// VPX-space matrix that is supposed to be applied to a VPX-space mesh /// Matrix that with the same transformation to be applied to a mesh converted to world-space. public static Matrix4x4 TransformVpxInWorld(this Matrix4x4 m) => math.mul(math.mul(VpxToWorld, m), WorldToVpx); + public static Matrix4x4 TransformWorldInVpx(this Matrix4x4 m) => math.mul(math.mul(WorldToVpx, m), VpxToWorld); //public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.mul(matrix, new float4(p, 1f)).xyz; public static float3 MultiplyPoint(this float4x4 matrix, float3 p) => math.transform(matrix, p); @@ -81,7 +83,6 @@ public static class Physics /// World-to-local transformation matrix of the item. /// Local-to-World transformation matrix of the playfield. /// Transformation matrix of the item in VPX space. - [Obsolete("use (and test) LocalToWorldTranslateWithinPlayfield()")] public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) => math.mul( math.mul(WorldToVpx, @@ -139,6 +140,7 @@ public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localT public static Vector3 ScaleInvVector = new(ScaleInv, ScaleInv, ScaleInv); public static float ScaleToVpx(float worldSize) => worldSize * Scale; + public static float ScaleToWorld(float vpxSize) => vpxSize * ScaleInv; public static float3 ScaleToWorld(float vpxX, float vpxY, float vpxZ) => new(ScaleToWorld(vpxX), ScaleToWorld(vpxY), ScaleToWorld(vpxZ)); @@ -151,6 +153,24 @@ public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localT private static readonly Quaternion ToWorldRotation = ((Matrix4x4)VpxToWorld).rotation; private static readonly Quaternion ToVpxRotation = ((Matrix4x4)WorldToVpx).rotation; + /// + /// Returns the transformation matrix if VPX space, defined by the local transform, i.e. independently of the parent transformation. + /// + /// + /// + public static float4x4 LocalToVpxMatrix(this Transform t) => float4x4.TRS( + math.transform(WorldToVpx, t.localPosition), + t.localRotation.RotateToVpx(), + t.localScale + ); + + public static Quaternion RotateToVpx(this Quaternion q) => RotateToVpx((quaternion)q); + public static quaternion RotateToVpx(this quaternion q) + { + var rm = math.mul(WorldToVpx, float4x4.TRS(float3.zero, q, new float3(1))); + return quaternion.LookRotationSafe(rm.c1.xyz, rm.c2.xyz); + } + public static Quaternion RotateToWorld(this Quaternion q) => Quaternion.Euler(RotateToWorld(q.eulerAngles)); public static float3 RotateToWorld(float vpxX, float vpxY, float vpxZ) => ((Matrix4x4)math.mul(VpxToWorld, float4x4.Euler(math.radians(vpxX), math.radians(vpxY), math.radians(vpxZ)))).rotation.eulerAngles; private static float3 RotateToWorld(float3 vpxRotation) => ((Matrix4x4)math.mul(VpxToWorld, float4x4.Euler(math.radians(vpxRotation.x), math.radians(vpxRotation.y), math.radians(vpxRotation.z)))).rotation.eulerAngles; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index d43c31b3d..bd64571bd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -46,6 +46,10 @@ public abstract class MainRenderableComponent : MainComponent, [NonSerialized] public Player Player; + [NonSerialized] + private PlayfieldComponent _playfield; + protected PlayfieldComponent Playfield => _playfield ? _playfield : _playfield = GetComponentInParent(); + /// /// Returns all child mesh components linked to this data. /// diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs index 1d11db365..c0ac3c3fc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs @@ -91,7 +91,7 @@ internal void GenerateColliders(float collisionReductionFactor, ref ColliderRefe PerfMarker2.End(); PerfMarker3.Begin(); - var worldToVpx = _meshGenerator.GetTransformationMatrix().TransformToVpx().ToUnityMatrix(); + var worldToVpx = (Matrix4x4)_primitiveComponent.TransformationWithinPlayfield.TransformToVpx(); ColliderUtils.GenerateCollidersFromMesh(in unityVertices, in unityIndices, ref worldToVpx, _api.GetColliderInfo(), ref colliders); PerfMarker3.End(); PerfMarker1.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 4bb5ae3c9..bc0235e34 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -61,7 +61,7 @@ public Vector3 Position { #region Transformation - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.TransformWorldInVpx(); + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix); #endregion @@ -235,10 +235,8 @@ private void Awake() public Mesh GetMesh() => GetDefaultMesh(); - public Matrix3D GetTransformationMatrix() - { - throw new Exception("deprecated"); - } + public Matrix3D GetTransformationMatrix() => + transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix).ToVpMatrix(); #endregion } From 3e4879f0b1f877df8e9e55dd5532e4845b9c0b5e Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 9 Dec 2023 21:06:56 +0100 Subject: [PATCH 054/208] fix: Flipper start angle based on transformation. --- .../VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index ed969b6c6..bcb7af44d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -63,7 +63,7 @@ public Vector2 Position { public float PosY => Position.y; public float StartAngle { - get => transform.localEulerAngles.y; + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; set { var t = transform; var e = t.localEulerAngles; From fbbf58aa3eb1ee15d732bdc33f3502b8842bb950 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 11 Dec 2023 22:46:25 +0100 Subject: [PATCH 055/208] gate: Replace position and rotation data with transformation. --- .../VPT/Gate/GateInspector.cs | 22 +++++++++++---- .../Extensions/MathExtensions.cs | 2 +- .../VPT/Gate/GateComponent.cs | 28 +++++++++++-------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs index b6822b548..b72e87a5d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs @@ -36,8 +36,6 @@ public class GateInspector : MainInspector { "Wire W", GateType.GateWireW }, }; - private SerializedProperty _positionProperty; - private SerializedProperty _rotationProperty; private SerializedProperty _lengthProperty; private SerializedProperty _surfaceProperty; private SerializedProperty _meshProperty; @@ -49,8 +47,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(GateComponent.Position)); - _rotationProperty = serializedObject.FindProperty(nameof(GateComponent._rotation)); _lengthProperty = serializedObject.FindProperty(nameof(GateComponent._length)); _surfaceProperty = serializedObject.FindProperty(nameof(GateComponent._surface)); _meshProperty = serializedObject.FindProperty(nameof(GateComponent._meshName)); @@ -67,8 +63,22 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_rotationProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the gate on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Gate Position"); + MainComponent.Position = newPos; + } + + // start angle + EditorGUI.BeginChangeCheck(); + var newAngle = EditorGUILayout.Slider(new GUIContent("Rotation", "Angle of the gate on the playfield (z-axis rotation)"), MainComponent.Rotation, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Flipper Start Angle"); + MainComponent.Rotation = newAngle; + } + PropertyField(_lengthProperty, updateTransforms: true); PropertyField(_surfaceProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index d2c72b55d..43182ccb9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -92,7 +92,7 @@ public static Vertex3D ToVertex3D(this Vector3 vector) return new Vertex3D(vector.x, vector.y, vector.z); } - public static Vertex2D ToVertex2Dxy(this ref Vector3 vector) + public static Vertex2D ToVertex2Dxy(this Vector3 vector) { return new Vertex2D(vector.x, vector.y); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index dfcd2d6b2..c3faa2755 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -42,12 +42,19 @@ public class GateComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the gate on the playfield.")] - public Vector3 Position; + public Vector3 Position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } - [Range(-180f, 180f)] - [Tooltip("Angle of the gate on the playfield (z-axis rotation)")] - public float _rotation; + public float Rotation { + get => transform.localEulerAngles.y; + set { + var t = transform; + var e = t.localEulerAngles; + t.localEulerAngles = new Vector3(e.x, value, e.z); + } + } [Range(10f, 250f)] [Tooltip("How much the gate is scaled, in percent.")] @@ -70,7 +77,6 @@ public class GateComponent : MainRenderableComponent, public float PosY => Position.y; public float Height => Position.z; - public float Rotation => _rotation; public float Length => _length; public bool ShowBracket { get { @@ -185,7 +191,7 @@ public override IEnumerable SetData(GateData data) // transforms Position = data.Center.ToUnityVector3(data.Height); - _rotation = data.Rotation > 180f ? data.Rotation - 360f : data.Rotation; + Rotation = data.Rotation > 180f ? data.Rotation - 360f : data.Rotation; _length = data.Length; _type = data.GateType; @@ -240,8 +246,8 @@ public override IEnumerable SetReferencedData(GateData data, Tabl public override GateData CopyDataTo(GateData data, string[] materialNames, string[] textureNames, bool forExport) { // name and transforms - data.Name = name; data.Center = Position.ToVertex2Dxy(); + data.Name = name; data.Rotation = Rotation; data.Height = Position.z; data.Length = Length; @@ -286,14 +292,14 @@ public override void CopyFromObject(GameObject go) var gateComponent = go.GetComponent(); if (gateComponent != null) { Position = gateComponent.Position; - _rotation = gateComponent._rotation; + Rotation = gateComponent.Rotation; _length = gateComponent._length; Surface = gateComponent.Surface; } else { Position = go.transform.localPosition.TranslateToVpx(); - _rotation = go.transform.localEulerAngles.z; + Rotation = go.transform.localEulerAngles.z; } UpdateTransforms(); @@ -350,7 +356,7 @@ public override Vector3 GetEditorPosition() => Surface != null public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; public override Vector3 GetEditorRotation() => new Vector3(Rotation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => _rotation = ClampDegrees(rot.x); + public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; public override Vector3 GetEditorScale() => new Vector3(Length, 0f, 0f); From ad13f990c4e94ee3b6f9dadfdde7d4be9da68a13 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 12 Dec 2023 21:56:09 +0100 Subject: [PATCH 056/208] gate: Use meshes from FBX file and fix scaling. --- .../Art/Meshes/Gate/Gate (Bracket).mesh | 166 ------------------ .../Art/Meshes/Gate/Gate (Bracket).mesh.meta | 8 - .../Assets/Art/Meshes/Gate/Gate Meshes.fbx | Bin 0 -> 57788 bytes .../Art/Meshes/Gate/Gate Meshes.fbx.meta | 109 ++++++++++++ .../Assets/Art/Meshes/Gate/Wire.meta | 8 - .../Art/Meshes/Gate/Wire/Long Plate.mesh | 166 ------------------ .../Art/Meshes/Gate/Wire/Long Plate.mesh.meta | 8 - .../Assets/Art/Meshes/Gate/Wire/Plate.mesh | 166 ------------------ .../Art/Meshes/Gate/Wire/Plate.mesh.meta | 8 - .../Art/Meshes/Gate/Wire/Wire Rectangle.mesh | 166 ------------------ .../Meshes/Gate/Wire/Wire Rectangle.mesh.meta | 8 - .../Assets/Art/Meshes/Gate/Wire/Wire W.mesh | 166 ------------------ .../Art/Meshes/Gate/Wire/Wire W.mesh.meta | 8 - .../Resources/Prefabs/Gate (Builtin).prefab | 12 +- .../Gate - Long Plate (Builtin).prefab | 10 +- .../Prefabs/Gate - Plate (Builtin).prefab | 6 +- .../Gate - Wire Rectangle (Builtin).prefab | 10 +- .../Prefabs/Gate - Wire W (Builtin).prefab | 10 +- .../VPT/Gate/GateInspector.cs | 25 +-- .../VPT/ItemInspector.cs | 69 ++++++++ .../VPT/Gate/GateComponent.cs | 2 +- 21 files changed, 230 insertions(+), 901 deletions(-) delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh.meta create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh deleted file mode 100644 index c604bb8c0..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Gate (Bracket) - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 516 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 184 - localAABB: - m_Center: {x: 0.0009315014, y: 0.021391, z: 0.010090001} - m_Extent: {x: 0.5159995, y: 0.09, z: 0.078001} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 00000100020001000000030004000500060006000700040008000400070009000600050007000a00080005000b0009000c0008000a000b00130009000a000d000c000c000d000e000f000e000d000f0010000e000f00110010001100120010001400090013001400150009001600140013001300170016001800160017001700190018001a001800190019001b001a001c001a001b001b001d001c001e001c001d001d001f001e001f0020001e0021001e0020001f002200200023002000220022002400230025002300240024002600250027002500260026002800270029002700280028002a00290029002a002b002c002b002a002c002d002b002c002e002d002e002f002d0030003100320032003300300034003200310035003000330031003600340033003700350038003400360039003500370036003a0038003b0038003a003a003c003b003c003d003b003c003e003d003e003f003d003900370040004100400037004200390040004000430042004400420043004300450044004600440045004500470046004800460047004700490048004a004800490049004b004a004b004c004a004b004d004c004e004a004c004f004e004c004c0050004f0051004f00500050005200510053005100520052005400530055005300540054005600550057005500560056005800570058005900570058005a0059005a005b0059005c005d005e005d005c0060005e005f005c0062005d00600061005f005e0060006400620063005f0061006600620064006100650063006400680066006700630065006a006600680065006900670068006c006a0069006b0067006e006a006c006b0069006d006c0070006e006d006f006b0072006e0070006f006d00710070007400720073006f0071007600720074007100750073007400780076007500770073007a007600780077007500790078007c007a00770079007b007e007a007c007d007b0079007c0080007e007f007b007d00800082007e007d0081007f0082008000840083007f0081008200840086008100850083008800860084008500870083008a008600880088008b008a0087008500890089008c0087008c0089008d008e008c008d008d008f008e0090008e008f008f00910090009200900091009100930092009400920093009300950094009600940095009500970096009800960097009700990098009a009800990099009b009a009c009a009b009b009d009c009d009e009c009e009d009f009f00a0009e00a0009f00a100a200a000a100a100a300a200a400a500a600a500a400a800aa00a500a800a800ab00aa00a600a700a400a700a600a900ac00a700a900a900ad00ac00ad00ae00ac00ae00ad00af00af00b000ae00b100b000af00b200b000b100b100b300b200b400b500b600b400b600b700 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 184 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 5888 - _typelessdata: 16a7b23efe60e03cf758faba0000000000000000000080bf0000003f000000bf3882ac3e7120e43df758faba0000000000000000000080bf0000003f000000bf16a7b23e7120e43df758faba0000000000000000000080bf0000003f000000bf3882ac3efe60e03cf758faba0000000000000000000080bf0000003f000000bf5d35af3ee0828cbd5a4880bd810435bf00000000810435bf0000003f000000bf3882ac3ee0828cbd6e6e4cbdb5157bbf000000004bc847be0000003f000000bf3882ac3efe60e03c6e6e4cbdb5157bbf000000004bc847be0000003f000000bf5d35af3efe60e03c5a4880bd810435bf00000000810435bf0000003f000000bfa6b9b53ee0828cbd66148bbd4bc847be00000000b5157bbf0000003f000000bf3882ac3efe60e03cf758faba000080bf00000000000000800000003f000000bfa6b9b53efe60e03c66148bbd4bc847be00000000b5157bbf0000003f000000bf3882ac3ee0828cbdf758faba000080bf00000000000000800000003f000000bfb917f03ee0828cbdec148bbd0000000000000000000080bf0000003f000000bfb917f03efe60e03cec148bbd0000000000000000000080bf0000003f000000bf7461fc3e5cae7ebdec148bbd0000000000000000000080bf0000003f000000bf7461fc3e35b2ab3cec148bbd0000000000000000000080bf0000003f000000bf32b0023fedb736bdec148bbd0000000000000000000080bf0000003f000000bf32b0023f8a3b5e3bec148bbd0000000000000000000080bf0000003f000000bf9755043f35d4a8bcec148bbd0000000000000000000080bf0000003f000000bf3882ac3ee0828cbd598b8f3db5157bbf0000000014ae47be0000003f000000bf3882ac3e7120e43d598b8f3db5157bbf0000000014ae47be0000003f000000bf3882ac3e7120e43df758faba000080bf00000000000000800000003f000000bf0f9cab3e7120e43d603b983d0f0b35bf00000000f4fd34bf0000003f000000bf0f9cab3ee0828cbd603b983d0f0b35bf00000000f4fd34bf0000003f000000bfeb6fa93e7120e43d13d59b3d4bc847be00000000b5157bbf0000003f000000bfeb6fa93ee0828cbd13d59b3d4bc847be00000000b5157bbf0000003f000000bf9a7ba8bef720e43d99d59b3d4bc8473e00000000b5157bbf0000003f000000bf9a7ba8be5a828cbd99d59b3d4bc8473e00000000b5157bbf0000003f000000bf9ca7aabef720e43d6d3c983d8104353f00000000810435bf0000003f000000bf9ca7aabe5a828cbd6d3c983d8104353f00000000810435bf0000003f000000bf088eabbef720e43ddf8b8f3db5157b3f000000004bc847be0000003f000000bf088eabbe5a828cbddf8b8f3db5157b3f000000004bc847be0000003f000000bf088eabbe3065e03c6937faba0000803f00000000000000800000003f000000bf088eabbef720e43d6937faba0000803f00000000000000800000003f000000bf088eabbe5a828cbd6937faba0000803f00000000000000800000003f000000bf088eabbe3065e03c616d4cbdb5157b3f000000004bc847be0000003f000000bf088eabbe5a828cbd616d4cbdb5157b3f000000004bc847be0000003f000000bf0b41aebe3065e03cd44780bd8104353f00000000810435bf0000003f000000bf0b41aebe5a828cbdd44780bd8104353f00000000810435bf0000003f000000bf76c5b4be3065e03ce0138bbd4bc8473e00000000b5157bbf0000003f000000bf76c5b4be5a828cbde0138bbd4bc8473e00000000b5157bbf0000003f000000bfab23efbe3065e03ce0138bbd0000000000000000000080bf0000003f000000bfab23efbe5a828cbde0138bbd0000000000000000000080bf0000003f000000bf656dfbbe67b6ab3ce0138bbd0000000000000000000080bf0000003f000000bf656dfbbe4fad7ebde0138bbd0000000000000000000080bf0000003f000000bf093602bf514c5e3be0138bbd0000000000000000000080bf0000003f000000bf093602bfe1b636bde0138bbd0000000000000000000080bf0000003f000000bf7fdb03bf1cd2a8bce0138bbd0000000000000000000080bf0000003f000000bf16a7b23ee0828cbd6e6e4cbdb5157b3f000000004bc8473e0000003f000000bf828db33ee0828cbd89cf5dbdf4fd343f000000000f0b353f0000003f000000bf828db33efe60e03c89cf5dbdf4fd343f000000000f0b353f0000003f000000bf16a7b23efe60e03c6e6e4cbdb5157b3f000000004bc8473e0000003f000000bfa6b9b53efe60e03ce20165bd14ae473e00000000b5157b3f0000003f000000bf16a7b23ee0828cbdf758faba0000803f00000000000000800000003f000000bfa6b9b53ee0828cbde20165bd14ae473e00000000b5157b3f0000003f000000bf16a7b23efe60e03cf758faba0000803f00000000000000800000003f000000bfb917f03efe60e03ce20165bd00000000000000000000803f0000003f000000bf16a7b23ee0828cbd598b8f3db5157b3f000000004bc8473e0000003f000000bfb917f03ee0828cbde20165bd00000000000000000000803f0000003f000000bf7461fc3e35b2ab3cef0265bd00000000000000000000803f0000003f000000bf7461fc3e5cae7ebdef0265bd00000000000000000000803f0000003f000000bf32b0023f8a3b5e3bef0265bd00000000000000000000803f0000003f000000bf32b0023fedb736bdef0265bd00000000000000000000803f0000003f000000bf9755043f35d4a8bcef0265bd00000000000000000000803f0000003f000000bf16a7b23e7120e43d598b8f3db5157b3f000000004bc8473e0000003f000000bf16a7b23e7120e43df758faba0000803f00000000000000800000003f000000bf34f4af3ee0828cbd7c9ca93d8104353f000000008104353f0000003f000000bf34f4af3e7120e43d7c9ca93d8104353f000000008104353f0000003f000000bfeb6fa93ee0828cbd8868b43d4bc8473e00000000b5157b3f0000003f000000bfeb6fa93e7120e43d8868b43d4bc8473e00000000b5157b3f0000003f000000bf9a7ba8be5a828cbd0e69b43d4bc847be00000000b5157b3f0000003f000000bf9a7ba8bef720e43d0e69b43d4bc847be00000000b5157b3f0000003f000000bf0400afbe5a828cbd029da93d810435bf000000008104353f0000003f000000bf0400afbef720e43d029da93d810435bf000000008104353f0000003f000000bf07b3b1be5a828cbddf8b8f3db5157bbf000000004bc8473e0000003f000000bf07b3b1bef720e43ddf8b8f3db5157bbf000000004bc8473e0000003f000000bf07b3b1be3065e03c6937faba000080bf00000000000000800000003f000000bf07b3b1bef720e43d6937faba000080bf00000000000000800000003f000000bf07b3b1be5a828cbd6937faba000080bf00000000000000800000003f000000bf07b3b1be5a828cbd616d4cbdb5157bbf0000000014ae473e0000003f000000bf07b3b1be3065e03c616d4cbdb5157bbf0000000014ae473e0000003f000000bf3199b2be5a828cbd7dce5dbd810435bf000000008104353f0000003f000000bf3199b2be3065e03c7dce5dbd810435bf000000008104353f0000003f000000bf76c5b4be5a828cbdd60065bd14ae47be00000000b5157b3f0000003f000000bf76c5b4be3065e03cd60065bd14ae47be00000000b5157b3f0000003f000000bfab23efbe5a828cbdd60065bd00000000000000000000803f0000003f000000bfab23efbe3065e03cd60065bd00000000000000000000803f0000003f000000bf656dfbbe4fad7ebdd60065bd00000000000000000000803f0000003f000000bf656dfbbe67b6ab3cd60065bd00000000000000000000803f0000003f000000bf093602bfe1b636bdd60065bd00000000000000000000803f0000003f000000bf093602bf514c5e3bd60065bd00000000000000000000803f0000003f000000bf7fdb03bf1cd2a8bcd60065bd00000000000000000000803f0000003f000000bf5d35af3ee0828cbd5a4880bd00000000000080bf000000800000003f000000bfa6b9b53ee0828cbde20165bd00000000000080bf000000800000003f000000bf828db33ee0828cbd89cf5dbd00000000000080bf000000800000003f000000bf3882ac3ee0828cbd6e6e4cbd00000000000080bf000000800000003f000000bfa6b9b53ee0828cbd66148bbd00000000000080bf000000800000003f000000bf16a7b23ee0828cbd6e6e4cbd00000000000080bf000000800000003f000000bfb917f03ee0828cbde20165bdcba1053e64cc7dbf000000800000003f000000bf3882ac3ee0828cbdf758faba00000000000080bf000000800000003f000000bfb917f03ee0828cbdec148bbdcba1053e64cc7dbf000000800000003f000000bf16a7b23ee0828cbdf758faba00000000000080bf000000800000003f000000bf7461fc3e5cae7ebdef0265bd0000003f2db25dbf000000800000003f000000bf3882ac3ee0828cbd598b8f3d00000000000080bf000000800000003f000000bf7461fc3e5cae7ebdec148bbd0000003f2db25dbf000000800000003f000000bf16a7b23ee0828cbd598b8f3d00000000000080bf000000800000003f000000bf32b0023fedb736bdef0265bd2db25d3f000000bf000000800000003f000000bf0f9cab3ee0828cbd603b983d00000000000080bf000000800000003f000000bf32b0023fedb736bdec148bbd2db25d3f000000bf000000800000003f000000bf34f4af3ee0828cbd7c9ca93d00000000000080bf000000800000003f000000bf9755043f35d4a8bcef0265bd0000803f00000000000000800000003f000000bfeb6fa93ee0828cbd13d59b3d00000000000080bf000000800000003f000000bf9755043f35d4a8bcec148bbd0000803f00000000000000800000003f000000bfeb6fa93ee0828cbd8868b43d00000000000080bf000000800000003f000000bf32b0023f8a3b5e3bef0265bd2db25d3f0000003f000000800000003f000000bf9a7ba8be5a828cbd99d59b3d00000000000080bf000000800000003f000000bf32b0023f8a3b5e3bec148bbd2db25d3f0000003f000000800000003f000000bf9a7ba8be5a828cbd0e69b43d00000000000080bf000000800000003f000000bf7461fc3e35b2ab3cef0265bd0000003f2db25d3f000000800000003f000000bf9ca7aabe5a828cbd6d3c983d00000000000080bf000000800000003f000000bf7461fc3e35b2ab3cec148bbd0000003f2db25d3f000000800000003f000000bf0400afbe5a828cbd029da93d00000000000080bf000000800000003f000000bfb917f03efe60e03ce20165bdcba1053e64cc7d3f000000800000003f000000bf088eabbe5a828cbddf8b8f3d00000000000080bf000000800000003f000000bfb917f03efe60e03cec148bbdcba1053e64cc7d3f000000800000003f000000bf07b3b1be5a828cbddf8b8f3d00000000000080bf000000800000003f000000bfa6b9b53efe60e03ce20165bd000000000000803f000000800000003f000000bf088eabbe5a828cbd6937faba00000000000080bf000000800000003f000000bfa6b9b53efe60e03c66148bbd000000000000803f000000800000003f000000bf07b3b1be5a828cbd6937faba00000000000080bf000000800000003f000000bf828db33efe60e03c89cf5dbd000000000000803f000000800000003f000000bf088eabbe5a828cbd616d4cbd00000000000080bf000000800000003f000000bf5d35af3efe60e03c5a4880bd000000000000803f000000800000003f000000bf07b3b1be5a828cbd616d4cbd00000000000080bf000000800000003f000000bf16a7b23efe60e03c6e6e4cbd000000000000803f000000800000003f000000bf0b41aebe5a828cbdd44780bd00000000000080bf000000800000003f000000bf3882ac3efe60e03c6e6e4cbd000000000000803f000000800000003f000000bf3199b2be5a828cbd7dce5dbd00000000000080bf000000800000003f000000bf16a7b23efe60e03cf758faba000000000000803f000000800000003f000000bf3882ac3efe60e03cf758faba000000000000803f000000800000003f000000bf76c5b4be5a828cbde0138bbd00000000000080bf000000800000003f000000bf76c5b4be5a828cbdd60065bd00000000000080bf000000800000003f000000bfab23efbe5a828cbde0138bbdcba105be64cc7dbf000000800000003f000000bfab23efbe5a828cbdd60065bdcba105be64cc7dbf000000800000003f000000bf656dfbbe4fad7ebde0138bbd000000bf2db25dbf000000800000003f000000bf656dfbbe4fad7ebdd60065bd000000bf2db25dbf000000800000003f000000bf093602bfe1b636bde0138bbd2db25dbf000000bf000000800000003f000000bf093602bfe1b636bdd60065bd2db25dbf000000bf000000800000003f000000bf7fdb03bf1cd2a8bce0138bbd000080bf00000000000000800000003f000000bf7fdb03bf1cd2a8bcd60065bd000080bf00000000000000800000003f000000bf093602bf514c5e3be0138bbd2db25dbf0000003f000000800000003f000000bf093602bf514c5e3bd60065bd2db25dbf0000003f000000800000003f000000bf656dfbbe67b6ab3ce0138bbd000000bf2db25d3f000000800000003f000000bf656dfbbe67b6ab3cd60065bd000000bf2db25d3f000000800000003f000000bfab23efbe3065e03ce0138bbdcba105be64cc7d3f000000800000003f000000bfab23efbe3065e03cd60065bdcba105be64cc7d3f000000800000003f000000bf76c5b4be3065e03ce0138bbd000000000000803f000000800000003f000000bf76c5b4be3065e03cd60065bd000000000000803f000000800000003f000000bf0b41aebe3065e03cd44780bd000000000000803f000000800000003f000000bf3199b2be3065e03c7dce5dbd000000000000803f000000800000003f000000bf088eabbe3065e03c616d4cbd000000000000803f000000800000003f000000bf07b3b1be3065e03c616d4cbd000000000000803f000000800000003f000000bf088eabbe3065e03c6937faba000000000000803f000000800000003f000000bf07b3b1be3065e03c6937faba000000000000803f000000800000003f000000bf34f4af3e7120e43d7c9ca93d000000000000803f000000800000003f000000bf3882ac3e7120e43d598b8f3d000000000000803f000000800000003f000000bf0f9cab3e7120e43d603b983d000000000000803f000000800000003f000000bfeb6fa93e7120e43d8868b43d000000000000803f000000800000003f000000bf16a7b23e7120e43d598b8f3d000000000000803f000000800000003f000000bfeb6fa93e7120e43d13d59b3d000000000000803f000000800000003f000000bf3882ac3e7120e43df758faba000000000000803f000000800000003f000000bf16a7b23e7120e43df758faba000000000000803f000000800000003f000000bf9a7ba8bef720e43d0e69b43d000000000000803f000000800000003f000000bf9a7ba8bef720e43d99d59b3d000000000000803f000000800000003f000000bf0400afbef720e43d029da93d000000000000803f000000800000003f000000bf9ca7aabef720e43d6d3c983d000000000000803f000000800000003f000000bf07b3b1bef720e43ddf8b8f3d000000000000803f000000800000003f000000bf088eabbef720e43ddf8b8f3d000000000000803f000000800000003f000000bf07b3b1bef720e43d6937faba000000000000803f000000800000003f000000bf088eabbef720e43d6937faba000000000000803f000000800000003f000000bf07b3b1bef720e43d6937faba0000000000000000000080bf0000003f000000bf088eabbef720e43d6937faba0000000000000000000080bf0000003f000000bf088eabbe3065e03c6937faba0000000000000000000080bf0000003f000000bf07b3b1be3065e03c6937faba0000000000000000000080bf0000003f000000bf - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0.0009315014, y: 0.021391, z: 0.010090001} - m_Extent: {x: 0.5159995, y: 0.09, z: 0.078001} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh.meta deleted file mode 100644 index c9d44fe60..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate (Bracket).mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7fa0b44495abba04c9aaed0dcfd513f5 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx new file mode 100644 index 0000000000000000000000000000000000000000..18c4f5d69cd6225171b1e4138a8f2ce39d66aa22 GIT binary patch literal 57788 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 literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta new file mode 100644 index 000000000..679a09c2a --- /dev/null +++ b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: f7b77729178432248a07efe02e9f5752 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire.meta deleted file mode 100644 index 3613a85ed..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0611425bdaebaf743988240e99bf1b91 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh deleted file mode 100644 index 53cf458fc..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Long Plate - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 132 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 62 - localAABB: - m_Center: {x: 0, y: 0.00012500002, z: -0.23979} - m_Extent: {x: 0.3795, y: 0.005, z: 0.25} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 1e001f00200020001f0021000000010002000100000003000100030015001500030016002b0015002a00150028002a00150016002800280016002900290016003800390029003800040005000600060007000400060005001200120005001d00080009000a000b000a0009000b00090017000b0017001800300017003100220017003000170022001800180022002300180023003a003a0023003b000c000d000e000e000d00130013000d0014000e000f000c0010000c000f000f001100100019001a001b001b001a00320032001a0033001b001c00190019001c003c003c001c003d002400250026002600250027002c002d002e002e002d002f00340035003600360035003700 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 62 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 1984 - _typelessdata: df4f0d3e77be9fbbc1c5fabe00000080000080bf0000008030ba283f38f66cbf2fdd24be77be9fbb5188f0be00000080000080bf00000080d0265b3f483465bfdf4f0dbe77be9fbbc1c5fabe00000080000080bf00000080d045573f38f66cbf2fdd243e77be9fbb5188f0be00000080000080bf0000008030d9243f483465bfdf4f0d3e9eefa73bc1c5fabe865ab33e0000000005c56fbf00000000404e98bd2fdd243e9eefa73b5188f0be36cd5b3f00000000c13903bfe5b7683c404e98bd2fdd243e77be9fbb5188f0be36cd5b3f00000000c13903bfe5b7683c4dbed9bddf4f0d3e77be9fbbc1c5fabe865ab33e0000000005c56fbf000000004dbed9bddf4f0dbe9eefa73bc1c5fabe000000000000803f00000000c0e8223e38f66cbf2fdd24be9eefa73b5188f0be000000000000803f00000000bf64133e483465bfdf4f0d3e9eefa73bc1c5fabe000000000000803f00000000a08bae3e38f66cbf2fdd243e9eefa73b5188f0be000000000000803f00000000a14db63e483465bfdf4f0dbe77be9fbbc1c5fabe865ab3be0000000005c56fbff0365f3f4dbed9bd2fdd24be77be9fbb5188f0be36cd5bbf00000000c13903bf10945b3f4dbed9bd2fdd24be9eefa73b5188f0be36cd5bbf00000000c13903bf10945b3f404e98bddf4f0dbe9eefa73bc1c5fabe865ab3be0000000005c56fbff0365f3f404e98bddf4f0d3e77be9fbbc1c5fabe865ab33e0000000005c56fbf0000803f4dbed9bddf4f0d3e9eefa73bc1c5fabe865ab33e0000000005c56fbf0000803f404e98bd8b6c873e77be9fbb3c6620bcecc0793f000000009cc460beabd18b3e4dbed9bd8b6c87be9eefa73b3c6620bcecc079bf000000009cc460be6550193f404e98bd8b6c87be77be9fbb3c6620bcecc079bf000000009cc460be6550193f4dbed9bd8b6c87be77be9fbb3c6620bc00000080000080bf00000080209b6c3f9f1e4bbe8b6c873e77be9fbb3c6620bc00000080000080bf00000080e064133f9f1e4bbe8b6c87be9eefa73b3c6620bc000000000000803f0000000003279b3d9f1e4bbe8b6c873e9eefa73b3c6620bc000000000000803f000000003f36d93e9f1e4bbe8b6c873e9eefa73bd847273c00000080000000000000803fd1cba83e404e98bd8b6c87be9eefa73bd847273c00000080000000000000803fb9e20a3f404e98bd8b6c87be77be9fbbd847273c00000080000000000000803fb9e20a3f4dbed9bd8b6c873e77be9fbbd847273c00000080000000000000803fd1cba83e4dbed9bd8b6c873e9eefa73b3c6620bcecc0793f000000009cc460beabd18b3e404e98bdd34dc2be9eefa73b3c6620bc000080bf000000000000008098de123f404e98bdd34dc2be77be9fbb3c6620bc000080bf000000000000008098de123f4dbed9bdd34dc2be9eefa73bd847273c000080bf00000000000000808b6e113f404e98bdd34dc2be77be9fbbd847273c000080bf00000000000000808b6e113f4dbed9bd8b6c87be9eefa73bd847273c000000000000803f0000000003279b3ddf162cbe8b6c873e9eefa73bd847273c000000000000803f000000003f36d93edf162cbed34dc23e77be9fbb3c6620bc0000803f0000000000000080b1c3983e4dbed9bdd34dc23e9eefa73b3c6620bc0000803f0000000000000080b1c3983e404e98bdd34dc23e77be9fbbd847273c0000803f00000000000000802db49b3e4dbed9bdd34dc23e9eefa73bd847273c0000803f00000000000000802db49b3e404e98bd8b6c87be77be9fbbd847273c00000080000080bf00000080209b6c3fdf162cbe8b6c873e77be9fbbd847273c00000080000080bf00000080e064133fdf162cbed34dc2be77be9fbbd847273c00000080000080bf000000800000803fdf162cbed34dc2be77be9fbb3c6620bc00000080000080bf000000800000803f9f1e4bbe8b6c87be9eefa73b3c6620bc0000008000000000000080bf6550193f404e98bd8b6c87be77be9fbb3c6620bc0000008000000000000080bf6550193f4dbed9bdd34dc2be9eefa73b3c6620bc0000008000000000000080bf98de123f404e98bdd34dc2be77be9fbb3c6620bc0000008000000000000080bf98de123f4dbed9bdd34dc2be9eefa73bd847273c000000000000803f0000000000000000df162cbed34dc2be9eefa73b3c6620bc000000000000803f00000000000000009f1e4bbed34dc2be77be9fbbd847273c00000080000000000000803f8b6e113f4dbed9bdd34dc2be9eefa73bd847273c00000080000000000000803f8b6e113f404e98bd8b6c873e77be9fbb3c6620bc0000008000000000000080bfabd18b3e4dbed9bd8b6c873e9eefa73b3c6620bc0000008000000000000080bfabd18b3e404e98bdd34dc23e77be9fbb3c6620bc0000008000000000000080bfb1c3983e4dbed9bdd34dc23e9eefa73b3c6620bc0000008000000000000080bfb1c3983e404e98bdd34dc23e77be9fbb3c6620bc00000080000080bf000000800000003f9f1e4bbed34dc23e77be9fbbd847273c00000080000080bf000000800000003fdf162cbed34dc23e9eefa73b3c6620bc000000000000803f000000000000003f9f1e4bbed34dc23e9eefa73bd847273c000000000000803f000000000000003fdf162cbed34dc23e9eefa73bd847273c00000080000000000000803f2db49b3e404e98bdd34dc23e77be9fbbd847273c00000080000000000000803f2db49b3e4dbed9bd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0.00012500002, z: -0.23979} - m_Extent: {x: 0.3795, y: 0.005, z: 0.25} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh.meta deleted file mode 100644 index 40cb340f6..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Long Plate.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a8ca1390ec4bbfa4b8ba55eaf0e60911 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh deleted file mode 100644 index 82b32c73d..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Plate - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 156 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 70 - localAABB: - m_Center: {x: -0.00034299493, y: -0.00028000004, z: -0.17177701} - m_Extent: {x: 0.3612, y: 0.005, z: 0.1785} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 260027002800280027002900000001000200010000000300010003001900190003001a001b0019001a0024001b001a0033001b0032001b00300032001b0024003000300024003100310024004000410031004000040005000600060007000400060005001300130005001400120013001400230012001400080009000a000b000a0009000b0009001c000b001c001d001c001e001d001e0025001d0038001e0039002a001e0038001e002a00250025002a002b0025002b00420042002b0043000c000d000e000e000f000c0010000c000f000f00110010000e000d00180018000d0015001500170018001500160017001f0020002100210020003a003a0020003b00210022001f001f00220044004400220045002c002d002e002e002d002f003400350036003600350037003c003d003e003e003d003f00 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 70 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 2240 - _typelessdata: 564a5f3eda03adbb8257b3be00000080000080bf00000080624d193fee3f66bf2f3271beda03adbbc03fadbe00000080000080bf00000080acac693fe44b60bf2afe5fbeda03adbb8257b3be00000080000080bf000000809eb2663fee3f66bf5a7e703eda03adbbc03fadbe00000080000080bf000000805453163fe44b60bf564a5f3e3baa9a3b8257b3be234a9b3e0000000041f173bf000000008f00cebd5a7e703e3baa9a3bc03fadbe66f7543f000000007b140ebfa774303c8f00cebd5a7e703eda03adbbc03fadbe66f7543f000000007b140ebfa774303c05dff4bd564a5f3eda03adbb8257b3be234a9b3e0000000041f173bf0000000005dff4bd2afe5fbe3baa9a3b8257b3be000000000000803f000000000e6bca3dee3f66bf2f3271be3baa9a3bc03fadbe000000000000803f00000000a29ab23de44b60bf564a5f3e3baa9a3b8257b3be000000000000803f000000003d65cd3eee3f66bf5a7e703e3baa9a3bc03fadbe000000000000803f000000005859d33ee44b60bf2afe5fbeda03adbb8257b3be234a9bbe0000000041f173bf04c8483f05dff4bd2f3271beda03adbbc03fadbe66f754bf000000007b140ebf4206463f05dff4bd2f3271be3baa9a3bc03fadbe66f754bf000000007b140ebf4206463f8f00cebd2afe5fbe3baa9a3b8257b3be234a9bbe0000000041f173bf04c8483f8f00cebd564a5f3eda03adbb8257b3be234a9b3e0000000041f173bf0000803f05dff4bd564a5f3e3baa9a3b8257b3be234a9b3e0000000041f173bf0000803f8f00cebd37a79a3eda03adbbd3a3a9bb0000803f000000000000008018cd6a3e05dff4bd37a79a3eda03adbba46e27bdad697e3f000000001cebe2bd2254593e05dff4bd37a79a3e3baa9a3ba46e27bdad697e3f000000001cebe2bd2254593e8f00cebd21019bbeda03adbba46e27bdad697ebf000000001cebe2bdfc72123f05dff4bd21019bbeda03adbbd3a3a9bb000080bf00000000000000802f160e3f05dff4bd21019bbe3baa9a3bd3a3a9bb000080bf00000000000000802f160e3f8f00cebd21019bbe3baa9a3ba46e27bdad697ebf000000001cebe2bdfc72123f8f00cebd21019bbeda03adbba46e27bd00000080000080bf00000080d194753f0beb96be37a79a3eda03adbba46e27bd00000080000080bf000000802f6b0a3f0beb96be21019bbeda03adbbd3a3a9bb00000080000080bf00000080d194753f176566be21019bbe3baa9a3ba46e27bd000000000000803f00000000f2b2263d0beb96be37a79a3e3baa9a3ba46e27bd000000000000803f00000000a229eb3e0beb96be21019bbe3baa9a3bd3a3a9bb000000000000803f00000000f2b2263d176566be37a79a3e3baa9a3b9d4cdc3b00000080000000000000803f6dfd843e8f00cebd21019bbe3baa9a3b9d4cdc3b00000080000000000000803fea3c063f8f00cebd21019bbeda03adbb9d4cdc3b00000080000000000000803fea3c063f05dff4bd37a79a3eda03adbb9d4cdc3b00000080000000000000803f6dfd843e05dff4bd37a79a3e3baa9a3bd3a3a9bb0000803f000000000000008018cd6a3e8f00cebd37a79a3eda03adbbd3a3a9bb00000080000080bf000000802f6b0a3f176566be37a79a3e3baa9a3bd3a3a9bb000000000000803f00000000a229eb3e176566be2a1cb9be3baa9a3bd3a3a9bb000080bf00000000000000804ed00a3f8f00cebd2a1cb9beda03adbbd3a3a9bb000080bf00000000000000804ed00a3f05dff4bd2a1cb9be3baa9a3b9d4cdc3b000080bf0000000000000080e78b093f8f00cebd2a1cb9beda03adbb9d4cdc3b000080bf0000000000000080e78b093f05dff4bd21019bbe3baa9a3b9d4cdc3b000000000000803f00000000f2b2263dab944ebe37a79a3e3baa9a3b9d4cdc3b000000000000803f00000000a229eb3eab944ebe40c2b83eda03adbbd3a3a9bb0000803f000000000000008049d8773e05dff4bd40c2b83e3baa9a3bd3a3a9bb0000803f000000000000008049d8773e8f00cebd40c2b83eda03adbb9d4cdc3b0000803f0000000000000080a5be7c3e05dff4bd40c2b83e3baa9a3b9d4cdc3b0000803f0000000000000080a5be7c3e8f00cebd21019bbeda03adbb9d4cdc3b00000080000080bf00000080d194753fab944ebe37a79a3eda03adbb9d4cdc3b00000080000080bf000000802f6b0a3fab944ebe2a1cb9beda03adbb9d4cdc3b00000080000080bf000000800000803fab944ebe2a1cb9beda03adbbd3a3a9bb00000080000080bf000000800000803f176566be21019bbe3baa9a3bd3a3a9bb0000000000000000000080bf2f160e3f8f00cebd21019bbeda03adbbd3a3a9bb0000000000000000000080bf2f160e3f05dff4bd2a1cb9be3baa9a3bd3a3a9bb0000000000000000000080bf4ed00a3f8f00cebd2a1cb9beda03adbbd3a3a9bb0000000000000000000080bf4ed00a3f05dff4bd2a1cb9be3baa9a3b9d4cdc3b000000000000803f0000000000000000ab944ebe2a1cb9be3baa9a3bd3a3a9bb000000000000803f0000000000000000176566be2a1cb9beda03adbb9d4cdc3b00000080000000000000803fe78b093f05dff4bd2a1cb9be3baa9a3b9d4cdc3b00000080000000000000803fe78b093f8f00cebd37a79a3eda03adbbd3a3a9bb0000000000000000000080bf18cd6a3e05dff4bd37a79a3e3baa9a3bd3a3a9bb0000000000000000000080bf18cd6a3e8f00cebd40c2b83eda03adbbd3a3a9bb0000000000000000000080bf49d8773e05dff4bd40c2b83e3baa9a3bd3a3a9bb0000000000000000000080bf49d8773e8f00cebd40c2b83eda03adbbd3a3a9bb00000080000080bf000000800000003f176566be40c2b83eda03adbb9d4cdc3b00000080000080bf000000800000003fab944ebe40c2b83e3baa9a3bd3a3a9bb000000000000803f000000000000003f176566be40c2b83e3baa9a3b9d4cdc3b000000000000803f000000000000003fab944ebe40c2b83e3baa9a3b9d4cdc3b00000080000000000000803fa5be7c3e8f00cebd40c2b83eda03adbb9d4cdc3b00000080000000000000803fa5be7c3e05dff4bd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: -0.00034299493, y: -0.00028000004, z: -0.17177701} - m_Extent: {x: 0.3612, y: 0.005, z: 0.1785} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh.meta deleted file mode 100644 index 66ab824ee..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Plate.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 10294356d8e9e5f49b0a8205a50a231d -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh deleted file mode 100644 index 261bbf87a..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Wire Rectangle - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 672 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 144 - localAABB: - m_Center: {x: 0, y: 0, z: -0.1663525} - m_Extent: {x: 0.368, y: 0.005038, z: 0.17505349} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 69007200710071006800690068007100730060006900680073006a0068006a007300740068005f0060005f0068006a00450060005f0074006b006a006b0074006d006a0061005f0061006a006b005f004400450044005f0061003300450044006d0064006b0064006d006c004400320033002a00330032006100460044003200440046006b006200610046006100620062006b006400320029002a000c002a00290046003400320029003200340062004700460034004600470064005b006200470062005b006c00630064005b006400630063006c006e005b00400047006e006500630065006e006f0063005a005b005a006300650040005b005a006f006600650066006f00700070006700660065005c005a005c00650066005d006600670066005d005c0067005e005d003f005a005c005a003f00400041005c005d005c0041003f0042005d005e005d00420041005e00430042002d003f0041003000420043004300310030002f0041004200420030002f0041002f002d0027003000310031002800270026002f00300030002700260024002d002f002f00260024003f002d002e002e0040003f002d002400250025002e002d0040002e0035003500470040004700350034002e0025002c002c0035002e002b003400350035002c002b0034002b00290010002c0025000a0029002b0029000a000c000f002b002c002b000f000a002c0010000f000a000b000c000200250024002500020010000a000d000b0017000b000d000f000e000a000e000d000a0010000e000f000d001800170018000d000e00200017001800100011000e001800210020003b00200021000e001900180021001800190019000e00110021003c003b004d003b003c001900220021003c002100220011001a001900220019001a000100110010001a00110001000200010010003c004e004d0056004d004e0022003d003c004e003c003d001a00230022003d00220023004e00570056007a005600570057007b007a003d004f004e0057004e004f007b00570058004f005800570058007c007b004f003d003e0023003e003d0058004f0050003e0050004f007c005800590050005900580059007d007c0050003e0036007d0059005100510075007d00590050004800480051005900360048005000750051005200520076007500510048004900490052005100480036003700370049004800760052005300530077007600520049004a004a005300520049003700380038004a00490077005300540054007800770053004a004b004b00540053005500790078007800540055004c005500540054004b004c003a004c004b0039004b004a004b0039003a004a00380039001f003a00390039001e001f001e003900380016001f001e0038001d001e001d00380037001e001500160015001e001d0008001600150037001c001d001c00370036001d001400150014001d001c0015000700080007001500140036001b001c001b0036003e003e0023001b001c001300140013001c001b0012001b0023001b001200130023001a001200010012001a000400140013001400040007000300130012001200010003001300030004000000030001000400030000000000010002002400000002000000240026002600050000000500040000000500260027000600070004000600040005002700060005000800070006000600270028000900080006002800090006007e007f0080007e0086007f007e00800081007e00850086007e00810082007e00840085007e00820083007e0083008400870088008900870089008f0087008a00880087008f008e0087008b008a0087008e008d0087008c008b0087008d008c00 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 144 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 4608 - _typelessdata: a69b443e3881693b87a2a8bec0ec9ebd832f4c3f151d193f0000203f6cd0d7bea69b44be00000000bc93a7bed9cef73d6f12833b091b7e3f0000003fca1714bfa69b443e00000080bc93a7bed9cef7bd6f1283bb091b7e3f0000003f6cd0d7bea69b44be3881693b87a2a8be73d7923d31084c3ff085193f0000203fca1714bfa69b44becf15a53b5130abbe96430bbc57ec7f3fcb10c7bc0000403fca1714bfa69b443ecf15a53b5130abbe4ed1113cc9e57f3f1973d7bc0000403f6cd0d7bea69b443e3881693bf9bdadbe5f29cb3d211f443f5c8f22bf0000603f6cd0d7bea69b44be3881693bf9bdadbea779c7bdaf25443f789c22bf0000603fca1714bfa69b44be00000000c4ccaebef38e13be6f1203ba6d567dbf0000803fca1714bfa69b443e00000080c4ccaebef38e133e6f12033a6d567dbf0000803f6cd0d7bea69b443e388169bbf9bdadbea779c73daf2544bf789c22bf0000003e6cd0d7bea69b44be00000000c4ccaebef38e13be6f1203ba6d567dbf00000000ca1714bfa69b443e00000080c4ccaebef38e133e6f12033a6d567dbf000000006cd0d7bea69b44be388169bbf9bdadbe5f29cbbd211f44bf5c8f22bf0000003eca1714bfa69b44becf15a5bb5130abbe4ed111bcc9e57fbf1973d7bc0000803eca1714bfa69b443ecf15a5bb5130abbe96430b3c57ec7fbfcb10c7bc0000803e6cd0d7bea69b443e388169bb87a2a8be73d792bd31084cbff085193f0000c03e6cd0d7bea69b44be388169bb87a2a8bec0ec9e3d832f4cbf151d193f0000c03eca1714bf280f4bbe00000000c7d7a6be4ca62a3f17b7d1b889d23e3f0000003f545316bfacc44cbe3881693b4697a7bec5feb23ea3015c3ff706bf3e0000203f545316bf60e550becf15a53b9065a9be371a40bd492e7f3fddb584bd0000403f545316bf140655be3881693bb933abbe5c20e1beb30c413fecc0f9be0000603f545316bf99bb56be0000000038f3abbeb29d2fbf17b75139fe433abf0000803f545316bf99bb56be0000000038f3abbeb29d2fbf17b75139fe433abf00000000545316bf140655be388169bbb933abbe5bb1dfbe97ff40bfec2ffbbe0000003e545316bf60e550becf15a5bb9065a9be10e937bd492e7fbfb9fc87bd0000803e545316bfacc44cbe388169bb4697a7be8d97ae3ea8c65bbf9318c43e0000c03e545316bfcfbc4cbe000000004f05a4be3fc67c3fc364aabb2eff213e0000003fde8e18bfbb274fbe3881693b4f05a4be12a50d3fb3ea533fc807bd3d0000203fde8e18bff4fd54becf15a53b4f05a4be211ff4bc3bdf7f3f17b751bc0000403fde8e18bf2cd45abe3881693b4f05a4be03091abf75024a3fda1bfcbd0000603fde8e18bf183f5dbe000000004f05a4bea8c67bbf6f12033aa32339be0000803fde8e18bf183f5dbe000000004f05a4bea8c67bbf6f12033aa32339be00000000de8e18bf2cd45abe388169bb4f05a4be1e161abf1e164abfd9cef7bd0000003ede8e18bff4fd54becf15a5bb4f05a4be1cebe2bc3bdf7fbf38f842bc0000803ede8e18bfbb274fbe388169bb4f05a4be96210e3f61c353bf57ecaf3d0000c03ede8e18bfacc44c3e3881693b4697a7be8d97aebea8c65b3f9318c43e0000203f5859d3be280f4b3e00000080c7d7a6be4ca62abf17b7d13889d23e3f0000003f5859d3be60e5503ecf15a53b9065a9be10e9373d492e7f3fb9fc87bd0000403f5859d3be1406553e3881693bb933abbe5bb1df3e97ff403fec2ffbbe0000603f5859d3be99bb563e0000008038f3abbeb29d2f3f17b751b9fe433abf0000803f5859d3be1406553e388169bbb933abbe5c20e13eb30c41bfecc0f9be0000003e5859d3be99bb563e0000008038f3abbeb29d2f3f17b751b9fe433abf000000005859d3be60e5503ecf15a5bb9065a9be371a403d492e7fbfddb584bd0000803e5859d3beacc44c3e388169bb4697a7bec5feb2bea3015cbff706bf3e0000c03e5859d3bebb274f3e3881693b4f05a4be96210ebf61c3533f57ecaf3d0000203f43e2cebecfbc4c3e000000804f05a4be3fc67cbfc364aa3b2eff213e0000003f43e2cebef4fd543ecf15a53b4f05a4be1cebe23c3bdf7f3f38f842bc0000403f43e2cebe2cd45a3e3881693b4f05a4be1e161a3f1e164a3fd9cef7bd0000603f43e2cebe183f5d3e000000804f05a4bea8c67b3f6f1203baa32339be0000803f43e2cebe2cd45a3e388169bb4f05a4be03091a3f75024abfda1bfcbd0000003e43e2cebe183f5d3e000000804f05a4bea8c67b3f6f1203baa32339be0000000043e2cebef4fd543ecf15a5bb4f05a4be211ff43c3bdf7fbf17b751bc0000803e43e2cebebb274f3e388169bb4f05a4be12a50dbfb3ea53bfc807bd3d0000c03e43e2cebecfbc4cbe000000003d644abca8c67b3f6f12033aa323393e0000003f36e84bbfbb274fbe3881693b3d644abc03091a3f75024a3fda1bfc3d0000203f36e84bbff4fd54becf15a53b3d644abc211ff43c3bdf7f3f17b7513c0000403f36e84bbf2cd45abe3881693b3d644abc12a50dbfb3ea533fc807bdbd0000603f36e84bbf183f5dbe000000003d644abc3fc67cbfc364aabb2eff21be0000803f36e84bbf183f5dbe000000003d644abc3fc67cbfc364aabb2eff21be0000000036e84bbf2cd45abe388169bb3d644abc96210ebf61c353bf57ecafbd0000003e36e84bbff4fd54becf15a5bb3d644abc1cebe23c3bdf7fbf38f8423c0000803e36e84bbfbb274fbe388169bb3d644abc1e161a3f1e164abfd9cef73d0000c03e36e84bbfbb274f3e3881693b3d644abc1e161abf1e164a3fd9cef73d0000203f295f50becfbc4c3e000000803d644abca8c67bbf6f1203baa323393e0000003f295f50bef4fd543ecf15a53b3d644abc1cebe2bc3bdf7f3f38f8423c0000403f295f50be2cd45a3e3881693b3d644abc96210e3f61c3533f57ecafbd0000603f295f50be183f5d3e000000803d644abc3fc67c3fc364aa3b2eff21be0000803f295f50be2cd45a3e388169bb3d644abc12a50d3fb3ea53bfc807bdbd0000003e295f50be183f5d3e000000803d644abc3fc67c3fc364aa3b2eff21be00000000295f50bef4fd543ecf15a5bb3d644abc211ff4bc3bdf7fbf17b7513c0000803e295f50bebb274f3e388169bb3d644abc03091abf75024abfda1bfc3d0000c03e295f50be4f4053be0000000092744d3bb29d2f3f17b75139fe433a3f0000003fc0234ebfd3f554be3881693b1f6adb3a5c20e13eb30c413fecc0f93e0000203fc0234ebf871659becf15a53b5de0f2ba371a403d492e7f3fddb5843d0000403fc0234ebf3b375dbe3881693b5342b0bbc5feb2bea3015c3ff706bfbe0000603fc0234ebfc0ec5ebe000000001422e0bb4ca62abf17b7d1b889d23ebf0000803fc0234ebfc0ec5ebe000000001422e0bb4ca62abf17b7d1b889d23ebf00000000c0234ebf3b375dbe388169bb5342b0bb8d97aebea8c65bbf9318c4be0000003ec0234ebf871659becf15a5bb5de0f2ba10e9373d492e7fbfb9fc873d0000803ec0234ebfd3f554be388169bb1f6adb3a5bb1df3e97ff40bfec2ffb3e0000c03ec0234ebf426065be00000000a48e0e3cf38e133e6f1203ba6d567d3f0000003f4a5f50bf426065be3881693b6a6ad93ba779c73daf25443f789c223f0000203f4a5f50bf426065becf15a53b26e0d73a96430b3c57ec7f3fcb10c73c0000403f4a5f50bf426065be3881693be7e35abb73d792bd31084c3ff08519bf0000603f4a5f50bf426065be00000000d124b1bbd9cef7bd6f12833b091b7ebf0000803f4a5f50bf426065be00000000d124b1bbd9cef7bd6f12833b091b7ebf000000004a5f50bf426065be388169bbe7e35abbc0ec9ebd832f4cbf151d19bf0000003e4a5f50bf426065becf15a5bb26e0d73a4ed1113cc9e57fbf1973d73c0000803e4a5f50bf426065be388169bb6a6ad93b5f29cb3d211f44bf5c8f223f0000c03e4a5f50bfd3f5543e3881693b1f6adb3a5bb1dfbe97ff403fec2ffb3e0000203f007147be4f40533e0000008092744d3bb29d2fbf17b751b9fe433a3f0000003f007147be8716593ecf15a53b5de0f2ba10e937bd492e7f3fb9fc873d0000403f007147be3b375d3e3881693b5342b0bb8d97ae3ea8c65b3f9318c4be0000603f007147bec0ec5e3e000000801422e0bb4ca62a3f17b7d13889d23ebf0000803f007147be3b375d3e388169bb5342b0bbc5feb23ea3015cbff706bfbe0000003e007147bec0ec5e3e000000801422e0bb4ca62a3f17b7d13889d23ebf00000000007147be8716593ecf15a5bb5de0f2ba371a40bd492e7fbfddb5843d0000803e007147bed3f5543e388169bb1f6adb3a5c20e1beb30c41bfecc0f93e0000c03e007147be4260653e3881693b6a6ad93b5f29cbbd211f443f5c8f223f0000203fd8823ebe4260653e00000080a48e0e3cf38e13be6f12033a6d567d3f0000003fd8823ebe4260653ecf15a53b26e0d73a4ed111bcc9e57f3f1973d73c0000403fd8823ebe4260653e3881693be7e35abbc0ec9e3d832f4c3f151d19bf0000603fd8823ebe4260653e00000080d124b1bbd9cef73d6f1283bb091b7ebf0000803fd8823ebe4260653e388169bbe7e35abb73d7923d31084cbff08519bf0000003ed8823ebe4260653e00000080d124b1bbd9cef73d6f1283bb091b7ebf00000000d8823ebe4260653ecf15a5bb26e0d73a96430bbc57ec7fbfcb10c73c0000803ed8823ebe4260653e388169bb6a6ad93ba779c7bdaf2544bf789c223f0000c03ed8823ebe7f6abc3e3881693b6a6ad93b000000004c37493f5b421e3f0000203f000000be7f6abc3e00000080a48e0e3c00000080000000800000803f0000003f000000be7f6abc3ecf15a53b26e0d73a000000000000803f000000000000403f000000be7f6abc3e3881693be7e35abb000000004c37493f5b421ebf0000603f000000be7f6abc3e00000080d124b1bb0000008000000000000080bf0000803f000000be7f6abc3e388169bbe7e35abb000000804c3749bf5b421ebf0000003e000000be7f6abc3e00000080d124b1bb0000008000000000000080bf00000000000000be7f6abc3ecf15a5bb26e0d73a00000080000080bf000000000000803e000000be7f6abc3e388169bb6a6ad93b000000804c3749bf5b421e3f0000c03e000000be7f6abcbe00000000a48e0e3c00000080000000800000803f0000003f000060bf7f6abcbe3881693b6a6ad93b000000004c37493f5b421e3f0000203f000060bf7f6abcbecf15a53b26e0d73a000000000000803f000000000000403f000060bf7f6abcbe3881693be7e35abb000000004c37493f5b421ebf0000603f000060bf7f6abcbe00000000d124b1bb0000008000000000000080bf0000803f000060bf7f6abcbe00000000d124b1bb0000008000000000000080bf00000000000060bf7f6abcbe388169bbe7e35abb000000804c3749bf5b421ebf0000003e000060bf7f6abcbecf15a5bb26e0d73a00000080000080bf000000000000803e000060bf7f6abcbe388169bb6a6ad93b000000804c3749bf5b421e3f0000c03e000060bf7f6abcbe0000000026e0d73a000080bf00000080000000800000003f000070bf7f6abcbe00000000a48e0e3c000080bf00000080000000800000003f000060bf7f6abcbe3881693b6a6ad93b000080bf000000800000008073825a3fb4af64bf7f6abcbecf15a53b26e0d73a000080bf00000080000000800000803f000070bf7f6abcbe3881693be7e35abb000080bf000000800000008073825a3f4c507bbf7f6abcbe00000000d124b1bb000080bf00000080000000800000003f000080bf7f6abcbe388169bbe7e35abb000080bf000000800000008034f6153e4c507bbf7f6abcbecf15a5bb26e0d73a000080bf000000800000008000000000000070bf7f6abcbe388169bb6a6ad93b000080bf000000800000008034f6153eb4af64bf7f6abc3e0000008026e0d73a0000803f00000000000000800000003f000080bd7f6abc3e3881693b6a6ad93b0000803f000000000000008084825a3f6282dabd7f6abc3e00000080a48e0e3c0000803f00000000000000800000003f000000be7f6abc3ecf15a53b26e0d73a0000803f00000000000000800000803f000080bd7f6abc3e3881693be7e35abb0000803f000000000000008084825a3f77f695bc7f6abc3e00000080d124b1bb0000803f00000000000000800000003f000000807f6abc3e388169bbe7e35abb0000803f000000000000008034f6153e77f695bc7f6abc3ecf15a5bb26e0d73a0000803f000000000000008000000000000080bd7f6abc3e388169bb6a6ad93b0000803f0000000000000080f1f5153e6282dabd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: -0.1663525} - m_Extent: {x: 0.368, y: 0.005038, z: 0.17505349} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh.meta deleted file mode 100644 index 352a54161..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire Rectangle.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7acf0d2e27548b9468aee732b53307d5 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh deleted file mode 100644 index 3bb66a3f0..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Wire W - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 1008 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 186 - localAABB: - m_Center: {x: -0.0000035017729, y: 0.0000020000152, z: -0.1721945} - m_Extent: {x: 0.38400048, y: 0.006, z: 0.1800005} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 00000100020000000300010000000200040000000500030000000400060000000700050000000600080000000800070009000a000b000a0009000c000b000a000d0009000e000c0013000d000a000a000c001100110013000a000b000f00090011000c001400100009000f00090010000e00160014000c000c000e001600140018001100280016000e001100180017001700130011000e0027002800100027000e001700190013001000250027001900150013000d00130015002500100012000f00120010000d001500a3000f00a4001200a300a1000d000b000d00a1000b00a2000f00a100a2000b00a4000f00a200a100a300b000a200a100af00a100b000af00ae00a400a200a200af00ae00a6001200a400ad00a400ae00ad00a600a400ae00af0077001200a6002400120024002500ae007300ad0077007300ae00ab00a600ad007b007700af00af00b0007b006f00ad007300ad006f00ab00730077007400ab00a800a600a8002400a6006f00730070007400700073006b00ab006f0078007400770077007b00780070006c006f006b006f006c00740072007000ab006b00aa00aa00a800ab00720074007600780076007400700072006e006e006c00700076007500720071006e0072007200750071006c006e006a00760078007a006c0068006b006a0068006c007500760079007a007900760067006b0068006700aa006b007c007a0078007c0078007b00aa006700a9007b007f007c007f007b00b000a900a700aa00aa00a700a800b000ac007f00a300ac00b000a800a700200020002400a800a300a500ac00a500a3001500a500a900ac00a500a700a900ac00a900650065007f00ac006500a9006700a700a5001c0015001c00a5001c002000a700150019001c006500670063006800630067007f006500620063006200650062007c007f0064006300680068006a00640061006200630063006400610062007e007c007e00620061007a007c007e006600610064007e007d007a0079007a007d00610080007e007d007e00800080006100660079007d0084007d0080008100810084007d00800066008200820081008000640069006600690064006a008300820066006600690083006a006d0069006d006a006e006e0071006d0085008300690069006d0085006d0071008700870085006d008800870071007100750088008900850087007500790086008600880075008400860079008a008700880087008a00890086008b00880088008b008a0084008c0086008b0086008c0081008d0084008c0084008d0099008a008b008c008e008b008b008e0099008d0090008c008e008c0090008d0081008f0082008f0081008e0090009400940096008e0099008e00960093009400900096009a00990090009100930090008d0091008f0091008d009500930091009b0099009a008a0099009b009b0089008a009a009c009b0091009700950091008f0097009800950097009d009b009c0089009b009d009c009e009d009e009800a000a0009d009e009700a00098009d009f0089009f009d00a000850089009f009f008300850097009200a000a00092009f00920097008f0083009f0092008f00820092009200820083001d001c00190020001c001d001b001d0019001b00190017001d00210020002400200021002100250024001f001d001b001f0021001d00250021002300230021001f0025002300270017001a001b001a00170018001b001e001f001e001b001a001f002200230022001f001e002700230026002600230022002600280027002e0022001e002c002600220022002e002c0029002800260026002c00290030001e001a001e0030002e002f001a0018001a002f00300018002d002f002d00180014002f003200300014002b002d002b00140016002d0031002f0032002f0031002a001600280016002a002b00280029002a002d002b003c003c0031002d002b002a003a003a003c002b002a002900370037003a002a0031003c003e0035003700290029002c0035003c003a003d003d003e003c003a0037003b003b003d003a0037003500390039003b00370035002c0034002e0034002c00390035003800340038003500300033002e0034002e00330033003000320038003400360033003600340032004100330036003300410031003f003200410032003f003e003f003100440036004100430041003f0041004300440042003f003e003f00420043004500380036003600440045003e004000420040003e003d003800450047004700390038003d004c0040004c003d003b004a003b0039003b004a004c00390047004a0040004c004e004c004a004d004d004e004c004a0047004b004b004d004a004500490047004b00470049004400480045004900450048004d004b00600049005e004b0060004b005e0048005c0049005e0049005c004800440046004300460044005e005c005d005d005f005e0060005e005f005b005d005c005f00590060005c005a005b005c0048005a0046005a00480058005b005a00500060005900600050004d004e004d0050005900570050005a00540058005a0046005400560058005400520050005700500052004e005700550052005500560053005300520055005400530056004f004e005200520053004f004e004f004000420040004f0053005400510051004f0053005100540046004f0051004200460043005100420051004300b100b200b300b100b400b200b100b300b500b100b600b400b100b500b700b100b800b600b100b700b900b100b900b800 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 186 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 5952 - _typelessdata: 2c9cc4bebd3706363274ec3a000080bf00000000000000800000003f000000bf2c9cc4beb3ef8abbea25c63b000080bf00000000000000800000003f000000bf2c9cc4bebd370636b2b8ff3b000080bf00000000000000800000003f000000bf2c9cc4bedf8ac4bb3274ec3a000080bf00000000000000800000003f000000bf2c9cc4bea4198b3bea25c63b000080bf00000000000000800000003f000000bf2c9cc4beb3ef8abbdac61fbb000080bf00000000000000800000003f000000bf2c9cc4be6dacc43b3274ec3a000080bf00000000000000800000003f000000bf2c9cc4bebd370636997e89bb000080bf00000000000000800000003f000000bf2c9cc4bea4198b3bdac61fbb000080bf00000000000000800000003f000000bf001f3c3c16f88abba1da30be228ef53ee3a52bbf60e5103f0000003f000000bf857c503cbd3706360c952fbeb003273f00000000bc05423f0000003f000000bfbd3786b6bd370636062f2abe00000000000000000000803f0000003f000000bf4485ea3d16f88abb89eaadbef628dc3e4df33ebf2a3a023f0000003f000000bfbd3786b6a4198b3b9cfb2bbe000000005eba293f40a43f3f0000003f000000bf6760e43ddf8ac4bbc173afbeeb7335bd643b7fbf000080bd0000003f000000bfbd3786b616f88abb9cfb2bbe000000005eba29bf40a43f3f0000003f000000bf48fc0a3cdf8ac4bb0fed33bec6dc353d643b7fbf6e34803d0000003f000000bf6f10ed3dbd3706369d47adbe34a2243f00000000ea04443f0000003f000000bfbd3786b6df8ac4bbe35330be0000000017d97ebf8126c23d0000003f000000bf001f3c3ca4198b3ba1da30be228ef53ee3a52b3f60e5103f0000003f000000bf83c1f53d16f88abb5514afbe3a920b3eccee39bf9a772c3f0000003f000000bfbd3786b66dacc43be35330be0000000017d97e3f8126c23d0000003f000000bf83c1f53ddf8ac4bb7940b1be2e90a0bcceaa7fbfa5bd41bd0000003f000000bf4485ea3da4198b3b89eaadbef628dc3ec0ec3e3f2a3a023f0000003f000000bf83c1f53dbd3706360a2eaebe4ed1513e00000000a3927a3f0000003f000000bf48fc0a3c6dacc43b0fed33bec6dc353d643b7f3f6e34803d0000003f000000bf83c1f53da4198b3b5514afbe3a920b3eccee393f9a772c3f0000003f000000bf6760e43d6dacc43bc173afbeeb7335bd643b7f3f000080bd0000003f000000bfbd3706b6a4198b3be7ab34be00000080f7e4413f742427bf0000003f000000bf59a2b33ba4198b3bc1ff36bef628dcbe4df33e3f9c3302bf0000003f000000bf83c1f53d6dacc43b7940b1be2e90a0bcceaa7f3fa5bd41bd0000003f000000bf8a3bde3da4198b3bf8fcb0be228ef5be71ac2b3fd3de10bf0000003f000000bfbd3706b6bd370636c07836be17b7d1b800000000000080bf0000003f000000bfb3ef8a3bbd370636564538be34a224bf00000080ea0444bf0000003f000000bf83c1f53da4198b3b7a6cb3be3d2c14be29ed2d3f0b2438bf0000003f000000bfd9afdb3dbd370636e49fb1beb00327bf00000000bc0542bf0000003f000000bfbd3786b616f88abbe7ab34be00000000f7e441bf742427bf0000003f000000bf59a2b33b16f88abbc1ff36bef628dcbec0ec3ebf2a3a02bf0000003f000000bf83c1f53dbd370636e752b4bedbf93ebe00000000917e7bbf0000003f000000bf8a3bde3d16f88abbf8fcb0be228ef5be71ac2bbfd3de10bf0000003f000000bf83c1f53d16f88abb7a6cb3be3d2c14be29ed2dbf0b2438bf0000003f000000bf2e53633e16f88abb7a6cb3be3d2c143e29ed2dbf0b2438bf0000003f000000bf2e53633edf8ac4bb5740b1be2e90a03cceaa7fbfca5441bd0000003f000000bf2e53633e16f88abb5514afbe3a920bbeccee39bf0d712c3f0000003f000000bf2e53633ebd370636c652b4bedbf93e3e00000000917e7bbf0000003f000000bf2e53633ebd370636e82daebe17b751be00000000a3927a3f0000003f000000bf2e53633ea4198b3b7a6cb3be3d2c143e29ed2d3f992a38bf0000003f000000bf2e53633ea4198b3b5514afbe3a920bbeccee393f9a772c3f0000003f000000bf2e53633e6dacc43b5740b1be2e90a03cceaa7f3fca5441bd0000003f000000bf32ab673ebd3706369d47adbe810435bf000000008104353f0000003f000000bf0bf1683ea4198b3b89eaadbef163ecbef7e4413ff163ec3e0000003f000000bf79036c3e6dacc43bc173afbe4c37893d17d97e3f4c3789bd0000003f000000bf2b166f3ea4198b3bf8fcb0be3480073f5eba293f348007bf0000003f000000bfc05b703ebd370636c39fb1be8104353f00000000810435bf0000003f000000bfe99c6f3e6dacc43b7a1babbeca54413dceaa7f3f2e90a0bc0000003f000000bf2b166f3e16f88abbf8fcb0be3480073f5eba29bf348007bf0000003f000000bf30f5733ea4198b3b7a1babbe0b24383f29ed2d3f3d2c14be0000003f000000bf09c2753ebd3706367a1babbe917e7b3f00000000dbf93ebe0000003f000000bf79036c3edf8ac4bbc173afbe4c37893d17d97ebf4c3789bd0000003f000000bf30f5733e16f88abb7a1babbe0b24383f29ed2dbf3d2c14be0000003f000000bf0bf1683e16f88abb89eaadbef163ecbef7e441bff163ec3e0000003f000000bfe99c6f3edf8ac4bb7a1babbeca54413dceaa7fbf77be9fbc0000003f000000bfe5446b3e16f88abb7a1babbe9a772cbfccee39bf3a920b3e0000003f000000bf0c78693ebd3706367a1babbea3927abf000000004ed1513e0000003f000000bfe5446b3e16f88abbbc0427bc7e1d38bfb6f32dbf3d2c143e0000003f000000bfe5446b3ea4198b3b7a1babbe9a772cbfccee393f3a920b3e0000003f000000bfc977693ebd370636bc0427bc917e7bbf00000000dbf93e3e0000003f000000bfe5446b3ea4198b3bbc0427bc7e1d38bfb6f32d3f3d2c143e0000003f000000bfe99c6f3e6dacc43bbc0427bca5bd41bdceaa7f3f2e90a03c0000003f000000bf30f5733ea4198b3bbc0427bc9a772c3fccee393f3a920bbe0000003f000000bfea23703ea4198b3bc636a93a348007bfecc0293f3480073f0000003f000000bf83c1753ebd370636bc0427bca3927a3f000000004ed151be0000003f000000bf1536733e6dacc43b8600e0baba6b89bd17d97e3fba6b893d0000003f000000bf8448763ea4198b3bf44d9abbf163ec3ef7e4413ff163ecbe0000003f000000bf30f5733e16f88abbbc0427bc9a772c3fccee39bf3a920bbe0000003f000000bf5c8e773ebd370636ff08c3bb8104353f00000000810435bf0000003f000000bfe99c6f3edf8ac4bbbc0427bca5bd41bdceaa7fbf2e90a03c0000003f000000bf8448763e16f88abbf44d9abbf163ec3ef7e441bff163ecbe0000003f000000bf1536733edf8ac4bb8600e0baba6b89bd17d97ebf4c37893d0000003f000000bfea23703e16f88abbc636a93a348007bfecc029bf3480073f0000003f000000bf60e67b3e16f88abb13b61fbb3a920b3eccee39bf9a772cbf0000003f000000bfcedd6e3ebd3706367711263b810435bf000000008104353f0000003f000000bf60e67b3edf8ac4bb4eb7ec3a2e90a0bcceaa7fbfa5bd413d0000003f000000bf60e67b3e16f88abb4d2ec63b3d2c14be29ed2dbf0b24383f0000003f000000bf60e67b3ebd37063679c9ff3bdbf93ebe00000000917e7b3f0000003f000000bf419bc43edf8ac4bb4eb7ec3a00000080000080bf000000000000003f000000bf419bc43e16f88abbb136c63b00000080810435bf8104353f0000003f000000bf419bc43e16f88abb13b61fbb00000080810435bf810435bf0000003f000000bf419bc43ebd37063679c9ff3b00000000000000000000803f0000003f000000bf419bc43ebd370636d26d89bb0000008000000000000080bf0000003f000000bf60e67b3ea4198b3b4d2ec63b3d2c14be29ed2d3f0b24383f0000003f000000bf419bc43e41118b3bb136c63b000000808104353f8104353f0000003f000000bf60e67b3e6dacc43b4eb7ec3a2e90a0bcceaa7f3fa5bd413d0000003f000000bf419bc43e6dacc43b4eb7ec3a000000000000803f000000000000003f000000bf60e67b3ea4198b3b13b61fbb3a920b3eccee393f9a772cbf0000003f000000bf419bc43e41118b3b13b61fbb000000008104353f810435bf0000003f000000bf60e67b3ebd370636d26d89bb4ed1513e00000080a3927abf0000003f000000bf535d70bebd370636e49fb1be810435bf00000080810435bf0000003f000000bfc15463bebd370636e752b4be12143fbe00000000917e7bbf0000003f000000bfc15463beb3ef8abb9c6cb3be3d2c14be9be62dbf992a38bf0000003f000000bf7a176fbeb3ef8abb1afdb0be348007bf5eba29bfc28607bf0000003f000000bf9cc3f5bdbd370636e752b4be12143f3e00000000917e7bbf0000003f000000bf16c375bebd3706369b1babbe917e7bbf00000080dbf93ebe0000003f000000bf9cc3f5bdb3ef8abb9c6cb3be7446143e9be62dbf992a38bf0000003f000000bfc15463bedf8ac4bb7940b1be2e90a0bcceaa7fbfca5441bd0000003f000000bfc2f673beb3ef8abb9b1babbe992a38bf9be62dbf744614be0000003f000000bf0c056cbedf8ac4bbe273afbeba6b89bd17d97ebfba6b89bd0000003f000000bf9cc3f5bddf8ac4bb7940b1be2e90a03cceaa7fbfa5bd41bd0000003f000000bfc15463beb3ef8abb7714afbe3a920b3eccee39bf9a772c3f0000003f000000bf7c9e6fbedf8ac4bb9b1babbeca5441bdceaa7fbf2e90a0bc0000003f000000bf9df268beb3ef8abbabeaadbef163ec3e6ade41bf0d71ec3e0000003f000000bf9cc3f5bdb3ef8abb5514afbe3a920bbe3ee839bf9a772c3f0000003f000000bfc15463bebd3706360a2eaebe17b7513e00000000a3927a3f0000003f000000bf78466bbeb3ef8abb9b1babbe287e2c3f3ee839bf3a920b3e0000003f000000bf08ad67bebd370636bf47adbe8104353f00000080f4fd343f0000003f000000bf9cc3f5bdbd3706360a2eaebe17b751be00000000a3927a3f0000003f000000bfc15463bea4198b3b7714afbe03780b3eccee393f9a772c3f0000003f000000bf9e7969bebd3706369b1babbea3927a3f17b7d1b817b7513e0000003f000000bf9df268bea4198b3babeaadbef163ec3ef7e4413ff163ec3e0000003f000000bf9cc3f5bda4198b3b5514afbe3a920bbeccee393f9a772c3f0000003f000000bfc15463be6dacc43b7940b1be2e90a0bcceaa7f3fca5441bd0000003f000000bf78466bbea4198b3b9b1babbe9a772c3fccee393f3a920b3e0000003f000000bf0c056cbe6dacc43be273afbeba6b89bd17d97e3f4c3789bd0000003f000000bf9cc3f5bd6dacc43b7940b1be2e90a03cceaa7f3fa5bd41bd0000003f000000bfc15463bea4198b3b9c6cb3be3d2c14be29ed2d3f992a38bf0000003f000000bf7c9e6fbe6dacc43b9b1babbeca5441bdceaa7f3f2e90a0bc0000003f000000bf7a176fbea4198b3b1afdb0be348007bf5eba293f348007bf0000003f000000bf9cc3f5bda4198b3b9c6cb3be7446143e29ed2d3f992a38bf0000003f000000bfc2f673bea4198b3b9b1babbe992a38bf29ed2d3f3d2c14be0000003f000000bfc2f673bea4198b3bee0827bc0d712cbfccee393f3a920bbe0000003f000000bf9cc375bebd370636ee0827bca3927abf17b7d1b817b751be0000003f000000bfc2f673beb3ef8abbee0827bc9a772cbf3ee839bf3a920bbe0000003f000000bfbf9e6fbe6dacc43bee0827bcca54413dceaa7f3f2e90a03c0000003f000000bfbf9e6fbedf8ac4bbee0827bcca54413dceaa7fbf2e90a03c0000003f000000bf78466bbea4198b3bee0827bc992a383f9be62d3f7446143e0000003f000000bf78466bbeb3ef8abbee0827bc992a383f9be62dbf7446143e0000003f000000bfe17969bebd370636ee0827bc917e7b3f0000008012143f3e0000003f000000bfeb3773bedf8ac4bb1422e0baba6b893d17d97ebfba6b893d0000003f000000bfc02570beb3ef8abb3815a93a3480073f5eba29bf3480073f0000003f000000bfe7df6ebebd370636b000263b8104353f000000808104353f0000003f000000bfc02570bea4198b3b3815a93a3480073f5eba293f3480073f0000003f000000bfeb3773be6dacc43b1422e0baba6b893d17d97e3f4c37893d0000003f000000bf36e87bbebd37063616c1ff3b12143f3e00000000917e7b3f0000003f000000bf9d4a76bea4198b3b58569abbf163ecbef7e4413ff163ecbe0000003f000000bf36e87bbea4198b3bea25c63b3d2c143e29ed2d3f0b24383f0000003f000000bf36e87bbe6dacc43bc095ec3a2e90a03cceaa7f3fa5bd413d0000003f000000bfef8f77bebd3706366211c3bb0f0b35bf17b7d1b8f4fd34bf0000003f000000bf2c9cc4be6dacc43b3274ec3a000000000000803f000000000000003f000000bf2c9cc4bea4198b3bea25c63b000000808104353f8104353f0000003f000000bf2c9cc4bea4198b3bdac61fbb000000000f0b353f810435bf0000003f000000bf2c9cc4bebd370636b2b8ff3b00000000000000000000803f0000003f000000bf36e87bbea4198b3bdac61fbb3a920bbeccee393f9a772cbf0000003f000000bf2c9cc4bebd370636997e89bb0000008000000000000080bf0000003f000000bf36e87bbeb3ef8abbea25c63b3d2c143e29ed2dbf0b24383f0000003f000000bf2c9cc4beb3ef8abbea25c63b00000080810435bf8104353f0000003f000000bf36e87bbedf8ac4bbc095ec3a2e90a03cceaa7fbfa5bd413d0000003f000000bf2c9cc4bedf8ac4bb3274ec3a00000080000080bf000000000000003f000000bf36e87bbeb3ef8abbdac61fbb3a920bbe3ee839bf9a772cbf0000003f000000bf2c9cc4beb3ef8abbdac61fbb00000080810435bf810435bf0000003f000000bf9d4a76beb3ef8abb58569abbf163ecbe6ade41bf0d71ecbe0000003f000000bf36e87bbebd370636367689bb4ed151be00000080a3927abf0000003f000000bf5c3c3cbca4198b3be4da30be3d9bf5bee3a52b3fd3de103f0000003f000000bfaf9550bcbd3706360c952fbe22fd26bf00000000bc05423f0000003f000000bf72150bbc6dacc43b52ed33beeb7335bd643b7f3f6e34803d0000003f000000bf5c3c3cbc16f88abbe4da30be3d9bf5bee3a52bbfd3de103f0000003f000000bfaed4b3bba4198b3bc1ff36bef628dc3ec0ec3e3f2a3a02bf0000003f000000bf72150bbcdf8ac4bb52ed33beeb7335bd643b7fbf6e34803d0000003f000000bf08228bbbbd370636564538be34a2243f00000000780b44bf0000003f000000bfaed4b3bb16f88abbc1ff36bef628dc3ec0ec3ebf2a3a02bf0000003f000000bfffb2dbbdbd370636e49fb1beb003273f00000000bc0542bf0000003f000000bfaf3edebdb3ef8abbf8fcb0be3d9bf53ee3a52bbfd3de10bf0000003f000000bf0663e4bddf8ac4bbc173afbec6dc353d643b7fbf6e3480bd0000003f000000bfaf3edebda4198b3bf8fcb0be228ef53e71ac2b3fd3de10bf0000003f000000bf6a88eabdb3ef8abb89eaadbe1136dcbec0ec3ebf2a3a023f0000003f000000bf1a14edbdbd370636bf47adbe34a224bf00000000ea04443f0000003f000000bf6a88eabda4198b3b89eaadbef628dcbec0ec3e3f2a3a023f0000003f000000bf0663e4bd6dacc43bc173afbec6dc353d643b7f3f6e3480bd0000003f000000bf419bc43ebd3706364eb7ec3a0000803f00000080000000800000003f000000bf419bc43e41118b3bb136c63b0000803f00000080000000800000003f000000bf419bc43ebd37063679c9ff3b0000803f00000080000000800000003f000000bf419bc43e6dacc43b4eb7ec3a0000803f00000080000000800000003f000000bf419bc43e16f88abbb136c63b0000803f00000080000000800000003f000000bf419bc43e41118b3b13b61fbb0000803f00000080000000800000003f000000bf419bc43edf8ac4bb4eb7ec3a0000803f00000080000000800000003f000000bf419bc43ebd370636d26d89bb0000803f00000080000000800000003f000000bf419bc43e16f88abb13b61fbb0000803f00000080000000800000003f000000bf - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: -0.0000035017729, y: 0.0000020000152, z: -0.1721945} - m_Extent: {x: 0.38400048, y: 0.006, z: 0.1800005} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh.meta deleted file mode 100644 index 4dbc798b5..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire/Wire W.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9769cadf367235440b2bf43056c012f7 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab index fe25f4acf..60e7ff087 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab @@ -25,6 +25,7 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 4689510019247135382} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} @@ -33,7 +34,6 @@ Transform: - {fileID: 6622806185196320941} - {fileID: 2980705729845053989} m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &3468265542873711340 MonoBehaviour: @@ -51,8 +51,6 @@ MonoBehaviour: _editorLayer: 0 _editorLayerName: _editorLayerVisibility: 1 - Position: {x: 0, y: 0, z: 0} - _rotation: 0 _length: 100 _surface: {fileID: 0} _type: 0 @@ -71,6 +69,7 @@ MonoBehaviour: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} ShowColliderMesh: 0 + ShowColliderOctree: 0 ShowAabbs: 0 _angleMax: 90 _angleMin: 0 @@ -79,6 +78,7 @@ MonoBehaviour: Friction: 0.02 GravityFactor: 0.25 _twoWay: 0 + _isKinematic: 0 --- !u!1 &6497995796854627700 GameObject: m_ObjectHideFlags: 0 @@ -104,13 +104,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6497995796854627700} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 7014708109448554416} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!33 &5934777780303694896 MeshFilter: @@ -119,7 +119,7 @@ MeshFilter: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6497995796854627700} - m_Mesh: {fileID: 4300000, guid: 7fa0b44495abba04c9aaed0dcfd513f5, type: 2} + m_Mesh: {fileID: 5571604379467031474, guid: f7b77729178432248a07efe02e9f5752, type: 3} --- !u!23 &2012952979694143057 MeshRenderer: m_ObjectHideFlags: 0 @@ -188,13 +188,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6797621395313525970} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 7014708109448554416} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &8205464296606553136 MonoBehaviour: diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Long Plate (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Long Plate (Builtin).prefab index d12154e26..0f461a8d4 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Long Plate (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Long Plate (Builtin).prefab @@ -5,12 +5,17 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 4689510019247135382, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Name value: Gate - Long Plate (Builtin) objectReference: {fileID: 0} + - target: {fileID: 5934777780303694896, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} + propertyPath: m_Mesh + value: + objectReference: {fileID: -3605029237302266654, guid: f7b77729178432248a07efe02e9f5752, type: 3} - target: {fileID: 7014708109448554416, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_RootOrder value: 0 @@ -58,6 +63,9 @@ PrefabInstance: - target: {fileID: 7906856659845120077, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Mesh value: - objectReference: {fileID: 4300000, guid: a8ca1390ec4bbfa4b8ba55eaf0e60911, type: 2} + objectReference: {fileID: -448636328567412366, guid: f7b77729178432248a07efe02e9f5752, type: 3} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Plate (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Plate (Builtin).prefab index dda4e27db..d63d7d584 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Plate (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Plate (Builtin).prefab @@ -5,6 +5,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 4689510019247135382, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} @@ -58,6 +59,9 @@ PrefabInstance: - target: {fileID: 7906856659845120077, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Mesh value: - objectReference: {fileID: 4300000, guid: 10294356d8e9e5f49b0a8205a50a231d, type: 2} + objectReference: {fileID: -5578165834312501777, guid: f7b77729178432248a07efe02e9f5752, type: 3} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire Rectangle (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire Rectangle (Builtin).prefab index 4b3e8bd34..b8258695b 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire Rectangle (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire Rectangle (Builtin).prefab @@ -5,12 +5,17 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 4689510019247135382, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Name value: Gate - Wire Rectangle (Builtin) objectReference: {fileID: 0} + - target: {fileID: 5934777780303694896, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} + propertyPath: m_Mesh + value: + objectReference: {fileID: 3748382445984247160, guid: f7b77729178432248a07efe02e9f5752, type: 3} - target: {fileID: 7014708109448554416, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_RootOrder value: 0 @@ -58,6 +63,9 @@ PrefabInstance: - target: {fileID: 7906856659845120077, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Mesh value: - objectReference: {fileID: 4300000, guid: 7acf0d2e27548b9468aee732b53307d5, type: 2} + objectReference: {fileID: 6729438931495173720, guid: f7b77729178432248a07efe02e9f5752, type: 3} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire W (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire W (Builtin).prefab index 34c4750bf..4b49a7433 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire W (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate - Wire W (Builtin).prefab @@ -5,12 +5,17 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 0} m_Modifications: - target: {fileID: 4689510019247135382, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Name value: Gate - Wire W (Builtin) objectReference: {fileID: 0} + - target: {fileID: 5934777780303694896, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} + propertyPath: m_Mesh + value: + objectReference: {fileID: -6366382902508278438, guid: f7b77729178432248a07efe02e9f5752, type: 3} - target: {fileID: 7014708109448554416, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_RootOrder value: 0 @@ -58,6 +63,9 @@ PrefabInstance: - target: {fileID: 7906856659845120077, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} propertyPath: m_Mesh value: - objectReference: {fileID: 4300000, guid: 9769cadf367235440b2bf43056c012f7, type: 2} + objectReference: {fileID: 1795337771752254519, guid: f7b77729178432248a07efe02e9f5752, type: 3} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: a277168ba0cf8a2408738f377eb98a2f, type: 3} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs index b72e87a5d..414e8e79c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs @@ -27,9 +27,9 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(GateComponent)), CanEditMultipleObjects] public class GateInspector : MainInspector { - private const string MeshFolder = "Packages/org.visualpinball.engine.unity/VisualPinball.Unity/Assets/Art/Meshes/Gate/Wire"; + private const string MeshFbx = "Packages/org.visualpinball.engine.unity/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx"; - private static readonly Dictionary TypeMap = new Dictionary { + private static readonly Dictionary WireTypeMap = new Dictionary { { "Long Plate", GateType.GateLongPlate }, { "Plate", GateType.GatePlate }, { "Wire Rectangle", GateType.GateWireRectangle }, @@ -84,7 +84,7 @@ public override void OnInspectorGUI() var wire = MainComponent.transform.Find(GateComponent.WireObjectName); if (wire != null) { - MeshDropdownProperty("Mesh", _meshProperty, MeshFolder, wire.gameObject, _typeProperty, TypeMap); + MeshDropdownPropertyFbx("Mesh", _meshProperty, MeshFbx, wire.gameObject, _typeProperty, WireTypeMap, "Wire."); } EndEditing(); @@ -100,19 +100,20 @@ protected override void FinishEdit(string label, bool dirtyMesh = true) protected void OnSceneGUI() { - if (target is not IMainRenderableComponent editable) { + if (target is not GateComponent gateComponent) { return; } - - var transform = (target as MonoBehaviour)?.transform; + + var transform = gateComponent.transform; if (transform == null || transform.parent == null) { return; } - - var position = editable.GetEditorPosition(); - position = transform.parent.TransformPoint(position); - var axis = transform.TransformDirection(-Vector3.up); //Local direction of the gate gameObject is -up - var scale = MainComponent.Length / 5000; + + var position = transform.position; + var axis = transform.TransformDirection(Vector3.forward); //Local direction of the gate gameObject is -up + var scale = gateComponent.Length / 5000; + + Handles.matrix = Matrix4x4.identity; Handles.color = Color.white; Handles.DrawWireDisc(position, axis, scale); var col = Color.grey; @@ -123,7 +124,7 @@ protected void OnSceneGUI() const float arrowScale = 0.05f; Handles.color = Color.white; Handles.ArrowHandleCap(-1, position, Quaternion.LookRotation(axis), arrowScale, EventType.Repaint); - var colliderComponent = MainComponent.GetComponent(); + var colliderComponent = gateComponent.GetComponent(); if (colliderComponent && colliderComponent.TwoWay) { Handles.ArrowHandleCap(-1, position, Quaternion.LookRotation(-axis), arrowScale, EventType.Repaint); } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index db73b30c2..99e694ac3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -212,6 +212,75 @@ protected void DropDownProperty(string label, SerializedProperty prop, string[] } } + /// + /// Shows a dropdown for selecting a mesh from an FBX file. The selected mesh will be assigned to the + /// MeshFilter of the given GameObject. It also updates another property based on the selected mesh. + /// + /// Label of the dropdown in the editor + /// Serialized property of the mesh + /// Path to the FBX file + /// GameObject to apply the mesh to + /// Serialized property of the mesh type + /// Map of the type based on the name of the mesh + /// Prefix to filter out non-selectable meshes in the FBX file + protected void MeshDropdownPropertyFbx(string label, SerializedProperty meshProp, string fbxPath, GameObject go, + SerializedProperty typeProp, Dictionary meshTypeMap, string prefix) + { + var allObjects = AssetDatabase.LoadAllAssetRepresentationsAtPath(fbxPath); + var meshObjects = allObjects + .Where(o => o is Mesh) + .Cast() + .Where(o => o.name.StartsWith(prefix)) + .ToArray(); + var meshNames = meshObjects + .Select(o => o.name[prefix.Length..]) + .Concat(new[] { CustomMeshLabel }) + .ToArray(); + + var selectedIndex = meshNames.ToList().IndexOf(meshProp.stringValue); + EditorGUI.BeginChangeCheck(); + var newIndex = EditorGUILayout.Popup(label, selectedIndex, meshNames); + if (EditorGUI.EndChangeCheck() && newIndex >= 0 && newIndex < meshNames.Length && go != null) { + var mf = go.GetComponent(); + var mr = go.GetComponent(); + if (newIndex < meshObjects.Length) { + if (!mf) { + mf = go.AddComponent(); + } + if (!mr) { + go.AddComponent(); + } + var mesh = meshObjects[newIndex]; + mr.enabled = true; + mf.sharedMesh = mesh; + + } else { + if (mr) { + mr.enabled = false; + } + if (mf) { + mf.sharedMesh = null; + } + } + + meshProp.stringValue = meshNames[newIndex]; + if (meshTypeMap.ContainsKey(meshNames[newIndex])) { + typeProp.intValue = meshTypeMap[meshNames[newIndex]]; + } + meshProp.serializedObject.ApplyModifiedProperties(); + if (target is MonoBehaviour mb) { + var colliderComponent = mb.GetComponent(); + if (colliderComponent != null) { + colliderComponent.CollidersDirty = true; + } + } + + if (target is IMainRenderableComponent mrc) { + mrc.UpdateTransforms(); + } + } + } + protected void MeshDropdownProperty(string label, SerializedProperty meshProp, string meshFolder, GameObject go, SerializedProperty typeProp, Dictionary meshTypeMap) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index c3faa2755..d66e0f9f6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -48,7 +48,7 @@ public Vector3 Position { } public float Rotation { - get => transform.localEulerAngles.y; + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; set { var t = transform; var e = t.localEulerAngles; From b17e7be34565f87225e177ec74b29f4674a6d2d0 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 15 Dec 2023 22:50:58 +0100 Subject: [PATCH 057/208] gate: Fix remaining colliders. --- .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 4 +- .../VPT/Gate/GateColliderGenerator.cs | 62 ++++++++++++------- 2 files changed, 41 insertions(+), 25 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index e309e79c2..ea69a5be4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -105,9 +105,9 @@ protected override void CreateColliders(ref ColliderReference colliders, var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, matrix); if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref kinematicColliders); + colliderGenerator.GenerateColliders(ref kinematicColliders); } else { - colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref colliders); + colliderGenerator.GenerateColliders(ref colliders); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 07922ff19..92caa2ac1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -28,6 +28,9 @@ internal class GateColliderGenerator private readonly GateApi _api; private readonly float4x4 _matrix; + private const float ZLow = -2f * PhysicsConstants.PhysSkin; + private const float ZHigh = 0; + internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderData collData, float4x4 matrix) { _api = gateApi; @@ -36,7 +39,7 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat _matrix = matrix; } - internal void GenerateColliders(float height, ref ColliderReference colliders) // var height = table.GetSurfaceHeight(_data.Surface, _data.Center.X, _data.Center.Y); + internal void GenerateColliders(ref ColliderReference colliders) // var height = table.GetSurfaceHeight(_data.Surface, _data.Center.X, _data.Center.Y); { var angleMin = math.min(_collData.AngleMin, _collData.AngleMax); // correct angle inversions var angleMax = math.max(_collData.AngleMin, _collData.AngleMax); @@ -51,31 +54,33 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat GenerateGateCollider(ref colliders); GenerateLineCollider(ref colliders); if (_data.ShowBracket) { - GenerateBracketColliders(ref colliders, height); + GenerateBracketColliders(ref colliders); } } + /// + /// The collider that triggers the animation + /// + /// private void GenerateGateCollider(ref ColliderReference colliders) { // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct // position, we generate them at the origin and then transform them later. - const float halfLength = 50f; - var v1 = new float2( - -(halfLength + PhysicsConstants.PhysSkin), - 0 - ); - var v2 = new float2( - halfLength + PhysicsConstants.PhysSkin, - 0 - ); + const float halfLength = 10f; + var v1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), 0); + var v2 = new float2(halfLength + PhysicsConstants.PhysSkin, 0); - var lineSeg0 = new LineCollider(v1, v2, 0, 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); - var lineSeg1 = new LineCollider(v2, v1, 0, 2f * PhysicsConstants.PhysSkin, _api.GetColliderInfo()); + var lineSeg0 = new LineCollider(v1, v2, ZLow, ZHigh, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, ZLow, ZHigh, _api.GetColliderInfo()); colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); } + /// + /// The collider the blocks the ball, if not two-way + /// + /// private void GenerateLineCollider(ref ColliderReference colliders) { if (_collData.TwoWay) { @@ -83,32 +88,43 @@ private void GenerateLineCollider(ref ColliderReference colliders) } // oversize by the ball's radius to prevent the ball from clipping through - const float halfLength = 50f; + const float halfLength = 10f; var rgv0 = new float2(halfLength + PhysicsConstants.PhysSkin, 0f); - var rgv1 = new float2(-halfLength + PhysicsConstants.PhysSkin, 0f); + var rgv1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), 0f); var info = _api.GetColliderInfo(ItemType.Invalid); // hack to not treat this line seg as gate colliders.AddLine(rgv0, rgv1, -2f * PhysicsConstants.PhysSkin, 0, info, _matrix); //!! = ball diameter } - private void GenerateBracketColliders(ref ColliderReference colliders, float height) + /// + /// The colliders left and right to the other colliders, in case the bracket is shown. + /// + /// + /// + private void GenerateBracketColliders(ref ColliderReference colliders) { - var halfLength = 50f; + const float halfLength = 45f; + var scale = _matrix.GetScale().x; + var height = 2f * PhysicsConstants.PhysSkin / scale; + var zPos = _matrix.GetTranslation().z / scale; + var zLow = zPos - height; + var zHigh = zPos; + colliders.Add(new CircleCollider( new float2(halfLength, 0), 1f, - 0, - 2f * PhysicsConstants.PhysSkin, + zLow, + zHigh, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate - )); + ), _matrix); colliders.Add(new CircleCollider( new float2(-halfLength, 0), 1f, - 0, - 2f * PhysicsConstants.PhysSkin, + zLow, + zHigh, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate - )); + ), _matrix); } } } From f987b83e09b2b14ba5700cb8aee9f9035fc09688 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 16 Dec 2023 23:17:48 +0100 Subject: [PATCH 058/208] ramp: Center pivot after setting data. --- .../VPT/Ramp/RampExtensions.cs | 2 +- .../VisualPinball.Unity/VPT/Ramp/RampComponent.cs | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampExtensions.cs index 3fa0ec153..e470b74c4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampExtensions.cs @@ -23,7 +23,7 @@ public static class RampExtensions { internal static IVpxPrefab InstantiatePrefab(this Ramp ramp) { - var prefab = UnityEngine.Resources.Load("Prefabs/Ramp"); + var prefab = Resources.Load("Prefabs/Ramp"); return new VpxPrefab(prefab, ramp); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 674b59d83..7b9d88170 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -283,6 +283,8 @@ public override IEnumerable SetData(RampData data) updatedComponents.Add(collComponent); } + CenterPivot(); + return updatedComponents; } @@ -392,6 +394,18 @@ public override void CopyFromObject(GameObject go) RebuildMeshes(); } + private void CenterPivot() + { + var centerVpx = DragPoints.Aggregate(Vector3.zero, (current, dragPoint) => current + dragPoint.Center.ToUnityVector3()); + centerVpx /= DragPoints.Length; + + transform.Translate(centerVpx.TranslateToWorld(transform) - transform.position); + foreach (var dragPoint in DragPoints) { + dragPoint.Center -= centerVpx.ToVertex3D(); + } + RebuildMeshes(); + } + #endregion #region Editor Tooling From 77f6f2df07969cb0f1bb3451834e55fe4565118f Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 17 Dec 2023 00:07:16 +0100 Subject: [PATCH 059/208] physics: Move stuff around and document. --- .../VPT/Surface/SurfaceMeshGenerator.cs | 2 +- .../Physics/Collider/ColliderReference.cs | 59 +++++++++++-------- 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs b/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs index 4056b87b9..14c757005 100644 --- a/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs @@ -270,7 +270,7 @@ private Mesh GenerateSideMesh(float playfieldHeight) { offset2 += 4; } - return sideMesh; + return sideMesh; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 7c53addbe..2b938f071 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -196,7 +196,6 @@ internal int Add(GateCollider collider) return collider.Id; } - internal int AddNonTransformable(Line3DCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(Line3DCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(Line3DCollider collider) @@ -244,7 +243,6 @@ internal int Add(PlungerCollider collider) return collider.Id; } - internal int AddNonTransformable(PointCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(PointCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(PointCollider collider) { @@ -265,7 +263,6 @@ internal int Add(SpinnerCollider collider) return collider.Id; } - internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); internal int Add(TriangleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); internal int Add(TriangleCollider collider) { @@ -285,9 +282,21 @@ internal int Add(PlaneCollider collider) return collider.Id; } - #endregion + internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) + { + if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { + var p1 = new float3(v1.xy, zLow); + var p2 = new float3(v1.xy, zHigh); + var p3 = new float3(v2.xy, zLow); + var p4 = new float3(v2.xy, zHigh); - #region Add non-transformable + Add(new TriangleCollider(p1, p3, p2, info).Transform(matrix)); + Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); + + } else { + Add(new LineCollider(v1, v2, zLow, zHigh, info).Transform(matrix)); + } + } internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) { @@ -298,35 +307,35 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl } } + #endregion + + #region Add non-transformable + + // Non-transformable items are collider components that implement ICollidableNonTransformableComponent. + // Colliders of these components are not transformed by the matrix of the parent component (only their + // AABBs are, in order not to break the broad phase). When colliding, it's the ball that is transformed + // to the local space of the collider. + // The physics engine will do this transformation automatically if the state has a non-transformable + // matrix of the collider, which is the case if the component implements ICollidableNonTransformableComponent + // (see PhysicsEngine.Start()) + internal void AddNonTransformableLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - { - Add(new LineZCollider(xy, zLow, zHigh, info).TransformAabb(matrix)); - } + => Add(new LineZCollider(xy, zLow, zHigh, info).TransformAabb(matrix)); internal void AddNonTransformableLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - { - Add(new LineCollider(v1, v2, zLow, zHigh, info).TransformAabb(matrix)); - } + => Add(new LineCollider(v1, v2, zLow, zHigh, info).TransformAabb(matrix)); - internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - { - if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { - var p1 = new float3(v1.xy, zLow); - var p2 = new float3(v1.xy, zHigh); - var p3 = new float3(v2.xy, zLow); - var p4 = new float3(v2.xy, zHigh); + internal int AddNonTransformable(Line3DCollider collider, float4x4 matrix) + => Add(collider.TransformAabb(matrix)); - Add(new TriangleCollider(p1, p3, p2, info).Transform(matrix)); - Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); + internal int AddNonTransformable(PointCollider collider, float4x4 matrix) + => Add(collider.TransformAabb(matrix)); - } else { - Add(new LineCollider(v1, v2, zLow, zHigh, info).Transform(matrix)); - } - } + internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) + => Add(collider.TransformAabb(matrix)); #endregion - public ICollider[] ToArray() { var array = new ICollider[Lookups.Length]; From 4fbe8edc75e2cc95f3b1371602b0bc87b1773523 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 18 Dec 2023 00:34:52 +0100 Subject: [PATCH 060/208] editor: Fix bug when rendering LineZ colliders gizmos. --- .../Physics/Collider/LineZCollider.cs | 24 +++++++++---------- .../VPT/ColliderComponent.cs | 3 +-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 34f86248f..652aa60d4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -31,8 +31,8 @@ public int Id public ColliderHeader Header; public float2 XY; - private float _zLow; - private float _zHigh; + public float ZLow; + public float ZHigh; public float XyY { set { @@ -47,8 +47,8 @@ public LineZCollider(float2 xy, float zLow, float zHigh, ColliderInfo info) : th { Header.Init(info, ColliderType.LineZ); XY = xy; - _zLow = zLow; - _zHigh = zHigh; + ZLow = zLow; + ZHigh = zHigh; CalculateBounds(); } @@ -120,7 +120,7 @@ public static float HitTest(ref CollisionEventData collEvent, in LineZCollider c var hitZ = ball.Position.z + hitTime * ball.Velocity.z; // ball z position at hit time - if (hitZ < coll._zLow || hitZ > coll._zHigh) { + if (hitZ < coll.ZLow || hitZ > coll.ZHigh) { // check z coordinate return -1.0f; } @@ -166,8 +166,8 @@ public LineZCollider Transform(float4x4 matrix) var t = matrix.GetTranslation(); XY += t.xy; - _zLow += t.z; - _zHigh += t.z; + ZLow += t.z; + ZHigh += t.z; CalculateBounds(); return this; @@ -175,8 +175,8 @@ public LineZCollider Transform(float4x4 matrix) public LineZCollider TransformAabb(float4x4 matrix) { - var p1 = matrix.MultiplyPoint(new float3(XY, _zLow)); - var p2 = matrix.MultiplyPoint(new float3(XY, _zHigh)); + var p1 = matrix.MultiplyPoint(new float3(XY, ZLow)); + var p2 = matrix.MultiplyPoint(new float3(XY, ZHigh)); Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); @@ -190,11 +190,11 @@ private void CalculateBounds() XY.x, XY.y, XY.y, - _zLow, - _zHigh + ZLow, + ZHigh )); } - public override string ToString() => $"LineZCollider[{Header.ItemId}] ({XY.x}/{XY.y}) {_zLow} -> {_zHigh}"; + public override string ToString() => $"LineZCollider[{Header.ItemId}] ({XY.x}/{XY.y}) {ZLow} -> {ZHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 7e69cba77..2ff196188 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -361,8 +361,7 @@ private void DrawNonMeshColliders() foreach (var col in _nonMeshColliders) { switch (col) { case LineZCollider lineZCol: { - var aabb = lineZCol.Bounds.Aabb; - DrawLine(lineZCol.XY.ToFloat3(aabb.ZLow), lineZCol.XY.ToFloat3(aabb.ZHigh)); + DrawLine(lineZCol.XY.ToFloat3(lineZCol.ZLow), lineZCol.XY.ToFloat3(lineZCol.ZHigh)); break; } } From be34490580a374cb928ce14777ebf30d30cf4645 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 19 Dec 2023 00:44:06 +0100 Subject: [PATCH 061/208] surface: Center pivot after setting data. --- .../VPT/Surface/SurfaceComponent.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 481b56d2c..736a26c06 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -135,6 +135,8 @@ public override IEnumerable SetData(SurfaceData data) updatedComponents.Add(collComponent); } + CenterPivot(); + return updatedComponents; } @@ -220,6 +222,25 @@ public override void CopyFromObject(GameObject go) RebuildMeshes(); } + private void CenterPivot() + { + // TODO move origin to the top. + // in order to do that, we'll need to treat top and bottom height differently: + // - top height is at local z = 0 + // - changing top height will both transform on local z and change the height of the object + // - changing bottom height will just change the height + // - change mesh and collider creation to create top-down instead of bottom-up. + + var centerVpx = DragPoints.Aggregate(Vector3.zero, (current, dragPoint) => current + dragPoint.Center.ToUnityVector3()); + centerVpx /= DragPoints.Length; + + transform.Translate(centerVpx.TranslateToWorld(transform) - transform.position); + foreach (var dragPoint in DragPoints) { + dragPoint.Center -= centerVpx.ToVertex3D(); + } + RebuildMeshes(); + } + #endregion #region State From 675db1b8a6017af4610db348ec32e5a85276e517 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 20 Dec 2023 00:34:09 +0100 Subject: [PATCH 062/208] plunger: Use transformation for positioning. --- .../VPT/Plunger/PlungerMeshGenerator.cs | 16 ++++++++-------- .../VPT/Plunger/PlungerInspector.cs | 12 +++++++++--- .../VPT/Plunger/PlungerComponent.cs | 15 +++++++++++++-- .../VPT/Plunger/PlungerRodMeshComponent.cs | 2 +- .../VPT/Plunger/PlungerSpringMeshComponent.cs | 2 +- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs b/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs index cffa1815e..d54064934 100644 --- a/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs @@ -116,8 +116,8 @@ public PbrMaterial GetMaterial(Table.Table table) private void Init(float height) { var stroke = _data.Stroke; - _beginY = _data.Center.Y; - _endY = _data.Center.Y - stroke; + _beginY = 0; + _endY = -stroke; NumFrames = (int)(stroke * (float)(PlungerFrameCount / 80.0)) + 1; // 25 frames per 80 units travel _invScale = NumFrames > 1 ? 1.0f / (NumFrames - 1) : 0.0f; _dyPerFrame = (_endY - _beginY) * _invScale; @@ -228,8 +228,8 @@ public Vertex3DNoTex2[] BuildFlatVertices(int frame) // for the current frame. (The 0th frame is the most retracted position; // the cframe-1'th frame is the most forward position.) The base is at // the nominal y position plus m_d.m_height. - var xLt = _data.Center.X - _data.Width; - var xRt = _data.Center.X + _data.Width; + var xLt = -_data.Width; + var xRt = _data.Width; var yTop = _beginY + _dyPerFrame * frame; var yBot = _beginY + _data.Height; @@ -372,7 +372,7 @@ public Vertex3DNoTex2[] BuildRodVertices(int frame) } vertices[i++] = new Vertex3DNoTex2 { - X = r * (sn * _data.Width) + _data.Center.X, + X = r * (sn * _data.Width), Y = y, Z = (r * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, Nx = c.nx * sn, @@ -511,7 +511,7 @@ public Vertex3DNoTex2[] BuildSpringVertices(int frame) // set the point on the front spiral vertices[pm++] = new Vertex3DNoTex2 { - X = _springRadius * (sn * _data.Width) + _data.Center.X, + X = _springRadius * (sn * _data.Width), Y = y - _springGauge, Z = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, Nx = 0.0f, @@ -523,7 +523,7 @@ public Vertex3DNoTex2[] BuildSpringVertices(int frame) // set the point on the top spiral vertices[pm++] = new Vertex3DNoTex2 { - X = (_springRadius + springGaugeRel / 1.5f) * (sn * _data.Width) + _data.Center.X, + X = (_springRadius + springGaugeRel / 1.5f) * (sn * _data.Width), Y = y, Z = ((_springRadius + springGaugeRel / 1.5f) * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, @@ -536,7 +536,7 @@ public Vertex3DNoTex2[] BuildSpringVertices(int frame) // set the point on the back spiral vertices[pm++] = new Vertex3DNoTex2 { - X = _springRadius * (sn * _data.Width) + _data.Center.X, + X = _springRadius * (sn * _data.Width), Y = y + _springGauge, Z = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, Nx = 0.0f, diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs index 443152b59..af8df1df6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs @@ -17,6 +17,7 @@ // ReSharper disable AssignmentInConditionalExpression using UnityEditor; +using UnityEngine; using VisualPinball.Engine.VPT.Plunger; namespace VisualPinball.Unity.Editor @@ -24,7 +25,6 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(PlungerComponent)), CanEditMultipleObjects] public class PlungerInspector : MainInspector { - private SerializedProperty _positionProperty; private SerializedProperty _widthProperty; private SerializedProperty _heightProperty; private SerializedProperty _zAdjustProperty; @@ -34,7 +34,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(PlungerComponent.Position)); _widthProperty = serializedObject.FindProperty(nameof(PlungerComponent.Width)); _heightProperty = serializedObject.FindProperty(nameof(PlungerComponent.Height)); _zAdjustProperty = serializedObject.FindProperty(nameof(PlungerComponent.ZAdjust)); @@ -51,7 +50,14 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, rebuildMesh: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "The position of the plunger on the playfield."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Plunger Position"); + MainComponent.Position = newPos; + } + PropertyField(_widthProperty, rebuildMesh: true); PropertyField(_heightProperty, rebuildMesh: true); PropertyField(_zAdjustProperty, rebuildMesh: true); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 6bee71fe9..209ecc7b9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -36,8 +36,19 @@ public class PlungerComponent : MainRenderableComponent, { #region Data - [Tooltip("The position of the plunger on the playfield.")] - public Vector2 Position; + public Vector2 Position { + get { + var pos = transform.localPosition; + var posVpx = pos.TranslateToVpx(); + return new Vector2(posVpx.x, posVpx.y); + } + set { + var posVpx = new Vector3(value.x, value.y, 0); + var pos = posVpx.TranslateToWorld(); + var t = transform; + t.localPosition = new Vector3(pos.x, t.localPosition.y, pos.z); + } + } public float Width = 25f; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs index 2d248b107..d12d0a654 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs @@ -64,7 +64,7 @@ public void CalculateBoundingBox() var bounds = smr.localBounds; var ringOffset = (RingGap + RingWidth) / 2f; var radius = math.max(RodDiam, RingDiam) * plungerComp.Width / 2; - bounds.center = new Vector3(plungerComp.Position.x, plungerComp.Position.y + ringOffset - 40, 45); + bounds.center = new Vector3(0, ringOffset - 40, 45); bounds.extents = new Vector3(radius, 125f + ringOffset, radius); smr.localBounds = bounds; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs index e64e3557c..73553806d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs @@ -60,7 +60,7 @@ public void CalculateBoundingBox() var bounds = smr.localBounds; var ringOffset = rodComp != null ? rodComp.RingGap + rodComp.RingWidth : 0f; var radius = plungerComp.Width / 2 * SpringDiam + 2f; - bounds.center = new Vector3(plungerComp.Position.x, plungerComp.Position.y + ringOffset - 25, 45); + bounds.center = new Vector3(0, ringOffset - 25, 45); bounds.extents = new Vector3(radius, 110f, radius); smr.localBounds = bounds; } From 5978ddcaa43e58b0fe7e312e0ac345f095c6bec3 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Dec 2023 00:27:35 +0100 Subject: [PATCH 063/208] plunger: Add new interfaces. --- .../VPT/Plunger/PlungerColliderInspector.cs | 3 ++ .../VPT/Plunger/PlungerApi.cs | 6 +++- .../VPT/Plunger/PlungerCollider.cs | 30 +++++++++++++++++-- .../VPT/Plunger/PlungerColliderComponent.cs | 18 ++++++++++- .../VPT/Plunger/PlungerComponent.cs | 11 +++++++ 5 files changed, 64 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs index 3e42fbbce..b097ddeb6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs @@ -24,6 +24,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(PlungerColliderComponent)), CanEditMultipleObjects] public class PlungerColliderInspector : ColliderInspector { + private SerializedProperty _isKinematicProperty; private SerializedProperty _speedPullProperty; private SerializedProperty _speedFireProperty; private SerializedProperty _strokeProperty; @@ -37,6 +38,7 @@ public class PlungerColliderInspector : ColliderInspector. -using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT.Plunger; @@ -40,6 +39,10 @@ public int Id public LineZCollider JointBase0; public LineZCollider JointBase1; + private float3 _pos; + private float2 _size; + private float _stroke; + public ColliderBounds Bounds { get; private set; } public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, ColliderInfo info) : this() @@ -51,6 +54,10 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, var y = comp.Position.y + comp.Height; var x2 = comp.Position.x + comp.Width; + _pos = new float3(comp.Position.x, comp.Position.y, comp.PositionZ); + _size = new float2(comp.Width, comp.Height); + _stroke = collComp.Stroke; + // static LineSegBase = new LineCollider(new float2(x, y), new float2(x2, y), zHeight, zHeight + Plunger.PlungerHeight, info); JointBase0 = new LineZCollider(new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info); @@ -67,6 +74,25 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, )); } + public PlungerCollider TransformAabb(float4x4 matrix) + { + var zHeight = _pos.z; + var x = _pos.x - _size.x; + var y = _pos.y + _size.y; + var x2 = _pos.x + _size.x; + var frameEnd = _pos.y - _stroke; + + var min = new float3(x - 0.1f, frameEnd - 0.1f, zHeight); + var max = new float3(x2 + 0.1f, y + 0.1f, zHeight + Plunger.PlungerHeight); + + var p1 = matrix.MultiplyPoint(min); + var p2 = matrix.MultiplyPoint(max); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); + + return this; + } + #region Narrowphase public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, @@ -315,6 +341,6 @@ public static void Collide(ref BallState ball, ref CollisionEventData collEvent, #endregion - public override string ToString() => $"LineSlingshotCollider[{Header.ItemId}] {LineSegBase.ToString()} | {JointBase0.ToString()} | {JointBase1.ToString()}"; + public override string ToString() => $"PlungerCollider[{Header.ItemId}] {LineSegBase.ToString()} | {JointBase0.ToString()} | {JointBase1.ToString()}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index cdb441d81..99b23d0e4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -16,13 +16,14 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Plunger; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Plunger Collider")] - public class PlungerColliderComponent : ColliderComponent + public class PlungerColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent { #region Data @@ -52,10 +53,25 @@ public class PlungerColliderComponent : ColliderComponent GetPhysicsMaterialData(); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.PlungerApi ?? new PlungerApi(gameObject, player, physicsEngine); + + #region IKinematicColliderComponent + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + + float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 209ecc7b9..a14a64bc9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -99,6 +99,9 @@ private void Awake() } } + public float4x4 TransformationWithinPlayfield + => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + #endregion #region Wiring @@ -116,6 +119,9 @@ private void Awake() #region Transformation + [NonSerialized] + private float4x4 _playfieldToWorld; + public void OnSurfaceUpdated() => RebuildMeshes(); public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); @@ -132,6 +138,11 @@ public override void UpdateTransforms() GetComponent()?.CalculateBoundingBox(); } + private void Start() + { + _playfieldToWorld = Player.PlayfieldToWorldMatrix; + } + #endregion #region Conversion From 20236f12746b2e5432421b2855a2f43e0f361941 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 8 Nov 2024 22:42:42 +0100 Subject: [PATCH 064/208] fix: Remove obsolete disposals. --- VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs | 2 -- VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs | 2 -- 2 files changed, 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs index f04de9ee2..7d7d2f2d3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs @@ -174,8 +174,6 @@ internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref Nati state.SwapBallCollisionHandling = !state.SwapBallCollisionHandling; } - ballOctree.Dispose(); - PerfMarker.End(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index adee33129..997290fb3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -446,8 +446,6 @@ private void Update() } #endregion - - overlappingColliders.Dispose(); } private void OnDestroy() From a1c7ea7c23e0ae8172aa4c6659eb32e2cf2dba49 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 9 Nov 2024 16:56:18 +0100 Subject: [PATCH 065/208] plunger: Make transformable part 1. --- .../VPT/Ball/BallComponent.cs | 14 +++--- .../VPT/Bumper/BumperApi.cs | 15 ++---- .../VPT/ColliderComponent.cs | 2 +- .../VPT/Plunger/PlungerApi.cs | 2 +- .../VPT/Plunger/PlungerCollider.cs | 46 +++++++++---------- .../VPT/Plunger/PlungerColliderComponent.cs | 4 +- .../VPT/Plunger/PlungerComponent.cs | 25 ++++++++-- .../VisualPinball.Unity/VPT/Ramp/RampApi.cs | 2 +- .../VPT/Rubber/RubberApi.cs | 2 +- .../VPT/Surface/SurfaceApi.cs | 2 +- .../VPT/Surface/SurfaceComponent.cs | 1 - 11 files changed, 61 insertions(+), 54 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs index 3330d4705..a8daa38d7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs @@ -74,13 +74,13 @@ private void DrawPhysicsDebug(UnityEditor.SceneView sceneView) 0.01f ); - // hit hormal - DrawArrow( - _playfieldToWorld.MultiplyPoint(ballState.Position.TranslateToWorld()), - _playfieldToWorld.MultiplyVector((ballState.CollisionEvent.HitNormal * 100).TranslateToWorld()), - ballState.CollisionEvent.HitFlag ? Color.red : Color.yellow, - 0.01f - ); + // hit normal + // DrawArrow( + // _playfieldToWorld.MultiplyPoint(ballState.Position.TranslateToWorld()), + // _playfieldToWorld.MultiplyVector((ballState.CollisionEvent.HitNormal * 100).TranslateToWorld()), + // ballState.CollisionEvent.HitFlag ? Color.red : Color.yellow, + // 0.01f + // ); } private static void DrawArrow(Vector3 pos, Vector3 direction, Color color, float arrowHeadLength = 0.025f, float arrowHeadAngle = 20.0f) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 0e601443e..eebde3782 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -15,15 +15,10 @@ // along with this program. If not, see . using System; +using System.Collections.Generic; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Bumper; -using System.Collections.Generic; -using Unity.Mathematics; -using static UnityEngine.UI.Scrollbar; -using VisualPinball.Engine.PinMAME.MPUs; -using VisualPinball.Engine.VPT; -using JetBrains.Annotations; namespace VisualPinball.Unity { @@ -120,11 +115,9 @@ protected override void CreateColliders(ref ColliderReference colliders, { var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var height = MainComponent.PositionZ; - var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, - height + 100f, GetColliderInfo(), ColliderType.Bumper); - var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, - height + 100f, GetColliderInfo(), ColliderType.Circle); - if (ColliderComponent.IsKinematic) { + var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); + var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); + if (ColliderComponent._isKinematic) { switchColliderId = kinematicColliders.Add(switchCollider, matrix); kinematicColliders.Add(rigidCollider, matrix); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 2ff196188..959ae3e09 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -179,7 +179,7 @@ private void OnDrawGizmos() } if (ShowAabbs) { - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; for (var i = 0; i < colliders.Count; i++) { var col = colliders[i]; DrawAabb(col.Bounds.Aabb, i == SelectedCollider); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 3da32220c..098db9aac 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -146,7 +146,7 @@ private IApiCoil Coil(string deviceItem) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - if (ColliderComponent.IsKinematic) { + if (ColliderComponent._isKinematic) { kinematicColliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()).TransformAabb(translateWithinPlayfieldMatrix)); } else { colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()).TransformAabb(translateWithinPlayfieldMatrix)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index 2cf723b92..e1c5fc72d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -15,8 +15,10 @@ // along with this program. If not, see . using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT.Plunger; +using Random = Unity.Mathematics.Random; namespace VisualPinball.Unity { @@ -39,9 +41,8 @@ public int Id public LineZCollider JointBase0; public LineZCollider JointBase1; - private float3 _pos; - private float2 _size; - private float _stroke; + private readonly float2 _size; + private readonly float _stroke; public ColliderBounds Bounds { get; private set; } @@ -50,11 +51,10 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, Header.Init(info, ColliderType.Plunger); var zHeight = comp.PositionZ; - var x = comp.Position.x - comp.Width; - var y = comp.Position.y + comp.Height; - var x2 = comp.Position.x + comp.Width; + var x = -comp.Width; + var x2 = comp.Width; + var y = comp.Height; - _pos = new float3(comp.Position.x, comp.Position.y, comp.PositionZ); _size = new float2(comp.Width, comp.Height); _stroke = collComp.Stroke; @@ -63,32 +63,30 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, JointBase0 = new LineZCollider(new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info); JointBase1 = new LineZCollider(new float2(x2, y), zHeight, zHeight + Plunger.PlungerHeight, info); - var frameEnd = comp.Position.y - collComp.Stroke; - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( - x - 0.1f, - x2 + 0.1f, - frameEnd - 0.1f, - y + 0.1f, - zHeight, - zHeight + Plunger.PlungerHeight - )); + TransformAabb(float4x4.identity); + // Debug.Log($"Initial bounds: {Bounds}"); } public PlungerCollider TransformAabb(float4x4 matrix) { - var zHeight = _pos.z; - var x = _pos.x - _size.x; - var y = _pos.y + _size.y; - var x2 = _pos.x + _size.x; - var frameEnd = _pos.y - _stroke; + var x = -_size.x; + var x2 = _size.x; + var y = _size.y; + var frameEnd = -_stroke; - var min = new float3(x - 0.1f, frameEnd - 0.1f, zHeight); - var max = new float3(x2 + 0.1f, y + 0.1f, zHeight + Plunger.PlungerHeight); + var min = new float3(x - 0.1f, frameEnd - 0.1f, 0); + var max = new float3(x2 + 0.1f, y + 0.1f, Plunger.PlungerHeight); var p1 = matrix.MultiplyPoint(min); var p2 = matrix.MultiplyPoint(max); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); + var aabb = new Aabb(math.min(p1, p2), math.max(p1, p2)); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb); + // Debug.Log($"pos: {matrix.GetTranslation()}"); + // Debug.Log($"rot: {matrix.GetRotationVector()}"); + // Debug.Log($"scale: {matrix.GetScale()}"); + // Debug.Log($"Transformed bounds: {Bounds}"); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index 99b23d0e4..a3940d500 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -68,7 +68,9 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public int ItemId => MainComponent.gameObject.GetInstanceID(); float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); +// => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index a14a64bc9..2a3ac32e4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -99,6 +99,8 @@ private void Awake() } } + // public float4x4 TransformationWithinPlayfield + // => math.mul(Physics.VpxToWorld, transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld)); public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); @@ -143,6 +145,19 @@ private void Start() _playfieldToWorld = Player.PlayfieldToWorldMatrix; } + public float4x4 LocalToWorldPhysicsMatrix + { + get + { + return float4x4.Translate(transform.localPosition); + return float4x4.identity; + // var t = transform; + // var m = t.localToWorldMatrix; + // var r = t.localRotation.eulerAngles; + // return math.mul(m, math.inverse(float4x4.RotateY(math.radians(r.y)))); + } + } + #endregion #region Conversion @@ -308,12 +323,12 @@ internal PlungerState CreateState() } var zHeight = PositionZ; - var x = Position.x - Width; - var y = Position.y + Height; - var x2 = Position.x + Width; + var x = -Width; + var x2 = Width; + var y = Height; - var frameTop = Position.y - collComponent.Stroke; - var frameBottom = Position.y; + var frameTop = -collComponent.Stroke; + var frameBottom = 0; var frameLen = frameBottom - frameTop; var restPos = collComponent.ParkPosition; var position = frameTop + restPos * frameLen; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index 699c7c5b1..bcc4425ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -66,7 +66,7 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); - if (ColliderComponent.IsKinematic) { + if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); } else { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index 914572665..990b013a2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -52,7 +52,7 @@ protected override void CreateColliders(ref ColliderReference colliders, new RubberMeshGenerator(MainComponent), GetTransformationWithinPlayfield() ); - if (ColliderComponent.IsKinematic) { + if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); } else { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index d07ad036f..c78d51505 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -55,7 +55,7 @@ protected override void CreateColliders(ref ColliderReference colliders, return; } var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); - if (ColliderComponent.IsKinematic) { + if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 736a26c06..885ff3bdc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -100,7 +100,6 @@ private void Start() public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - #endregion #region Conversion From 48b812410887ba7447c43024037fb37df39bcd5c Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 9 Nov 2024 20:44:20 +0100 Subject: [PATCH 066/208] bumper: Make non-transformable. --- .../Physics/Collider/CircleCollider.cs | 63 ++++++++++++------- .../Physics/Collider/ColliderReference.cs | 3 + .../VPT/Bumper/BumperApi.cs | 8 +-- .../VPT/Bumper/BumperColliderComponent.cs | 5 +- .../VPT/ColliderComponent.cs | 4 +- 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 43389ebdc..abd852b8f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -30,28 +30,30 @@ public int Id public ColliderHeader Header; - public float2 Center; + public float2 Center; // todo remove and assume float2.zero public float Radius; - private float _zHigh; - private float _zLow; + public float ZHigh; + public float ZLow; - public ColliderBounds Bounds => new(Header.ItemId, Header.Id, new Aabb( - Center.x - Radius, - Center.x + Radius, - Center.y - Radius, - Center.y + Radius, - _zLow, - _zHigh - )); + public ColliderBounds Bounds { get; private set; } public CircleCollider(float2 center, float radius, float zLow, float zHigh, ColliderInfo info, ColliderType type = ColliderType.Circle) : this() { Header.Init(info, type); Center = center; Radius = radius; - _zHigh = zHigh; - _zLow = zLow; + ZHigh = zHigh; + ZLow = zLow; + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + Center.x - Radius, + Center.x + Radius, + Center.y - Radius, + Center.y + Radius, + ZLow, + ZHigh + )); } #region Narrowphase @@ -73,14 +75,14 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref InsideOfs var dist = ball.Position - c; // relative ball position var dv = ball.Velocity; - var capsule3D = !lateral && ball.Position.z > _zHigh; + var capsule3D = !lateral && ball.Position.z > ZHigh; var isKicker = Header.ItemType == ItemType.Kicker; var isKickerOrTrigger = Header.ItemType == ItemType.Trigger || Header.ItemType == ItemType.Kicker; float targetRadius; if (capsule3D) { targetRadius = Radius * (float) (13.0 / 5.0); - c.z = _zHigh - Radius * (float) (12.0 / 5.0); + c.z = ZHigh - Radius * (float) (12.0 / 5.0); dist.z = ball.Position.z - c.z; // ball rolling point - capsule center height } else { @@ -174,9 +176,9 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref InsideOfs } var hitZ = ball.Position.z + ball.Velocity.z * hitTime; // rolling point - if (hitZ + ball.Radius * 0.5 < _zLow - || !capsule3D && hitZ - ball.Radius * 0.5 > _zHigh - || capsule3D && hitZ < _zHigh) { + if (hitZ + ball.Radius * 0.5 < ZLow + || !capsule3D && hitZ - ball.Radius * 0.5 > ZHigh + || capsule3D && hitZ < ZHigh) { return -1.0f; } @@ -227,8 +229,8 @@ public void Transform(CircleCollider circle, float4x4 matrix) var s = matrix.GetScale(); Center = matrix.MultiplyPoint(new float3(circle.Center, 0)).xy; Radius = circle.Radius * s.x; - _zHigh = circle._zHigh * s.z; - _zLow = circle._zLow * s.z; + ZHigh = circle.ZHigh * s.z; + ZLow = circle.ZLow * s.z; } public CircleCollider Transform(float4x4 matrix) @@ -237,6 +239,25 @@ public CircleCollider Transform(float4x4 matrix) return this; } - public override string ToString() => $"CircleCollider[{Header.ItemId}] ({Center.x}/{Center.y}) {_zLow} -> {_zHigh}"; + public CircleCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(new float3( Radius, Radius, ZLow)); + var p2 = matrix.MultiplyPoint(new float3( Radius, -Radius, ZLow)); + var p3 = matrix.MultiplyPoint(new float3(-Radius, Radius, ZLow)); + var p4 = matrix.MultiplyPoint(new float3(-Radius, -Radius, ZLow)); + var p5 = matrix.MultiplyPoint(new float3( Radius, Radius, ZHigh)); + var p6 = matrix.MultiplyPoint(new float3( Radius, -Radius, ZHigh)); + var p7 = matrix.MultiplyPoint(new float3(-Radius, Radius, ZHigh)); + var p8 = matrix.MultiplyPoint(new float3(-Radius, -Radius, ZHigh)); + + var min = math.min(p1, math.min(p2, math.min(p3, math.min(p4, math.min(p5, math.min(p6, math.min(p7, p8))))))); + var max = math.max(p1, math.max(p2, math.max(p3, math.max(p4, math.max(p5, math.max(p6, math.max(p7, p8))))))); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + + return this; + } + + public override string ToString() => $"CircleCollider[{Header.ItemId}] ({Center.x}/{Center.y}) {ZLow} -> {ZHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 2b938f071..72abfca24 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -334,6 +334,9 @@ internal int AddNonTransformable(PointCollider collider, float4x4 matrix) internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); + internal int AddNonTransformable(CircleCollider collider, float4x4 matrix) + => Add(collider.TransformAabb(matrix)); + #endregion public ICollider[] ToArray() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index eebde3782..934c67cc1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -118,11 +118,11 @@ protected override void CreateColliders(ref ColliderReference colliders, var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent._isKinematic) { - switchColliderId = kinematicColliders.Add(switchCollider, matrix); - kinematicColliders.Add(rigidCollider, matrix); + switchColliderId = kinematicColliders.AddNonTransformable(switchCollider, matrix); + kinematicColliders.AddNonTransformable(rigidCollider, matrix); } else { - switchColliderId = colliders.Add(switchCollider, matrix); - colliders.Add(rigidCollider, matrix); + switchColliderId = colliders.AddNonTransformable(switchCollider, matrix); + colliders.AddNonTransformable(rigidCollider, matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 12881d017..21fc722ae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Bumper Collider")] - public class BumperColliderComponent : ColliderComponent, IKinematicColliderComponent + public class BumperColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent { #region Data @@ -53,6 +53,9 @@ public class BumperColliderComponent : ColliderComponent MainComponent.gameObject.GetInstanceID(); public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; + float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + #endregion protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 959ae3e09..4c2e35230 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -379,8 +379,8 @@ private static void AddCollider(CircleCollider circleCol, IList vertice var pos = new Vector3(circleCol.Center.x, circleCol.Center.y, 0); // Make first side. - vertices.Add(rotation * new Vector3(circleCol.Radius, 0f, aabb.ZHigh) + pos); // tr - vertices.Add(rotation * new Vector3(circleCol.Radius, 0f, aabb.ZLow) + pos); // bl + vertices.Add(rotation * new Vector3(circleCol.Radius, 0f, circleCol.ZHigh) + pos); // tr + vertices.Add(rotation * new Vector3(circleCol.Radius, 0f, circleCol.ZLow) + pos); // bl vertices.Add(rotation * (vertices[vertices.Count - 1] - pos) + pos); // br vertices.Add(rotation * (vertices[vertices.Count - 3] - pos) + pos); // tl From 4fdd77360e41acbade43784aced23d9691699bff Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 9 Nov 2024 23:57:56 +0100 Subject: [PATCH 067/208] bumpers: Re-enable ring animation. --- .../VPT/Bumper/BumperApi.cs | 61 ++++++++++--------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 934c67cc1..847af7d7d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -45,12 +45,10 @@ public class BumperApi : CollidableApi public event EventHandler Switch; - private readonly PhysicsEngine _physicsEngine; - private int switchColliderId; + private int _switchColliderId; public BumperApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { - _physicsEngine = physicsEngine; } #region Wiring @@ -71,33 +69,35 @@ void IApiCoil.OnCoil(bool enabled) } ref var bumperState = ref PhysicsEngine.BumperState(ItemId); bumperState.RingAnimation.IsHit = true; + ref var insideOfs = ref PhysicsEngine.InsideOfs; - List idsOfBallsInColl = insideOfs.GetIdsOfBallsInsideItem(ItemId); + var idsOfBallsInColl = insideOfs.GetIdsOfBallsInsideItem(ItemId); foreach (var ballId in idsOfBallsInColl) { - if (PhysicsEngine.Balls.ContainsKey(ballId)) { - ref var ballState = ref PhysicsEngine.BallState(ballId); - float3 bumperPos = new(MainComponent.Position.x, MainComponent.Position.y, MainComponent.PositionZ); - float3 ballPos = ballState.Position; - var bumpDirection = ballPos - bumperPos; - bumpDirection.z = 0f; - bumpDirection = math.normalize(bumpDirection); - var collEvent = new CollisionEventData { - HitTime = 0f, - HitNormal = bumpDirection, - HitVelocity = new float2(bumpDirection.x, bumpDirection.y) * ColliderComponent.Force, - HitDistance = 0f, - HitFlag = false, - HitOrgNormalVelocity = math.dot(bumpDirection, math.normalize(ballState.Velocity)), - IsContact = true, - ColliderId = switchColliderId, - IsKinematic = false, - BallId = ballId - }; - var physicsMaterialData = ColliderComponent.PhysicsMaterialData; - var random = PhysicsEngine.Random; - BallCollider.Collide3DWall(ref ballState, in physicsMaterialData, in collEvent, in bumpDirection, ref random); - ballState.Velocity += bumpDirection * ColliderComponent.Force; + if (!PhysicsEngine.Balls.ContainsKey(ballId)) { + continue; } + ref var ballState = ref PhysicsEngine.BallState(ballId); + float3 bumperPos = new(MainComponent.Position.x, MainComponent.Position.y, MainComponent.PositionZ); + float3 ballPos = ballState.Position; + var bumpDirection = ballPos - bumperPos; + bumpDirection.z = 0f; + bumpDirection = math.normalize(bumpDirection); + var collEvent = new CollisionEventData { + HitTime = 0f, + HitNormal = bumpDirection, + HitVelocity = new float2(bumpDirection.x, bumpDirection.y) * ColliderComponent.Force, + HitDistance = 0f, + HitFlag = false, + HitOrgNormalVelocity = math.dot(bumpDirection, math.normalize(ballState.Velocity)), + IsContact = true, + ColliderId = _switchColliderId, + IsKinematic = false, + BallId = ballId + }; + var physicsMaterialData = ColliderComponent.PhysicsMaterialData; + var random = PhysicsEngine.Random; + BallCollider.Collide3DWall(ref ballState, in physicsMaterialData, in collEvent, in bumpDirection, ref random); + ballState.Velocity += bumpDirection * ColliderComponent.Force; } } @@ -118,10 +118,10 @@ protected override void CreateColliders(ref ColliderReference colliders, var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent._isKinematic) { - switchColliderId = kinematicColliders.AddNonTransformable(switchCollider, matrix); + _switchColliderId = kinematicColliders.AddNonTransformable(switchCollider, matrix); kinematicColliders.AddNonTransformable(rigidCollider, matrix); } else { - switchColliderId = colliders.AddNonTransformable(switchCollider, matrix); + _switchColliderId = colliders.AddNonTransformable(switchCollider, matrix); colliders.AddNonTransformable(rigidCollider, matrix); } } @@ -142,7 +142,7 @@ void IApi.OnDestroy() void IApiHittable.OnHit(int ballId, bool isUnHit) { - ref var insideOfs = ref _physicsEngine.InsideOfs; + ref var insideOfs = ref PhysicsEngine.InsideOfs; if (isUnHit) { UnHit?.Invoke(this, new HitEventArgs(ballId)); if (insideOfs.IsEmpty(ItemId)) { // Last ball just left @@ -154,6 +154,7 @@ void IApiHittable.OnHit(int ballId, bool isUnHit) if (insideOfs.GetInsideCount(ItemId) == 1) { // Must've been empty before ref var bumperState = ref PhysicsEngine.BumperState(ItemId); bumperState.SkirtAnimation.HitEvent = true; + bumperState.RingAnimation.IsHit = true; ref var ballState = ref PhysicsEngine.BallState(ballId); bumperState.SkirtAnimation.BallPosition = ballState.Position; Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); From 35ddaf556aeafca91af2edc1a0032dac365170f1 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 10 Nov 2024 23:14:00 +0100 Subject: [PATCH 068/208] doc: Start writing doc about transformations. --- .../VisualPinball.Unity/Physics/README.md | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index fa705b46e..1e556e53c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -117,3 +117,128 @@ The only dynamic collision in Visual Pinball is collision between balls. For this, another KD-Tree is re-created on each physics cycle. Then, during phase 3.1, balls are not only checked against static objects, but also against other balls. + +# VPE Physics + +While VPE uses the same formulas and heuristics to resolve collisions and simulate +behavior as VPX, we've made some changes to make it more flexible. Here's a quick +overview. + +- **Kinematic Objects**: As described above, the only objects that can be freely + transformed are the balls, all other objects are static. In VPE, you can mark + any object as kinematic, which means it can be moved freely at runtime. +- **Unrestricted Transformations**: In VPX, every type of object has its limitations + how it can be transformed. For example a flipper must always be parallel to the + playfield. In VPE, you can rotate, move and scale objects freely. +- **World Space**: VPX defines its own way of measuring units. It defines a unit as + 50th of the diameter of a ball (1 VP unit = 0.02125" = 0.53975mm). VPE uses real + world units (meters) and converts them to VP units at runtime for the physics + simulation only. This means we get the advantages of real world units, but can still + rely on the heuristics of the physics engine that has been tuned over the years. + +> [!NOTE] +> We don't use the term *Hit Object* in VPE. We call them *Colliders*. + +## Unrestricted Transformations + +The key to transforming objects lies in how their transformations are stored. In +VPX, due to its restrictions, transformations are generally stored as separate +values. In Unity (and game engines in general), transformations are stored as +matrices. Instead of converting between the transformations that the physics +engine understands and the transformation matrix — and thereby restricting the +editor's transformation tools to those limitations — we decided to rely exclusively +on the transformation matrix. This approach allows complete freedom in transforming +objects. + +It also enables VPE to support parenting objects to others, allowing +transformations of parent objects without disrupting the physics simulation. + +So, how can the VPX physics code handle arbitrary transformations? The solution is a +simple trick: rather than transforming the colliders, we +transform the balls: To resolve a collision between a ball and a collider that +is transformed in a non-supported way, we temporarily move the ball into the +collider's local space, resolve the collision, and then return the ball to the +playfield space. + +### Collider Data + +Our colliders don't and won't know about our transformation matrices, i.e. the +data used to calculate the simulation is still the same as in VPX. So, we need to +get this data from the transformation matrices into the colliders. The chosen +approach is the following: + +- Each collider gets a `Transform(float4x4)` method that transforms the collider + in VPX space. Transforming means updating the collider's position, rotation, and + scale, whatever is supported. That's the data the VPX physics code uses. +- Obviously this has flaws. For example, a Line collider, by definition, is + aligned parallel and orthogonally to the playfield. In this case we'd transform + its two points and retrieve their new xy position. This works for translate and scale, + but rotating around anything else than the z-axis would still result in a rectangle + parallel and orthogonal to the playfield, which wouldn't be desired. +- But more on that problem later. What's important is that for transformations + *supported by the VPX physics code*, we have a method that allows to transform + each collider. +- Additionally, colliders don't take in a transformation matrix. That means by default, + they are placed at the origin and have no rotation or scale. +- Finally, each collider gets a `TransformAABBs(float4x4)` method that only transforms + the collider's axis-aligned bounding boxes. + +So, with all of the above, we do the following when the game starts: + +1. We retrieve the overall world-to-local matrix of an item. +2. Using the playfield's world-to-local matrix, we calculate the playfield-to-local matrix + of the item. +3. We check which kind of transformation the VPX physics code supports for this type of + item and compare it to the transformation of playfield-to-local matrix. + - If all transformations are supported, we simply transform the collider with the item's + transformation matrix (let's call this Plan A). + - If not, we check whether this collider might be replaceable by another type of collider + that supports the transformation. For example, a line collider can be replaced by two + triangle colliders, which then are 100% transformable. That's our Plan B. + - If neither Plan A nor Plan B is possible, we fall back to our trick explained above, + which is to transform the ball during collision resolution. Note that the AABBs of the + object still need to be transformed, so the broad phase can correctly sort out the + items that are out of range. + +This relatively simple approach gives us an incredible amount of flexibility. We now have a +true 3D physics engine that can handle any kind of transformation, including parenting, while +still being able to rely on the heuristics of the VPX physics code that has been tuned over +the years. + +### Code Changes + +Let's dive into the code. We'll need to more methods for each collider: + +1. `Transform(float4x4)`: This method transforms the collider in VPX space. +2. `TransformAABBs(float4x4)`: This method transforms the collider's axis-aligned bounding boxes. + +In our `ColliderReference` class, we'll add a `float4x4` to each `Add()` method, +which transforms the collider and its AABBs. + +> [!IMPORTANT] +> Remove the overload that doesn't need the matrix once all collider generators have been updated. + +We'll also have a `AddNonTransformable()` method which only transforms tha AABBs and marks +the collider as non-transformable, so we can do the ball transformation trick during collision +later. In order to know whether to do that, we'll need a flag in the collider. Let's call it +`IsTransformed`. + +In order to transform the ball, we need to also store the transformation matrix of the item. However, +while the `IsTransformed` flag is collider-specific, the transformation matrix is item-specific. + +## Coordinate Systems + +Give the above, we'll be transforming between multiple coordinate systems, or *spaces*: + +- **World Space**: This is the space in which the game objects are defined. It's also the + space in which you should model your assets. +- **Playfield Space** (or VPX space): This is the space in which the physics engine operates. +- **Local Space**: This is the space of the collider. For elements that are transformed in a way + not supported by the physics engine, we'll move the ball into this space to resolve the collision. + +## Kinematic Objects + +Kinematic objects need to be enabled manually. + +> [!NOTE] +> Maybe, in the future, we could leverage Unity's "static" flag for this. From 22c3cbef659d33e55f01892321566e2b12600e38 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 11 Nov 2024 21:23:07 +0100 Subject: [PATCH 069/208] physics: Move IsTransformed from ICollidableNonTransformableComponent to collider and setup circle collider. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 10 +- .../Game/PhysicsStaticCollision.cs | 5 +- .../Game/PhysicsStaticNarrowPhase.cs | 3 +- .../Physics/Collider/CircleCollider.cs | 4 +- .../Physics/Collider/ColliderReference.cs | 51 ++++- .../Physics/Collision/ColliderHeader.cs | 7 + .../Physics/Collision/ContactPhysics.cs | 4 +- .../Physics/NativeColliders.cs | 3 +- .../VPT/Bumper/BumperApi.cs | 8 +- .../VPT/ColliderComponent.cs | 174 +++++++++++------- 10 files changed, 185 insertions(+), 84 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 997290fb3..67fe1e337 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -74,12 +74,12 @@ public class PhysicsEngine : MonoBehaviour /// /// Last transforms of kinematic items, so we can detect changes. /// - [NonSerialized] private LazyInit> _kinematicTransforms = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _kinematicTransforms = new(() => new(0, Allocator.Persistent)); /// /// The transforms of the kinematic items that have changes since the last frame. /// - [NonSerialized] private LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); /// /// The current matrix to the ball will be transformed to, if it collides with a non-transformable collider. @@ -88,7 +88,7 @@ public class PhysicsEngine : MonoBehaviour /// /// todo save inverse matrix, too /// - [NonSerialized] private LazyInit> _nonTransformableColliderMatrices = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _nonTransformableColliderMatrices = new(() => new(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); [NonSerialized] private readonly Dictionary _rotatableComponent = new(); @@ -211,8 +211,8 @@ private void Start() var colliderItems = GetComponentsInChildren(); Debug.Log($"Found {colliderItems.Length} collidable items."); - var colliders = new ColliderReference(Allocator.Temp); - var kinematicColliders = new ColliderReference(Allocator.Temp, true); + var colliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp); + var kinematicColliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp, true); foreach (var colliderItem in colliderItems) { if (!colliderItem.IsCollidable) { _disabledCollisionItems.Ref.Add(colliderItem.ItemId); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 3ee1c1afa..9d774072c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -16,6 +16,7 @@ // ReSharper disable ConvertIfStatementToSwitchStatement +using System; using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Unity.Collections; @@ -40,7 +41,7 @@ internal static void Collide(float hitTime, ref BallState ball, ref PhysicsState private static void TransformBallIntoColliderSpace(ref NativeColliders colliders, ref BallState ball, ref PhysicsState state, int colliderId) { - if (!state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + if (colliders.IsTransformed(colliderId)) { return; } ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); @@ -49,7 +50,7 @@ private static void TransformBallIntoColliderSpace(ref NativeColliders colliders private static void TransformBallFromColliderSpace(ref NativeColliders colliders, ref BallState ball, ref PhysicsState state, int colliderId) { - if (!state.HasNonTransformableColliderMatrix(colliderId, ref colliders)) { + if (colliders.IsTransformed(colliderId)) { return; } ref var matrix = ref state.GetNonTransformableColliderMatrix(colliderId, ref colliders); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs index e23ae533a..799d63be0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs @@ -46,7 +46,8 @@ ref PhysicsState state float newTime; var newCollEvent = new CollisionEventData(); - if (state.HasNonTransformableColliderMatrix(overlappingColliderId, ref colliders)) { + if (!colliders.IsTransformed(overlappingColliderId)) { + ref var matrix = ref state.GetNonTransformableColliderMatrix(overlappingColliderId, ref colliders); var ballTransformed = ball; ballTransformed.Transform(math.inverse(matrix)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index abd852b8f..a37878875 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -30,7 +30,7 @@ public int Id public ColliderHeader Header; - public float2 Center; // todo remove and assume float2.zero + public float2 Center; public float Radius; public float ZHigh; @@ -231,6 +231,8 @@ public void Transform(CircleCollider circle, float4x4 matrix) Radius = circle.Radius * s.x; ZHigh = circle.ZHigh * s.z; ZLow = circle.ZLow * s.z; + + TransformAabb(matrix); } public CircleCollider Transform(float4x4 matrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 72abfca24..9fb6d1281 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -17,12 +17,19 @@ using System; using Unity.Collections; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Unity.Collections; namespace VisualPinball.Unity { + /// + /// A wrapper class used to pass around colliders during collider generation. + /// This isn't used during the physics runtime, where it's copied into NativeColliders. + /// public struct ColliderReference : IDisposable { + private const float Tolerance = 1e-7f; // 1e-9f; + internal NativeList CircleColliders; internal NativeList FlipperColliders; internal NativeList GateColliders; @@ -40,8 +47,9 @@ public struct ColliderReference : IDisposable public readonly bool KinematicColliders; // if set, populate _itemIdToColliderIds private NativeParallelHashMap> _itemIdToColliderIds; + private NativeParallelHashMap _nonTransformableColliderMatrices; - public ColliderReference(Allocator allocator, bool kinematicColliders = false) + public ColliderReference(ref NativeParallelHashMap nonTransformableColliderMatrices, Allocator allocator, bool kinematicColliders = false) { CircleColliders = new NativeList(allocator); FlipperColliders = new NativeList(allocator); @@ -55,10 +63,12 @@ public ColliderReference(Allocator allocator, bool kinematicColliders = false) SpinnerColliders = new NativeList(allocator); TriangleColliders = new NativeList(allocator); PlaneColliders = new NativeList(allocator); + Lookups = new NativeList(allocator); KinematicColliders = kinematicColliders; _itemIdToColliderIds = new NativeParallelHashMap>(0, allocator); + _nonTransformableColliderMatrices = nonTransformableColliderMatrices; } public void Dispose() @@ -166,8 +176,39 @@ private void TrackReference(int itemId, int colliderId) _itemIdToColliderIds[itemId].Add(colliderId); } - internal int Add(CircleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); + internal int Add(CircleCollider collider, float4x4 matrix) + { + // position: fully transformable: 3d (center + ZLow) + // scale: x+y must be equal, z applies to zHigh + // rotation: can be z-rotated, since it's a cylinder. x/y rotation is not supported. + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + + // if xy-scale is not uniform or x/y rotation is not zero, we can't transform the collider + if (math.abs(scale.x - scale.y) > Tolerance || math.abs(rotation.x) > Tolerance || math.abs(rotation.y) > Tolerance) { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } else { + + collider.Header.IsTransformed = true; + collider.Transform(matrix); + } + + collider.Id = Lookups.Length; + TrackReference(collider.Header.ItemId, collider.Header.Id); + Lookups.Add(new ColliderLookup(ColliderType.Circle, CircleColliders.Length)); + CircleColliders.Add(collider); + + return collider.Id; + } + [Obsolete("Add with matrix only.")] internal int Add(CircleCollider collider) { collider.Id = Lookups.Length; @@ -319,21 +360,27 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl // matrix of the collider, which is the case if the component implements ICollidableNonTransformableComponent // (see PhysicsEngine.Start()) + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal void AddNonTransformableLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) => Add(new LineZCollider(xy, zLow, zHigh, info).TransformAabb(matrix)); + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal void AddNonTransformableLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) => Add(new LineCollider(v1, v2, zLow, zHigh, info).TransformAabb(matrix)); + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal int AddNonTransformable(Line3DCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal int AddNonTransformable(PointCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); + [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] internal int AddNonTransformable(CircleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs index 1204bcd7e..9eaa54ad8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs @@ -30,6 +30,12 @@ public struct ColliderHeader : IEquatable public ItemType ItemType; public int Id; public int ItemId; + /** + * If this is false, that means that the collider's item has a transformation matrix that is not supported + * by this collider. It tells the physics runtime to instead transform the ball into this item's space before + * testing for collision. + */ + public bool IsTransformed; public PhysicsMaterialData Material; public float Threshold; @@ -61,6 +67,7 @@ public void Init(ColliderInfo info, ColliderType colliderType) } Type = colliderType; ItemType = info.ItemType; + IsTransformed = true; // per default, we assume that we don't have to transform the ball during runtime. Id = info.Id; ItemId = info.ItemId; Material = info.Material; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs index 30546e060..b6b712a09 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs @@ -26,7 +26,7 @@ internal static void Update(ref ContactBufferElement contact, ref BallState ball if (collEvent.ColliderId > -1) { // collide with static collider var gravity = state.Env.Gravity; - if (state.HasNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders)) { + if (!colliders.IsTransformed(collEvent.ColliderId)) { ref var matrix = ref state.GetNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders); var matrixInv = math.inverse(matrix); ball.Transform(matrixInv); @@ -43,7 +43,7 @@ internal static void Update(ref ContactBufferElement contact, ref BallState ball Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in gravity); } - if (state.HasNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders)) { + if (!colliders.IsTransformed(collEvent.ColliderId)) { ref var matrix = ref state.GetNonTransformableColliderMatrix(collEvent.ColliderId, ref colliders); ball.Transform(matrix); collEvent.Transform(matrix); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs index c221c73ae..82e62447d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs @@ -43,6 +43,7 @@ public unsafe struct NativeColliders : IDisposable /// An array that links the collider IDs (the key) to the position in the respective collider buffer. /// [NativeDisableUnsafePtrRestriction] private void* m_LookupBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_CircleColliderBuffer; [NativeDisableUnsafePtrRestriction] private void* m_FlipperColliderBuffer; [NativeDisableUnsafePtrRestriction] private void* m_GateColliderBuffer; @@ -472,8 +473,8 @@ public Aabb GetAabb(int index) throw new ArgumentException($"Unknown lookup type."); } + public bool IsTransformed(int index) => GetHeader(index).IsTransformed; public int GetItemId(int index) => GetHeader(index).ItemId; - public ItemType GetItemType(int index) => GetHeader(index).ItemType; public ref ColliderHeader GetHeader(int index) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 847af7d7d..5f403ab9e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -118,11 +118,11 @@ protected override void CreateColliders(ref ColliderReference colliders, var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent._isKinematic) { - _switchColliderId = kinematicColliders.AddNonTransformable(switchCollider, matrix); - kinematicColliders.AddNonTransformable(rigidCollider, matrix); + _switchColliderId = kinematicColliders.Add(switchCollider, matrix); + kinematicColliders.Add(rigidCollider, matrix); } else { - _switchColliderId = colliders.AddNonTransformable(switchCollider, matrix); - colliders.AddNonTransformable(rigidCollider, matrix); + _switchColliderId = colliders.Add(switchCollider, matrix); + colliders.Add(rigidCollider, matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 4c2e35230..fb5f172a1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -58,7 +58,9 @@ public abstract class ColliderComponent : SubComponent _collidersDirty = value; } - [NonSerialized] private Mesh _colliderMesh; + [NonSerialized] private Mesh _staticColliderMesh; + [NonSerialized] private Mesh _kinematicColliderMesh; + [NonSerialized] private Mesh _nonTransformableColliderMesh; [NonSerialized] private readonly List _nonMeshColliders = new List(); [NonSerialized] private bool _collidersDirty; @@ -70,7 +72,9 @@ public abstract class ColliderComponent : SubComponent(); + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; Handles.matrix = Gizmos.matrix; @@ -159,22 +165,23 @@ private void OnDrawGizmos() var colliders = _physicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); if (showColliders) { - _colliderMesh = GenerateColliderMesh(colliders); + GenerateColliderMesh(colliders); //_collidersDirty = false; } } else { - var api = InstantiateColliderApi(player, null); - var colliders = new ColliderReference(Allocator.Temp); - var kinematicColliders = new ColliderReference(Allocator.Temp, true); + var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp); + var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp, true); try { api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); if (showColliders) { - _colliderMesh = IsKinematic - ? GenerateColliderMesh(ref kinematicColliders) - : GenerateColliderMesh(ref colliders); + if (IsKinematic) { + GenerateColliderMesh(ref kinematicColliders); + } else { + GenerateColliderMesh(ref colliders); + } _collidersDirty = false; } @@ -194,8 +201,8 @@ private void OnDrawGizmos() if (ShowColliderOctree) { var api = InstantiateColliderApi(player, null); - var colliders = new ColliderReference(Allocator.TempJob); - var kinematicColliders = new ColliderReference(Allocator.TempJob, true); + var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob); + var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob, true); try { api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); @@ -220,17 +227,34 @@ private void OnDrawGizmos() Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; Handles.matrix = Gizmos.matrix; - var color = Application.isPlaying && IsKinematic - ? Color.magenta - : IsKinematic ? new Color(0, 1, 1) : Color.green; - Handles.color = color; + // var color = Application.isPlaying && IsKinematic + // ? Color.magenta + // : IsKinematic ? new Color(0, 1, 1) : Color.green; + // Handles.color = color; + // color.a = 0.3f; + // Gizmos.color = color; + // Gizmos.DrawMesh(_colliderMesh); + // color = Color.white; + // color.a = 0.01f; + // Gizmos.color = color; + // Gizmos.DrawWireMesh(_colliderMesh); + + var color = new Color(0, 1, 1); + Handles.color = Color.cyan; color.a = 0.3f; + Gizmos.color = color; - Gizmos.DrawMesh(_colliderMesh); + Gizmos.DrawMesh(_nonTransformableColliderMesh); + + Gizmos.color = Color.green; + Gizmos.DrawMesh(_staticColliderMesh); + color = Color.white; color.a = 0.01f; Gizmos.color = color; - Gizmos.DrawWireMesh(_colliderMesh); + Gizmos.DrawWireMesh(_staticColliderMesh); + Gizmos.DrawWireMesh(_nonTransformableColliderMesh); + DrawNonMeshColliders(); } @@ -240,18 +264,25 @@ private void OnDrawGizmos() Profiler.EndSample(); } - private Mesh GenerateColliderMesh(ref ColliderReference colliders) + private void GenerateColliderMesh(ref ColliderReference colliders) { var color = Color.green; Handles.color = color; color.a = 0.3f; Gizmos.color = color; var vertices = new List(); + var verticesNonTransformable = new List(); var normals = new List(); + var normalsNonTransformable = new List(); var indices = new List(); + var indicesNonTransformable = new List(); _nonMeshColliders.Clear(); foreach (var col in colliders.CircleColliders) { - AddCollider(col, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col, vertices, normals, indices); + } else { + AddCollider(col, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var _ in colliders.FlipperColliders) { AddFlipperCollider(vertices, normals, indices); @@ -284,76 +315,89 @@ private Mesh GenerateColliderMesh(ref ColliderReference colliders) // todo Line3DCollider - return new Mesh { - name = $"{name} (debug collider)", + _staticColliderMesh = new Mesh { + name = $"{name} (static collider)", + vertices = vertices.ToArray(), + triangles = indices.ToArray(), + normals = normals.ToArray() + }; + _nonTransformableColliderMesh = new Mesh { + name = $"{name} (non-transformable colliders)", vertices = vertices.ToArray(), triangles = indices.ToArray(), normals = normals.ToArray() }; } - private Mesh GenerateColliderMesh(IEnumerable colliders) + private void GenerateColliderMesh(IEnumerable colliders) { var color = Color.magenta; Handles.color = color; color.a = 0.3f; Gizmos.color = color; var vertices = new List(); + var verticesNonTransformable = new List(); var normals = new List(); + var normalsNonTransformable = new List(); var indices = new List(); + var indicesNonTransformable = new List(); _nonMeshColliders.Clear(); foreach (var coll in colliders) { - if (coll is CircleCollider circleCollider) { - AddCollider(circleCollider, vertices, normals, indices); - } - - if (coll is FlipperCollider) { - AddFlipperCollider(vertices, normals, indices); - } - - if (coll is GateCollider gateCollider) { - AddCollider(gateCollider.LineSeg0, vertices, normals, indices); - AddCollider(gateCollider.LineSeg1, vertices, normals, indices); - } - - if (coll is LineCollider lineCollider) { - AddCollider(lineCollider, vertices, normals, indices); - } - - if (coll is LineSlingshotCollider lineSlingshotCollider) { - AddCollider(lineSlingshotCollider, vertices, normals, indices); - } - - if (coll is LineZCollider lineZCollider) { - _nonMeshColliders.Add(lineZCollider); - } - - if (coll is PlungerCollider plungerCollider) { - AddCollider(plungerCollider.LineSegBase, vertices, normals, indices); - AddCollider(plungerCollider.JointBase0, vertices, normals, indices); - AddCollider(plungerCollider.JointBase1, vertices, normals, indices); - } - - if (coll is SpinnerCollider spinnerCollider) { - AddCollider(spinnerCollider.LineSeg0, vertices, normals, indices); - AddCollider(spinnerCollider.LineSeg1, vertices, normals, indices); - } - - if (coll is TriangleCollider triangleCollider) { - AddCollider(triangleCollider, vertices, normals, indices); + switch (coll) { + case CircleCollider { Header: { IsTransformed: true } } circleCollider: + AddCollider(circleCollider, vertices, normals, indices); + break; + case CircleCollider circleCollider: + AddCollider(circleCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; + case FlipperCollider: + AddFlipperCollider(vertices, normals, indices); + break; + case GateCollider gateCollider: + AddCollider(gateCollider.LineSeg0, vertices, normals, indices); + AddCollider(gateCollider.LineSeg1, vertices, normals, indices); + break; + case LineCollider lineCollider: + AddCollider(lineCollider, vertices, normals, indices); + break; + case LineSlingshotCollider lineSlingshotCollider: + AddCollider(lineSlingshotCollider, vertices, normals, indices); + break; + case LineZCollider lineZCollider: + _nonMeshColliders.Add(lineZCollider); + break; + case PlungerCollider plungerCollider: + AddCollider(plungerCollider.LineSegBase, vertices, normals, indices); + AddCollider(plungerCollider.JointBase0, vertices, normals, indices); + AddCollider(plungerCollider.JointBase1, vertices, normals, indices); + break; + case SpinnerCollider spinnerCollider: + AddCollider(spinnerCollider.LineSeg0, vertices, normals, indices); + AddCollider(spinnerCollider.LineSeg1, vertices, normals, indices); + break; + case TriangleCollider triangleCollider: + AddCollider(triangleCollider, vertices, normals, indices); + break; } } // todo Line3DCollider - return new Mesh { - name = $"{name} (debug collider)", + _staticColliderMesh = new Mesh { + name = $"{name} (static collider)", vertices = vertices.ToArray(), triangles = indices.ToArray(), normals = normals.ToArray() }; + + _nonTransformableColliderMesh = new Mesh { + name = $"{name} (static collider)", + vertices = verticesNonTransformable.ToArray(), + triangles = indicesNonTransformable.ToArray(), + normals = normalsNonTransformable.ToArray() + }; } private void DrawNonMeshColliders() @@ -372,7 +416,6 @@ private static void AddCollider(CircleCollider circleCol, IList vertice { var startIdx = vertices.Count; const int m_Sides = 32; - var aabb = circleCol.Bounds.Aabb; const float angleStep = 360.0f / m_Sides; var rotation = Quaternion.Euler(0.0f, 0.0f, angleStep); const int max = m_Sides - 1; @@ -431,11 +474,10 @@ private static void DrawLine(Vector3 p1, Vector3 p2) private static void AddCollider(LineZCollider lineZCol, ICollection vertices, ICollection normals, ICollection indices) { - var aabb = lineZCol.Bounds.Aabb; const float width = 10f; - var bottom = lineZCol.XY.ToFloat3(aabb.ZLow); - var top = lineZCol.XY.ToFloat3(aabb.ZHigh); + var bottom = lineZCol.XY.ToFloat3(lineZCol.ZLow); + var top = lineZCol.XY.ToFloat3(lineZCol.ZHigh); var i = vertices.Count; vertices.Add(bottom + new float3(width, 0, 0)); From bb70147da4c5217f88a9e31d09e17220983ba9eb Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 11 Nov 2024 21:46:09 +0100 Subject: [PATCH 070/208] fix: Fix rendering of colliders and some styling. --- .../VisualPinball.Unity/Common/LazyInit.cs | 25 +++---- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 6 +- .../VPT/ColliderComponent.cs | 74 ++++++++++++------- 3 files changed, 62 insertions(+), 43 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/LazyInit.cs b/VisualPinball.Unity/VisualPinball.Unity/Common/LazyInit.cs index bd4aaeb48..c66ef693f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Common/LazyInit.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/LazyInit.cs @@ -4,25 +4,24 @@ namespace VisualPinball.Unity { public class LazyInit { - private bool isInitialized = false; - private T value; - public ref T Ref - { - get - { - if (!isInitialized) { - isInitialized = true; - value = constructor(); + private readonly Func _constructor; + private bool _isInitialized; + private T _value; + + public ref T Ref { + get { + if (_isInitialized) { + return ref _value; } - return ref value; + _isInitialized = true; + _value = _constructor(); + return ref _value; } } - private readonly Func constructor; - public LazyInit(Func constructor) { - this.constructor = constructor; + _constructor = constructor; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 67fe1e337..a9fd42ef2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -74,12 +74,12 @@ public class PhysicsEngine : MonoBehaviour /// /// Last transforms of kinematic items, so we can detect changes. /// - [NonSerialized] private readonly LazyInit> _kinematicTransforms = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _kinematicTransforms = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); /// /// The transforms of the kinematic items that have changes since the last frame. /// - [NonSerialized] private readonly LazyInit> _updatedKinematicTransforms = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _updatedKinematicTransforms = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); /// /// The current matrix to the ball will be transformed to, if it collides with a non-transformable collider. @@ -88,7 +88,7 @@ public class PhysicsEngine : MonoBehaviour /// /// todo save inverse matrix, too /// - [NonSerialized] private readonly LazyInit> _nonTransformableColliderMatrices = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _nonTransformableColliderMatrices = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); [NonSerialized] private readonly Dictionary _rotatableComponent = new(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index fb5f172a1..921b44851 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -23,6 +23,7 @@ using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; +using Unity.VisualScripting.YamlDotNet.Serialization.NodeDeserializers; using UnityEditor; using UnityEngine; using UnityEngine.Profiling; @@ -149,7 +150,7 @@ private void OnDrawGizmos() ? ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)) : float4x4.identity; - var nonTransformableColliderMatrices = new NativeParallelHashMap(); + var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; Handles.matrix = Gizmos.matrix; @@ -224,7 +225,8 @@ private void OnDrawGizmos() if (showColliders) { - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; + //Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; Handles.matrix = Gizmos.matrix; // var color = Application.isPlaying && IsKinematic @@ -239,21 +241,30 @@ private void OnDrawGizmos() // Gizmos.color = color; // Gizmos.DrawWireMesh(_colliderMesh); - var color = new Color(0, 1, 1); - Handles.color = Color.cyan; - color.a = 0.3f; - - Gizmos.color = color; - Gizmos.DrawMesh(_nonTransformableColliderMesh); + var white = Color.white; + var blue = new Color(0, 1, 1); + var green = Color.green; + green.a = 0.3f; + blue.a = 0.3f; + white.a = 0.01f; + + if (_nonTransformableColliderMesh) { + var m = ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)); + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)m; + Handles.matrix = Gizmos.matrix; + Handles.color = blue; + Gizmos.color = blue; + Gizmos.DrawMesh(_nonTransformableColliderMesh); + } - Gizmos.color = Color.green; - Gizmos.DrawMesh(_staticColliderMesh); + if (_staticColliderMesh) { + Gizmos.color = green; + Gizmos.DrawMesh(_staticColliderMesh); - color = Color.white; - color.a = 0.01f; - Gizmos.color = color; - Gizmos.DrawWireMesh(_staticColliderMesh); - Gizmos.DrawWireMesh(_nonTransformableColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_staticColliderMesh); + Gizmos.DrawWireMesh(_nonTransformableColliderMesh); + } DrawNonMeshColliders(); } @@ -315,18 +326,27 @@ private void GenerateColliderMesh(ref ColliderReference colliders) // todo Line3DCollider - _staticColliderMesh = new Mesh { - name = $"{name} (static collider)", - vertices = vertices.ToArray(), - triangles = indices.ToArray(), - normals = normals.ToArray() - }; - _nonTransformableColliderMesh = new Mesh { - name = $"{name} (non-transformable colliders)", - vertices = vertices.ToArray(), - triangles = indices.ToArray(), - normals = normals.ToArray() - }; + if (vertices.Count > 0) { + _staticColliderMesh = new Mesh { + name = $"{name} (static collider)", + vertices = vertices.ToArray(), + triangles = indices.ToArray(), + normals = normals.ToArray() + }; + } else { + _staticColliderMesh = null; + } + + if (verticesNonTransformable.Count > 0) { + _nonTransformableColliderMesh = new Mesh { + name = $"{name} (non-transformable colliders)", + vertices = verticesNonTransformable.ToArray(), + triangles = indicesNonTransformable.ToArray(), + normals = normalsNonTransformable.ToArray() + }; + } else { + _nonTransformableColliderMesh = null; + } } private void GenerateColliderMesh(IEnumerable colliders) From dc88a50733cc56924bbc080648370073bb5e2873 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 12 Nov 2024 21:41:57 +0100 Subject: [PATCH 071/208] colliders: Clean up. --- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 921b44851..273bc854e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -146,15 +146,8 @@ private void OnDrawGizmos() ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(math.inverse(playfieldToWorld)) : float4x4.identity; - var translateFullyWithinPlayfieldMatrix = this is ICollidableNonTransformableComponent - ? ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)) - : float4x4.identity; - var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; - Handles.matrix = Gizmos.matrix; - var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { From 9402fb81174c320428a85843b074ce73f1622c13 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 12 Nov 2024 23:18:27 +0100 Subject: [PATCH 072/208] bumper: Make orientation change somewhat better if bumper is x/z rotated. Needs still fixing, though. --- .../VPT/Bumper/BumperInspector.cs | 10 +++++- .../VPT/ItemInspector.cs | 31 +++++++++++++++++++ .../VPT/Bumper/BumperComponent.cs | 17 +++++----- 3 files changed, 50 insertions(+), 8 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index 735b5b52f..9e43019ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -17,6 +17,7 @@ // ReSharper disable AssignmentInConditionalExpression using UnityEditor; +using UnityEngine; using VisualPinball.Engine.VPT.Bumper; namespace VisualPinball.Unity.Editor @@ -30,6 +31,9 @@ public class BumperInspector : MainInspector private SerializedProperty _orientationProperty; private SerializedProperty _surfaceProperty; + private bool _isChangingYRotation; + private Quaternion _initialXZRotation; + protected override void OnEnable() { base.OnEnable(); @@ -39,6 +43,8 @@ protected override void OnEnable() _heightScaleProperty = serializedObject.FindProperty(nameof(BumperComponent.HeightScale)); _orientationProperty = serializedObject.FindProperty(nameof(BumperComponent.Orientation)); _surfaceProperty = serializedObject.FindProperty(nameof(BumperComponent._surface)); + + _initialXZRotation = Quaternion.identity; } public override void OnInspectorGUI() @@ -54,7 +60,9 @@ public override void OnInspectorGUI() PropertyField(_positionProperty, updateTransforms: true); PropertyField(_radiusProperty, updateTransforms: true); PropertyField(_heightScaleProperty, updateTransforms: true); - PropertyField(_orientationProperty, updateTransforms: true); + PropertyField(_orientationProperty, "Orientation", ref _isChangingYRotation, + before => _initialXZRotation = Quaternion.Euler(before.eulerAngles.x, 0, before.eulerAngles.z), + () => MainComponent.UpdateTransforms(_initialXZRotation), updateTransforms: true); PropertyField(_surfaceProperty, updateTransforms: true); base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 99e694ac3..69efc9b19 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -170,6 +170,37 @@ protected void PropertyField(SerializedProperty serializedProperty, string label } } + protected void PropertyField(SerializedProperty serializedProperty, string label, ref bool isUpdating, + Action onStartChanging, Action onChanging, bool updateTransforms = false) + { + var controlName = "__detectFinished_" + serializedProperty.name; + GUI.SetNextControlName(controlName); + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.PropertyField(serializedProperty, string.IsNullOrEmpty(serializedProperty.tooltip) + ? new GUIContent(label) + : new GUIContent(label, serializedProperty.tooltip) + ); + var hasFocus = GUI.GetNameOfFocusedControl() == controlName; + + switch (hasFocus) { + case true when !isUpdating: + isUpdating = true; + onStartChanging.Invoke(((MonoBehaviour)target).transform); + break; + + case false when isUpdating: + isUpdating = false; + break; + } + + if (EditorGUI.EndChangeCheck()) { + GUI.changed = true; + _transformsDirty = updateTransforms; + onChanging?.Invoke(); + } + } + protected void DropDownProperty(string label, SerializedProperty prop, string[] optionStrings, int[] optionValues, bool rebuildMesh = false, bool updateVisibility = false) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 25d34ab89..a4c3b2073 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -142,16 +142,19 @@ private void Start() public override void UpdateTransforms() { base.UpdateTransforms(); - var t = transform; + var t = transform; - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); + // position + t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); - // scale - t.localScale = new Vector3(Radius * 2f, HeightScale, Radius * 2f) / DataMeshScale; + // scale + t.localScale = new Vector3(Radius * 2f, HeightScale, Radius * 2f) / DataMeshScale; + } - // rotation - t.localEulerAngles = new Vector3(0, Orientation, 0); + public void UpdateTransforms(Quaternion xz) + { + var y = Quaternion.Euler(0, Orientation, 0); + transform.rotation = xz * y; } public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); From 97e45fe6e2952533eae2ce5a808e591ed5497d2b Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 12 Nov 2024 23:43:54 +0100 Subject: [PATCH 073/208] fix: Bumper AABBs. --- .../VisualPinball.Unity/Physics/Collider/CircleCollider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index a37878875..dbb0132c7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -226,13 +226,13 @@ public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Ran public void Transform(CircleCollider circle, float4x4 matrix) { + TransformAabb(matrix); + var s = matrix.GetScale(); Center = matrix.MultiplyPoint(new float3(circle.Center, 0)).xy; Radius = circle.Radius * s.x; ZHigh = circle.ZHigh * s.z; ZLow = circle.ZLow * s.z; - - TransformAabb(matrix); } public CircleCollider Transform(float4x4 matrix) From 4dc89859ae8b626801e7da37c6104f6b5e33b47e Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Mon, 11 Nov 2024 13:52:12 +0100 Subject: [PATCH 074/208] Fix bumper animations --- .../VisualPinball.Unity/Game/PhysicsUpdateJob.cs | 7 ++++--- .../VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index 6b279a82c..24cca7598 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -19,6 +19,7 @@ using Unity.Jobs; using Unity.Mathematics; using VisualPinball.Engine.Common; +using UnityEngine; namespace VisualPinball.Unity { @@ -137,10 +138,10 @@ public void Execute() while (enumerator.MoveNext()) { ref var bumperState = ref enumerator.Current.Value; if (bumperState.RingItemId != 0) { - BumperRingAnimation.Update(ref bumperState.RingAnimation, DeltaTimeMs); + BumperRingAnimation.Update(ref bumperState.RingAnimation, PhysicsConstants.PhysicsStepTime / 1000f); } if (bumperState.SkirtItemId != 0) { - BumperSkirtAnimation.Update(ref bumperState.SkirtAnimation, DeltaTimeMs); + BumperSkirtAnimation.Update(ref bumperState.SkirtAnimation, PhysicsConstants.PhysicsStepTime / 1000f); } } } @@ -173,7 +174,7 @@ public void Execute() using (var enumerator = TriggerStates.GetEnumerator()) { while (enumerator.MoveNext()) { ref var triggerState = ref enumerator.Current.Value; - TriggerAnimation.Update(ref triggerState.Animation, ref triggerState.Movement, in triggerState.Static, DeltaTimeMs); + TriggerAnimation.Update(ref triggerState.Animation, ref triggerState.Movement, in triggerState.Static, PhysicsConstants.PhysicsStepTime / 1000f); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs index 0dc629b8b..86eaa7173 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs @@ -18,7 +18,7 @@ namespace VisualPinball.Unity { internal static class BumperRingAnimation { - internal static void Update(ref BumperRingAnimationState state, float dTime) + internal static void Update(ref BumperRingAnimationState state, float dTimeMs) { // todo visibility - skip if invisible @@ -33,7 +33,7 @@ internal static void Update(ref BumperRingAnimationState state, float dTime) if (state.AnimateDown) { step = -step; } - state.Offset += step * dTime; + state.Offset += step * dTimeMs; if (state.AnimateDown) { if (state.Offset <= -limit) { state.Offset = -limit; From 1ee9187e27742a38327ad3f482765586aad7b41d Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Mon, 11 Nov 2024 16:47:47 +0100 Subject: [PATCH 075/208] Increase default ring animation speed --- .../VPT/Bumper/BumperRingAnimationComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationComponent.cs index 29cccdfff..7996ed7a9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationComponent.cs @@ -27,7 +27,7 @@ public class BumperRingAnimationComponent : AnimationComponent Date: Mon, 11 Nov 2024 16:48:15 +0100 Subject: [PATCH 076/208] Customizable bumper skirt animation duration --- .../VisualPinball.Unity/VPT/Bumper/BumperComponent.cs | 3 ++- .../VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs | 6 +++--- .../VPT/Bumper/BumperSkirtAnimationComponent.cs | 4 ++++ .../VPT/Bumper/BumperSkirtAnimationState.cs | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index a4c3b2073..44f0a2d21 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -344,7 +344,8 @@ internal BumperState CreateState() DoUpdate = false, EnableAnimation = true, Rotation = new float2(0, 0), - Center = Position + Center = Position, + Duration = skirtAnimComponent.duration, } : default; // ring animation data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs index 3e331db4a..fde8ed4e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs @@ -20,7 +20,7 @@ namespace VisualPinball.Unity { internal static class BumperSkirtAnimation { - internal static void Update(ref BumperSkirtAnimationState state, float dTime) + internal static void Update(ref BumperSkirtAnimationState state, float dTimeMs) { // todo visibility - skip if invisible @@ -34,8 +34,8 @@ internal static void Update(ref BumperSkirtAnimationState state, float dTime) state.AnimationCounter = 0.0f; } if (state.DoAnimate) { - state.AnimationCounter += dTime; - if (state.AnimationCounter > 160.0f) { + state.AnimationCounter += dTimeMs; + if (state.AnimationCounter > state.Duration * 1000) { state.DoAnimate = false; ResetSkirt(ref state); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationComponent.cs index ae610f5e6..027cb01f1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationComponent.cs @@ -22,5 +22,9 @@ namespace VisualPinball.Unity [AddComponentMenu("Visual Pinball/Animation/Bumper Skirt Animation")] public class BumperSkirtAnimationComponent : AnimationComponent { + #region Data + [Tooltip("How long the skirt is pushed down when hit by a ball in seconds")] + public float duration = 0.1f; + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs index f61274231..e9206f6ed 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs @@ -31,5 +31,6 @@ internal struct BumperSkirtAnimationState // static public float2 Center; + public float Duration; } } From eac691bb16d01ac08f742de170dab53b7b4926bf Mon Sep 17 00:00:00 2001 From: arthurkehrwald <50906979+arthurkehrwald@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:31:43 +0100 Subject: [PATCH 077/208] Hardwire bumpers by default # Conflicts: # VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs # VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs --- .../VPT/Bumper/BumperInspector.cs | 3 +++ .../VPT/Bumper/BumperComponent.cs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index 9e43019ee..5f413c659 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -30,6 +30,7 @@ public class BumperInspector : MainInspector private SerializedProperty _heightScaleProperty; private SerializedProperty _orientationProperty; private SerializedProperty _surfaceProperty; + private SerializedProperty _isHardwiredProperty; private bool _isChangingYRotation; private Quaternion _initialXZRotation; @@ -43,6 +44,7 @@ protected override void OnEnable() _heightScaleProperty = serializedObject.FindProperty(nameof(BumperComponent.HeightScale)); _orientationProperty = serializedObject.FindProperty(nameof(BumperComponent.Orientation)); _surfaceProperty = serializedObject.FindProperty(nameof(BumperComponent._surface)); + _isHardwiredProperty = serializedObject.FindProperty(nameof(BumperComponent.IsHardwired)); _initialXZRotation = Quaternion.identity; } @@ -64,6 +66,7 @@ public override void OnInspectorGUI() before => _initialXZRotation = Quaternion.Euler(before.eulerAngles.x, 0, before.eulerAngles.z), () => MainComponent.UpdateTransforms(_initialXZRotation), updateTransforms: true); PropertyField(_surfaceProperty, updateTransforms: true); + PropertyField(_isHardwiredProperty, updateTransforms: false); base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 44f0a2d21..64113fe4f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; @@ -53,6 +54,9 @@ public class BumperComponent : MainRenderableComponent, [Tooltip("Orientation angle. Updates z rotation.")] public float Orientation; + [Tooltip("Should the bumper coil always activate when touched by a ball? Disable to give game logic engine full control")] + public bool IsHardwired = true; + public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } [SerializeField] @@ -103,6 +107,19 @@ private void Awake() private void Start() { _playfieldToWorld = Player.PlayfieldToWorldMatrix; + if (IsHardwired) { + WireMapping wireMapping = new() { + Description = $"Hardwired bumper '{name}'", + Source = SwitchSource.Playfield, + SourceDevice = this, + SourceDeviceItem = AvailableSwitches.FirstOrDefault().Id, + DestinationDevice = this, + DestinationDeviceItem = AvailableCoils.FirstOrDefault().Id, + IsDynamic = false, + }; + WireDestConfig wireDestConfig = new(wireMapping.WithId()); + BumperApi.AddWireDest(wireDestConfig); + } } #endregion From d219c13248afc25869b23f77965658f4c5ee797e Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 13 Nov 2024 23:28:01 +0100 Subject: [PATCH 078/208] flipper: Refactor and make fully transformable. --- .../Game/PhysicsStaticCollision.cs | 3 +- .../Physics/Collider/CircleCollider.cs | 5 ++ .../Physics/Collider/ColliderReference.cs | 25 ++++++++- .../Physics/Collision/Aabb.cs | 35 +++++------- .../VisualPinball.Unity/Physics/README.md | 9 ++- .../VPT/Flipper/FlipperApi.cs | 26 +++------ .../VPT/Flipper/FlipperCollider.cs | 56 +++++++++++++++---- .../VPT/Flipper/FlipperComponent.cs | 14 ++--- .../VPT/Flipper/FlipperCorrection.cs | 7 ++- .../VPT/Flipper/FlipperCorrectionState.cs | 11 ++-- .../VPT/Flipper/FlipperStaticData.cs | 1 - .../VPT/Trigger/TriggerComponent.cs | 1 + 12 files changed, 119 insertions(+), 74 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 9d774072c..d48b1e31e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -201,7 +201,8 @@ private static void TriggerCollide(ref BallState ball, ref PhysicsState state, i if (triggerState.Animation.UnHitEvent) { ref var flipperCorrectionState = ref triggerState.FlipperCorrection; ref var fs = ref state.FlipperStates.GetValueByRef(flipperCorrectionState.FlipperItemId); - FlipperCorrection.OnBallLeaveFlipper(ref ball, ref flipperCorrectionState, in fs.Movement, in fs.Tricks, in fs.Static, state.Env.TimeMsec); + ref var flipperPos = ref state.Colliders.Flipper(flipperCorrectionState.FlipperColliderId).Position; + FlipperCorrection.OnBallLeaveFlipper(ref ball, ref flipperCorrectionState, in fs.Movement, in fs.Tricks, flipperPos, in fs.Static, state.Env.TimeMsec); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index dbb0132c7..a31644c19 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -219,11 +219,14 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref InsideOfs #endregion + public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Random random) { BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); } + #region Transformation + public void Transform(CircleCollider circle, float4x4 matrix) { TransformAabb(matrix); @@ -260,6 +263,8 @@ public CircleCollider TransformAabb(float4x4 matrix) return this; } + #endregion + public override string ToString() => $"CircleCollider[{Header.ItemId}] ({Center.x}/{Center.y}) {ZLow} -> {ZHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 9fb6d1281..45c06202c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -218,8 +218,31 @@ internal int Add(CircleCollider collider) return collider.Id; } - internal int Add(FlipperCollider collider) + internal int Add(FlipperCollider collider, float4x4 matrix) { + // position: fully transformable: 3d (center + ZLow) + // scale: none + // rotation: none (maybe, in the future: z-rotation should update start- and end angle) + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + var rotated = math.abs(rotation.x - 1) > Tolerance || math.abs(rotation.y - 1) > Tolerance || math.abs(rotation.z - 1) > Tolerance; + var scaled = math.abs(scale.x - 1) > Tolerance || math.abs(scale.y - 1) > Tolerance || math.abs(scale.z - 1) > Tolerance; + + if (scaled || rotated) { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + + } else { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Flipper, FlipperColliders.Length)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs index 32b161027..061927a0a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs @@ -134,28 +134,19 @@ public readonly bool Equals(Aabb a) public Aabb Transform(float4x4 m) { - var t = m.GetTranslation(); - var translateOnly = float4x4.Translate(new float3(t.x, t.y, 0)); - var p1 = translateOnly.MultiplyPoint(new float3(Left, Top, ZHigh)); - var p2 = translateOnly.MultiplyPoint(new float3(Right, Top, ZHigh)); - var p3 = translateOnly.MultiplyPoint(new float3(Left, Bottom, ZHigh)); - var p4 = translateOnly.MultiplyPoint(new float3(Right, Bottom, ZHigh)); - var p5 = translateOnly.MultiplyPoint(new float3(Left, Top, ZLow)); - var p6 = translateOnly.MultiplyPoint(new float3(Right, Top, ZLow)); - var p7 = translateOnly.MultiplyPoint(new float3(Left, Bottom, ZLow)); - var p8 = translateOnly.MultiplyPoint(new float3(Right, Bottom, ZLow)); - - //return new Aabb(Left, Right, Top, Bottom, ZLow, ZHigh); - //return new Aabb(Left, Right, Top, Bottom, ZLow, ZHigh); - // todo optimize, use min(float3) instead of min(float) - return new Aabb( - min(p1.x, p2.x, p3.x, p4.x, p5.x, p6.x, p7.x, p8.x), - max(p1.x, p2.x, p3.x, p4.x, p5.x, p6.x, p7.x, p8.x), - min(p1.y, p2.y, p3.y, p4.y, p5.y, p6.y, p7.y, p8.y), - max(p1.y, p2.y, p3.y, p4.y, p5.y, p6.y, p7.y, p8.y), - min(p1.z, p2.z, p3.z, p4.z, p5.z, p6.z, p7.z, p8.z), - max(p1.z, p2.z, p3.z, p4.z, p5.z, p6.z, p7.z, p8.z) - ); + var p1 = m.MultiplyPoint(new float3(Left, Top, ZHigh)); + var p2 = m.MultiplyPoint(new float3(Right, Top, ZHigh)); + var p3 = m.MultiplyPoint(new float3(Left, Bottom, ZHigh)); + var p4 = m.MultiplyPoint(new float3(Right, Bottom, ZHigh)); + var p5 = m.MultiplyPoint(new float3(Left, Top, ZLow)); + var p6 = m.MultiplyPoint(new float3(Right, Top, ZLow)); + var p7 = m.MultiplyPoint(new float3(Left, Bottom, ZLow)); + var p8 = m.MultiplyPoint(new float3(Right, Bottom, ZLow)); + + var min = math.min(p1, math.min(p2, math.min(p3, math.min(p4, math.min(p5, math.min(p6, math.min(p7, p8))))))); + var max = math.max(p1, math.max(p2, math.max(p3, math.max(p4, math.max(p5, math.max(p6, math.max(p7, p8))))))); + + return new Aabb(min, max); } private static float min(params float[] values) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index 1e556e53c..4a5e6f119 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -154,11 +154,10 @@ It also enables VPE to support parenting objects to others, allowing transformations of parent objects without disrupting the physics simulation. So, how can the VPX physics code handle arbitrary transformations? The solution is a -simple trick: rather than transforming the colliders, we -transform the balls: To resolve a collision between a ball and a collider that -is transformed in a non-supported way, we temporarily move the ball into the -collider's local space, resolve the collision, and then return the ball to the -playfield space. +simple trick: rather than transforming the colliders, we transform the balls: To +resolve a collision between a ball and a collider that is transformed in a +non-supported way, we temporarily move the ball into the collider's local space, +resolve the collision, and then return the ball to the playfield space. ### Collider Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 22bfebc66..0707fba91 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -66,6 +66,8 @@ public class FlipperApi : CollidableApi MainComponent.EndAngle ? -1f : 1f; // usually: -1f = left flipper + multiplicator = MainComponent.StartAngle > MainComponent.EndAngle ? -1f : 1f; // usually: -1f = left flipper } // and add ColliderComponent.Overshoot to the endangle so that the bounding box is correctly calculated - colliders.Add( + ColliderId = colliders.Add( new FlipperCollider( - hitCircleBase, + MainComponent.PositionZ, + MainComponent.Height, MainComponent.FlipperRadiusMax, MainComponent.BaseRadius, MainComponent.EndRadius, MainComponent.StartAngle, MainComponent.EndAngle + ColliderComponent.Overshoot * multiplicator, - GetColliderInfo(), - translateWithinPlayfieldMatrix - ) + GetColliderInfo() + ), + translateWithinPlayfieldMatrix ); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 3ca64c645..87245dc10 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -31,8 +31,9 @@ public int Id } public ColliderHeader Header; + public float3 Position; - private readonly CircleCollider _hitCircleBase; + private CircleCollider _hitCircleBase; private readonly float _zLow; private readonly float _zHigh; @@ -42,12 +43,22 @@ public int Id #region Setup - public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float startRadius, float endRadius, - float startAngle, float endAngle, ColliderInfo info, float4x4 matrix) : this() + public FlipperCollider(float posZ, float height, float flipperRadius, float startRadius, float endRadius, + float startAngle, float endAngle, ColliderInfo info) : this() { - var bounds = hitCircleBase.Bounds; + var baseRadius = math.max(startRadius, 0.01f); + _hitCircleBase = new CircleCollider( + float2.zero, // flipper collision is always done through the center and a matrix + baseRadius, + posZ, + posZ + height, + info + ); + Header.Init(info, ColliderType.Flipper); - _hitCircleBase = hitCircleBase; + Position = float3.zero; + + var bounds = _hitCircleBase.Bounds; _zLow = bounds.Aabb.ZLow; _zHigh = bounds.Aabb.ZHigh; @@ -72,10 +83,10 @@ public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 90f); aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 180f); - var l = flipperRadius * 1.2f; - aabb = new Aabb(-l, l, -l, l, -l, l); + // var l = flipperRadius * 1.2f; + // aabb = new Aabb(-l, l, -l, l, -l, l); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb); } private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float2 c, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) @@ -714,12 +725,12 @@ private void GetRelativeVelocity(in float3 normal, in BallState ball, in Flipper #region LiveCatch - public static void LiveCatch(ref BallState ball, ref CollisionEventData collEvent, ref FlipperTricksData tricks, in FlipperStaticData matData, uint msec ) { + public static void LiveCatch(ref BallState ball, ref CollisionEventData collEvent, ref FlipperTricksData tricks, float3 flipperPos, 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 flipperToBall = ball.Position - flipperPos; var hitTangent = Math.CrossZ(1f, collEvent.HitNormal); var ballPosition = math.dot(hitTangent, flipperToBall); //Logger.Info("BallPosition = {0}", ballPosition); @@ -915,6 +926,31 @@ public void Collide(ref BallState ball, ref CollisionEventData collEvent, ref Fl #endregion + #region Transformation + + public void Transform(FlipperCollider flipperCollider, float4x4 matrix) + { + TransformAabb(matrix); + + var s = matrix.GetScale(); + _hitCircleBase = _hitCircleBase.Transform(matrix); + Position = matrix.MultiplyPoint(flipperCollider.Position); + } + + public FlipperCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + + public FlipperCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + return this; + } + + #endregion + public override string ToString() => $"FlipperCollider[{Header.ItemId}] ({_hitCircleBase.Center.x}/{_hitCircleBase.Center.y}) {_zLow} -> {_zHigh}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index bcb7af44d..93975498d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -532,13 +532,13 @@ internal FlipperState CreateState() if (colliderComponent) { // vpx physics - var d = GetMaterialData(colliderComponent); + var staticData = GetStaticData(colliderComponent); var state = new FlipperState( - d, - GetMovementData(d), - GetVelocityData(d), + staticData, + GetMovementData(staticData), + GetVelocityData(staticData), GetHitData(), - GetFlipperTricksData(colliderComponent, d), + GetFlipperTricksData(colliderComponent, staticData), new SolenoidState { Value = false } ); @@ -586,7 +586,7 @@ internal FlipperTricksData GetFlipperTricksData(FlipperColliderComponent collide }; } - internal FlipperStaticData GetMaterialData(FlipperColliderComponent colliderComponent) + internal FlipperStaticData GetStaticData(FlipperColliderComponent colliderComponent) { float flipperRadius; if (FlipperRadiusMin > 0 && FlipperRadiusMax > FlipperRadiusMin) { @@ -609,10 +609,8 @@ internal FlipperStaticData GetMaterialData(FlipperColliderComponent colliderComp // model inertia of flipper as that of rod of length flipper around its end var inertia = (float) (1.0 / 3.0) * colliderComponent.Mass * (flipperRadius * flipperRadius); - var localPos = transform.localPosition; return new FlipperStaticData { - Position = new float3(localPos.x, localPos.y, 0F), // TODO: surface height? Inertia = inertia, AngleStart = angleStart, AngleEnd = angleEnd, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs index 9f9d475b1..8db0d1a15 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs @@ -21,7 +21,8 @@ namespace VisualPinball.Unity internal static class FlipperCorrection { public static void OnBallLeaveFlipper(ref BallState ballState, ref FlipperCorrectionState flipperCorrectionState, - in FlipperMovementState flipperMovementState, in FlipperTricksData tricks, in FlipperStaticData flipperStaticData, uint timeMs) + in FlipperMovementState flipperMovementState, in FlipperTricksData tricks, float3 flipperPos, + in FlipperStaticData flipperStaticData, uint timeMs) { var timeSinceFlipperStartedRotatingToEndMs = timeMs - flipperMovementState.StartRotateToEndTime; @@ -44,9 +45,9 @@ public static void OnBallLeaveFlipper(ref BallState ballState, ref FlipperCorrec var angleStart = flipperStaticData.AngleStart; var angleEnd = tricks.AngleEnd; - var flipPos = flipperStaticData.Position; + var flipPos = flipperPos; - var flipEnd = flipperStaticData.Position; + var flipEnd = flipperPos; flipEnd.x += math.sin(angleCur) * flipperStaticData.FlipperRadius; flipEnd.y += -math.cos(angleCur) * flipperStaticData.FlipperRadius; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs index 7c2b939a6..83cefa31f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs @@ -24,6 +24,7 @@ namespace VisualPinball.Unity public unsafe struct FlipperCorrectionState : IDisposable { public readonly bool IsEnabled; + public readonly int FlipperColliderId; public readonly int FlipperItemId; public readonly uint TimeDelayMs; @@ -33,16 +34,14 @@ public unsafe struct FlipperCorrectionState : IDisposable private readonly int _numPolarities; private readonly int _numVelocities; - private readonly Allocator _allocator; - - public FlipperCorrectionState(bool isEnabled, int flipperItemId, uint timeDelayMs, float2[] polarities, float2[] velocities, Allocator allocator) + public FlipperCorrectionState(bool isEnabled, int flipperItemId, int flipperColliderId, uint timeDelayMs, float2[] polarities, float2[] velocities, Allocator allocator) { IsEnabled = isEnabled; FlipperItemId = flipperItemId; + FlipperColliderId = flipperColliderId; TimeDelayMs = timeDelayMs; - _allocator = allocator; - _polarities = Allocate(polarities, _allocator); - _velocities = Allocate(velocities, _allocator); + _polarities = Allocate(polarities, allocator); + _velocities = Allocate(velocities, allocator); _numPolarities = polarities.Length; _numVelocities = velocities.Length; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs index 478bb1290..ba3ea2bc4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs @@ -20,7 +20,6 @@ namespace VisualPinball.Unity { internal struct FlipperStaticData { - public float3 Position; public float Inertia; public float AngleStart; public float Strength; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index c3d8f607a..6a1e22bee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -294,6 +294,7 @@ internal TriggerState CreateState() new FlipperCorrectionState( true, collComponent.ForFlipper.gameObject.GetInstanceID(), + collComponent.ForFlipper.FlipperApi.ColliderId, // todo fixme this is not yet set collComponent.TimeThresholdMs, collComponent.FlipperPolarities, collComponent.FlipperVelocities, From ee78e7fee54f3467cdf0c2b7a6f7583da4169693 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 13 Nov 2024 23:51:04 +0100 Subject: [PATCH 079/208] flipper: Fix collider gizmo and refactor tranformation check. --- .../Physics/Collider/CircleCollider.cs | 22 +++++++ .../Physics/Collider/Collider.cs | 2 + .../Physics/Collider/ColliderReference.cs | 37 +++-------- .../VPT/ColliderComponent.cs | 8 ++- .../VPT/Flipper/FlipperCollider.cs | 65 ++++++++++++------- 5 files changed, 79 insertions(+), 55 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index a31644c19..dd937ec7e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -227,8 +227,30 @@ public void Collide(ref BallState ball, in CollisionEventData collEvent, ref Ran #region Transformation + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable: 3d (center + ZLow) + // scale: x+y must be equal, z applies to zHigh + // rotation: can be z-rotated, since it's a cylinder. x/y rotation is not supported. + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + + // if xy-scale is not uniform or x/y rotation is not zero, we can't transform the collider + var uniformScale = math.abs(scale.x - scale.y) < Collider.Tolerance; + var xyRotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + + return uniformScale && !xyRotated; + } + public void Transform(CircleCollider circle, float4x4 matrix) { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform flipper."); + } + #endif + TransformAabb(matrix); var s = matrix.GetScale(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs index 541ef975f..4f0a29ea8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs @@ -28,6 +28,8 @@ namespace VisualPinball.Unity /// public struct Collider { + public const float Tolerance = 1e-7f; // 1e-9f; + public ColliderHeader Header; public int Id => Header.Id; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 45c06202c..80d70f7b3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -28,8 +28,6 @@ namespace VisualPinball.Unity ///
public struct ColliderReference : IDisposable { - private const float Tolerance = 1e-7f; // 1e-9f; - internal NativeList CircleColliders; internal NativeList FlipperColliders; internal NativeList GateColliders; @@ -178,26 +176,17 @@ private void TrackReference(int itemId, int colliderId) internal int Add(CircleCollider collider, float4x4 matrix) { - // position: fully transformable: 3d (center + ZLow) - // scale: x+y must be equal, z applies to zHigh - // rotation: can be z-rotated, since it's a cylinder. x/y rotation is not supported. - - var scale = matrix.GetScale(); - var rotation = matrix.GetRotationVector(); + if (CircleCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); - // if xy-scale is not uniform or x/y rotation is not zero, we can't transform the collider - if (math.abs(scale.x - scale.y) > Tolerance || math.abs(rotation.x) > Tolerance || math.abs(rotation.y) > Tolerance) { + } else { // save matrix for use during runtime if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); } - collider.Header.IsTransformed = false; collider.TransformAabb(matrix); - } else { - - collider.Header.IsTransformed = true; - collider.Transform(matrix); } collider.Id = Lookups.Length; @@ -220,27 +209,17 @@ internal int Add(CircleCollider collider) internal int Add(FlipperCollider collider, float4x4 matrix) { - // position: fully transformable: 3d (center + ZLow) - // scale: none - // rotation: none (maybe, in the future: z-rotation should update start- and end angle) - - var scale = matrix.GetScale(); - var rotation = matrix.GetRotationVector(); - var rotated = math.abs(rotation.x - 1) > Tolerance || math.abs(rotation.y - 1) > Tolerance || math.abs(rotation.z - 1) > Tolerance; - var scaled = math.abs(scale.x - 1) > Tolerance || math.abs(scale.y - 1) > Tolerance || math.abs(scale.z - 1) > Tolerance; + if (FlipperCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); - if (scaled || rotated) { + } else { // save matrix for use during runtime if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); } - collider.Header.IsTransformed = false; collider.TransformAabb(matrix); - - } else { - collider.Header.IsTransformed = true; - collider.Transform(matrix); } collider.Id = Lookups.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 273bc854e..fb8d9ce54 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -288,8 +288,12 @@ private void GenerateColliderMesh(ref ColliderReference colliders) AddCollider(col, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); } } - foreach (var _ in colliders.FlipperColliders) { - AddFlipperCollider(vertices, normals, indices); + foreach (var col in colliders.FlipperColliders) { + if (col.Header.IsTransformed) { + AddFlipperCollider(vertices, normals, indices); + } else { + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.GateColliders) { AddCollider(col.LineSeg0, vertices, normals, indices); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 87245dc10..e57972430 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -63,7 +63,6 @@ public FlipperCollider(float posZ, float height, float flipperRadius, float star _zHigh = bounds.Aabb.ZHigh; // compute bounds. we look at the flipper angles to compute the smallest possible bounds. - var c = _hitCircleBase.Center; var r2 = endRadius + 0.1f; var r3 = startRadius + 0.1f; @@ -71,17 +70,17 @@ public FlipperCollider(float posZ, float height, float flipperRadius, float star var a1 = ClampDegrees(endAngle); // start with no bounds - var aabb = new Aabb(c.x, c.x, c.y, c.y, _zLow, _zHigh); + var aabb = new Aabb(0, 0, 0, 0, _zLow, _zHigh); // extend with start and end position - aabb = ExtendBoundsAtPosition(aabb, c, flipperRadius, r2, a0); - aabb = ExtendBoundsAtPosition(aabb, c, flipperRadius, r2, a1); + aabb = ExtendBoundsAtPosition(aabb, flipperRadius, r2, a0); + aabb = ExtendBoundsAtPosition(aabb, flipperRadius, r2, a1); // extend with extremes (-90°, 0°, 90° and 180°) - aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, -90f); - aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 0f); - aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 90f); - aabb = ExtendBoundsAtExtreme(aabb, c, flipperRadius, r2, r3, a0, a1, 180f); + aabb = ExtendBoundsAtExtreme(aabb, flipperRadius, r2, r3, a0, a1, -90f); + aabb = ExtendBoundsAtExtreme(aabb, flipperRadius, r2, r3, a0, a1, 0f); + aabb = ExtendBoundsAtExtreme(aabb, flipperRadius, r2, r3, a0, a1, 90f); + aabb = ExtendBoundsAtExtreme(aabb, flipperRadius, r2, r3, a0, a1, 180f); // var l = flipperRadius * 1.2f; // aabb = new Aabb(-l, l, -l, l, -l, l); @@ -89,56 +88,56 @@ public FlipperCollider(float posZ, float height, float flipperRadius, float star Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb); } - private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float2 c, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) + private static Aabb ExtendBoundsAtExtreme(Aabb aabb, float length, float endRadius, float startRadius, float startAngle, float endAngle, float angle) { if (startAngle < angle && endAngle > angle || endAngle < angle && startAngle > angle) { // extend front side - return ExtendBoundsAtPosition(aabb, c, length, endRadius, angle); + return ExtendBoundsAtPosition(aabb, length, endRadius, angle); } // extend back side - return ExtendBacksideBounds(aabb, c, startRadius, ClampDegrees(angle + 180)); + return ExtendBacksideBounds(aabb, startRadius, ClampDegrees(angle + 180)); } - private static Aabb ExtendBacksideBounds(Aabb bounds, float2 center, float fixedRadius, float angle) + private static Aabb ExtendBacksideBounds(Aabb bounds, float fixedRadius, float angle) { switch (angle) { - case -90f: bounds.Right = math.max(bounds.Right, center.x + fixedRadius); break; - case 90f: bounds.Left = math.min(bounds.Left, center.x - fixedRadius); break; - case 0f: bounds.Bottom = math.max(bounds.Bottom, center.y + fixedRadius); break; - case 180f: bounds.Top = math.min(bounds.Top, center.y - fixedRadius); break; + case -90f: bounds.Right = math.max(bounds.Right, fixedRadius); break; + case 90f: bounds.Left = math.min(bounds.Left, fixedRadius); break; + case 0f: bounds.Bottom = math.max(bounds.Bottom, fixedRadius); break; + case 180f: bounds.Top = math.min(bounds.Top, fixedRadius); break; } return bounds; } - private static Aabb ExtendBoundsAtPosition(Aabb bounds, float2 center, float length, float fixedRadius, float angle) + private static Aabb ExtendBoundsAtPosition(Aabb bounds, float length, float fixedRadius, float angle) { var deg = ClampDegrees(angle); if (deg > 0) { var l = math.sin(math.radians(180 - deg)); var d1 = length * l; var d2 = math.sign(l) * fixedRadius; - bounds.Right = math.max(bounds.Right, center.x + d1 + d2); + bounds.Right = math.max(bounds.Right, d1 + d2); } else { var l = math.sin(math.radians(180 - deg)); var d1 = length * l; var d2 = math.sign(l) * fixedRadius; - bounds.Left = math.min(bounds.Left, center.x + d1 + d2); + bounds.Left = math.min(bounds.Left, d1 + d2); } if (deg > 90 || deg < -90) { var l = math.cos(math.radians(180 - deg)); var d1 = length * l; var d2 = math.sign(l) * fixedRadius; - bounds.Bottom = math.max(bounds.Bottom, center.y + d1 + d2); + bounds.Bottom = math.max(bounds.Bottom, d1 + d2); } else { var l = math.cos(math.radians(180 - deg)); var d1 = length * l; var d2 = math.sign(l) * fixedRadius; - bounds.Top = math.min(bounds.Top, center.y + d1 + d2); + bounds.Top = math.min(bounds.Top, d1 + d2); } return bounds; @@ -928,11 +927,29 @@ public void Collide(ref BallState ball, ref CollisionEventData collEvent, ref Fl #region Transformation - public void Transform(FlipperCollider flipperCollider, float4x4 matrix) + public static bool IsTransformable(float4x4 matrix) { - TransformAabb(matrix); + // position: fully transformable: 3d (center + ZLow) + // scale: none + // rotation: z is start angle, otherwise none + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + var rotated = math.abs(rotation.x - 1) > Collider.Tolerance || math.abs(rotation.y - 1) > Collider.Tolerance; + var scaled = math.abs(scale.x - 1) > Collider.Tolerance || math.abs(scale.y - 1) > Collider.Tolerance || math.abs(scale.z - 1) > Collider.Tolerance; + + return !rotated && !scaled; + } - var s = matrix.GetScale(); + private void Transform(FlipperCollider flipperCollider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform flipper."); + } + #endif + + TransformAabb(matrix); _hitCircleBase = _hitCircleBase.Transform(matrix); Position = matrix.MultiplyPoint(flipperCollider.Position); } From f8d001f9be9dc72d2352f7364e6abd948ca01bbc Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 14 Nov 2024 23:45:07 +0100 Subject: [PATCH 080/208] doc: Update README of physics changes. --- .../VisualPinball.Unity/Physics/README.md | 75 ++++++++++--------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index 4a5e6f119..db5821251 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -124,9 +124,10 @@ While VPE uses the same formulas and heuristics to resolve collisions and simula behavior as VPX, we've made some changes to make it more flexible. Here's a quick overview. -- **Kinematic Objects**: As described above, the only objects that can be freely - transformed are the balls, all other objects are static. In VPE, you can mark - any object as kinematic, which means it can be moved freely at runtime. +- **Kinematic Objects**: In VPX, as described above, the only objects that can be + freely transformed within the physics world are the balls, all other objects are + static. In VPE, you can mark any object as kinematic, which means it can be moved + freely at runtime. - **Unrestricted Transformations**: In VPX, every type of object has its limitations how it can be transformed. For example a flipper must always be parallel to the playfield. In VPE, you can rotate, move and scale objects freely. @@ -137,7 +138,7 @@ overview. rely on the heuristics of the physics engine that has been tuned over the years. > [!NOTE] -> We don't use the term *Hit Object* in VPE. We call them *Colliders*. +> In VPE, we don't use the term *Hit Object*. We call them *Colliders*. ## Unrestricted Transformations @@ -167,18 +168,20 @@ get this data from the transformation matrices into the colliders. The chosen approach is the following: - Each collider gets a `Transform(float4x4)` method that transforms the collider - in VPX space. Transforming means updating the collider's position, rotation, and - scale, whatever is supported. That's the data the VPX physics code uses. -- Obviously this has flaws. For example, a Line collider, by definition, is - aligned parallel and orthogonally to the playfield. In this case we'd transform - its two points and retrieve their new xy position. This works for translate and scale, - but rotating around anything else than the z-axis would still result in a rectangle - parallel and orthogonal to the playfield, which wouldn't be desired. + in the playfield space. Transforming means updating the collider's position, + rotation, and scale, whatever is supported. That's the data the VPX physics code + uses. +- This comes with restrictions as mentioned above. For example, a Line collider, + by definition, is aligned parallel and orthogonally to the playfield. In this case + we'd transform its two points and retrieve their new xy position. This works for + translate and scale, but rotating around anything else than the z-axis would still + result in a rectangle parallel and orthogonal to the playfield, which wouldn't be + desired. - But more on that problem later. What's important is that for transformations *supported by the VPX physics code*, we have a method that allows to transform - each collider. -- Additionally, colliders don't take in a transformation matrix. That means by default, - they are placed at the origin and have no rotation or scale. + each collider, based on a matrix. +- Additionally, colliders are instantiated without a transformation matrix. That means + by default, they are placed at the origin and have no rotation or scale. - Finally, each collider gets a `TransformAABBs(float4x4)` method that only transforms the collider's axis-aligned bounding boxes. @@ -187,14 +190,14 @@ So, with all of the above, we do the following when the game starts: 1. We retrieve the overall world-to-local matrix of an item. 2. Using the playfield's world-to-local matrix, we calculate the playfield-to-local matrix of the item. -3. We check which kind of transformation the VPX physics code supports for this type of - item and compare it to the transformation of playfield-to-local matrix. +3. We check which kind of transformation the VPX physics code supports for the type of + collider and compare it to the transformation of playfield-to-local matrix. - If all transformations are supported, we simply transform the collider with the item's - transformation matrix (let's call this Plan A). + transformation matrix. - If not, we check whether this collider might be replaceable by another type of collider that supports the transformation. For example, a line collider can be replaced by two - triangle colliders, which then are 100% transformable. That's our Plan B. - - If neither Plan A nor Plan B is possible, we fall back to our trick explained above, + triangle colliders, which then are 100% transformable. + - If neither of the above is possible, we fall back to ball transformation trick, which is to transform the ball during collision resolution. Note that the AABBs of the object still need to be transformed, so the broad phase can correctly sort out the items that are out of range. @@ -206,24 +209,22 @@ the years. ### Code Changes -Let's dive into the code. We'll need to more methods for each collider: - -1. `Transform(float4x4)`: This method transforms the collider in VPX space. -2. `TransformAABBs(float4x4)`: This method transforms the collider's axis-aligned bounding boxes. - -In our `ColliderReference` class, we'll add a `float4x4` to each `Add()` method, -which transforms the collider and its AABBs. - -> [!IMPORTANT] -> Remove the overload that doesn't need the matrix once all collider generators have been updated. - -We'll also have a `AddNonTransformable()` method which only transforms tha AABBs and marks -the collider as non-transformable, so we can do the ball transformation trick during collision -later. In order to know whether to do that, we'll need a flag in the collider. Let's call it -`IsTransformed`. - -In order to transform the ball, we need to also store the transformation matrix of the item. However, -while the `IsTransformed` flag is collider-specific, the transformation matrix is item-specific. +Let's dive into the code. We'll need three more methods for each collider: + +1. `IsTransformable(float4x4)`: This method checks whether the collider can be transformed + with the given matrix, i.e. if the physics code supports the transformation natively. +2. `Transform(float4x4)`: This method transforms the collider in VPX space. +3. `TransformAABBs(float4x4)`: This method transforms the collider's axis-aligned bounding boxes. + +In our `ColliderReference` class, we'll add a `float4x4` to each `Add()` method. This method goes +through the three use cases described above. + - Using `IsTransformable(float4x4)`, it'll determine whether the collider can be transformed + natively and uses `Transform(float4x4)` if that's the case. + - Otherwise, it'll check whether the collider can be replaced by another collider, converts it + and transforms the converted collider(s) instead. + - Otherwise, it only transforms its AABBs and marks the collider as non-transformable by + setting the `IsTransformed` of the collider to `false`. It also stores the transformation + matrix so it can be used during runtime for the ball transformation trick. ## Coordinate Systems From 02e2140fc621311811649dcc69b3eca3b28e8c5c Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 14 Nov 2024 23:45:36 +0100 Subject: [PATCH 081/208] flipper: Fix check for transformation. --- .../VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index e57972430..c9f0f4065 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -935,7 +935,7 @@ public static bool IsTransformable(float4x4 matrix) var scale = matrix.GetScale(); var rotation = matrix.GetRotationVector(); - var rotated = math.abs(rotation.x - 1) > Collider.Tolerance || math.abs(rotation.y - 1) > Collider.Tolerance; + var rotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; var scaled = math.abs(scale.x - 1) > Collider.Tolerance || math.abs(scale.y - 1) > Collider.Tolerance || math.abs(scale.z - 1) > Collider.Tolerance; return !rotated && !scaled; From c06c1583710fe5b01d5c28bd38e73748312dd353 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 15 Nov 2024 23:59:21 +0100 Subject: [PATCH 082/208] flipper: Fix collider gizmo for transformed pos. --- .../VPT/ColliderComponent.cs | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index fb8d9ce54..5523d3e9f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -27,6 +27,7 @@ using UnityEditor; using UnityEngine; using UnityEngine.Profiling; +using VisualPinball.Engine.Game; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Flipper; using Mesh = UnityEngine.Mesh; @@ -121,6 +122,12 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa private PhysicsEngine _physicsEngine; private bool IsKinematic => this is IKinematicColliderComponent { IsKinematic: true }; + private float4x4 TransformWithinPlayfieldMatrix { get { + var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; + var m = ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)); + return playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)m; + }} + private void OnDrawGizmos() { Profiler.BeginSample("ItemColliderComponent.OnDrawGizmosSelected"); @@ -290,9 +297,9 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } foreach (var col in colliders.FlipperColliders) { if (col.Header.IsTransformed) { - AddFlipperCollider(vertices, normals, indices); + AddFlipperCollider(vertices, normals, indices, float4x4.identity); } else { - AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, float4x4.identity); // TODO fix } } foreach (var col in colliders.GateColliders) { @@ -369,8 +376,11 @@ private void GenerateColliderMesh(IEnumerable colliders) case CircleCollider circleCollider: AddCollider(circleCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); break; + case FlipperCollider { Header: { IsTransformed: true } }: + AddFlipperCollider(vertices, normals, indices, float4x4.identity); + break; case FlipperCollider: - AddFlipperCollider(vertices, normals, indices); + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, TransformWithinPlayfieldMatrix); break; case GateCollider gateCollider: AddCollider(gateCollider.LineSeg0, vertices, normals, indices); @@ -573,7 +583,7 @@ private static void AddCollider(TriangleCollider triangleCol, ICollection vertices, List normals, List indices) + private void AddFlipperCollider(List vertices, List normals, List indices, float4x4 matrix) { // first see if we already have a mesh var flipperComponent = GetComponentInChildren(); @@ -581,16 +591,12 @@ private void AddFlipperCollider(List vertices, List normals, L return; } -// var t = transform; -// var ltp = Matrix4x4.TRS(t.localPosition.TranslateToVpx(), quaternion.Euler(0, 0, math.radians(flipperComponent.StartAngle)), t.localScale); var startIdx = vertices.Count; - var mesh = new FlipperMeshGenerator(flipperComponent).GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f); + var mesh = new FlipperMeshGenerator(flipperComponent).GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f, Origin.Global, false, 0.2f); for (var i = 0; i < mesh.Vertices.Length; i++) { var vertex = mesh.Vertices[i]; - // vertices.Add(ltp.MultiplyPoint(vertex.ToUnityFloat3())); - // normals.Add(ltp.MultiplyVector(vertex.ToUnityNormalVector3())); - vertices.Add(vertex.ToUnityFloat3()); - normals.Add(vertex.ToUnityNormalVector3()); + vertices.Add(matrix.MultiplyPoint(vertex.ToUnityFloat3())); + normals.Add(matrix.MultiplyVector(vertex.ToUnityNormalVector3())); } indices.AddRange(mesh.Indices.Select(n => startIdx + n)); } From e75055c79bba3b3b5fe843e59c7fbbfb962f9311 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 16 Nov 2024 01:01:20 +0100 Subject: [PATCH 083/208] flipper: Fix collider gizmo for all pos. --- .../Physics/Collider/Collider.cs | 2 +- .../VPT/ColliderComponent.cs | 22 +++++++------------ .../VPT/Flipper/FlipperCollider.cs | 1 + .../VPT/Flipper/FlipperComponent.cs | 7 +++++- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs index 4f0a29ea8..3e840cc1c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs @@ -28,7 +28,7 @@ namespace VisualPinball.Unity ///
public struct Collider { - public const float Tolerance = 1e-7f; // 1e-9f; + public const float Tolerance = 1e-6f; // 1e-9f; public ColliderHeader Header; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 5523d3e9f..70c68344d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -122,12 +122,6 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa private PhysicsEngine _physicsEngine; private bool IsKinematic => this is IKinematicColliderComponent { IsKinematic: true }; - private float4x4 TransformWithinPlayfieldMatrix { get { - var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; - var m = ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)); - return playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)m; - }} - private void OnDrawGizmos() { Profiler.BeginSample("ItemColliderComponent.OnDrawGizmosSelected"); @@ -297,9 +291,9 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } foreach (var col in colliders.FlipperColliders) { if (col.Header.IsTransformed) { - AddFlipperCollider(vertices, normals, indices, float4x4.identity); + AddFlipperCollider(vertices, normals, indices, Origin.Global); } else { - AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, float4x4.identity); // TODO fix + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Original); } } foreach (var col in colliders.GateColliders) { @@ -377,10 +371,10 @@ private void GenerateColliderMesh(IEnumerable colliders) AddCollider(circleCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); break; case FlipperCollider { Header: { IsTransformed: true } }: - AddFlipperCollider(vertices, normals, indices, float4x4.identity); + AddFlipperCollider(vertices, normals, indices, Origin.Global); break; case FlipperCollider: - AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, TransformWithinPlayfieldMatrix); + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Global); break; case GateCollider gateCollider: AddCollider(gateCollider.LineSeg0, vertices, normals, indices); @@ -583,7 +577,7 @@ private static void AddCollider(TriangleCollider triangleCol, ICollection vertices, List normals, List indices, float4x4 matrix) + private void AddFlipperCollider(List vertices, List normals, List indices, Origin origin) { // first see if we already have a mesh var flipperComponent = GetComponentInChildren(); @@ -592,11 +586,11 @@ private void AddFlipperCollider(List vertices, List normals, L } var startIdx = vertices.Count; - var mesh = new FlipperMeshGenerator(flipperComponent).GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f, Origin.Global, false, 0.2f); + var mesh = new FlipperMeshGenerator(flipperComponent).GetMesh(FlipperMeshGenerator.Rubber, 0, 0.01f, origin, false, 0.2f); for (var i = 0; i < mesh.Vertices.Length; i++) { var vertex = mesh.Vertices[i]; - vertices.Add(matrix.MultiplyPoint(vertex.ToUnityFloat3())); - normals.Add(matrix.MultiplyVector(vertex.ToUnityNormalVector3())); + vertices.Add(vertex.ToUnityFloat3()); + normals.Add(vertex.ToUnityNormalVector3()); } indices.AddRange(mesh.Indices.Select(n => startIdx + n)); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index c9f0f4065..a29f9e8ce 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -19,6 +19,7 @@ using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; +using Logger = NLog.Logger; namespace VisualPinball.Unity { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 93975498d..a4ca64099 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -190,9 +190,14 @@ public override void UpdateTransforms() //t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); // rotation - t.localEulerAngles = new Vector3(0, StartAngle, 0); + // var currentRotation = transform.localEulerAngles; + // currentRotation.y = math.degrees(StartAngle); + // t.localEulerAngles = currentRotation; } + /// + /// Returns the local-to-world matrix of the flipper, but without the rotation around the Y-axis. + /// public float4x4 LocalToWorldPhysicsMatrix { get From 434f79bab7023ea6ab0b6dd5ce56e4e766e67c65 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 16 Nov 2024 01:35:51 +0100 Subject: [PATCH 084/208] refactor: Move movement translations into separate class. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 106 ++------------- .../Game/PhysicsMovements.cs | 125 ++++++++++++++++++ .../Game/PhysicsMovements.cs.meta | 3 + 3 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index a9fd42ef2..f89cbc6d6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -347,103 +347,15 @@ private void Update() #region Movements - // balls - using (var enumerator = state.Balls.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var ball = ref enumerator.Current.Value; - BallMovementPhysics.Move(ball, _transforms[ball.Id]); - } - } - - // flippers - using (var enumerator = _flipperStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var flipperState = ref enumerator.Current.Value; - var flipperTransform = _transforms[enumerator.Current.Key]; - flipperTransform.localRotation = quaternion.Euler(0, flipperState.Movement.Angle, 0); - } - } - - // bumpers - using (var enumerator = _bumperStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var bumperState = ref enumerator.Current.Value; - if (bumperState.SkirtItemId != 0) { - BumperTransform.UpdateSkirt(in bumperState.SkirtAnimation, _transforms[bumperState.SkirtItemId]); - } - if (bumperState.RingItemId != 0) { - BumperTransform.UpdateRing(bumperState.RingItemId, in bumperState.RingAnimation, _transforms[bumperState.RingItemId]); - } - } - } - - // drop targets - using (var enumerator = _dropTargetStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var dropTargetState = ref enumerator.Current.Value; - var dropTargetTransform = _transforms[dropTargetState.AnimatedItemId]; - var localPos = dropTargetTransform.localPosition; - dropTargetTransform.localPosition = new Vector3( - localPos.x, - Physics.ScaleToWorld(dropTargetState.Animation.ZOffset), - localPos.z - ); - } - } - - // hit targets - using (var enumerator = _hitTargetStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var hitTargetState = ref enumerator.Current.Value; - var hitTargetTransform = _transforms[hitTargetState.AnimatedItemId]; - var localRot = hitTargetTransform.localEulerAngles; - hitTargetTransform.localEulerAngles = new Vector3( - hitTargetState.Animation.XRotation, - localRot.y, - localRot.z - ); - } - } - - // gates - using (var enumerator = _gateStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var gateState = ref enumerator.Current.Value; - var component = _rotatableComponent[enumerator.Current.Key]; - component.OnRotationUpdated(gateState.Movement.Angle); - } - } - - // plungers - using (var enumerator = _plungerStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var plungerState = ref enumerator.Current.Value; - foreach (var skinnedMeshRenderer in _skinnedMeshRenderers[enumerator.Current.Key]) { - skinnedMeshRenderer.SetBlendShapeWeight(0, plungerState.Animation.Position); - } - } - } - - // spinners - using (var enumerator = _spinnerStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var spinnerState = ref enumerator.Current.Value; - var component = _rotatableComponent[enumerator.Current.Key]; - component.OnRotationUpdated(spinnerState.Movement.Angle); - } - } - - // triggers - using (var enumerator = _triggerStates.Ref.GetEnumerator()) { - while (enumerator.MoveNext()) { - ref var triggerState = ref enumerator.Current.Value; - if (triggerState.AnimatedItemId == 0) { - continue; - } - var triggerTransform = _transforms[triggerState.AnimatedItemId]; - TriggerTransform.Update(triggerState.AnimatedItemId, in triggerState.Movement, triggerTransform); - } - } + PhysicsMovements.ApplyBallMovement(ref state, _transforms); + PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, transform, _transforms); + PhysicsMovements.ApplyBumperMovement(ref _bumperStates.Ref, _transforms); + PhysicsMovements.ApplyDropTargetMovement(ref _dropTargetStates.Ref, _transforms); + PhysicsMovements.ApplyHitTargetMovement(ref _hitTargetStates.Ref, _transforms); + PhysicsMovements.ApplyGateMovement(ref _gateStates.Ref, _rotatableComponent); + PhysicsMovements.ApplyPlungerMovement(ref _plungerStates.Ref, _skinnedMeshRenderers); + PhysicsMovements.ApplySpinnerMovement(ref _spinnerStates.Ref, _rotatableComponent); + PhysicsMovements.ApplyTriggerMovement(ref _triggerStates.Ref, _transforms); #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs new file mode 100644 index 000000000..12438b548 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -0,0 +1,125 @@ +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; +using UnityEngine; + +namespace VisualPinball.Unity +{ + internal static class PhysicsMovements + { + + internal static void ApplyBallMovement(ref PhysicsState state, Dictionary transforms) + { + using var enumerator = state.Balls.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var ball = ref enumerator.Current.Value; + BallMovementPhysics.Move(ball, transforms[ball.Id]); + } + } + + internal static void ApplyFlipperMovement(ref NativeParallelHashMap flipperStates, + in Transform transform, Dictionary transforms) + { + using var enumerator = flipperStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var flipperState = ref enumerator.Current.Value; + var flipperTransform = transforms[enumerator.Current.Key]; + var currentRotation = transform.localEulerAngles; + currentRotation.y = math.degrees(flipperState.Movement.Angle); + flipperTransform.localEulerAngles = currentRotation; + } + } + + internal static void ApplyBumperMovement(ref NativeParallelHashMap bumperStates, Dictionary transforms) + { + using var enumerator = bumperStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var bumperState = ref enumerator.Current.Value; + if (bumperState.SkirtItemId != 0) { + BumperTransform.UpdateSkirt(in bumperState.SkirtAnimation, transforms[bumperState.SkirtItemId]); + } + if (bumperState.RingItemId != 0) { + BumperTransform.UpdateRing(bumperState.RingItemId, in bumperState.RingAnimation, transforms[bumperState.RingItemId]); + } + } + } + + internal static void ApplyDropTargetMovement(ref NativeParallelHashMap dropTargetStates, Dictionary transforms) + { + using var enumerator = dropTargetStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var dropTargetState = ref enumerator.Current.Value; + var dropTargetTransform = transforms[dropTargetState.AnimatedItemId]; + var localPos = dropTargetTransform.localPosition; + dropTargetTransform.localPosition = new Vector3( + localPos.x, + Physics.ScaleToWorld(dropTargetState.Animation.ZOffset), + localPos.z + ); + } + } + + internal static void ApplyHitTargetMovement(ref NativeParallelHashMap hitTargetStates, Dictionary transforms) + { + using var enumerator = hitTargetStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var hitTargetState = ref enumerator.Current.Value; + var hitTargetTransform = transforms[hitTargetState.AnimatedItemId]; + var localRot = hitTargetTransform.localEulerAngles; + hitTargetTransform.localEulerAngles = new Vector3( + hitTargetState.Animation.XRotation, + localRot.y, + localRot.z + ); + } + } + + internal static void ApplyGateMovement(ref NativeParallelHashMap gateStates, + Dictionary rotatableComponent) + { + using var enumerator = gateStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var gateState = ref enumerator.Current.Value; + var component = rotatableComponent[enumerator.Current.Key]; + component.OnRotationUpdated(gateState.Movement.Angle); + } + } + + internal static void ApplyPlungerMovement(ref NativeParallelHashMap plungerStates, + Dictionary skinnedMeshRenderers) + { + using var enumerator = plungerStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var plungerState = ref enumerator.Current.Value; + foreach (var skinnedMeshRenderer in skinnedMeshRenderers[enumerator.Current.Key]) { + skinnedMeshRenderer.SetBlendShapeWeight(0, plungerState.Animation.Position); + } + } + } + + internal static void ApplySpinnerMovement(ref NativeParallelHashMap spinnerStates, + Dictionary rotatableComponent) + { + using var enumerator = spinnerStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var spinnerState = ref enumerator.Current.Value; + var component = rotatableComponent[enumerator.Current.Key]; + component.OnRotationUpdated(spinnerState.Movement.Angle); + } + } + + internal static void ApplyTriggerMovement(ref NativeParallelHashMap triggerStates, + Dictionary transforms) + { + using var enumerator = triggerStates.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var triggerState = ref enumerator.Current.Value; + if (triggerState.AnimatedItemId == 0) { + continue; + } + var triggerTransform = transforms[triggerState.AnimatedItemId]; + TriggerTransform.Update(triggerState.AnimatedItemId, in triggerState.Movement, triggerTransform); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs.meta new file mode 100644 index 000000000..c4ce735f0 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 912ef5fa03a34e029fd64e23b9fa9c57 +timeCreated: 1731715324 \ No newline at end of file From dc00d6061f64a40d8a101d58c5a679946c586ae8 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 16 Nov 2024 02:08:27 +0100 Subject: [PATCH 085/208] flippers: Some progress with arbitrary rotations. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 8 ++++++-- .../VisualPinball.Unity/Game/PhysicsMovements.cs | 11 +++++++---- .../VisualPinball.Unity/VPT/Bumper/BumperComponent.cs | 2 +- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index f89cbc6d6..4a9d9894f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -70,6 +70,7 @@ public class PhysicsEngine : MonoBehaviour #region Transforms [NonSerialized] private readonly Dictionary _transforms = new(); + [NonSerialized] private readonly Dictionary _rotations = new(); /// /// Last transforms of kinematic items, so we can detect changes. @@ -149,7 +150,10 @@ internal void Register(T item) where T : MonoBehaviour } break; case BumperComponent c: _bumperStates.Ref[itemId] = c.CreateState(); break; - case FlipperComponent c: _flipperStates.Ref[itemId] = c.CreateState(); break; + case FlipperComponent c: + _flipperStates.Ref[itemId] = c.CreateState(); + _rotations.TryAdd(itemId, go.transform.rotation); + break; case GateComponent c: _gateStates.Ref[itemId] = c.CreateState(); break; case DropTargetComponent c: _dropTargetStates.Ref[itemId] = c.CreateState(); break; case HitTargetComponent c: _hitTargetStates.Ref[itemId] = c.CreateState(); break; @@ -348,7 +352,7 @@ private void Update() #region Movements PhysicsMovements.ApplyBallMovement(ref state, _transforms); - PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, transform, _transforms); + PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, _transforms, _rotations); PhysicsMovements.ApplyBumperMovement(ref _bumperStates.Ref, _transforms); PhysicsMovements.ApplyDropTargetMovement(ref _dropTargetStates.Ref, _transforms); PhysicsMovements.ApplyHitTargetMovement(ref _hitTargetStates.Ref, _transforms); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index 12438b548..a60e89f4f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -18,15 +18,18 @@ internal static void ApplyBallMovement(ref PhysicsState state, Dictionary flipperStates, - in Transform transform, Dictionary transforms) + Dictionary transforms, Dictionary rotations) { using var enumerator = flipperStates.GetEnumerator(); while (enumerator.MoveNext()) { ref var flipperState = ref enumerator.Current.Value; var flipperTransform = transforms[enumerator.Current.Key]; - var currentRotation = transform.localEulerAngles; - currentRotation.y = math.degrees(flipperState.Movement.Angle); - flipperTransform.localEulerAngles = currentRotation; + var flipperInitialRotation = rotations[enumerator.Current.Key]; + + // todo fix, and when done, optimize, i.e. just transform y for IsTransformed colliders + var xzRotation = Quaternion.Euler(flipperInitialRotation.eulerAngles.x, 0, flipperInitialRotation.eulerAngles.z); + var xRotation = Quaternion.Euler(0, math.degrees(flipperState.Movement.Angle), 0); + flipperTransform.rotation = xzRotation * xRotation; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 64113fe4f..03072af63 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -171,7 +171,7 @@ public override void UpdateTransforms() public void UpdateTransforms(Quaternion xz) { var y = Quaternion.Euler(0, Orientation, 0); - transform.rotation = xz * y; + transform.rotation = xz * y; // localRotation? } public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); From 504f544c8c36847c6a8b73d4b14b3a098796f098 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 17 Nov 2024 15:07:22 +0100 Subject: [PATCH 086/208] flippers: Fix z-rotation for arbitrary rotations. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 4 +--- .../Game/PhysicsMovements.cs | 20 +++++++++++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 4a9d9894f..04392de81 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -70,7 +70,6 @@ public class PhysicsEngine : MonoBehaviour #region Transforms [NonSerialized] private readonly Dictionary _transforms = new(); - [NonSerialized] private readonly Dictionary _rotations = new(); /// /// Last transforms of kinematic items, so we can detect changes. @@ -152,7 +151,6 @@ internal void Register(T item) where T : MonoBehaviour case BumperComponent c: _bumperStates.Ref[itemId] = c.CreateState(); break; case FlipperComponent c: _flipperStates.Ref[itemId] = c.CreateState(); - _rotations.TryAdd(itemId, go.transform.rotation); break; case GateComponent c: _gateStates.Ref[itemId] = c.CreateState(); break; case DropTargetComponent c: _dropTargetStates.Ref[itemId] = c.CreateState(); break; @@ -352,7 +350,7 @@ private void Update() #region Movements PhysicsMovements.ApplyBallMovement(ref state, _transforms); - PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, _transforms, _rotations); + PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, _transforms); PhysicsMovements.ApplyBumperMovement(ref _bumperStates.Ref, _transforms); PhysicsMovements.ApplyDropTargetMovement(ref _dropTargetStates.Ref, _transforms); PhysicsMovements.ApplyHitTargetMovement(ref _hitTargetStates.Ref, _transforms); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index a60e89f4f..f4625338b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -17,19 +17,23 @@ internal static void ApplyBallMovement(ref PhysicsState state, Dictionary flipperStates, - Dictionary transforms, Dictionary rotations) + internal static void ApplyFlipperMovement(ref NativeParallelHashMap flipperStates, Dictionary transforms) { using var enumerator = flipperStates.GetEnumerator(); while (enumerator.MoveNext()) { ref var flipperState = ref enumerator.Current.Value; - var flipperTransform = transforms[enumerator.Current.Key]; - var flipperInitialRotation = rotations[enumerator.Current.Key]; + var transform = transforms[enumerator.Current.Key]; - // todo fix, and when done, optimize, i.e. just transform y for IsTransformed colliders - var xzRotation = Quaternion.Euler(flipperInitialRotation.eulerAngles.x, 0, flipperInitialRotation.eulerAngles.z); - var xRotation = Quaternion.Euler(0, math.degrees(flipperState.Movement.Angle), 0); - flipperTransform.rotation = xzRotation * xRotation; + var m = transform.localToWorldMatrix; + var r = transform.localRotation.eulerAngles; + var localToWorldPhysicsMatrix = math.mul(m, math.inverse(float4x4.RotateY(math.radians(r.y)))); + var localToWorldPhysicsRotatedMatrix = math.mul(localToWorldPhysicsMatrix, float4x4.RotateY(flipperState.Movement.Angle)); + + var newUp = transform.localToWorldMatrix.MultiplyVector(Vector3.up); + var newForward = localToWorldPhysicsRotatedMatrix.MultiplyVector(Vector3.forward); + + var targetRotation = Quaternion.LookRotation(newForward, newUp); + transform.rotation = targetRotation; } } From c8873973cd113a75b60a3af2559aa299febed070 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 18 Nov 2024 22:34:32 +0100 Subject: [PATCH 087/208] refactor: Move y-rotation code to extension and slightly optimize it. --- .../Extensions/TransformExtensions.cs | 16 ++++++++++++++++ .../VisualPinball.Unity/Game/PhysicsMovements.cs | 11 +---------- .../VPT/Flipper/FlipperComponent.cs | 4 +--- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs index 84ed8b8ba..d96d998c4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.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 Unity.Mathematics; using UnityEngine; namespace VisualPinball.Unity @@ -33,5 +34,20 @@ public static void SetFromMatrix(this Transform tf, Matrix4x4 trs) trs.GetColumn(1) ); } + + public static void SetLocalYRotation(this Transform transform, float angleRad) + { + var localToWorldMatrix = transform.localToWorldMatrix; + var localRotationY = transform.localRotation.eulerAngles.y; + var inverseYRotation = math.inverse(float4x4.RotateY(math.radians(localRotationY))); + + var localToWorldPhysicsMatrix = math.mul(localToWorldMatrix, inverseYRotation); + var rotatedMatrix = math.mul(localToWorldPhysicsMatrix, float4x4.RotateY(angleRad)); + + var newUp = localToWorldMatrix.MultiplyVector(Vector3.up); + var newForward = rotatedMatrix.c2.xyz; // Extract forward direction from the matrix + + transform.rotation = Quaternion.LookRotation(newForward, newUp); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index f4625338b..414b15480 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -24,16 +24,7 @@ internal static void ApplyFlipperMovement(ref NativeParallelHashMap From 390a640b40aec0a90cab8303c4cfc178a3d150b2 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 18 Nov 2024 22:45:16 +0100 Subject: [PATCH 088/208] flippers: Fix transformation when flipper is arbitarly rotated and StartAngle is modified. --- .../VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 2f13a5bf6..0c7282847 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -64,11 +64,7 @@ public Vector2 Position { public float StartAngle { get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; - set { - var t = transform; - var e = t.localEulerAngles; - t.localEulerAngles = new Vector3(e.x, value, e.z); - } + set => transform.SetLocalYRotation(math.radians(value)); } [Range(-180f, 180f)] @@ -190,7 +186,7 @@ public override void UpdateTransforms() //t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); // rotation - t.SetLocalYRotation(math.radians(StartAngle)); + //t.SetLocalYRotation(math.radians(StartAngle)); } /// From 053920712b5b5d6dc24709af3ca08ad1566670d2 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 19 Nov 2024 00:14:04 +0100 Subject: [PATCH 089/208] refactor: Remove ICollidableNonTransformableComponent in favor of ICollidableComponent.TranslateWithinPlayfieldMatrix. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 9 +- .../VisualPinball.Unity/Physics/README.md | 6 + .../VPT/Bumper/BumperColliderComponent.cs | 4 +- .../VPT/ColliderComponent.cs | 12 +- .../VPT/Flipper/FlipperColliderComponent.cs | 4 +- .../HitTarget/DropTargetColliderComponent.cs | 4 + .../HitTarget/HitTargetColliderComponent.cs | 4 + .../VPT/ICollidableComponent.cs | 8 ++ .../ICollidableNonTransformableComponent.cs | 31 ----- ...ollidableNonTransformableComponent.cs.meta | 3 - .../VPT/Kicker/KickerColliderComponent.cs | 4 + .../MetalWireGuideColliderComponent.cs | 124 +++++++++--------- .../Playfield/PlayfieldColliderComponent.cs | 3 + .../VPT/Plunger/PlungerColliderComponent.cs | 6 +- .../Primitive/PrimitiveColliderComponent.cs | 3 + .../VPT/Ramp/RampColliderComponent.cs | 3 + .../VPT/Rubber/RubberColliderComponent.cs | 3 + .../VPT/Spinner/SpinnerColliderComponent.cs | 3 + .../VPT/Surface/SurfaceColliderComponent.cs | 8 +- .../VPT/Trigger/TriggerColliderComponent.cs | 2 + 20 files changed, 125 insertions(+), 119 deletions(-) delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 04392de81..1a8e2dbf8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -220,12 +220,9 @@ private void Start() _disabledCollisionItems.Ref.Add(colliderItem.ItemId); } - var translateWithinPlayfieldMatrix = colliderItem is ICollidableNonTransformableComponent nonTransformableColliderItem - ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix) - : float4x4.identity; - if (colliderItem is ICollidableNonTransformableComponent) { - _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; - } + var translateWithinPlayfieldMatrix = colliderItem.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix); + // todo check if we cannot only add those that are actually non-transformable + _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; colliderItem.GetColliders(_player, this, ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index db5821251..2520216ab 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -226,6 +226,12 @@ through the three use cases described above. setting the `IsTransformed` of the collider to `false`. It also stores the transformation matrix so it can be used during runtime for the ball transformation trick. +> [!NOTE] +> For already fully transformable items like primitives, the above checks are not necessary, and we +> transform the collider with its AABBs directly without checking the actual transformation. + + + ## Coordinate Systems Give the above, we'll be transforming between multiple coordinate systems, or *spaces*: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 21fc722ae..4500bd9e5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Bumper Collider")] - public class BumperColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent + public class BumperColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -53,7 +53,7 @@ public class BumperColliderComponent : ColliderComponent MainComponent.gameObject.GetInstanceID(); public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; - float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 70c68344d..feb560b4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -23,7 +23,6 @@ using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; -using Unity.VisualScripting.YamlDotNet.Serialization.NodeDeserializers; using UnityEditor; using UnityEngine; using UnityEngine.Profiling; @@ -68,6 +67,8 @@ public abstract class ColliderComponent : SubComponent false;// _colliderMesh != null && !_collidersDirty; @@ -143,9 +144,7 @@ private void OnDrawGizmos() var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; // todo optimize - var translateWithinPlayfieldMatrix = this is ICollidableNonTransformableComponent nonTransformableColliderItem - ? nonTransformableColliderItem.TranslateWithinPlayfieldMatrix(math.inverse(playfieldToWorld)) - : float4x4.identity; + var translateWithinPlayfieldMatrix = TranslateWithinPlayfieldMatrix(math.inverse(playfieldToWorld)); var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); @@ -297,8 +296,8 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } } foreach (var col in colliders.GateColliders) { - AddCollider(col.LineSeg0, vertices, normals, indices); - AddCollider(col.LineSeg1, vertices, normals, indices); + AddCollider(col.LineSeg0, vertices, normals, indices); + AddCollider(col.LineSeg1, vertices, normals, indices); } foreach (var col in colliders.LineColliders) { AddCollider(col, vertices, normals, indices); @@ -612,7 +611,6 @@ void ICollidableComponent.GetColliders(Player player, PhysicsEngine physicsEngin int ICollidableComponent.ItemId => MainComponent.gameObject.GetInstanceID(); bool ICollidableComponent.IsCollidable => isActiveAndEnabled; - } internal static class ColliderColor diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 97d7e83c3..25a77164f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Flipper Collider")] [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] - public class FlipperColliderComponent : ColliderComponent, ICollidableNonTransformableComponent + public class FlipperColliderComponent : ColliderComponent { #region Data @@ -162,7 +162,7 @@ public class FlipperColliderComponent : ColliderComponent MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); - float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs index bda149905..c4677c76f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs @@ -16,6 +16,7 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -62,5 +63,8 @@ public class DropTargetColliderComponent : ColliderComponent (MainComponent as DropTargetComponent)?.DropTargetApi ?? new DropTargetApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index 75d9bcf5f..3ff3dc3e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -16,6 +16,7 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -56,5 +57,8 @@ public class HitTargetColliderComponent : ColliderComponent (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 57d3b9fd9..a0d41a418 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -42,5 +42,13 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// collided with during gameplay. /// internal bool IsCollidable { get; } + + /// + /// The translation matrix, that will be applied in reverse to the ball + /// for hit testing and collision. + /// + /// The playfield's worldToLocal matrix. + /// + public float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs deleted file mode 100644 index 58c01a347..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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 . - -using Unity.Mathematics; - -namespace VisualPinball.Unity -{ - public interface ICollidableNonTransformableComponent : ICollidableComponent - { - /// - /// The translation matrix, that will be applied in reverse to the ball - /// for hit testing and collision. - /// - /// The playfield's worldToLocal matrix. - /// - internal float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta deleted file mode 100644 index 8f6d8b9b4..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableNonTransformableComponent.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: f11252e97bdd48619151f104f22693d3 -timeCreated: 1701003509 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index 535c77a39..46a925cc0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -16,6 +16,7 @@ // ReSharper disable InconsistentNaming +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Kicker; @@ -51,5 +52,8 @@ public class KickerColliderComponent : ColliderComponent GetPhysicsMaterialData(scatterAngleDeg: Scatter); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.KickerApi ?? new KickerApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs index 9dc0e0b4c..b93038a14 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs @@ -1,60 +1,64 @@ -// 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 . - -// ReSharper disable InconsistentNaming - -using UnityEngine; -using VisualPinball.Engine.VPT.MetalWireGuide; - -namespace VisualPinball.Unity -{ - [AddComponentMenu("Visual Pinball/Collision/MetalWireGuide Collider")] - public class MetalWireGuideColliderComponent : ColliderComponent - { - #region Data - - [Tooltip("If set, a hit event is triggered.")] - public bool HitEvent; - - [Tooltip("Z-axis translation for the collider mesh.")] - public float HitHeight = 25f; - - [Tooltip("Ignore the assigned physics material above and use the value below.")] - public bool OverwritePhysics; - - [Min(0f)] - [Tooltip("Bounciness, also known as coefficient of restitution. Higher is more bouncy.")] - public float Elasticity; - - [Min(0f)] - [Tooltip("How much to decrease elasticity for fast impacts.")] - public float ElasticityFalloff; - - [Min(0)] - [Tooltip("Friction of the material.")] - public float Friction; - - [Range(-90f, 90f)] - [Tooltip("When hit, add a random angle between 0 and this value to the trajectory.")] - public float Scatter; - - #endregion - - public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) - => MainComponent.MetalWireGuideApi ?? new MetalWireGuideApi(gameObject, player, physicsEngine); - } -} +// 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 . + +// ReSharper disable InconsistentNaming + +using Unity.Mathematics; +using UnityEngine; +using VisualPinball.Engine.VPT.MetalWireGuide; + +namespace VisualPinball.Unity +{ + [AddComponentMenu("Visual Pinball/Collision/MetalWireGuide Collider")] + public class MetalWireGuideColliderComponent : ColliderComponent + { + #region Data + + [Tooltip("If set, a hit event is triggered.")] + public bool HitEvent; + + [Tooltip("Z-axis translation for the collider mesh.")] + public float HitHeight = 25f; + + [Tooltip("Ignore the assigned physics material above and use the value below.")] + public bool OverwritePhysics; + + [Min(0f)] + [Tooltip("Bounciness, also known as coefficient of restitution. Higher is more bouncy.")] + public float Elasticity; + + [Min(0f)] + [Tooltip("How much to decrease elasticity for fast impacts.")] + public float ElasticityFalloff; + + [Min(0)] + [Tooltip("Friction of the material.")] + public float Friction; + + [Range(-90f, 90f)] + [Tooltip("When hit, add a random angle between 0 and this value to the trajectory.")] + public float Scatter; + + #endregion + + public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.MetalWireGuideApi ?? new MetalWireGuideApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs index 5176c90ba..423a3dbe5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Table; @@ -56,5 +57,7 @@ public class PlayfieldColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.PlayfieldApi ?? new PlayfieldApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => float4x4.identity; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index a3940d500..378e88a34 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Plunger Collider")] - public class PlungerColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent + public class PlungerColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -67,11 +67,9 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) -// => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index dd106ec44..042eece4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -73,5 +73,8 @@ public class PrimitiveColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) =>MainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index a5a2aa3df..872359ab1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -73,5 +73,8 @@ public class RampColliderComponent : ColliderComponent, public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction, scatterAngleDeg: Scatter, overwrite: OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.RampApi ?? new RampApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 0cd2f263c..7e240d21d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -68,5 +68,8 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index 2e56f4aa7..86fdd0208 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -37,6 +37,9 @@ public class SpinnerColliderComponent : ColliderComponent MainComponent.SpinnerApi ?? new SpinnerApi(gameObject, player, physicsEngine); + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + #region IKinematicColliderComponent [Tooltip("If set, transforming this object will transform the colliders as well.")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 2ce0e48b7..d89986b56 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Surface Collider")] - public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent, ICollidableNonTransformableComponent + public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -73,14 +73,14 @@ public class SurfaceColliderComponent : ColliderComponent MainComponent.SurfaceApi ?? new SurfaceApi(gameObject, player, physicsEngine); + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + #region IKinematicColliderComponent public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - float4x4 ICollidableNonTransformableComponent.TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index 32e77e891..49fce2c24 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -57,6 +57,8 @@ public class TriggerColliderComponent : ColliderComponent MainComponent.TriggerApi ?? new TriggerApi(gameObject, player, physicsEngine); + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); #region IKinematicColliderComponent From 747d44abfa916a2db9abbff459b003d6cc332958 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 19 Nov 2024 00:40:29 +0100 Subject: [PATCH 090/208] gate: Setup for free translation. --- .../Physics/Collider/CircleCollider.cs | 20 ++++--- .../Physics/Collider/ColliderReference.cs | 21 +++++-- .../VPT/ColliderComponent.cs | 11 +++- .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 8 ++- .../VPT/Gate/GateCollider.cs | 57 ++++++++++++++----- .../VPT/Gate/GateColliderComponent.cs | 3 + .../VPT/Gate/GateComponent.cs | 20 +++---- 7 files changed, 98 insertions(+), 42 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index dd937ec7e..8a62fdc2d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -30,6 +30,10 @@ public int Id public ColliderHeader Header; + /// + /// This is the center relative to the origin. For a bumper, the origin is at (0,0), + /// but for a gate it's at (45,0), for example. + /// public float2 Center; public float Radius; @@ -268,14 +272,14 @@ public CircleCollider Transform(float4x4 matrix) public CircleCollider TransformAabb(float4x4 matrix) { - var p1 = matrix.MultiplyPoint(new float3( Radius, Radius, ZLow)); - var p2 = matrix.MultiplyPoint(new float3( Radius, -Radius, ZLow)); - var p3 = matrix.MultiplyPoint(new float3(-Radius, Radius, ZLow)); - var p4 = matrix.MultiplyPoint(new float3(-Radius, -Radius, ZLow)); - var p5 = matrix.MultiplyPoint(new float3( Radius, Radius, ZHigh)); - var p6 = matrix.MultiplyPoint(new float3( Radius, -Radius, ZHigh)); - var p7 = matrix.MultiplyPoint(new float3(-Radius, Radius, ZHigh)); - var p8 = matrix.MultiplyPoint(new float3(-Radius, -Radius, ZHigh)); + var p1 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y + Radius, ZLow)); + var p2 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y - Radius, ZLow)); + var p3 = matrix.MultiplyPoint(new float3(Center.x - Radius, Center.y + Radius, ZLow)); + var p4 = matrix.MultiplyPoint(new float3(Center.x - Radius, Center.y - Radius, ZLow)); + var p5 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y + Radius, ZHigh)); + var p6 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y - Radius, ZHigh)); + var p7 = matrix.MultiplyPoint(new float3(Center.x - Radius, Center.y + Radius, ZHigh)); + var p8 = matrix.MultiplyPoint(new float3(Center.x - Radius, Center.y - Radius, ZHigh)); var min = math.min(p1, math.min(p2, math.min(p3, math.min(p4, math.min(p5, math.min(p6, math.min(p7, p8))))))); var max = math.max(p1, math.max(p2, math.max(p3, math.max(p4, math.max(p5, math.max(p6, math.max(p7, p8))))))); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 80d70f7b3..df0c6f531 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -229,9 +229,22 @@ internal int Add(FlipperCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(GateCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); - internal int Add(GateCollider collider) + internal int Add(GateCollider collider, float4x4 matrix) { + if (GateCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + } else { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Gate, GateColliders.Length)); @@ -382,10 +395,6 @@ internal int AddNonTransformable(PointCollider collider, float4x4 matrix) internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) => Add(collider.TransformAabb(matrix)); - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal int AddNonTransformable(CircleCollider collider, float4x4 matrix) - => Add(collider.TransformAabb(matrix)); - #endregion public ICollider[] ToArray() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index feb560b4a..0d6ae6811 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -296,8 +296,13 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } } foreach (var col in colliders.GateColliders) { + if (col.Header.IsTransformed) { AddCollider(col.LineSeg0, vertices, normals, indices); AddCollider(col.LineSeg1, vertices, normals, indices); + } else { + AddCollider(col.LineSeg0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(col.LineSeg1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.LineColliders) { AddCollider(col, vertices, normals, indices); @@ -375,10 +380,14 @@ private void GenerateColliderMesh(IEnumerable colliders) case FlipperCollider: AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Global); break; - case GateCollider gateCollider: + case GateCollider { Header: { IsTransformed: true } } gateCollider: AddCollider(gateCollider.LineSeg0, vertices, normals, indices); AddCollider(gateCollider.LineSeg1, vertices, normals, indices); break; + case GateCollider gateCollider: + AddCollider(gateCollider.LineSeg0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(gateCollider.LineSeg1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; case LineCollider lineCollider: AddCollider(lineCollider, vertices, normals, indices); break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index ea69a5be4..569d1ab18 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -24,6 +24,9 @@ namespace VisualPinball.Unity public class GateApi : CollidableApi, IApi, IApiHittable, IApiRotatable, IApiSwitch, IApiSwitchDevice { + + #region Members + /// /// Event emitted when the table is started. /// @@ -72,6 +75,8 @@ public class GateApi : CollidableApi $"GateCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; + #region Transformation + + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable + // scale: only uniform scale ("length") + // rotation: only around Z axis ("rotation") + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + var rotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + var uniformlyScaled = math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance; + return !rotated && uniformlyScaled; + } + + public void Transform(GateCollider collider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform gate."); + } + #endif + + LineSeg0 = collider.LineSeg0.Transform(matrix); + LineSeg1 = collider.LineSeg1.Transform(matrix); + Bounds = collider.LineSeg0.Bounds; + } + + public GateCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + + public GateCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + return this; + } + + #endregion + public override string ToString() => $"GateCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index b28449b90..5b83fcc8c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -70,6 +70,9 @@ public class GateColliderComponent : ColliderComponent, protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.GateApi ?? new GateApi(gameObject, player, physicsEngine); + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + #region IKinematicColliderComponent [Tooltip("If set, transforming this object will transform the colliders as well.")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index d66e0f9f6..e5d9b98db 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -166,16 +166,16 @@ private void Start() public override void UpdateTransforms() { base.UpdateTransforms(); - var t = transform; - - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, Position.z + PositionZ); - - // scale - t.localScale = new float3(Length * 0.01f); - - // rotation - t.localRotation = quaternion.RotateY(math.radians(Rotation)); + // var t = transform; + // + // // position + // t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, Position.z + PositionZ); + // + // // scale + // t.localScale = new float3(Length * 0.01f); + // + // // rotation + // t.localRotation = quaternion.RotateY(math.radians(Rotation)); } public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); From db22596a998f0eaa1a6a1b17e42ba4db0bd79032 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 20 Nov 2024 23:44:49 +0100 Subject: [PATCH 091/208] circle: Fix z-position of collider. --- .../Physics/Collider/CircleCollider.cs | 19 ++++++++++--------- .../VPT/Gate/GateColliderGenerator.cs | 14 ++++---------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 8a62fdc2d..ee04d9a5f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -47,8 +47,8 @@ public CircleCollider(float2 center, float radius, float zLow, float zHigh, Coll Header.Init(info, type); Center = center; Radius = radius; - ZHigh = zHigh; ZLow = zLow; + ZHigh = zHigh; Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( Center.x - Radius, @@ -247,6 +247,12 @@ public static bool IsTransformable(float4x4 matrix) return uniformScale && !xyRotated; } + public CircleCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + public void Transform(CircleCollider circle, float4x4 matrix) { #if UNITY_EDITOR @@ -258,16 +264,11 @@ public void Transform(CircleCollider circle, float4x4 matrix) TransformAabb(matrix); var s = matrix.GetScale(); + var t = matrix.GetTranslation(); Center = matrix.MultiplyPoint(new float3(circle.Center, 0)).xy; Radius = circle.Radius * s.x; - ZHigh = circle.ZHigh * s.z; - ZLow = circle.ZLow * s.z; - } - - public CircleCollider Transform(float4x4 matrix) - { - Transform(this, matrix); - return this; + ZHigh = t.z + circle.ZHigh * s.z; + ZLow = t.z + circle.ZLow * s.z; } public CircleCollider TransformAabb(float4x4 matrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 92caa2ac1..ef61ecc7b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -100,29 +100,23 @@ private void GenerateLineCollider(ref ColliderReference colliders) /// The colliders left and right to the other colliders, in case the bracket is shown. /// /// - /// private void GenerateBracketColliders(ref ColliderReference colliders) { const float halfLength = 45f; - var scale = _matrix.GetScale().x; - var height = 2f * PhysicsConstants.PhysSkin / scale; - var zPos = _matrix.GetTranslation().z / scale; - var zLow = zPos - height; - var zHigh = zPos; colliders.Add(new CircleCollider( new float2(halfLength, 0), 1f, - zLow, - zHigh, + ZLow, + 0, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate ), _matrix); colliders.Add(new CircleCollider( new float2(-halfLength, 0), 1f, - zLow, - zHigh, + ZLow, + 0, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate ), _matrix); } From 62ff69d5d066b33b24262b8fc5c770072ab175a1 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 20 Nov 2024 23:52:18 +0100 Subject: [PATCH 092/208] bumper: Read and write position from transformation. --- .../VPT/Bumper/BumperInspector.cs | 9 ++++++++- .../VisualPinball.Unity.Test/VPT/BumperTests.cs | 3 +-- .../VPT/Bumper/BumperComponent.cs | 16 +++++++++++++++- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index 5f413c659..465f68546 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -59,7 +59,14 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the bumper on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Bumper Position"); + MainComponent.Position = newPos; + } + PropertyField(_radiusProperty, updateTransforms: true); PropertyField(_heightScaleProperty, updateTransforms: true); PropertyField(_orientationProperty, "Orientation", ref _isChangingYRotation, diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/BumperTests.cs b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/BumperTests.cs index f2d7600c0..53d4d7208 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/BumperTests.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/BumperTests.cs @@ -51,8 +51,7 @@ public void ShouldWriteUpdatedBumperData() var bumper = go.transform.GetComponentsInChildren().First(c => c.gameObject.name == "Bumper2"); var bumperAuth = bumper.GetComponent(); - bumperAuth.Position.x = 128f; - bumperAuth.Position.y = 255f; + bumperAuth.Position = new Vector2(128f, 255f); go.GetComponent().TableContainer.Save(tmpFileName); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 03072af63..2dc2ed5cf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -40,7 +40,21 @@ public class BumperComponent : MainRenderableComponent, #region Data [Tooltip("Position of the bumper on the playfield.")] - public Vector2 Position; + public Vector2 Position { + get + { + var pos = transform.localPosition; + var posVpx = pos.TranslateToVpx(); + return new Vector2(posVpx.x, posVpx.y); + } + set + { + var posVpx = new Vector3(value.x, value.y, 0); + var pos = posVpx.TranslateToWorld(); + var t = transform; + t.localPosition = new Vector3(pos.x, t.localPosition.y, t.localPosition.z); + } + } [Range(20f, 250f)] [Tooltip("Radius of the bumper. Updates xy scaling. 50 = Original size.")] From 0a845e568d922bf7da5abf62c52eb8a2080e213a Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 20 Nov 2024 23:58:51 +0100 Subject: [PATCH 093/208] bumper: Read and write orientation from transformation. --- .../VPT/Bumper/BumperInspector.cs | 17 +++++++++-------- .../VPT/Bumper/BumperComponent.cs | 8 ++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index 465f68546..e300d080d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -32,9 +32,6 @@ public class BumperInspector : MainInspector private SerializedProperty _surfaceProperty; private SerializedProperty _isHardwiredProperty; - private bool _isChangingYRotation; - private Quaternion _initialXZRotation; - protected override void OnEnable() { base.OnEnable(); @@ -45,8 +42,6 @@ protected override void OnEnable() _orientationProperty = serializedObject.FindProperty(nameof(BumperComponent.Orientation)); _surfaceProperty = serializedObject.FindProperty(nameof(BumperComponent._surface)); _isHardwiredProperty = serializedObject.FindProperty(nameof(BumperComponent.IsHardwired)); - - _initialXZRotation = Quaternion.identity; } public override void OnInspectorGUI() @@ -69,9 +64,15 @@ public override void OnInspectorGUI() PropertyField(_radiusProperty, updateTransforms: true); PropertyField(_heightScaleProperty, updateTransforms: true); - PropertyField(_orientationProperty, "Orientation", ref _isChangingYRotation, - before => _initialXZRotation = Quaternion.Euler(before.eulerAngles.x, 0, before.eulerAngles.z), - () => MainComponent.UpdateTransforms(_initialXZRotation), updateTransforms: true); + + // orientation + EditorGUI.BeginChangeCheck(); + var newAngle = EditorGUILayout.Slider(new GUIContent("Orientation", "Orientation angle. Updates z rotation"), MainComponent.Orientation, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Bumper Orientation"); + MainComponent.Orientation = newAngle; + } + PropertyField(_surfaceProperty, updateTransforms: true); PropertyField(_isHardwiredProperty, updateTransforms: false); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 2dc2ed5cf..847928114 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -39,7 +39,6 @@ public class BumperComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the bumper on the playfield.")] public Vector2 Position { get { @@ -64,9 +63,10 @@ public Vector2 Position { [Tooltip("Height of the bumper. Updates z scaling. 100 = Original size.")] public float HeightScale = 45f; - [Range(-180f, 180f)] - [Tooltip("Orientation angle. Updates z rotation.")] - public float Orientation; + public float Orientation { + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; + set => transform.SetLocalYRotation(math.radians(value)); + } [Tooltip("Should the bumper coil always activate when touched by a ball? Disable to give game logic engine full control")] public bool IsHardwired = true; From 74a6893132b496ecd78de14e3cb6a45500c43035 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Nov 2024 00:08:25 +0100 Subject: [PATCH 094/208] bumper: Clear initial offsets to fix ring animation. --- .../VisualPinball.Unity/VPT/Bumper/BumperComponent.cs | 1 + .../VisualPinball.Unity/VPT/Bumper/BumperTransform.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 847928114..741a25b33 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -120,6 +120,7 @@ private void Awake() private void Start() { + BumperTransform.InitialOffset.Clear(); _playfieldToWorld = Player.PlayfieldToWorldMatrix; if (IsHardwired) { WireMapping wireMapping = new() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs index 62f84f244..dd701912a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity /// internal static class BumperTransform { - private static readonly Dictionary InitialOffset = new(); + internal static readonly Dictionary InitialOffset = new(); internal static void UpdateRing(int itemId, in BumperRingAnimationState state, Transform transform) { From bbe0997130d7633a0151b44545ca1b5c8739f38b Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Nov 2024 00:12:08 +0100 Subject: [PATCH 095/208] physics: Make movement classes non-static. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 21 ++++----- .../Game/PhysicsMovements.cs | 44 +++++++++++++------ .../VPT/Bumper/BumperComponent.cs | 1 - .../VPT/Bumper/BumperTransform.cs | 14 +++--- 4 files changed, 48 insertions(+), 32 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 1a8e2dbf8..e66ee2c7b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -99,9 +99,9 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private readonly List _scheduledActions = new(); [NonSerialized] private Player _player; + [NonSerialized] private PhysicsMovements _physicsMovements; [NonSerialized] private IKinematicColliderComponent[] _kinematicColliderComponents; - private static ulong NowUsec => (ulong)(Time.timeAsDouble * 1000000); #region API @@ -200,6 +200,7 @@ internal void DisableCollider(int itemId) private void Awake() { _player = GetComponentInParent(); + _physicsMovements = new PhysicsMovements(); _insideOfs = new InsideOfs(Allocator.Persistent); _physicsEnv.Ref[0] = new PhysicsEnv(NowUsec, GetComponentInChildren(), GravityStrength); _kinematicColliderComponents = GetComponentsInChildren(); @@ -346,15 +347,15 @@ private void Update() #region Movements - PhysicsMovements.ApplyBallMovement(ref state, _transforms); - PhysicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, _transforms); - PhysicsMovements.ApplyBumperMovement(ref _bumperStates.Ref, _transforms); - PhysicsMovements.ApplyDropTargetMovement(ref _dropTargetStates.Ref, _transforms); - PhysicsMovements.ApplyHitTargetMovement(ref _hitTargetStates.Ref, _transforms); - PhysicsMovements.ApplyGateMovement(ref _gateStates.Ref, _rotatableComponent); - PhysicsMovements.ApplyPlungerMovement(ref _plungerStates.Ref, _skinnedMeshRenderers); - PhysicsMovements.ApplySpinnerMovement(ref _spinnerStates.Ref, _rotatableComponent); - PhysicsMovements.ApplyTriggerMovement(ref _triggerStates.Ref, _transforms); + _physicsMovements.ApplyBallMovement(ref state, _transforms); + _physicsMovements.ApplyFlipperMovement(ref _flipperStates.Ref, _transforms); + _physicsMovements.ApplyBumperMovement(ref _bumperStates.Ref, _transforms); + _physicsMovements.ApplyDropTargetMovement(ref _dropTargetStates.Ref, _transforms); + _physicsMovements.ApplyHitTargetMovement(ref _hitTargetStates.Ref, _transforms); + _physicsMovements.ApplyGateMovement(ref _gateStates.Ref, _rotatableComponent); + _physicsMovements.ApplyPlungerMovement(ref _plungerStates.Ref, _skinnedMeshRenderers); + _physicsMovements.ApplySpinnerMovement(ref _spinnerStates.Ref, _rotatableComponent); + _physicsMovements.ApplyTriggerMovement(ref _triggerStates.Ref, _transforms); #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index 414b15480..d9ba41c10 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -1,14 +1,30 @@ -using System.Collections.Generic; +// 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 . + +using System.Collections.Generic; using Unity.Collections; -using Unity.Mathematics; using UnityEngine; namespace VisualPinball.Unity { - internal static class PhysicsMovements + internal class PhysicsMovements { + private readonly BumperTransform _bumperTransform = new(); - internal static void ApplyBallMovement(ref PhysicsState state, Dictionary transforms) + internal void ApplyBallMovement(ref PhysicsState state, Dictionary transforms) { using var enumerator = state.Balls.GetEnumerator(); while (enumerator.MoveNext()) { @@ -17,7 +33,7 @@ internal static void ApplyBallMovement(ref PhysicsState state, Dictionary flipperStates, Dictionary transforms) + internal void ApplyFlipperMovement(ref NativeParallelHashMap flipperStates, Dictionary transforms) { using var enumerator = flipperStates.GetEnumerator(); while (enumerator.MoveNext()) { @@ -28,21 +44,21 @@ internal static void ApplyFlipperMovement(ref NativeParallelHashMap bumperStates, Dictionary transforms) + internal void ApplyBumperMovement(ref NativeParallelHashMap bumperStates, Dictionary transforms) { using var enumerator = bumperStates.GetEnumerator(); while (enumerator.MoveNext()) { ref var bumperState = ref enumerator.Current.Value; if (bumperState.SkirtItemId != 0) { - BumperTransform.UpdateSkirt(in bumperState.SkirtAnimation, transforms[bumperState.SkirtItemId]); + _bumperTransform.UpdateSkirt(in bumperState.SkirtAnimation, transforms[bumperState.SkirtItemId]); } if (bumperState.RingItemId != 0) { - BumperTransform.UpdateRing(bumperState.RingItemId, in bumperState.RingAnimation, transforms[bumperState.RingItemId]); + _bumperTransform.UpdateRing(bumperState.RingItemId, in bumperState.RingAnimation, transforms[bumperState.RingItemId]); } } } - internal static void ApplyDropTargetMovement(ref NativeParallelHashMap dropTargetStates, Dictionary transforms) + internal void ApplyDropTargetMovement(ref NativeParallelHashMap dropTargetStates, Dictionary transforms) { using var enumerator = dropTargetStates.GetEnumerator(); while (enumerator.MoveNext()) { @@ -57,7 +73,7 @@ internal static void ApplyDropTargetMovement(ref NativeParallelHashMap hitTargetStates, Dictionary transforms) + internal void ApplyHitTargetMovement(ref NativeParallelHashMap hitTargetStates, Dictionary transforms) { using var enumerator = hitTargetStates.GetEnumerator(); while (enumerator.MoveNext()) { @@ -72,7 +88,7 @@ internal static void ApplyHitTargetMovement(ref NativeParallelHashMap gateStates, + internal void ApplyGateMovement(ref NativeParallelHashMap gateStates, Dictionary rotatableComponent) { using var enumerator = gateStates.GetEnumerator(); @@ -83,7 +99,7 @@ internal static void ApplyGateMovement(ref NativeParallelHashMap } } - internal static void ApplyPlungerMovement(ref NativeParallelHashMap plungerStates, + internal void ApplyPlungerMovement(ref NativeParallelHashMap plungerStates, Dictionary skinnedMeshRenderers) { using var enumerator = plungerStates.GetEnumerator(); @@ -95,7 +111,7 @@ internal static void ApplyPlungerMovement(ref NativeParallelHashMap spinnerStates, + internal void ApplySpinnerMovement(ref NativeParallelHashMap spinnerStates, Dictionary rotatableComponent) { using var enumerator = spinnerStates.GetEnumerator(); @@ -106,7 +122,7 @@ internal static void ApplySpinnerMovement(ref NativeParallelHashMap triggerStates, + internal void ApplyTriggerMovement(ref NativeParallelHashMap triggerStates, Dictionary transforms) { using var enumerator = triggerStates.GetEnumerator(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 741a25b33..847928114 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -120,7 +120,6 @@ private void Awake() private void Start() { - BumperTransform.InitialOffset.Clear(); _playfieldToWorld = Player.PlayfieldToWorldMatrix; if (IsHardwired) { WireMapping wireMapping = new() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs index dd701912a..3dcd70d12 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs @@ -22,24 +22,24 @@ namespace VisualPinball.Unity /// /// Applies the state to the scene, aka the transform of the game objects. /// - internal static class BumperTransform + internal class BumperTransform { - internal static readonly Dictionary InitialOffset = new(); + private readonly Dictionary _initialOffset = new(); - internal static void UpdateRing(int itemId, in BumperRingAnimationState state, Transform transform) + internal void UpdateRing(int itemId, in BumperRingAnimationState state, Transform transform) { var worldPos = transform.position; - InitialOffset.TryAdd(itemId, worldPos.y); + _initialOffset.TryAdd(itemId, worldPos.y); var limit = state.DropOffset + state.HeightScale * 0.5f; - var localLimit = InitialOffset[itemId] + limit; + var localLimit = _initialOffset[itemId] + limit; var localOffset = localLimit / limit * state.Offset; - worldPos.y = InitialOffset[itemId] + Physics.ScaleToWorld(localOffset); + worldPos.y = _initialOffset[itemId] + Physics.ScaleToWorld(localOffset); transform.position = worldPos; } - internal static void UpdateSkirt(in BumperSkirtAnimationState state, Transform transform) + internal void UpdateSkirt(in BumperSkirtAnimationState state, Transform transform) { var parentRotation = transform.parent.rotation; transform.rotation = Quaternion.Euler(state.Rotation.x, 0, -state.Rotation.y) * parentRotation; From aa8dfd03a9ea75a8139b06ac8791c9f2e3febcc1 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Nov 2024 00:19:25 +0100 Subject: [PATCH 096/208] gate: Fix z-rotation in editor. --- .../VisualPinball.Unity/VPT/Gate/GateComponent.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index e5d9b98db..2bab89925 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -49,11 +49,7 @@ public Vector3 Position { public float Rotation { get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; - set { - var t = transform; - var e = t.localEulerAngles; - t.localEulerAngles = new Vector3(e.x, value, e.z); - } + set => transform.SetLocalYRotation(math.radians(value)); } [Range(10f, 250f)] From 88654708c48fb8fa617c087fe047ec8703637f0a Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Nov 2024 23:24:32 +0100 Subject: [PATCH 097/208] spinner: Replace data with props from transformation. --- .../VPT/Spinner/SpinnerInspector.cs | 33 ++++++++++++++----- .../Extensions/MathExtensions.cs | 2 ++ .../VPT/Spinner/SpinnerComponent.cs | 27 +++++++++++---- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs index a39255b01..84b2f5718 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs @@ -17,6 +17,7 @@ // ReSharper disable AssignmentInConditionalExpression using UnityEditor; +using UnityEngine; using VisualPinball.Engine.VPT.Spinner; namespace VisualPinball.Unity.Editor @@ -26,9 +27,6 @@ public class SpinnerInspector : MainInspector { private bool _foldoutPhysics = true; - private SerializedProperty _positionProperty; - private SerializedProperty _heightProperty; - private SerializedProperty _rotationProperty; private SerializedProperty _lengthProperty; private SerializedProperty _dampingProperty; private SerializedProperty _angleMaxProperty; @@ -39,9 +37,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Position)); - _heightProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Height)); - _rotationProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Rotation)); _lengthProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Length)); _surfaceProperty = serializedObject.FindProperty(nameof(SpinnerComponent._surface)); _dampingProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Damping)); @@ -59,10 +54,30 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_heightProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the spinner on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Spinner Position"); + MainComponent.Position = newPos; + } + + EditorGUI.BeginChangeCheck(); + var newHeight = EditorGUILayout.FloatField(new GUIContent("Height", "Z-Position on the playfield, relative to its parent."), MainComponent.Height); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Spinner Height"); + MainComponent.Height = newHeight; + } + PropertyField(_lengthProperty, updateTransforms: true); - PropertyField(_rotationProperty, updateTransforms: true); + + EditorGUI.BeginChangeCheck(); + var newRotation = EditorGUILayout.Slider(new GUIContent("Rotation", "Z-Axis rotation of the spinner on the playfield."), MainComponent.Rotation, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Spinner Rotation"); + MainComponent.Rotation = newRotation; + } + PropertyField(_surfaceProperty, updateTransforms: true); if (_foldoutPhysics = EditorGUILayout.BeginFoldoutHeaderGroup(_foldoutPhysics, "Physics")) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index 43182ccb9..bb65e532f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -97,6 +97,8 @@ public static Vertex2D ToVertex2Dxy(this Vector3 vector) return new Vertex2D(vector.x, vector.y); } + public static Vector2 XY(this Vector3 vector) => new(vector.x, vector.y); + public static Vector3 ToUnityVector3(this Vertex3D vertex) { return new Vector3(vertex.X, vertex.Y, vertex.Z); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index a69773076..9c201d52e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -40,15 +40,28 @@ public class SpinnerComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the spinner on the playfield.")] - public Vector2 Position; + private Vector3 _position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } - [Tooltip("Z-Position on the playfield.")] - public float Height = 60f; + public Vector2 Position { + get => _position.XY(); + set => _position = new Vector3(value.x, value.y, Height); + } - [Range(-180f, 180f)] - [Tooltip("Z-Axis rotation of the spinner on the playfield.")] - public float Rotation; + public float Height { + get => _position.z; + set { + var pos = _position; + _position = new Vector3(pos.x, pos.y, value); + } + } + + public float Rotation { + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; + set => transform.SetLocalYRotation(math.radians(value)); + } [Min(0)] [Tooltip("Overall scaling of the spinner")] From afdd8f8f08cfbfc7edf7d0975fe4581cfb5bbdec Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 21 Nov 2024 23:54:43 +0100 Subject: [PATCH 098/208] bumper: Move height scale to transforms and clean up inspector. --- .../VPT/Bumper/BumperInspector.cs | 15 ++++---- .../VPT/Bumper/BumperComponent.cs | 38 ++++++++----------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index e300d080d..399af7ab5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -25,10 +25,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(BumperComponent)), CanEditMultipleObjects] public class BumperInspector : MainInspector { - private SerializedProperty _positionProperty; private SerializedProperty _radiusProperty; - private SerializedProperty _heightScaleProperty; - private SerializedProperty _orientationProperty; private SerializedProperty _surfaceProperty; private SerializedProperty _isHardwiredProperty; @@ -36,10 +33,7 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(BumperComponent.Position)); _radiusProperty = serializedObject.FindProperty(nameof(BumperComponent.Radius)); - _heightScaleProperty = serializedObject.FindProperty(nameof(BumperComponent.HeightScale)); - _orientationProperty = serializedObject.FindProperty(nameof(BumperComponent.Orientation)); _surfaceProperty = serializedObject.FindProperty(nameof(BumperComponent._surface)); _isHardwiredProperty = serializedObject.FindProperty(nameof(BumperComponent.IsHardwired)); } @@ -63,7 +57,14 @@ public override void OnInspectorGUI() } PropertyField(_radiusProperty, updateTransforms: true); - PropertyField(_heightScaleProperty, updateTransforms: true); + + // height scale + EditorGUI.BeginChangeCheck(); + var newHeightScale = EditorGUILayout.Slider(new GUIContent("Height Scale", "Height of the bumper. Updates z scaling. 100 = Original size."), MainComponent.HeightScale, 50f, 300f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Bumper Height Scale"); + MainComponent.HeightScale = newHeightScale; + } // orientation EditorGUI.BeginChangeCheck(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 847928114..e80a7c39d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -39,29 +39,26 @@ public class BumperComponent : MainRenderableComponent, { #region Data + private Vector3 _position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } + public Vector2 Position { - get - { - var pos = transform.localPosition; - var posVpx = pos.TranslateToVpx(); - return new Vector2(posVpx.x, posVpx.y); - } - set - { - var posVpx = new Vector3(value.x, value.y, 0); - var pos = posVpx.TranslateToWorld(); - var t = transform; - t.localPosition = new Vector3(pos.x, t.localPosition.y, t.localPosition.z); - } + get => _position.XY(); + set => _position = new Vector3(value.x, value.y, _position.z); } [Range(20f, 250f)] [Tooltip("Radius of the bumper. Updates xy scaling. 50 = Original size.")] public float Radius = 45f; - [Range(50f, 300f)] - [Tooltip("Height of the bumper. Updates z scaling. 100 = Original size.")] - public float HeightScale = 45f; + public float HeightScale + { + get => transform.localScale.y * DataMeshScale; + set => transform.localScale = new Vector3(transform.localScale.x, value / DataMeshScale, transform.localScale.z); + + } public float Orientation { get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; @@ -173,13 +170,8 @@ private void Start() public override void UpdateTransforms() { base.UpdateTransforms(); - var t = transform; - - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); - - // scale - t.localScale = new Vector3(Radius * 2f, HeightScale, Radius * 2f) / DataMeshScale; + // this is for when the radius is changed, in this case we syn x/z scale + transform.localScale = new Vector3(Radius * 2f, HeightScale, Radius * 2f) / DataMeshScale; } public void UpdateTransforms(Quaternion xz) From 4e6b7ff4b6e88c973e81a9399013259ea06052db Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 00:07:36 +0100 Subject: [PATCH 099/208] spinner: Use length through transformation. --- .../VPT/Spinner/SpinnerInspector.cs | 9 +++-- .../VPT/Spinner/SpinnerCollider.cs | 35 ++++++++++++++-- .../VPT/Spinner/SpinnerComponent.cs | 40 +++++++++++++------ 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs index 84b2f5718..5cb3650f7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs @@ -27,7 +27,6 @@ public class SpinnerInspector : MainInspector { private bool _foldoutPhysics = true; - private SerializedProperty _lengthProperty; private SerializedProperty _dampingProperty; private SerializedProperty _angleMaxProperty; private SerializedProperty _angleMinProperty; @@ -37,7 +36,6 @@ protected override void OnEnable() { base.OnEnable(); - _lengthProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Length)); _surfaceProperty = serializedObject.FindProperty(nameof(SpinnerComponent._surface)); _dampingProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Damping)); _angleMaxProperty = serializedObject.FindProperty(nameof(SpinnerComponent.AngleMax)); @@ -69,7 +67,12 @@ public override void OnInspectorGUI() MainComponent.Height = newHeight; } - PropertyField(_lengthProperty, updateTransforms: true); + EditorGUI.BeginChangeCheck(); + var newLength = EditorGUILayout.FloatField(new GUIContent("Length", "Overall scaling of the spinner, 80 = original size."), MainComponent.Length); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Spinner Length"); + MainComponent.Length = newLength; + } EditorGUI.BeginChangeCheck(); var newRotation = EditorGUILayout.Slider(new GUIContent("Rotation", "Z-Axis rotation of the spinner on the playfield."), MainComponent.Rotation, -180f, 180f); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index b57b99e59..cf3c1d65b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using Unity.Mathematics; -using UnityEngine; using VisualPinball.Engine.Common; namespace VisualPinball.Unity @@ -124,19 +123,49 @@ public static void Collide(in BallState ball, ref CollisionEventData collEvent, #endregion + #region Transformation + + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable + // scale: only uniform scale ("length") + // rotation: only around Z axis ("rotation") + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + var rotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + var uniformlyScaled = math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance; + + return !rotated && uniformlyScaled; + } + + public SpinnerCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + public void Transform(SpinnerCollider collider, float4x4 matrix) { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform spinner."); + } + #endif + LineSeg0 = collider.LineSeg0.Transform(matrix); LineSeg1 = collider.LineSeg1.Transform(matrix); Bounds = collider.LineSeg0.Bounds; } - public SpinnerCollider Transform(float4x4 matrix) + public SpinnerCollider TransformAabb(float4x4 matrix) { - Transform(this, matrix); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); return this; } + #endregion + public override string ToString() => $"SpinnerCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 9c201d52e..1e144fa33 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -63,9 +63,23 @@ public float Rotation { set => transform.SetLocalYRotation(math.radians(value)); } - [Min(0)] - [Tooltip("Overall scaling of the spinner")] - public float Length = 80f; + public float Length + { + get { + var scale = transform.localScale; + if (math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance) { + return scale.x * 80f; + } + return _length; + } + set { + _length = value; + var s = value / 80f; + transform.localScale = new Vector3(s, s, s); + } + } + + private float _length = 80f; [Range(0, 1f)] [Tooltip("Damping on each turn while moving.")] @@ -158,16 +172,16 @@ private void Start() public override void UpdateTransforms() { base.UpdateTransforms(); - var t = transform; - - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, HeightOnPlayfield); - - // scale - t.localScale = new float3(Length / 80f); - - // rotation - t.localRotation = quaternion.RotateY(math.radians(Rotation)); + // var t = transform; + + // // position + // t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, HeightOnPlayfield); + // + // // scale + // t.localScale = new float3(Length / 80f); + // + // // rotation + // t.localRotation = quaternion.RotateY(math.radians(Rotation)); } public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); From a13f6eba5e9c46065eb3ea978cd8aae643a6bf23 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 00:12:50 +0100 Subject: [PATCH 100/208] gate: Use length through transformation. --- .../VPT/Gate/GateInspector.cs | 11 ++++-- .../VPT/Gate/GateCollider.cs | 12 +++--- .../VPT/Gate/GateComponent.cs | 37 +++++++++---------- 3 files changed, 32 insertions(+), 28 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs index 414e8e79c..db6281bb5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs @@ -36,7 +36,6 @@ public class GateInspector : MainInspector { "Wire W", GateType.GateWireW }, }; - private SerializedProperty _lengthProperty; private SerializedProperty _surfaceProperty; private SerializedProperty _meshProperty; private SerializedProperty _typeProperty; @@ -47,7 +46,6 @@ protected override void OnEnable() { base.OnEnable(); - _lengthProperty = serializedObject.FindProperty(nameof(GateComponent._length)); _surfaceProperty = serializedObject.FindProperty(nameof(GateComponent._surface)); _meshProperty = serializedObject.FindProperty(nameof(GateComponent._meshName)); _typeProperty = serializedObject.FindProperty(nameof(GateComponent._type)); @@ -79,7 +77,14 @@ public override void OnInspectorGUI() MainComponent.Rotation = newAngle; } - PropertyField(_lengthProperty, updateTransforms: true); + // length + EditorGUI.BeginChangeCheck(); + var newLength = EditorGUILayout.Slider(new GUIContent("Length", "How much the gate is scaled, in percent."), MainComponent.Length, 10f, 250f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Gate Length"); + MainComponent.Length = newLength; + } + PropertyField(_surfaceProperty); var wire = MainComponent.transform.Find(GateComponent.WireObjectName); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 52a76c48a..75290d297 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -121,6 +121,12 @@ public static bool IsTransformable(float4x4 matrix) return !rotated && uniformlyScaled; } + public GateCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + public void Transform(GateCollider collider, float4x4 matrix) { #if UNITY_EDITOR @@ -134,12 +140,6 @@ public void Transform(GateCollider collider, float4x4 matrix) Bounds = collider.LineSeg0.Bounds; } - public GateCollider Transform(float4x4 matrix) - { - Transform(this, matrix); - return this; - } - public GateCollider TransformAabb(float4x4 matrix) { Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 2bab89925..518e5a2fc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -53,9 +53,25 @@ public float Rotation { } [Range(10f, 250f)] - [Tooltip("How much the gate is scaled, in percent.")] public float _length = 100f; + public float Length + { + get { + var scale = transform.localScale; + if (math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance) { + return scale.x * 100f; + } + return _length; + } + set { + _length = value; + var s = value / 100f; + transform.localScale = new Vector3(s, s, s); + } + } + + public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } [SerializeField] [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] @@ -73,8 +89,6 @@ public float Rotation { public float PosY => Position.y; public float Height => Position.z; - public float Length => _length; - public bool ShowBracket { get { foreach (var mf in GetComponentsInChildren()) { switch (mf.gameObject.name) { @@ -157,22 +171,7 @@ private void Start() public void OnSurfaceUpdated() => UpdateTransforms(); - public float PositionZ => SurfaceHeight(Surface, Position); - - public override void UpdateTransforms() - { - base.UpdateTransforms(); - // var t = transform; - // - // // position - // t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, Position.z + PositionZ); - // - // // scale - // t.localScale = new float3(Length * 0.01f); - // - // // rotation - // t.localRotation = quaternion.RotateY(math.radians(Rotation)); - } + public float PositionZ => SurfaceHeight(Surface, Position); // todo handle surface public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); From db9feed17dfe9dac704a78e89d3f74b7d88373ac Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 00:48:28 +0100 Subject: [PATCH 101/208] spinner: Update transformations. --- .../VisualPinball.Unity.Editor/Utils/Icons.cs | 1 + .../Physics/Collider/ColliderReference.cs | 17 +++++++++-- .../VPT/Gate/GateColliderGenerator.cs | 2 +- .../VPT/Spinner/SpinnerApi.cs | 3 +- .../SpinnerBracketColliderComponent.cs | 25 ++++++++++++++++ .../SpinnerBracketColliderComponent.cs.meta | 11 +++++++ .../VPT/Spinner/SpinnerCollider.cs | 21 ++------------ .../VPT/Spinner/SpinnerColliderGenerator.cs | 28 +++++++++++++++++- .../VPT/Spinner/SpinnerComponent.cs | 29 ++----------------- 9 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs index 1a9abde26..511614da8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs @@ -373,6 +373,7 @@ public void DisableGizmoIcons() Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); + Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index df0c6f531..441d27c50 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -309,9 +309,22 @@ internal int Add(PointCollider collider) return collider.Id; } - internal int Add(SpinnerCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); - internal int Add(SpinnerCollider collider) + internal int Add(SpinnerCollider collider, float4x4 matrix) { + if (SpinnerCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + } else { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Spinner, SpinnerColliders.Length)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index ef61ecc7b..00f19dd40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -65,7 +65,7 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat private void GenerateGateCollider(ref ColliderReference colliders) { // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct - // position, we generate them at the origin and then transform them later. + // position, we generate them relative to the origin and then transform them. const float halfLength = 10f; var v1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), 0); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs index a61c37707..83e2be6b4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs @@ -86,8 +86,7 @@ public SpinnerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : b protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); - var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent, matrix); + var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(ref kinematicColliders); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs new file mode 100644 index 000000000..b93d107fd --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs @@ -0,0 +1,25 @@ +// 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 . + +using UnityEngine; + +namespace VisualPinball.Unity +{ + [AddComponentMenu("Visual Pinball/Collision/Spinner Bracket Collider")] + public class SpinnerBracketColliderComponent : MonoBehaviour + { + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs.meta new file mode 100644 index 000000000..a4e5bb492 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerBracketColliderComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efccc06f60f04004844e441039036859 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 3b42d400cd06e8d4e98804233aa038a4, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index cf3c1d65b..b39e85cfb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -39,26 +39,11 @@ public int Id public ColliderBounds Bounds { get; private set; } - public SpinnerCollider(ColliderInfo info) : this() + public SpinnerCollider(in LineCollider lineSeg0, in LineCollider lineSeg1, ColliderInfo info) : this() { Header.Init(info, ColliderType.Spinner); - - const float halfLength = 40f; - - // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct - // position, we generate them at the origin and then transform them later. - var v1 = new float2( - - (halfLength + PhysicsConstants.PhysSkin), // through the edge of the - 0 // spinner - ); - var v2 = new float2( - halfLength + PhysicsConstants.PhysSkin, // oversize by the ball radius - 0 // this will prevent clipping - ); - - // todo probably broke surface - LineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, info); - LineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, info); + LineSeg0 = lineSeg0; + LineSeg1 = lineSeg1; Bounds = LineSeg0.Bounds; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs index 6cf661190..48775f2c0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs @@ -34,12 +34,38 @@ public SpinnerColliderGenerator(SpinnerApi spinnerApi, SpinnerComponent componen internal void GenerateColliders(ref ColliderReference colliders) { - colliders.Add(new SpinnerCollider(_api.GetColliderInfo()), _matrix); + GenerateSpinnerCollider(ref colliders); if (_component.ShowBracket) { GenerateBracketColliders(ref colliders); } } + /// + /// The collider that triggers the animation + /// + /// + private void GenerateSpinnerCollider(ref ColliderReference colliders) + { + const float halfLength = 40f; + + // note: this has diverged a bit from the vpx code: instead of generating the colliders at the correct + // position, we generate them relative to the origin and then transform them later. + var v1 = new float2( + - (halfLength + PhysicsConstants.PhysSkin), // through the edge of the + 0 // spinner + ); + var v2 = new float2( + halfLength + PhysicsConstants.PhysSkin, // oversize by the ball radius + 0 // this will prevent clipping + ); + + // todo probably broke surface + var lineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); + + colliders.Add(new SpinnerCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); + } + private void GenerateBracketColliders(ref ColliderReference colliders) { const float h = 30.0f + PhysicsConstants.PhysSkin; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 1e144fa33..69d861939 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -167,22 +167,7 @@ private void Start() public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); - public float HeightOnPlayfield => Height + PositionZ; - - public override void UpdateTransforms() - { - base.UpdateTransforms(); - // var t = transform; - - // // position - // t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, HeightOnPlayfield); - // - // // scale - // t.localScale = new float3(Length / 80f); - // - // // rotation - // t.localRotation = quaternion.RotateY(math.radians(Rotation)); - } + public float HeightOnPlayfield => Height + PositionZ; // todo handle surface public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); @@ -346,17 +331,7 @@ public override void SetEditorPosition(Vector3 pos) public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - public bool ShowBracket { - get { - foreach (var mf in GetComponentsInChildren()) { - switch (mf.sharedMesh.name) { - case BracketMeshName: - return mf.gameObject.activeInHierarchy; - } - } - return false; - } - } + public bool ShowBracket => GetComponentsInChildren().Any(); public override Vector3 GetEditorScale() => new Vector3(Length, 0f, 0f); public override void SetEditorScale(Vector3 scale) => Length = scale.x; From edb9cea33728c4a6ed7d0aff4b2fc057df3e1f37 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 00:59:11 +0100 Subject: [PATCH 102/208] spinner: Fix bracket collider position. --- .../VPT/Spinner/SpinnerColliderGenerator.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs index 48775f2c0..5d3f65f00 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs @@ -69,10 +69,7 @@ private void GenerateSpinnerCollider(ref ColliderReference colliders) private void GenerateBracketColliders(ref ColliderReference colliders) { const float h = 30.0f + PhysicsConstants.PhysSkin; - - // extract dimensions from translation matrix const float length = 80f; // 80 = size at scale 1 - var height = 0; /*add a hit shape for the bracket if shown, just in case if the bracket spinner height is low enough so the ball can hit it*/ const float halfLength = length * 0.5f + length * 0.1875f; @@ -80,16 +77,16 @@ private void GenerateBracketColliders(ref ColliderReference colliders) colliders.Add(new CircleCollider( new float2(halfLength, 0), length * 0.075f, - height, - height + h, + -h, + 0, _api.GetColliderInfo() ), _matrix); colliders.Add(new CircleCollider( new float2( -halfLength, 0), length * 0.075f, - height, - height + h, + -h, + 0, _api.GetColliderInfo() ), _matrix); } From cf663b2fa34756e19622d4075cfb8b0fb9df9483 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 08:25:11 +0100 Subject: [PATCH 103/208] plunger: Move z-adjust to transform. --- .../VPT/Plunger/PlungerInspector.cs | 12 ++++++-- .../VPT/Plunger/PlungerComponent.cs | 28 +++++++++---------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs index af8df1df6..2f5375c47 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs @@ -27,7 +27,6 @@ public class PlungerInspector : MainInspector { private SerializedProperty _widthProperty; private SerializedProperty _heightProperty; - private SerializedProperty _zAdjustProperty; private SerializedProperty _surfaceProperty; protected override void OnEnable() @@ -36,7 +35,6 @@ protected override void OnEnable() _widthProperty = serializedObject.FindProperty(nameof(PlungerComponent.Width)); _heightProperty = serializedObject.FindProperty(nameof(PlungerComponent.Height)); - _zAdjustProperty = serializedObject.FindProperty(nameof(PlungerComponent.ZAdjust)); _surfaceProperty = serializedObject.FindProperty(nameof(PlungerComponent._surface)); } @@ -60,7 +58,15 @@ public override void OnInspectorGUI() PropertyField(_widthProperty, rebuildMesh: true); PropertyField(_heightProperty, rebuildMesh: true); - PropertyField(_zAdjustProperty, rebuildMesh: true); + + // z-adjust + EditorGUI.BeginChangeCheck(); + var newZAdjust = EditorGUILayout.FloatField(new GUIContent("Z Adjustment", "The Z-Position of the plunger. VPX calls it like that."), MainComponent.ZAdjust); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Plunger Z Adjustment"); + MainComponent.ZAdjust = newZAdjust; + } + PropertyField(_surfaceProperty, rebuildMesh: true); base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 2a3ac32e4..68b67173d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -36,25 +36,27 @@ public class PlungerComponent : MainRenderableComponent, { #region Data + private Vector3 _position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } + public Vector2 Position { - get { - var pos = transform.localPosition; - var posVpx = pos.TranslateToVpx(); - return new Vector2(posVpx.x, posVpx.y); - } - set { - var posVpx = new Vector3(value.x, value.y, 0); - var pos = posVpx.TranslateToWorld(); - var t = transform; - t.localPosition = new Vector3(pos.x, t.localPosition.y, pos.z); - } + get => _position.XY(); + set => _position = new Vector3(value.x, value.y, Height); } public float Width = 25f; public float Height = 20f; - public float ZAdjust; + public float ZAdjust { + get => _position.z; + set { + var pos = _position; + _position = new Vector3(pos.x, pos.y, value); + } + } public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } [SerializeField] @@ -133,8 +135,6 @@ public float4x4 TransformationWithinPlayfield public override void UpdateTransforms() { base.UpdateTransforms(); - transform.localScale = Physics.ScaleToWorld(1, 1, 1); - transform.localRotation = Quaternion.Euler(Physics.RotateToWorld(0f, 0f, 0f)); GetComponent()?.CalculateBoundingBox(); GetComponent()?.CalculateBoundingBox(); From 58d93d6d98b27c92d7e5940ba4ee404de96ddd82 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 11:47:16 +0100 Subject: [PATCH 104/208] refactor: Add TranslateWithinPlayfieldMatrix to ColliderComponent and only override when different. --- .../Physics/Collider/CircleCollider.cs | 2 +- .../VPT/Bumper/BumperColliderComponent.cs | 3 --- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 3 ++- .../VisualPinball.Unity/VPT/Gate/GateCollider.cs | 3 ++- .../VPT/Gate/GateColliderComponent.cs | 3 --- .../VPT/HitTarget/DropTargetColliderComponent.cs | 3 --- .../VPT/HitTarget/HitTargetColliderComponent.cs | 3 --- .../VPT/Kicker/KickerColliderComponent.cs | 3 --- .../MetalWireGuideColliderComponent.cs | 3 --- .../VPT/Plunger/PlungerColliderComponent.cs | 3 --- .../VPT/Plunger/PlungerComponent.cs | 15 +-------------- .../VPT/Primitive/PrimitiveColliderComponent.cs | 3 --- .../VPT/Ramp/RampColliderComponent.cs | 3 --- .../VPT/Rubber/RubberColliderComponent.cs | 3 --- .../VPT/Spinner/SpinnerColliderComponent.cs | 3 --- .../VPT/Surface/SurfaceColliderComponent.cs | 3 --- .../VPT/Trigger/TriggerColliderComponent.cs | 3 --- 17 files changed, 6 insertions(+), 56 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index ee04d9a5f..59dd7f1bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -257,7 +257,7 @@ public void Transform(CircleCollider circle, float4x4 matrix) { #if UNITY_EDITOR if (!IsTransformable(matrix)) { - throw new System.InvalidOperationException($"Matrix {matrix} cannot transform flipper."); + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform circle collider."); } #endif diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 4500bd9e5..12881d017 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -53,9 +53,6 @@ public class BumperColliderComponent : ColliderComponent MainComponent.gameObject.GetInstanceID(); public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - #endregion protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 0d6ae6811..bbbe90bd9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -67,7 +67,8 @@ public abstract class ColliderComponent : SubComponent MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); public abstract PhysicsMaterialData PhysicsMaterialData { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 75290d297..0d9c26cf2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -147,6 +147,7 @@ public GateCollider TransformAabb(float4x4 matrix) } #endregion - public override string ToString() => $"GateCollider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; + + public override string ToString() => $"Gate$Collider[{Header.ItemId}] {LineSeg0.ToString()} | {LineSeg1.ToString()}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index 5b83fcc8c..b28449b90 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -70,9 +70,6 @@ public class GateColliderComponent : ColliderComponent, protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.GateApi ?? new GateApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - #region IKinematicColliderComponent [Tooltip("If set, transforming this object will transform the colliders as well.")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs index c4677c76f..c0a22d8c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs @@ -63,8 +63,5 @@ public class DropTargetColliderComponent : ColliderComponent (MainComponent as DropTargetComponent)?.DropTargetApi ?? new DropTargetApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index 3ff3dc3e0..641032af9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -57,8 +57,5 @@ public class HitTargetColliderComponent : ColliderComponent (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index 46a925cc0..340178027 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -52,8 +52,5 @@ public class KickerColliderComponent : ColliderComponent GetPhysicsMaterialData(scatterAngleDeg: Scatter); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.KickerApi ?? new KickerApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs index b93038a14..284d20121 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs @@ -57,8 +57,5 @@ public class MetalWireGuideColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.MetalWireGuideApi ?? new MetalWireGuideApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index 378e88a34..605b3df4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -67,9 +67,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 68b67173d..72c536255 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -135,7 +135,7 @@ public float4x4 TransformationWithinPlayfield public override void UpdateTransforms() { base.UpdateTransforms(); - + GetComponent()?.CalculateBoundingBox(); GetComponent()?.CalculateBoundingBox(); } @@ -145,19 +145,6 @@ private void Start() _playfieldToWorld = Player.PlayfieldToWorldMatrix; } - public float4x4 LocalToWorldPhysicsMatrix - { - get - { - return float4x4.Translate(transform.localPosition); - return float4x4.identity; - // var t = transform; - // var m = t.localToWorldMatrix; - // var r = t.localRotation.eulerAngles; - // return math.mul(m, math.inverse(float4x4.RotateY(math.radians(r.y)))); - } - } - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index 042eece4a..dd106ec44 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -73,8 +73,5 @@ public class PrimitiveColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) =>MainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index 872359ab1..a5a2aa3df 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -73,8 +73,5 @@ public class RampColliderComponent : ColliderComponent, public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction, scatterAngleDeg: Scatter, overwrite: OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.RampApi ?? new RampApi(gameObject, player, physicsEngine); - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 7e240d21d..0cd2f263c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -68,8 +68,5 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion - - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index 86fdd0208..2e56f4aa7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -37,9 +37,6 @@ public class SpinnerColliderComponent : ColliderComponent MainComponent.SpinnerApi ?? new SpinnerApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - #region IKinematicColliderComponent [Tooltip("If set, transforming this object will transform the colliders as well.")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index d89986b56..9e4868aef 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -73,9 +73,6 @@ public class SurfaceColliderComponent : ColliderComponent MainComponent.SurfaceApi ?? new SurfaceApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - #region IKinematicColliderComponent public bool IsKinematic => _isKinematic; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index 49fce2c24..917fc2368 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -57,9 +57,6 @@ public class TriggerColliderComponent : ColliderComponent MainComponent.TriggerApi ?? new TriggerApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); - #region IKinematicColliderComponent public bool IsKinematic => _isKinematic; From 333e2d75b399a5c9f2ff6a943f0531d47ae53ca9 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 12:05:20 +0100 Subject: [PATCH 105/208] plunger: Setup transformation matrices. --- .../Physics/Collider/ColliderReference.cs | 16 ++++++- .../VPT/ColliderComponent.cs | 19 ++++++-- .../VPT/Plunger/PlungerApi.cs | 4 +- .../VPT/Plunger/PlungerCollider.cs | 47 ++++++++++++++++++- 4 files changed, 78 insertions(+), 8 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 441d27c50..b2ee231ca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -290,8 +290,22 @@ internal int Add(LineZCollider collider) return collider.Id; } - internal int Add(PlungerCollider collider) + internal int Add(PlungerCollider collider, float4x4 matrix) { + if (PlungerCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + } else { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Plunger, PlungerColliders.Length)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index bbbe90bd9..278f9991a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -315,9 +315,15 @@ private void GenerateColliderMesh(ref ColliderReference colliders) _nonMeshColliders.Add(col); } foreach (var col in colliders.PlungerColliders) { - AddCollider(col.LineSegBase, vertices, normals, indices); - AddCollider(col.JointBase0, vertices, normals, indices); - AddCollider(col.JointBase1, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col.LineSegBase, vertices, normals, indices); + AddCollider(col.JointBase0, vertices, normals, indices); + AddCollider(col.JointBase1, vertices, normals, indices); + } else { + AddCollider(col.LineSegBase, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(col.JointBase0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(col.JointBase1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.SpinnerColliders) { AddCollider(col.LineSeg0, vertices, normals, indices); @@ -398,11 +404,16 @@ private void GenerateColliderMesh(IEnumerable colliders) case LineZCollider lineZCollider: _nonMeshColliders.Add(lineZCollider); break; - case PlungerCollider plungerCollider: + case PlungerCollider { Header: { IsTransformed: true } } plungerCollider: AddCollider(plungerCollider.LineSegBase, vertices, normals, indices); AddCollider(plungerCollider.JointBase0, vertices, normals, indices); AddCollider(plungerCollider.JointBase1, vertices, normals, indices); break; + case PlungerCollider plungerCollider: + AddCollider(plungerCollider.LineSegBase, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(plungerCollider.JointBase0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(plungerCollider.JointBase1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; case SpinnerCollider spinnerCollider: AddCollider(spinnerCollider.LineSeg0, vertices, normals, indices); AddCollider(spinnerCollider.LineSeg1, vertices, normals, indices); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 098db9aac..90ff80eef 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -147,9 +147,9 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { if (ColliderComponent._isKinematic) { - kinematicColliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()).TransformAabb(translateWithinPlayfieldMatrix)); + kinematicColliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()), translateWithinPlayfieldMatrix); } else { - colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()).TransformAabb(translateWithinPlayfieldMatrix)); + colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()), translateWithinPlayfieldMatrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index e1c5fc72d..2f0b74fd0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -63,10 +63,53 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, JointBase0 = new LineZCollider(new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info); JointBase1 = new LineZCollider(new float2(x2, y), zHeight, zHeight + Plunger.PlungerHeight, info); - TransformAabb(float4x4.identity); // Debug.Log($"Initial bounds: {Bounds}"); } + #region Transformation + + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable + // scale: none + // rotation: none + + var scale = matrix.GetScale(); + var rotation = matrix.GetRotationVector(); + + var rotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance || math.abs(rotation.z) > Collider.Tolerance; + var scaled = math.abs(1 - scale.x) > Collider.Tolerance || math.abs(1 - scale.y) > Collider.Tolerance || math.abs(1 - scale.z) > Collider.Tolerance; + + return !rotated && !scaled; + } + + public PlungerCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + + public void Transform(PlungerCollider collider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform plunger."); + } + #endif + + LineSegBase = collider.LineSegBase.Transform(matrix); + JointBase0 = collider.JointBase0.Transform(matrix); + JointBase1 = collider.JointBase1.Transform(matrix); + + //Bounds = collider.LineSeg0.Bounds; + } + + // public PlungerCollider TransformAabb(float4x4 matrix) + // { + // Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + // return this; + // } + public PlungerCollider TransformAabb(float4x4 matrix) { var x = -_size.x; @@ -91,6 +134,8 @@ public PlungerCollider TransformAabb(float4x4 matrix) return this; } + #endregion + #region Narrowphase public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, From edbf6831b120c6bd7300446895ec38fbcfa8f179 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 13:51:48 +0100 Subject: [PATCH 106/208] plunger: Generate mesh in world scale. --- .../VPT/Plunger/PlungerMeshGenerator.cs | 46 ++++++++++--------- .../VisualPinball.Unity/Physics/Physics.cs | 2 +- .../VPT/Plunger/PlungerRodMeshComponent.cs | 4 +- .../VPT/Plunger/PlungerSpringMeshComponent.cs | 4 +- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs b/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs index d54064934..1a237d9e3 100644 --- a/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Plunger/PlungerMeshGenerator.cs @@ -18,14 +18,18 @@ using System.Collections.Generic; using System.Linq; using NLog; +using UnityEngine; using VisualPinball.Engine.Game; using VisualPinball.Engine.Math; +using Logger = NLog.Logger; using MathF = VisualPinball.Engine.Math.MathF; namespace VisualPinball.Engine.VPT.Plunger { public class PlungerMeshGenerator { + private const float Scale = 1852.71f; + private const float ScaleInv = (float)(1 / (double)Scale); public const string Flat = "Flat"; public const string Rod = "Rod"; public const string Spring = "Spring"; @@ -371,13 +375,14 @@ public Vertex3DNoTex2[] BuildRodVertices(int frame) tv = vertices[m - 1].Tv + (tv - vertices[m - 1].Tv) * ratio; } + // we swap yz and scale to move it to world space vertices[i++] = new Vertex3DNoTex2 { - X = r * (sn * _data.Width), - Y = y, - Z = (r * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, + X = r * (sn * _data.Width) * ScaleInv, + Y = ((r * (cs * _data.Width) + _data.Width + _zHeight) * _zScale) * ScaleInv, + Z = -y * ScaleInv, Nx = c.nx * sn, - Ny = c.ny, - Nz = c.nx * cs, + Ny = c.nx * cs, + Nz = -c.ny, Tu = tu, Tv = tv }; @@ -511,37 +516,36 @@ public Vertex3DNoTex2[] BuildSpringVertices(int frame) // set the point on the front spiral vertices[pm++] = new Vertex3DNoTex2 { - X = _springRadius * (sn * _data.Width), - Y = y - _springGauge, - Z = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, + X = _springRadius * (sn * _data.Width) * ScaleInv, + Y = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale * ScaleInv, + Z = -(y - _springGauge) * ScaleInv, Nx = 0.0f, - Ny = -1.0f, - Nz = 0.0f, + Ny = 0.0f, + Nz = 1.0f, Tu = (sn + 1.0f) * 0.5f, Tv = 0.76f }; // set the point on the top spiral vertices[pm++] = new Vertex3DNoTex2 { - X = (_springRadius + springGaugeRel / 1.5f) * (sn * _data.Width), - Y = y, - Z = ((_springRadius + springGaugeRel / 1.5f) * (cs * _data.Width) + _data.Width + _zHeight) * - _zScale, + X = (_springRadius + springGaugeRel / 1.5f) * (sn * _data.Width) * ScaleInv, + Y = ((_springRadius + springGaugeRel / 1.5f) * (cs * _data.Width) + _data.Width + _zHeight) * _zScale * ScaleInv, + Z = -y * ScaleInv, Nx = sn, - Ny = 0.0f, - Nz = cs, + Ny = cs, + Nz = 0.0f, Tu = (sn + 1.0f) * 0.5f, Tv = 0.85f }; // set the point on the back spiral vertices[pm++] = new Vertex3DNoTex2 { - X = _springRadius * (sn * _data.Width), - Y = y + _springGauge, - Z = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale, + X = _springRadius * (sn * _data.Width) * ScaleInv, + Y = (_springRadius * (cs * _data.Width) + _data.Width + _zHeight) * _zScale * ScaleInv, + Z = -(y + _springGauge) * ScaleInv, Nx = 0.0f, - Ny = 1.0f, - Nz = 0.0f, + Ny = 0.0f, + Nz = -1.0f, Tu = (sn + 1.0f) * 0.5f, Tv = 0.98f }; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 33197f6ac..05e23e97f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -32,7 +32,7 @@ public static class Physics #region Definitions private const float Scale = 1852.71f; - private const float ScaleInv = (float)(1 / (double)Scale); + public const float ScaleInv = (float)(1 / (double)Scale); private static readonly float2 Translate = new(0, 0); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs index d12d0a654..5df8d48cc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs @@ -64,8 +64,8 @@ public void CalculateBoundingBox() var bounds = smr.localBounds; var ringOffset = (RingGap + RingWidth) / 2f; var radius = math.max(RodDiam, RingDiam) * plungerComp.Width / 2; - bounds.center = new Vector3(0, ringOffset - 40, 45); - bounds.extents = new Vector3(radius, 125f + ringOffset, radius); + bounds.center = new Vector3(0, 25, -(ringOffset - 40)) * Physics.ScaleInv; + bounds.extents = new Vector3(radius, radius, -(125f + ringOffset)) * Physics.ScaleInv; smr.localBounds = bounds; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs index 73553806d..d9bc04303 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs @@ -60,8 +60,8 @@ public void CalculateBoundingBox() var bounds = smr.localBounds; var ringOffset = rodComp != null ? rodComp.RingGap + rodComp.RingWidth : 0f; var radius = plungerComp.Width / 2 * SpringDiam + 2f; - bounds.center = new Vector3(0, ringOffset - 25, 45); - bounds.extents = new Vector3(radius, 110f, radius); + bounds.center = new Vector3(0, 25f, -(ringOffset - 25)) * Physics.ScaleInv; + bounds.extents = new Vector3(radius, radius, -110f) * Physics.ScaleInv; smr.localBounds = bounds; } } From 9d779756d493261b000ac2e4907148603fc5c58b Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 15:03:59 +0100 Subject: [PATCH 107/208] plunger: Fix bounding box. --- .../VPT/Plunger/PlungerCollider.cs | 37 ++++--------------- .../VPT/Plunger/PlungerRodMeshComponent.cs | 1 - 2 files changed, 8 insertions(+), 30 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index 2f0b74fd0..f8ccac042 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -63,7 +63,10 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, JointBase0 = new LineZCollider(new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info); JointBase1 = new LineZCollider(new float2(x2, y), zHeight, zHeight + Plunger.PlungerHeight, info); - // Debug.Log($"Initial bounds: {Bounds}"); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + new float3(-comp.Width - 10, comp.Height, 0), + new float3(comp.Width + 10, -100, 50) + )); } #region Transformation @@ -89,7 +92,7 @@ public PlungerCollider Transform(float4x4 matrix) return this; } - public void Transform(PlungerCollider collider, float4x4 matrix) + private void Transform(PlungerCollider collider, float4x4 matrix) { #if UNITY_EDITOR if (!IsTransformable(matrix)) { @@ -97,40 +100,16 @@ public void Transform(PlungerCollider collider, float4x4 matrix) } #endif + TransformAabb(matrix); + LineSegBase = collider.LineSegBase.Transform(matrix); JointBase0 = collider.JointBase0.Transform(matrix); JointBase1 = collider.JointBase1.Transform(matrix); - - //Bounds = collider.LineSeg0.Bounds; } - // public PlungerCollider TransformAabb(float4x4 matrix) - // { - // Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); - // return this; - // } - public PlungerCollider TransformAabb(float4x4 matrix) { - var x = -_size.x; - var x2 = _size.x; - var y = _size.y; - var frameEnd = -_stroke; - - var min = new float3(x - 0.1f, frameEnd - 0.1f, 0); - var max = new float3(x2 + 0.1f, y + 0.1f, Plunger.PlungerHeight); - - var p1 = matrix.MultiplyPoint(min); - var p2 = matrix.MultiplyPoint(max); - - var aabb = new Aabb(math.min(p1, p2), math.max(p1, p2)); - - Bounds = new ColliderBounds(Header.ItemId, Header.Id, aabb); - // Debug.Log($"pos: {matrix.GetTranslation()}"); - // Debug.Log($"rot: {matrix.GetRotationVector()}"); - // Debug.Log($"scale: {matrix.GetScale()}"); - // Debug.Log($"Transformed bounds: {Bounds}"); - + Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs index 5df8d48cc..2390e6854 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs @@ -16,7 +16,6 @@ // ReSharper disable InconsistentNaming -using System; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT; From ddcf331939dadb17032fa77b0906aa7e7812c820 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 22 Nov 2024 17:42:53 +0100 Subject: [PATCH 108/208] primitive: Update transformation code to be coherent with the rest. --- .../Physics/Collider/ColliderReference.cs | 73 ++++++++++--------- .../Physics/Collider/ColliderUtils.cs | 54 +++++--------- .../Physics/Collider/Line3DCollider.cs | 20 +++-- .../Physics/Collider/PointCollider.cs | 14 ++-- .../Physics/Collider/TriangleCollider.cs | 16 ++-- .../VisualPinball.Unity/Physics/Physics.cs | 13 +--- .../VPT/ColliderComponent.cs | 66 ++++++++++++++--- .../VPT/Primitive/PrimitiveApi.cs | 2 +- .../Primitive/PrimitiveColliderComponent.cs | 3 + .../Primitive/PrimitiveColliderGenerator.cs | 10 ++- .../VPT/Surface/SurfaceColliderGenerator.cs | 16 ++-- .../VPT/Trigger/TriggerColliderGenerator.cs | 2 +- 12 files changed, 164 insertions(+), 125 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index b2ee231ca..3f5ebb03d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -252,8 +252,19 @@ internal int Add(GateCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(Line3DCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); + internal int Add(Line3DCollider collider, float4x4 matrix) + { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + collider.Id = Lookups.Length; + TrackReference(collider.Header.ItemId, collider.Header.Id); + Lookups.Add(new ColliderLookup(ColliderType.Line3D, Line3DColliders.Length)); + Line3DColliders.Add(collider); + return collider.Id; + } + [Obsolete("Add with matrix only.")] internal int Add(Line3DCollider collider) { collider.Id = Lookups.Length; @@ -313,7 +324,19 @@ internal int Add(PlungerCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(PointCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); + internal int Add(PointCollider collider, float4x4 matrix) + { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + collider.Id = Lookups.Length; + TrackReference(collider.Header.ItemId, collider.Header.Id); + Lookups.Add(new ColliderLookup(ColliderType.Point, PointColliders.Length)); + PointColliders.Add(collider); + return collider.Id; + } + + [Obsolete("Add with matrix only.")] internal int Add(PointCollider collider) { collider.Id = Lookups.Length; @@ -346,7 +369,19 @@ internal int Add(SpinnerCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(TriangleCollider collider, float4x4 matrix) => Add(collider.Transform(matrix)); + internal int Add(TriangleCollider collider, float4x4 matrix) + { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + collider.Id = Lookups.Length; + TrackReference(collider.Header.ItemId, collider.Header.Id); + Lookups.Add(new ColliderLookup(ColliderType.Triangle, TriangleColliders.Length)); + TriangleColliders.Add(collider); + return collider.Id; + } + + [Obsolete("Add with matrix only.")] internal int Add(TriangleCollider collider) { collider.Id = Lookups.Length; @@ -392,38 +427,6 @@ internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, fl #endregion - #region Add non-transformable - - // Non-transformable items are collider components that implement ICollidableNonTransformableComponent. - // Colliders of these components are not transformed by the matrix of the parent component (only their - // AABBs are, in order not to break the broad phase). When colliding, it's the ball that is transformed - // to the local space of the collider. - // The physics engine will do this transformation automatically if the state has a non-transformable - // matrix of the collider, which is the case if the component implements ICollidableNonTransformableComponent - // (see PhysicsEngine.Start()) - - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal void AddNonTransformableLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - => Add(new LineZCollider(xy, zLow, zHigh, info).TransformAabb(matrix)); - - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal void AddNonTransformableLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - => Add(new LineCollider(v1, v2, zLow, zHigh, info).TransformAabb(matrix)); - - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal int AddNonTransformable(Line3DCollider collider, float4x4 matrix) - => Add(collider.TransformAabb(matrix)); - - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal int AddNonTransformable(PointCollider collider, float4x4 matrix) - => Add(collider.TransformAabb(matrix)); - - [Obsolete("Just add with matrix and it'll figure out whether to make it non-transformable.")] - internal int AddNonTransformable(TriangleCollider collider, float4x4 matrix) - => Add(collider.TransformAabb(matrix)); - - #endregion - public ICollider[] ToArray() { var array = new ICollider[Lookups.Length]; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index 68fe55a62..354510d1b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -28,7 +28,7 @@ public static class ColliderUtils private static readonly ProfilerMarker PerfMarker1 = new("ColliderUtils.GenerateCollidersFromMesh.ICollider"); private static readonly ProfilerMarker PerfMarker2 = new("ColliderUtils.GenerateCollidersFromMesh.NativeArray"); - public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool isNonTransformableParent) + public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix) { var inputVerts = new float2[rgv.Length]; @@ -46,10 +46,10 @@ public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, r } var mesh = new Mesh(triangulatedVerts, outputIndices); - GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false, isNonTransformableParent); + GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false); } - public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false, bool isNonTransformableParent = false) + public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false) { PerfMarker1.Begin(); var addedEdges = EdgeSet.Get(Allocator.TempJob); @@ -66,33 +66,17 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref C var rgv1 = mesh.Vertices[i1].GetVertex().ToUnityFloat3(); var rgv2 = mesh.Vertices[i2].GetVertex().ToUnityFloat3(); - if (isNonTransformableParent) { - colliders.AddNonTransformable(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); - } else { - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); - } + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); if (!onlyTriangles) { if (addedEdges.ShouldAddHitEdge(i0, i1)) { - if (isNonTransformableParent) { - colliders.AddNonTransformable(new Line3DCollider(rgv0, rgv2, info), matrix); - } else { - colliders.Add(new Line3DCollider(rgv0, rgv2, info), matrix); - } + colliders.Add(new Line3DCollider(rgv0, rgv2, info), matrix); } if (addedEdges.ShouldAddHitEdge(i1, i2)) { - if (isNonTransformableParent) { - colliders.AddNonTransformable(new Line3DCollider(rgv2, rgv1, info), matrix); - } else { - colliders.Add(new Line3DCollider(rgv2, rgv1, info), matrix); - } + colliders.Add(new Line3DCollider(rgv2, rgv1, info), matrix); } if (addedEdges.ShouldAddHitEdge(i2, i0)) { - if (isNonTransformableParent) { - colliders.AddNonTransformable(new Line3DCollider(rgv1, rgv0, info), matrix); - } else { - colliders.Add(new Line3DCollider(rgv1, rgv0, info), matrix); - } + colliders.Add(new Line3DCollider(rgv1, rgv0, info), matrix); } } } @@ -101,17 +85,13 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref C // add collision vertices if (!onlyTriangles) { foreach (var vertex in mesh.Vertices) { - if (isNonTransformableParent) { - colliders.AddNonTransformable(new PointCollider(vertex.ToUnityFloat3(), info), matrix); - } else { - colliders.Add(new PointCollider(vertex.ToUnityFloat3(), info), matrix); - } + colliders.Add(new PointCollider(vertex.ToUnityFloat3(), info), matrix); } } PerfMarker1.End(); } - public static void GenerateCollidersFromMesh(in NativeArray vertices, in NativeArray indices, ref Matrix4x4 matrix, ColliderInfo info, ref ColliderReference colliders, bool onlyTriangles = false) + public static void GenerateCollidersFromMesh(in NativeArray vertices, in NativeArray indices, float4x4 matrix, ColliderInfo info, ref ColliderReference colliders, bool onlyTriangles = false) { PerfMarker2.Begin(); var addedEdges = EdgeSet.Get(Allocator.TempJob, vertices.Length); @@ -123,22 +103,22 @@ public static void GenerateCollidersFromMesh(in NativeArray vertices, i var i2 = indices[i + 2]; // NB: HitTriangle wants CCW vertices, but for rendering we have them in CW order - var rgv0 = matrix.MultiplyPoint(vertices[i0]); - var rgv1 = matrix.MultiplyPoint(vertices[i1]); - var rgv2 = matrix.MultiplyPoint(vertices[i2]); + var rgv0 = vertices[i0]; + var rgv1 = vertices[i1]; + var rgv2 = vertices[i2]; - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info)); + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info), matrix); if (!onlyTriangles) { if (addedEdges.ShouldAddHitEdge(i0, i1)) { - colliders.Add(new Line3DCollider(rgv0, rgv2, info)); + colliders.Add(new Line3DCollider(rgv0, rgv2, info), matrix); } if (addedEdges.ShouldAddHitEdge(i1, i2)) { - colliders.Add(new Line3DCollider(rgv2, rgv1, info)); + colliders.Add(new Line3DCollider(rgv2, rgv1, info), matrix); } if (addedEdges.ShouldAddHitEdge(i2, i0)) { - colliders.Add(new Line3DCollider(rgv1, rgv0, info)); + colliders.Add(new Line3DCollider(rgv1, rgv0, info), matrix); } } } @@ -146,7 +126,7 @@ public static void GenerateCollidersFromMesh(in NativeArray vertices, i // add collision vertices if (!onlyTriangles) { foreach (var vertex in vertices) { - colliders.Add(new PointCollider(matrix.MultiplyPoint(vertex), info)); + colliders.Add(new PointCollider(vertex, info), matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index 9a55c7778..ae69c2938 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -139,7 +139,13 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite } } - public override string ToString() => $"Line3DCollider[{Header.ItemId}] ({_xy.x}/{_xy.y} | {_zLow} -> {_zHigh})"; + #region Transformation + + public Line3DCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } public void Transform(Line3DCollider line3D, float4x4 matrix) { @@ -147,12 +153,7 @@ public void Transform(Line3DCollider line3D, float4x4 matrix) math.mul(matrix, new float4(line3D._v1, 1f)).xyz, math.mul(matrix, new float4(line3D._v2, 1f)).xyz ); - } - - public Line3DCollider Transform(float4x4 matrix) - { - Transform(this, matrix); - return this; + // the above also transforms the aabbs. } public Line3DCollider TransformAabb(float4x4 matrix) @@ -161,8 +162,11 @@ public Line3DCollider TransformAabb(float4x4 matrix) var p2 = matrix.MultiplyPoint(_v2); Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); - return this; } + + #endregion + + public override string ToString() => $"Line3DCollider[{Header.ItemId}] ({_xy.x}/{_xy.y} | {_zLow} -> {_zHigh})"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index bf465f298..74969b486 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -134,11 +134,7 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrit public override string ToString() => $"PointCollider[{Header.ItemId}] ({P.x}/{P.y}/{P.z})"; - public void Transform(PointCollider point, float4x4 matrix) - { - P = matrix.MultiplyPoint(point.P); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(P, P)); - } + #region Transformation public PointCollider Transform(float4x4 matrix) { @@ -146,11 +142,19 @@ public PointCollider Transform(float4x4 matrix) return this; } + public void Transform(PointCollider point, float4x4 matrix) + { + P = matrix.MultiplyPoint(point.P); + TransformAabb(matrix); + } + public PointCollider TransformAabb(float4x4 matrix) { var p = matrix.MultiplyPoint(P); Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(p, p)); return this; } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index b637a75b5..20ca7115a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -177,6 +177,14 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite #endregion + #region Transformation + + public TriangleCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + public void Transform(TriangleCollider triangle, float4x4 matrix) { Rgv0 = math.mul(matrix, new float4(triangle.Rgv0, 1f)).xyz; @@ -186,12 +194,6 @@ public void Transform(TriangleCollider triangle, float4x4 matrix) CalculateBounds(); } - public TriangleCollider Transform(float4x4 matrix) - { - Transform(this, matrix); - return this; - } - public TriangleCollider TransformAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(Rgv0); @@ -217,5 +219,7 @@ private void CalculateBounds() math.max(Rgv0.z, math.max(Rgv1.z, Rgv2.z)) )); } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 05e23e97f..4fa6e5ecb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -84,22 +84,13 @@ public static class Physics /// Local-to-World transformation matrix of the playfield. /// Transformation matrix of the item in VPX space. public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) - => math.mul( - math.mul(WorldToVpx, - math.inverse(math.mul(worldToLocal, playfieldToWorld)) - ), - VpxToWorld); + => math.mul(math.mul(WorldToVpx, math.inverse(math.mul(worldToLocal, playfieldToWorld))), VpxToWorld); public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) => LocalToWorldTranslateWithinPlayfield((float4x4)localToWorldMatrix, worldToPlayfield); public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorldMatrix, float4x4 worldToPlayfield) - => math.mul( - math.mul(WorldToVpx, - math.mul(worldToPlayfield, localToWorldMatrix) - ), - VpxToWorld - ); + => math.mul(math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorldMatrix)), VpxToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 278f9991a..8b92f1cfe 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -306,10 +306,18 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } } foreach (var col in colliders.LineColliders) { - AddCollider(col, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col, vertices, normals, indices); + } else { + AddCollider(col, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.LineSlingshotColliders) { - AddCollider(col, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col, vertices, normals, indices); + } else { + AddCollider(col, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.LineZColliders) { _nonMeshColliders.Add(col); @@ -326,11 +334,20 @@ private void GenerateColliderMesh(ref ColliderReference colliders) } } foreach (var col in colliders.SpinnerColliders) { - AddCollider(col.LineSeg0, vertices, normals, indices); - AddCollider(col.LineSeg1, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col.LineSeg0, vertices, normals, indices); + AddCollider(col.LineSeg1, vertices, normals, indices); + } else { + AddCollider(col.LineSeg0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(col.LineSeg1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } foreach (var col in colliders.TriangleColliders) { - AddCollider(col, vertices, normals, indices); + if (col.Header.IsTransformed) { + AddCollider(col, vertices, normals, indices); + } else { + AddCollider(col, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + } } // todo Line3DCollider @@ -375,18 +392,24 @@ private void GenerateColliderMesh(IEnumerable colliders) foreach (var coll in colliders) { switch (coll) { + + // circle collider case CircleCollider { Header: { IsTransformed: true } } circleCollider: AddCollider(circleCollider, vertices, normals, indices); break; case CircleCollider circleCollider: AddCollider(circleCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); break; + + // flipper collider case FlipperCollider { Header: { IsTransformed: true } }: AddFlipperCollider(vertices, normals, indices, Origin.Global); break; case FlipperCollider: AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Global); break; + + // gate collider case GateCollider { Header: { IsTransformed: true } } gateCollider: AddCollider(gateCollider.LineSeg0, vertices, normals, indices); AddCollider(gateCollider.LineSeg1, vertices, normals, indices); @@ -395,15 +418,29 @@ private void GenerateColliderMesh(IEnumerable colliders) AddCollider(gateCollider.LineSeg0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); AddCollider(gateCollider.LineSeg1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); break; - case LineCollider lineCollider: + + // line collider + case LineCollider { Header: { IsTransformed: true } } lineCollider: AddCollider(lineCollider, vertices, normals, indices); break; - case LineSlingshotCollider lineSlingshotCollider: + case LineCollider lineCollider: + AddCollider(lineCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; + + // line slingshot collider + case LineSlingshotCollider { Header: { IsTransformed: true } } lineSlingshotCollider: AddCollider(lineSlingshotCollider, vertices, normals, indices); break; + case LineSlingshotCollider lineSlingshotCollider: + AddCollider(lineSlingshotCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; + + // line z collider case LineZCollider lineZCollider: _nonMeshColliders.Add(lineZCollider); break; + + // plunger collider case PlungerCollider { Header: { IsTransformed: true } } plungerCollider: AddCollider(plungerCollider.LineSegBase, vertices, normals, indices); AddCollider(plungerCollider.JointBase0, vertices, normals, indices); @@ -414,13 +451,24 @@ private void GenerateColliderMesh(IEnumerable colliders) AddCollider(plungerCollider.JointBase0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); AddCollider(plungerCollider.JointBase1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); break; - case SpinnerCollider spinnerCollider: + + // spinner collider + case SpinnerCollider { Header: { IsTransformed: true } } spinnerCollider: AddCollider(spinnerCollider.LineSeg0, vertices, normals, indices); AddCollider(spinnerCollider.LineSeg1, vertices, normals, indices); break; - case TriangleCollider triangleCollider: + case SpinnerCollider spinnerCollider: + AddCollider(spinnerCollider.LineSeg0, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + AddCollider(spinnerCollider.LineSeg1, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; + + // triangle collider + case TriangleCollider { Header: { IsTransformed: true } } triangleCollider: AddCollider(triangleCollider, vertices, normals, indices); break; + case TriangleCollider triangleCollider: + AddCollider(triangleCollider, verticesNonTransformable, normalsNonTransformable, indicesNonTransformable); + break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs index b42e2e5dc..d5f253986 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs @@ -47,7 +47,7 @@ internal PrimitiveApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent, MainComponent); + var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, ref kinematicColliders); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index dd106ec44..15e4a7644 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -73,5 +73,8 @@ public class PrimitiveColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) =>MainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.TransformationWithinPlayfield.TransformToVpx(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs index c0ac3c3fc..5e34ae67e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs @@ -38,17 +38,20 @@ public class PrimitiveColliderGenerator private readonly IApiColliderGenerator _api; private readonly IMeshGenerator _meshGenerator; private readonly PrimitiveComponent _primitiveComponent; + private readonly float4x4 _matrix; private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private static readonly ProfilerMarker PerfMarker1 = new("PrimitiveColliderGenerator"); private static readonly ProfilerMarker PerfMarker2 = new("PrimitiveColliderGenerator.reduce"); private static readonly ProfilerMarker PerfMarker3 = new("PrimitiveColliderGenerator.generate"); - public PrimitiveColliderGenerator(IApiColliderGenerator primitiveApi, IMeshGenerator meshGenerator, PrimitiveComponent primitiveComponent) + public PrimitiveColliderGenerator(IApiColliderGenerator primitiveApi, IMeshGenerator meshGenerator, + PrimitiveComponent primitiveComponent, float4x4 translateWithinPlayfieldMatrix) { _api = primitiveApi; _meshGenerator = meshGenerator; _primitiveComponent = primitiveComponent; + _matrix = translateWithinPlayfieldMatrix; } internal void GenerateColliders(float collisionReductionFactor, ref ColliderReference colliders) @@ -74,7 +77,7 @@ internal void GenerateColliders(float collisionReductionFactor, ref ColliderRefe var reducedVertices = math.max( (uint) math.pow(meshData.vertexCount, - math.clamp(1f - collisionReductionFactor, 0f, 1f) * 0.25f + 0.75f), + math.clamp(1f - collisionReductionFactor, 0f, 1f) * 0.25f + 0.75f), 420u //!! 420 = magic ); @@ -91,8 +94,7 @@ internal void GenerateColliders(float collisionReductionFactor, ref ColliderRefe PerfMarker2.End(); PerfMarker3.Begin(); - var worldToVpx = (Matrix4x4)_primitiveComponent.TransformationWithinPlayfield.TransformToVpx(); - ColliderUtils.GenerateCollidersFromMesh(in unityVertices, in unityIndices, ref worldToVpx, _api.GetColliderInfo(), ref colliders); + ColliderUtils.GenerateCollidersFromMesh(in unityVertices, in unityIndices, _matrix, _api.GetColliderInfo(), ref colliders); PerfMarker3.End(); PerfMarker1.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index 1dd09ea90..31c5dc80b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -61,10 +61,10 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col GenerateLinePolys(pv2, pv3, playfieldHeight, ref colliders); } - ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders, _matrix, true); + ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders, _matrix); if (rgv3Db != null) { - ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders, _matrix, true); + ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders, _matrix); } } @@ -77,7 +77,7 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield var top = _component.HeightTop + playfieldHeight; if (!pv1.IsSlingshot) { - colliders.AddNonTransformableLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.AddLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); } else { // todo @@ -86,21 +86,21 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield if (_component.HeightBottom != 0) { // add lower edge as a line - colliders.AddNonTransformable(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo()), _matrix); + colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, bottom), new float3(pv2.X, pv2.Y, bottom), _api.GetColliderInfo()), _matrix); } // add upper edge as a line - colliders.AddNonTransformable(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); + colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); // create vertical joint between the two line segments - colliders.AddNonTransformableLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.AddLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); // add upper and lower end points of line if (_component.HeightBottom != 0) { - colliders.AddNonTransformable(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo()), _matrix); + colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, bottom), _api.GetColliderInfo()), _matrix); } - colliders.AddNonTransformable(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo()), _matrix); + colliders.Add(new PointCollider(new float3(pv1.X, pv1.Y, top), _api.GetColliderInfo()), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index bfd05d939..ce0d8066a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -70,7 +70,7 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) rgv[i] = vVertex[i]; rgv3D[i] = new float3(rgv[i].X, rgv[i].Y, height + (float)(PhysicsConstants.PhysSkin * 2.0)); } - ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, _matrix, false); + ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref colliders, _matrix); for (var i = 0; i < count; i++) { var pv2 = rgv[i < count - 1 ? i + 1 : 0]; From b28bccea845bffe573b40e940622f6e8d6a07d85 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 23 Nov 2024 00:04:31 +0100 Subject: [PATCH 109/208] kicker: Add free transformations. --- .../VPT/Kicker/KickerInspector.cs | 23 +++++++++---- .../Physics/Collider/ColliderReference.cs | 10 ------ .../VPT/Kicker/KickerApi.cs | 4 +-- .../VPT/Kicker/KickerComponent.cs | 32 +++++++++++++++---- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs index 035d1e792..9c57258f2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs @@ -31,7 +31,7 @@ public class KickerInspector : MainInspector { private const string MeshFolder = "Packages/org.visualpinball.engine.unity/VisualPinball.Unity/Assets/Art/Meshes/Kicker"; - private static readonly Dictionary TypeMap = new Dictionary { + private static readonly Dictionary TypeMap = new() { { "Cup 1", KickerType.KickerCup }, { "Cup 2", KickerType.KickerCup2 }, { "Gottlieb", KickerType.KickerGottlieb }, @@ -41,8 +41,6 @@ public class KickerInspector : MainInspector { CustomMeshLabel, KickerType.KickerInvisible }, }; - private SerializedProperty _positionProperty; - private SerializedProperty _radiusProperty; private SerializedProperty _orientationProperty; private SerializedProperty _surfaceProperty; private SerializedProperty _kickerTypeProperty; @@ -53,8 +51,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(KickerComponent.Position)); - _radiusProperty = serializedObject.FindProperty(nameof(KickerComponent.Radius)); _orientationProperty = serializedObject.FindProperty(nameof(KickerComponent.Orientation)); _surfaceProperty = serializedObject.FindProperty(nameof(KickerComponent._surface)); _kickerTypeProperty = serializedObject.FindProperty(nameof(KickerComponent.KickerType)); @@ -72,8 +68,21 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_radiusProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the kicker on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Kicker Position"); + MainComponent.Position = newPos; + } + + // radius + EditorGUI.BeginChangeCheck(); + var newRadius = EditorGUILayout.FloatField(new GUIContent("Radius", "Kicker radius. Scales the mesh accordingly."), MainComponent.Radius); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Kicker Radius"); + MainComponent.Radius = newRadius; + } if (MainComponent.KickerType == KickerType.KickerCup || MainComponent.KickerType == KickerType.KickerWilliams) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 3f5ebb03d..ee7823c9b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -197,16 +197,6 @@ internal int Add(CircleCollider collider, float4x4 matrix) return collider.Id; } - [Obsolete("Add with matrix only.")] - internal int Add(CircleCollider collider) - { - collider.Id = Lookups.Length; - TrackReference(collider.Header.ItemId, collider.Header.Id); - Lookups.Add(new ColliderLookup(ColliderType.Circle, CircleColliders.Length)); - CircleColliders.Add(collider); - return collider.Id; - } - internal int Add(FlipperCollider collider, float4x4 matrix) { if (FlipperCollider.IsTransformable(matrix)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index f3c655dde..124321b31 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -232,8 +232,8 @@ protected override void CreateColliders(ref ColliderReference colliders, // reduce the hit circle radius because only the inner circle of the kicker should start a hit event var radius = MainComponent.Radius * (ColliderComponent.LegacyMode ? ColliderComponent.FallThrough ? 0.75f : 0.6f : 1f); - colliders.Add(new CircleCollider(MainComponent.Position, radius, height, - height + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle)); + colliders.Add(new CircleCollider(float2.zero, radius, height, + height + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle), translateWithinPlayfieldMatrix); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index 872819dde..98adc1c14 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -45,11 +45,32 @@ public class KickerComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the kicker on the playfield.")] - public Vector2 Position; + private Vector3 _position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } + + public Vector2 Position { + get => _position.XY(); + set => _position = new Vector3(value.x, value.y, _position.z); + } + + public float Radius { + get { + var scale = transform.localScale; + if (math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance) { + return scale.x * 25f; + } + return _radius; + } + set { + _radius = value; + var s = value / 25f; + transform.localScale = new Vector3(s, s, s); + } + } - [Tooltip("Kicker radius. Scales the mesh accordingly.")] - public float Radius = 25f; + private float _radius = 25f; [Tooltip("R-Rotation of the kicker")] public float Orientation; @@ -157,8 +178,7 @@ public float RotateZ { public float2 RotatedPosition { get => new(Position.x, Position.y); set { - Position.x = value.x; - Position.y = value.y; + Position = new Vector2(value.x, value.y); UpdateTransforms(); } } From 2b2cfe3d9b38f76ac4130997ae6a54b9e3bc9496 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 24 Nov 2024 23:06:26 +0100 Subject: [PATCH 110/208] refactor: Be more generous when checking for translation only. --- .../VisualPinball.Unity/Extensions/MathExtensions.cs | 6 +++--- .../Physics/Collider/ColliderReference.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index bb65e532f..a9ec3aae4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -74,13 +74,13 @@ public static float3 GetScale(this float4x4 m) public static bool IsPureTranslationMatrix(this float4x4 matrix) { // check scaling (diagonal elements) - if (matrix.c0.x != 1.0f || matrix.c1.y != 1.0f || matrix.c2.z != 1.0f) { + if (math.abs(matrix.c0.x - 1.0f) > Collider.Tolerance || math.abs(matrix.c1.y - 1.0f) > Collider.Tolerance || math.abs(matrix.c2.z - 1.0f) > Collider.Tolerance) { return false; } // Check rotation (non-diagonal elements) - if (matrix.c0.y != 0.0f || matrix.c0.z != 0.0f || matrix.c1.x != 0.0f || - matrix.c1.z != 0.0f || matrix.c2.x != 0.0f || matrix.c2.y != 0.0f) { + if (math.abs(matrix.c0.y) > Collider.Tolerance || math.abs(matrix.c0.z) > Collider.Tolerance || math.abs(matrix.c1.x) > Collider.Tolerance || + math.abs(matrix.c1.z) > Collider.Tolerance || math.abs(matrix.c2.x) > Collider.Tolerance || math.abs(matrix.c2.y) > Collider.Tolerance) { return false; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index ee7823c9b..d68420edc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -409,7 +409,7 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) { if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { - Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info).Transform(matrix)); + Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info), matrix); } else { Add(new LineZCollider(xy, zLow, zHigh, info).Transform(matrix)); } From 5d5cbb4c443437df202093d19b5352637757141a Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 24 Nov 2024 23:12:45 +0100 Subject: [PATCH 111/208] ramps: Add colliders with transformation matrix. --- VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs | 2 +- .../VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index bcc4425ba..0c16d5aa5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -65,7 +65,7 @@ void IApi.OnDestroy() protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, GetTransformationWithinPlayfield()); + var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index 2dd0475cb..1d57d4275 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -122,7 +122,7 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide } // add joint for left edge - colliders.Add(new Line3DCollider(rg0, rg2, _api.GetColliderInfo())); + colliders.Add(new Line3DCollider(rg0, rg2, _api.GetColliderInfo()), _matrix); // degenerate triangles happen if width is 0 at some point if (!TriangleCollider.IsDegenerate(rg0, rg1, rg2)) { From db2311a9eed825c95e6cad91dd813c69d0369b3c Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 24 Nov 2024 23:15:59 +0100 Subject: [PATCH 112/208] rubbers: Pass correct transformation matrix. --- VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index 990b013a2..70eea1b4b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -50,7 +50,7 @@ protected override void CreateColliders(ref ColliderReference colliders, var colliderGenerator = new RubberColliderGenerator( this, new RubberMeshGenerator(MainComponent), - GetTransformationWithinPlayfield() + translateWithinPlayfieldMatrix ); if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); From 677dcc4b7ea9ccb330b5e1207177102b21d60565 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 00:01:13 +0100 Subject: [PATCH 113/208] line: Add transformation APIs. --- .../Physics/Collider/ColliderReference.cs | 37 ++++++++++++++--- .../Physics/Collider/LineCollider.cs | 41 +++++++++++++++---- 2 files changed, 65 insertions(+), 13 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index d68420edc..b42a5a5a3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -273,8 +273,23 @@ internal int Add(LineSlingshotCollider collider) return collider.Id; } - internal int Add(LineCollider collider) + internal int Add(LineCollider collider) => Add(collider, float4x4.identity); + + internal int Add(LineCollider collider, float4x4 matrix) { + if (LineCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + } else { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Line, LineColliders.Length)); @@ -392,17 +407,27 @@ internal int Add(PlaneCollider collider) internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) { - if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { + if (!KinematicColliders && LineCollider.IsTransformable(matrix)) { + var collider = new LineCollider(v1, v2, zLow, zHigh, info); + collider.Header.IsTransformed = true; + Add(collider, matrix); + + } else { + + // convert line collider to two triangle colliders var p1 = new float3(v1.xy, zLow); var p2 = new float3(v1.xy, zHigh); var p3 = new float3(v2.xy, zLow); var p4 = new float3(v2.xy, zHigh); - Add(new TriangleCollider(p1, p3, p2, info).Transform(matrix)); - Add(new TriangleCollider(p3, p4, p2, info).Transform(matrix)); + var t1 = new TriangleCollider(p1, p3, p2, info); + var t2 = new TriangleCollider(p3, p4, p2, info); - } else { - Add(new LineCollider(v1, v2, zLow, zHigh, info).Transform(matrix)); + t1.Header.IsTransformed = true; + t2.Header.IsTransformed = true; + + Add(t1, matrix); + Add(t2, matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index 5e96601ee..da9abaa40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -235,19 +235,43 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite #endregion + #region Transformation + + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable: 3d (center + ZLow) + // scale: fully scalable + // rotation: can be z-rotated, x/y rotation is not supported. + + var rotation = matrix.GetRotationVector(); + var xyRotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + + return !xyRotated; + } + public LineCollider Transform(float4x4 matrix) { - var t = matrix.GetTranslation(); + Transform(this, matrix); + return this; + } - V1 = matrix.MultiplyPoint(new float3(V1, 0)).xy; - V2 = matrix.MultiplyPoint(new float3(V2, 0)).xy; - ZLow += t.z; - ZHigh += t.z; + public void Transform(LineCollider lineCollider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform line collider."); + } + #endif + + var s = matrix.GetScale(); + var t = matrix.GetTranslation(); + V1 = matrix.MultiplyPoint(new float3(lineCollider.V1, 0)).xy; + V2 = matrix.MultiplyPoint(new float3(lineCollider.V2, 0)).xy; + ZHigh = t.z + lineCollider.ZHigh * s.z; + ZLow = t.z + lineCollider.ZLow * s.z; CalcNormal(); CalculateBounds(); - - return this; } public LineCollider TransformAabb(float4x4 matrix) @@ -265,8 +289,11 @@ public LineCollider TransformAabb(float4x4 matrix) return this; } + #endregion + private void CalculateBounds() { + // TransformAabb() takes in a matrix, this is faster if the matrix has been applied to the collider already. Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( math.min(V1.x, V2.x), math.max(V1.x, V2.x), From d9fead73561f78678d37eec1dcd3e21bcddf68fe Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 00:11:43 +0100 Subject: [PATCH 114/208] slinghot: Make slingshot fully transformable. --- .../Physics/Collider/ColliderReference.cs | 15 +++- .../Physics/Collider/LineSlingshotCollider.cs | 87 ++++++++++++++++--- .../VPT/Surface/SurfaceColliderGenerator.cs | 3 +- 3 files changed, 90 insertions(+), 15 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index b42a5a5a3..f7ed8b372 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -264,8 +264,21 @@ internal int Add(Line3DCollider collider) return collider.Id; } - internal int Add(LineSlingshotCollider collider) + internal int Add(LineSlingshotCollider collider, float4x4 matrix) { + if (LineSlingshotCollider.IsTransformable(matrix)) { + collider.Header.IsTransformed = true; + collider.Transform(matrix); + + } else { + // save matrix for use during runtime + if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + } + collider.Header.IsTransformed = false; + collider.TransformAabb(matrix); + } + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.LineSlingShot, LineSlingshotColliders.Length)); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index 8586adb07..508ff4319 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -31,24 +31,17 @@ public int Id public ColliderHeader Header; - public readonly float2 V1; - public readonly float2 V2; + public float2 V1; + public float2 V2; public float2 Normal; - public readonly float ZLow; - public readonly float ZHigh; + public float ZLow; + public float ZHigh; private float _length; private readonly float _force; - public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( - math.min(V1.x, V2.x), - math.max(V1.x, V2.x), - math.min(V1.y, V2.y), - math.max(V1.y, V2.y), - ZLow, - ZHigh - )); + public ColliderBounds Bounds { get; private set; } public LineSlingshotCollider(float force, float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info) : this() { @@ -59,6 +52,7 @@ public LineSlingshotCollider(float force, float2 v1, float2 v2, float zLow, floa ZLow = zLow; ZHigh = zHigh; CalcNormal(); + CalculateBounds(); } private void CalcNormal() @@ -144,6 +138,75 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite #endregion + #region Transformation + + public static bool IsTransformable(float4x4 matrix) + { + // position: fully transformable: 3d (center + ZLow) + // scale: fully scalable + // rotation: can be z-rotated, x/y rotation is not supported. + + var rotation = matrix.GetRotationVector(); + var xyRotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + + return !xyRotated; + } + + public LineSlingshotCollider Transform(float4x4 matrix) + { + Transform(this, matrix); + return this; + } + + public void Transform(LineSlingshotCollider lineCollider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix} cannot transform slingshot collider."); + } + #endif + + var s = matrix.GetScale(); + var t = matrix.GetTranslation(); + V1 = matrix.MultiplyPoint(new float3(lineCollider.V1, 0)).xy; + V2 = matrix.MultiplyPoint(new float3(lineCollider.V2, 0)).xy; + ZHigh = t.z + lineCollider.ZHigh * s.z; + ZLow = t.z + lineCollider.ZLow * s.z; + + CalcNormal(); + CalculateBounds(); + } + + public LineSlingshotCollider TransformAabb(float4x4 matrix) + { + var p1 = matrix.MultiplyPoint(new float3(V1, ZLow)); + var p2 = matrix.MultiplyPoint(new float3(V1, ZHigh)); + var p3 = matrix.MultiplyPoint(new float3(V2, ZLow)); + var p4 = matrix.MultiplyPoint(new float3(V2, ZHigh)); + + var min = math.min(p1, math.min(p2, math.min(p3, p4))); + var max = math.max(p1, math.max(p2, math.max(p3, p4))); + + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + + return this; + } + + #endregion + + private void CalculateBounds() + { + // TransformAabb() takes in a matrix, this is faster if the matrix has been applied to the collider already. + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( + math.min(V1.x, V2.x), + math.max(V1.x, V2.x), + math.min(V1.y, V2.y), + math.max(V1.y, V2.y), + ZLow, + ZHigh + )); + } + public override string ToString() => $"LineSlingshotCollider[{Header.ItemId}] ({V1.x}/{V1.y}@{ZLow}) -> ({V2.x}/{V2.y}@{ZHigh}) at ({Normal.x}/{Normal.y}), len: {_length}"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index 31c5dc80b..ba23529c1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -80,8 +80,7 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield colliders.AddLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); } else { - // todo - colliders.Add(new LineSlingshotCollider(_colliderComponent.SlingshotForce, pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo())); + colliders.Add(new LineSlingshotCollider(_colliderComponent.SlingshotForce, pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo()), _matrix); } if (_component.HeightBottom != 0) { From f662c2786f04696df6a625bb7e97c0c34e797809 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 00:27:22 +0100 Subject: [PATCH 115/208] editor: Only show collider meshes when vertices are set. --- .../VPT/ColliderComponent.cs | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 8b92f1cfe..f03db38ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -249,15 +249,15 @@ private void OnDrawGizmos() Handles.color = blue; Gizmos.color = blue; Gizmos.DrawMesh(_nonTransformableColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_nonTransformableColliderMesh); } if (_staticColliderMesh) { Gizmos.color = green; Gizmos.DrawMesh(_staticColliderMesh); - Gizmos.color = white; Gizmos.DrawWireMesh(_staticColliderMesh); - Gizmos.DrawWireMesh(_nonTransformableColliderMesh); } DrawNonMeshColliders(); @@ -474,19 +474,28 @@ private void GenerateColliderMesh(IEnumerable colliders) // todo Line3DCollider - _staticColliderMesh = new Mesh { - name = $"{name} (static collider)", - vertices = vertices.ToArray(), - triangles = indices.ToArray(), - normals = normals.ToArray() - }; + if (vertices.Count > 0) { + _staticColliderMesh = new Mesh { + name = $"{name} (static collider)", + vertices = vertices.ToArray(), + triangles = indices.ToArray(), + normals = normals.ToArray() + }; + } else { + _staticColliderMesh = null; + } + + if (verticesNonTransformable.Count > 0) { + _nonTransformableColliderMesh = new Mesh { + name = $"{name} (static collider)", + vertices = verticesNonTransformable.ToArray(), + triangles = indicesNonTransformable.ToArray(), + normals = normalsNonTransformable.ToArray() + }; + } else { + _nonTransformableColliderMesh = null; + } - _nonTransformableColliderMesh = new Mesh { - name = $"{name} (static collider)", - vertices = verticesNonTransformable.ToArray(), - triangles = indicesNonTransformable.ToArray(), - normals = normalsNonTransformable.ToArray() - }; } private void DrawNonMeshColliders() From e2fbb95c0ba07aba9830c949cad260b55bb4572c Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 18:36:15 +0100 Subject: [PATCH 116/208] mwg: Make them freely transformable. --- .../VPT/MetalWireGuide/MetalWireGuideApi.cs | 2 +- .../MetalWireGuideColliderGenerator.cs | 146 +++++++++--------- 2 files changed, 75 insertions(+), 73 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs index ab1ab1774..902931fb1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs @@ -47,7 +47,7 @@ internal MetalWireGuideApi(GameObject go, Player player, PhysicsEngine physicsEn protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent)); + var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent), translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs index 070b35499..0a487e03f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs @@ -1,72 +1,74 @@ -// 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 . - -using System.Collections.Generic; -using Unity.Collections; -using VisualPinball.Engine.VPT; -using VisualPinball.Engine.VPT.MetalWireGuide; - -namespace VisualPinball.Unity -{ - public class MetalWireGuideColliderGenerator - { - private readonly IApiColliderGenerator _api; - private readonly MetalWireGuideMeshGenerator _meshGenerator; - - public MetalWireGuideColliderGenerator(MetalWireGuideApi metalWireGuideApi, MetalWireGuideMeshGenerator meshGenerator) - { - _api = metalWireGuideApi; - _meshGenerator = meshGenerator; - } - - internal void GenerateColliders(float playfieldHeight, float hitHeight, float bendradius, int detailLevel, ref ColliderReference colliders, float margin) - { - var mesh = _meshGenerator.GetTransformedMesh(playfieldHeight, hitHeight, detailLevel, bendradius, 6, true, margin); //!! adapt hacky code in the function if changing the "6" here - var addedEdges = EdgeSet.Get(Allocator.TempJob); - - // add collision triangles and edges - for (var i = 0; i < mesh.Indices.Length; i += 3) { - // NB: HitTriangle wants CCW vertices, but for rendering we have them in CW order - var rg0 = mesh.Vertices[mesh.Indices[i]].ToUnityFloat3(); - var rg1 = mesh.Vertices[mesh.Indices[i + 2]].ToUnityFloat3(); - var rg2 = mesh.Vertices[mesh.Indices[i + 1]].ToUnityFloat3(); - - colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); - - GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i], mesh.Indices[i + 2], ref colliders); - GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], ref colliders); - GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 1], mesh.Indices[i], ref colliders); - } - - // add collision vertices - foreach (var mv in mesh.Vertices) { - colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo())); - } - - addedEdges.Dispose(); - } - - private void GenerateHitEdge(Mesh mesh, ref EdgeSet addedEdges, int i, int j, - ref ColliderReference colliders) - { - if (addedEdges.ShouldAddHitEdge(i, j)) { - var v1 = mesh.Vertices[i].ToUnityFloat3(); - var v2 = mesh.Vertices[j].ToUnityFloat3(); - colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo())); - } - } - } -} +// 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 . + +using System.Collections.Generic; +using Unity.Collections; +using Unity.Mathematics; +using VisualPinball.Engine.VPT; +using VisualPinball.Engine.VPT.MetalWireGuide; + +namespace VisualPinball.Unity +{ + public class MetalWireGuideColliderGenerator + { + private readonly IApiColliderGenerator _api; + private readonly MetalWireGuideMeshGenerator _meshGenerator; + private readonly float4x4 _matrix; + + public MetalWireGuideColliderGenerator(MetalWireGuideApi metalWireGuideApi, MetalWireGuideMeshGenerator meshGenerator, float4x4 matrix) + { + _api = metalWireGuideApi; + _meshGenerator = meshGenerator; + _matrix = matrix; + } + + internal void GenerateColliders(float playfieldHeight, float hitHeight, float bendradius, int detailLevel, ref ColliderReference colliders, float margin) + { + var mesh = _meshGenerator.GetTransformedMesh(playfieldHeight, hitHeight, detailLevel, bendradius, 6, true, margin); //!! adapt hacky code in the function if changing the "6" here + var addedEdges = EdgeSet.Get(Allocator.TempJob); + + // add collision triangles and edges + for (var i = 0; i < mesh.Indices.Length; i += 3) { + // NB: HitTriangle wants CCW vertices, but for rendering we have them in CW order + var rg0 = mesh.Vertices[mesh.Indices[i]].ToUnityFloat3(); + var rg1 = mesh.Vertices[mesh.Indices[i + 2]].ToUnityFloat3(); + var rg2 = mesh.Vertices[mesh.Indices[i + 1]].ToUnityFloat3(); + + colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()), _matrix); + + GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i], mesh.Indices[i + 2], ref colliders); + GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], ref colliders); + GenerateHitEdge(mesh, ref addedEdges, mesh.Indices[i + 1], mesh.Indices[i], ref colliders); + } + + // add collision vertices + foreach (var mv in mesh.Vertices) { + colliders.Add(new PointCollider(mv.ToUnityFloat3(), _api.GetColliderInfo()), _matrix); + } + + addedEdges.Dispose(); + } + + private void GenerateHitEdge(Mesh mesh, ref EdgeSet addedEdges, int i, int j, ref ColliderReference colliders) + { + if (addedEdges.ShouldAddHitEdge(i, j)) { + var v1 = mesh.Vertices[i].ToUnityFloat3(); + var v2 = mesh.Vertices[j].ToUnityFloat3(); + colliders.Add(new Line3DCollider(v1, v2, _api.GetColliderInfo()), _matrix); + } + } + } +} From faeefa247a8787cf418df23e29501d8fbe798703 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 21:49:43 +0100 Subject: [PATCH 117/208] meshes: Add new world-sized meshes for drop targets and spinners. --- .../Meshes/Drop Target/Drop Targets VPX.fbx | Bin 0 -> 19356 bytes .../Drop Target/Drop Targets VPX.fbx.meta | 109 ++++++++++++++++++ .../Assets/Art/Meshes/Spinner/Spinner VPX.fbx | Bin 0 -> 26700 bytes .../Art/Meshes/Spinner/Spinner VPX.fbx.meta | 109 ++++++++++++++++++ 4 files changed, 218 insertions(+) create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx new file mode 100644 index 0000000000000000000000000000000000000000..189c3c8be544a96ae95504ca559039d25cfe4baf GIT binary patch literal 19356 zcmc&+c_5VA`yZhpWm@T`&??``QlS#El!%1FEn+YBnxM-d5jnMF7m_)P=t9qh8eZS{ zK3M3ooUcOYPZE(~PzAI2%2z6EOrx^48HhpA1c2b72yOGkFb`?P>SbFl*TY$ z2w!w4Br5P*c0Q9uw5O0b5;8E6mt7>gRAI5gVp*e&=5Vi~puAXN>EAHPQ9vUHS=8Kt zL?szeooME0DSS=EBAFCbV z%NSrcFNqkFSVV-iZK&41d=pd>`VLj)P3v*28~W)u*f8) zn$mWdD4P9p$O0S8k>=>p99$8~CI-bEk*zalG00SBbHox<1{WNsp`sHF*w0XKvS{AHlH(8po;7;Fyb!1QNPbhoyD{hu+|i&la!}}D<~NLpUt;L1x#-{-21ox1 z87qRG0S9sGxsVbAAbB$VMIn1(*qU5yX}I+151%2e2itHXA*m0clt4Gq^aQ#@%QMo| z(FoGO(QG{2&%gb@n%Fs650W3>_}}FPUz>Wdc8G;z2Y5`xex$C8l$7DWtTLvwpt?$W zuy`Qw-|{{W#sUJC#JT>)oWx?mg2-fJ_(&do-lGcEuRDA+16JF|K!IAzLk8ss^}ubS zYqQBr&f-djl^f=XGoZ3a&Lpl&FhCO!1S>&giy#Bp7`R|$WM}H|q#770a=Mtgtzkfr zFnp)rQ*d4wr4pACInaK}Vf*ccbm#kib!p(vhVK{pz%_s0Z$IS^`&|soL99YDhxsB$ z#M9v)aT*QV6(d*Sk|Ve94f(JmK*CDEAeM0<2yod%C9}*Oh!oNqq5}(j5R$ti%@qP4 z&h2_v7kfnMaM*|jySavqQ5%}U7?FnPk(`LG6qYV{Lb(AufUyk|JO+vC>eBD63=k7%!A#I_mq5lwfJn^gL@I>R z3>IfD>o`RSfpY^_PN;ycIC~6WG28E^8RT z0yA*BKNhy;DgYWHeL+mck|-~nVm3gpNY&8Pg>@lZVR4p;N@$FNv>A{^c#VyAXTF#R z1vt)JI6-p%T_PX+fQ5<*6^LVq661?GY!@6zCfq0%F`w2S&~n6b9ccK3F$tPza|EkR zCA)w)9Ci%jMWOM)^bXL_9AP!UiI~x7to5J=Mg!Xn_H8OMc`S`FBRP>6B&q|6$t&Vq zFU;xxDh4V`28)7tnrQ&EJihiV=#J3Vj;{h^I5MYeSTUOF7%pcxTMRUWfv)20_W-D) z0qF}kK2(Tg!jFawL7zl(A+Z=9pT5UI60gKak^YXy!h4U6Bo{gbX&@Tv8hf@eiMj0^ za;{h!AaGAur%*d>$RKbe#~&{Zr@~PBq^0-WO%?{H$Av@4Iyy8qY%V!#jRjadjj=?J zV;tNs92G*%IqCZwX&5+fQaU)2pOh~*7^+$vI{yTuz>xQJ()sgd2m)9 zdm0Vf_COmP$JUNv#Q)SOnL2qctO9NEs8UUkSu(cG0bf&JL ze+()*Gl7_fL8o!_Q3z_jS6W!$^p<03B)q2ce1R~*(8#0UfKc#%Xf+}|3mA;2Q}C~#pxTDXl2B9#d; z0Ty$Y>cP|&+TqS)L*kPSAMJ0L_1EEnf8DxOAKyMN(V<`J1(V_=QLw4&lC;C2uUhu?qPPgZk@BP%NOLNy>94iPmwx zu?}g`b;FVkY0h)NoQCdgNL$`A4B0TtPROuiLk==?Sh67p*^80Q=W-J1wgGh;i9u#@ z;_V><3N<(iMjYW=rZ7J`ypA0NI;3Oz+}=`iUK!v1j3K^Cn zaKyab7TJL>V))hI^F0Q?o(qqnE^ISLcHpfBzlyC6JAE{CV=gvqE&8!BJs1fzpF;Ji zm@hyhUaE4TQOpd=uLqAO&4^TI(uh24+VB`7a;|tdg&t-cBl4^32Eib*e{L?p!()tS zE-#127|~p&$#D6xoGTY3a!fApl#WDoW^EghXox?A8PSX`{SanElWG_QhAW_-nv|;S z_q-VhGond({}5(GlX`%`sDX&E5C`HhbROlXO?9S>%!KeWxl(P%rgS-j&WPK^5PnP5Cdg}Gp)uAAuGFmzbDPuP&*f|!RoHs3~bA8h+1 z0CkG9xNKU+Z4TZ8x1Qt-`&u^=95BNx11va}foK1&bUF=Ag%R8!XF9F}QDC#eJI~f; z!6R^c^j-wKwZIuH(yU$a7R{#xuj9P@D zuHmAh_zj`*eAKt!zr!5NL(Q1Ol{t3YgrYRQW4mv@4uVL>=vPA>%oO%seDtzdcu9n_ z^{zC?rRm0IwxSZ_sJ)yt$}7Hw`$d;}vqQ`p^og1#+_l9tQ8dqg_yV1fBw* z220^X1S(i$I8b-=ff7`T-YH@Wc!!)l{9x7O)iP2#YsZbY+>>_ACgX(fs@)&;MY?gd zov!W1DQ?P_T*K|iI-?E<8I2RESYLVKgG+)#K*T+bq~-(rj6yo)Cmd~cck@tFw~Rk6 zo$CK3dcuTLksXTy0R{>$->#cEtL>l(fBpvPE^Ogn(u527fch1jed(y z8kKQ=3wsrzb&jNBFq5H0Ab4$8h`R2k*~I#S#&PCev+bB^83n+ zB-O?ccaQ4}vpUxG_)dRQIKA3dEZjZuu{w35+Z2yccWX}HY)Y{#)6j}*Juk=pvo|`i zCV2U7t?UotpJp`b)X8yKrU@*YxPeCTaHdfaKS^xPx@;B`Nd@<)+SNPs^R%Q1vGFhWAxcQK#hT)W~E) z!P^z#sfFtC&OH}3B0q{Lyr26;wB}7i?$ceeY0sdq-ZakWFfIoU?IW?O2uf%);C2@~#9Z%nGe(oO;wn(Bdh0CZvy9kcC$} zI+K{>Z5Th3;mQ^mmu9jc4O%>t3D=UhA9MEc^ep0WhC|HRlhbC38_KV~I%nqw!|c_k zHqVqhAS#+U$D;Lh>((S$kv-FPjQb-t$5V>(U#8O*%AM?eb#8g&(4yA79Z}ulQ(d3U zbP$(ve`uO9-Ti^VqkE#}b9Os*tDG;;K*8P~$$GD5yW_`c|V&qw(47 zU=8KU%g(36+aBx*-Re~h#v5+B87zB(ppbrxWFrLP>d6Sg9gL_)TZz86$!pb^Hm6O9>PH(ZWC6D zc03ZsL^>S~uF

fX8_s5xp#j{D_Xn098GRk-_0NZff5Hwr>g1hJmBh8)Y{sN=`{!;g7h{fwWlpAh~=Le4P9JZ7zd zRl(lRn-_;{wlwU1@?87EBFW;YGlwi(@+ZV*8hY(ad#Bcr)S2D-(Z;FR`F=xJSB`st z@^+PplSx;0PAc!MzEI$`^U#;C_r3CKXKNqiwy!F*B1!++l zrEPY);SVa(3o~6W9HKqUXu7C6UpzQNa85&}=ker)4NmiuvQ$IYc3Y7DE(`r+exS=+ z__No!e<>!@##LHgA@qo^)biHbSx9)3<~V!G+rtxl^LirA@3Nb7*JNj*U~8zC@ZxAa z4ZEb0mPt9~gzW!S*hgDgddY1XGx5^olT&w@TC>wvRFtldl8nr<%&q%uQzuyNq;gKJ zhpu9-+~6F%Hz~zR{&RBkP7TSut+z|d5>7AJ!n~Qhlu1%)Pl?EuDg1ZWftVN@>n2-@ z@P|ACreu#mBx5ld4P2S-K@D5r@5A^pg37qGYhDdWZKr#EZ5Gkb9RKf`ATeL`gOfo> zmXToxL8a*WzKF~P`M37Ck2zO+ST|EjJtv9Q@U>a7(B=NoqZw;=1fN~rIpxlyeOr5! z1AL{bbF7~EA6xW%O2h{j_rwP2#E-@1r4ebXZXBK6Wwu;7HM&|#o3OtAp*1}~v6hme zPAPk`KI84^k^}LVeCBMa7PG8VYntq(E)|ie62XcSiA^{r?o88{n<2huTw@?xU=nL= zDvZ59`6%`qi#WeQZL%;xtv2x)+yG;uV-+aE;2IOMw@nscLD5*%Pg+zlM)s2ywFMsJ zAmgFJ$EZyfj(g!AeJ8>PTfp^L(mFxy@#)bL+Iq{x!nYCU&bsuE=N+4AcbMwx!l{NL zffH8W7;m;$)~|au<;tx+`~I1ws@=G>{m}f)LPGxenTe#^jJIQxy%U;?i;KpaCdMyW zs$Um9^-$iaR*9|NZ#tUYw?<8ux!odDa>bkVNg$!;YHmnTsNMp}*pM628uz}0J*j!C z8I!70Ud)Kz-x@&q18(;WmPf)vfm?HersX5cRT{qBIA}x3N zQt6JD?lpE{ z?we>g>pySl5xg|sY}!steMSMZ@t`tjU(n~jTR;kI0hJel>o=O>_I{WmZ#ZeA(5JPg zr(Rrm+vY1@v95yp*|{g|c0N-rLH+#mDwAd6$q%+$i9O9uzZmHi+*}mDa*WJaP9zC0jK2!_Aeh$6~ua zANkLqz$I=+WnBF{TD4?m3|*pUv^Tv&y%z7h>wF|Fme6zCduC>&!>qhAt8UxRi`#Ro zJ6*)z;X}QMNzS`ssA-039sks8>`H75CN9px_nuF8qcT+!HT5W34=5+ambglth&NVq z-9?DaHJ(ut@4daceH!Jx`G*;1NB-CX^X^YU5XB;WMCKvPu`rHdUE?#nKErZ^HdCQJ zFOB85$Oeo8o*7{8a3f3MZ^2!w#P~WKgARrrIbz}T$^+(HeTM336NUf}C9$43`&>SE zyUAkbNP8L3<$y0~u$@QoJ=(hT$M+Z3Zyt-9nWOD1Uwm&)hW9u~gM|0O%BVRv4ISRk zgH}8-3>x0YV-0)(sXx5u?AW1%0huk}U}x7(-(6nkGkWys;ozrfq`^`G)X7%vq<-mLRYtlHPv1Oelum%kpZ88Rnly#sS)$fE1 zmUbveTyGUb%7L!!y}b`-G+hf(F^Jx=;@GFDI**^F>#fw6yOPjA_w#)wP*d+4XLPSH zMP)NF$*}N4k6K&tuDva)H}9!Q-J{+;sA03D*4HaluJ-}$dg76S1%w+(x7vDGq1k1k zvB~;c?W0$3v|65m4bBoe$0AMG-*U!d$QjSLw4yP(b`WXinkfI}^k=xlW;2P}QV(cYbzpZg^i+R$H3!I=kq4V05Tf@A*}NH;*sv z7u`@nUpF?G?}ENbuzTdmy+7zP#7ckCMfqW56JdTo5%f*LCa@dG`hq@Wd}v@y$VjtN zMr-CdOd{E}-;mifJtiK%$ydhzwMKXIsDGtnR-7@NUn;t;y{lC_OMil`Qj_j%)h5A6 zsf+K3roLxG;=5(X3reO-p848a^3K51nh;|2Zfp`| z|GtnLk4bBLJwxAw=VhOLc=UComTL4QlZMg>#zyYb$EvUFR%Nx;;PbD>N&dB0VBq?5 z8BWxBVi_#%$mWxI-|~op_$sVokhntz&IJ(WWXL}e$y;D#KPl8n*bJ^gZz%3q30Ktn zS;(A~!s^V&2!~vpP`16HY^R^Y>jjQIdr~IRQcE9su8^G;dHu%ODvet!SIAI%k_pFB ztL-XuJCvHI(wolKtB+saJN}l>RIOCEiXBImDQISCD0#3nv%JT3hug}A6F#P8Wuym& z;g9K8%4H>v70(siW3QN6QQ$o)Q1T$*n6KSI@D}ksN;o00SqBLJ{cq3pF58!3j^xGp`7XYj z*U96T>HM`Lp4_`v?Nh72;=)G5x8-5k5g!FT0+d-7!t);`HD@1BOgAYLtf%i!$kYr^ zxIeCQv}c}Q?`L1PB4b+<|F~`eBYVMrp{xEB3l4#&6nw5SpHEl%bOE_aU1-ls16<`a z3`i1cxj%P8!u{S=s`7O>tgE~{R8PAxgh_opeeWtGM%oK+F5h1#=y6>o8b-gX6nt6& zx(lvS7Cydnl?rR%m8L=br3yM8#?sLK(gIrXWHG3}q{6co)IbnE`u(LHl%Ud9$Q&zR zKRek<)e;m;8|S63fR`O7sMJ)_mF)c=Ui4Aay)oysOJ%2pdmdS)-6G!Vd?b6qa-53H zB}pOiI3r)NkX+Hiz*6b4O*WM#{;!mk7W#}kSMu0b$23^}{?sedPKV#rYRRnXoMv&` z+CS{JOwe-K7TKMYdhH2;@%W33+DJ} zSDXn-dm-iNi3=l`1xstvcd`?8OC>9$SuIMh#q~f0;*iyPR@TZz^;x zqOug5CLdlZx1W5n^?=%W&95Kl_x@X5?nFFYxTSqz{82W6wmNWsjgkpfx+2pi0AKDK zVDdCsQS))f+(Zc*t0xXkl-yNG#Z z!Huv(6D!kQI_meyd<-x5dC=XGp0WK)P0y<AeS+uQ#*?J0k4vo~8RJlOkP*o?5N(`CYw zZ63!oOD9Su+;y+6wJ|=A1Cvyx8Cg@y_4pZQPU=EGj>9(XFi1(e{N` zi;f5}Yo}bq($eFm(UCjfoMh%Cyg#=y)LQk!0Ru~$C8T!;Yn+OdoQsX`7P@8Fv=!d9 zdme(9-rV}w!DMAi)`7~cRxK|U?AYq%P_(5Xu`N|!O4X&K?6!;g{k)dfr)jO;ZD}TW zpXFZ)WPH1sWmE=#p za(gov#TUCAgYMA~Rze|Lz~z|9HgoOqkJ>KallC28KffbE@LXJ{J)u)#)GUegAib;a zH|zM@<2RD?I?TczDEH|m1Yc|^2){}K7SV52)6**Ps{&h`D*S2$~Z31j)nj0T> z2Kloqc5G(TBy$NFE5^Si3i~{^O3sdU)G(N_#H(>$RkNblx}yc@fy}rJkGL3oTt~q} z&-SQJ`GYb~Hx#uMzg=ZAS?%ELl5n{mM%VPj+hO@1Q_|xMA+mS4Y)fkoT!2U$VVi~H^oU1i)Dx!{~k|Uc7K0q zcJzWv*S)ISHJ-HDv}z^^ukgGdBXKX%_C#u8wT*h$suGFIK9T<{fA#Wyz5I+XPp=eC z#m(r->7oaeX3tM=s&r{O|3_BF(^vW(S5-v27hhi4;vLXgHD00FD_*MRlW5nr!|N~X zz8%~9I7{&ip>)SG%T}F9-gf3?cV|>(blj!wuTg*H`=t85^MkiG7aJ-H9<6&HZeug< ztC`Q~I+5%cg^u1&&AMOmd%hIrw)l2;*(}Sjp8K^I?>k#viGf)MI+-3Agz{F&TTsIm zc+EL!8!s4ndR;@(vZT=7WQA$Z{F0BP$ktVj%Fz)~F;!C}%{y^UJ|M@h%Q!Q@R%B(# zhrHHN8L<;={T#f+rb`{Jn@y*TD`~HivSrE5JP`OkS);A0O!L6)B}sCva&seV6RsCB zGrA{larP`JXX7(ZCkG`o${laX30dnKcQobY*;(|mSCKQ^r#QH))(6>XiKwpht_+WC zc;j^O;nBE%!wT;Fz392J&D?r?e$iNTJlLPwAg)5jg3NocP&bZmiRcS&P^kM7s~9BI z9kc3})&Omc>?hZNQf+Q;AutUT>h8vIzyAf_*#eth7|+);os4fZpV;v(&(u?D|BK_1 zuM5r0B_y2orw1JmraW4&xi$aR21C=tr9t~6=LCgb6BfIDcD}qdWp9&mx>>PCLl5n3 zL81?{xVM;@J7d=8==FzROx9AjE$FW23><~29wI%&PSDYNi%TSI-D{rwyN*0q#{bcfHYw~qN7#xQ=K61ZdM&07~1 zF&!*BXQ-ZLO1JvFSw?u8M#@Qh+*Q65;MYFGIcB;j8OBM87J=PI(pP1ut zU#UXO!H2%f(dSj++WnPB4zzxe65k%v-b*YBsqnTtB~Q;eRrNC4Jl1A=kiThp z^3>0)Cabq;od(x}0w0;KPMdYvHL>!nE2*oa6X|~PeZ9q z@T1R_?Cu-AxLgJGylPCvzJOr*!g-zvRYD{1Cj5I4Y{~dC zZY5#vg4G+kE#}9IJuCP}bOlqr$i`h15eenY5R0AeViy7AS0 zBh-Uf)j*+M$4W;k)Dv)QCJ5t4#)+A#s4e>I2Ow1R1K00_itYLbN>*xZ{}cvD_7A>O zxdD7SKUO+Y$pT0F(=EgPdof)am5P1~4nM^$1yCe@v?>eenxh^~O!^jUbVAEX zqCozAGoJPLHB%b28Bd@3npqCojQ8GEU++GHHsd|MG2-?9&!EkCM^{F?-g#5uT&sbG zmxR$W$t;2ro~U3kWWYn}zEAC^u^(N7<;DIFo0fVZ literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta new file mode 100644 index 000000000..c0dfd6bde --- /dev/null +++ b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: 736c9b6aa90a2b547abe58746ecc1764 +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx new file mode 100644 index 0000000000000000000000000000000000000000..c96eb8032ad6dcfc3f40ca576933df78988077d2 GIT binary patch literal 26700 zcmc(I2|QHYAGfk4MWs^N+7*SU>?EPZQrVKNi6L2rSq#~tqEe|O$ugCqB9yWX#!~h* zWZ##u&M=lSV|&kKl%9X}JpbqazMuDgT=(8{&hLB9@BEhYyXTylIR$gIgdt&D)sLRr zdep`NhIHM!m1zr8FFz9#ot^3Xu@2-Phr=x4NHrHU+yP~Sa4-Rx%fQJB&Nbh&4d6&9 z^TYr%6O+r55-UN8#XJc7$&x%SH3ti%t0NlF>A5<>Pk<~YZwQ)ktW`zAVe`2*_HbP^ z%-*pa%uJ%4wffGKstI!I;WP6ot+J8Au|Ou@~0z@ zQStytWuIphfz;gx9*)|<9YB9?-Gf5IF51E8C3He(@7DcW4~j{ONp98D(*^g3#12S` z9UKBoE(bKi;N;M?fIGlXI9MTcA$~OM5Eo%~&?D2bA2};Pjy?*GJO)FryEIphkqe3Wdskb6hm}h>S)m0-!yv3nzboyvB!gAY25kq|1sF_EmL#+5VlG~W zTcGDN5|(7JsbJ8TK(9aqMN86GoPxVz5lBnad`{QWoU?Fe8%Pi5b7q$0u&W~M?coj! zl|X>skG3W1zUnj*;Rr{fZQv+riOYadh>FDPz>0Nsxiv%2W=kpZIs@&F8^J-SGXe}mKmkXQiu&IKe$S~^G@Yhb;0 z^P&SRIwCL*mJ7`QxlBhuafk&?aDuwGfXgC|BT$lQ0cRb! zThvK)2!5Uc8d`eu{kWL$qaSsl#!4Iz2Y&}-r6smXNXX6qiAhLE zJYqmIzv{J3GSnOG-#eni$xiFfhB*HV(j9 z+li?;0BeT-B~jnO2JLF1f|K!*X z{08LqAsb+WntH>6;tCsAY|X=h0ep&&uzjp+1TG6$cx8APx)LKa9?x@Xv=NK!bP$UWjSMSp)*D z4fH_s=Q6)~a$LgF%Cm4QI1=t)0Z08*bb9BHM^2!!Ho&4loQ`_Vs1xGiyF<4G4=GDB z7#$9kVFShuwGm=<f#3BpCkjzv; zc#PVrdkJQVz@mOfw+jdWs&wB19q4tYvv3PIaDRVC2}=HxgA8;V(jv$^E!85ZQplCs zU1#VDl)%tsNQF8WbbnMK$Odab!Nubz6=DND2!S$uDHPJ3!2tb3IzV$VAf`b&BIfmx z1?2q2nL~i)fF4qmQ{V*hfQ;xY0u6&KQ^(2*1f2g?(8&zolMBF*tQLV!+c+csH7+-> zd#J%naG_BigogiHd#xC_Cl=ZZNrZv>KQ;FSfQ95Ewg3xx0zD+m0R?0Nx_H4=|4MB^ zIi@K_la|P43GM%2)_Ncs_>0h&0{?sKz6XFIZkHI^QeYd0bHC%Y;O{7nen+_+==>k+ zJpVgNg+EimTz=bH=f9);ZEF+$j`Fvy?PX9daej*irGo_1h4Wng!ov4~*I=lB4uca| zIP@03NC!eT3%EK0scBo z0LbW{0I`GM9OCLvEjs^CfYca35(}(BW&yn}X~G@;o_gnzP(TflFk*ZV@q^(E{=I*p zDB@oXyA%fM5?^#0fkOYUu%cj&4(c;eaQ?;D|Jf0F1H!*>x!9Kf!3)*?0rlUzATFTb z5R-csn1uZJlD7WVqSgLL_FHpy|0CINZ8`A|WPflw{eL9;Z6mjVS>NAv3C!iUjWlGC z?OEtaNViWoT!JHQ(DU*3Zvu({2GtTb#TZBbtYck(4$?7=h1ynv3xn=|P%&9B9r_#4 zf2Lu>%Ev%Mz_H{80YIVdT+%N;TKZcBd-W%(--@pBPgK8^+*&YW_nQ{}LAx~mMD^Pi zdi;s%w=HY}RFGiKfD^bPXm*0}wq;|nBx3lt!KVy(8;9Vv7vQ0&>pT3SH~6>MfedV| z1#Iw2@&lWp2mb_mi9&r>v04BP$&E2oLoqW@{$pU-^DNB48vc()SUxZ_{RfPHG`O1o z8RH*~tC7FJfVup4yb%A4@sD^B75dNqSkjJv#0!#vA^St*47q^SKe&T6`qDoVP3qsm z{3DK-wk~k=`_}v;rZj#5v(PdB6;t>A7Umx@Rr9wn|A?tA+ZOspn(2Fp1L84gKFU(X z!P@ShnUMM4fcztdUj7ZpKVqi=fI#CcW2k}N&NUGTTNU);bklr{`qw$Zb=w!H7(+1R zq4XUW!q7j`sQ^02b%07CrwmoBdkKO4)$7wTkmtuJ&X{utBNdZ2+#0;sI>W&PGuVoN z2GcTNPXWf!5dmg}A-G@6>6HGA0$y?!=d)GNU}^m#wBG{khH&})iVL)aFv~#M0mwnq zp_x*3BpiNaVG{c<)q+eWaZnQCbqRr{&M`1s_N#V61pc7pk|70}L(;Uc+q!hH`c)c4 zstfg&3d76)YQC+Vfqfj51FeT*RtWq1NcA5Hpupdw9@@FkV)X@7D1L)b7ssf-KK}>i zuSO^gz=XPuG2H}3X-lT<{_^+>!~zDr8ps2jg15mX^jjI>B@E0R|0!!87y36pDG1&C zx5q219~A5hbI7P$atjmr(2L-T#i?ISzt90N2!Y-Pp*NGRqm6?D94U4b3Fg(|Xr^V0 z#paa;n(B4i1r=U6ewwFSOq{=lfl`o8Kt0L5h_Set$sHu70E34Lurq_UfDE(^m`%4V z07+0PeL2`+wd`F``6)fvdS9}H$i2_KyBkloYnJj@ z;(IRQEDhZ>jF>4nKTDOrp|5K={^~|fKVed%>{aLHIkE_0d6^&YUD@G7UV4NgbNe#w zzg7`qi50FJYkI{*$|J{6z%vmuk{{zSv>AtYlr6!Qs?0c62at;TI$o3dd1u;Yz3)B5 znG+-BT%VL@R2R37kLnTp$sHfxiYgmzdOw%^R+%8sTO?%CGS!fd7*8%bA;!wzjYk_c zBU1hO?o!5PYubg=M4ip&{3B)C@#uH>uQja}E!?v4O)E$zGTmfeBDkF$o>14tlp2M5 zA~yOJ6=!m>&(!3$%|6pL#30Y|5_(h&xnwI*JHnm0yN(G*4=BZyVO7Y7spc3`YRj^g z=>BxSm{W<)5hXC<@YvH3DY? zWTh!Tn$m`F=1Lc(-@Sq}m-e4<%ccg;ye;$851U#%g1}l zbbTg&HFcjJ9_NZ2P}DF~c8ybB-Re4Bls!i~F&UxJCpK$&*uDHssJuY$*p!{&7gCH7 zmuG3cNb48b2mGW1WWO+{Il-rHk|Dj^eZ13Cc4Jg|ltHw4x!Z8hxo{zm<~gw^Q_Oyz z`q5(U(_{YH+A=f~$K;X7Tw!53*#PmfhUsCR+3V(dL2sjQo7@*W+LDtrYMa7h`Y_$QxDsAS&OX#0oG?UEGP&k(oRqyG*DW4} z6%bQ3++NQWdSI!NK1)^#CA=Xxe` zk<{>2Zt|g)zGM!etAREIBf(7WbRn}N_^N>`e$=}+cDpOPm^Tdsw#|N|y5+=Q4Q%&r z$L6MWw`T}xI=5p)bE&UptgI*XhxgSrPFRY^8uOXeilvvCZkML_bjhUeo<4YvNZOZv zujNR3t@IAV3*q?9juvTaWtroncpvNqT^By=-%fM1PurNfa^P&vK+}w`&MOaL=Gog~ zI;5OiALTu647W_>m0N1L5M0@GUd~wUHwlS5YL?Zay)`t~EqRyA!RhtR;>y=zD)kEk z+v3|bvIE=l6XF~u3qMjrhvl%z?VCU1_nEV2-l5(Nu{_jtefDva4<_*gEoyj|`??`x zQCN7Z$<>Tz&E2dBA6*}*Hnr^0_6-T-0^Mu1xu@D(Kgy?Wm=TcOtgvE&F!(K-)a~zr z*GN!2SSPr#DP;Bme|IKN^9_Dq{MRy#3)JRUY$MT&@%GSMv zHHJ0oeORU)+5FBAgE}sk{>WKc+TpNOC)!1Fc69$>M*5wXtkyYivW?sNhUXfEj2wLT zFX(t3&}CLNOKUjFMv$S_#o+3)Y{OR@+ZDhFDh`y`J0!v`9nRMb{Pc;x5h+ycYhU)h zr|FK}L1f|~-)*Y*4&1oD=E0h^-FuMhWnb{Gy>WjIb7ZvJXbH?zd4}Vb8Ja4%hXn2inIfZXAEntB$?0Mr&2Z%~#S+p97C<_vq8`V!MA# za_=fj3DwPcj_MK*&%Au7E@!dn6kCYpfd~#wtCjVdyZlV7Bm#{}b_ckxe3HL><=%_i zzU;H&IAxTFH284)(4*5QHxKNwX6AcyLCUyZeOF_Bp_p@`pUA|S(q&x8o4Ne$d-7$< zIJA+stHRp%5oO9*#gMm(P1;3jQ_5C5ApIMg+Qq0T<;;SVoB0aud&^zQIL#=xYt!5J zlU>T$l_EuD+KYWp(!RQX4C-al?mm%6(1;R)E*YnyUT`%2x`Qx61uZq}IR z^OolJP@As%#b_%@f7z}F-m8=!FE`wBT6Jk&4A zV3}X%hl;NOYfTU&G^w&t3+4!d7Heo1FuQ zcX=cq+Iwuhj`6O?`y2~;t~%d7|G0z_S-5rlN$&D9ZrwGjod#C!Uw49;@TTp$amjnD zPbanPgriW$_gFPNdKFG|C4F)uIS_GJGFb^nAa?TS-;&Esj69vcne<`gQtF1{VCRQn zGZ`~e`;`Qfc<2EQgN;#XgDd1!6kg{`XUf(FCl4!H1Rv{g)i9i#96VyCaMjp3PtR`P z&P|PyvbQ%s>>zD9ZY26DAky8q$6=a4Z?NYd>~DJ19G=z6 z!_0S$GF|f4W0LUEPI;Q*$|`@RjGR1*5k3%SF1^ZVtzfivFauB_srgs~LQvzTUg!oD2c+TkySqiqqm(epf6Qn4taDkw0mmo?Fp(vQui9ac2a*yw+=^3i0WvDD9JdM9$+{oub4mTI`!*P_p^@T$xXh!OWze ztfHCHH-3Nr#)fH`)7Sm6%+eI4o(P_3^19HCJXZdpGufx$J}G(@s#WWP^ zX#v;V2OnQ6brmL3j+FDqc+sMA2bipJICVG5X$q`Fl;9@S=D^vmZPx+2lbGz=Z{Flh ziH$Sbrld5mHSZIAeQ6@mPbx?4@==S!E`+fw<52k`lx8`=-Kx6M%}a9%uunZ_iZYtG$txD^|6gJ zJL13G!g))_PN|h8y52Xc7MA>I-8Ha2z_w?_q*G^PZ{`@iX{h{&C;FyPvUsF`OO+Jc zJCC;AUuP>@BGy~rFJ7vx=Im}^-&X*i?wIT~-3t@M#NwAxy6Xf>9<*;evykKQ}W@jbQXehU2+GjVZBNwSTT z((D7f0uXBW+gcie$8NHJx-}s}2-916#ZGp}7!%aO+|P|SD;nEB+$l7Bu;vsdS=;}q z?V-+iEMW1H(1z&t2;KL4gOu*M6Yu3`yt;C72N`?l@ZN{gh=i~2#V#)^?8!R6KW$k6_2Dev<=OL7;f1rW0P^CLdjRe%r@J{qP-hHrYUCo)sP$($gW5!kenmh~>_QrN4 z;Y9cw{CeLG&E~hmcdDCxyDxY}kQ9}h8(Zr#%DcY}X^AJhZivV+tiw$mxngWmKbY8l zK*8G|W*+tizsBle+Lk9wQb-+b>E=A&GcO&s_j<&mq=yo-nrFAZNgVKR6r>Vr-`x`T z%AFM~BD;%gbQDi@#9NBaUTPT#P27J6Rn^L#Y}yu8`;IxoV@JwBtxu@w+*)51VpOgE z^R;87w9wP6Q;LqY%|0S_%;BM)dsFI@eeiM1#?n+Oq^~v#R|v64<2|{1n=)S!b~lg9 zDTgC;j47#&=00w;D5qYmPpHSb$v!S`Gok_K&@)tGfFFfV+QT#*pW%L``N{T&6MNf8 z(wLpI8HrO?a+77GcT*f5344-h+o<9n?8Zm1olWRWFV^7fcP`Cu#*|;;XV@m?n>X&s z^lyB(qfCp4aK7rn6;^5=S8iWcY)_Z|?55%{y6Vm;ssroX1hL8dYiV+7bHx;6c(g`=XvZMP?+-L*%s+iilsI5nTZ?_1Vf_|nul|I^MPBtKg8Au5nqh$M6- zO?_*mwkYNOoC)&KyyJ0 zF$om5=o^4QX68x2t0nNfH(vII%Wf_XFGUP}uwX)1zuRZu9Auh~ zx$1pEX!$kR=}mMTijOz%VWt!&FsDmK*O0g8ft+sg&V!#r((b~T-^ec;y3iU-YJNx> zl6EFbrD96^zSxtlu9e{T?T*tF5;C-#5ZSak@`B|i9_#+y*KfUyV~aY1A+h#FHBKko zsV-&8AY(|Us;nQ9CO);r$#Y)`ZOTsmWB zTgm{pPvee?)V_eL#?mQ$7(W#Rc^P)*fMX&xOGAcBPCA367{-!;Co%U9MQ58*ImR@A zfz7RQm>6n{8(`0H;OoUWn5z&3n+ECRXDD0Exf5vv7||odM)AJl6IeI#lmU1Aim~{t z(#$nYm;jY{ROm{bBSb448ws1Gr7SQ;nrLi`s|{-3(%V!d!Pzuqn4J;!(aoHxwLYU* zg565h-T~!68nAp{lizw1RYmhW(8QXYGhh(ax}$k0CX0-wJ|B)rZ5I5T+Ba>rx^6Cu z+|+|_>M3rDB5Z3>MuwKRtEFLiCL2SMxRcV-L}%)R*8wW=a3eC*8b#iYJ!)I0)j#nP zU)Yjz@1<#(3cHDBL-*OH(wC-kD%68E!PZV5g|&s|mQI9r$e>z>Jwr(|`vEbsT zmmsf%R_rb3HFmz%Md0}>;&(NvGnx#xp)qQjUw~NhlA>>LaS2<0tmUUFe|i|ZxG?bw z-anR1ys}>4gx)on=6B&QPQ0Fmwle-=+Lf}D1;!f3{3rN+s)$GahX{T-?fUV^LV-KL zW&N0TjRmF+NzQBjW#-Fcgk}(YQ=Yle`l_a{?~pfUHn2afWPe@yJmJH`?a_BXJ-+mg zZRHa_jvY_b{kOy*(x=`|VTlJE2_;N_~PSH_0p)7;3P7|fHY zF0#M+T~b73Vr(XFSyKVVFK-n=0ZC7wU5|H~4B~m$>1#M_lzn=u*WhIdE`qd2-ldpg zgxprox>KEl`ppEllHq*PTIJzGT*}*{OD47@o}JFBm6;Ck>vq3wqHM)A<-><#%lGp1 z4b!R4sPbc`wZ+WHxR*UI8RscrpDPT=ZGE(xsN35EpPZ!jb59G+xKH@RPtlIXheeZB zMgo*3@3{Iz=B9p0p|R0rgLV<^kkT^l@tLJ3OdoF`Q~0A{#b>EHJy*sJsCP}$=~1mb zlk`jpL+WZ1>dG)wMFu^dyh@I)AnO&*Nz?wsoo+~MY$immI8-!F4(k{$FXQ?^KBY_`jLbWu*nu%GFm07b;`%do zV`miANM__GwQjFkcOh$Ai{DeNWjP$;$nK-Z{9M_uyxMdgexBZtv6B^b9VTpf3}OgIXbxA zR+)&n^10)@S6BHH(g00i+P`hmQjE^d)-PHzmpiA2Y3q#ceV(^fU?O}|Z`>hz3A}Zr zG+TxkKjRqDYduSURk{n0X)O1iQo_7y?#S;HN_KTpdWU&lq<{btU)iRfpUym6S}H$)^?gYYz5{>vQYDJ3Z@^-v|DJ|kq8UgOyHXh>q0z-kRgI`SCbCi+OvTpI=pGrBk#KxlI`_812wY^Ip z%-WT^m3I`zlRmjt=BE0;L>LpL8`CRf@hMdlf90|I`OfPF4D>yp2q-hY#i_ck$wN-H zi^}7f6{uZE=9|7MU*^L#I&~`}E2gZNyzX5hMbtI;6PNo?zJIyN#6_{$%EJ0J8>}X7 zi$2b{7gQuXS&31cpt5*oRho~yn3k7O`gCvN4Xq)77neGCgKM_(M9H0%qp5CvQ>@yE zRPna4uqoC|T4dW4s}k)>z>qL?pw$tb6m(P2y+?buzo3j##^e>!-3IC$N$p-&%#?a& znxXVt+~jn=;4Eu8E1l^mSj&W5F#@>aU(NmB2kT`}{>6D$t@X&-?lJ3n^#Lx2FB$9( zMv`!&q(KUGAn=CJ;xQJtBm*yPG-1v~N9b?%!pHzyIY zFXSk~-=H^H;Mz=A7Sx&7;b4<5_T= zp5C+DO_+kjSqLl4eaw$`i?>TxO21BYR2jqcYfh^9a6T^^^MBE!lY8TgH63xoYh@gh ze3kgd*}~!GW+Bg!f=2-*%cj?53%4P&+1npx=eK{z_G>$vo!=&z?brS_TcbVsT0qu@ z_G2Qv1~Ib%;X}E#r(Qa1M6FBbjoUaIH!NC3_Kxu*NKK234P3L$qOaZ&86SLn=qeuz z;;Jnh!f${baov^`acuxhuG_LBZVa#?dJ;6dzZ$Z26P6QFbhRe~xscFD2@!HayG9 z_zdaoMB;_fcW?OcWg9#3G32@XWtVs_bHrL(?FKj(C+%UQO1fseA#n-P<4w3_RF$c- zP;YVo1_j%(*LbrEUr$x-sN$Zuhoa`^>Q>r3!iKrU$OBopPR_u{`!ozsTj@=873lb7 zyh&?aW1CVE9|Lv%LD<75VV`4Nw#%*)DL zW;d=B-?`knq2=n8Xo=Vc=^Gi;%{6Nxu3phl<~*dk!rAlA$s4@STMm+&O!&@~8=1?< zPd`j>%YU=p<-p}Uc~Xe&>Ub`(Wub~HhrENIky32GnTXc$(LLI3y(SOI_oi0yMO>E~ zo!wI1+>wy1{CrDp|B1J2(>ES6+(((%4}@WyQ(W;12&0^$*bUdy0n*@AU=7y<($LN% z4BD-0;v0fzmxXKjj)#?Uk!-l9LQ1r}<(~`e9W84PVIGh*-t5U;M=l~4&b)El((p(U zx5cidnd=c%e@Z_=+C9%Cg`?kLV{F4fG|PZ6t95RRjSkJE#uHioL=9COTsRXVYmmS99KnDvtAoZDX0LWq>=CF-f1o7q0O zuV2%gd~sN|IwZ5@M%(CXDGxN zg5${LwIw^Mqr}E99Z}t~_UIn16_K0wUn-2e&ZEND@HOJN9A(oPbQEVLI#T|Cg33Op zv5ph;ih$WwJ0nz9S*_q$1>baQ=K&wfvn~;5?pW;;m$;{wC*=*+aqjNbgm3Ayl(?p8 z2H%(%l51N6SK>T(oXfuee!#U$H*4MFGX+S4gxlK?L)tL+`A&r&aP2}T}1 zlRaf;#VPb5D4?!PTM5RC`w&`QS4RTOQ`#CWuwJFgeeqT#uSZ`w6WhA-s#V*(+joxa+i~1#XXK_* z|2sQ^H~@2nnlO=h=Byrm;r|elzZ`k)m>9&?*_B3RCvTtAIKt_~vfA z7e1x#Sq4e(!vzK{dGYnRW)o*lY+#o;e#jgw%(>^Dxrg#W$QEucLq=>7b9?|T$`OX; zr6;e2Hg#}eQ}aD$+qcaHyxXY4qU%RLxb=G8^>yk}PpfW!J63UT;@DSjZS?M^ai1%$ zox8G513~{9;1Y3h4J4Y}6FWnQ$6*J-I-*FY>n#q=ouK7FESkcBMwSOc?Rq2wX!~~h zed&6jvkWXF4#ZyAfH)1rajsbIq)~Zey6Wcc^!(i-btM~>FVS{>AF4PA7n*wW z^7b`7)RTCboCBcgWSX~bSPxiI)LG3-yLHN>XXTG2MM0`-R;$8JiQ}RyYc&^EMRz~i zeee~uDk@`t=@h%%S~cqzed?>;kG+)k=q+aI*j%zEV%q~~jc}*MCJm9m;}(*-E+ICW z!q*H7EO#dsT;!@RfiL4Mgsn~82Q7u3%Yiz5rNPf`dat4bh|+4Zl&eh^Vm}^eyr3}d3VOx zxR&Yc+r#?DT=$r~*6R?RWII%L*8fHI$%{8Tc0Q+uB?bqS((gHLK#)^W=^eRy=L|XHLP}prKt$$|iYhJ7<6E^Cu4Bs>1srN?qBO_D(w@pi$ zwB$A%p=Q6(Sr(l7QsYxzo?7c5W@|kE+&Q+Ai*IV8?E-Ag5|@pa>h3eEd&G&JPE{5- zLfH%)oW|hzV_3Csztg&pE|Q! z%_eH4x&$nL*l5)E#hAYTv;V%#oLrv9Z0sP;Ipez3nY0^YsR%{NptDm8UJ1sCdI7v9UC!)J?ir-sj8d<+O&vqEEDg6n96{#L#f6O%Kb5} zj$X2&&(~RDFPMz*I0ZYs>zlR0*W})Ky}YeBT46hJ9mO!>TcbBBEU+uteRKO|DcUCm zZQ6CL+*P`i*WLZBp0U=oEM3KY5k2EDmwP9R3t42<-0?gTqbGtXin88jb}pUU+DJ#{ zI!eWTcSJ~X551^*Y?*T&?>v&4C^KfYtt36|{YEQmZQ(moBU`!;^z|HgWEFop2s zf&TI-NgO4$a2x(+c4C2`K&&90`o&$$;PkB1L%~!s&+|hC!Y%Yhubk8I9b3D3nggcq z>KL+h@i@$~Uy*vgy4hbpP^q!fRnmJfi~i{q>YZYBu|eEPp$4_GSCbnwTEwYiSCN(x zUnXn#B14xCpmIzt^FZXj1W&=sqnG`h&qcp9l-AL0 zW(mqVt*9wj=jSzQxvPYfmHz7CUcz4IoytrBx8i+1C5CietUoB4%)M;twKm^-oe}z) zL)Ynw8&yWc>{@hNS>wguUZrdztl3n1SzUx4qWRoYm3vl;t>F2_wGJ&in%*c+adhOr z5qxj*A%M2FUajVkh0iJJ>8~F7-{b{qaE*5KnTdtIaH&Cn&cbAqvOSvx!!+2wnoM< z_<@<;!7t*8GjCheC)Pc%9rf1cN<_LJ8QIEJu@5JJ>4FK44eM(gGC4mVj~COs+ zpT^Nw=M-Vq(4cUxd6TZa>L1cm11t_@`_B*%iqJI(kz{-AD3|m5*e#hUk#BYGn3S;duw; z>mLQz$$AOhblE;$t}t`C7|$`1Q?@>tm%Wcksr!0pf2aHr2kZq;)V&AFx7>^HY0|VH z9@_!(@+#u0)dp)fDrK3cMXO=U7TRII8{1|tcy5WMY0S`yLnTtb><1UTQ^bn4tMi@Yh{xSJ8MgQ zbvW}wPCTUQVZ?27b#f;1#w4C>&=$?6JEJcz*MZMdP@mzB`9$_FoOzEsxNSoD zi>p63O+m1`zH-BW|Ef?_K^0#I^IqlGM+wS11P}E@Xc%@xl-G6LlL-B+bqSH(r+lY# z)=xztf|4?NxH(tCE#7IT-w3_Lmv3je^&@tzjGDyrw`diEJC)lNBS$=7+h3PGRp|E` z)4I*;{FHtc^<*&G@6@0&O~h6sza!F~kmf@d>-=VW-_ZWmoNB{2+dzkzio42b5tK!Q!;sYLMfyEW8pPAYi%1xBW|V#`-0DDrZ+v^|!;mibo}Jj92O-p01*wAv$L#7dpDA>L}}-cSgF>@n1g(6jucVJq|yUIo{dbO=f9P_%vqo3O;7sO%>lC8NZ)^u-Tkp zO0zv#?7Vlse6oVaIh%UF?mpgA#bSGP;tkI`+qlvxUq6f&SK)&nSN3>(SsP^f_(X9Z zzBu0ayn4L;EbXf#{T?Mb`0>zsu2nkMrEDn_Ec1*-MWs>{sqk4RZ*0hcG2Mzn-nVap ziyb&9vTW(oJqeC*IkQaUxo00Ef?L^sccohVYbrQV{(FN@u$SY-{4LM`Vj(4S{xmPrTsXYm9pbVRT3tN8FSBDT97cAF+uS; z#R_MPagcOlK)|;~Q>i*>8gEhS)B5DJi6}QWUSE=?VA_sr%^5x(6&60>HLBFz zD4EKAu4Wx&H`I1-?sa;73$_p6xc_|K+-8@=nd2Wb@xd?N+roDoa_ybnJA>{yJ}e)S zP8z-tEbZDG#YVf8-CXxHopk>KR^4VZLEX`sGSP%ix(xToZw%jbiT3ELWHVPb-)=;C zndB?i^U6^lXU8BWs85Un^FI~|mW`vbWze1TV2ajt1C|ci~@cIVoE5uKd$` za5p347h6vFjsDfkur7n_zkC_KX}VC`Zs6yjEhmc~*x5Yq_b|bVHwE?A`?7O+_f zThl%tS$}X{_~5$8IIs6NgLa}?Q4wzCu_>j)gQ-%ZC8^{ld2@aJn(WchP&%_$XGfRpyAZ+di4)^$i*qXz0%#Jgr9mhHbe26+5MQu7w)(w@q}qfyv_w0%VY4*(pl(*2ps_s$|E!$NH?=0o z?-g=q9+(;WW`b215597trm;JJTu|4q>XA#P_9Sgf>+}^NyBVh_<(jWj!kz9pW5NmD zt@*_9=!EW!`9Fc(Yz=HxURi*L0fIW^Jx)bS0wy#o7&-Ui~b zii#)iiFzF;>iQYA<|ntjksFXN8INh~VJGGeiU$_Q&mAx&P-a+rrs*BbZ&6+;E!I

eLa;B+7`bE>13)Qd8z0V`3S?Vceayqy`xI!Bn<_SF(Fie9|H&l-$4u&6efHvmOer#h7yA3&mcW92iJiV0&xeE?QUm z#&1)4;w|oCw4N)l7<0V!Iq}#l(r%Ye`{^Eg6j?0d5#28J)QH`XA-!5NM$g4TG zywRQRLJ1m(h|fBVgQu4Ym$)OnGO1kEbcft%WS($BsW7G|F`?8N7FwQj7$-5>r|G3$ zt8kdnND^p{clUgofR5mZt!%E?JijaD--P4?G@H1ndb*qE zc191mrALHKn$oD`@gCF8VfP|gT0bs7rAeoRE~hm5_DL(Z1WgDros~C5y>MZM5Psga zRmBX5Nn?EV+eAXP2_da}Hk;;1%1;}bXrYlK^Fn6eW0Y9aRw{VGzdPmw>p zajmH@Pt(eK?&U?h5=k7I;%M~XF<(9{AR$-EaC--UllZ=^Y%yZmIqx~Ns;&hJ7;MUG ze`hcM$-dD!G|A?2W6SQnXB$&K*+(Z0eVoytD@nCVCx*~UV^C$yp>|Q5wrqJS8EY^c z~L0e(55^BE2k6pKiDh{!3 z35+W~w$_wi-Os7JF9;^nJ4D&g?RTm>#-~&ugz%=?8C4mM>b$0fwQwZ&KTFhFmDbYL zegR8*oYRwTjKzY>!(dS><^+Nuwn`DZxvVMs3L}o@Z zdk2PcO(iOPX5U0h+}(UkO})LOUGQUqQbTJ%(J-}3ULI@YsG|d~6I)T%AjW;Ap`P%( zIh9kDtoMzq+hT+jf2%3a4MKuu>6BC5;i6>TA$(F9T1F=MRPWuQ1p5tP&C08Ir+eE< z;sCq&L`-jZQNp{mVU7F8jP;zYLM!BhnvJB!wBdD+SC`rIrSA~u9n>@XNI8GT+J;QR z5SuP$D~gnxmgt7t)7n-ySZjR2R}n4o3W!Y`zHxa36e)S#Xd8Tewm!*a(*}B`T_BN@ zBA0D6vrff2cUmR+VnU+sv8*A6vp5$Px44fWRbZjD>bgyV%Q8|us9Wcz5B!E@ow zyK|GUG=Ue+t+$GjGJFyxgJ66-V~y<8X~i@3&ZixnkGh7ZTRR36m8DiSd)JIB*4K}C z*9M5jCl0MmSAm25BhS)H8a0BG5xjK4pegF3qG3E4i>0>R^*ohS+!7RCGct>-YO$O$ zfq6^V_Jws@`!|fG85QXWed@zh1qH0CACvQ%q~=Dq7Z0C+n^E=K2rCYoQH^Qlk%u!# zI7)TYoY2gL`;;G16u2XT4Rz2W9JFAtWUJxICCT68CreNU4KfywpSBvFVo?0uRzvG0 z$bZ}f*zmiW-ZK#XwAHZmpVqR-%YR<;0lQ#jk_miniV6H21o&b`&GJpqhwQ*U!;R2C z@L8#!KHxoH=KH6+H7$O0?Drn*1#~B&Ze$!kee63MQ2gKf)_|*K7FCO1NCa>}rI3ha zzzIG}|1Z8d0cx=#0|k8I972J<72t2aW&M z(}USA{Ln`*BWpRpVTZoK6MRlhby*rC75ao4yDA3!eg^n;1t_v-+=Fjy_#g_jhP`)LV{3Otafa3p^KWs0Scrv2%ffBf%F4{o*%CegdJV$8b~>R9n4wp1Q_Q-R(J_3H_GN|F2{6 U{GH1! Date: Mon, 25 Nov 2024 21:51:25 +0100 Subject: [PATCH 118/208] droptargets: Make em fully transformable. --- .../VPT/HitTarget/ITargetData.cs | 8 +-- .../VPT/HitTarget/TargetInspector.cs | 32 ++++++--- .../VPT/Gate/GateComponent.cs | 1 - .../VPT/HitTarget/DropTargetApi.cs | 4 +- .../HitTarget/DropTargetColliderGenerator.cs | 66 +++++++------------ .../VPT/HitTarget/HitTargetApi.cs | 2 +- .../HitTarget/HitTargetColliderGenerator.cs | 6 +- .../VPT/HitTarget/TargetColliderGenerator.cs | 6 +- .../VPT/HitTarget/TargetComponent.cs | 40 ++++------- 9 files changed, 73 insertions(+), 92 deletions(-) diff --git a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs b/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs index 8132bef20..9f610f0c3 100644 --- a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs +++ b/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs @@ -19,13 +19,7 @@ namespace VisualPinball.Engine.VPT.HitTarget public interface ITargetData { bool IsLegacy { get; } - float RotZ { get; } + int TargetType { get; } - float ScaleX { get; } - float ScaleY { get; } - float ScaleZ { get; } - float PositionX { get; } - float PositionY { get; } - float PositionZ { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetInspector.cs index e4a4ad5b9..37b7290e8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetInspector.cs @@ -25,9 +25,6 @@ namespace VisualPinball.Unity.Editor { public abstract class TargetInspector : MainInspector { - private SerializedProperty _positionProperty; - private SerializedProperty _rotationProperty; - private SerializedProperty _sizeProperty; private SerializedProperty _meshNameProperty; private SerializedProperty _typeNameProperty; @@ -38,9 +35,6 @@ protected override void OnEnable() { base.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(TargetComponent.Position)); - _rotationProperty = serializedObject.FindProperty(nameof(TargetComponent.Rotation)); - _sizeProperty = serializedObject.FindProperty(nameof(TargetComponent.Size)); _meshNameProperty = serializedObject.FindProperty(nameof(TargetComponent._meshName)); _typeNameProperty = serializedObject.FindProperty(nameof(TargetComponent._targetType)); } @@ -55,9 +49,29 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_rotationProperty, updateTransforms: true); - PropertyField(_sizeProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the target on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Target Position"); + MainComponent.Position = newPos; + } + + // rotation + EditorGUI.BeginChangeCheck(); + var newAngle = EditorGUILayout.Slider(new GUIContent("Rotation", "Z-Axis rotation of the target."), MainComponent.Rotation, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Target Rotation"); + MainComponent.Rotation = newAngle; + } + + // size + EditorGUI.BeginChangeCheck(); + var newSize = EditorGUILayout.Vector3Field(new GUIContent("Size", "Overall scaling of the target. 32 equals 100%."), MainComponent.Size); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Gate Length"); + MainComponent.Size = newSize; + } MeshDropdownProperty("Mesh", _meshNameProperty, MeshAssetFolder, MainComponent.gameObject, _typeNameProperty, MeshTypeMapping); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 518e5a2fc..f60b1a334 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -52,7 +52,6 @@ public float Rotation { set => transform.SetLocalYRotation(math.radians(value)); } - [Range(10f, 250f)] public float _length = 100f; public float Length diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs index e8f3dabdb..005d4c1e2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs @@ -106,8 +106,8 @@ private void SetIsDropped(bool isDropped) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders); + var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); + colliderGenerator.GenerateColliders(ref colliders); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs index bf07c0cee..6d1ed5213 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.Math; @@ -25,14 +24,11 @@ namespace VisualPinball.Unity { public class DropTargetColliderGenerator : TargetColliderGenerator { - public DropTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider) : base(api, data, meshProvider) - { - } + public DropTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider, float4x4 matrix) + : base(api, data, meshProvider, matrix) { } - internal void GenerateColliders(float playfieldHeight, ref ColliderReference colliders) + internal void GenerateColliders(ref ColliderReference colliders) { - var localToPlayfield = MeshGenerator.GetTransformationMatrix(); - // QUICK FIX and TODO for Cupiii /* hitmesh should not be generated by the Mesh generator. Drop Targets need special Hitshapes, that shoujld be very simple and cannot be activated from behind. var hitMesh = MeshGenerator.GetMesh(); @@ -46,12 +42,8 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col */ var addedEdges = EdgeSet.Get(Allocator.TempJob); - var tempMatrix = new Matrix3D().RotateZMatrix(MathF.DegToRad(Data.RotZ)); - var fullMatrix = new Matrix3D().Multiply(tempMatrix); - //if (!Data.IsLegacy) // Always generate special hitshapes (QUICKFIX) { - var rgv3D = new Vertex3D[DropTargetHitPlaneVertices.Length]; var hitShapeOffset = 0.18f; if (Data.TargetType == TargetType.DropTargetBeveled) { @@ -70,15 +62,7 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col dropTargetHitPlaneVertex.z ); - vert.X *= Data.ScaleX; - vert.Y *= Data.ScaleY; - vert.Z *= Data.ScaleZ; - vert = vert.MultiplyMatrix(fullMatrix); - rgv3D[i] = new Vertex3D( - vert.X + Data.PositionX, - vert.Y + Data.PositionY, - vert.Z + Data.PositionZ + playfieldHeight - ); + rgv3D[i] = new Vertex3D(vert.X, vert.Y, vert.Z); } for (var i = 0; i < DropTargetHitPlaneIndices.Length; i += 3) { @@ -91,22 +75,22 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col var rgv1 = rgv3D[i1].ToUnityFloat3(); var rgv2 = rgv3D[i2].ToUnityFloat3(); - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(true))); + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(true)), _matrix); if (addedEdges.ShouldAddHitEdge(i0, i1)) { - colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(true))); + colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(true)), _matrix); } if (addedEdges.ShouldAddHitEdge(i1, i2)) { - colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(true))); + colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(true)), _matrix); } if (addedEdges.ShouldAddHitEdge(i2, i0)) { - colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(true))); + colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(true)), _matrix); } } // add collision vertices for (var i = 0; i < DropTargetHitPlaneVertices.Length; ++i) { - colliders.Add(new PointCollider(rgv3D[i].ToUnityFloat3(), GetColliderInfo(true))); + colliders.Add(new PointCollider(rgv3D[i].ToUnityFloat3(), GetColliderInfo(true)), _matrix); } } @@ -114,22 +98,22 @@ internal void GenerateColliders(float playfieldHeight, ref ColliderReference col } private static readonly float3[] DropTargetHitPlaneVertices = { - new float3(-0.300000f, 0.001737f, -0.160074f), - new float3(-0.300000f, 0.001738f, 0.439926f), - new float3(0.300000f, 0.001738f, 0.439926f), - new float3(0.300000f, 0.001737f, -0.160074f), - new float3(-0.500000f, 0.001738f, 0.439926f), - new float3(-0.500000f, 0.001738f, 1.789926f), - new float3(0.500000f, 0.001738f, 1.789926f), - new float3(0.500000f, 0.001738f, 0.439926f), - new float3(-0.535355f, 0.001738f, 0.454570f), - new float3(-0.535355f, 0.001738f, 1.775281f), - new float3(-0.550000f, 0.001738f, 0.489926f), - new float3(-0.550000f, 0.001738f, 1.739926f), - new float3(0.535355f, 0.001738f, 0.454570f), - new float3(0.535355f, 0.001738f, 1.775281f), - new float3(0.550000f, 0.001738f, 0.489926f), - new float3(0.550000f, 0.001738f, 1.739926f) + new float3(-0.300000f, 0.001737f, -0.160074f) * 32f, + new float3(-0.300000f, 0.001738f, 0.439926f) * 32f, + new float3(0.300000f, 0.001738f, 0.439926f) * 32f, + new float3(0.300000f, 0.001737f, -0.160074f) * 32f, + new float3(-0.500000f, 0.001738f, 0.439926f) * 32f, + new float3(-0.500000f, 0.001738f, 1.789926f) * 32f, + new float3(0.500000f, 0.001738f, 1.789926f) * 32f, + new float3(0.500000f, 0.001738f, 0.439926f) * 32f, + new float3(-0.535355f, 0.001738f, 0.454570f) * 32f, + new float3(-0.535355f, 0.001738f, 1.775281f) * 32f, + new float3(-0.550000f, 0.001738f, 0.489926f) * 32f, + new float3(-0.550000f, 0.001738f, 1.739926f) * 32f, + new float3(0.535355f, 0.001738f, 0.454570f) * 32f, + new float3(0.535355f, 0.001738f, 1.775281f) * 32f, + new float3(0.550000f, 0.001738f, 0.489926f) * 32f, + new float3(0.550000f, 0.001738f, 1.739926f * 32f) }; private static readonly int[] DropTargetHitPlaneIndices = { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 9c4871871..5bdad7794 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -63,7 +63,7 @@ internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new HitTargetColliderGenerator(this, MainComponent, MainComponent); + var colliderGenerator = new HitTargetColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs index d7eb3b77c..eb570a153 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using Unity.Collections; +using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.HitTarget; @@ -23,9 +24,8 @@ namespace VisualPinball.Unity { public class HitTargetColliderGenerator : TargetColliderGenerator { - public HitTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider) : base(api, data, meshProvider) - { - } + public HitTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider, float4x4 matrix) + : base(api, data, meshProvider, matrix) { } internal void GenerateColliders(ref ColliderReference colliders) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs index c1120b01a..a55ded7bf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; +using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.HitTarget; @@ -25,12 +25,14 @@ public abstract class TargetColliderGenerator private readonly IApiColliderGenerator _api; protected readonly ITargetData Data; protected readonly IMeshGenerator MeshGenerator; + protected readonly float4x4 _matrix; - protected TargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshGenerator) + protected TargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshGenerator, float4x4 matrix) { _api = api; Data = data; MeshGenerator = meshGenerator; + _matrix = matrix; } private protected void GenerateCollidables(Mesh hitMesh, ref EdgeSet addedEdges, bool setHitObject, ref ColliderReference colliders) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index a9ff6a93d..0029e2d9d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -24,6 +24,7 @@ using System; using System.Collections.Generic; using System.IO; +using Unity.Mathematics; using UnityEditor; using UnityEngine; using VisualPinball.Engine.Game.Engines; @@ -39,15 +40,20 @@ public abstract class TargetComponent : MainRenderableComponent, { #region Data - [Tooltip("Position of the target on the playfield.")] - public Vector3 Position; + public Vector3 Position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } - [Range(-180f, 180f)] - [Tooltip("Z-Axis rotation of the target.")] - public float Rotation; + public float Rotation { + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; + set => transform.SetLocalYRotation(math.radians(value)); + } - [Tooltip("Overall scaling of the target.")] - public Vector3 Size = new Vector3(32f, 32f, 32f); + public float3 Size { + get => transform.localScale * 32f; + set => transform.localScale = value / 32f; + } public int _targetType = Engine.VPT.TargetType.DropTargetBeveled; public string _meshName; @@ -60,14 +66,6 @@ public abstract class TargetComponent : MainRenderableComponent, public int TargetType => _targetType; - public float RotZ => Rotation; - public float ScaleX => Size.x; - public float ScaleY => Size.y; - public float ScaleZ => Size.z; - public float PositionX => Position.x; - public float PositionY => Position.y; - public float PositionZ => Position.z; - #endregion #region IMeshGenerator @@ -111,16 +109,6 @@ public Matrix3D GetTransformationMatrix() protected abstract float ZOffset { get; } - public override void UpdateTransforms() - { - base.UpdateTransforms(); - - var t = transform; - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, Position.z + PlayfieldHeight + ZOffset); - t.localScale = Physics.ScaleToWorld(Size); - t.localEulerAngles = Physics.RotateToWorld(0, 0, Rotation); - } - #endregion #region Conversion @@ -151,7 +139,7 @@ public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNa data.Name = name; data.Position = Position.ToVertex3D(); data.RotZ = Rotation; - data.Size = Size.ToVertex3D(); + data.Size = ((Vector3)Size).ToVertex3D(); data.TargetType = _targetType; data.IsVisible = GetEnabled(); From f3488f564e781c356c97efb2df8edbbf45328610 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 25 Nov 2024 22:36:58 +0100 Subject: [PATCH 119/208] meshes: Add new world-sized meshes for hit targets. --- .../Art/Meshes/Hit Target/Hit Targets VPX.fbx | Bin 0 -> 66588 bytes .../Hit Target/Hit Targets VPX.fbx.meta | 109 ++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx create mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx new file mode 100644 index 0000000000000000000000000000000000000000..0b7a7143b3f733894676f5c5117651dd662c7775 GIT binary patch literal 66588 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

J)41DjGV*n{B8}VRlVMvQ+3zS- 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 TA@}i%J@A>yT_f(Hr#b&03xm;I literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta new file mode 100644 index 000000000..d09d07d3a --- /dev/null +++ b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta @@ -0,0 +1,109 @@ +fileFormatVersion: 2 +guid: ebd09566b9175b44384fa3761c41a19a +ModelImporter: + serializedVersion: 22200 + internalIDToNameTable: [] + externalObjects: {} + materials: + materialImportMode: 2 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + removeConstantScaleCurves: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: [] + isReadable: 0 + meshes: + lODScreenPercentages: [] + globalScale: 1 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + sortHierarchyByName: 1 + importPhysicalCameras: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + nodeNameCollisionStrategy: 1 + fileIdsGeneration: 2 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + keepQuads: 0 + weldVertices: 1 + bakeAxisConversion: 0 + preserveHierarchy: 0 + skinWeightsMode: 0 + maxBonesPerVertex: 4 + minBoneWeight: 0.001 + optimizeBones: 1 + meshOptimizationFlags: -1 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVMarginMethod: 1 + secondaryUVMinLightmapResolution: 40 + secondaryUVMinObjectScale: 1 + secondaryUVPackMargin: 4 + useFileScale: 1 + strictVertexDataChecks: 0 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + referencedClips: [] + importAnimation: 1 + humanDescription: + serializedVersion: 3 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + globalScale: 1 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + autoGenerateAvatarMappingIfUnspecified: 1 + animationType: 2 + humanoidOversampling: 1 + avatarSetup: 0 + addHumanoidExtraRootOnlyWhenUsingAvatar: 1 + importBlendShapeDeformPercent: 1 + remapMaterialsIfMaterialImportModeIsNone: 0 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: From 4485076e4d99287ce9cff5540dea4df9a09d61c8 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 00:10:25 +0100 Subject: [PATCH 120/208] hittargets: Make freely transformable. --- .../VPT/HitTarget/TargetColliderInspector.cs | 6 ++ .../Extensions/TransformExtensions.cs | 22 ++++++ .../Game/PhysicsMovements.cs | 10 +-- .../Physics/Collider/TriangleCollider.cs | 2 +- .../HitTarget/DropTargetColliderGenerator.cs | 10 +-- .../VPT/HitTarget/HitTargetAnimationData.cs | 3 + .../VPT/HitTarget/HitTargetApi.cs | 2 +- .../HitTarget/HitTargetColliderComponent.cs | 7 ++ .../HitTarget/HitTargetColliderGenerator.cs | 33 ++++++--- .../VPT/HitTarget/HitTargetComponent.cs | 2 + .../VPT/HitTarget/HitTargetStaticData.cs | 4 ++ .../VPT/HitTarget/TargetColliderGenerator.cs | 72 +++++++++---------- .../VPT/HitTarget/TargetComponent.cs | 3 + 13 files changed, 115 insertions(+), 61 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetColliderInspector.cs index 8df17607e..9c2058c50 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetColliderInspector.cs @@ -26,6 +26,7 @@ public abstract class TargetColliderInspector : ColliderInsp { private bool _foldoutMaterial = true; + private SerializedProperty _colliderMeshProperty; private SerializedProperty _thresholdProperty; private SerializedProperty _physicsMaterialProperty; private SerializedProperty _overwritePhysicsProperty; @@ -38,6 +39,7 @@ protected override void OnEnable() { base.OnEnable(); + _colliderMeshProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.ColliderMesh)); _thresholdProperty = serializedObject.FindProperty(nameof(HitTargetColliderComponent.Threshold)); _physicsMaterialProperty = serializedObject.FindProperty(nameof(ColliderComponent.PhysicsMaterial)); @@ -62,6 +64,10 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); + if (_colliderMeshProperty != null) { + PropertyField(_colliderMeshProperty, "Collider Mesh"); + } + PropertyField(_thresholdProperty, "Hit Threshold"); OnTargetInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs index d96d998c4..7d0562946 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs @@ -49,5 +49,27 @@ public static void SetLocalYRotation(this Transform transform, float angleRad) transform.rotation = Quaternion.LookRotation(newForward, newUp); } + + public static void SetLocalXRotation(this Transform transform, float angleRad) + { + var localToWorldMatrix = transform.localToWorldMatrix; + + // Get the current local X rotation and calculate its inverse + var localRotationX = transform.localRotation.eulerAngles.x; + var inverseXRotation = math.inverse(float4x4.RotateX(math.radians(localRotationX))); + + // Remove the current X rotation + var localToWorldPhysicsMatrix = math.mul(localToWorldMatrix, inverseXRotation); + + // Apply the new X rotation + var rotatedMatrix = math.mul(localToWorldPhysicsMatrix, float4x4.RotateX(angleRad)); + + // Extract the updated forward and up directions from the rotated matrix + var newForward = rotatedMatrix.c2.xyz; // Correct forward vector after rotation + var newUp = rotatedMatrix.c1.xyz; // Correct up vector after rotation + + // Set the object's rotation using the new forward and up directions + transform.rotation = Quaternion.LookRotation(newForward, newUp); + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index d9ba41c10..ac69ab194 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using Unity.Collections; +using Unity.Mathematics; using UnityEngine; namespace VisualPinball.Unity @@ -78,13 +79,8 @@ internal void ApplyHitTargetMovement(ref NativeParallelHashMap _normal; - public override string ToString() => $"TriangleCollider[{Header.ItemId}] ({Rgv0.x}/{Rgv0.y}/{Rgv0.z}), ({Rgv1.x}/{Rgv1.y}/{Rgv1.z}), ({Rgv2.x}/{Rgv2.y}/{Rgv2.z}) at ({_normal.x}/{_normal.y/_normal.z})"; + public override string ToString() => $"TriangleCollider[{Header.ItemId}] ({Rgv0.x}/{Rgv0.y}/{Rgv0.z}), ({Rgv1.x}/{Rgv1.y}/{Rgv1.z}), ({Rgv2.x}/{Rgv2.y}/{Rgv2.z}) at ({_normal.x}/{_normal.y}/{_normal.z})"; public ColliderBounds Bounds { get; private set; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs index 6d1ed5213..46cdedebb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs @@ -75,22 +75,22 @@ internal void GenerateColliders(ref ColliderReference colliders) var rgv1 = rgv3D[i1].ToUnityFloat3(); var rgv2 = rgv3D[i2].ToUnityFloat3(); - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(true)), _matrix); + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(true)), Matrix); if (addedEdges.ShouldAddHitEdge(i0, i1)) { - colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(true)), _matrix); + colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(true)), Matrix); } if (addedEdges.ShouldAddHitEdge(i1, i2)) { - colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(true)), _matrix); + colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(true)), Matrix); } if (addedEdges.ShouldAddHitEdge(i2, i0)) { - colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(true)), _matrix); + colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(true)), Matrix); } } // add collision vertices for (var i = 0; i < DropTargetHitPlaneVertices.Length; ++i) { - colliders.Add(new PointCollider(rgv3D[i].ToUnityFloat3(), GetColliderInfo(true)), _matrix); + colliders.Add(new PointCollider(rgv3D[i].ToUnityFloat3(), GetColliderInfo(true)), Matrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs index 2f7e46ca9..b26565b45 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs @@ -18,6 +18,9 @@ namespace VisualPinball.Unity { internal struct HitTargetAnimationData { + ///

+ /// Current X-rotation, in degrees. + /// public float XRotation; public bool HitEvent; public uint TimeMsec; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 5bdad7794..2a4049908 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -63,7 +63,7 @@ internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new HitTargetColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); + var colliderGenerator = new HitTargetColliderGenerator(ColliderComponent.ColliderMesh, this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index 641032af9..424027ebf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -28,6 +28,9 @@ public class HitTargetColliderComponent : ColliderComponent (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); + + public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) + => MainComponent.TransformationWithinPlayfield.TransformToVpx(); + } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs index eb570a153..4b906bc4f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs @@ -14,29 +14,40 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.HitTarget; +using Mesh = UnityEngine.Mesh; namespace VisualPinball.Unity { public class HitTargetColliderGenerator : TargetColliderGenerator { - public HitTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider, float4x4 matrix) - : base(api, data, meshProvider, matrix) { } + private readonly Mesh _colliderMesh; + + public HitTargetColliderGenerator(Mesh colliderMesh, IApiColliderGenerator api, ITargetData data, + IMeshGenerator meshProvider, float4x4 matrix) + : base(api, data, meshProvider, matrix) + { + _colliderMesh = colliderMesh; + } internal void GenerateColliders(ref ColliderReference colliders) { - var localToPlayfield = MeshGenerator.GetTransformationMatrix(); - var hitMesh = MeshGenerator.GetMesh(); - for (var i = 0; i < hitMesh.Vertices.Length; i++) { - hitMesh.Vertices[i].MultiplyMatrix(localToPlayfield); - } - var addedEdges = EdgeSet.Get(Allocator.TempJob); - GenerateCollidables(hitMesh, ref addedEdges, true, ref colliders); - addedEdges.Dispose(); + using var meshDataArray = Mesh.AcquireReadOnlyMeshData(_colliderMesh); + var meshData = meshDataArray[0]; + var subMesh = meshData.GetSubMesh(0); // todo loop through all sub meshes? + var unityVertices = new NativeArray(meshData.vertexCount, Allocator.TempJob); + var unityIndices = new NativeArray(subMesh.indexCount, Allocator.TempJob); + meshData.GetVertices(unityVertices); + meshData.GetIndices(unityIndices, 0); + + ColliderUtils.GenerateCollidersFromMesh(in unityVertices, in unityIndices, Matrix, Api.GetColliderInfo(), ref colliders); + + unityVertices.Dispose(); + unityIndices.Dispose(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs index 9f98de683..ae318f888 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; using VisualPinball.Engine.VPT.Table; @@ -136,6 +137,7 @@ internal HitTargetState CreateState() ? new HitTargetStaticData { Speed = hitTargetAnimationComponent.Speed, MaxAngle = hitTargetAnimationComponent.MaxAngle, + InitialXRotation = transform.localRotation.eulerAngles.x, } : default; var animationData = hitTargetColliderComponent && hitTargetAnimationComponent diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs index 4e3e02271..b8adb26ed 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs @@ -20,5 +20,9 @@ internal struct HitTargetStaticData { public float Speed; public float MaxAngle; + /// + /// Initial X-rotation, in degrees. + /// + public float InitialXRotation; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs index a55ded7bf..43dbeba74 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs @@ -22,54 +22,54 @@ namespace VisualPinball.Unity { public abstract class TargetColliderGenerator { - private readonly IApiColliderGenerator _api; + protected readonly IApiColliderGenerator Api; protected readonly ITargetData Data; protected readonly IMeshGenerator MeshGenerator; - protected readonly float4x4 _matrix; + protected readonly float4x4 Matrix; protected TargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshGenerator, float4x4 matrix) { - _api = api; + Api = api; Data = data; MeshGenerator = meshGenerator; - _matrix = matrix; + Matrix = matrix; } - private protected void GenerateCollidables(Mesh hitMesh, ref EdgeSet addedEdges, bool setHitObject, ref ColliderReference colliders) { - - // add the normal drop target as collidable but without hit event - for (var i = 0; i < hitMesh.Indices.Length; i += 3) { - var i0 = hitMesh.Indices[i]; - var i1 = hitMesh.Indices[i + 1]; - var i2 = hitMesh.Indices[i + 2]; - - // NB: HitTriangle wants CCW vertices, but for rendering we have them in CW order - var rgv0 = hitMesh.Vertices[i0].ToUnityFloat3(); - var rgv1 = hitMesh.Vertices[i1].ToUnityFloat3(); - var rgv2 = hitMesh.Vertices[i2].ToUnityFloat3(); - - colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(setHitObject))); - - if (addedEdges.ShouldAddHitEdge(i0, i1)) { - colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(setHitObject))); - } - if (addedEdges.ShouldAddHitEdge(i1, i2)) { - colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(setHitObject))); - } - if (addedEdges.ShouldAddHitEdge(i2, i0)) { - colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(setHitObject))); - } - } - - // add collision vertices - foreach (var vertex in hitMesh.Vertices) { - colliders.Add(new PointCollider(vertex.ToUnityFloat3(), GetColliderInfo(setHitObject))); - } - } + // private protected void GenerateCollidables(Mesh hitMesh, ref EdgeSet addedEdges, bool setHitObject, ref ColliderReference colliders) { + // + // // add the normal drop target as collidable but without hit event + // for (var i = 0; i < hitMesh.Indices.Length; i += 3) { + // var i0 = hitMesh.Indices[i]; + // var i1 = hitMesh.Indices[i + 1]; + // var i2 = hitMesh.Indices[i + 2]; + // + // // NB: HitTriangle wants CCW vertices, but for rendering we have them in CW order + // var rgv0 = hitMesh.Vertices[i0].ToUnityFloat3(); + // var rgv1 = hitMesh.Vertices[i1].ToUnityFloat3(); + // var rgv2 = hitMesh.Vertices[i2].ToUnityFloat3(); + // + // colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, GetColliderInfo(setHitObject))); + // + // if (addedEdges.ShouldAddHitEdge(i0, i1)) { + // colliders.Add(new Line3DCollider(rgv0, rgv2, GetColliderInfo(setHitObject))); + // } + // if (addedEdges.ShouldAddHitEdge(i1, i2)) { + // colliders.Add(new Line3DCollider(rgv2, rgv1, GetColliderInfo(setHitObject))); + // } + // if (addedEdges.ShouldAddHitEdge(i2, i0)) { + // colliders.Add(new Line3DCollider(rgv1, rgv0, GetColliderInfo(setHitObject))); + // } + // } + // + // // add collision vertices + // foreach (var vertex in hitMesh.Vertices) { + // colliders.Add(new PointCollider(vertex.ToUnityFloat3(), GetColliderInfo(setHitObject))); + // } + // } protected ColliderInfo GetColliderInfo(bool setHitObject) { - var info = _api.GetColliderInfo(); + var info = Api.GetColliderInfo(); info.FireEvents = setHitObject && info.FireEvents; return info; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index 0029e2d9d..8a607ff35 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -109,6 +109,8 @@ public Matrix3D GetTransformationMatrix() protected abstract float ZOffset { get; } + public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix); + #endregion #region Conversion @@ -177,6 +179,7 @@ public override void CopyFromObject(GameObject go) public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); public override ItemDataTransformType EditorScaleType => ItemDataTransformType.ThreeD; + public override Vector3 GetEditorScale() => Size; public override void SetEditorScale(Vector3 scale) => Size = scale; From e5bca19f80b575eba813f72bfbb826a89bee8102 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 00:10:41 +0100 Subject: [PATCH 121/208] hittargets: Add collider meshes. --- .../Art/Meshes/Hit Target/Hit Targets VPX.fbx | Bin 66588 -> 93308 bytes .../Hit Target/Hit Targets VPX.fbx.meta | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx index 0b7a7143b3f733894676f5c5117651dd662c7775..820a2a1489e0884ca44fad63dda989694dd0fced 100644 GIT binary patch delta 24869 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 delta 3412 zcmZu!30PFu6@KSDhJ72h+1Q3*1XC23f>8!hzyt&$3Myj76?MRl3qxzF22HGBqN1MY zvkEnFiMAn9`;0bmiP6T0)fzu{Tdmd@6>HQ8E^#6E-FY3&r|*O3f6jU5{^!5vo%`_d z8BIf-$5IolTNZhz#vlkn0tf>85j5=ABWG&$jNAx@GM@8#7T6ut&hlIx^ACUCUsOJR zs$)WK0P{Pa^ZOOpr%bU|xq_7}__OPP`eb)OpheIJR>)sN7GRwhZPn^zi|FqWJj=^f zPG6P?g+mFot?w z69wT@e`QHL=uJ1hQ$Qq(PajC9GN0Zsi&puhqA;!8}y(zeEUK*E%wdC zoxXI;HyeB?!mj{E(*(bvxW$il`W4_MKl1hO4sm4j?+4>(j(-Mj_NP7mWyx*IHmH|R z_SEs!zbHen2HSvWdvGs25=^&( zM}tIzw6Xdm?F;N3K6LkYXb z(jUSNu$1KRUU(v${uiDJQIs5!0~6WZLTe(d(9F@d5mvkzL4GW1NQ z9o^IQg3i=cKNM=%-9&5keQ>*;<{FGt9cIFtE|_F6K|E;$iZW+IKD)=zJhK(vrcLG){M<|z%&B!Ub27MF~t;|MLuVo!@8eNhCNbl9SUg|x&n6wh1eie(7v@}Df zzaSzRl<{$0UW3L}l#h2-?Xgjq^>s|JD(|lp>j?p3j#ZGlc|tVIm)BdQ zp-}Gri#I`>lp;v;vcQasllW;-&K6vsw7fnH&m~buQVMFj&~#@C6w6b(WO_oW+}tw+ z#SLkK^q~c!@Pib0)~*!#rqzgdQ+V1dsXVP-{(b5M5hS^_cNpSU8$oS2o=8(h7W>+$ zd#z3kw+YggG_XQfY^1G-W5dOK%0UN_WJoW-9_i1N6{WkB;igQvIemx* z=j6zF>rZI#tvuQMrw$ao%eb~U$a5EYEKiZHt~J7;FwTIfij-hPwq6@sloQia>InZ9vD=?)%CD*bq#i|u8OUusKa_Y zO}8Z=Rw!;fYggo9J9qMf{EJ$eS&@ac70(22u25U|mMm7D6!DYk@;OHr5#E!%XPgHy zqDF1q!76v57pfF};Z8*#dkv>KQ(5OWIla8WM{b_|CBjCUUK0a@XjzROD{FYi*46Oc zI9sEL!tdXaQ|5+xz$!U+{&vK_)^brN7bv12gSyq~#fVz9V?AhkZ6aHeIdOI^Z`dlu z#Tyn##=0!*T=z`Rz&ce=CwXJtzYvV_y!Y+`Op>b?=YzO%r7CB`61SYsmnd@XEa3$v z*7E`nzc0_PU#EeEa>9xh5Zl(M?K-;Bopxg-7ZxY+yb%)bR+_v`x`cRjjV!I+0SJv; zSbT#b43{+UZk0Ev-I~|HyVb_s_>TtOt*hM0#nqF&@pYI@TN|xnW25>W-dZO=Y%J8^ zSDRh$;en6&d&ro~O%@cJ-1Qln6j_B$yu8{bF6*nm$(NgK{;*Vz{p_@dSbb2Hb!xXe z?T6i5R_8rDZ_FN^w|l?5YtKjxOqTV3-{FC8w{Te-4{}*SEnL=jY%xA!Nc~$9v6_1^ zpPkQa#3m~~b;Md*attZYj5vn({;}^UF}?0L%VCb*jldXYIjZ7J=b)#yQYgf6=~w#`-0>c4E=ECJznZH zM*5rkics<9J=bX~8AgLn?8zQv$8wfvXMlaO`iaXKh$#hw_=yrK2EAebeb=`h*&<#= zlgCc5J0@kpcPxCT7`li@9}3cdA{c#GGQhd6Gr+k#*czP6*zq#1bQV^) zkbPj*ltLzM>TpwS6r^J|&|#z?X-7gSj$@R=Asbh*qrVvXBh zUk2qv{PUL~9HiUD(3QUT3NR*$&py?-&kkYIwMn4G6{-<5nHjrO zBULdjtHvnB@I_b7e8uRl8Xqf0iE8Y!GowZ|E-S`Is^MF~j3X{1wDjy-R#vr;fBeRB rrF5Z9eX2xx$|SmIYM5bGqV8+Vb7C~bV=K$6ibQEmB@|1yD`C-3U!4l| diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta index d09d07d3a..9b8e0d306 100644 --- a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta +++ b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta @@ -32,7 +32,7 @@ ModelImporter: extraExposedTransformPaths: [] extraUserProperties: [] clipAnimations: [] - isReadable: 0 + isReadable: 1 meshes: lODScreenPercentages: [] globalScale: 1 From c91a3003fa2778951b35fad5ac4dec8426ed9859 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 00:32:00 +0100 Subject: [PATCH 122/208] droptarget: Fix animation translation when rotated. --- .../VisualPinball.Unity/Game/PhysicsMovements.cs | 13 +++++++------ .../VPT/HitTarget/DropTargetComponent.cs | 1 + .../VPT/HitTarget/DropTargetStaticState.cs | 3 +++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index ac69ab194..02e369cd6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -65,12 +65,13 @@ internal void ApplyDropTargetMovement(ref NativeParallelHashMap. +using Unity.Mathematics; + namespace VisualPinball.Unity { internal struct DropTargetStaticState @@ -21,5 +23,6 @@ internal struct DropTargetStaticState public float Speed; public float RaiseDelay; public bool UseHitEvent; + public float3 InitialPosition; } } From 62ce3e9f86bee2ab9effb656ec701ada4eab5c7d Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 18:37:26 +0100 Subject: [PATCH 123/208] triggers: Replace data with transformation parameters. --- .../VPT/Trigger/TriggerInspector.cs | 33 ++++++++++++----- .../VPT/Trigger/TriggerComponent.cs | 36 ++++++++++++++----- 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs index dc2442085..3c575b240 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs @@ -29,9 +29,6 @@ public class TriggerInspector : MainInspector, ID { public Transform Transform => MainComponent.transform; - private SerializedProperty _positionProperty; - private SerializedProperty _scaleProperty; - private SerializedProperty _rotationProperty; private SerializedProperty _surfaceProperty; protected override void OnEnable() @@ -41,9 +38,6 @@ protected override void OnEnable() DragPointsHelper = new DragPointsInspectorHelper(MainComponent, this); DragPointsHelper.OnEnable(); - _positionProperty = serializedObject.FindProperty(nameof(TriggerComponent.Position)); - _scaleProperty = serializedObject.FindProperty(nameof(TriggerComponent.Scale)); - _rotationProperty = serializedObject.FindProperty(nameof(TriggerComponent.Rotation)); _surfaceProperty = serializedObject.FindProperty(nameof(TriggerComponent._surface)); } @@ -63,9 +57,30 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_scaleProperty, updateTransforms: true); - PropertyField(_rotationProperty, updateTransforms: true); + // position + EditorGUI.BeginChangeCheck(); + var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the trigger on the playfield, relative to its parent."), MainComponent.Position); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Trigger Position"); + MainComponent.Position = newPos; + } + + // scale + EditorGUI.BeginChangeCheck(); + var newScale = EditorGUILayout.Slider(new GUIContent("Scale", "Scales the trigger mesh by this value."), MainComponent.Scale, 0.5f, 1.5f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Trigger Scale"); + MainComponent.Scale = newScale; + } + + // rotation + EditorGUI.BeginChangeCheck(); + var newRotation = EditorGUILayout.Slider(new GUIContent("Rotation", "Orientation angle. Updates z rotation."), MainComponent.Rotation, -180f, 180f); + if (EditorGUI.EndChangeCheck()) { + Undo.RecordObject(MainComponent.transform, "Change Trigger Rotation"); + MainComponent.Rotation = newRotation; + } + PropertyField(_surfaceProperty, updateTransforms: true); DragPointsHelper.OnInspectorGUI(this); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 6a1e22bee..2587b9875 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -41,16 +41,36 @@ public class TriggerComponent : MainRenderableComponent, { #region Data - [Tooltip("Position on the playfield.")] - public Vector2 Position; + private Vector3 _position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); + } + + public Vector2 Position { + get => _position.XY(); + set => _position = new Vector3(value.x, value.y, _position.z); + } - [Tooltip("Scales the trigger mesh by this value.")] - [Range(0.5f, 1.5f)] - public float Scale = 1; + public float _scale = 1f; + public float Scale + { + get { + var scale = transform.localScale; + if (math.abs(scale.x - scale.y) < Collider.Tolerance && math.abs(scale.x - scale.z) < Collider.Tolerance && math.abs(scale.y - scale.z) < Collider.Tolerance) { + return scale.x; + } + return _scale; + } + set { + _scale = value; + transform.localScale = new Vector3(value, value, value); + } + } - [Tooltip("Rotation of the trigger.")] - [Range(-180f, 180f)] - public float Rotation; + public float Rotation { + get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; + set => transform.SetLocalYRotation(math.radians(value)); + } [SerializeField] [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] From 9f1e123f8cbe1b8c152f8fabf1059c1f1e455c61 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 19:25:03 +0100 Subject: [PATCH 124/208] triggers: Make colliders freely transformable. --- VisualPinball.Engine/Math/DragPointData.cs | 6 ++++++ .../VisualPinball.Unity/Physics/Collider/ColliderUtils.cs | 7 +++++++ .../VisualPinball.Unity/VPT/Trigger/TriggerApi.cs | 2 +- .../VPT/Trigger/TriggerColliderGenerator.cs | 2 +- .../VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs | 4 ++-- 5 files changed, 17 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Engine/Math/DragPointData.cs b/VisualPinball.Engine/Math/DragPointData.cs index 8d057a258..51224ce18 100644 --- a/VisualPinball.Engine/Math/DragPointData.cs +++ b/VisualPinball.Engine/Math/DragPointData.cs @@ -95,6 +95,12 @@ public DragPointData Lerp(DragPointData dp, float pos) }; } + public DragPointData Translate(Vertex3D v) + { + Center += v; + return this; + } + public DragPointData Clone() { return new DragPointData(Center) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index 354510d1b..e8034ef05 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -28,6 +28,13 @@ public static class ColliderUtils private static readonly ProfilerMarker PerfMarker1 = new("ColliderUtils.GenerateCollidersFromMesh.ICollider"); private static readonly ProfilerMarker PerfMarker2 = new("ColliderUtils.GenerateCollidersFromMesh.NativeArray"); + /// + /// Creates a 3D polygon collider from a list of vertices by triangulating them. + /// + /// Vertices, in VPX space + /// Collider info + /// Colliders to write to + /// Transformation matrix to supply when adding colliders. public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix) { var inputVerts = new float2[rgv.Length]; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs index e098a7151..39a5d7516 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs @@ -68,7 +68,7 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var meshComponent = GameObject.GetComponent(); - var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent, GetTransformationWithinPlayfield()); + var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index ce0d8066a..285cd0efa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -53,7 +53,7 @@ internal void GenerateColliders(ref ColliderReference colliders) private void GenerateRoundHitObjects(ref ColliderReference colliders) { var height = _component.PositionZ; - colliders.Add(new CircleCollider(_component.Center, _colliderComponent.HitCircleRadius, height, height + _colliderComponent.HitHeight, + colliders.Add(new CircleCollider(new float2(0), _colliderComponent.HitCircleRadius, height, height + _colliderComponent.HitHeight, _api.GetColliderInfo(), ColliderType.TriggerCircle), _matrix); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 2587b9875..bf3bb4d3c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -141,7 +141,7 @@ private void Start() [NonSerialized] private float4x4 _playfieldToWorld; - public Vector2 Center => Position; + public Vector2 Center => Position; // todo remove? public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); @@ -176,7 +176,7 @@ public override IEnumerable SetData(TriggerData data) Rotation = data.Rotation; // geometry - DragPoints = data.DragPoints; + DragPoints = data.DragPoints.Select(dp => dp.Translate((-transform.localPosition).TranslateToVpx().ToVertex3D())).ToArray(); // mesh var meshComponent = GetComponent(); From 703a2c1d1afd19ae041f11f6463ef93d93194691 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 27 Nov 2024 21:23:40 +0100 Subject: [PATCH 125/208] chore: Clean up unused collider reference APIs. --- .../Physics/Collider/ColliderReference.cs | 41 +++---------------- 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index f7ed8b372..5325373ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -254,16 +254,6 @@ internal int Add(Line3DCollider collider, float4x4 matrix) return collider.Id; } - [Obsolete("Add with matrix only.")] - internal int Add(Line3DCollider collider) - { - collider.Id = Lookups.Length; - TrackReference(collider.Header.ItemId, collider.Header.Id); - Lookups.Add(new ColliderLookup(ColliderType.Line3D, Line3DColliders.Length)); - Line3DColliders.Add(collider); - return collider.Id; - } - internal int Add(LineSlingshotCollider collider, float4x4 matrix) { if (LineSlingshotCollider.IsTransformable(matrix)) { @@ -286,9 +276,8 @@ internal int Add(LineSlingshotCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(LineCollider collider) => Add(collider, float4x4.identity); - - internal int Add(LineCollider collider, float4x4 matrix) + internal int Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only + private int Add(LineCollider collider, float4x4 matrix) { if (LineCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; @@ -310,7 +299,7 @@ internal int Add(LineCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(LineZCollider collider) + private int Add(LineZCollider collider) { collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); @@ -354,16 +343,6 @@ internal int Add(PointCollider collider, float4x4 matrix) return collider.Id; } - [Obsolete("Add with matrix only.")] - internal int Add(PointCollider collider) - { - collider.Id = Lookups.Length; - TrackReference(collider.Header.ItemId, collider.Header.Id); - Lookups.Add(new ColliderLookup(ColliderType.Point, PointColliders.Length)); - PointColliders.Add(collider); - return collider.Id; - } - internal int Add(SpinnerCollider collider, float4x4 matrix) { if (SpinnerCollider.IsTransformable(matrix)) { @@ -399,17 +378,7 @@ internal int Add(TriangleCollider collider, float4x4 matrix) return collider.Id; } - [Obsolete("Add with matrix only.")] - internal int Add(TriangleCollider collider) - { - collider.Id = Lookups.Length; - TrackReference(collider.Header.ItemId, collider.Header.Id); - Lookups.Add(new ColliderLookup(ColliderType.Triangle, TriangleColliders.Length)); - TriangleColliders.Add(collider); - return collider.Id; - } - - internal int Add(PlaneCollider collider) + internal int Add(PlaneCollider collider) // used for the playfield only { collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); @@ -446,7 +415,7 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) { - if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { + if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { // todo support scale and z-rotation Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info), matrix); } else { Add(new LineZCollider(xy, zLow, zHigh, info).Transform(matrix)); From 06e4c24d48782348e2798c6c322c7965751c6744 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 28 Nov 2024 22:10:41 +0100 Subject: [PATCH 126/208] trigger: Fix animation. --- .../Game/PhysicsMovements.cs | 9 +- .../VPT/Trigger/TriggerAnimation.cs | 164 +++++++++--------- .../VPT/Trigger/TriggerComponent.cs | 6 +- .../VPT/Trigger/TriggerStaticState.cs | 5 +- .../VPT/Trigger/TriggerTransform.cs | 35 ---- .../VPT/Trigger/TriggerTransform.cs.meta | 11 -- 6 files changed, 98 insertions(+), 132 deletions(-) delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index 02e369cd6..ed28c3079 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -129,7 +129,14 @@ internal void ApplyTriggerMovement(ref NativeParallelHashMap continue; } var triggerTransform = transforms[triggerState.AnimatedItemId]; - TriggerTransform.Update(triggerState.AnimatedItemId, in triggerState.Movement, triggerTransform); + + var localYDirection = triggerTransform.up; + + // Compute the new position by moving along the local Y-axis + var newPosition = (Vector3)triggerState.Static.InitialPosition + localYDirection * Physics.ScaleToWorld(triggerState.Movement.HeightOffset); + + // Apply the new position + triggerTransform.localPosition = newPosition; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs index 245822b61..1a60991e3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs @@ -1,82 +1,82 @@ -// 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 . - -using VisualPinball.Engine.VPT; - -namespace VisualPinball.Unity -{ - internal static class TriggerAnimation - { - internal static void Update(ref TriggerAnimationState animation, ref TriggerMovementState movement, in TriggerStaticState staticState, - float dTimeMs) - { - var oldTimeMsec = animation.TimeMsec < dTimeMs ? animation.TimeMsec : dTimeMs; - animation.TimeMsec = dTimeMs; - var diffTimeMsec = dTimeMs - oldTimeMsec; - - var animLimit = staticState.Shape == TriggerShape.TriggerStar ? staticState.Radius * (float)(1.0 / 5.0) : 32.0f; - if (staticState.Shape == TriggerShape.TriggerButton) { - animLimit = staticState.Radius * (float)(1.0 / 10.0); - } - if (staticState.Shape == TriggerShape.TriggerWireC) { - animLimit = 60.0f; - } - if (staticState.Shape == TriggerShape.TriggerWireD) { - animLimit = 25.0f; - } - - var limit = animLimit * staticState.TableScaleZ; - - if (animation.HitEvent) { - animation.DoAnimation = true; - animation.HitEvent = false; - // unhitEvent = false; // Bugfix: If HitEvent and unhitEvent happen at the same time, you want to favor the unhit, otherwise the switch gets stuck down. - movement.HeightOffset = 0.0f; - animation.MoveDown = true; - } - if (animation.UnHitEvent) { - animation.DoAnimation = true; - animation.UnHitEvent = false; - animation.HitEvent = false; - movement.HeightOffset = limit; - animation.MoveDown = false; - } - - if (animation.DoAnimation) { - var step = diffTimeMsec * staticState.AnimSpeed * staticState.TableScaleZ; - if (animation.MoveDown) { - step = -step; - } - movement.HeightOffset += step; - - if (animation.MoveDown) { - if (movement.HeightOffset <= -limit) { - movement.HeightOffset = -limit; - animation.DoAnimation = false; - animation.MoveDown = false; - } - - } else { - if (movement.HeightOffset >= 0.0f) { - movement.HeightOffset = 0.0f; - animation.DoAnimation = false; - animation.MoveDown = true; - } - } - } - } - } -} +// 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 . + +using VisualPinball.Engine.VPT; + +namespace VisualPinball.Unity +{ + internal static class TriggerAnimation + { + internal static void Update(ref TriggerAnimationState animation, ref TriggerMovementState movement, in TriggerStaticState staticState, + float dTimeMs) + { + // var oldTimeMsec = animation.TimeMsec < dTimeMs ? animation.TimeMsec : dTimeMs; + // animation.TimeMsec = dTimeMs; + // var diffTimeMsec = dTimeMs - oldTimeMsec; + + var animLimit = staticState.Shape == TriggerShape.TriggerStar ? staticState.Radius * (float)(1.0 / 5.0) : 32.0f; + if (staticState.Shape == TriggerShape.TriggerButton) { + animLimit = staticState.Radius * (float)(1.0 / 10.0); + } + if (staticState.Shape == TriggerShape.TriggerWireC) { + animLimit = 60.0f; + } + if (staticState.Shape == TriggerShape.TriggerWireD) { + animLimit = 25.0f; + } + + var limit = animLimit * staticState.TableScaleZ; + + if (animation.HitEvent) { + animation.DoAnimation = true; + animation.HitEvent = false; + // unhitEvent = false; // Bugfix: If HitEvent and unhitEvent happen at the same time, you want to favor the unhit, otherwise the switch gets stuck down. + movement.HeightOffset = 0.0f; + animation.MoveDown = true; + } + if (animation.UnHitEvent) { + animation.DoAnimation = true; + animation.UnHitEvent = false; + animation.HitEvent = false; + //movement.HeightOffset = limit; + animation.MoveDown = false; + } + + if (animation.DoAnimation) { + var step = dTimeMs * staticState.AnimSpeed * staticState.TableScaleZ; + if (animation.MoveDown) { + step = -step; + } + movement.HeightOffset += step; + + if (animation.MoveDown) { + if (movement.HeightOffset <= -limit) { + movement.HeightOffset = -limit; + animation.DoAnimation = false; + animation.MoveDown = false; + } + + } else { + if (movement.HeightOffset >= 0.0f) { + movement.HeightOffset = 0.0f; + animation.DoAnimation = false; + animation.MoveDown = true; + } + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index bf3bb4d3c..607d37c48 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -297,7 +297,8 @@ internal TriggerState CreateState() AnimSpeed = animComponent ? animComponent.AnimSpeed : 0, Radius = collComponent.HitCircleRadius, Shape = meshComponent ? meshComponent.Shape : 0, - TableScaleZ = 1f + TableScaleZ = 1f, + InitialPosition = transform.localPosition }, new TriggerMovementState(), new TriggerAnimationState() @@ -309,7 +310,8 @@ internal TriggerState CreateState() AnimSpeed = 0, Radius = collComponent.HitCircleRadius, Shape = TriggerShape.TriggerNone, - TableScaleZ = 1f + TableScaleZ = 1f, + InitialPosition = transform.position }, new FlipperCorrectionState( true, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs index ecf610673..70f27952b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using Unity.Mathematics; + namespace VisualPinball.Unity { internal struct TriggerStaticState @@ -21,8 +23,9 @@ internal struct TriggerStaticState public int Shape; public float Radius; public float AnimSpeed; + public float3 InitialPosition; // table data - public float TableScaleZ; + public float TableScaleZ; // todo nuke } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs deleted file mode 100644 index 62f22ee02..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs +++ /dev/null @@ -1,35 +0,0 @@ -// 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 . - -using System.Collections.Generic; -using UnityEngine; - -namespace VisualPinball.Unity -{ - internal static class TriggerTransform - { - private static readonly Dictionary _initialOffset = new(); - - internal static void Update(int itemId, in TriggerMovementState movement, Transform transform) - { - var worldPos = transform.position; - _initialOffset.TryAdd(itemId, worldPos.y); - - worldPos.y = _initialOffset[itemId] + Physics.ScaleToWorld(movement.HeightOffset); - transform.position = worldPos; - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta deleted file mode 100644 index 224cdad95..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 74ac7befec6231b4d9d48cd9098980f0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From ee199715db92a4fbe4e90855eea97a332894b7e5 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 28 Nov 2024 22:34:10 +0100 Subject: [PATCH 127/208] fix: Collider gizmo when object contains both transformable and non-transformable colliders. --- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index f03db38ba..6d0dd1510 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -220,8 +220,7 @@ private void OnDrawGizmos() if (showColliders) { //Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; - Handles.matrix = Gizmos.matrix; + // var color = Application.isPlaying && IsKinematic // ? Color.magenta @@ -254,6 +253,8 @@ private void OnDrawGizmos() } if (_staticColliderMesh) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; + Handles.matrix = Gizmos.matrix; Gizmos.color = green; Gizmos.DrawMesh(_staticColliderMesh); Gizmos.color = white; From 7375ae71a79b3eb69073942c1e46ce5c117f843b Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 28 Nov 2024 23:25:59 +0100 Subject: [PATCH 128/208] trigger: Fix transformation on start. --- .../Physics/Collider/ColliderUtils.cs | 2 +- .../VisualPinball.Unity/VPT/Ball/BallState.cs | 2 +- .../VPT/Trigger/TriggerComponent.cs | 15 --------------- 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index e8034ef05..2bbdd0eae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -53,7 +53,7 @@ public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, r } var mesh = new Mesh(triangulatedVerts, outputIndices); - GenerateCollidersFromMesh(mesh, info, ref colliders, matrix, false); + GenerateCollidersFromMesh(mesh, info, ref colliders, matrix); } public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, float4x4 matrix, bool onlyTriangles = false) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs index 506e38a37..ada85255e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs @@ -140,7 +140,7 @@ the angular velocity used here is not the angular velocity that the ball has (wh public override string ToString() { - return $"Ball{Id} ({Position.x}/{Position.y})"; + return $"Ball{Id} ({Position.x}/{Position.y}/{Position.z})"; } public void Transform(float4x4 matrix) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 607d37c48..5c3f1d944 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -146,21 +146,6 @@ private void Start() public void OnSurfaceUpdated() => UpdateTransforms(); public float PositionZ => SurfaceHeight(Surface, Position); - public override void UpdateTransforms() - { - base.UpdateTransforms(); - var t = transform; - - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); - - // scale - t.localScale = new Vector3(Scale, Scale, Scale); - - // rotation - t.localEulerAngles = new Vector3(0, Rotation, 0); - } - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion From ed83d8b6a181c8d7e1dfbf999a1720a91a5d1f2f Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 29 Nov 2024 01:03:26 +0100 Subject: [PATCH 129/208] fix: Properly check for hit before translating normal back into VPX space. --- .../Game/PhysicsStaticNarrowPhase.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs index 799d63be0..8fcee1216 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs @@ -54,7 +54,7 @@ ref PhysicsState state newTime = state.HitTest(ref colliders, overlappingColliderId, ref ballTransformed, ref newCollEvent, ref contacts); - if (newTime > 0) { + if (IsValidHit(ref ball, newTime)) { // transform hit normal back to world space newCollEvent.Transform(matrix); } @@ -70,12 +70,15 @@ ref PhysicsState state PerfMarkerNarrowPhase.End(); } + private static bool IsValidHit(ref BallState ball, float newTime) + { + return newTime >= 0f && !Math.Sign(newTime) && newTime <= ball.CollisionEvent.HitTime; + } + private static void SaveCollisions(ref BallState ball, ref CollisionEventData newCollEvent, ref NativeList contacts, int colliderId, float newTime, bool isKinematic) { - var validHit = newTime >= 0f && !Math.Sign(newTime) && newTime <= ball.CollisionEvent.HitTime; - - if (newCollEvent.IsContact || validHit) { // todo why newCollEvent.IsContact? it's not in vpx source + if (newCollEvent.IsContact || IsValidHit(ref ball, newTime)) { // todo why newCollEvent.IsContact? it's not in vpx source newCollEvent.SetCollider(colliderId, isKinematic); newCollEvent.HitTime = newTime; if (newCollEvent.IsContact) { // remember all contacts? From cb40fdb89f107e8c8cd1add85481dde4e29cf3be Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 29 Nov 2024 01:08:13 +0100 Subject: [PATCH 130/208] trigger: Use non-transformable colliders. --- .../Physics/Collider/ColliderReference.cs | 2 +- .../VPT/Trigger/TriggerColliderGenerator.cs | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 5325373ba..6de4141c7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -277,7 +277,7 @@ internal int Add(LineSlingshotCollider collider, float4x4 matrix) } internal int Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only - private int Add(LineCollider collider, float4x4 matrix) + internal int Add(LineCollider collider, float4x4 matrix) { if (LineCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index 285cd0efa..53219c16c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -38,6 +38,7 @@ public TriggerColliderGenerator(TriggerApi triggerApi, TriggerComponent componen _meshComponent = meshComponent; _colliderComponent = colliderComponent; _matrix = matrix; + } internal void GenerateColliders(ref ColliderReference colliders) @@ -75,13 +76,13 @@ private void GenerateCurvedHitObjects(ref ColliderReference colliders) for (var i = 0; i < count; i++) { var pv2 = rgv[i < count - 1 ? i + 1 : 0]; var pv3 = rgv[i < count - 2 ? i + 2 : i + 2 - count]; - AddLineSeg(pv2.ToUnityFloat2(), pv3.ToUnityFloat2(), height, ref colliders); + colliders.Add(new LineCollider( + pv2.ToUnityFloat2(), + pv3.ToUnityFloat2(), + height, + height + math.max(_colliderComponent.HitHeight - 8.0f, 0f), + _api.GetColliderInfo()), _matrix); } } - - private void AddLineSeg(float2 pv1, float2 pv2, float height, ref ColliderReference colliders) { - colliders.AddLine(pv1, pv2, height, height + math.max(_colliderComponent.HitHeight - 8.0f, 0f), - _api.GetColliderInfo(), _matrix); - } } } From c7f50a3684a6eb2901d89efcd64555ae67eacf21 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 29 Nov 2024 22:08:19 +0100 Subject: [PATCH 131/208] cleanup: Remove editor transform overloads, surface props, table height, and merged z-pos into pos where possible. --- .../MetalWireGuideMeshGenerator.cs | 1 - .../AssetStructure/AssetDetails.cs | 4 +- .../DragPoint/DragPointMenuItems.cs | 2 +- .../DragPoint/DragPointsInspectorHelper.cs | 48 +- .../DragPoint/DragPointsSceneViewHandler.cs | 4 +- .../DragPoint/IDragPointsInspector.cs | 8 +- .../Utils/HandlesUtils.cs | 6 +- .../VPT/Bumper/BumperInspector.cs | 5 +- .../VPT/Flipper/FlipperInspector.cs | 3 - .../VPT/Gate/GateInspector.cs | 4 - .../VPT/ItemInspector.cs | 12 +- .../VPT/Kicker/KickerInspector.cs | 9 +- .../VPT/Light/LightInsertMeshInspector.cs | 2 +- .../VPT/Light/LightInspector.cs | 6 - .../VPT/MainInspector.cs | 14 - .../MetalWireGuide/MetalWireGuideInspector.cs | 2 +- .../VPT/Playfield/PlayfieldInspector.cs | 5 - .../VPT/Plunger/PlungerInspector.cs | 14 +- .../VPT/Ramp/RampInspector.cs | 2 +- .../VPT/Rubber/RubberInspector.cs | 2 +- .../VPT/Spinner/SpinnerInspector.cs | 8 +- .../VPT/Surface/SurfaceInspector.cs | 8 +- .../VPT/TransformInspector.cs | 371 ------------- .../VPT/TransformInspector.cs.meta | 11 - .../VPT/Trigger/TriggerInspector.cs | 10 +- .../Game/BallRollerComponent.cs | 7 +- .../Game/DebugBallCreator.cs | 4 +- .../Game/Engine/DefaultGamelogicEngine.cs | 2 +- .../VPT/Bumper/BumperApi.cs | 4 +- .../VPT/Bumper/BumperComponent.cs | 46 +- .../VPT/Bumper/BumperSkirtAnimationState.cs | 2 +- .../VPT/Flipper/FlipperApi.cs | 1 - .../VPT/Flipper/FlipperCollider.cs | 6 +- .../VPT/Flipper/FlipperComponent.cs | 39 +- .../VPT/Gate/GateComponent.cs | 39 +- .../VPT/HitTarget/TargetComponent.cs | 17 - .../VPT/IMainRenderableComponent.cs | 42 +- .../VPT/ISurfaceComponent.cs | 43 -- .../VPT/ISurfaceComponent.cs.meta | 11 - .../VPT/Kicker/KickerApi.cs | 8 +- .../VPT/Kicker/KickerComponent.cs | 58 +-- .../VPT/Light/LightComponent.cs | 15 - .../VPT/Light/LightInsertMeshComponent.cs | 4 +- .../VisualPinball.Unity/VPT/MainComponent.cs | 7 - .../VPT/MainRenderableComponent.cs | 26 +- .../VisualPinball.Unity/VPT/MeshComponent.cs | 9 - .../VPT/MetalWireGuide/MetalWireGuideApi.cs | 3 +- .../MetalWireGuide/MetalWireGuideComponent.cs | 489 ++++++++---------- .../MetalWireGuideMeshComponent.cs | 2 +- .../VPT/Playfield/PlayfieldApi.cs | 12 +- .../VPT/Playfield/PlayfieldComponent.cs | 17 +- .../VPT/Plunger/PlungerCollider.cs | 2 +- .../VPT/Plunger/PlungerComponent.cs | 57 +- .../VPT/Plunger/PlungerFlatMeshComponent.cs | 2 +- .../VPT/Plunger/PlungerRodMeshComponent.cs | 2 +- .../VPT/Plunger/PlungerSpringMeshComponent.cs | 2 +- .../VPT/Primitive/PrimitiveComponent.cs | 2 +- .../VPT/Primitive/PrimitiveMeshComponent.cs | 2 +- .../VisualPinball.Unity/VPT/Ramp/RampApi.cs | 4 +- .../VPT/Ramp/RampComponent.cs | 117 ++--- .../VPT/Ramp/RampFloorMeshComponent.cs | 2 +- .../VPT/Ramp/RampWallMeshComponent.cs | 2 +- .../VPT/Ramp/RampWireMeshComponent.cs | 2 +- .../VPT/Rubber/RubberApi.cs | 4 +- .../VPT/Rubber/RubberComponent.cs | 66 --- .../VPT/Rubber/RubberMeshComponent.cs | 2 +- .../VPT/Spinner/SpinnerComponent.cs | 81 +-- .../VPT/Surface/SlingshotComponent.cs | 33 +- .../VPT/Surface/SurfaceApi.cs | 4 +- .../VPT/Surface/SurfaceComponent.cs | 33 +- .../VPT/Surface/SurfaceSideMeshComponent.cs | 2 +- .../VPT/Surface/SurfaceTopMeshComponent.cs | 2 +- .../VPT/Table/TableComponent.cs | 2 +- .../VPT/Trigger/TriggerColliderGenerator.cs | 6 +- .../VPT/Trigger/TriggerComponent.cs | 55 +- .../VPT/Trigger/TriggerMeshComponent.cs | 3 +- 76 files changed, 406 insertions(+), 1557 deletions(-) delete mode 100644 VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs.meta delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta diff --git a/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs b/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs index 4d0b54f20..a7c36f7ed 100644 --- a/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/MetalWireGuide/MetalWireGuideMeshGenerator.cs @@ -19,7 +19,6 @@ #nullable enable using VisualPinball.Engine.Common; -using VisualPinball.Engine.Game; using VisualPinball.Engine.Math; namespace VisualPinball.Engine.VPT.MetalWireGuide diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/AssetBrowser/AssetStructure/AssetDetails.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/AssetBrowser/AssetStructure/AssetDetails.cs index ded688c71..985003d9c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/AssetBrowser/AssetStructure/AssetDetails.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/AssetBrowser/AssetStructure/AssetDetails.cs @@ -274,11 +274,9 @@ private void OnAddSelected() var go = InstantiateAsset(parentTransform); // move to the middle of the playfield + go.transform.localPosition = new Vector3(Physics.ScaleToWorld(pf.Width / 2), 0, -Physics.ScaleToWorld(pf.Height / 2)); if (pf != null && go.GetComponent(typeof(IMainRenderableComponent)) is IMainRenderableComponent comp) { - comp.SetEditorPosition(new Vector3(pf.Width / 2, pf.Height / 2, 0)); comp.UpdateTransforms(); - } else if (pf != null) { - go.transform.localPosition = new Vector3(Physics.ScaleToWorld(pf.Width / 2), 0, -Physics.ScaleToWorld(pf.Height / 2)); } ApplyVariation(go); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointMenuItems.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointMenuItems.cs index 93d2a153c..d2c9b16b0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointMenuItems.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointMenuItems.cs @@ -203,7 +203,7 @@ private static void FlipZ(MenuCommand command) private static bool FlipZValidate(MenuCommand command) { if (command.context is IDragPointsInspector inspector) { - return inspector.HandleType == ItemDataTransformType.ThreeD; + return inspector.HandleType == DragPointTransformType.ThreeD; } return false; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs index c2904d95a..d7428b1fe 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsInspectorHelper.cs @@ -14,8 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System; -using System.Collections; using System.Linq; using UnityEditor; using UnityEngine; @@ -65,11 +63,6 @@ public DragPointsInspectorHelper(IMainRenderableComponent mainComponent, IDragPo public void RebuildMeshes() { _mainComponent.RebuildMeshes(); - if (_playfieldComponent) { - WalkChildren(_playfieldComponent.transform, UpdateSurfaceReferences); - } else { - Debug.LogWarning($"{_mainComponent.name} doesn't seem to have a playfield parent."); - } } public void OnEnable() @@ -125,7 +118,8 @@ public void PasteDragPoint(int controlId) /// True if game item is locked, false otherwise. public bool IsItemLocked() { - return _mainComponent.IsLocked; + // todo delete or implement properly + return false; } /// @@ -144,7 +138,7 @@ public bool HasDragPointExposure(DragPointExposure exposure) /// Axis to flip on public void FlipDragPoints(FlipAxis flipAxis) { - if (_dragPointsInspector.HandleType != ItemDataTransformType.ThreeD && flipAxis == FlipAxis.Z) { + if (_dragPointsInspector.HandleType != DragPointTransformType.ThreeD && flipAxis == FlipAxis.Z) { return; } @@ -206,10 +200,11 @@ public void PrepareUndo(string message) public void OnInspectorGUI(ItemInspector inspector) { - if (_mainComponent.IsLocked) { - EditorGUILayout.LabelField("Drag Points are Locked"); - return; - } + // todo remove or implement properly + // if (_mainComponent.IsLocked) { + // EditorGUILayout.LabelField("Drag Points are Locked"); + // return; + // } GUILayout.Space(10); if (GUILayout.Button("Center Origin")) { @@ -232,7 +227,7 @@ public void OnInspectorGUI(ItemInspector inspector) EditorGUILayout.EndHorizontal(); EditorGUI.indentLevel++; EditorGUI.BeginChangeCheck(); - if (_dragPointsInspector.HandleType == ItemDataTransformType.TwoD) { + if (_dragPointsInspector.HandleType == DragPointTransformType.TwoD) { var pos = EditorGUILayout.Vector2Field("Position", controlPoint.DragPoint.Center.ToUnityVector2()); if (EditorGUI.EndChangeCheck()) { controlPoint.DragPoint.Center.X = pos.x; @@ -266,9 +261,10 @@ public void OnInspectorGUI(ItemInspector inspector) private void UpdateDragPointsLock() { - if (DragPointsHandler.UpdateDragPointsLock(_mainComponent.IsLocked)) { - HandleUtility.Repaint(); - } + // todo remove or implement properly + // if (DragPointsHandler.UpdateDragPointsLock(_mainComponent.IsLocked)) { + // HandleUtility.Repaint(); + // } } private void OnDragPointPositionChange() @@ -280,7 +276,6 @@ private void OnDragPointPositionChange() private void OnUndoRedoPerformed() { RemapControlPoints(); - WalkChildren(_playfieldComponent.transform, UpdateSurfaceReferences); } public void OnSceneGUI(ItemInspector inspector) @@ -310,22 +305,5 @@ public void OnSceneGUI(ItemInspector inspector) } } } - - private static void WalkChildren(IEnumerable node, Action action) - { - foreach (Transform childTransform in node) { - action(childTransform); - WalkChildren(childTransform, action); - } - } - - private void UpdateSurfaceReferences(Transform obj) - { - var surfaceComponent = obj.gameObject.GetComponent(); - if (surfaceComponent != null && surfaceComponent.Surface == _mainComponent) { - surfaceComponent.OnSurfaceUpdated(); - } - } - } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs index 1e1f77794..a5138b96d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/DragPointsSceneViewHandler.cs @@ -241,7 +241,7 @@ private void DisplayControlPoints() } Handles.matrix = Matrix4x4.identity; - if (!_handler.MainComponent.IsLocked) { + // if (!_handler.MainComponent.IsLocked) { // curve traveller is not overlapping a control point, we can draw it. if (distToControlPoint > HandleUtility.GetHandleSize(_handler.CurveTravellerPosition) * ControlPoint.ScreenRadius) { Handles.color = Color.grey; @@ -257,7 +257,7 @@ private void DisplayControlPoints() HandleUtility.Repaint(); } } - } + // } Profiler.EndSample(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs index 5cc92e471..959a7d0ef 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/DragPoint/IDragPointsInspector.cs @@ -46,6 +46,12 @@ public enum DragPointExposure Texture } + public enum DragPointTransformType + { + TwoD, + ThreeD, + } + /// /// Abstraction inspectors that support drag points. /// @@ -77,7 +83,7 @@ public interface IDragPointsInspector /// Returns the applied constrains to drag points position edition. /// /// - ItemDataTransformType HandleType { get; } + DragPointTransformType HandleType { get; } DragPointsInspectorHelper DragPointsHelper { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs index 714266e7c..24f18f67e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/HandlesUtils.cs @@ -30,14 +30,14 @@ public static class HandlesUtils /// /// /// Moved position in VPX space. - public static Vector3 HandlePosition(Vector3 position, Matrix4x4 localToWorld, ItemDataTransformType type, float handleSize = 0.2f, float snap = 0.0f) + public static Vector3 HandlePosition(Vector3 position, Matrix4x4 localToWorld, DragPointTransformType type, float handleSize = 0.2f, float snap = 0.0f) { var pos = position.TranslateToWorld(); Handles.matrix = localToWorld; switch (type) { - case ItemDataTransformType.TwoD: { + case DragPointTransformType.TwoD: { var forward = Vector3.forward.TranslateToWorld().normalized; var right = Vector3.right.TranslateToWorld().normalized; @@ -61,7 +61,7 @@ public static Vector3 HandlePosition(Vector3 position, Matrix4x4 localToWorld, I break; } - case ItemDataTransformType.ThreeD: { + case DragPointTransformType.ThreeD: { pos = Handles.PositionHandle(pos, Quaternion.identity.RotateToWorld()); break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs index 399af7ab5..8c8f672d9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperInspector.cs @@ -26,7 +26,6 @@ namespace VisualPinball.Unity.Editor public class BumperInspector : MainInspector { private SerializedProperty _radiusProperty; - private SerializedProperty _surfaceProperty; private SerializedProperty _isHardwiredProperty; protected override void OnEnable() @@ -34,7 +33,6 @@ protected override void OnEnable() base.OnEnable(); _radiusProperty = serializedObject.FindProperty(nameof(BumperComponent.Radius)); - _surfaceProperty = serializedObject.FindProperty(nameof(BumperComponent._surface)); _isHardwiredProperty = serializedObject.FindProperty(nameof(BumperComponent.IsHardwired)); } @@ -50,7 +48,7 @@ public override void OnInspectorGUI() // position EditorGUI.BeginChangeCheck(); - var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the bumper on the playfield, relative to its parent."), MainComponent.Position); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the bumper on the playfield, relative to its parent."), MainComponent.Position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(MainComponent.transform, "Change Bumper Position"); MainComponent.Position = newPos; @@ -74,7 +72,6 @@ public override void OnInspectorGUI() MainComponent.Orientation = newAngle; } - PropertyField(_surfaceProperty, updateTransforms: true); PropertyField(_isHardwiredProperty, updateTransforms: false); base.OnInspectorGUI(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs index b848528aa..a8c04f92e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs @@ -29,7 +29,6 @@ public class FlipperInspector : MainInspector private bool _foldoutRubberGeometry = true; private SerializedProperty _endAngleProperty; - private SerializedProperty _surfaceProperty; private SerializedProperty _isEnabledProperty; private SerializedProperty _isDualWoundProperty; private SerializedProperty _heightProperty; @@ -46,7 +45,6 @@ protected override void OnEnable() base.OnEnable(); _endAngleProperty = serializedObject.FindProperty(nameof(FlipperComponent.EndAngle)); - _surfaceProperty = serializedObject.FindProperty(nameof(FlipperComponent._surface)); _isEnabledProperty = serializedObject.FindProperty(nameof(FlipperComponent.IsEnabled)); _isDualWoundProperty = serializedObject.FindProperty(nameof(FlipperComponent.IsDualWound)); _heightProperty = serializedObject.FindProperty(nameof(FlipperComponent._height)); @@ -86,7 +84,6 @@ public override void OnInspectorGUI() } PropertyField(_endAngleProperty); - PropertyField(_surfaceProperty); PropertyField(_isEnabledProperty); PropertyField(_isDualWoundProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs index db6281bb5..947f0e455 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateInspector.cs @@ -36,7 +36,6 @@ public class GateInspector : MainInspector { "Wire W", GateType.GateWireW }, }; - private SerializedProperty _surfaceProperty; private SerializedProperty _meshProperty; private SerializedProperty _typeProperty; @@ -46,7 +45,6 @@ protected override void OnEnable() { base.OnEnable(); - _surfaceProperty = serializedObject.FindProperty(nameof(GateComponent._surface)); _meshProperty = serializedObject.FindProperty(nameof(GateComponent._meshName)); _typeProperty = serializedObject.FindProperty(nameof(GateComponent._type)); } @@ -85,8 +83,6 @@ public override void OnInspectorGUI() MainComponent.Length = newLength; } - PropertyField(_surfaceProperty); - var wire = MainComponent.transform.Find(GateComponent.WireObjectName); if (wire != null) { MeshDropdownPropertyFbx("Mesh", _meshProperty, MeshFbx, wire.gameObject, _typeProperty, WireTypeMap, "Wire."); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 69efc9b19..de1179aa4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -64,12 +64,12 @@ private void OnUndoRedoPerformed() case IMeshComponent meshItem: meshItem.MainRenderableComponent.RebuildMeshes(); meshItem.MainRenderableComponent.UpdateTransforms(); - meshItem.MainRenderableComponent.UpdateVisibility(); + // meshItem.MainRenderableComponent.UpdateVisibility(); break; case IMainRenderableComponent mainItem: mainItem.RebuildMeshes(); mainItem.UpdateTransforms(); - mainItem.UpdateVisibility(); + // mainItem.UpdateVisibility(); break; } } @@ -106,7 +106,7 @@ protected void EndEditing() meshItem.MainRenderableComponent.UpdateTransforms(); } if (_visibilityDirty) { - meshItem.MainRenderableComponent.UpdateVisibility(); + // meshItem.MainRenderableComponent.UpdateVisibility(); } break; @@ -118,7 +118,7 @@ protected void EndEditing() mainItem.UpdateTransforms(); } if (_visibilityDirty) { - mainItem.UpdateVisibility(); + // mainItem.UpdateVisibility(); } break; @@ -227,7 +227,7 @@ protected void DropDownProperty(string label, SerializedProperty prop, string[] meshItem.MainRenderableComponent.RebuildMeshes(); } if (updateVisibility) { - meshItem.MainRenderableComponent.UpdateVisibility(); + // meshItem.MainRenderableComponent.UpdateVisibility(); } break; @@ -236,7 +236,7 @@ protected void DropDownProperty(string label, SerializedProperty prop, string[] mainItem.RebuildMeshes(); } if (updateVisibility) { - mainItem.UpdateVisibility(); + // mainItem.UpdateVisibility(); } break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs index 9c57258f2..5cc370c03 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs @@ -42,7 +42,6 @@ public class KickerInspector : MainInspector }; private SerializedProperty _orientationProperty; - private SerializedProperty _surfaceProperty; private SerializedProperty _kickerTypeProperty; private SerializedProperty _meshNameProperty; private SerializedProperty _coilsProperty; @@ -52,7 +51,6 @@ protected override void OnEnable() base.OnEnable(); _orientationProperty = serializedObject.FindProperty(nameof(KickerComponent.Orientation)); - _surfaceProperty = serializedObject.FindProperty(nameof(KickerComponent._surface)); _kickerTypeProperty = serializedObject.FindProperty(nameof(KickerComponent.KickerType)); _meshNameProperty = serializedObject.FindProperty(nameof(KickerComponent.MeshName)); _coilsProperty = serializedObject.FindProperty(nameof(KickerComponent.Coils)); @@ -70,7 +68,7 @@ public override void OnInspectorGUI() // position EditorGUI.BeginChangeCheck(); - var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the kicker on the playfield, relative to its parent."), MainComponent.Position); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the kicker on the playfield, relative to its parent."), MainComponent.Position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(MainComponent.transform, "Change Kicker Position"); MainComponent.Position = newPos; @@ -88,7 +86,6 @@ public override void OnInspectorGUI() MainComponent.KickerType == KickerType.KickerWilliams) { PropertyField(_orientationProperty, updateTransforms: true); } - PropertyField(_surfaceProperty, updateTransforms: true); MeshDropdownProperty("Mesh", _meshNameProperty, MeshFolder, MainComponent.gameObject, _kickerTypeProperty, TypeMap); @@ -108,9 +105,7 @@ private void OnSceneGUI() Handles.color = Color.cyan; Handles.matrix = Matrix4x4.identity; var transform = MainComponent.transform; - var localPos = MainComponent.GetEditorPosition(); - localPos.z = MainComponent.PositionZ; - + var localPos = MainComponent.Position; var worldPos = transform.parent == null ? localPos : localPos.TranslateToWorld(); foreach (var coil in MainComponent.Coils) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs index ba0a6f97b..23b1857af 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInsertMeshInspector.cs @@ -82,7 +82,7 @@ private void OnSceneGUI() public bool PointsAreLooping => true; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth, DragPointExposure.Texture }; - public ItemDataTransformType HandleType => ItemDataTransformType.TwoD; + public DragPointTransformType HandleType => DragPointTransformType.TwoD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } public float ZOffset => MeshComponent.PositionZ; public float[] TopBottomZ => null; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInspector.cs index 875569b74..f482893c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Light/LightInspector.cs @@ -34,8 +34,6 @@ public class LightInspector : MainInspector private static readonly string[] LightStateLabels = { "Off", "On", "Blinking" }; private static readonly int[] LightStateValues = { LightStatus.LightStateOff, LightStatus.LightStateOn, LightStatus.LightStateBlinking }; - //private SerializedProperty _positionProperty; - private SerializedProperty _surfaceProperty; private SerializedProperty _bulbSizeProperty; private SerializedProperty _stateProperty; private SerializedProperty _blinkPatternProperty; @@ -47,8 +45,6 @@ protected override void OnEnable() { base.OnEnable(); - //_positionProperty = serializedObject.FindProperty(nameof(LightComponent.Position)); - _surfaceProperty = serializedObject.FindProperty(nameof(LightComponent._surface)); _bulbSizeProperty = serializedObject.FindProperty(nameof(LightComponent.BulbSize)); _stateProperty = serializedObject.FindProperty(nameof(LightComponent.State)); @@ -68,8 +64,6 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - //PropertyField(_positionProperty, updateTransforms: true); - PropertyField(_surfaceProperty, updateTransforms: true); PropertyField(_bulbSizeProperty, "Bulb Mesh Size", updateTransforms: true); if (_foldoutState = EditorGUILayout.BeginFoldoutHeaderGroup(_foldoutState, "State")) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MainInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MainInspector.cs index fa542eea4..fd1693585 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MainInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MainInspector.cs @@ -36,19 +36,5 @@ protected override void OnEnable() } protected bool HasErrors() => false; - - protected void UpdateSurfaceReferences(Transform obj) - { - var surfaceComponent = obj.gameObject.GetComponent(); - if (surfaceComponent != null && surfaceComponent.Surface == MainComponent) { - surfaceComponent.OnSurfaceUpdated(); - } - } - - protected void UpdateTableHeightReferences(Transform obj) - { - var onPlayfieldComponent = obj.gameObject.GetComponent(); - onPlayfieldComponent?.OnPlayfieldHeightUpdated(); - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs index 23631a40b..e565f534a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/MetalWireGuide/MetalWireGuideInspector.cs @@ -91,7 +91,7 @@ private void OnSceneGUI() public DragPointData[] DragPoints { get => MainComponent.DragPoints; set => MainComponent.DragPoints = value; } public bool PointsAreLooping => false; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth }; - public ItemDataTransformType HandleType => ItemDataTransformType.TwoD; + public DragPointTransformType HandleType => DragPointTransformType.TwoD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } public float ZOffset => 0f; public float[] TopBottomZ => null; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldInspector.cs index 1feac8464..ec4fa4f0b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldInspector.cs @@ -25,7 +25,6 @@ public class PlayfieldInspector : MainInspector private SerializedProperty _rightProperty; private SerializedProperty _bottomProperty; private SerializedProperty _glassHeightProperty; - private SerializedProperty _tableHeightProperty; private SerializedProperty _angleTiltMinProperty; private SerializedProperty _angleTiltMaxProperty; private SerializedProperty _renderSlopeProperty; @@ -37,7 +36,6 @@ protected override void OnEnable() _rightProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.Right)); _bottomProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.Bottom)); _glassHeightProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.GlassHeight)); - _tableHeightProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.TableHeight)); _angleTiltMinProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.AngleTiltMin)); _angleTiltMaxProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.AngleTiltMax)); _renderSlopeProperty = serializedObject.FindProperty(nameof(PlayfieldComponent.RenderSlope)); @@ -56,9 +54,6 @@ public override void OnInspectorGUI() PropertyField(_rightProperty, "Table Width", true); PropertyField(_bottomProperty, "Table Height/Length", true); PropertyField(_glassHeightProperty, "Top Glass Height", true); - PropertyField(_tableHeightProperty, "Table Field Height", true, onChanged: () => { - WalkChildren(PlayfieldComponent.transform, UpdateTableHeightReferences); - }); PropertyField(_angleTiltMinProperty, "Slope for Min. Difficulty"); PropertyField(_angleTiltMaxProperty, "Slope for Max. Difficulty"); PropertyField(_renderSlopeProperty, "Rendered Playfield Angle"); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs index 2f5375c47..529f42169 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerInspector.cs @@ -27,7 +27,6 @@ public class PlungerInspector : MainInspector { private SerializedProperty _widthProperty; private SerializedProperty _heightProperty; - private SerializedProperty _surfaceProperty; protected override void OnEnable() { @@ -35,7 +34,6 @@ protected override void OnEnable() _widthProperty = serializedObject.FindProperty(nameof(PlungerComponent.Width)); _heightProperty = serializedObject.FindProperty(nameof(PlungerComponent.Height)); - _surfaceProperty = serializedObject.FindProperty(nameof(PlungerComponent._surface)); } public override void OnInspectorGUI() @@ -50,7 +48,7 @@ public override void OnInspectorGUI() // position EditorGUI.BeginChangeCheck(); - var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "The position of the plunger on the playfield."), MainComponent.Position); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "The position of the plunger on the playfield."), MainComponent.Position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(MainComponent.transform, "Change Plunger Position"); MainComponent.Position = newPos; @@ -59,16 +57,6 @@ public override void OnInspectorGUI() PropertyField(_widthProperty, rebuildMesh: true); PropertyField(_heightProperty, rebuildMesh: true); - // z-adjust - EditorGUI.BeginChangeCheck(); - var newZAdjust = EditorGUILayout.FloatField(new GUIContent("Z Adjustment", "The Z-Position of the plunger. VPX calls it like that."), MainComponent.ZAdjust); - if (EditorGUI.EndChangeCheck()) { - Undo.RecordObject(MainComponent.transform, "Change Plunger Z Adjustment"); - MainComponent.ZAdjust = newZAdjust; - } - - PropertyField(_surfaceProperty, rebuildMesh: true); - base.OnInspectorGUI(); EndEditing(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs index 75bc1b7a1..fafa4313b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Ramp/RampInspector.cs @@ -155,7 +155,7 @@ private void OnSceneGUI() public DragPointData[] DragPoints { get => MainComponent.DragPoints; set => MainComponent.DragPoints = value; } public bool PointsAreLooping => false; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth, DragPointExposure.SlingShot }; - public ItemDataTransformType HandleType => ItemDataTransformType.ThreeD; + public DragPointTransformType HandleType => DragPointTransformType.ThreeD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } public float ZOffset => 0f; public float[] TopBottomZ => new[] { MainComponent._heightBottom, MainComponent._heightTop }; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs index c75805695..1515a1127 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Rubber/RubberInspector.cs @@ -84,7 +84,7 @@ private void OnSceneGUI() public DragPointData[] DragPoints { get => MainComponent.DragPoints; set => MainComponent.DragPoints = value; } public bool PointsAreLooping => true; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth }; - public ItemDataTransformType HandleType => ItemDataTransformType.TwoD; + public DragPointTransformType HandleType => DragPointTransformType.TwoD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } public float ZOffset => MainComponent.Height; public float[] TopBottomZ => null; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs index 5cb3650f7..8a3edd5e9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Spinner/SpinnerInspector.cs @@ -30,13 +30,11 @@ public class SpinnerInspector : MainInspector private SerializedProperty _dampingProperty; private SerializedProperty _angleMaxProperty; private SerializedProperty _angleMinProperty; - private SerializedProperty _surfaceProperty; protected override void OnEnable() { base.OnEnable(); - _surfaceProperty = serializedObject.FindProperty(nameof(SpinnerComponent._surface)); _dampingProperty = serializedObject.FindProperty(nameof(SpinnerComponent.Damping)); _angleMaxProperty = serializedObject.FindProperty(nameof(SpinnerComponent.AngleMax)); _angleMinProperty = serializedObject.FindProperty(nameof(SpinnerComponent.AngleMin)); @@ -61,10 +59,10 @@ public override void OnInspectorGUI() } EditorGUI.BeginChangeCheck(); - var newHeight = EditorGUILayout.FloatField(new GUIContent("Height", "Z-Position on the playfield, relative to its parent."), MainComponent.Height); + var newHeight = EditorGUILayout.FloatField(new GUIContent("Height", "Z-Position on the playfield, relative to its parent."), MainComponent.Position.z); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(MainComponent.transform, "Change Spinner Height"); - MainComponent.Height = newHeight; + MainComponent.Position = new Vector3(MainComponent.Position.x, MainComponent.Position.y, newHeight); } EditorGUI.BeginChangeCheck(); @@ -81,8 +79,6 @@ public override void OnInspectorGUI() MainComponent.Rotation = newRotation; } - PropertyField(_surfaceProperty, updateTransforms: true); - if (_foldoutPhysics = EditorGUILayout.BeginFoldoutHeaderGroup(_foldoutPhysics, "Physics")) { PropertyField(_dampingProperty); PropertyField(_angleMinProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs index 16b6b3f62..81d0c4ae0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceInspector.cs @@ -62,9 +62,7 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_heightTopProperty, "Top Height", true, onChanged: () => { - WalkChildren(PlayfieldComponent.transform, UpdateSurfaceReferences); - }); + PropertyField(_heightTopProperty, "Top Height", true); PropertyField(_heightBottomProperty, "Bottom Height", true); DragPointsHelper.OnInspectorGUI(this); @@ -84,9 +82,9 @@ private void OnSceneGUI() public DragPointData[] DragPoints { get => MainComponent.DragPoints; set => MainComponent.DragPoints = value; } public bool PointsAreLooping => true; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth, DragPointExposure.SlingShot, DragPointExposure.Texture }; - public ItemDataTransformType HandleType => ItemDataTransformType.TwoD; + public DragPointTransformType HandleType => DragPointTransformType.TwoD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } - public float ZOffset => MainComponent.HeightTop + MainComponent.PlayfieldHeight; + public float ZOffset => MainComponent.HeightTop; public float[] TopBottomZ => null; public void SetDragPointPosition(DragPointData dragPoint, Vertex3D value, int numSelectedDragPoints, float[] topBottomZ) => dragPoint.Center = value; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs deleted file mode 100644 index 746c1efd2..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs +++ /dev/null @@ -1,371 +0,0 @@ -// 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 . - -// ReSharper disable SwitchStatementMissingSomeEnumCasesNoDefault - -using System; -using System.Collections.Generic; -using System.Reflection; -using UnityEditor; -using UnityEngine; - -namespace VisualPinball.Unity.Editor -{ - //[CustomEditor(typeof(Transform))] - [CanEditMultipleObjects] - public class TransformInspector : UnityEditor.Editor - { - private UnityEditor.Editor _defaultEditor; - private Transform _transform; - - /// - /// The first selected item - /// - private IMainRenderableComponent _primaryItem; - - /// - /// On multi-selection, these are the other selected items. - /// - private readonly List _secondaryItems = new List(); - - private ItemDataTransformType _positionType = ItemDataTransformType.ThreeD; - private ItemDataTransformType _rotationType = ItemDataTransformType.ThreeD; - private ItemDataTransformType _scaleType = ItemDataTransformType.ThreeD; - - // work around for scale handle weirdness - private float _scaleFactor = 1.0f; - - // control when to rotate each axis of your custom rotation handle - private Matrix4x4? _pauseAxisX; - private Matrix4x4? _pauseAxisY; - private Matrix4x4? _pauseAxisZ; - private bool _itemSelected; - - protected virtual void OnEnable() - { - _transform = target as Transform; - - // use default inspector. we do that when no vpe components are selected. - var useDefault = true; - - // loop through selected objects - foreach (var t in targets) { - - var itemTransform = t as Transform; - if (!itemTransform) { - continue; - } - - var item = itemTransform.GetComponent(); - var isRenderableItem = item != null; - var overrideTransform = isRenderableItem && item.OverrideTransform; - _itemSelected = itemTransform.GetComponent() != null; - useDefault = useDefault && (!_itemSelected || !overrideTransform) && itemTransform.GetComponent() == null; - - // must be main but not the table itself - if (isRenderableItem && item is not TableComponent) { - - if (_primaryItem == null) { - _primaryItem = item; - _positionType = item.EditorPositionType; - _rotationType = item.EditorRotationType; - _scaleType = item.EditorScaleType; - - } else { - // only transform on axes supported by all - if (item.EditorPositionType < _positionType) { - _positionType = item.EditorPositionType; - } - if (item.EditorRotationType < _rotationType) { - _rotationType = item.EditorRotationType; - } - if (item.EditorScaleType < _scaleType) { - _scaleType = item.EditorScaleType; - } - _secondaryItems.Add(new SecondaryItem { - Transform = t as Transform, - Item = item, - Offset = item.GetEditorPosition() - _primaryItem.GetEditorPosition(), - }); - } - } - } - if (useDefault) { - _defaultEditor = CreateEditor(targets, Type.GetType("UnityEditor.TransformInspector, UnityEditor")); - } - } - - protected virtual void OnDisable() - { - if (_defaultEditor != null) { - var defaultDisableMethod = _defaultEditor.GetType().GetMethod("OnDisable", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - defaultDisableMethod?.Invoke(_defaultEditor, null); - DestroyImmediate(_defaultEditor); - _defaultEditor = null; - } - // restore tools - Tools.hidden = false; - } - - public override void OnInspectorGUI() - { - if (_defaultEditor != null) { - _defaultEditor.OnInspectorGUI(); - return; - } - - GUILayout.Label(_itemSelected - ? "Transforms are below. Don't collapse this component." - : "Cannot transform GameObjects on the playfield yet." - ); - } - - private void OnSceneGUI() - { - if (_defaultEditor != null) { - return; - } - - Tools.hidden = true; - - if (_transform == null || _primaryItem == null) { - return; - } - - if (!_primaryItem.CanBeTransformed) { - return; - } - - //var dragPointEditEnabled = (_primaryItem as IDragPointsEditable)?.DragPointEditEnabled ?? false; - - //if (!dragPointEditEnabled) { - if (_primaryItem.IsLocked) { - HandleLockedTool(); - - } else { - switch (Tools.current) { - case Tool.Rotate: - HandleRotationTool(); - break; - - case Tool.Move: - HandleMoveTool(); - break; - - case Tool.Scale: - HandleScaleTool(); - break; - } - } - //} - } - - private void HandleLockedTool() - { - Handles.matrix = Matrix4x4.identity; - var handlePos = _primaryItem.GetEditorPosition().TranslateToWorld(); - if (_transform.parent != null) { - handlePos = _transform.parent.TransformPoint(handlePos); - } - Handles.color = Color.red; - Handles.Button(handlePos, Quaternion.identity, HandleUtility.GetHandleSize(handlePos) * 0.25f, HandleUtility.GetHandleSize(handlePos) * 0.25f, Handles.SphereHandleCap); - Handles.Label(handlePos + Vector3.right * (HandleUtility.GetHandleSize(handlePos) * 0.3f), "LOCKED"); - } - - private void HandleRotationTool() - { - var e = Event.current; - if (e.type == EventType.MouseDown || e.type == EventType.MouseUp) { - _pauseAxisX = _pauseAxisY = _pauseAxisZ = null; - } - if (_secondaryItems.Count > 0) { - return; - } - var handlePos = _primaryItem.GetEditorPosition().TranslateToWorld(); - if (_transform.parent != null) { - handlePos = _transform.parent.TransformPoint(handlePos); - } - var handleSize = HandleUtility.GetHandleSize(handlePos); - var currentRot = _primaryItem.GetEditorRotation(); - switch (_primaryItem.EditorRotationType) { - case ItemDataTransformType.OneD: { - EditorGUI.BeginChangeCheck(); - if (_transform.parent != null) { - Handles.matrix = Matrix4x4.TRS(handlePos, _transform.parent.transform.rotation, Vector3.one); - } - Handles.color = Handles.zAxisColor; - var rot = Handles.Disc(Quaternion.AngleAxis(currentRot.x, Vector3.up), Vector3.zero, Vector3.up, handleSize, false, 10f); - if (EditorGUI.EndChangeCheck()) { - FinishRotate(new Vector3(rot.eulerAngles.y, 0f, 0f)); - } - break; - } - case ItemDataTransformType.ThreeD: { - EditorGUI.BeginChangeCheck(); - var baseMatrix = Handles.matrix; - if (_transform.parent != null) { - baseMatrix = Matrix4x4.TRS(handlePos, _transform.parent.transform.rotation, Vector3.one); - } - var currentRotTran = Matrix4x4.identity; - currentRotTran *= Matrix4x4.Rotate(Quaternion.Euler(currentRot.x, 0, 0)); - currentRotTran *= Matrix4x4.Rotate(Quaternion.Euler(0, currentRot.y, 0)); - - Handles.matrix = baseMatrix * /*(_pauseAxisX ?? currentRotTran) **/ Matrix4x4.Rotate(Quaternion.Euler(0, 0, -90)); - Handles.color = Handles.xAxisColor; - var rotX = Handles.Disc(Quaternion.AngleAxis(currentRot.x, Vector3.up), Vector3.zero, Vector3.up, handleSize, true, 10f); - - Handles.matrix = baseMatrix * (_pauseAxisY ?? currentRotTran) * Matrix4x4.Rotate(Quaternion.Euler(0, 0, 0)); - Handles.color = Handles.yAxisColor; - var rotY = Handles.Disc(Quaternion.AngleAxis(currentRot.y, Vector3.up), Vector3.zero, Vector3.up, handleSize, true, 10f); - - Handles.matrix = baseMatrix * (_pauseAxisZ ?? currentRotTran) * Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0)); - Handles.color = Handles.zAxisColor; - var rotZ = Handles.Disc(Quaternion.AngleAxis(currentRot.z, Vector3.up), Vector3.zero, Vector3.up, handleSize, true, 10f); - - if (EditorGUI.EndChangeCheck()) { - // check which axis had the biggest change (they'll all change slightly due to float precision) - // and pause that axis' local rotation so the gizmo doesn't flip out - var xDiff = System.Math.Abs(rotX.eulerAngles.y - currentRot.x); - var yDiff = System.Math.Abs(rotY.eulerAngles.y - currentRot.y); - var zDiff = System.Math.Abs(rotZ.eulerAngles.y - currentRot.z); - if (_pauseAxisX == null && xDiff > yDiff && xDiff > zDiff) { - _pauseAxisX = currentRotTran; - } else if (_pauseAxisY == null && yDiff > xDiff && yDiff > zDiff) { - _pauseAxisY = currentRotTran; - } else if (_pauseAxisZ == null && zDiff > xDiff && zDiff > yDiff) { - _pauseAxisZ = currentRotTran; - } - - FinishRotate(new Vector3(rotX.eulerAngles.y, rotY.eulerAngles.y, rotZ.eulerAngles.y)); - } - break; - } - } - } - - private void HandleMoveTool() - { - // var handlePos = _primaryItem.GetEditorPosition(); - // EditorGUI.BeginChangeCheck(); - // handlePos = HandlesUtils.HandlePosition(_transform.GetComponentInParent(), handlePos, _primaryItem.EditorPositionType); - // if (EditorGUI.EndChangeCheck()) { - // FinishMove(handlePos); - // } - } - - private void HandleScaleTool() - { - var e = Event.current; - var startScaling = e.type == EventType.MouseDown; - var endScaling = e.type == EventType.MouseUp; - if (startScaling || endScaling) { - _scaleFactor = _primaryItem.GetEditorScale().x; - } - - if (_secondaryItems.Count > 0) { - return; - } - var handlePos = _primaryItem.GetEditorPosition().TranslateToWorld(); - if (_transform.parent != null) { - handlePos = _transform.parent.TransformPoint(handlePos); - } - var handleRot = _transform.rotation.RotateToWorld(); - var handleScale = HandleUtility.GetHandleSize(handlePos); - - if (startScaling) { - _primaryItem.EditorStartScaling(); - } - if (endScaling) { - _primaryItem.EditorEndScaling(); - } - Handles.matrix = Matrix4x4.identity; - - switch (_primaryItem.EditorScaleType) { - - case ItemDataTransformType.OneD: { - EditorGUI.BeginChangeCheck(); - var scale = Handles.ScaleSlider(_primaryItem.GetEditorScale().x, handlePos, _transform.right, handleRot, handleScale, 0f); - if (EditorGUI.EndChangeCheck()) { - FinishScale(new Vector3(scale, 0f, 0f)); - } - break; - } - - case ItemDataTransformType.ThreeD: { - EditorGUI.BeginChangeCheck(); - var oldScale = _primaryItem.GetEditorScale(); - var newScale = Handles.ScaleHandle(oldScale, handlePos, handleRot, handleScale); - if (Mathf.Abs(newScale.x - oldScale.x) > Mathf.Epsilon && Mathf.Abs(newScale.y - oldScale.y) > Mathf.Epsilon && Mathf.Abs(newScale.z - oldScale.z) > Mathf.Epsilon) { - // the center bit of the scale handle appears to be doing some extra multiplying, not totally sure what's going on, but experimentally - // it seems like its a factor of one of the axes too much, so (on click) we'll update a factor and adjust accordingly - if (_scaleFactor != 0) { - newScale /= _scaleFactor; - } else { - newScale = Vector3.zero; - } - } - if (EditorGUI.EndChangeCheck()) { - FinishScale(newScale); - } - break; - } - } - } - - /// - /// Applies the new translation to the item. - /// - /// New position in VPX space. - private void FinishMove(Vector3 newPosition) - { - var undoLabel = "Move " + _transform.gameObject.name; - Undo.RecordObjects(new[]{ _transform, _primaryItem as UnityEngine.Object }, undoLabel); - var finalPos = newPosition; - - _primaryItem.SetEditorPosition(finalPos); - _primaryItem.UpdateTransforms(); - - foreach (var secondary in _secondaryItems) { - secondary.Item.UpdateTransforms(); - Undo.RecordObjects(new[]{ secondary.Item as UnityEngine.Object, secondary.Transform }, undoLabel); - secondary.Item.SetEditorPosition(finalPos + secondary.Offset); - } - } - - private void FinishRotate(Vector3 newEuler) - { - var undoLabel = "Rotate " + _transform.gameObject.name; - Undo.RecordObjects(new [] {_primaryItem as UnityEngine.Object, _transform }, undoLabel); - _primaryItem.SetEditorRotation(newEuler); - _primaryItem.UpdateTransforms(); - } - - private void FinishScale(Vector3 newScale) - { - var undoLabel = "Scale " + _transform.gameObject.name; - Undo.RecordObjects(new [] {_primaryItem as UnityEngine.Object, _transform }, undoLabel); - _primaryItem.SetEditorScale(newScale); - _primaryItem.UpdateTransforms(); - } - - private class SecondaryItem - { - public Transform Transform; - public IMainRenderableComponent Item; - public Vector3 Offset; - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs.meta deleted file mode 100644 index b9da64ab0..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/TransformInspector.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 37d98137d4c873e468cf564e34f71658 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs index 3c575b240..682a3545a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Trigger/TriggerInspector.cs @@ -29,16 +29,12 @@ public class TriggerInspector : MainInspector, ID { public Transform Transform => MainComponent.transform; - private SerializedProperty _surfaceProperty; - protected override void OnEnable() { base.OnEnable(); DragPointsHelper = new DragPointsInspectorHelper(MainComponent, this); DragPointsHelper.OnEnable(); - - _surfaceProperty = serializedObject.FindProperty(nameof(TriggerComponent._surface)); } protected override void OnDisable() @@ -81,8 +77,6 @@ public override void OnInspectorGUI() MainComponent.Rotation = newRotation; } - PropertyField(_surfaceProperty, updateTransforms: true); - DragPointsHelper.OnInspectorGUI(this); base.OnInspectorGUI(); @@ -107,9 +101,9 @@ public bool DragPointsActive { public DragPointData[] DragPoints { get => MainComponent.DragPoints; set => MainComponent.DragPoints = value; } public bool PointsAreLooping => true; public IEnumerable DragPointExposition => new[] { DragPointExposure.Smooth, DragPointExposure.SlingShot }; - public ItemDataTransformType HandleType => ItemDataTransformType.TwoD; + public DragPointTransformType HandleType => DragPointTransformType.TwoD; public DragPointsInspectorHelper DragPointsHelper { get; private set; } - public float ZOffset => MainComponent.PositionZ; + public float ZOffset => MainComponent.Position.z; public float[] TopBottomZ => null; public void SetDragPointPosition(DragPointData dragPoint, Vertex3D value, int numSelectedDragPoints, float[] topBottomZ) => dragPoint.Center = value; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs index 2b450be6a..efc52019b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs @@ -38,10 +38,9 @@ private void Awake() _ltw = Physics.VpxToWorld; _wtl = Physics.WorldToVpx; - var z = _playfield.PlayfieldHeight; - var p1 = _ltw.MultiplyPoint(new Vector3(-100f, 100f, z)); - var p2 = _ltw.MultiplyPoint(new Vector3(100f, 100f, z)); - var p3 = _ltw.MultiplyPoint(new Vector3(100f, -100f, z)); + var p1 = _ltw.MultiplyPoint(new Vector3(-100f, 100f, 0)); + var p2 = _ltw.MultiplyPoint(new Vector3(100f, 100f, 0)); + var p3 = _ltw.MultiplyPoint(new Vector3(100f, -100f, 0)); _playfieldPlane.Set3Points(p1, p2, p3); _physicsEngine = GetComponentInChildren(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs index 29778fc63..14b00bf67 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DebugBallCreator.cs @@ -29,11 +29,11 @@ public class DebugBallCreator : IBallCreationPosition private readonly float _kickAngle; private readonly float _kickForce; - public DebugBallCreator(float x, float y, float playfieldHeight) + public DebugBallCreator(float x, float y) { _x = x; _y = y; - _z = playfieldHeight; + _z = 0; _kickAngle = 0; _kickForce = 0; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs index 48b75e01e..6ecb3fb9d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs @@ -271,7 +271,7 @@ public void Switch(string id, bool isClosed) case SwCreateBall: { if (isClosed) { - _ballManager.CreateBall(new DebugBallCreator(630, _playfieldComponent.Height / 2f, _playfieldComponent.TableHeight)); + _ballManager.CreateBall(new DebugBallCreator(630, _playfieldComponent.Height / 2f)); } break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 5f403ab9e..95ad45d4d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -77,7 +77,7 @@ void IApiCoil.OnCoil(bool enabled) continue; } ref var ballState = ref PhysicsEngine.BallState(ballId); - float3 bumperPos = new(MainComponent.Position.x, MainComponent.Position.y, MainComponent.PositionZ); + float3 bumperPos = MainComponent.Position; float3 ballPos = ballState.Position; var bumpDirection = ballPos - bumperPos; bumpDirection.z = 0f; @@ -114,7 +114,7 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); - var height = MainComponent.PositionZ; + var height = MainComponent.Position.z; var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent._isKinematic) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index e80a7c39d..5e0de1d18 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -27,6 +27,7 @@ using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Table; @@ -34,21 +35,15 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Bumper")] - public class BumperComponent : MainRenderableComponent, - ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent + public class BumperComponent : MainRenderableComponent, ISwitchDeviceComponent, ICoilDeviceComponent { #region Data - private Vector3 _position { + public Vector3 Position { get => transform.localPosition.TranslateToVpx(); set => transform.localPosition = value.TranslateToWorld(); } - public Vector2 Position { - get => _position.XY(); - set => _position = new Vector3(value.x, value.y, _position.z); - } - [Range(20f, 250f)] [Tooltip("Radius of the bumper. Updates xy scaling. 50 = Original size.")] public float Radius = 45f; @@ -68,12 +63,6 @@ public float Orientation { [Tooltip("Should the bumper coil always activate when touched by a ball? Disable to give game logic engine full control")] public bool IsHardwired = true; - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this bumper is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; private IEnumerable _availableDeviceItems; #endregion @@ -165,8 +154,6 @@ private void Start() public void OnSurfaceUpdated() => UpdateTransforms(); - public float PositionZ => SurfaceHeight(Surface, Position); - public override void UpdateTransforms() { base.UpdateTransforms(); @@ -191,7 +178,7 @@ public override IEnumerable SetData(BumperData data) var updatedComponents = new List { this }; // transforms - Position = data.Center.ToUnityFloat2(); + Position = data.Center.ToUnityVector3(0); Radius = data.Radius; HeightScale = data.HeightScale; Orientation = data.Orientation; @@ -218,7 +205,6 @@ public override IEnumerable SetData(BumperData data) public override IEnumerable SetReferencedData(BumperData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); UpdateTransforms(); // children visibility @@ -262,14 +248,11 @@ public override BumperData CopyDataTo(BumperData data, string[] materialNames, s { // name and transforms data.Name = name; - data.Center = Position.ToVertex2D(); + data.Center = new Vertex2D(Position.x, Position.y); data.Radius = Radius; data.HeightScale = HeightScale; data.Orientation = Orientation; - // surface - data.Surface = Surface != null ? Surface.name : string.Empty; - // children visibility data.IsBaseVisible = false; data.IsCapVisible = false; @@ -329,7 +312,6 @@ public override void CopyFromObject(GameObject go) Radius = bumperComponent.Radius; HeightScale = bumperComponent.HeightScale; Orientation = bumperComponent.Orientation; - Surface = bumperComponent.Surface; } else { var scale = go.transform.localScale; @@ -398,23 +380,5 @@ internal BumperState CreateState() } #endregion - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - public override Vector3 GetEditorPosition() => Surface != null - ? new Vector3(Position.x, Position.y, Surface.Height(Position)) - : new Vector3(Position.x, Position.y, 0); - public override void SetEditorPosition(Vector3 pos) => Position = ((float3)pos).xy; - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(Orientation, 0, 0); - public override void SetEditorRotation(Vector3 rot) => Orientation = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - public override Vector3 GetEditorScale() => new Vector3(Radius * 2f, 0f, 0f); - public override void SetEditorScale(Vector3 scale) => Radius = scale.x / 2f; - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs index e9206f6ed..bdc209036 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs @@ -30,7 +30,7 @@ internal struct BumperSkirtAnimationState public float2 Rotation; // static - public float2 Center; + public float3 Center; public float Duration; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 0707fba91..42d35f5d8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -235,7 +235,6 @@ protected override void CreateColliders(ref ColliderReference colliders, // and add ColliderComponent.Overshoot to the endangle so that the bounding box is correctly calculated ColliderId = colliders.Add( new FlipperCollider( - MainComponent.PositionZ, MainComponent.Height, MainComponent.FlipperRadiusMax, MainComponent.BaseRadius, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index a29f9e8ce..573e8d590 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -44,15 +44,15 @@ public int Id #region Setup - public FlipperCollider(float posZ, float height, float flipperRadius, float startRadius, float endRadius, + public FlipperCollider(float height, float flipperRadius, float startRadius, float endRadius, float startAngle, float endAngle, ColliderInfo info) : this() { var baseRadius = math.max(startRadius, 0.01f); _hitCircleBase = new CircleCollider( float2.zero, // flipper collision is always done through the center and a matrix baseRadius, - posZ, - posZ + height, + 0, + height, info ); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 0c7282847..e53d2e099 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -38,7 +38,7 @@ namespace VisualPinball.Unity [AddComponentMenu("Visual Pinball/Game Item/Flipper")] [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] public class FlipperComponent : MainRenderableComponent, - IFlipperData, ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, + IFlipperData, ISwitchDeviceComponent, ICoilDeviceComponent, IRotatableComponent { #region Data @@ -71,12 +71,6 @@ public float StartAngle { [Tooltip("Angle of the flipper in end position (flipped)")] public float EndAngle = 70.0f; - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this flipper is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - // todo implement [Tooltip("This does nothing yet!")] public bool IsEnabled = true; @@ -173,22 +167,6 @@ public float StartAngle { #region Transformation - public void OnSurfaceUpdated() => UpdateTransforms(); - - public float PositionZ => SurfaceHeight(Surface, Position); - - public override void UpdateTransforms() - { - base.UpdateTransforms(); - var t = transform; - - // position - //t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); - - // rotation - //t.SetLocalYRotation(math.radians(StartAngle)); - } - /// /// Returns the local-to-world matrix of the flipper, but without the rotation around the Y-axis. /// @@ -270,7 +248,6 @@ public override IEnumerable SetData(FlipperData data) public override IEnumerable SetReferencedData(FlipperData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); UpdateTransforms(); // children mesh creation and visibility @@ -295,7 +272,6 @@ public override FlipperData CopyDataTo(FlipperData data, string[] materialNames, data.Name = name; data.Center = Position.ToVertex2D(); data.StartAngle = StartAngle; - data.Surface = Surface != null ? Surface.name : string.Empty; // geometry data.Height = _height; @@ -341,7 +317,6 @@ public override void CopyFromObject(GameObject go) Position = flipperComponent.Position; StartAngle = flipperComponent.StartAngle; EndAngle = flipperComponent.EndAngle; - Surface = flipperComponent.Surface; IsDualWound = flipperComponent.IsDualWound; _height = flipperComponent._height; _baseRadius = flipperComponent._baseRadius; @@ -363,18 +338,6 @@ public override void CopyFromObject(GameObject go) #region Editor Tooling - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - public override Vector3 GetEditorPosition() => Surface != null - ? new Vector3(Position.x, Position.y, Surface.Height(Position)) - : new Vector3(Position.x, Position.y, 0); - public override void SetEditorPosition(Vector3 pos) => Position = ((float3)pos).xy; - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(StartAngle, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => StartAngle = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.None; - #if UNITY_EDITOR protected void OnDrawGizmosSelected() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index f60b1a334..7972d4786 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -38,7 +38,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Gate")] public class GateComponent : MainRenderableComponent, - IGateData, ISwitchDeviceComponent, IOnSurfaceComponent, IRotatableAnimationComponent + IGateData, ISwitchDeviceComponent, IRotatableAnimationComponent { #region Data @@ -70,13 +70,6 @@ public float Length } } - - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this flipper is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - public int _type; public string _meshName; @@ -168,13 +161,8 @@ private void Start() [NonSerialized] private float4x4 _playfieldToWorld; - public void OnSurfaceUpdated() => UpdateTransforms(); - - public float PositionZ => SurfaceHeight(Surface, Position); // todo handle surface - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - #endregion #region Conversion @@ -214,8 +202,6 @@ public override IEnumerable SetData(GateData data) public override IEnumerable SetReferencedData(GateData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); - // visibility foreach (var mf in GetComponentsInChildren()) { switch (mf.gameObject.name) { @@ -245,7 +231,6 @@ public override GateData CopyDataTo(GateData data, string[] materialNames, strin data.Rotation = Rotation; data.Height = Position.z; data.Length = Length; - data.Surface = Surface != null ? Surface.name : string.Empty; data.GateType = _type; @@ -288,7 +273,6 @@ public override void CopyFromObject(GameObject go) Position = gateComponent.Position; Rotation = gateComponent.Rotation; _length = gateComponent._length; - Surface = gateComponent.Surface; } else { @@ -337,27 +321,6 @@ internal GateState CreateState() #endregion - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override void SetEditorPosition(Vector3 pos) => Position = Surface != null - ? pos - new Vector3(0, 0, Surface.Height(Position)) - : pos; - public override Vector3 GetEditorPosition() => Surface != null - ? new Vector3(Position.x, Position.y, Position.z + Surface.Height(Position)) - : new Vector3(Position.x, Position.y, Position.z); - - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(Rotation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - public override Vector3 GetEditorScale() => new Vector3(Length, 0f, 0f); - public override void SetEditorScale(Vector3 scale) => _length = scale.x; - - #endregion - #region IRotatableAnimationComponent public void OnRotationUpdated(float angleRad) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index 8a607ff35..8ac084d25 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -167,22 +167,5 @@ public override void CopyFromObject(GameObject go) } #endregion - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorPosition() => Position; - public override void SetEditorPosition(Vector3 pos) => Position = pos; - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(Rotation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.ThreeD; - - public override Vector3 GetEditorScale() => Size; - public override void SetEditorScale(Vector3 scale) => Size = scale; - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs index 7eb40db1f..62f81fcec 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs @@ -20,54 +20,14 @@ namespace VisualPinball.Unity { public interface IMainRenderableComponent : IMainComponent { - bool IsLocked { get; set; } - - /// - /// If true, no transformation gizmos are displayed - /// - bool CanBeTransformed { get; } - - /// - /// If true, we override the transformation gizmos to be able to sync transformation - /// with the component data. Otherwise, Unity's transformation is used. - /// - bool OverrideTransform { get; } - - string ItemName { get; } - /// /// Sets the mesh of all mesh sub components to dirty. /// void RebuildMeshes(); void UpdateTransforms(); - void UpdateVisibility(); - - // the following interfaces allow each item behavior to define which axes should - // be shown on the scene view gizmo, the gizmo itself will use the associated - // get and set methods, which are expected to update item data directly - ItemDataTransformType EditorPositionType { get; } - Vector3 GetEditorPosition(); - void SetEditorPosition(Vector3 pos); - - ItemDataTransformType EditorRotationType { get; } - Vector3 GetEditorRotation(); - void SetEditorRotation(Vector3 pos); - ItemDataTransformType EditorScaleType { get; } - Vector3 GetEditorScale(); - void SetEditorScale(Vector3 pos); - void EditorStartScaling(); - void EditorEndScaling(); - - //void SetTransform(Vector3 position, Vector3 scale, Quaternion rotation); void CopyFromObject(GameObject go); } - public enum ItemDataTransformType - { - None, - OneD, - TwoD, - ThreeD, - } + } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs deleted file mode 100644 index fcfbec986..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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 . - -using UnityEngine; - -namespace VisualPinball.Unity -{ - /// - /// Components implementing this interface can be referenced by - /// - public interface ISurfaceComponent : IIdentifiableItemComponent - { - float Height(Vector2 pos); - } - - /// - /// Components implementing this interface can be placed on . - /// - public interface IOnSurfaceComponent - { - ISurfaceComponent Surface { get; } - - void OnSurfaceUpdated(); - } - - public interface IOnPlayfieldComponent - { - void OnPlayfieldHeightUpdated(); - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta deleted file mode 100644 index 64f5c5d37..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: be4a294401329574f9a8ae76aceccf6f -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 124321b31..7cee4435f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -47,7 +47,7 @@ public class KickerApi : CollidableApi public event EventHandler Switch; - internal float3 Position => new(MainComponent.Position.x, MainComponent.Position.y, MainComponent.PositionZ); + internal float3 Position => MainComponent.Position; public KickerDeviceCoil KickerCoil => _coils.Values.FirstOrDefault(); @@ -227,13 +227,11 @@ private void KickXYZ(float angle, float speed, float inclination, float x, float protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var height = MainComponent.PositionZ; - // reduce the hit circle radius because only the inner circle of the kicker should start a hit event var radius = MainComponent.Radius * (ColliderComponent.LegacyMode ? ColliderComponent.FallThrough ? 0.75f : 0.6f : 1f); - colliders.Add(new CircleCollider(float2.zero, radius, height, - height + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle), translateWithinPlayfieldMatrix); + colliders.Add(new CircleCollider(float2.zero, radius, MainComponent.Position.z, + MainComponent.Position.z + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle), translateWithinPlayfieldMatrix); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index 98adc1c14..c94b7e9a4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -40,21 +40,16 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Kicker")] public class KickerComponent : MainRenderableComponent, - ICoilDeviceComponent, ITriggerComponent, IBallCreationPosition, IOnSurfaceComponent, + ICoilDeviceComponent, ITriggerComponent, IBallCreationPosition, IRotatableComponent, ISerializationCallbackReceiver { #region Data - private Vector3 _position { + public Vector3 Position { get => transform.localPosition.TranslateToVpx(); set => transform.localPosition = value.TranslateToWorld(); } - public Vector2 Position { - get => _position.XY(); - set => _position = new Vector3(value.x, value.y, _position.z); - } - public float Radius { get { var scale = transform.localScale; @@ -75,12 +70,6 @@ public float Radius { [Tooltip("R-Rotation of the kicker")] public float Orientation; - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface the kicker is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - public List Coils = new() { new KickerCoil { Name = "Default Coil" } }; @@ -127,18 +116,12 @@ public float Radius { #region Transformation - public void OnSurfaceUpdated() => UpdateTransforms(); - public float PositionZ => SurfaceHeight(Surface, Position); - - public override void UpdateTransforms() { base.UpdateTransforms(); var t = transform; - // position - t.localPosition = Physics.TranslateToWorld(Position.x, Position.y, PositionZ); - + // todo move this to import if (KickerType == Engine.VPT.KickerType.KickerCup) { t.localPosition += Physics.TranslateToWorld(0, 0, -0.18f * Radius); } @@ -223,7 +206,6 @@ public override IEnumerable SetData(KickerData data) public override IEnumerable SetReferencedData(KickerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); return Array.Empty(); } @@ -232,10 +214,9 @@ public override KickerData CopyDataTo(KickerData data, string[] materialNames, s { // name and transforms data.Name = name; - data.Center = Position.ToVertex2D(); + data.Center = new Vertex2D(Position.x, Position.y); data.Orientation = Orientation; data.Radius = Radius; - data.Surface = Surface != null ? Surface.name : string.Empty; data.KickerType = KickerType; @@ -264,7 +245,6 @@ public override void CopyFromObject(GameObject go) Position = kickerComponent.Position; Radius = kickerComponent.Radius; Orientation = kickerComponent.Orientation; - Surface = kickerComponent.Surface; } else { Position = go.transform.localPosition.TranslateToVpx(); @@ -285,19 +265,18 @@ internal KickerState CreateState() var colliderComponent = GetComponent(); var staticData = colliderComponent ? new KickerStaticState { - Center = Position, + Center = new float2(Position.x, Position.y), FallIn = colliderComponent.FallIn, FallThrough = colliderComponent.FallThrough, HitAccuracy = colliderComponent.HitAccuracy, Scatter = colliderComponent.Scatter, LegacyMode = colliderComponent.LegacyMode, - ZLow = Surface?.Height(Position) ?? PlayfieldHeight + ZLow = Position.z } : default; - var height = SurfaceHeight(Surface, Position); var meshData = colliderComponent.LegacyMode ? new ColliderMeshData(Array.Empty(), 0, float3.zero, Allocator.Persistent) - : new ColliderMeshData(KickerHitMesh.Vertices, Radius, new float3(Center.x, Center.y, height), Allocator.Persistent); + : new ColliderMeshData(KickerHitMesh.Vertices, Radius, Position, Allocator.Persistent); return new KickerState( staticData, @@ -363,32 +342,11 @@ private void Start() #region IBallCreationPosition - public Vertex3D GetBallCreationPosition() => new Vertex3D(Position.x, Position.y, PositionZ); + public Vertex3D GetBallCreationPosition() => new Vertex3D(Position.x, Position.y, 0); public Vertex3D GetBallCreationVelocity() => new Vertex3D(0.1f, 0, 0); #endregion - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - - public override Vector3 GetEditorPosition() => Position; - - public override void SetEditorPosition(Vector3 pos) => Position = ((float3)pos).xy; - - public override ItemDataTransformType EditorRotationType => - KickerType == Engine.VPT.KickerType.KickerCup || KickerType == Engine.VPT.KickerType.KickerWilliams - ? ItemDataTransformType.OneD : ItemDataTransformType.None; - - public override Vector3 GetEditorRotation() => new Vector3(Orientation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => Orientation = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - public override void SetEditorScale(Vector3 rot) => Radius = rot.x; - public override Vector3 GetEditorScale() => new Vector3(Radius, 0f, 0f); - - #endregion } [Serializable] diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 52d705507..21e2b0d10 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -42,12 +42,6 @@ public class LightComponent : MainRenderableComponent, ILampDeviceCom { #region Data - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this light is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - [Min(0)] [Tooltip("The radius of the bulb mesh")] public float BulbSize = 20f; @@ -124,11 +118,6 @@ public override void UpdateTransforms() var vpxPos = (float3)transform.localPosition.TranslateToVpx(); - // position - vpxPos.z = Surface != null - ? Surface.Height(vpxPos.xy) + vpxPos.z - : PlayfieldHeight + vpxPos.z; - transform.localPosition = vpxPos.TranslateToWorld(); // bulb size @@ -390,8 +379,6 @@ public override IEnumerable SetData(LightData data) public override IEnumerable SetReferencedData(LightData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); - // visibility if (!data.ShowBulbMesh) { foreach (var mf in GetComponentsInChildren()) { @@ -427,7 +414,6 @@ public override LightData CopyDataTo(LightData data, string[] materialNames, str // name and position data.Name = name; data.Center = pos.ToVertex2Dxy(); - data.Surface = Surface != null ? Surface.name : string.Empty; data.MeshRadius = BulbSize; // logical params @@ -467,7 +453,6 @@ public override void CopyFromObject(GameObject go) var lightComponent = go.GetComponent(); if (lightComponent != null) { - Surface = lightComponent.Surface; BulbSize = lightComponent.BulbSize; State = lightComponent.State; BlinkPattern = lightComponent.BlinkPattern; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs index ebd37aa2e..9f37a1d68 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs @@ -44,8 +44,8 @@ protected override Mesh GetMesh(LightData data) { var playfieldComponent = GetComponentInParent(); var meshGen = new SurfaceMeshGenerator(new LightInsertData(_dragPoints, InsertHeight)); - var topMesh = meshGen.GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, false); - var sideMesh = meshGen.GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, false); + var topMesh = meshGen.GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, 0, false); + var sideMesh = meshGen.GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, 0, false); return topMesh.Merge(sideMesh).TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs index 23b4fc75b..439bd0dd7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs @@ -32,13 +32,6 @@ public abstract class MainComponent : ItemComponent, [SerializeField] private bool _isLocked; - public float PlayfieldHeight { - get { - var playfieldComponent = GetComponentInParent(); - return playfieldComponent ? playfieldComponent.TableHeight : 0f; - } - } - public int PlayfieldDetailLevel { get { var playfieldComponent = GetComponentInParent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index bd64571bd..80e123930 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -24,8 +24,7 @@ namespace VisualPinball.Unity { - public abstract class MainRenderableComponent : MainComponent, - IMainRenderableComponent, IOnPlayfieldComponent + public abstract class MainRenderableComponent : MainComponent, IMainRenderableComponent where TData : ItemData { public virtual bool CanBeTransformed => true; @@ -102,15 +101,6 @@ public virtual void UpdateTransforms() } } - public virtual void UpdateVisibility() - { - } - - protected float SurfaceHeight(ISurfaceComponent surface, Vector2 position) - { - return surface?.Height(position) ?? PlayfieldHeight; - } - protected static void CopyMaterialName(MeshRenderer mr, string[] materialNames, string[] textureNames, ref string materialName) { @@ -166,20 +156,6 @@ private static void CopyMaterialName(MeshRenderer mr, string[] materialNames, st #region Tools - public virtual ItemDataTransformType EditorPositionType => ItemDataTransformType.None; - public virtual Vector3 GetEditorPosition() => transform.localPosition; - public virtual void SetEditorPosition(Vector3 pos) { } - - public virtual ItemDataTransformType EditorRotationType => ItemDataTransformType.None; - public virtual Vector3 GetEditorRotation() => Vector3.zero; - public virtual void SetEditorRotation(Vector3 rot) { } - - public virtual ItemDataTransformType EditorScaleType => ItemDataTransformType.None; - public virtual Vector3 GetEditorScale() => Vector3.zero; - public virtual void SetEditorScale(Vector3 rot) { } - public virtual void EditorStartScaling() {} - public virtual void EditorEndScaling() {} - protected static void MoveDragPointsTo(DragPointData[] dragPoints, Vector3 destination) { var sum = Vertex3D.Zero; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs index 51f85fa21..e125fb66e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs @@ -186,15 +186,6 @@ public static bool ApplyChildrenTransformsValidate() var comps = UnityEditor.Selection.activeGameObject.GetComponentsInChildren(); return comps.Length != 0; } - - [UnityEditor.MenuItem("GameObject/Visual Pinball/Rebuild Meshes", false, 40)] - public static void ApplyChildrenTransforms() - { - var comps = UnityEditor.Selection.activeGameObject.GetComponentsInChildren(); - foreach (var comp in comps) { - comp.MainRenderableComponent.RebuildMeshes(); - } - } } #endif diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs index 902931fb1..aafa74b9f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.MetalWireGuide; @@ -48,7 +47,7 @@ protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent), translateWithinPlayfieldMatrix); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); + colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs index 769b365bb..3dc6dfbfe 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs @@ -1,265 +1,224 @@ -// 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 . - -#region ReSharper -// ReSharper disable CompareOfFloatsByEqualityOperator -// ReSharper disable ClassNeverInstantiated.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable InconsistentNaming -#endregion - -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using VisualPinball.Engine.Math; -using VisualPinball.Engine.VPT; -using VisualPinball.Engine.VPT.MetalWireGuide; -using VisualPinball.Engine.VPT.Table; - -namespace VisualPinball.Unity -{ - [AddComponentMenu("Visual Pinball/Game Item/Metal Wire Guide")] - public class MetalWireGuideComponent : MainRenderableComponent, - IMetalWireGuideData - { - #region Data - - [Tooltip("Height of the metal wire guide (z-axis).")] - public float _height = 25f; - - [Min(0)] - [Tooltip("How thick the metal wire guide band is rendered.")] - public float _thickness = 3; - - [Min(0)] - [Tooltip("How tall the metal wire ends are rendered.")] - public float _standheight = 30f; - - - [Tooltip("Rotation on the playfield")] - public Vector3 Rotation; - - [Tooltip("Radius of the bend")] - public float _bendradius = 8f; - - [SerializeField] - private DragPointData[] _dragPoints; - - #endregion - - #region IMetalWireGuideData - - public DragPointData[] DragPoints { get => _dragPoints; set => _dragPoints = value; } - public float Thickness => _thickness; - public float Standheight => _standheight; - public float Height => _height; - public float RotX => Rotation.x; - public float RotY => Rotation.y; - public float RotZ => Rotation.z; - public float Bendradius => _bendradius; - - #endregion - - #region Overrides - - public override ItemType ItemType => ItemType.MetalWireGuide; - public override string ItemName => "MetalWireGuide"; - - public override MetalWireGuideData InstantiateData() => new MetalWireGuideData(); - - public override bool HasProceduralMesh => true; - - protected override Type MeshComponentType { get; } = typeof(MeshComponent); - protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); - - #endregion - - #region Runtime - - public MetalWireGuideApi MetalWireGuideApi { get; private set; } - - private void Awake() - { - var player = GetComponentInParent(); - var physicsEngine = GetComponentInParent(); - MetalWireGuideApi = new MetalWireGuideApi(gameObject, player, physicsEngine); - - player.Register(MetalWireGuideApi, this); - RegisterPhysics(physicsEngine); - } - - #endregion - - #region Transformation - - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - - #endregion - - #region Conversion - - public override IEnumerable SetData(MetalWireGuideData data) - { - var updatedComponents = new List { this }; - - // geometry - _height = data.Height; - Rotation = new Vector3(data.RotX, data.RotY, data.RotZ); - _thickness = data.Thickness; - DragPoints = data.DragPoints; - _bendradius = data.Bendradius; - _standheight = data.Standheight; - - // collider data - var collComponent = GetComponent(); - if (collComponent) { - collComponent.enabled = data.IsCollidable; - - collComponent.HitEvent = data.HitEvent; - collComponent.HitHeight = data.HitHeight; - collComponent.OverwritePhysics = data.OverwritePhysics; - collComponent.Elasticity = data.Elasticity; - collComponent.ElasticityFalloff = data.ElasticityFalloff; - collComponent.Friction = data.Friction; - collComponent.Scatter = data.Scatter; - - updatedComponents.Add(collComponent); - } - - return updatedComponents; - } - - public override IEnumerable SetReferencedData(MetalWireGuideData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) - { - // mesh - var mesh = GetComponent(); - if (mesh) { - mesh.CreateMesh(data, table, textureProvider, materialProvider); - mesh.enabled = data.IsVisible; - SetEnabled(data.IsVisible); - } - - // collider data - var collComponent = GetComponentInChildren(); - if (collComponent) { - collComponent.PhysicsMaterial = materialProvider.GetPhysicsMaterial(data.PhysicsMaterial); - } - - return Array.Empty(); - } - - public override MetalWireGuideData CopyDataTo(MetalWireGuideData data, string[] materialNames, string[] textureNames, bool forExport) - { - // update the name - data.Name = name; - - // geometry - data.Height = _height; - data.RotX = Rotation.x; - data.RotY = Rotation.y; - data.RotZ = Rotation.z; - data.Thickness = _thickness; - data.Bendradius = _bendradius; - data.DragPoints = DragPoints; - - // visibility - data.IsVisible = GetEnabled(); - - // collision - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - - data.HitEvent = collComponent.HitEvent; - data.HitHeight = collComponent.HitHeight; - - data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; - data.OverwritePhysics = collComponent.OverwritePhysics; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - return data; - } - - public override void CopyFromObject(GameObject go) - { - var mwgComponent = go.GetComponent(); - if (mwgComponent != null) { - _height = mwgComponent._height; - _thickness = mwgComponent._thickness; - _standheight = mwgComponent._standheight; - Rotation = mwgComponent.Rotation; - _bendradius = mwgComponent._bendradius; - _dragPoints = mwgComponent._dragPoints.Select(dp => dp.Clone()).ToArray(); - - } else { - MoveDragPointsTo(_dragPoints, go.transform.localPosition.TranslateToVpx()); - } - - UpdateTransforms(); - } - - #endregion - - #region Editor Tooling - - internal Vector3 DragPointCenter { - get { - var sum = Vertex3D.Zero; - foreach (var t in DragPoints) { - sum += t.Center; - } - var center = sum / DragPoints.Length; - return center.ToUnityVector3(); - } - } - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorPosition() - { - var pos = DragPoints.Length == 0 ? Vector3.zero : DragPointCenter; - return new Vector3(pos.x, pos.y, _height); - } - public override void SetEditorPosition(Vector3 pos) { - if (DragPoints.Length == 0) { - return; - } - var diff = (pos - DragPointCenter).ToVertex3D(); - diff.Z = 0f; - foreach (var pt in DragPoints) { - pt.Center += diff; - } - _height = pos.z; - RebuildMeshes(); - } - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorRotation() => Rotation; - public override void SetEditorRotation(Vector3 rot) { - Rotation = rot; - RebuildMeshes(); - } - - #endregion - } -} +// 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 . + +#region ReSharper +// ReSharper disable CompareOfFloatsByEqualityOperator +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable InconsistentNaming +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VisualPinball.Engine.Math; +using VisualPinball.Engine.VPT; +using VisualPinball.Engine.VPT.MetalWireGuide; +using VisualPinball.Engine.VPT.Table; + +namespace VisualPinball.Unity +{ + [AddComponentMenu("Visual Pinball/Game Item/Metal Wire Guide")] + public class MetalWireGuideComponent : MainRenderableComponent, + IMetalWireGuideData + { + #region Data + + [Tooltip("Height of the metal wire guide (z-axis).")] + public float _height = 25f; + + [Min(0)] + [Tooltip("How thick the metal wire guide band is rendered.")] + public float _thickness = 3; + + [Min(0)] + [Tooltip("How tall the metal wire ends are rendered.")] + public float _standheight = 30f; + + + [Tooltip("Rotation on the playfield")] + public Vector3 Rotation; + + [Tooltip("Radius of the bend")] + public float _bendradius = 8f; + + [SerializeField] + private DragPointData[] _dragPoints; + + #endregion + + #region IMetalWireGuideData + + public DragPointData[] DragPoints { get => _dragPoints; set => _dragPoints = value; } + public float Thickness => _thickness; + public float Standheight => _standheight; + public float Height => _height; + public float RotX => Rotation.x; + public float RotY => Rotation.y; + public float RotZ => Rotation.z; + public float Bendradius => _bendradius; + + #endregion + + #region Overrides + + public override ItemType ItemType => ItemType.MetalWireGuide; + public override string ItemName => "MetalWireGuide"; + + public override MetalWireGuideData InstantiateData() => new MetalWireGuideData(); + + public override bool HasProceduralMesh => true; + + protected override Type MeshComponentType { get; } = typeof(MeshComponent); + protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); + + #endregion + + #region Runtime + + public MetalWireGuideApi MetalWireGuideApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + MetalWireGuideApi = new MetalWireGuideApi(gameObject, player, physicsEngine); + + player.Register(MetalWireGuideApi, this); + RegisterPhysics(physicsEngine); + } + + #endregion + + #region Transformation + + public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); + + #endregion + + #region Conversion + + public override IEnumerable SetData(MetalWireGuideData data) + { + var updatedComponents = new List { this }; + + // geometry + _height = data.Height; + Rotation = new Vector3(data.RotX, data.RotY, data.RotZ); + _thickness = data.Thickness; + DragPoints = data.DragPoints; + _bendradius = data.Bendradius; + _standheight = data.Standheight; + + // collider data + var collComponent = GetComponent(); + if (collComponent) { + collComponent.enabled = data.IsCollidable; + + collComponent.HitEvent = data.HitEvent; + collComponent.HitHeight = data.HitHeight; + collComponent.OverwritePhysics = data.OverwritePhysics; + collComponent.Elasticity = data.Elasticity; + collComponent.ElasticityFalloff = data.ElasticityFalloff; + collComponent.Friction = data.Friction; + collComponent.Scatter = data.Scatter; + + updatedComponents.Add(collComponent); + } + + return updatedComponents; + } + + public override IEnumerable SetReferencedData(MetalWireGuideData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) + { + // mesh + var mesh = GetComponent(); + if (mesh) { + mesh.CreateMesh(data, table, textureProvider, materialProvider); + mesh.enabled = data.IsVisible; + SetEnabled(data.IsVisible); + } + + // collider data + var collComponent = GetComponentInChildren(); + if (collComponent) { + collComponent.PhysicsMaterial = materialProvider.GetPhysicsMaterial(data.PhysicsMaterial); + } + + return Array.Empty(); + } + + public override MetalWireGuideData CopyDataTo(MetalWireGuideData data, string[] materialNames, string[] textureNames, bool forExport) + { + // update the name + data.Name = name; + + // geometry + data.Height = _height; + data.RotX = Rotation.x; + data.RotY = Rotation.y; + data.RotZ = Rotation.z; + data.Thickness = _thickness; + data.Bendradius = _bendradius; + data.DragPoints = DragPoints; + + // visibility + data.IsVisible = GetEnabled(); + + // collision + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + + data.HitEvent = collComponent.HitEvent; + data.HitHeight = collComponent.HitHeight; + + data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; + data.OverwritePhysics = collComponent.OverwritePhysics; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + return data; + } + + public override void CopyFromObject(GameObject go) + { + var mwgComponent = go.GetComponent(); + if (mwgComponent != null) { + _height = mwgComponent._height; + _thickness = mwgComponent._thickness; + _standheight = mwgComponent._standheight; + Rotation = mwgComponent.Rotation; + _bendradius = mwgComponent._bendradius; + _dragPoints = mwgComponent._dragPoints.Select(dp => dp.Clone()).ToArray(); + + } else { + MoveDragPointsTo(_dragPoints, go.transform.localPosition.TranslateToVpx()); + } + + UpdateTransforms(); + } + + #endregion + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideMeshComponent.cs index 85bf57062..75c40f316 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideMeshComponent.cs @@ -28,7 +28,7 @@ public class MetalWireGuideMeshComponent : MeshComponent new MetalWireGuideMeshGenerator(MainComponent) - .GetTransformedMesh(MainComponent.PlayfieldHeight, MainComponent.Height, MainComponent.PlayfieldDetailLevel, MainComponent.Bendradius) + .GetTransformedMesh(0, MainComponent.Height, MainComponent.PlayfieldDetailLevel, MainComponent.Bendradius) .TransformToWorld(); protected override PbrMaterial GetMaterial(MetalWireGuideData data, Table table) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs index c2268259c..5c477f4aa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs @@ -43,10 +43,10 @@ protected override void CreateColliders(ref ColliderReference colliders, } else { Debug.LogWarning($"Could not find mesh filter on playfield {GameObject.name}"); - colliders.Add(new PlaneCollider(new float3(0, 0, 1), MainComponent.TableHeight, info)); + colliders.Add(new PlaneCollider(new float3(0, 0, 1), 0, info)); } } else { - colliders.Add(new PlaneCollider(new float3(0, 0, 1), MainComponent.TableHeight, info)); + colliders.Add(new PlaneCollider(new float3(0, 0, 1), 0, info)); } // add playfield glass collider colliders.Add(new PlaneCollider(new float3(0, 0, -1), MainComponent.GlassHeight, info)); @@ -57,7 +57,7 @@ protected override void CreateColliders(ref ColliderReference colliders, colliders.Add(new LineCollider( new float2(MainComponent.Right, MainComponent.Top), new float2(MainComponent.Right, MainComponent.Bottom), - MainComponent.TableHeight, + 0, MainComponent.GlassHeight, info )); @@ -65,7 +65,7 @@ protected override void CreateColliders(ref ColliderReference colliders, colliders.Add(new LineCollider( new float2(MainComponent.Left, MainComponent.Bottom), new float2(MainComponent.Left, MainComponent.Top), - MainComponent.TableHeight, + 0, MainComponent.GlassHeight, info )); @@ -73,7 +73,7 @@ protected override void CreateColliders(ref ColliderReference colliders, colliders.Add(new LineCollider( new float2(MainComponent.Right, MainComponent.Bottom), new float2(MainComponent.Left, MainComponent.Bottom), - MainComponent.TableHeight, + 0, MainComponent.GlassHeight, info )); @@ -81,7 +81,7 @@ protected override void CreateColliders(ref ColliderReference colliders, colliders.Add(new LineCollider( new float2(MainComponent.Left, MainComponent.Top), new float2(MainComponent.Right, MainComponent.Top), - MainComponent.TableHeight, + 0, MainComponent.GlassHeight, info )); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs index 800420ee1..0e7010e04 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs @@ -34,16 +34,8 @@ namespace VisualPinball.Unity [AddComponentMenu("Visual Pinball/Game Item/Playfield")] public class PlayfieldComponent : MainRenderableComponent { - //public static readonly Quaternion GlobalRotation = Quaternion.Euler(-90, 0, 0); - //public const float GlobalScale = 0.00053975f; // see: https://github.com/freezy/VisualPinball.Engine/issues/151 - //public static readonly Quaternion GlobalRotation = Quaternion.Euler(0, 0, 0); - //public const float GlobalScale = 1f; // see: https://github.com/freezy/VisualPinball.Engine/issues/151 - #region Data - [Tooltip("Height of the table. Translates all elements along the Z-axis.")] - public float TableHeight; - [Tooltip("Height of the glass above the playfield. Serves as outer collision bounds of the Z-axis.")] public float GlassHeight; @@ -88,8 +80,8 @@ public class PlayfieldComponent : MainRenderableComponent protected override Type MeshComponentType => typeof(PlayfieldMeshComponent); protected override Type ColliderComponentType => typeof(PlayfieldColliderComponent); - public Rect3D BoundingBox => new Rect3D(Left, Right, Top, Bottom, TableHeight, GlassHeight); - public Aabb Bounds => new Aabb(Left, Right, Top, Bottom, TableHeight, GlassHeight); + public Rect3D BoundingBox => new Rect3D(Left, Right, Top, Bottom, 0, GlassHeight); + public Aabb Bounds => new Aabb(Left, Right, Top, Bottom, 0, GlassHeight); public AABB2D Bounds2D => new AABB2D(new float2(Left, Top), new float2(Right, Bottom)); public float3 PlayfieldGravity(float strength) { @@ -117,7 +109,6 @@ public override IEnumerable SetData(TableData data) var updatedComponents = new List { this }; // position - TableHeight = data.TableHeight; GlassHeight = data.GlassHeight; Left = data.Left; Left = data.Left; @@ -172,7 +163,7 @@ public IEnumerable SetReferencedData(PrimitiveData primitiveData, var mg = new PrimitiveMeshGenerator(primitiveData); var mesh = mg .GetTransformedMesh(table?.TableHeight ?? 0f, primitiveData.Mesh, Origin.Original, false) - .Transform(mg.TransformationMatrix(PlayfieldHeight)) // apply transformation to mesh, because this is the playfield + .Transform(mg.TransformationMatrix(0)) // apply transformation to mesh, because this is the playfield .TransformToWorld(); // also, transform this to world space. var material = new PbrMaterial( table?.GetMaterial(_playfieldMaterial), @@ -191,7 +182,7 @@ public override TableData CopyDataTo(TableData data, string[] materialNames, str var physicsEngine = GetComponentInParent(); // position - data.TableHeight = TableHeight; + data.TableHeight = 0; data.GlassHeight = GlassHeight; data.Left = Left; data.Right = Right; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index f8ccac042..dbfabf09a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -50,7 +50,7 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, { Header.Init(info, ColliderType.Plunger); - var zHeight = comp.PositionZ; + var zHeight = comp.Position.z; var x = -comp.Width; var x2 = comp.Width; var y = comp.Height; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 72c536255..bd898a3ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -24,6 +24,7 @@ using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Plunger; using VisualPinball.Engine.VPT.Table; @@ -31,39 +32,17 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Plunger")] - public class PlungerComponent : MainRenderableComponent, - ICoilDeviceComponent, IOnSurfaceComponent + public class PlungerComponent : MainRenderableComponent, ICoilDeviceComponent { #region Data - private Vector3 _position { + public Vector3 Position { get => transform.localPosition.TranslateToVpx(); set => transform.localPosition = value.TranslateToWorld(); } - - public Vector2 Position { - get => _position.XY(); - set => _position = new Vector3(value.x, value.y, Height); - } - public float Width = 25f; - public float Height = 20f; - public float ZAdjust { - get => _position.z; - set { - var pos = _position; - _position = new Vector3(pos.x, pos.y, value); - } - } - - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this plunger is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - #endregion public InputActionReference analogPlungerAction; @@ -126,12 +105,8 @@ public float4x4 TransformationWithinPlayfield [NonSerialized] private float4x4 _playfieldToWorld; - public void OnSurfaceUpdated() => RebuildMeshes(); - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float PositionZ => SurfaceHeight(Surface, Position); - public override void UpdateTransforms() { base.UpdateTransforms(); @@ -154,10 +129,9 @@ public override IEnumerable SetData(PlungerData data) var updatedComponents = new List { this }; // geometry and position - Position = data.Center.ToUnityVector2(); + Position = new Vector3(data.Center.X, data.Center.Y, data.ZAdjust); Width = data.Width; Height = data.Height; - ZAdjust = data.ZAdjust; // collider data var collComponent = GetComponent(); @@ -209,8 +183,6 @@ public override IEnumerable SetData(PlungerData data) public override IEnumerable SetReferencedData(PlungerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); - // rod mesh var rodMesh = GetComponentInChildren(true); if (rodMesh) { @@ -232,11 +204,10 @@ public override PlungerData CopyDataTo(PlungerData data, string[] materialNames, { // name, geometry and position data.Name = name; - data.Center = Position.ToVertex2D(); + data.Center = new Vertex2D(Position.x, Position.y); data.Width = Width; data.Height = Height; - data.ZAdjust = ZAdjust; - data.Surface = Surface != null ? Surface.name : string.Empty; + data.ZAdjust = Position.z; // collider data var collComponent = GetComponent(); @@ -287,8 +258,6 @@ public override void CopyFromObject(GameObject go) Position = plungerComponent.Position; Width = plungerComponent.Width; Height = plungerComponent.Height; - ZAdjust = plungerComponent.ZAdjust; - Surface = plungerComponent.Surface; } else { Position = go.transform.localPosition.TranslateToVpx(); @@ -309,7 +278,7 @@ internal PlungerState CreateState() return default; } - var zHeight = PositionZ; + var zHeight = Position.z; var x = -Width; var x2 = Width; var y = Height; @@ -381,17 +350,5 @@ public void UpdateParkPosition(float pos) skinnedMeshRenderer.SetBlendShapeWeight(0, pos); } } - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - public override Vector3 GetEditorPosition() => Position; - public override void SetEditorPosition(Vector3 pos) - { - Position = ((float3)pos).xy; - RebuildMeshes(); - } - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerFlatMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerFlatMeshComponent.cs index 362f0c84f..f104ecaed 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerFlatMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerFlatMeshComponent.cs @@ -29,7 +29,7 @@ public class PlungerFlatMeshComponent : PlungerMeshComponent { protected override Mesh GetMesh(PlungerData data) => new PlungerMeshGenerator(data) - .GetMesh(MainComponent.PositionZ, PlungerMeshGenerator.Flat) + .GetMesh(MainComponent.Position.z, PlungerMeshGenerator.Flat) .TransformToWorld(); protected override PbrMaterial GetMaterial(PlungerData data, Table table) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs index 2390e6854..f0c25fed6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerRodMeshComponent.cs @@ -45,7 +45,7 @@ public class PlungerRodMeshComponent : PlungerMeshComponent protected override Mesh GetMesh(PlungerData data) => new PlungerMeshGenerator(data) - .GetMesh(MainComponent.PositionZ, PlungerMeshGenerator.Rod); + .GetMesh(MainComponent.Position.z, PlungerMeshGenerator.Rod); protected override PbrMaterial GetMaterial(PlungerData data, Table table) => new PlungerMeshGenerator(data).GetMaterial(table); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs index d9bc04303..653432495 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerSpringMeshComponent.cs @@ -41,7 +41,7 @@ public class PlungerSpringMeshComponent : PlungerMeshComponent #endregion protected override Mesh GetMesh(PlungerData data) - => new PlungerMeshGenerator(data).GetMesh(MainComponent.PositionZ, PlungerMeshGenerator.Spring); + => new PlungerMeshGenerator(data).GetMesh(MainComponent.Position.z, PlungerMeshGenerator.Spring); protected override PbrMaterial GetMaterial(PlungerData data, Table table) => new PlungerMeshGenerator(data).GetMaterial(table); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index bc0235e34..92aeece1c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -79,7 +79,7 @@ public override IEnumerable SetData(PrimitiveData data) var objectRotation = new Vector3(data.RotAndTra[6], data.RotAndTra[7], data.RotAndTra[8]); var scaleMatrix = float4x4.Scale(size); - var transMatrix = float4x4.Translate(new float3(position.x, position.y, position.z + PlayfieldHeight)); + var transMatrix = float4x4.Translate(position); var rotTransMatrix = math.mul( float4x4.EulerZYX(math.radians(objectRotation)), math.mul( diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveMeshComponent.cs index 4b209cf51..633ebc6a0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveMeshComponent.cs @@ -45,7 +45,7 @@ public class PrimitiveMeshComponent : MeshComponent new PrimitiveMeshGenerator(data) - .GetMesh(MainComponent.PlayfieldHeight, data.Mesh, Origin.Original, false) + .GetMesh(0, data.Mesh, Origin.Original, false) .TransformToWorld(); protected override PbrMaterial GetMaterial(PrimitiveData data, Table table) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index 0c16d5aa5..f3e365616 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -67,9 +67,9 @@ protected override void CreateColliders(ref ColliderReference colliders, { var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); + colliderGenerator.GenerateColliders(0, ref kinematicColliders, margin); } else { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + colliderGenerator.GenerateColliders(0, ref colliders, margin); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 7b9d88170..7e7882e65 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -22,7 +22,6 @@ #endregion using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using Unity.Mathematics; @@ -37,8 +36,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Ramp")] - public class RampComponent : MainRenderableComponent, - IRampData, ISurfaceComponent + public class RampComponent : MainRenderableComponent, IRampData { #region Data @@ -177,39 +175,40 @@ public float Height(Vector2 pos) { var len = MathF.Sqrt(dx * dx + dy * dy); startLength += len; // Add the distance the object is between the two closest polyline segments. Matters mostly for straight edges. Z does not respect that yet! - var topHeight = _heightTop + PlayfieldHeight; - var bottomHeight = _heightBottom + PlayfieldHeight; + var topHeight = _heightTop; + var bottomHeight = _heightBottom; return vVertex[iSeg].Z + startLength / totalLength * (topHeight - bottomHeight) + bottomHeight; } - public override void UpdateVisibility() - { - // visibility - var wallComponent = GetComponentInChildren(true); - var floorComponent = GetComponentInChildren(true); - var wireComponent = GetComponentInChildren(true); - var isVisible = wireComponent && wireComponent.gameObject.activeInHierarchy || - floorComponent && floorComponent.gameObject.activeInHierarchy; - if (IsWireRamp) { - if (wireComponent) wireComponent.gameObject.SetActive(isVisible); - if (floorComponent) { - floorComponent.gameObject.SetActive(false); - floorComponent.ClearMeshVertices(); - } - if (wallComponent) { - wallComponent.gameObject.SetActive(false); - wallComponent.ClearMeshVertices(); - } - } else { - if (wireComponent) { - wireComponent.gameObject.SetActive(false); - wireComponent.ClearMeshVertices(); - } - if (floorComponent) floorComponent.gameObject.SetActive(isVisible); - if (wallComponent) wallComponent.gameObject.SetActive(isVisible && (_leftWallHeightVisible > 0 || _rightWallHeightVisible > 0)); - } - } + // todo revisit + // public override void UpdateVisibility() + // { + // // visibility + // var wallComponent = GetComponentInChildren(true); + // var floorComponent = GetComponentInChildren(true); + // var wireComponent = GetComponentInChildren(true); + // var isVisible = wireComponent && wireComponent.gameObject.activeInHierarchy || + // floorComponent && floorComponent.gameObject.activeInHierarchy; + // if (IsWireRamp) { + // if (wireComponent) wireComponent.gameObject.SetActive(isVisible); + // if (floorComponent) { + // floorComponent.gameObject.SetActive(false); + // floorComponent.ClearMeshVertices(); + // } + // if (wallComponent) { + // wallComponent.gameObject.SetActive(false); + // wallComponent.ClearMeshVertices(); + // } + // } else { + // if (wireComponent) { + // wireComponent.gameObject.SetActive(false); + // wireComponent.ClearMeshVertices(); + // } + // if (floorComponent) floorComponent.gameObject.SetActive(isVisible); + // if (wallComponent) wallComponent.gameObject.SetActive(isVisible && (_leftWallHeightVisible > 0 || _rightWallHeightVisible > 0)); + // } + // } public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); @@ -408,59 +407,5 @@ private void CenterPivot() #endregion - #region Editor Tooling - - private Vector3 DragPointCenter { - get { - var sum = Vertex3D.Zero; - foreach (var t in DragPoints) { - sum += t.Center; - } - var center = sum / DragPoints.Length; - return new Vector3(center.X, center.Y, _heightTop); - } - } - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - public override Vector3 GetEditorPosition() - { - return DragPoints.Length == 0 ? Vector3.zero : DragPointCenter; - } - - public override void SetEditorPosition(Vector3 pos) - { - if (DragPoints.Length == 0) { - return; - } - var diff = (pos - DragPointCenter).ToVertex3D(); - diff.Z = 0f; - foreach (var pt in DragPoints) { - pt.Center += diff; - } - RebuildMeshes(); - var playfieldComponent = GetComponentInParent(); - if (playfieldComponent) { - WalkChildren(playfieldComponent.transform, UpdateSurfaceReferences); - } - } - - protected static void WalkChildren(IEnumerable node, Action action) - { - foreach (Transform childTransform in node) { - action(childTransform); - WalkChildren(childTransform, action); - } - } - - protected void UpdateSurfaceReferences(Transform obj) - { - var surfaceComponent = obj.gameObject.GetComponent(); - if (surfaceComponent != null && ReferenceEquals(surfaceComponent.Surface, this)) - { - surfaceComponent.OnSurfaceUpdated(); - } - } - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs index dd8c180f3..244e86ed9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs @@ -31,7 +31,7 @@ protected override Mesh GetMesh(RampData _) { var playfieldComponent = GetComponentInParent(); return new RampMeshGenerator(MainComponent) - .GetMesh(playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, RampMeshGenerator.Floor) + .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Floor) .TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs index ab72368b2..3831b82db 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs @@ -31,7 +31,7 @@ protected override Mesh GetMesh(RampData data) { var playfieldComponent = GetComponentInParent(); return new RampMeshGenerator(MainComponent) - .GetMesh(playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, RampMeshGenerator.Wall) + .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Wall) .TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs index 00286b2ff..57a83c7c7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs @@ -31,7 +31,7 @@ protected override Mesh GetMesh(RampData data) { var playfieldComponent = GetComponentInParent(); return new RampMeshGenerator(MainComponent) - .GetMesh(playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, RampMeshGenerator.Wires) + .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Wires) .TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index 70eea1b4b..1fd393c86 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -53,9 +53,9 @@ protected override void CreateColliders(ref ColliderReference colliders, translateWithinPlayfieldMatrix ); if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); + colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); } else { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); + colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index d5986580a..1550d3d4d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -225,71 +225,5 @@ public override void CopyFromObject(GameObject go) } #endregion - - #region Editor Tooling - - private Vector3 DragPointCenter { - get { - var sum = Vertex3D.Zero; - foreach (var t in DragPoints) { - sum += t.Center; - } - var center = sum / DragPoints.Length; - return center.ToUnityVector3(); - } - } - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorPosition() - { - var pos = DragPoints.Length == 0 ? Vector3.zero : DragPointCenter; - return new Vector3(pos.x, pos.y, _height); - } - public override void SetEditorPosition(Vector3 pos) { - if (DragPoints.Length == 0) { - return; - } - var diff = (pos - DragPointCenter).ToVertex3D(); - diff.Z = 0f; - foreach (var pt in DragPoints) { - pt.Center += diff; - } - _height = pos.z; - RebuildMeshes(); - } - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorRotation() => Rotation; - public override void SetEditorRotation(Vector3 rot) { - Rotation = rot; - RebuildMeshes(); - } - - public override void EditorStartScaling() - { - _scalingDragPoints = _dragPoints.Select(dp => dp.Center).ToArray(); - _scale = 1f; - } - - public override void EditorEndScaling() - { - _scalingDragPoints = null; - } - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - public override Vector3 GetEditorScale() => new(_scale, 1f, 1f); - public override void SetEditorScale(Vector3 vScale) - { - var scale = 1 + (vScale.x - 1) / 5f; - _scale = scale; - var center = DragPointCenter.ToVertex3D(); - for (var i = 0; i < _dragPoints.Length; i++) { - _dragPoints[i].Center = center + scale * (_scalingDragPoints[i] - center); - } - - RebuildMeshes(); - } - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberMeshComponent.cs index 575d8308e..590a54c40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberMeshComponent.cs @@ -29,7 +29,7 @@ public class RubberMeshComponent : MeshComponent { protected override Mesh GetMesh(RubberData data) => new RubberMeshGenerator(MainComponent) - .GetTransformedMesh(MainComponent.PlayfieldHeight, MainComponent.Height, MainComponent.PlayfieldDetailLevel) + .GetTransformedMesh(0, MainComponent.Height, MainComponent.PlayfieldDetailLevel) .TransformToWorld(); protected override PbrMaterial GetMaterial(RubberData data, Table table) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 69d861939..6299ef1e8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -28,6 +28,7 @@ using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Spinner; using VisualPinball.Engine.VPT.Table; @@ -36,28 +37,15 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Spinner")] public class SpinnerComponent : MainRenderableComponent, - ISwitchDeviceComponent, IOnSurfaceComponent, IRotatableAnimationComponent + ISwitchDeviceComponent, IRotatableAnimationComponent { #region Data - private Vector3 _position { + public Vector3 Position { get => transform.localPosition.TranslateToVpx(); set => transform.localPosition = value.TranslateToWorld(); } - public Vector2 Position { - get => _position.XY(); - set => _position = new Vector3(value.x, value.y, Height); - } - - public float Height { - get => _position.z; - set { - var pos = _position; - _position = new Vector3(pos.x, pos.y, value); - } - } - public float Rotation { get => transform.localEulerAngles.y > 180 ? transform.localEulerAngles.y - 360 : transform.localEulerAngles.y; set => transform.SetLocalYRotation(math.radians(value)); @@ -93,11 +81,18 @@ public float Length [Tooltip("Minimal angle. This allows the spinner to bounce back instead of executing a 360° rotation.")] public float AngleMin; - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this spinner is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; + public bool ShowBracket { + get { + foreach (var mf in GetComponentsInChildren()) { + // todo use a component instead of relying on mesh names + switch (mf.sharedMesh.name) { + case BracketMeshName: + return mf.gameObject.activeInHierarchy; + } + } + return false; + } + } #endregion @@ -106,7 +101,7 @@ public float Length public override ItemType ItemType => ItemType.Spinner; public override string ItemName => "Spinner"; - public override SpinnerData InstantiateData() => new SpinnerData(); + public override SpinnerData InstantiateData() => new(); public override bool HasProceduralMesh => false; @@ -164,11 +159,6 @@ private void Start() [NonSerialized] private float4x4 _playfieldToWorld; - public void OnSurfaceUpdated() => UpdateTransforms(); - public float PositionZ => SurfaceHeight(Surface, Position); - - public float HeightOnPlayfield => Height + PositionZ; // todo handle surface - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion @@ -180,8 +170,7 @@ public override IEnumerable SetData(SpinnerData data) var updatedComponents = new List { this }; // transforms - Position = data.Center.ToUnityFloat2(); - Height = data.Height; + Position = new Vector3(data.Center.X, data.Center.Y, data.Height); Length = data.Length; Rotation = data.Rotation; @@ -214,7 +203,6 @@ public override IEnumerable SetData(SpinnerData data) public override IEnumerable SetReferencedData(SpinnerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); return Array.Empty(); } @@ -222,11 +210,10 @@ public override SpinnerData CopyDataTo(SpinnerData data, string[] materialNames, { // name and transforms data.Name = name; - data.Center = Position.ToVertex2D(); - data.Height = Height; + data.Center = new Vertex2D(Position.x, Position.y); + data.Height = Position.z; data.Length = Length; data.Rotation = Rotation; - data.Surface = Surface != null ? Surface.name : string.Empty; // spinner props data.Damping = Damping; @@ -262,13 +249,11 @@ public override void CopyFromObject(GameObject go) var spinnerComponent = go.GetComponent(); if (spinnerComponent != null) { Position = spinnerComponent.Position; - Height = spinnerComponent.Height; Rotation = spinnerComponent.Rotation; Length = spinnerComponent.Length; Damping = spinnerComponent.Damping; AngleMax = spinnerComponent.AngleMax; AngleMin = spinnerComponent.AngleMin; - Surface = spinnerComponent.Surface; } else { Position = go.transform.localPosition.TranslateToVpx(); @@ -292,7 +277,7 @@ internal SpinnerState CreateState() AngleMin = math.radians(AngleMin), Damping = math.pow(Damping, (float)PhysicsConstants.PhysFactor), Elasticity = collComponent.Elasticity, - Height = Height + Height = Position.z } : default; // animation @@ -312,32 +297,6 @@ internal SpinnerState CreateState() #endregion - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; - public override Vector3 GetEditorPosition() - { - return new Vector3(Position.x, Position.y, Height); - } - public override void SetEditorPosition(Vector3 pos) - { - Position = pos; - Height = pos.z; - } - - public override ItemDataTransformType EditorRotationType => ItemDataTransformType.OneD; - public override Vector3 GetEditorRotation() => new Vector3(Rotation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); - - public override ItemDataTransformType EditorScaleType => ItemDataTransformType.OneD; - - public bool ShowBracket => GetComponentsInChildren().Any(); - - public override Vector3 GetEditorScale() => new Vector3(Length, 0f, 0f); - public override void SetEditorScale(Vector3 scale) => Length = scale.x; - - #endregion - #region IRotatableAnimationComponent public void OnRotationUpdated(float angleRad) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 10ea2821d..878d85c4c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -164,9 +164,8 @@ private IEnumerator Animate() #endregion - #region IMeshComponent - public IMainRenderableComponent MainRenderableComponent => this; + public void UpdateTransforms() { } public void RebuildMeshes() { @@ -227,7 +226,7 @@ private Mesh GetMesh() Debug.Log($"Generating new mesh at {pos}"); var mesh = MeshGenerator - .GetTransformedMesh(pf.PlayfieldHeight, r0.Height, pf.PlayfieldDetailLevel) + .GetTransformedMesh(0, r0.Height, pf.PlayfieldDetailLevel) .TransformToWorld() .ToUnityMesh(); @@ -237,34 +236,6 @@ private Mesh GetMesh() return mesh; } - #endregion - - #region IMainRenderableComponent - - public bool IsLocked { get => _isLocked; set => _isLocked = value; } - public bool CanBeTransformed => false; - public bool OverrideTransform => false; - public string ItemName => "Slingshot"; - - public void UpdateTransforms() { } - public void UpdateVisibility() { } - - public ItemDataTransformType EditorPositionType => ItemDataTransformType.None; - public Vector3 GetEditorPosition() => Vector3.zero; - public void SetEditorPosition(Vector3 pos) { } - - public ItemDataTransformType EditorRotationType => ItemDataTransformType.None; - public Vector3 GetEditorRotation() => Vector3.zero; - public void SetEditorRotation(Vector3 pos) { } - - public ItemDataTransformType EditorScaleType => ItemDataTransformType.None; - public Vector3 GetEditorScale() => Vector3.one; - public void SetEditorScale(Vector3 pos) { } - public void EditorStartScaling() { } - public void EditorEndScaling() { } - - #endregion - public static GameObject LoadPrefab() => Resources.Load("Prefabs/Slingshot"); private DragPointData[] DragPointsAt(float pos) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index c78d51505..24f94f99d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -56,10 +56,10 @@ protected override void CreateColliders(ref ColliderReference colliders, } var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref kinematicColliders, margin); + colliderGenerator.GenerateColliders(0, ref kinematicColliders, margin); } else { - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); + colliderGenerator.GenerateColliders(0, ref colliders, margin); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 885ff3bdc..9ea69f18d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -34,7 +34,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Surface")] - public class SurfaceComponent : MainRenderableComponent, ISurfaceComponent + public class SurfaceComponent : MainRenderableComponent { #region Data @@ -93,7 +93,7 @@ private void Start() [NonSerialized] private float4x4 _playfieldToWorld; - public float Height(Vector2 _) => HeightTop + PlayfieldHeight; + public float Height(Vector2 _) => HeightTop; public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); @@ -259,34 +259,5 @@ internal SurfaceState CreateState() } #endregion - - #region Editor Tooling - - private Vector3 DragPointCenter { - get { - var sum = Vertex3D.Zero; - foreach (var t in DragPoints) { - sum += t.Center; - } - var center = sum / DragPoints.Length; - return new Vector3(center.X, center.Y, HeightTop); - } - } - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - public override Vector3 GetEditorPosition() => DragPoints.Length == 0 ? Vector3.zero : DragPointCenter; - public override void SetEditorPosition(Vector3 pos) { - if (DragPoints.Length == 0) { - return; - } - var diff = (pos - DragPointCenter).ToVertex3D(); - diff.Z = 0f; - foreach (var pt in DragPoints) { - pt.Center += diff; - } - RebuildMeshes(); - } - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs index 11d462e8c..4203d48af 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs @@ -31,7 +31,7 @@ protected override Mesh GetMesh(SurfaceData data) { var playfieldComponent = GetComponentInParent(); return new SurfaceMeshGenerator(data) - .GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, false) + .GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, 0, false) .TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs index 06ad3e7e0..e51abd3c1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs @@ -30,7 +30,7 @@ protected override Mesh GetMesh(SurfaceData data) { var playfieldComponent = GetComponentInParent(); return new SurfaceMeshGenerator(data) - .GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, playfieldComponent.PlayfieldHeight, false) + .GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, 0, false) .TransformToWorld(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs index 6c27281e6..d8c5fb7e4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs @@ -107,7 +107,7 @@ public override IEnumerable SetReferencedData(TableData data, Tab public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) { - data.TableHeight = PlayfieldHeight; + data.TableHeight = 0; data.GlobalDifficulty = GlobalDifficulty; data.OverridePhysics = OverridePhysics; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index 53219c16c..70420e773 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -53,14 +53,14 @@ internal void GenerateColliders(ref ColliderReference colliders) private void GenerateRoundHitObjects(ref ColliderReference colliders) { - var height = _component.PositionZ; - colliders.Add(new CircleCollider(new float2(0), _colliderComponent.HitCircleRadius, height, height + _colliderComponent.HitHeight, + var height = _component.Position.z; + colliders.Add(new CircleCollider(new float2(0), _colliderComponent.HitCircleRadius, 0, height + _colliderComponent.HitHeight, _api.GetColliderInfo(), ColliderType.TriggerCircle), _matrix); } private void GenerateCurvedHitObjects(ref ColliderReference colliders) { - var height = _component.PositionZ; + var height = _component.Position.z; var vVertex = DragPoint.GetRgVertex(_component.DragPoints); var count = vVertex.Length; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 5c3f1d944..149614850 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -37,20 +37,15 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Trigger")] public class TriggerComponent : MainRenderableComponent, - ITriggerComponent, IOnSurfaceComponent + ITriggerComponent { #region Data - private Vector3 _position { + public Vector3 Position { get => transform.localPosition.TranslateToVpx(); set => transform.localPosition = value.TranslateToWorld(); } - public Vector2 Position { - get => _position.XY(); - set => _position = new Vector3(value.x, value.y, _position.z); - } - public float _scale = 1f; public float Scale { @@ -72,12 +67,6 @@ public float Rotation { set => transform.SetLocalYRotation(math.radians(value)); } - [SerializeField] - [TypeRestriction(typeof(ISurfaceComponent), PickerLabel = "Walls & Ramps", UpdateTransforms = true)] - [Tooltip("On which surface this surface is attached to. Updates Z-translation.")] - public MonoBehaviour _surface; - public ISurfaceComponent Surface { get => _surface as ISurfaceComponent; set => _surface = value as MonoBehaviour; } - [SerializeField] private DragPointData[] _dragPoints; public DragPointData[] DragPoints { get => _dragPoints; set => _dragPoints = value; } @@ -144,8 +133,6 @@ private void Start() public Vector2 Center => Position; // todo remove? public void OnSurfaceUpdated() => UpdateTransforms(); - public float PositionZ => SurfaceHeight(Surface, Position); - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion @@ -191,8 +178,6 @@ public override IEnumerable SetData(TriggerData data) public override IEnumerable SetReferencedData(TriggerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { - Surface = FindComponent(components, data.Surface); - // mesh var meshComponent = GetComponent(); if (meshComponent) { @@ -208,9 +193,8 @@ public override TriggerData CopyDataTo(TriggerData data, string[] materialNames, { // name and transforms data.Name = name; - data.Center = Position.ToVertex2D(); + data.Center = new Vertex2D(Position.x, Position.y); data.Rotation = Rotation; - data.Surface = Surface != null ? Surface.name : string.Empty; // geometry data.DragPoints = DragPoints; @@ -251,7 +235,6 @@ public override void CopyFromObject(GameObject go) Position = triggerComponent.Position; Scale = triggerComponent.Scale; Rotation = triggerComponent.Rotation; - Surface = triggerComponent.Surface; _dragPoints = triggerComponent._dragPoints.Select(dp => dp.Clone()).ToArray(); } else { @@ -311,37 +294,5 @@ internal TriggerState CreateState() } #endregion - - #region Editor Tooling - - public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; - - public override Vector3 GetEditorPosition() => Surface != null - ? new Vector3(Position.x, Position.y, Surface.Height(Position)) - : new Vector3(Position.x, Position.y, 0); // todo? plus table height? - - public override void SetEditorPosition(Vector3 pos) - { - var newPos = (Vector2)((float3)pos).xy; - if (DragPoints.Length > 0) { - var diff = newPos - Position; - foreach (var pt in DragPoints) { - pt.Center += new Vertex3D(diff.x, diff.y, 0f); - } - } - RebuildMeshes(); - Position = ((float3)pos).xy; - } - - public override ItemDataTransformType EditorRotationType{ - get { - var meshComp = GetComponent(); - return !meshComp || !meshComp.IsCircle ? ItemDataTransformType.None : ItemDataTransformType.OneD; - } - } - public override Vector3 GetEditorRotation() => new Vector3(Rotation, 0f, 0f); - public override void SetEditorRotation(Vector3 rot) => Rotation = ClampDegrees(rot.x); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMeshComponent.cs index f7641b23c..5c3b44597 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMeshComponent.cs @@ -16,7 +16,6 @@ // ReSharper disable InconsistentNaming -using System; using UnityEngine; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Table; @@ -44,7 +43,7 @@ public class TriggerMeshComponent : MeshComponent protected override Mesh GetMesh(TriggerData data) => new TriggerMeshGenerator(data) - .GetMesh(MainComponent.PlayfieldHeight) + .GetMesh(0) .TransformToWorld(); protected override PbrMaterial GetMaterial(TriggerData data, Table table) From f6d434e15da642491289ed92ced154d4afe1feec Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 30 Nov 2024 23:59:23 +0100 Subject: [PATCH 132/208] debug: Throw exception when no gate could be instantiated. --- .../VPT/HitTarget/TargetExtensions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetExtensions.cs index 9b1bc4c05..5036322ae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/HitTarget/TargetExtensions.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 System; using VisualPinball.Engine.VPT.HitTarget; namespace VisualPinball.Unity.Editor @@ -26,6 +27,9 @@ internal static IVpxPrefab InstantiatePrefab(this HitTarget hitTarget) ? RenderPipeline.Current.PrefabProvider.CreateDropTarget(hitTarget.Data.TargetType) : RenderPipeline.Current.PrefabProvider.CreateHitTarget(hitTarget.Data.TargetType); + if (!prefab) { + throw new Exception($"Cannot instantiate prefab for target type {hitTarget.Data.TargetType}"); + } return new VpxPrefab(prefab, hitTarget); } } From d4772906380cca4f244320954c1a2c0eaa450a74 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 1 Dec 2024 02:14:00 +0100 Subject: [PATCH 133/208] gates: Fix scale on import. --- .../VisualPinball.Unity/VPT/Gate/GateComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 7972d4786..83fd25aaa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -174,7 +174,7 @@ public override IEnumerable SetData(GateData data) // transforms Position = data.Center.ToUnityVector3(data.Height); Rotation = data.Rotation > 180f ? data.Rotation - 360f : data.Rotation; - _length = data.Length; + Length = data.Length; _type = data.GateType; // collider data From f514e12c79e34416887086b059a57db876e68692 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 2 Dec 2024 22:26:28 +0100 Subject: [PATCH 134/208] assets: Move standard assets into asset library. --- .../Assets/Art/Meshes/Bumper/Bumper.fbx | Bin 112396 -> 0 bytes .../Assets/Art/Meshes/Bumper/Bumper.fbx.meta | 105 ----------- .../Assets/Art/Meshes/Drop Target.meta | 8 - .../Art/Meshes/Drop Target/Beveled.mesh | 166 ------------------ .../Art/Meshes/Drop Target/Beveled.mesh.meta | 8 - .../Meshes/Drop Target/Drop Targets VPX.fbx | Bin 19356 -> 0 bytes .../Drop Target/Drop Targets VPX.fbx.meta | 109 ------------ .../Art/Meshes/Drop Target/Simple Flat.mesh | 166 ------------------ .../Meshes/Drop Target/Simple Flat.mesh.meta | 8 - .../Assets/Art/Meshes/Drop Target/Simple.mesh | 166 ------------------ .../Art/Meshes/Drop Target/Simple.mesh.meta | 8 - .../Assets/Art/Meshes/Gate.meta | 8 - .../Assets/Art/Meshes/Gate/Gate Meshes.fbx | Bin 57788 -> 0 bytes .../Art/Meshes/Gate/Gate Meshes.fbx.meta | 109 ------------ .../Assets/Art/Meshes/Hit Target.meta | 8 - .../Art/Meshes/Hit Target/Hit Targets VPX.fbx | Bin 93308 -> 0 bytes .../Hit Target/Hit Targets VPX.fbx.meta | 109 ------------ .../Assets/Art/Meshes/Hit Target/Narrow.mesh | 166 ------------------ .../Art/Meshes/Hit Target/Narrow.mesh.meta | 8 - .../Hit Target/Rectangle Fat Narrow.mesh | 166 ------------------ .../Hit Target/Rectangle Fat Narrow.mesh.meta | 8 - .../Art/Meshes/Hit Target/Rectangle Fat.mesh | 166 ------------------ .../Meshes/Hit Target/Rectangle Fat.mesh.meta | 8 - .../Art/Meshes/Hit Target/Rectangle.mesh | 166 ------------------ .../Art/Meshes/Hit Target/Rectangle.mesh.meta | 8 - .../Assets/Art/Meshes/Hit Target/Round.mesh | 166 ------------------ .../Art/Meshes/Hit Target/Round.mesh.meta | 8 - .../Art/Meshes/Hit Target/Square Fat.mesh | 166 ------------------ .../Meshes/Hit Target/Square Fat.mesh.meta | 8 - 29 files changed, 2022 deletions(-) delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Narrow.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Narrow.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle Fat Narrow.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle Fat Narrow.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle Fat.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle Fat.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Rectangle.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Round.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Round.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Square Fat.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Square Fat.mesh.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx deleted file mode 100644 index 47507f9f686bb075aa9aaecc3a673cce294333e2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112396 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
`eeRIiuH?qdGG-mJjcU3gt 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 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx.meta deleted file mode 100644 index f0f394243..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Bumper/Bumper.fbx.meta +++ /dev/null @@ -1,105 +0,0 @@ -fileFormatVersion: 2 -guid: 2704e7232a1c60943bfb7d5656bef9f5 -ModelImporter: - serializedVersion: 21202 - internalIDToNameTable: [] - externalObjects: {} - materials: - materialImportMode: 2 - materialName: 0 - materialSearch: 1 - materialLocation: 1 - animations: - legacyGenerateAnimations: 4 - bakeSimulation: 0 - resampleCurves: 1 - optimizeGameObjects: 0 - removeConstantScaleCurves: 1 - motionNodeName: - rigImportErrors: - rigImportWarnings: - animationImportErrors: - animationImportWarnings: - animationRetargetingWarnings: - animationDoRetargetingWarnings: 0 - importAnimatedCustomProperties: 0 - importConstraints: 0 - animationCompression: 1 - animationRotationError: 0.5 - animationPositionError: 0.5 - animationScaleError: 0.5 - animationWrapMode: 0 - extraExposedTransformPaths: [] - extraUserProperties: [] - clipAnimations: [] - isReadable: 0 - meshes: - lODScreenPercentages: [] - globalScale: 1 - meshCompression: 0 - addColliders: 0 - useSRGBMaterialColor: 1 - sortHierarchyByName: 1 - importVisibility: 1 - importBlendShapes: 1 - importCameras: 1 - importLights: 1 - nodeNameCollisionStrategy: 1 - fileIdsGeneration: 2 - swapUVChannels: 0 - generateSecondaryUV: 0 - useFileUnits: 1 - keepQuads: 0 - weldVertices: 1 - bakeAxisConversion: 0 - preserveHierarchy: 0 - skinWeightsMode: 0 - maxBonesPerVertex: 4 - minBoneWeight: 0.001 - optimizeBones: 1 - meshOptimizationFlags: -1 - indexFormat: 0 - secondaryUVAngleDistortion: 8 - secondaryUVAreaDistortion: 15.000001 - secondaryUVHardAngle: 88 - secondaryUVMarginMethod: 1 - secondaryUVMinLightmapResolution: 40 - secondaryUVMinObjectScale: 1 - secondaryUVPackMargin: 4 - useFileScale: 1 - tangentSpace: - normalSmoothAngle: 60 - normalImportMode: 0 - tangentImportMode: 3 - normalCalculationMode: 4 - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 - blendShapeNormalImportMode: 1 - normalSmoothingSource: 0 - referencedClips: [] - importAnimation: 1 - humanDescription: - serializedVersion: 3 - human: [] - skeleton: [] - armTwist: 0.5 - foreArmTwist: 0.5 - upperLegTwist: 0.5 - legTwist: 0.5 - armStretch: 0.05 - legStretch: 0.05 - feetSpacing: 0 - globalScale: 1 - rootMotionBoneName: - hasTranslationDoF: 0 - hasExtraRoot: 0 - skeletonHasParents: 1 - lastHumanDescriptionAvatarSource: {instanceID: 0} - autoGenerateAvatarMappingIfUnspecified: 1 - animationType: 2 - humanoidOversampling: 1 - avatarSetup: 0 - addHumanoidExtraRootOnlyWhenUsingAvatar: 1 - additionalBone: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target.meta deleted file mode 100644 index 2cc6d13df..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: b7fd4dcb2e8607947ac81602ad3bc871 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh deleted file mode 100644 index e8c4ad9c3..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Beveled - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 192 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 88 - localAABB: - m_Center: {x: 0, y: 0, z: 0.78725} - m_Extent: {x: 0.525, y: 0.25, z: 0.95} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 0c000d000e000000010002003e0000004b003e0001000000030000000200080003000200020009000800160008000900160009000a000a00170016000b0017000a0004000b000a000a00050004000100040005000100050002000600020005000500070006000f0010001100120013001400150014001300180019001a001a001b0018001c001d001e001e001f001c002000210022002200230020002400250026002400260027002700290024002700280029002a002b002c002a002c002d002e002a002d002e002d002f002e002f0033002e0033003000330031003000300031003200320049003000320048004900340035003600370034003600370036003800390037003800390038003a003a0038003b003b003d003a003b003c003d0042003c003b0047003c0042003f00400041003f0041004a004300440045004500460043004c004d004e004e004d004f004f0050004e0050004f005100510052005000520051005300530054005200540053005500550056005400570056005500 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 88 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 2816 - _typelessdata: 6666063f9a99193ed578f93e000000800000803f0000008067f0533ffe813abf666606bf9a99193ed578f93e000000800000803f000000807233dc38fe813abf9a99d93e9a99193e355ebe3f00000080fb5c7d3f3cbd12bed8bb3f3fed7daabe6666063f9a99193e355ebe3f000000808104753faa6094be67f0533fed7daabe666606bf9a99193e355ebe3f000000808104753faa6094be7233dc38ed7daabe9a99d9be9a99193e355ebe3f00000080fb5c7d3f3cbd12be1aa7a13ded7daabe9a99d93e9a99193e6891d13f000000800000803f0000008053b03a3fc9ae8cbe9a99d9be9a99193e6891d13f000000800000803f00000080b802ca3dc9ae8cbe6666063f0000803e6891d13f000000808104753faa6094be67f0533f24b683be9a99d93e0000803e6891d13f00000000083d7b3f14d044bed8bb3f3f24b683be9a99d9be0000803e6891d13f00000000083d7b3f14d044be1aa7a13d24b683be666606bf0000803e6891d13f000000808104753faa6094be7233dc3824b683be9a99d9be0000803e6891d13f0000803f00000000000000801aa7a13d24b683be9a99d9be9a99193e6891d13f0000803f0000000000000080b802ca3dc9ae8cbe9a99d9be9a99193e355ebe3f0000803f00000000000000801aa7a13ded7daabe9a99d93e9a99193e355ebe3f000080bf0000000000000080d8bb3f3fed7daabe9a99d93e9a99193e6891d13f000080bf000000000000008053b03a3fc9ae8cbe9a99d93e0000803e6891d13f000080bf0000000000000080d8bb3f3f24b683be9a99d93e9a99193e6891d13f0000000000000000000080bf53b03a3fc9ae8cbe9a99d9be9a99193e6891d13f0000000000000000000080bfb802ca3dc9ae8cbe9a99d93e0000803e6891d13f0000000000000000000080bfd8bb3f3f24b683be9a99d9be0000803e6891d13f0000000000000000000080bf1aa7a13d24b683be6666063f0000803e355ede3f000000800000803f0000008067f0533f130f68be666606bf0000803e355ede3f000000800000803f00000080bd3706b8130f68be6666063f000080be355ede3f00000000000000000000803f67f0533ff7cc12b96666063f0000803e355ede3f00000000000000000000803f67f0533f130f68be666606bf0000803e355ede3f00000000000000000000803fbd3706b8130f68be666606bf000080be355ede3f00000000000000000000803fbd3706b8f7cc12b96666063f000080be6891d13f00000000000080bf00000080cb9c563f87fb48bc6666063f000080be355ede3f00000000000080bf00000080cb9c563f61fbc9ba666606bf000080be355ede3f00000000000080bf00000080f7917f3f61fbc9ba666606bf000080be6891d13f00000000000080bf00000080f7917f3f87fb48bc6666063fcdcc4cbe022bcb3f00000000810435bf810435bfcb9c563fca6c90bc6666063f000080be6891d13f00000000810435bf810435bfcb9c563f87fb48bc666606bf000080be6891d13f00000000810435bf810435bff7917f3f87fb48bc666606bfcdcc4cbe022bcb3f00000000810435bf810435bff7917f3fca6c90bc6666063fcdcc4cbed578f93e00000000000080bf00000080cb9c563fe4d915be6666063fcdcc4cbe022bcb3f00000000000080bf00000080cb9c563fca6c90bc666606bfcdcc4cbe022bcb3f00000000000080bf00000080f7917f3fca6c90bc666606bfcdcc4cbed578f93e00000000000080bf00000080f7917f3fe4d915be666606bfcdcc4cbef0a726be00000000000080bf00000080f7917f3fc93d5dbe6666063fcdcc4cbef0a726be00000000000080bf00000080cb9c563fc93d5dbe666606bf000080be6891d13f000080bf000000000000008006d47f3f24b683be666606bf000080be355ede3f000080bf000000000000008006d47f3f130f68be666606bf0000803e355ede3f000080bf000000000000008067f0533f130f68be666606bf0000803e6891d13f000080bf000000000000008067f0533f24b683be666606bfcdcc4cbe022bcb3f000080bf00000000000000801dac7b3ff3c691be666606bf9a99193e355ebe3f000080bf0000000000000080ebc95c3fed7daabe666606bfcdcc4cbed578f93e000080bf00000000000000801dac7b3ffe813abf666606bf9a99193e448b0c3e000080bf0000000000000080ebc95c3f05e05fbf666606bfcdcc4cbd79e9e6bd000080bf00000000000000808d426e3fc0037bbf666606bf9a99193ed578f93e000080bf0000000000000080ebc95c3ffe813abf6666063f0000803e355ede3f0000803f000000000000008067f0533f130f68be6666063f000080be355ede3f0000803f000000000000008006d47f3f130f68be6666063f000080be6891d13f0000803f000000000000008006d47f3f24b683be6666063f0000803e6891d13f0000803f000000000000008067f0533f24b683be6666063fcdcc4cbe022bcb3f0000803f00000000000000801dac7b3ff3c691be6666063f9a99193e355ebe3f0000803f0000000000000080ebc95c3fed7daabe6666063f9a99193ed578f93e0000803f0000000000000080ebc95c3ffe813abf6666063fcdcc4cbed578f93e0000803f00000000000000801dac7b3ffe813abf6666063fcdcc4cbd79e9e6bd0000803f00000000000000808d426e3fc0037bbf6666063f9a99193e448b0c3e0000803f0000000000000080ebc95c3f05e05fbf666606bf9a99193e448b0c3e000000800000803f000000807233dc3805e05fbf6666063f9a99193e448b0c3e0000008010e9473f57ec1fbf67f0533f05e05fbf6666063fcdcc4cbd79e9e6bd0000008010e9473f57ec1fbf67f0533fc0037bbf666606bfcdcc4cbd79e9e6bd0000008010e9473f57ec1fbf7233dc38c0037bbf6666063fcdcc4cbef0a726be0000803f0000000000000080e57e7b3fe9f17fbf666606bfcdcc4cbdf0a726be000000800000803f000000807233dc38f5f67fbf666606bfcdcc4cbd79e9e6bd000000800000803f000000807233dc38c0037bbf6666063fcdcc4cbd79e9e6bd000000800000803f0000008067f0533fc0037bbf6666063fcdcc4cbdf0a726be000000800000803f0000008067f0533ff5f67fbf6666063fcdcc4cbdf0a726be0000803f00000000000000808d426e3fe9f17fbf666606bfcdcc4cbdf0a726be000080bf00000000000000808d426e3fe9f17fbf666606bfcdcc4cbef0a726be000080bf0000000000000080e57e7b3fe9f17fbf666606bf9a99193e448b0c3e0000008010e9473f57ec1fbf7233dc3805e05fbf6666063f9a99193e448b0c3e000000800000803f0000008067f0533f05e05fbfcdcc4c3ecdcc4cbd79e9e6bd3945773f6f81843e000000806154123fc0037bbfa75c313ecdcc4c3d79e9e6bd2db25d3f0000003f0000008020ec0c3fc0037bbfa75c313ecdcc4c3d448b0c3e4694463fc58f213f0000008020ec0c3f05e05fbfcdcccc3de752fc3d79e9e6bd0000003f2db25d3f00000080be4cfc3ec0037bbfcdcccc3de752fc3d448b0c3e0000003f2db25d3f00000080be4cfc3e05e05fbf000000009a99193e79e9e6bd000000800000803f00000080bbf0d33ec0037bbf000000009a99193e448b0c3e000000800000803f00000080bbf0d33e05e05fbfcdccccbde752fc3d79e9e6bd000000bf2db25d3f00000080b894ab3ec0037bbfcdccccbde752fc3d448b0c3e000000bf2db25d3f00000080b894ab3e05e05fbfa75c31becdcc4c3d79e9e6bd2db25dbf0000003f0000008035098e3ec0037bbfa75c31becdcc4c3d448b0c3e469446bfc58f213f0000008035098e3e05e05fbfcdcc4cbecdcc4cbd79e9e6bd394577bf6f81843e00000080b438833ec0037bbf - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: 0.78725} - m_Extent: {x: 0.525, y: 0.25, z: 0.95} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh.meta deleted file mode 100644 index f1f8d70be..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Beveled.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 29c7cef5b5af8e5458418461da4ab685 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx deleted file mode 100644 index 189c3c8be544a96ae95504ca559039d25cfe4baf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19356 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 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta deleted file mode 100644 index c0dfd6bde..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Drop Targets VPX.fbx.meta +++ /dev/null @@ -1,109 +0,0 @@ -fileFormatVersion: 2 -guid: 736c9b6aa90a2b547abe58746ecc1764 -ModelImporter: - serializedVersion: 22200 - internalIDToNameTable: [] - externalObjects: {} - materials: - materialImportMode: 2 - materialName: 0 - materialSearch: 1 - materialLocation: 1 - animations: - legacyGenerateAnimations: 4 - bakeSimulation: 0 - resampleCurves: 1 - optimizeGameObjects: 0 - removeConstantScaleCurves: 0 - motionNodeName: - rigImportErrors: - rigImportWarnings: - animationImportErrors: - animationImportWarnings: - animationRetargetingWarnings: - animationDoRetargetingWarnings: 0 - importAnimatedCustomProperties: 0 - importConstraints: 0 - animationCompression: 1 - animationRotationError: 0.5 - animationPositionError: 0.5 - animationScaleError: 0.5 - animationWrapMode: 0 - extraExposedTransformPaths: [] - extraUserProperties: [] - clipAnimations: [] - isReadable: 0 - meshes: - lODScreenPercentages: [] - globalScale: 1 - meshCompression: 0 - addColliders: 0 - useSRGBMaterialColor: 1 - sortHierarchyByName: 1 - importPhysicalCameras: 1 - importVisibility: 1 - importBlendShapes: 1 - importCameras: 1 - importLights: 1 - nodeNameCollisionStrategy: 1 - fileIdsGeneration: 2 - swapUVChannels: 0 - generateSecondaryUV: 0 - useFileUnits: 1 - keepQuads: 0 - weldVertices: 1 - bakeAxisConversion: 0 - preserveHierarchy: 0 - skinWeightsMode: 0 - maxBonesPerVertex: 4 - minBoneWeight: 0.001 - optimizeBones: 1 - meshOptimizationFlags: -1 - indexFormat: 0 - secondaryUVAngleDistortion: 8 - secondaryUVAreaDistortion: 15.000001 - secondaryUVHardAngle: 88 - secondaryUVMarginMethod: 1 - secondaryUVMinLightmapResolution: 40 - secondaryUVMinObjectScale: 1 - secondaryUVPackMargin: 4 - useFileScale: 1 - strictVertexDataChecks: 0 - tangentSpace: - normalSmoothAngle: 60 - normalImportMode: 0 - tangentImportMode: 3 - normalCalculationMode: 4 - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 - blendShapeNormalImportMode: 1 - normalSmoothingSource: 0 - referencedClips: [] - importAnimation: 1 - humanDescription: - serializedVersion: 3 - human: [] - skeleton: [] - armTwist: 0.5 - foreArmTwist: 0.5 - upperLegTwist: 0.5 - legTwist: 0.5 - armStretch: 0.05 - legStretch: 0.05 - feetSpacing: 0 - globalScale: 1 - rootMotionBoneName: - hasTranslationDoF: 0 - hasExtraRoot: 0 - skeletonHasParents: 1 - lastHumanDescriptionAvatarSource: {instanceID: 0} - autoGenerateAvatarMappingIfUnspecified: 1 - animationType: 2 - humanoidOversampling: 1 - avatarSetup: 0 - addHumanoidExtraRootOnlyWhenUsingAvatar: 1 - importBlendShapeDeformPercent: 1 - remapMaterialsIfMaterialImportModeIsNone: 0 - additionalBone: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh deleted file mode 100644 index 897840f10..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Simple Flat - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 174 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 68 - localAABB: - m_Center: {x: 0, y: 0, z: 0.814926} - m_Extent: {x: 0.55, y: 0.1, z: 0.975} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 2400250026002600270024000000010002000000030001000000020013000100030014001200130002001500140003001300120011001400150016001000110012001700160015000b00110010001b00160017001a001b00170010000a000b000b000a000900080009000a00090008000600040006000800040005000600040007000500050007000f000e000f0007000f000e000d000c000d000e000d000c0018000c00190018001c001d001e001e001f001c001e001d002e002e002f001e002e0034002f002d002e001d0034002e0035001d002c002d0034003500360035003700360031002d002c002c0030003100330031003000300032003300200021002200220023002000220021003a003a003b0022003a0040003b0039003a00210040003a0041002100380039004000410042004100430042003d003900380038003c003d003f003d003c003c003e003f00280029002a002a002b002800 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 68 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 2176 - _typelessdata: cdcc0c3ff168e3bce5b5de3fe86a7b3f000000002506413edbdcd03ef0306dbdcdcc0c3fcdcccc3d95d7fa3eb5157b3f000000004bc847be361e0c3e6f1283bccdcc0c3fcdcccc3de5b5de3fb5157b3f000000004bc8473edbdcd03e6f1283bccdcc0c3fee08c7bd95d7fa3ef5b97a3f00000000a9a44ebe361e0c3e9b3ca5bdcdcc0cbfcdcccc3de5b5de3fb5157bbf000000004bc8473e9291173f6f1283bccdcc0cbfee08c7bd95d7fa3ef5b97abf00000000a9a44ebe72f85c3f9b3ca5bdcdcc0cbff168e3bce5b5de3fe86a7bbf000000002506413e9291173ff0306dbdcdcc0cbfcdcccc3d95d7fa3eb5157bbf000000004bc847be72f85c3f6f1283bc060d09bfcdcccc3d683ce33f810435bf000000008104353f045a163f6f1283bc060d09bf8d45d3bc683ce33f0bb536bf000000006a4d333f045a163f8f8b6abd000000bfcdcccc3d4c1ce53f4bc847be00000000b5157b3f91d2143f6f1283bc000000bfcdccccbc4c1ce53f151d49be000000000c027b3f91d2143ffa7b69bd000000bfcdcccc3dfb3de13e4bc847be00000000b5157bbff4f8613f6f1283bc000000bfcdccccbdfb3de13e4b5946be000000005f297bbff4f8613f8620a7bd060d09bfcdcccc3d66bde83e810435bf00000000810435bf5cad5f3f6f1283bc060d09bf431ccbbd66bde83e4f4033bf00000000b4c836bf5cad5f3f7192a6bd0000003fcdcccc3d4c1ce53f4bc8473e00000000b5157b3fde5ad63e6f1283bc0000003fcdccccbc4c1ce53f151d493e000000000c027b3fde5ad63efa7b69bd060d093fcdcccc3d683ce33f8104353f000000008104353ff94bd33e6f1283bc060d093f8d45d3bc683ce33f0bb5363f000000006a4d333ff94bd33e8f8b6abd060d093fcdcccc3d66bde83e8104353f00000000810435bf904a013e6f1283bc060d093f431ccbbd66bde83e4f40333f00000000b4c836bf904a013e7192a6bd0000003fcdcccc3dfb3de13e4bc8473e00000000b5157bbf5c38f03d6f1283bc0000003fcdccccbdfb3de13e4b59463e000000005f297bbf5c38f03d8620a7bd9a9999becdccccbdfb3de13e0000000000000000000080bfd0436d3f8620a7bd9a9999becdcccc3dfb3de13e0000000000000000000080bfd0436d3f6f1283bc9a99993ecdccccbdfb3de13e0000000000000000000080bf7ee1953d8620a7bd9a99993ecdcccc3dfb3de13e0000000000000000000080bf7ee1953d6f1283bc9a9999becdcccc3d70ea23be000000000000803f000000806b2bf63d2b8776bf9a9999becdcccc3dfb3de13e000000000000803f000000806b2bf63dd9ce2fbf9a99993ecdcccc3dfb3de13e000000000000803f00000080371ac03ed9ce2fbf9a99993ecdcccc3d70ea23be000000000000803f00000080371ac03e2b8776bf9a99993ecdccccbd70ea23be00000000000080bf00000080e5f21f3f2b8776bf9a99993ecdccccbdfb3de13e0000000020d27fbf508d173de5f21f3fd9ce2fbf9a9999becdccccbdfb3de13e0000000020d27fbf508d173d933a613fd9ce2fbf9a9999becdccccbd70ea23be00000000000080bf00000080933a613f2b8776bf9a9999becdccccbd70ea23be000080bf00000000000000800000803f8620a7bd9a9999becdccccbdfb3de13e000080bf0000000000000080d0436d3f8620a7bd9a9999becdcccc3dfb3de13e000080bf0000000000000080d0436d3f6f1283bc9a9999becdcccc3d70ea23be000080bf00000000000000800000803f6f1283bc9a99993ecdcccc3d70ea23be0000803f0000000000000000000000006f1283bc9a99993ecdcccc3dfb3de13e0000803f00000000000000007ee1953d6f1283bc9a99993ecdccccbdfb3de13e0000803f00000000000000007ee1953d8620a7bd9a99993ecdccccbd70ea23be0000803f0000000000000000000000008620a7bd000000bfcdcccc3dfb3de13e000000000000803f00000080e02d103dd9ce2fbf000000bfcdcccc3d4c1ce53f000000000000803f00000080e02d103d8b6ce7bd0000003fcdcccc3d4c1ce53f000000000000803f00000080569feb3e8b6ce7bd0000003fcdcccc3dfb3de13e000000000000803f00000080569feb3ed9ce2fbf060d09bfcdcccc3d66bde83e000000000000803f00000080f243a53cf4362ebf060d09bfcdcccc3d683ce33f000000000000803f00000080f243a53cb62bf4bdcdcc0cbfcdcccc3d95d7fa3e000000000000803f000000808a8e643c355e2abfcdcc0cbfcdcccc3de5b5de3f000000000000803f000000808a8e643cd57809be060d093fcdcccc3d66bde83e000000000000803f00000080d350f33ef4362ebf060d093fcdcccc3d683ce33f000000000000803f00000080d350f33eb62bf4bdcdcc0c3fcdcccc3d95d7fa3e000000000000803f000000809d80f63e355e2abfcdcc0c3fcdcccc3de5b5de3f000000000000803f000000809d80f63ed57809be0000003fcdccccbdfb3de13e17b7d138b29d7fbff853633d55300a3fd9ce2fbf0000003fcdccccbc4c1ce53f52499d39b29d7fbff853633d55300a3f8b6ce7bd000000bfcdccccbc4c1ce53f52499db9b29d7fbff853633d22fd763f8b6ce7bd000000bfcdccccbdfb3de13e17b7d1b8b29d7fbff853633d22fd763fd9ce2fbf060d093f431ccbbd66bde83e52491d3ab29d7fbff853633d9757063ff4362ebf060d093f8d45d3bc683ce33f6f12033ab29d7fbff853633d9757063fb62bf4bdcdcc0c3fee08c7bd95d7fa3efaedeb3ab29d7fbff853633db1bf043f355e2abfcdcc0c3ff168e3bce5b5de3f17b7d1b9b29d7fbff853633db1bf043fd57809be060d09bf431ccbbd66bde83e52491dbab29d7fbff853633de0d57a3ff4362ebf060d09bf8d45d3bc683ce33f6f1203bab29d7fbff853633de0d57a3fb62bf4bdcdcc0cbfee08c7bd95d7fa3e89d2debab29d7fbff853633dc66d7c3f355e2abfcdcc0cbff168e3bce5b5de3f17b7d139b29d7fbff853633dc66d7c3fd57809be - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: 0.814926} - m_Extent: {x: 0.55, y: 0.1, z: 0.975} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh.meta deleted file mode 100644 index 0dc03b03b..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple Flat.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0d00cc9a80b64304abbfda951a5351f0 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh deleted file mode 100644 index 46a8c4399..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Simple - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 66 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 36 - localAABB: - m_Center: {x: 0, y: 0, z: 0.785199} - m_Extent: {x: 0.525, y: 0.175, z: 0.95} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 000001000200010003000200040005000600060007000400080009000a0008000a000b000b000d0008000b000c000d000e000f001000100022000e0010002100220011000f000e00110012000f001300140015001500140016001600170015001b0017001600200017001b00180019001a0018001a0023001c001d001e001e001f001c00 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 36 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 1152 - _typelessdata: 6666063f3333333e9b710a3e000000000000803f000000800fd2533f4cc15abf666606bf3333333e9b710a3e000000000000803f000000806519623b4cc15abf6666063f3333333e001bde3f000000000000803f000000800fd2533f118e69be666606bf3333333e001bde3f000000000000803f000000806519623b118e69be6666063f333333be001bde3f00000000000000000000803f0fd2533f58ff67ba6666063f3333333e001bde3f00000000000000000000803f0fd2533f118e69be666606bf3333333e001bde3f00000000000000000000803f6519623b118e69be666606bf333333be001bde3f00000000000000000000803f6519623b58ff67ba6666063f333333be9b710a3e00000000000080bf00000000cb9c563fe4d915be6666063f333333be001bde3f00000000000080bf00000000cb9c563f58ff67ba666606bf333333be001bde3f00000000000080bf00000000f7917f3f58ff67ba666606bf333333be9b710a3e00000000000080bf00000000f7917f3fe4d915be666606bf333333be98c128be00000000000080bf00000000f7917f3fc93d5dbe6666063f333333be98c128be00000000000080bf00000000cb9c563fc93d5dbe666606bf333333be9b710a3e000080bf000000000000008081957b3f4cc15abf666606bf3333333e9b710a3e000080bf0000000000000080ebc95c3f4cc15abf666606bfcdccccbcc91cebbd000080bf00000000000000808d426e3fc0037bbf666606bf333333be001bde3f000080bf000000000000008081957b3f118e69be666606bf3333333e001bde3f000080bf0000000000000080ebc95c3f118e69be6666063f3333333e001bde3f0000803f0000000000000080ebc95c3f118e69be6666063f333333be001bde3f0000803f000000000000008081957b3f118e69be6666063f3333333e9b710a3e0000803f0000000000000080ebc95c3f4cc15abf6666063f333333be9b710a3e0000803f000000000000008081957b3f4cc15abf6666063fcdccccbcc91cebbd0000803f00000000000000808d426e3fc0037bbf6666063f3333333e9b710a3e0000000010e9473f57ec1fbf0fd2533f4cc15abf6666063fcdccccbcc91cebbd0000000010e9473f57ec1fbf0fd2533fc0037bbf666606bfcdccccbcc91cebbd0000000010e9473f57ec1fbf6519623bc0037bbf6666063f333333be98c128be0000803f000000000000008081957b3fe9f17fbf666606bfcdccccbc98c128be000000000000803f000000806519623bf5f67fbf666606bfcdccccbcc91cebbd000000000000803f000000806519623bc0037bbf6666063fcdccccbcc91cebbd000000000000803f000000800fd2533fc0037bbf6666063fcdccccbc98c128be000000000000803f000000800fd2533ff5f67fbf6666063fcdccccbc98c128be0000803f00000000000000808d426e3fe9f17fbf666606bfcdccccbc98c128be000080bf00000000000000808d426e3fe9f17fbf666606bf333333be98c128be000080bf000000000000008081957b3fe9f17fbf666606bf3333333e9b710a3e0000000010e9473f57ec1fbf6519623b4cc15abf - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: 0.785199} - m_Extent: {x: 0.525, y: 0.175, z: 0.95} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh.meta deleted file mode 100644 index f4d1d43ff..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Drop Target/Simple.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 5473bb84e990aa94b9d703490e04f106 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 4300000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate.meta deleted file mode 100644 index b2d3420cc..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: db120d3f8af2eac42ad79286730ed05e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx deleted file mode 100644 index 18c4f5d69cd6225171b1e4138a8f2ce39d66aa22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57788 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 diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta b/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta deleted file mode 100644 index 679a09c2a..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Gate/Gate Meshes.fbx.meta +++ /dev/null @@ -1,109 +0,0 @@ -fileFormatVersion: 2 -guid: f7b77729178432248a07efe02e9f5752 -ModelImporter: - serializedVersion: 22200 - internalIDToNameTable: [] - externalObjects: {} - materials: - materialImportMode: 2 - materialName: 0 - materialSearch: 1 - materialLocation: 1 - animations: - legacyGenerateAnimations: 4 - bakeSimulation: 0 - resampleCurves: 1 - optimizeGameObjects: 0 - removeConstantScaleCurves: 0 - motionNodeName: - rigImportErrors: - rigImportWarnings: - animationImportErrors: - animationImportWarnings: - animationRetargetingWarnings: - animationDoRetargetingWarnings: 0 - importAnimatedCustomProperties: 0 - importConstraints: 0 - animationCompression: 1 - animationRotationError: 0.5 - animationPositionError: 0.5 - animationScaleError: 0.5 - animationWrapMode: 0 - extraExposedTransformPaths: [] - extraUserProperties: [] - clipAnimations: [] - isReadable: 0 - meshes: - lODScreenPercentages: [] - globalScale: 1 - meshCompression: 0 - addColliders: 0 - useSRGBMaterialColor: 1 - sortHierarchyByName: 1 - importPhysicalCameras: 1 - importVisibility: 1 - importBlendShapes: 1 - importCameras: 1 - importLights: 1 - nodeNameCollisionStrategy: 1 - fileIdsGeneration: 2 - swapUVChannels: 0 - generateSecondaryUV: 0 - useFileUnits: 1 - keepQuads: 0 - weldVertices: 1 - bakeAxisConversion: 0 - preserveHierarchy: 0 - skinWeightsMode: 0 - maxBonesPerVertex: 4 - minBoneWeight: 0.001 - optimizeBones: 1 - meshOptimizationFlags: -1 - indexFormat: 0 - secondaryUVAngleDistortion: 8 - secondaryUVAreaDistortion: 15.000001 - secondaryUVHardAngle: 88 - secondaryUVMarginMethod: 1 - secondaryUVMinLightmapResolution: 40 - secondaryUVMinObjectScale: 1 - secondaryUVPackMargin: 4 - useFileScale: 1 - strictVertexDataChecks: 0 - tangentSpace: - normalSmoothAngle: 60 - normalImportMode: 0 - tangentImportMode: 3 - normalCalculationMode: 4 - legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 - blendShapeNormalImportMode: 1 - normalSmoothingSource: 0 - referencedClips: [] - importAnimation: 1 - humanDescription: - serializedVersion: 3 - human: [] - skeleton: [] - armTwist: 0.5 - foreArmTwist: 0.5 - upperLegTwist: 0.5 - legTwist: 0.5 - armStretch: 0.05 - legStretch: 0.05 - feetSpacing: 0 - globalScale: 1 - rootMotionBoneName: - hasTranslationDoF: 0 - hasExtraRoot: 0 - skeletonHasParents: 1 - lastHumanDescriptionAvatarSource: {instanceID: 0} - autoGenerateAvatarMappingIfUnspecified: 1 - animationType: 2 - humanoidOversampling: 1 - avatarSetup: 0 - addHumanoidExtraRootOnlyWhenUsingAvatar: 1 - importBlendShapeDeformPercent: 1 - remapMaterialsIfMaterialImportModeIsNone: 0 - additionalBone: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target.meta b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target.meta deleted file mode 100644 index ec7c8f7e4..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 59e0f5bbf1bfdda4c8efbb5aaf6b836f -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Hit Target/Hit Targets VPX.fbx deleted file mode 100644 index 820a2a1489e0884ca44fad63dda989694dd0fced..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93308 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 Tx0JIu5`iP&|%L^~=F0E=BglU9^TJ;GI9 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�i+@;@Jf#{xNRlM+*n=hmq<(sAEAwzuAw^pmp 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 Date: Mon, 2 Dec 2024 22:36:12 +0100 Subject: [PATCH 135/208] flipper: Fix position and rotation on import. --- .../VPT/Flipper/FlipperInspector.cs | 2 +- .../VPT/Flipper/FlipperComponent.cs | 24 ++++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs index a8c04f92e..dd191a9a3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperInspector.cs @@ -69,7 +69,7 @@ public override void OnInspectorGUI() // position EditorGUI.BeginChangeCheck(); - var newPos = EditorGUILayout.Vector2Field(new GUIContent("Position", "Position of the flipper on the playfield, relative to its parent."), MainComponent.Position); + var newPos = EditorGUILayout.Vector3Field(new GUIContent("Position", "Position of the flipper on the playfield, relative to its parent."), MainComponent.Position); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(MainComponent.transform, "Change Flipper Position"); MainComponent.Position = newPos; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index e53d2e099..b7ca64b5f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -43,20 +43,9 @@ public class FlipperComponent : MainRenderableComponent, { #region Data - public Vector2 Position { - get - { - var pos = transform.localPosition; - var posVpx = pos.TranslateToVpx(); - return new Vector2(posVpx.x, posVpx.y); - } - set - { - var posVpx = new Vector3(value.x, value.y, 0); - var pos = posVpx.TranslateToWorld(); - var t = transform; - t.localPosition = new Vector3(pos.x, t.localPosition.y, pos.z); - } + public Vector3 Position { + get => transform.localPosition.TranslateToVpx(); + set => transform.localPosition = value.TranslateToWorld(); } public float PosX => Position.x; @@ -195,7 +184,7 @@ public float RotateZ { public float2 RotatedPosition { get => new(Position.x, Position.y); set { - Position = value; + Position = new Vector3(value.x, value.y, Position.z); UpdateTransforms(); } } @@ -209,7 +198,8 @@ public override IEnumerable SetData(FlipperData data) var updatedComponents = new List { this }; // transforms - Position = data.Center.ToUnityVector2(); + Position = new Vector3(data.Center.X, data.Center.Y, 0); + transform.localEulerAngles = Vector3.zero; StartAngle = data.StartAngle > 180f ? data.StartAngle - 360f : data.StartAngle; // geometry @@ -270,7 +260,7 @@ public override FlipperData CopyDataTo(FlipperData data, string[] materialNames, { // name and transforms data.Name = name; - data.Center = Position.ToVertex2D(); + data.Center = new Vertex2D(Position.x, Position.y); data.StartAngle = StartAngle; // geometry From 8b44e726a7751dc083cf5bdbc69914efab81f37e Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 2 Dec 2024 23:28:35 +0100 Subject: [PATCH 136/208] import: Parent and fix z-position for items that have a surface set. --- .../Extensions/TransformExtensions.cs | 7 +++++++ .../VisualPinball.Unity/VPT/Bumper/BumperComponent.cs | 3 +++ .../VPT/Flipper/FlipperComponent.cs | 3 +++ .../VisualPinball.Unity/VPT/Gate/GateComponent.cs | 3 +++ .../VisualPinball.Unity/VPT/ISurfaceComponent.cs | 11 +++++++++++ .../VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta | 3 +++ .../VisualPinball.Unity/VPT/Kicker/KickerComponent.cs | 3 +++ .../VisualPinball.Unity/VPT/Light/LightComponent.cs | 3 +++ .../VPT/MainRenderableComponent.cs | 9 +++++++++ .../VPT/Plunger/PlungerComponent.cs | 3 +++ .../VisualPinball.Unity/VPT/Ramp/RampComponent.cs | 5 +++-- .../VPT/Spinner/SpinnerComponent.cs | 2 ++ .../VPT/Surface/SurfaceComponent.cs | 3 +-- .../VPT/Trigger/TriggerComponent.cs | 3 +++ 14 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs index 7d0562946..7b077917b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/TransformExtensions.cs @@ -35,6 +35,13 @@ public static void SetFromMatrix(this Transform tf, Matrix4x4 trs) ); } + public static void SetZPosition(this Transform transform, float pos) + { + var position = transform.position; + position.y = Physics.ScaleToWorld(pos); // we're in z here + transform.position = position; + } + public static void SetLocalYRotation(this Transform transform, float angleRad) { var localToWorldMatrix = transform.localToWorldMatrix; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 5e0de1d18..0f2835494 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -205,6 +205,9 @@ public override IEnumerable SetData(BumperData data) public override IEnumerable SetReferencedData(BumperData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + UpdateTransforms(); // children visibility diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index b7ca64b5f..6689835e1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -238,6 +238,9 @@ public override IEnumerable SetData(FlipperData data) public override IEnumerable SetReferencedData(FlipperData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + UpdateTransforms(); // children mesh creation and visibility diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 83fd25aaa..4d1e04ef3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -202,6 +202,9 @@ public override IEnumerable SetData(GateData data) public override IEnumerable SetReferencedData(GateData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + // visibility foreach (var mf in GetComponentsInChildren()) { switch (mf.gameObject.name) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs new file mode 100644 index 000000000..67c31d768 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs @@ -0,0 +1,11 @@ +using UnityEngine; + +namespace VisualPinball.Unity +{ + public interface ISurfaceComponent + { + float Height(Vector2 position); + + Transform transform { get; } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta new file mode 100644 index 000000000..b42fdfcec --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f9a1b783974a4687a2f4b7f39fcd5ad9 +timeCreated: 1733175577 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index c94b7e9a4..6fa69dc43 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -206,6 +206,9 @@ public override IEnumerable SetData(KickerData data) public override IEnumerable SetReferencedData(KickerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + return Array.Empty(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 21e2b0d10..a378cfb8b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -379,6 +379,9 @@ public override IEnumerable SetData(LightData data) public override IEnumerable SetReferencedData(LightData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + // visibility if (!data.ShowBulbMesh) { foreach (var mf in GetComponentsInChildren()) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 80e123930..2dbe29ce1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -94,6 +94,15 @@ public UnityEngine.Mesh GetUnityMesh() public virtual void OnPlayfieldHeightUpdated() => UpdateTransforms(); + protected void ParentToSurface(string surfaceName, Vertex2D center, Dictionary components) + { + if (!string.IsNullOrEmpty(surfaceName)) { + var surface = FindComponent(components, surfaceName); + transform.SetZPosition(surface.Height(center.ToUnityVector2())); + transform.SetParent(surface.transform, true); + } + } + public virtual void UpdateTransforms() { foreach (var colliderComponent in ColliderComponents) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index bd898a3ee..fc280738f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -183,6 +183,9 @@ public override IEnumerable SetData(PlungerData data) public override IEnumerable SetReferencedData(PlungerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + // rod mesh var rodMesh = GetComponentInChildren(true); if (rodMesh) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 7e7882e65..366fa2052 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -36,7 +36,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Ramp")] - public class RampComponent : MainRenderableComponent, IRampData + public class RampComponent : MainRenderableComponent, IRampData, ISurfaceComponent { #region Data @@ -149,7 +149,8 @@ private void Start() public float Height(Vector2 pos) { var vVertex = new RampMeshGenerator(this).GetCentralCurve(); - Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x, pos.y), false, out var vOut, out var iSeg); + var t = transform.localPosition.TranslateToVpx(); + Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x - t.x, pos.y - t.y), false, out var vOut, out var iSeg); if (iSeg == -1) { return 0.0f; // Object is not on ramp path diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 6299ef1e8..7bec17087 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -203,6 +203,8 @@ public override IEnumerable SetData(SpinnerData data) public override IEnumerable SetReferencedData(SpinnerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); return Array.Empty(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 9ea69f18d..a714a8bdb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -34,7 +34,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Surface")] - public class SurfaceComponent : MainRenderableComponent + public class SurfaceComponent : MainRenderableComponent, ISurfaceComponent { #region Data @@ -59,7 +59,6 @@ public class SurfaceComponent : MainRenderableComponent public override bool HasProceduralMesh => true; - protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 149614850..beafd48a4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -178,6 +178,9 @@ public override IEnumerable SetData(TriggerData data) public override IEnumerable SetReferencedData(TriggerData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components) { + // surface + ParentToSurface(data.Surface, data.Center, components); + // mesh var meshComponent = GetComponent(); if (meshComponent) { From d120fb7a8a67b86e9033d1b32020e3ac081118ca Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 3 Dec 2024 00:00:02 +0100 Subject: [PATCH 137/208] kicker: Fix import. --- .../Import/VpxSceneConverter.cs | 5 --- .../VPT/Kicker/KickerComponent.cs | 32 ------------------- 2 files changed, 37 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index fe26430bf..5c7f817df 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -348,11 +348,6 @@ private IVpxPrefab InstantiateAndParentPrefab(IItem item) var parentGo = GetGroupParent(item); prefab.GameObject.transform.SetParent(parentGo.transform, false); - // apply transformation - if (item is IRenderable renderable) { - // todo can probably remove that, it's in setData already.. - prefab.GameObject.transform.SetFromMatrix(renderable.TransformationMatrix(_sourceTable, Origin.Original).ToUnityMatrix()); - } return prefab; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index 6fa69dc43..c37de0597 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -116,38 +116,6 @@ public float Radius { #region Transformation - public override void UpdateTransforms() - { - base.UpdateTransforms(); - var t = transform; - - // todo move this to import - if (KickerType == Engine.VPT.KickerType.KickerCup) { - t.localPosition += Physics.TranslateToWorld(0, 0, -0.18f * Radius); - } - - // scale - t.localScale = KickerType == Engine.VPT.KickerType.KickerInvisible - ? Vector3.one - : Physics.ScaleToWorld(Radius, Radius, Radius); - - switch (KickerType) { - // rotation - case Engine.VPT.KickerType.KickerCup: - t.localEulerAngles = Physics.RotateToWorld(0, 0, Orientation); - break; - case Engine.VPT.KickerType.KickerWilliams: - t.localEulerAngles = Physics.RotateToWorld(0, 0, Orientation + 90f); - break; - case Engine.VPT.KickerType.KickerInvisible: - t.localRotation = Quaternion.identity; - break; - default: - t.localEulerAngles = Physics.RotateToWorld(0, 0, Orientation); - break; - } - } - private float _originalRotationZ; private float _originalKickerAngle; From bc1a87c569477f7f8815a68c9c62283ef0fd3724 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 5 Dec 2024 21:16:09 +0100 Subject: [PATCH 138/208] debug: Throw better exceptions. --- VisualPinball.Engine/VPT/Gate/Gate.cs | 2 ++ .../VisualPinball.Unity.Editor/Import/VpxPrefab.cs | 5 +++++ .../VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/VisualPinball.Engine/VPT/Gate/Gate.cs b/VisualPinball.Engine/VPT/Gate/Gate.cs index 3344cebab..fcb273be9 100644 --- a/VisualPinball.Engine/VPT/Gate/Gate.cs +++ b/VisualPinball.Engine/VPT/Gate/Gate.cs @@ -51,5 +51,7 @@ public PbrMaterial GetMaterial(string id, Table.Table table) => _meshGenerator.GetMaterial(id, table); #endregion + + public override string ToString() => $"Gate[{Data.Name}/{Data.GateType} at {Data.Center}]"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs index 6d3730a0a..ee9c4386e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.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 System; using System.Collections.Generic; using System.Linq; using UnityEditor; @@ -42,6 +43,10 @@ internal class VpxPrefab : IVpxPrefab public VpxPrefab(Object prefab, TItem item) { _item = item; + if (!prefab) { + throw new Exception($"Could not instantiate prefab for item {item} of type {item.GetType()}."); + } + GameObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject; GameObject!.name = item.Name; _mainComponent = GameObject.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index 5c7f817df..6c6d9b250 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -340,6 +340,10 @@ private IVpxPrefab InstantiateAndParentPrefab(IItem item) { var prefab = InstantiatePrefab(item); + if (prefab == null) { + throw new Exception($"Could not instantiate prefab for item {item.Name} of type {item.GetType()}."); + } + if (prefab.SkipParenting) { return prefab; } From 5fb0fd68244d7c5000be0b0fefa4137f83abbd78 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 5 Dec 2024 21:17:21 +0100 Subject: [PATCH 139/208] cleanup: Delete bumper and spinner meshes that are now in the asset lib. --- .../Assets/Art/Meshes/Bumper.meta | 8 - .../Assets/Art/Meshes/Spinner.meta | 8 - .../Art/Meshes/Spinner/Spinner (Bracket).mesh | 166 ------------------ .../Spinner/Spinner (Bracket).mesh.meta | 8 - .../Art/Meshes/Spinner/Spinner (Plate).mesh | 166 ------------------ .../Meshes/Spinner/Spinner (Plate).mesh.meta | 8 - .../Assets/Art/Meshes/Spinner/Spinner VPX.fbx | Bin 26700 -> 0 bytes .../Art/Meshes/Spinner/Spinner VPX.fbx.meta | 109 ------------ 8 files changed, 473 deletions(-) delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Bumper.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Bumper.meta b/VisualPinball.Unity/Assets/Art/Meshes/Bumper.meta deleted file mode 100644 index 41b8703d0..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Bumper.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 45f425f274ce4944d91a653eb54e0579 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner.meta b/VisualPinball.Unity/Assets/Art/Meshes/Spinner.meta deleted file mode 100644 index e190b35a3..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Spinner.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 36cdd94d888585c4188327d5a862ece5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh deleted file mode 100644 index 5620cafae..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Spinner (Bracket) - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 420 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 152 - localAABB: - m_Center: {x: 0, y: 0, z: 0.2030145} - m_Extent: {x: 0.967074, y: 0.096001, z: 0.2731735} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 0000010002000100030002000400050006000600070004001100060005000500100011001600110010001000170016002600160017001700270026002e002600270027002f002e003d002e002f002f003c003d003c005f0058003c0058004e003c004e0047003c0047003d003d0047005c003d005c00460045003d004600080009000a0009000b000a000900080013001200130008000c000d000e000e000f000c0014000c000f000f0015001400180019001a00190029001a0028001a00290029003100280018001a001e003000280031001e001f0018003100380030001e0062001f003900300038001e006600620041003900380066001e00670038004200410067006e0066004100420050006e0067006f004100500052006f0076006e0050005100520076006f007700510054005200760077007f005100530054007f00800076005300560054008a0080007f0056005300570088008a007f00570053005900880089008a0059005b00570088008b0089005b00590055008b008c008900550044005b008b008f008c005500430044008e008c008f0043003b00440091008c008e003a0044003b008e009300910033003a003b008d00910093003b0032003300930081008d002b0033003200810082008d0032002a002b008100780082001b002b002a007800810079002a001c001b007800790071001b001c001d0071007000780020001b001d007000710069001d002100200069006800700068006900200021006300200020006300680022002300240025002200240023006a00240025002d0022006a0023006b002c0022002d006a006b00720034002c002d00720073006a002d00350034007200740073003500360034007400720075003700340036009500960074008700950074007e008700740075007e00740092007e0075007c00920075007c0075007d003e0037003f0037005a003f00370040005a0036004000370036004f00400036005d004f0036005e005d00480049004a004a004b004800490048004c004c004d0049004d004c006000600061004d00610060006400640065006100650064006c006c006d0065006d006c007a007a007b006d0083007a008400830094007a00940085007a007a0085007b00850086007b00860090007b00900097007b00 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 152 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 4864 - _typelessdata: 488c023f2cb90a3ceacef33e00000080000000000000803f0000000000000080488c02bffab40a3cc8cef33e00000080000000000000803f0000000000000080488c023ffab40abceacef33e00000080000000000000803f0000000000000080488c02bf2cb90abcc8cef33e00000080000000000000803f0000000000000080488c02bffab40a3cc8cef33e4ed191bd00000000295c7f3f0000000000000080f0520ebf211e093d9b20f23ece88d2bd00000000a9a47e3f0000000000000080f0520ebf2e1f09bd9b20f23ece88d2bd00000000a9a47e3f0000000000000080488c02bf2cb90abcc8cef33e4ed191bd00000000295c7f3f0000000000000080f0520e3f2e1f093dbc20f23e0000008080b7c03da4df7e3f0000000000000080f0520ebf211e093d9b20f23e0000008080b7c03da4df7e3f0000000000000080488c023f2cb90a3ceacef33e00000080ef38053ef2d27d3f0000000000000080488c02bffab40a3cc8cef33e00000080ef38053ef2d27d3f0000000000000080f0520e3f211e09bdbc20f23e0000008080b7c0bda4df7e3f0000000000000080488c023ffab40abceacef33e00000080ef3805bef2d27d3f0000000000000080488c02bf2cb90abcc8cef33e00000080ef3805bef2d27d3f0000000000000080f0520ebf2e1f09bd9b20f23e0000008080b7c0bda4df7e3f000000000000008098191abfa69bc43d855eef3e27a009bd00000000aed87f3f000000000000008098191abfa69bc4bd855eef3e27a009bd00000000aed87f3f000000000000008098191a3fa69bc43d855eef3e00000080e9b7af3d840d7f3f000000000000008098191abfa69bc43d855eef3e00000080e9b7af3d840d7f3f000000000000008098191a3fa69bc4bd855eef3e00000080e9b7afbd840d7f3f000000000000008098191abfa69bc4bd855eef3e00000080e9b7afbd840d7f3f00000000000000807f1520bfa69bc4bd855eef3e711bcdbe00000000a3926a3f00000000000000807f1520bfa69bc43d855eef3e711bcdbe00000000a3926a3f000000000000008098191abfa69bc4bd855eef3e00000000000080bf0000000000000000000000807f1520bfa69bc4bd855eef3e00000000000080bf000000000000000000000080410b1dbfa69bc4bd07b6e23e00000000000080bf000000000000000000000080410b1dbfa69bc43d07b6e23e000000800000803f0000000000000000000000807f1520bfa69bc43d855eef3e000000800000803f00000000000000000000008098191abfa69bc43d855eef3e000000800000803f000000000000000000000080410b1d3fa69bc4bd28b6e23e00000000000080bf00000000000000000000008098191a3fa69bc4bd855eef3e00000000000080bf000000000000000000000080410b1d3fa69bc43d28b6e23e000000800000803f00000000000000000000008098191a3fa69bc43d855eef3e000000800000803f000000000000000000000080410b1dbfa69bc43d07b6e23ea69bc43e00000000645d6cbf0000000000000080410b1d3fa69bc43d28b6e23ea69bc4be00000000645d6cbf0000000000000080410b1d3fa69bc4bd28b6e23ea69bc4be00000000645d6cbf0000000000000080410b1dbfa69bc4bd07b6e23ea69bc43e00000000645d6cbf00000000000000806ff025bfa69bc4bd07b6e23e925c6ebf00000080f5b9ba3e00000000000000806ff025bfa69bc43d07b6e23e925c6ebf00000080f5b9ba3e00000000000000807f1520bfa69bc4bd8198dc3e00000000000080bf0000000000000000000000806ff025bfa69bc4bd07b6e23e00000000000080bf0000000000000000000000806ff025bfa69bc43d07b6e23e000000800000803f0000000000000000000000807f1520bfa69bc43d8198dc3e000000800000803f0000000000000000000000807f1520bfa69bc43d8198dc3e7aa56c3f00000000c139c3be00000000000000807f1520bfa69bc4bd8198dc3e7aa56c3f00000000c139c3be00000000000000806ff025bfa69bc4bdcf2c09bd1ac06bbf00000080c286c73e00000000000000806ff025bfa69bc43dcf2c09bd1ac06bbf00000080c286c73e00000000000000807f1520bfa69bc4bd191b3abd00000000000080bf0000000000000000000000806ff025bfa69bc4bdcf2c09bd00000000000080bf0000000000000000000000806ff025bfa69bc43dcf2c09bd000000800000803f0000000000000000000000807f1520bfa69bc43d191b3abd000000800000803f0000000000000000000000807f1520bfa69bc43d191b3abd925c6e3f00000000f5b9babe00000000000000807f1520bfa69bc4bd191b3abd925c6e3f00000000f5b9babe00000000000000806ff025bfa69bc4bd86af8fbd711bcd3e00000000a3926abf00000000000000806ff025bfa69bc43d86af8fbd711bcd3e00000000a3926abf0000000000000080a41729bfa69bc4bd191b3abd00000000000080bf0000000000000000000000806ff025bfa69bc4bd86af8fbd00000000000080bf0000000000000000000000806ff025bfa69bc43d86af8fbd000000800000803f000000000000000000000080a41729bfa69bc43d191b3abd000000800000803f000000000000000000000080a41729bfa69bc43d191b3abda54ec0be00000080c4426d3f0000000000000080a41729bfa69bc4bd191b3abda54ec0be00000080c4426d3f0000000000000080bccc70bf209bc43d86af8fbd0000000000000080000080bf0000000000000080225574bf4a0d8d3d86af8fbd0000000000000080000080bf0000000000000080299277bfbd3786b586af8fbd0000000000000080000080bf0000000000000080bccc70bf2c9cc4bd86af8fbd39d605bf713d5abf000000800000000000000080bccc70bf2c9cc4bd191b3abd39d605bf713d5abf000000800000000000000080bccc70bf209bc43d191b3abd39d605bf713d5a3f000000800000000000000080bccc70bf209bc43d86af8fbd39d605bf713d5a3f000000800000000000000080bccc70bf2c9cc4bd191b3abd00000080000000000000803f0000000000000080225574bfd00d8dbd191b3abd00000080000000000000803f0000000000000080299277bfbd3786b5191b3abd00000080000000000000803f0000000000000080f0520e3f211e09bdbc20f23ece88d23d00000000a9a47e3f0000000000000080f0520e3f2e1f093dbc20f23ece88d23d00000000a9a47e3f0000000000000080488c023f2cb90a3ceacef33e4ed1913d00000000295c7f3f0000000000000080488c023ffab40abceacef33e4ed1913d00000000295c7f3f000000000000008098191a3fa69bc4bd855eef3e27a0093d00000000aed87f3f000000000000008098191a3fa69bc43d855eef3e27a0093d00000000aed87f3f000000000000008010b076bf3563113d191b3abd00000080000000000000803f000000000000008010b076bf416411bd86af8fbd0000000000000080000080bf0000000000000080225574bfd00d8dbd191b3abdc4b16ebf6c09b9be00000080000000000000008010b076bf416411bd191b3abd8cb97bbffe433abe000000800000000000000080225574bfd00d8dbd86af8fbdc4b16ebf6c09b9be000000800000000000000080299277bfbd3786b5191b3abd000080bf0000000000000080000000000000008010b076bf416411bd86af8fbd8cb97bbffe433abe000000800000000000000080225574bf4a0d8d3d191b3abdc4b16ebf6c09b93e000000800000000000000080299277bfbd3786b586af8fbd000080bf0000000000000080000000000000008010b076bf3563113d86af8fbd8cb97bbffe433a3e000000800000000000000080225574bf4a0d8d3d191b3abd00000080000000000000803f000000000000008010b076bf3563113d191b3abd8cb97bbffe433a3e00000080000000000000008010b076bf3563113d86af8fbd0000000000000080000080bf0000000000000080225574bf4a0d8d3d86af8fbdc4b16ebf6c09b93e00000080000000000000008010b076bf416411bd191b3abd00000080000000000000803f0000000000000080225574bfd00d8dbd86af8fbd0000000000000080000080bf0000000000000080bccc70bf2c9cc4bd86af8fbd0000000000000080000080bf0000000000000080bccc70bf209bc43d191b3abd00000080000000000000803f00000000000000807f15203fa69bc4bd855eef3e711bcd3e00000000a3926a3f00000000000000807f15203fa69bc43d855eef3e711bcd3e00000000a3926a3f00000000000000807f15203fa69bc4bd855eef3e00000000000080bf0000000000000000000000807f15203fa69bc43d855eef3e000000800000803f0000000000000000000000806ff0253fa69bc4bd28b6e23e925c6e3f00000000f5b9ba3e00000000000000806ff0253fa69bc43d28b6e23e925c6e3f00000000f5b9ba3e00000000000000806ff0253fa69bc4bd28b6e23e00000000000080bf0000000000000000000000807f15203fa69bc4bda298dc3e00000000000080bf0000000000000000000000806ff0253fa69bc43d28b6e23e000000800000803f0000000000000000000000807f15203fa69bc43da298dc3e000000800000803f0000000000000000000000807f15203fa69bc4bda298dc3e7aa56cbf00000080c139c3be00000000000000807f15203fa69bc43da298dc3e7aa56cbf00000080c139c3be00000000000000806ff0253fa69bc4bdcf2c09bd1ac06b3f00000000c286c73e00000000000000806ff0253fa69bc43dcf2c09bd1ac06b3f00000000c286c73e00000000000000806ff0253fa69bc4bdcf2c09bd00000000000080bf0000000000000000000000807f15203fa69bc4bd0d1a3abd00000000000080bf0000000000000000000000806ff0253fa69bc43dcf2c09bd000000800000803f0000000000000000000000807f15203fa69bc43d0d1a3abd000000800000803f0000000000000000000000807f15203fa69bc43d0d1a3abd925c6ebf00000080f5b9babe00000000000000807f15203fa69bc4bd0d1a3abd925c6ebf00000080f5b9babe00000000000000806ff0253fa69bc4bdffae8fbd711bcdbe00000080a3926abf00000000000000806ff0253fa69bc43dffae8fbd711bcdbe00000080a3926abf0000000000000080a417293fa69bc4bd0d1a3abd00000000000080bf0000000000000000000000806ff0253fa69bc4bdffae8fbd00000000000080bf000000000000000000000080a417293fa69bc43d0d1a3abd000000800000803f0000000000000000000000806ff0253fa69bc43dffae8fbd000000800000803f000000000000000000000080a417293fa69bc4bd0d1a3abda54ec03e00000000c4426d3f0000000000000080a417293fa69bc43d0d1a3abda54ec03e00000000c4426d3f00000000000000802255743fd00d8d3dffae8fbd0000000000000080000080bf0000000000000080bccc703f2c9cc43dffae8fbd0000000000000080000080bf00000000000000802992773fbd378635ffae8fbd0000000000000080000080bf0000000000000080bccc703f209bc4bdffae8fbd39d6053f713d5abf000000000000000000000080bccc703f209bc4bd0d1a3abd39d6053f713d5abf000000000000000000000080bccc703f2c9cc43dffae8fbd39d6053f713d5a3f000000000000000000000080bccc703f2c9cc43d0d1a3abd39d6053f713d5a3f0000000000000000000000802255743f4a0d8dbd0d1a3abd00000080000000000000803f0000000000000080bccc703f209bc4bd0d1a3abd00000080000000000000803f00000000000000802992773fbd3786350d1a3abd00000080000000000000803f000000000000008010b0763f4164113d0d1a3abd00000080000000000000803f000000000000008010b0763f356311bdffae8fbd0000000000000080000080bf00000000000000802255743f4a0d8dbdffae8fbdc4b16e3f6c09b9be00000000000000000000008010b0763f356311bd0d1a3abd8cb97b3ffe433abe0000000000000000000000802255743f4a0d8dbd0d1a3abdc4b16e3f6c09b9be00000000000000000000008010b0763f356311bdffae8fbd8cb97b3ffe433abe0000000000000000000000802992773fbd3786350d1a3abd0000803f000000000000000000000000000000802255743fd00d8d3d0d1a3abdc4b16e3f6c09b93e00000000000000000000008010b0763f4164113dffae8fbd8cb97b3ffe433a3e0000000000000000000000802992773fbd378635ffae8fbd0000803f000000000000000000000000000000802255743fd00d8d3d0d1a3abd00000080000000000000803f000000000000008010b0763f4164113d0d1a3abd8cb97b3ffe433a3e00000000000000000000008010b0763f4164113dffae8fbd0000000000000080000080bf00000000000000802255743fd00d8d3dffae8fbdc4b16e3f6c09b93e00000000000000000000008010b0763f356311bd0d1a3abd00000080000000000000803f00000000000000802255743f4a0d8dbdffae8fbd0000000000000080000080bf0000000000000080bccc703f209bc4bdffae8fbd0000000000000080000080bf0000000000000080bccc703f2c9cc43d0d1a3abd00000080000000000000803f0000000000000080 - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: 0.2030145} - m_Extent: {x: 0.967074, y: 0.096001, z: 0.2731735} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh.meta deleted file mode 100644 index 933068619..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Bracket).mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 615c6af8e30958e4082cf6806a26da35 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh deleted file mode 100644 index f70fc2207..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Spinner (Plate) - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 912 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 228 - localAABB: - m_Center: {x: -0.00097599626, y: 0, z: -0.0000004917383} - m_Extent: {x: 0.69945896, y: 0.027512, z: 0.2812605} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 0a0009000b00080009000a0008000a001c0008001c001d001d001c001e001d001e001f001f001e0020001f002000210021002000030021000300020003000000020000000100020000001400010001001400150014001200150015001200130012001000130013001000110010000f00110011000f000c000c000f000d000c000d000e000e000d0016000e0016001700170016001800170018001900190018001a0019001a001b001b001a0005001b00050004000400050006000700040006000600280007000700280029002800260029002900260027002600220027002700220024002200230024002400230025002a002b002c002b002a002d002b0030002c002b002d002e0030002b0031002b002e002f0031002b0032002b002f003e0032002b0033003d002b003e002b00340033003c002b003d0034002b0035003b002b003c0035002b0036003a002b003b0036002b00370039002b003a0037002b00380038002b0039003f00400041003f00450040004200410040004600400045004300420040004700400046004400430040004800400047005300440040004800490040005300400052004a00400049005200400051004b0040004a005100400050004c0040004b00500040004f004d0040004c004f0040004e004e0040004d00540055005600780054005600560055006400560079007800790056006400540078007a0081007800790083007a0078007800810083007a005900540079008200810059007a007b0082007900800064008000790084007b007a007a00830084007b008400850085007c007b009700850084005a007b007c007b005a0059007c005d005a005d005c005a00840096009700960084008300b200970096008300950096009500830081009600b100b200b10096009500bb00b200b1008100930095009300810082009500b000b100b00095009300b100ba00bb00ba00b100b000c400bb00ba00ba00c300c400c300ba00b900b000b900ba00b900c200c300b900b000ae009300ae00b000c200b900b700ae00b700b900b700c000c200ae0093009400820094009300b700ae00af009400af00ae00c000b700b800af00b800b700b800c100c000940082008900800089008200c100b800bf00bf00c800c100b800af00b600b600bf00b800af0094009b009b00b600af0089009b009400c800bf00be00be00c700c800bf00b600b500b500be00bf00b6009b009a009a00b500b600c700be00bc00bc00c500c700bd00c600c500c500bc00bd00b400bd00bc00b300bc00be00bc00b300b400be00b500b3009900b400b3009800b300b500b30098009900b5009a009800870099009800980086008700860098009a007e00870086009a008800860088009a009b009b008900880086007d007e007d008600880060007e007d007f008800890088007f007d00890080007f007d005e0060005e007d007f005e005f00600063007f0080007f0063005e008000640063005e0061005f006b005f006100630062005e00620061005e0064006200630061006c006b006c006100620074006b006c0064006500620055006500640062006d006c006d00620065006c007500740075006c006d008f007400750065006e006d006e00650055006d007600750076006d006e00750090008f00900075007600a1008f0090006e00770076009000a200a100aa00a100a200760091009000a20090009100910076007700a200ab00aa00ce00aa00ab00ab00cf00ce009100a300a200ab00a200a300cf00ab00ac00a300ac00ab00ac00d000cf00a30091009200770092009100ac00a300a4009200a400a300d000ac00ad00a400ad00ac00ad00d100d000a40092008a00d100ad00a500a500c900d100ad00a4009c009c00a500ad008a009c00a400c900a500a600a600ca00c900a5009c009d009d00a600a500ca00a600a700a700cb00ca00a6009d009e009e00a700a6008b009d009c009c008a008b008c009e009d009d008b008c00cb00a700a800a800cc00cb00a900cd00cc00cc00a800a900a000a900a8009f00a800a700a8009f00a000a7009e009f008e00a0009f008d009f009e009f008d008e009e008c008d0073008e008d008d007200730072008d008c006a00730072008c007100720071008c008b00720069006a006900720071005c006a0069008b007000710070008b008a0069005b005c005c005b005a007100680069005b00690068006800710070005a005b005800680058005b005a0058005900590058005400700067006800580068006700580057005400670057005800540057005500670070006f008a006f0070006f008a009200920077006f005700670066006f006600670066005500570066006f007700550066006e0077006e006600d200d300d400d200da00d300d200d400d500d200d900da00d200d500d600d200d800d900d200d600d700d200d700d800db00dc00dd00db00dd00e300db00de00dc00db00e300e200db00df00de00db00e200e100db00e000df00db00e100e000 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 228 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 7296 - _typelessdata: f69bb93ed960e13cb29b593ee0be7e3f00000000f085c93dec51743fcd560abff69bb93ec05ee1bcb29b593ee0be7e3f00000000f085c93dec517c3fcd560abf459db93ec05ee1bc399c59bee0be7e3f000000005ebac9bdec517c3f569c3ebf459db93ed960e13c399c59bee0be7e3f000000005ebac9bdec51743f569c3ebf459db9bed960e1bc399c593ee0be7ebf000000805ebac93dec517c3fb96e7abe459db9bec05ee13c399c593ee0be7ebf000000805ebac93dec51743fb96e7abe249db9bec05ee13c7c9c59bee0be7ebf00000080cceec9bdec51743f4e6125bd249db9bed960e1bc7c9c59bee0be7ebf00000080cceec9bdec517c3f4e6125bd126a963ec05ee1bc500190be8351c93d000000006dc57ebfec517c3fa7e848bff06996bed960e1bc710190be8351c9bd000000806dc57ebfec517c3f000080bf126a963ed960e13c500190be8351c93d000000006dc57ebfec51743fa7e848bff06996bec05ee13c710190be8351c9bd000000806dc57ebfec51743f000080bfc268963ec05ee1bc0c01903e151dc93d000000006dc57e3fec517c3f580300bf126a96bec05ee13c5001903e8351c9bd000000806dc57e3fec51743f0fd491be126a96bed960e1bc5001903e8351c9bd000000806dc57e3fec517c3f0fd491bec268963ed960e13c0c01903e151dc93d000000006dc57e3fec51743f580300bfc6dea33ed960e13c3b528d3e780bc43e00000000287e6c3fec51743f039402bfc6dea33ec05ee1bc3b528d3e780bc43e00000000287e6c3fec517c3f039402bf344aaf3ed960e13cdeb0853e8104353f000000008104353fec51743ff83605bf344aaf3ec05ee1bcdeb0853e8104353f000000008104353fec517c3ff83605bf92ebb63ed960e13c9c8a743e9a776c3f000000009318c43eec51743fdac607bfb3ebb63ec05ee1bc9c8a743e9a776c3f000000009318c43eec517c3fdac607bfe8dea3bec05ee13cf8518d3e9318c4be00000080287e6c3fec51743f3eb38cbee8dea3bed960e1bcf8518d3e9318c4be00000080287e6c3fec517c3f3eb38cbe564aafbec05ee13c9ab0853e810435bf000000808104353fec51743fbf9e87be564aafbed960e1bc9ab0853e810435bf000000808104353fec517c3fbf9e87beb3ebb6bec05ee13c598a743e9a776cbf00000080ca32c43eec51743f215782beb3ebb6bed960e1bc598a743e9a776cbf00000080ca32c43eec517c3f215782bee8dea33ed960e13cf8518dbe9318c43e00000000287e6cbfec51743f3f5846bfe8dea33ec05ee1bcf8518dbe9318c43e00000000287e6cbfec517c3f3f5846bf564aaf3ed960e13c9ab085be8104353f00000000810435bfec51743ff6cf43bf564aaf3ec05ee1bc9ab085be8104353f00000000810435bfec517c3ff6cf43bfb3ebb63ed960e13c598a74be9a776c3f00000000ca32c4beec51743f382c41bfb3ebb63ec05ee1bc598a74be9a776c3f00000000ca32c4beec517c3f382c41bfc6dea3bec05ee13c3b528dbe780bc4be00000080287e6cbfec51743f261a24bcf06996bec05ee13c710190be8351c9bd000000806dc57ebfec51743f00000080c6dea3bed960e1bc3b528dbe780bc4be00000080287e6cbfec517c3f261a24bcf06996bed960e1bc710190be8351c9bd000000806dc57ebfec517c3f00000080344aafbec05ee13cbcb085be810435bf00000080810435bfec51743f04caa6bc344aafbed960e1bcbcb085be810435bf00000080810435bfec517c3f04caa6bcb3ebb6bec05ee13c9c8a74be9a776cbf00000080ca32c4beec51743f50c6f8bc92ebb6bed960e1bc9c8a74be9a776cbf00000080ca32c4beec517c3f50c6f8bcc6dea33ed960e13c3b528d3e000000800000803f000000003810463f66f764bc00000080c05ee13c00000080000000800000803f000000000000003fbdff7fbec268963ed960e13c0c01903e000000800000803f00000000e84e403fb98c1bbc344aaf3ed960e13cdeb0853e000000800000803f0000000030f24a3fb5dfdabc92ebb63ed960e13c9c8a743e000000800000803f0000000061354e3f588f3bbdf69bb93ed960e13cb29b593e000000800000803f00000000a35b4f3ff5d78bbd126a96bec05ee13c5001903e000000800000803f000000008bc27e3e24801bbce8dea3bec05ee13cf8518d3e000000800000803f000000001fbf673e66f764bc564aafbec05ee13c9ab0853e000000800000803f000000003f37543eb5dfdabcb3ebb6bec05ee13c598a743e000000800000803f000000007c2a473e588f3bbd459db9bec05ee13c399c593e000000800000803f000000005c8f423e62d68bbd249db9bec05ee13c7c9c59be000000800000803f000000005c8f423e460addbeb3ebb6bec05ee13c9c8a74be000000800000803f000000007c2a473ef38de8be344aafbec05ee13cbcb085be000000800000803f000000003f37543ec251f2bec6dea3bec05ee13c3b528dbe000000800000803f000000001fbf673e23d8f8bef06996bec05ee13c710190be000000800000803f000000008bc27e3edd23fbbe126a963ed960e13c500190be000000800000803f000000005d4f403fdd23fbbee8dea33ed960e13cf8518dbe000000800000803f000000003810463f23d8f8be564aaf3ed960e13c9ab085be000000800000803f0000000030f24a3fc251f2beb3ebb63ed960e13c598a74be000000800000803f0000000061354e3ff38de8be459db93ed960e13c399c59be000000800000803f00000000295c4f3f460addbec268963ec05ee1bc0c01903e00000000000080bf0000000061c47e3e446e02bf00000000c05ee1bc0000000000000000000080bf000000000000003f000040bfc6dea33ec05ee1bc3b528d3e00000000000080bf000000001fbf673ede9303bf344aaf3ec05ee1bcdeb0853e00000000000080bf000000003f37543e0ed706bfb3ebb63ec05ee1bc9c8a743e00000000000080bf000000007c2a473e06b90bbff69bb93ec05ee1bcb29b593e00000000000080bf000000007591423eff7a11bf126a96bed960e1bc5001903e00000000000080bf000000005d4f403f116e02bfe8dea3bed960e1bcf8518d3e00000000000080bf000000003810463fde9303bf564aafbed960e1bc9ab0853e00000000000080bf0000000030f24a3f0ed706bfb3ebb6bed960e1bc598a743e00000000000080bf0000000061354e3f06b90bbf459db9bed960e1bc399c593e00000000000080bf00000000295c4f3fcc7a11bf249db9bed960e1bc7c9c59be00000000000080bf00000000295c4f3f34856ebf92ebb6bed960e1bc9c8a74be00000000000080bf0000000061354e3ffa4674bf344aafbed960e1bcbcb085be00000000000080bf0000000030f24a3ff22879bfc6dea3bed960e1bc3b528dbe00000000000080bf000000003810463f126c7cbff06996bed960e1bc710190be00000000000080bf000000005d4f403fef917dbf126a963ec05ee1bc500190be00000000000080bf000000008bc27e3eef917dbfe8dea33ec05ee1bcf8518dbe00000000000080bf000000001fbf673e126c7cbf564aaf3ec05ee1bc9ab085be00000000000080bf000000003f37543ef22879bfb3ebb63ec05ee1bc598a74be00000000000080bf000000007c2a473efa4674bf459db93ec05ee1bc399c59be00000000000080bf000000005c8f423e34856ebf3333f33eaaee113cc45b87bdaaf1d2bdec51383fceaa2f3f508db73d399da4be3333f3bebd3786b56f9d7fbd280f0b3ee02d903b849e7d3f508d973d64b12dbf3333f33ebd378635569b7fbd280f0bbee02d90bb849e7d3f508d973d399da4be3333f3be47e6113cd15c87bdca32c43dd044383f0000303f508db73d64b12dbf3333f3be425b4e3c209a99bdfaed6bbc92cb7f3f2bf617bd508dd73d64b12dbf3333f33e745f4e3c139999bddaac7a3c05c57f3f0ad723bd508dd73d399da4be3333f33eaaee113c62d6abbdb7d1003e89d22e3f273138bf508df73d399da4be3333f3be47e6113c6fd7abbdff21fdbd89d22e3f5e4b38bf508df73d64b12dbf3333f3bebd3786b58865b3bdd50928be52491dbab6847cbfa8c60b3e64b12dbf3333f33ebd3786357c64b3bdd509283e52491d3ab6847cbfa8c60b3e399da4be3333f33e47e611bc62d6abbdff21fd3d89d22ebf5e4b38bf3f35de3c399da4be3333f3bebd3786b58865b3bdd50928be52491dbab6847cbf7f6a3c3c64b12dbf3333f33ebd3786357c64b3bdd509283e52491d3ab6847cbf7f6a3c3c399da4be3333f3beaaee11bc6fd7abbdb7d100be89d22ebf273138bf3f35de3c64b12dbf3333f3be745f4ebc209a99bddaac7abc05c57fbf0ad723bda01a2f3d64b12dbf3333f33e425b4ebc139999bdfaed6b3c92cb7fbf2bf617bda01a2f3d399da4be3333f33e47e611bcc45b87bdca32c4bdd04438bf0000303fa01a6f3d399da4be3333f3beaaee11bcd15c87bdaaf1d23dec5138bfceaa2f3fa01a6f3d64b12dbf793df8bebd3786b5082075bd8104353f000000008104353f508d973deeec2fbf4f93f9be47e6113cb8ce7fbd5452e73ed49a463f1c7ce13e508db73deeec2fbfcdccfcbe11574e3c53cd8cbd2fdda4bd20637e3f77be9fbd508dd73deeec2fbf250300bf47e6113c4ab399bde78c08bf39b4283fbec107bf508df73deeec2fbf10ae00bfbd3786b5a20a9fbd810435bf00000080810435bfa8c60b3eeeec2fbf10ae00bfbd3786b5a20a9fbd810435bf00000080810435bf7f6a3c3ceeec2fbf250300bfaaee11bc4ab399bdbec107bf39b428bfe78c08bf3f35de3ceeec2fbfcdccfcbe745f4ebc53cd8cbd77be9fbd20637ebf2fdda4bda01a2f3deeec2fbf4f93f9beaaee11bcb8ce7fbd1c7ce13ed49a46bf5452e73ea01a6f3deeec2fbf048df9bebd3786b5d9cd4cbd849e7d3fe02d90bb280f0b3e508d973d782832bfac70fbbe47e6113cd9cd4cbdceaa2f3fec51383faaf1d23d508db73d782832bf000000bf11574e3cd9cd4cbd0ad723bd05c57f3f48507cbc508dd73d782832bfaa4702bf47e6113cd9cd4cbd273138bf89d22e3fb7d100be508df73d782832bf6d3903bfbd3786b5d9cd4cbdb6847cbf52491d3ad50928bea8c60b3e782832bf6d3903bfbd3786b5d9cd4cbdb6847cbf52491d3ad50928be7f6a3c3c782832bfaa4702bfaaee11bcd9cd4cbd5e4b38bf89d22ebfff21fdbd3f35de3c782832bf000000bf745f4ebcd9cd4cbd2bf617bd92cb7fbffaed6bbca01a2f3d782832bfac70fbbeaaee11bcd9cd4cbd0000303fd04438bfca32c43da01a6f3d782832bf4f93f93eaaee113c9fcc7fbd1c7ce1bed49a463f5452e73e508db73d2426a0be793df83ebd378635ef1d75bd810435bf000000808104353f508d973d2426a0becdccfc3e745f4e3c47cc8cbd77be9f3d20637e3f2fdda4bd508dd73d2426a0be2503003faaee113c3eb299bdbec1073f39b4283fe78c08bf508df73d2426a0be21ae003fbd37863596099fbd8104353f00000000810435bfa8c60b3e2426a0be2503003f47e611bc3eb299bde78c083f39b428bfbec107bf3f35de3c2426a0be21ae003fbd37863596099fbd8104353f00000000810435bf7f6a3c3c2426a0becdccfc3e11574ebc47cc8cbd2fdda43d20637ebf77be9fbda01a2f3d2426a0be4f93f93e47e611bc9fcc7fbd5452e7bed49a46bf1c7ce13ea01a6f3d2426a0beac70fb3eaaee113cc0cb4cbd000030bfd044383fca32c43d508db73d10af9bbe268df93ebd378635c0cb4cbd849e7dbfe02d903b280f0b3e508d973d10af9bbe0000003f745f4e3cc0cb4cbd2bf6173d92cb7f3ffaed6bbc508dd73d10af9bbeaa47023faaee113cc0cb4cbd5e4b383f89d22e3fff21fdbd508df73d10af9bbe7e39033fbd378635c0cb4cbdb6847c3f52491dbad50928bea8c60b3e10af9bbeaa47023f47e611bcc0cb4cbd2731383f89d22ebfb7d100be3f35de3c10af9bbe7e39033fbd378635c0cb4cbdb6847c3f52491dbad50928be7f6a3c3c10af9bbe0000003f11574ebcc0cb4cbd0ad7233d05c57fbf48507cbca01a2f3d10af9bbeac70fb3e47e611bcc0cb4cbdceaa2fbfec5138bfaaf1d23da01a6f3d10af9bbe268df9bebd3786b5e6ceccbcb6847c3f52491d3ad509283e508d973d7ec946bfac70fbbe47e6113ce6ceccbc2731383f89d22e3fb7d1003e508db73d7ec946bf000000bf11574e3ce6ceccbc0ad7233d05c57f3f48507c3c508dd73d7ec946bfaa4702bf47e6113ce6ceccbcceaa2fbfec51383faaf1d2bd508df73d7ec946bf6d3903bfbd3786b5e6ceccbc849e7dbfe02d90bb280f0bbea8c60b3e7ec946bf6d3903bfbd3786b5e6ceccbc849e7dbfe02d90bb280f0bbe7f6a3c3c7ec946bfaa4702bfaaee11bce6ceccbc000030bfd04438bfca32c4bd3f35de3c7ec946bf000000bf745f4ebce6ceccbc2bf6173d92cb7fbffaed6b3ca01a2f3d7ec946bfac70fbbeaaee11bce6ceccbc5e4b383f89d22ebfff21fd3da01a6f3d7ec946bfac70fb3eaaee113cb4caccbc5e4b38bf89d22e3fff21fd3d508db73d0ada64be268df93ebd378635b4caccbcb6847cbf52491dbad509283e508d973d0ada64be0000003f745f4e3cb4caccbc2bf617bd92cb7f3ffaed6b3c508dd73d0ada64beaa47023faaee113cb4caccbc0000303fd044383fca32c4bd508df73d0ada64be6d39033fbd378635b4caccbc849e7d3fe02d903b280f0bbea8c60b3e0ada64beaa47023f47e611bcb4caccbcceaa2f3fec5138bfaaf1d2bd3f35de3c0ada64be6d39033fbd378635b4caccbc849e7d3fe02d903b280f0bbe7f6a3c3c0ada64be0000003f11574ebcb4caccbc0ad723bd05c57fbf48507c3ca01a2f3d0ada64beac70fb3e47e611bcb4caccbc273138bf89d22ebfb7d1003ea01a6f3d0ada64bedfa3febebd3786b582ff2d3b8104353f000000008104353f508d973d080549bfb5f9ffbe47e6113cde214538e78c083f39b4283fbec1073f508db73d080549bf9a9901bf11574e3c30d5ccbb2fdda43d20637e3f77be9f3d508dd73d080549bf593603bf47e6113c529a4dbc5452e7bed49a463f1c7ce1be508df73d080549bf43e103bfbd3786b5115578bc810435bf00000080810435bfa8c60b3e080549bf43e103bfbd3786b5115578bc810435bf00000080810435bf7f6a3c3c080549bf593603bfaaee11bc529a4dbc1c7ce1bed49a46bf5452e7be3f35de3c080549bf9a9901bf745f4ebc30d5ccbb77be9f3d20637ebf2fdda43da01a2f3d080549bfb5f9ffbeaaee11bcde214538bec1073f39b428bfe78c083fa01a6f3d080549bf666606bfbd3786b511574e3cd509283e52491dbab6847c3f508d973d92404bbf666606bf47e6113c47e6113cff21fd3d89d22e3f5e4b383f508db73d92404bbf666606bf11574e3cbd3786b5faed6b3c92cb7f3f2bf6173d508dd73d92404bbf666606bf47e6113caaee11bcca32c4bdd044383f000030bf508df73d92404bbf666606bfbd3786b5745f4ebc280f0bbee02d903b849e7dbfa8c60b3e92404bbf666606bfbd3786b5745f4ebc280f0bbee02d903b849e7dbf7f6a3c3c92404bbf666606bfaaee11bcaaee11bcaaf1d2bdec5138bfceaa2fbf3f35de3c92404bbf666606bf745f4ebcbd3786b548507c3c05c57fbf0ad7233da01a2f3d92404bbf666606bfaaee11bc47e6113cb7d1003e89d22ebf2731383fa01a6f3d92404bbfb5f9ff3eaaee113c59854d38bec107bf39b4283fe78c083f508db73de1eb5bbedfa3fe3ebd37863510212e3b810435bf000000808104353f508d973de1eb5bbe9a99013f745f4e3c69c4ccbb77be9fbd20637e3f2fdda43d508dd73de1eb5bbe5936033faaee113cef914dbc1c7ce13ed49a463f5452e7be508df73de1eb5bbe43e1033fbd378635ad4c78bc8104353f00000000810435bfa8c60b3ee1eb5bbe5936033f47e611bcef914dbc5452e73ed49a46bf1c7ce1be3f35de3ce1eb5bbe43e1033fbd378635ad4c78bc8104353f00000000810435bf7f6a3c3ce1eb5bbe9a99013f11574ebc69c4ccbb2fdda4bd20637ebf77be9f3da01a2f3de1eb5bbeb5f9ff3e47e611bc59854d38e78c08bf39b428bfbec1073fa01a6f3de1eb5bbe6666063faaee113caaee113cb7d100be89d22e3f2731383f508db73db9fd52be6666063fbd378635745f4e3cd50928be52491d3ab6847c3f508d973db9fd52be6666063f745f4e3cbd37863548507cbc05c57f3f0ad7233d508dd73db9fd52be6666063faaee113c47e611bcaaf1d23dec51383fceaa2fbf508df73db9fd52be6666063fbd37863511574ebc280f0b3ee02d90bb849e7dbfa8c60b3eb9fd52be6666063f47e611bc47e611bcca32c43dd04438bf000030bf3f35de3cb9fd52be6666063fbd37863511574ebc280f0b3ee02d90bb849e7dbf7f6a3c3cb9fd52be6666063f11574ebcbd378635faed6bbc92cb7fbf2bf6173da01a2f3db9fd52be6666063f47e611bcaaee113cff21fdbd89d22ebf5e4b383fa01a6f3db9fd52bec8cf323fdcf2113cdcf2113c000000808104353f8104353f508db73d000000bec8cf323fbd378635a6634e3c00000080000000000000803f508d973d000000bec8cf323fa6634e3cbd378635000000800000803f00000000508dd73d000000bec8cf323fdcf2113c47e611bc000000808104353f810435bf508df73d000000bec8cf323fbd37863511574ebc0000000000000000000080bfa8c60b3e000000bec8cf323f47e611bc47e611bc00000000810435bf810435bf3f35de3c000000bec8cf323fbd37863511574ebc0000000000000000000080bf7f6a3c3c000000bec8cf323f11574ebcbd37863500000000000080bf00000000a01a2f3d000000bec8cf323f47e611bcdcf2113c00000080810435bf8104353fa01a6f3d000000beb54f33bfbd3786b511574e3c00000080000000000000803f508d973d000060bfb54f33bf47e6113c47e6113c000000808104353f8104353f508db73d000060bfb54f33bf11574e3cbd3786b5000000800000803f00000000508dd73d000060bfb54f33bf47e6113cdcf211bc000000808104353f810435bf508df73d000060bfb54f33bfbd3786b5a6634ebc0000000000000000000080bfa8c60b3e000060bfb54f33bfbd3786b5a6634ebc0000000000000000000080bf7f6a3c3c000060bfb54f33bfdcf211bcdcf211bc00000000810435bf810435bf3f35de3c000060bfb54f33bfa6634ebcbd3786b500000000000080bf00000000a01a2f3d000060bfb54f33bfdcf211bc47e6113c00000080810435bf8104353fa01a6f3d000060bfb54f33bfbd3786b5bd3786b5000080bf0000000000000080508d973d000070bfb54f33bfbd3786b511574e3c000080bf0000000000000080508d973d000060bfb54f33bf47e6113c47e6113c000080bf0000000000000080b20ff23db4af64bfb54f33bf11574e3cbd3786b5000080bf0000000000000080a8c60b3e000070bfb54f33bf47e6113cdcf211bc000080bf0000000000000080b20ff23d4c507bbfb54f33bfbd3786b5a6634ebc000080bf0000000000000080508d973d000080bfb54f33bfdcf211bcdcf211bc000080bf0000000000000080b62bf43c4c507bbfb54f33bfa6634ebcbd3786b5000080bf00000000000000807f6a3c3c000070bfb54f33bfdcf211bc47e6113c000080bf0000000000000080b62bf43cb4af64bfc8cf323fbd378635bd3786350000803f0000008000000080508d973d000080bdc8cf323fdcf2113cdcf2113c0000803f0000008000000080b20ff23d6282dabdc8cf323fbd378635a6634e3c0000803f0000008000000080508d973d000000bec8cf323fa6634e3cbd3786350000803f0000008000000080a8c60b3e000080bdc8cf323fdcf2113c47e611bc0000803f0000008000000080b20ff23d77f695bcc8cf323fbd37863511574ebc0000803f0000008000000080508d973d00000080c8cf323f47e611bc47e611bc0000803f0000008000000080b62bf43c77f695bcc8cf323f11574ebcbd3786350000803f00000080000000807f6a3c3c000080bdc8cf323f47e611bcdcf2113c0000803f0000008000000080b62bf43c6282dabd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: -0.00097599626, y: 0, z: -0.0000004917383} - m_Extent: {x: 0.69945896, y: 0.027512, z: 0.2812605} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh.meta deleted file mode 100644 index 6a42e9545..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner (Plate).mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 623afdc63d62bbb43a6f8f26c33aec10 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx b/VisualPinball.Unity/Assets/Art/Meshes/Spinner/Spinner VPX.fbx deleted file mode 100644 index c96eb8032ad6dcfc3f40ca576933df78988077d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26700 zcmc(I2|QHYAGfk4MWs^N+7*SU>?EPZQrVKNi6L2rSq#~tqEe|O$ugCqB9yWX#!~h* zWZ##u&M=lSV|&kKl%9X}JpbqazMuDgT=(8{&hLB9@BEhYyXTylIR$gIgdt&D)sLRr zdep`NhIHM!m1zr8FFz9#ot^3Xu@2-Phr=x4NHrHU+yP~Sa4-Rx%fQJB&Nbh&4d6&9 z^TYr%6O+r55-UN8#XJc7$&x%SH3ti%t0NlF>A5<>Pk<~YZwQ)ktW`zAVe`2*_HbP^ z%-*pa%uJ%4wffGKstI!I;WP6ot+J8Au|Ou@~0z@ zQStytWuIphfz;gx9*)|<9YB9?-Gf5IF51E8C3He(@7DcW4~j{ONp98D(*^g3#12S` z9UKBoE(bKi;N;M?fIGlXI9MTcA$~OM5Eo%~&?D2bA2};Pjy?*GJO)FryEIphkqe3Wdskb6hm}h>S)m0-!yv3nzboyvB!gAY25kq|1sF_EmL#+5VlG~W zTcGDN5|(7JsbJ8TK(9aqMN86GoPxVz5lBnad`{QWoU?Fe8%Pi5b7q$0u&W~M?coj! zl|X>skG3W1zUnj*;Rr{fZQv+riOYadh>FDPz>0Nsxiv%2W=kpZIs@&F8^J-SGXe}mKmkXQiu&IKe$S~^G@Yhb;0 z^P&SRIwCL*mJ7`QxlBhuafk&?aDuwGfXgC|BT$lQ0cRb! zThvK)2!5Uc8d`eu{kWL$qaSsl#!4Iz2Y&}-r6smXNXX6qiAhLE zJYqmIzv{J3GSnOG-#eni$xiFfhB*HV(j9 z+li?;0BeT-B~jnO2JLF1f|K!*X z{08LqAsb+WntH>6;tCsAY|X=h0ep&&uzjp+1TG6$cx8APx)LKa9?x@Xv=NK!bP$UWjSMSp)*D z4fH_s=Q6)~a$LgF%Cm4QI1=t)0Z08*bb9BHM^2!!Ho&4loQ`_Vs1xGiyF<4G4=GDB z7#$9kVFShuwGm=<f#3BpCkjzv; zc#PVrdkJQVz@mOfw+jdWs&wB19q4tYvv3PIaDRVC2}=HxgA8;V(jv$^E!85ZQplCs zU1#VDl)%tsNQF8WbbnMK$Odab!Nubz6=DND2!S$uDHPJ3!2tb3IzV$VAf`b&BIfmx z1?2q2nL~i)fF4qmQ{V*hfQ;xY0u6&KQ^(2*1f2g?(8&zolMBF*tQLV!+c+csH7+-> zd#J%naG_BigogiHd#xC_Cl=ZZNrZv>KQ;FSfQ95Ewg3xx0zD+m0R?0Nx_H4=|4MB^ zIi@K_la|P43GM%2)_Ncs_>0h&0{?sKz6XFIZkHI^QeYd0bHC%Y;O{7nen+_+==>k+ zJpVgNg+EimTz=bH=f9);ZEF+$j`Fvy?PX9daej*irGo_1h4Wng!ov4~*I=lB4uca| zIP@03NC!eT3%EK0scBo z0LbW{0I`GM9OCLvEjs^CfYca35(}(BW&yn}X~G@;o_gnzP(TflFk*ZV@q^(E{=I*p zDB@oXyA%fM5?^#0fkOYUu%cj&4(c;eaQ?;D|Jf0F1H!*>x!9Kf!3)*?0rlUzATFTb z5R-csn1uZJlD7WVqSgLL_FHpy|0CINZ8`A|WPflw{eL9;Z6mjVS>NAv3C!iUjWlGC z?OEtaNViWoT!JHQ(DU*3Zvu({2GtTb#TZBbtYck(4$?7=h1ynv3xn=|P%&9B9r_#4 zf2Lu>%Ev%Mz_H{80YIVdT+%N;TKZcBd-W%(--@pBPgK8^+*&YW_nQ{}LAx~mMD^Pi zdi;s%w=HY}RFGiKfD^bPXm*0}wq;|nBx3lt!KVy(8;9Vv7vQ0&>pT3SH~6>MfedV| z1#Iw2@&lWp2mb_mi9&r>v04BP$&E2oLoqW@{$pU-^DNB48vc()SUxZ_{RfPHG`O1o z8RH*~tC7FJfVup4yb%A4@sD^B75dNqSkjJv#0!#vA^St*47q^SKe&T6`qDoVP3qsm z{3DK-wk~k=`_}v;rZj#5v(PdB6;t>A7Umx@Rr9wn|A?tA+ZOspn(2Fp1L84gKFU(X z!P@ShnUMM4fcztdUj7ZpKVqi=fI#CcW2k}N&NUGTTNU);bklr{`qw$Zb=w!H7(+1R zq4XUW!q7j`sQ^02b%07CrwmoBdkKO4)$7wTkmtuJ&X{utBNdZ2+#0;sI>W&PGuVoN z2GcTNPXWf!5dmg}A-G@6>6HGA0$y?!=d)GNU}^m#wBG{khH&})iVL)aFv~#M0mwnq zp_x*3BpiNaVG{c<)q+eWaZnQCbqRr{&M`1s_N#V61pc7pk|70}L(;Uc+q!hH`c)c4 zstfg&3d76)YQC+Vfqfj51FeT*RtWq1NcA5Hpupdw9@@FkV)X@7D1L)b7ssf-KK}>i zuSO^gz=XPuG2H}3X-lT<{_^+>!~zDr8ps2jg15mX^jjI>B@E0R|0!!87y36pDG1&C zx5q219~A5hbI7P$atjmr(2L-T#i?ISzt90N2!Y-Pp*NGRqm6?D94U4b3Fg(|Xr^V0 z#paa;n(B4i1r=U6ewwFSOq{=lfl`o8Kt0L5h_Set$sHu70E34Lurq_UfDE(^m`%4V z07+0PeL2`+wd`F``6)fvdS9}H$i2_KyBkloYnJj@ z;(IRQEDhZ>jF>4nKTDOrp|5K={^~|fKVed%>{aLHIkE_0d6^&YUD@G7UV4NgbNe#w zzg7`qi50FJYkI{*$|J{6z%vmuk{{zSv>AtYlr6!Qs?0c62at;TI$o3dd1u;Yz3)B5 znG+-BT%VL@R2R37kLnTp$sHfxiYgmzdOw%^R+%8sTO?%CGS!fd7*8%bA;!wzjYk_c zBU1hO?o!5PYubg=M4ip&{3B)C@#uH>uQja}E!?v4O)E$zGTmfeBDkF$o>14tlp2M5 zA~yOJ6=!m>&(!3$%|6pL#30Y|5_(h&xnwI*JHnm0yN(G*4=BZyVO7Y7spc3`YRj^g z=>BxSm{W<)5hXC<@YvH3DY? zWTh!Tn$m`F=1Lc(-@Sq}m-e4<%ccg;ye;$851U#%g1}l zbbTg&HFcjJ9_NZ2P}DF~c8ybB-Re4Bls!i~F&UxJCpK$&*uDHssJuY$*p!{&7gCH7 zmuG3cNb48b2mGW1WWO+{Il-rHk|Dj^eZ13Cc4Jg|ltHw4x!Z8hxo{zm<~gw^Q_Oyz z`q5(U(_{YH+A=f~$K;X7Tw!53*#PmfhUsCR+3V(dL2sjQo7@*W+LDtrYMa7h`Y_$QxDsAS&OX#0oG?UEGP&k(oRqyG*DW4} z6%bQ3++NQWdSI!NK1)^#CA=Xxe` zk<{>2Zt|g)zGM!etAREIBf(7WbRn}N_^N>`e$=}+cDpOPm^Tdsw#|N|y5+=Q4Q%&r z$L6MWw`T}xI=5p)bE&UptgI*XhxgSrPFRY^8uOXeilvvCZkML_bjhUeo<4YvNZOZv zujNR3t@IAV3*q?9juvTaWtroncpvNqT^By=-%fM1PurNfa^P&vK+}w`&MOaL=Gog~ zI;5OiALTu647W_>m0N1L5M0@GUd~wUHwlS5YL?Zay)`t~EqRyA!RhtR;>y=zD)kEk z+v3|bvIE=l6XF~u3qMjrhvl%z?VCU1_nEV2-l5(Nu{_jtefDva4<_*gEoyj|`??`x zQCN7Z$<>Tz&E2dBA6*}*Hnr^0_6-T-0^Mu1xu@D(Kgy?Wm=TcOtgvE&F!(K-)a~zr z*GN!2SSPr#DP;Bme|IKN^9_Dq{MRy#3)JRUY$MT&@%GSMv zHHJ0oeORU)+5FBAgE}sk{>WKc+TpNOC)!1Fc69$>M*5wXtkyYivW?sNhUXfEj2wLT zFX(t3&}CLNOKUjFMv$S_#o+3)Y{OR@+ZDhFDh`y`J0!v`9nRMb{Pc;x5h+ycYhU)h zr|FK}L1f|~-)*Y*4&1oD=E0h^-FuMhWnb{Gy>WjIb7ZvJXbH?zd4}Vb8Ja4%hXn2inIfZXAEntB$?0Mr&2Z%~#S+p97C<_vq8`V!MA# za_=fj3DwPcj_MK*&%Au7E@!dn6kCYpfd~#wtCjVdyZlV7Bm#{}b_ckxe3HL><=%_i zzU;H&IAxTFH284)(4*5QHxKNwX6AcyLCUyZeOF_Bp_p@`pUA|S(q&x8o4Ne$d-7$< zIJA+stHRp%5oO9*#gMm(P1;3jQ_5C5ApIMg+Qq0T<;;SVoB0aud&^zQIL#=xYt!5J zlU>T$l_EuD+KYWp(!RQX4C-al?mm%6(1;R)E*YnyUT`%2x`Qx61uZq}IR z^OolJP@As%#b_%@f7z}F-m8=!FE`wBT6Jk&4A zV3}X%hl;NOYfTU&G^w&t3+4!d7Heo1FuQ zcX=cq+Iwuhj`6O?`y2~;t~%d7|G0z_S-5rlN$&D9ZrwGjod#C!Uw49;@TTp$amjnD zPbanPgriW$_gFPNdKFG|C4F)uIS_GJGFb^nAa?TS-;&Esj69vcne<`gQtF1{VCRQn zGZ`~e`;`Qfc<2EQgN;#XgDd1!6kg{`XUf(FCl4!H1Rv{g)i9i#96VyCaMjp3PtR`P z&P|PyvbQ%s>>zD9ZY26DAky8q$6=a4Z?NYd>~DJ19G=z6 z!_0S$GF|f4W0LUEPI;Q*$|`@RjGR1*5k3%SF1^ZVtzfivFauB_srgs~LQvzTUg!oD2c+TkySqiqqm(epf6Qn4taDkw0mmo?Fp(vQui9ac2a*yw+=^3i0WvDD9JdM9$+{oub4mTI`!*P_p^@T$xXh!OWze ztfHCHH-3Nr#)fH`)7Sm6%+eI4o(P_3^19HCJXZdpGufx$J}G(@s#WWP^ zX#v;V2OnQ6brmL3j+FDqc+sMA2bipJICVG5X$q`Fl;9@S=D^vmZPx+2lbGz=Z{Flh ziH$Sbrld5mHSZIAeQ6@mPbx?4@==S!E`+fw<52k`lx8`=-Kx6M%}a9%uunZ_iZYtG$txD^|6gJ zJL13G!g))_PN|h8y52Xc7MA>I-8Ha2z_w?_q*G^PZ{`@iX{h{&C;FyPvUsF`OO+Jc zJCC;AUuP>@BGy~rFJ7vx=Im}^-&X*i?wIT~-3t@M#NwAxy6Xf>9<*;evykKQ}W@jbQXehU2+GjVZBNwSTT z((D7f0uXBW+gcie$8NHJx-}s}2-916#ZGp}7!%aO+|P|SD;nEB+$l7Bu;vsdS=;}q z?V-+iEMW1H(1z&t2;KL4gOu*M6Yu3`yt;C72N`?l@ZN{gh=i~2#V#)^?8!R6KW$k6_2Dev<=OL7;f1rW0P^CLdjRe%r@J{qP-hHrYUCo)sP$($gW5!kenmh~>_QrN4 z;Y9cw{CeLG&E~hmcdDCxyDxY}kQ9}h8(Zr#%DcY}X^AJhZivV+tiw$mxngWmKbY8l zK*8G|W*+tizsBle+Lk9wQb-+b>E=A&GcO&s_j<&mq=yo-nrFAZNgVKR6r>Vr-`x`T z%AFM~BD;%gbQDi@#9NBaUTPT#P27J6Rn^L#Y}yu8`;IxoV@JwBtxu@w+*)51VpOgE z^R;87w9wP6Q;LqY%|0S_%;BM)dsFI@eeiM1#?n+Oq^~v#R|v64<2|{1n=)S!b~lg9 zDTgC;j47#&=00w;D5qYmPpHSb$v!S`Gok_K&@)tGfFFfV+QT#*pW%L``N{T&6MNf8 z(wLpI8HrO?a+77GcT*f5344-h+o<9n?8Zm1olWRWFV^7fcP`Cu#*|;;XV@m?n>X&s z^lyB(qfCp4aK7rn6;^5=S8iWcY)_Z|?55%{y6Vm;ssroX1hL8dYiV+7bHx;6c(g`=XvZMP?+-L*%s+iilsI5nTZ?_1Vf_|nul|I^MPBtKg8Au5nqh$M6- zO?_*mwkYNOoC)&KyyJ0 zF$om5=o^4QX68x2t0nNfH(vII%Wf_XFGUP}uwX)1zuRZu9Auh~ zx$1pEX!$kR=}mMTijOz%VWt!&FsDmK*O0g8ft+sg&V!#r((b~T-^ec;y3iU-YJNx> zl6EFbrD96^zSxtlu9e{T?T*tF5;C-#5ZSak@`B|i9_#+y*KfUyV~aY1A+h#FHBKko zsV-&8AY(|Us;nQ9CO);r$#Y)`ZOTsmWB zTgm{pPvee?)V_eL#?mQ$7(W#Rc^P)*fMX&xOGAcBPCA367{-!;Co%U9MQ58*ImR@A zfz7RQm>6n{8(`0H;OoUWn5z&3n+ECRXDD0Exf5vv7||odM)AJl6IeI#lmU1Aim~{t z(#$nYm;jY{ROm{bBSb448ws1Gr7SQ;nrLi`s|{-3(%V!d!Pzuqn4J;!(aoHxwLYU* zg565h-T~!68nAp{lizw1RYmhW(8QXYGhh(ax}$k0CX0-wJ|B)rZ5I5T+Ba>rx^6Cu z+|+|_>M3rDB5Z3>MuwKRtEFLiCL2SMxRcV-L}%)R*8wW=a3eC*8b#iYJ!)I0)j#nP zU)Yjz@1<#(3cHDBL-*OH(wC-kD%68E!PZV5g|&s|mQI9r$e>z>Jwr(|`vEbsT zmmsf%R_rb3HFmz%Md0}>;&(NvGnx#xp)qQjUw~NhlA>>LaS2<0tmUUFe|i|ZxG?bw z-anR1ys}>4gx)on=6B&QPQ0Fmwle-=+Lf}D1;!f3{3rN+s)$GahX{T-?fUV^LV-KL zW&N0TjRmF+NzQBjW#-Fcgk}(YQ=Yle`l_a{?~pfUHn2afWPe@yJmJH`?a_BXJ-+mg zZRHa_jvY_b{kOy*(x=`|VTlJE2_;N_~PSH_0p)7;3P7|fHY zF0#M+T~b73Vr(XFSyKVVFK-n=0ZC7wU5|H~4B~m$>1#M_lzn=u*WhIdE`qd2-ldpg zgxprox>KEl`ppEllHq*PTIJzGT*}*{OD47@o}JFBm6;Ck>vq3wqHM)A<-><#%lGp1 z4b!R4sPbc`wZ+WHxR*UI8RscrpDPT=ZGE(xsN35EpPZ!jb59G+xKH@RPtlIXheeZB zMgo*3@3{Iz=B9p0p|R0rgLV<^kkT^l@tLJ3OdoF`Q~0A{#b>EHJy*sJsCP}$=~1mb zlk`jpL+WZ1>dG)wMFu^dyh@I)AnO&*Nz?wsoo+~MY$immI8-!F4(k{$FXQ?^KBY_`jLbWu*nu%GFm07b;`%do zV`miANM__GwQjFkcOh$Ai{DeNWjP$;$nK-Z{9M_uyxMdgexBZtv6B^b9VTpf3}OgIXbxA zR+)&n^10)@S6BHH(g00i+P`hmQjE^d)-PHzmpiA2Y3q#ceV(^fU?O}|Z`>hz3A}Zr zG+TxkKjRqDYduSURk{n0X)O1iQo_7y?#S;HN_KTpdWU&lq<{btU)iRfpUym6S}H$)^?gYYz5{>vQYDJ3Z@^-v|DJ|kq8UgOyHXh>q0z-kRgI`SCbCi+OvTpI=pGrBk#KxlI`_812wY^Ip z%-WT^m3I`zlRmjt=BE0;L>LpL8`CRf@hMdlf90|I`OfPF4D>yp2q-hY#i_ck$wN-H zi^}7f6{uZE=9|7MU*^L#I&~`}E2gZNyzX5hMbtI;6PNo?zJIyN#6_{$%EJ0J8>}X7 zi$2b{7gQuXS&31cpt5*oRho~yn3k7O`gCvN4Xq)77neGCgKM_(M9H0%qp5CvQ>@yE zRPna4uqoC|T4dW4s}k)>z>qL?pw$tb6m(P2y+?buzo3j##^e>!-3IC$N$p-&%#?a& znxXVt+~jn=;4Eu8E1l^mSj&W5F#@>aU(NmB2kT`}{>6D$t@X&-?lJ3n^#Lx2FB$9( zMv`!&q(KUGAn=CJ;xQJtBm*yPG-1v~N9b?%!pHzyIY zFXSk~-=H^H;Mz=A7Sx&7;b4<5_T= zp5C+DO_+kjSqLl4eaw$`i?>TxO21BYR2jqcYfh^9a6T^^^MBE!lY8TgH63xoYh@gh ze3kgd*}~!GW+Bg!f=2-*%cj?53%4P&+1npx=eK{z_G>$vo!=&z?brS_TcbVsT0qu@ z_G2Qv1~Ib%;X}E#r(Qa1M6FBbjoUaIH!NC3_Kxu*NKK234P3L$qOaZ&86SLn=qeuz z;;Jnh!f${baov^`acuxhuG_LBZVa#?dJ;6dzZ$Z26P6QFbhRe~xscFD2@!HayG9 z_zdaoMB;_fcW?OcWg9#3G32@XWtVs_bHrL(?FKj(C+%UQO1fseA#n-P<4w3_RF$c- zP;YVo1_j%(*LbrEUr$x-sN$Zuhoa`^>Q>r3!iKrU$OBopPR_u{`!ozsTj@=873lb7 zyh&?aW1CVE9|Lv%LD<75VV`4Nw#%*)DL zW;d=B-?`knq2=n8Xo=Vc=^Gi;%{6Nxu3phl<~*dk!rAlA$s4@STMm+&O!&@~8=1?< zPd`j>%YU=p<-p}Uc~Xe&>Ub`(Wub~HhrENIky32GnTXc$(LLI3y(SOI_oi0yMO>E~ zo!wI1+>wy1{CrDp|B1J2(>ES6+(((%4}@WyQ(W;12&0^$*bUdy0n*@AU=7y<($LN% z4BD-0;v0fzmxXKjj)#?Uk!-l9LQ1r}<(~`e9W84PVIGh*-t5U;M=l~4&b)El((p(U zx5cidnd=c%e@Z_=+C9%Cg`?kLV{F4fG|PZ6t95RRjSkJE#uHioL=9COTsRXVYmmS99KnDvtAoZDX0LWq>=CF-f1o7q0O zuV2%gd~sN|IwZ5@M%(CXDGxN zg5${LwIw^Mqr}E99Z}t~_UIn16_K0wUn-2e&ZEND@HOJN9A(oPbQEVLI#T|Cg33Op zv5ph;ih$WwJ0nz9S*_q$1>baQ=K&wfvn~;5?pW;;m$;{wC*=*+aqjNbgm3Ayl(?p8 z2H%(%l51N6SK>T(oXfuee!#U$H*4MFGX+S4gxlK?L)tL+`A&r&aP2}T}1 zlRaf;#VPb5D4?!PTM5RC`w&`QS4RTOQ`#CWuwJFgeeqT#uSZ`w6WhA-s#V*(+joxa+i~1#XXK_* z|2sQ^H~@2nnlO=h=Byrm;r|elzZ`k)m>9&?*_B3RCvTtAIKt_~vfA z7e1x#Sq4e(!vzK{dGYnRW)o*lY+#o;e#jgw%(>^Dxrg#W$QEucLq=>7b9?|T$`OX; zr6;e2Hg#}eQ}aD$+qcaHyxXY4qU%RLxb=G8^>yk}PpfW!J63UT;@DSjZS?M^ai1%$ zox8G513~{9;1Y3h4J4Y}6FWnQ$6*J-I-*FY>n#q=ouK7FESkcBMwSOc?Rq2wX!~~h zed&6jvkWXF4#ZyAfH)1rajsbIq)~Zey6Wcc^!(i-btM~>FVS{>AF4PA7n*wW z^7b`7)RTCboCBcgWSX~bSPxiI)LG3-yLHN>XXTG2MM0`-R;$8JiQ}RyYc&^EMRz~i zeee~uDk@`t=@h%%S~cqzed?>;kG+)k=q+aI*j%zEV%q~~jc}*MCJm9m;}(*-E+ICW z!q*H7EO#dsT;!@RfiL4Mgsn~82Q7u3%Yiz5rNPf`dat4bh|+4Zl&eh^Vm}^eyr3}d3VOx zxR&Yc+r#?DT=$r~*6R?RWII%L*8fHI$%{8Tc0Q+uB?bqS((gHLK#)^W=^eRy=L|XHLP}prKt$$|iYhJ7<6E^Cu4Bs>1srN?qBO_D(w@pi$ zwB$A%p=Q6(Sr(l7QsYxzo?7c5W@|kE+&Q+Ai*IV8?E-Ag5|@pa>h3eEd&G&JPE{5- zLfH%)oW|hzV_3Csztg&pE|Q! z%_eH4x&$nL*l5)E#hAYTv;V%#oLrv9Z0sP;Ipez3nY0^YsR%{NptDm8UJ1sCdI7v9UC!)J?ir-sj8d<+O&vqEEDg6n96{#L#f6O%Kb5} zj$X2&&(~RDFPMz*I0ZYs>zlR0*W})Ky}YeBT46hJ9mO!>TcbBBEU+uteRKO|DcUCm zZQ6CL+*P`i*WLZBp0U=oEM3KY5k2EDmwP9R3t42<-0?gTqbGtXin88jb}pUU+DJ#{ zI!eWTcSJ~X551^*Y?*T&?>v&4C^KfYtt36|{YEQmZQ(moBU`!;^z|HgWEFop2s zf&TI-NgO4$a2x(+c4C2`K&&90`o&$$;PkB1L%~!s&+|hC!Y%Yhubk8I9b3D3nggcq z>KL+h@i@$~Uy*vgy4hbpP^q!fRnmJfi~i{q>YZYBu|eEPp$4_GSCbnwTEwYiSCN(x zUnXn#B14xCpmIzt^FZXj1W&=sqnG`h&qcp9l-AL0 zW(mqVt*9wj=jSzQxvPYfmHz7CUcz4IoytrBx8i+1C5CietUoB4%)M;twKm^-oe}z) zL)Ynw8&yWc>{@hNS>wguUZrdztl3n1SzUx4qWRoYm3vl;t>F2_wGJ&in%*c+adhOr z5qxj*A%M2FUajVkh0iJJ>8~F7-{b{qaE*5KnTdtIaH&Cn&cbAqvOSvx!!+2wnoM< z_<@<;!7t*8GjCheC)Pc%9rf1cN<_LJ8QIEJu@5JJ>4FK44eM(gGC4mVj~COs+ zpT^Nw=M-Vq(4cUxd6TZa>L1cm11t_@`_B*%iqJI(kz{-AD3|m5*e#hUk#BYGn3S;duw; z>mLQz$$AOhblE;$t}t`C7|$`1Q?@>tm%Wcksr!0pf2aHr2kZq;)V&AFx7>^HY0|VH z9@_!(@+#u0)dp)fDrK3cMXO=U7TRII8{1|tcy5WMY0S`yLnTtb><1UTQ^bn4tMi@Yh{xSJ8MgQ zbvW}wPCTUQVZ?27b#f;1#w4C>&=$?6JEJcz*MZMdP@mzB`9$_FoOzEsxNSoD zi>p63O+m1`zH-BW|Ef?_K^0#I^IqlGM+wS11P}E@Xc%@xl-G6LlL-B+bqSH(r+lY# z)=xztf|4?NxH(tCE#7IT-w3_Lmv3je^&@tzjGDyrw`diEJC)lNBS$=7+h3PGRp|E` z)4I*;{FHtc^<*&G@6@0&O~h6sza!F~kmf@d>-=VW-_ZWmoNB{2+dzkzio42b5tK!Q!;sYLMfyEW8pPAYi%1xBW|V#`-0DDrZ+v^|!;mibo}Jj92O-p01*wAv$L#7dpDA>L}}-cSgF>@n1g(6jucVJq|yUIo{dbO=f9P_%vqo3O;7sO%>lC8NZ)^u-Tkp zO0zv#?7Vlse6oVaIh%UF?mpgA#bSGP;tkI`+qlvxUq6f&SK)&nSN3>(SsP^f_(X9Z zzBu0ayn4L;EbXf#{T?Mb`0>zsu2nkMrEDn_Ec1*-MWs>{sqk4RZ*0hcG2Mzn-nVap ziyb&9vTW(oJqeC*IkQaUxo00Ef?L^sccohVYbrQV{(FN@u$SY-{4LM`Vj(4S{xmPrTsXYm9pbVRT3tN8FSBDT97cAF+uS; z#R_MPagcOlK)|;~Q>i*>8gEhS)B5DJi6}QWUSE=?VA_sr%^5x(6&60>HLBFz zD4EKAu4Wx&H`I1-?sa;73$_p6xc_|K+-8@=nd2Wb@xd?N+roDoa_ybnJA>{yJ}e)S zP8z-tEbZDG#YVf8-CXxHopk>KR^4VZLEX`sGSP%ix(xToZw%jbiT3ELWHVPb-)=;C zndB?i^U6^lXU8BWs85Un^FI~|mW`vbWze1TV2ajt1C|ci~@cIVoE5uKd$` za5p347h6vFjsDfkur7n_zkC_KX}VC`Zs6yjEhmc~*x5Yq_b|bVHwE?A`?7O+_f zThl%tS$}X{_~5$8IIs6NgLa}?Q4wzCu_>j)gQ-%ZC8^{ld2@aJn(WchP&%_$XGfRpyAZ+di4)^$i*qXz0%#Jgr9mhHbe26+5MQu7w)(w@q}qfyv_w0%VY4*(pl(*2ps_s$|E!$NH?=0o z?-g=q9+(;WW`b215597trm;JJTu|4q>XA#P_9Sgf>+}^NyBVh_<(jWj!kz9pW5NmD zt@*_9=!EW!`9Fc(Yz=HxURi*L0fIW^Jx)bS0wy#o7&-Ui~b zii#)iiFzF;>iQYA<|ntjksFXN8INh~VJGGeiU$_Q&mAx&P-a+rrs*BbZ&6+;E!I

eLa;B+7`bE>13)Qd8z0V`3S?Vceayqy`xI!Bn<_SF(Fie9|H&l-$4u&6efHvmOer#h7yA3&mcW92iJiV0&xeE?QUm z#&1)4;w|oCw4N)l7<0V!Iq}#l(r%Ye`{^Eg6j?0d5#28J)QH`XA-!5NM$g4TG zywRQRLJ1m(h|fBVgQu4Ym$)OnGO1kEbcft%WS($BsW7G|F`?8N7FwQj7$-5>r|G3$ zt8kdnND^p{clUgofR5mZt!%E?JijaD--P4?G@H1ndb*qE zc191mrALHKn$oD`@gCF8VfP|gT0bs7rAeoRE~hm5_DL(Z1WgDros~C5y>MZM5Psga zRmBX5Nn?EV+eAXP2_da}Hk;;1%1;}bXrYlK^Fn6eW0Y9aRw{VGzdPmw>p zajmH@Pt(eK?&U?h5=k7I;%M~XF<(9{AR$-EaC--UllZ=^Y%yZmIqx~Ns;&hJ7;MUG ze`hcM$-dD!G|A?2W6SQnXB$&K*+(Z0eVoytD@nCVCx*~UV^C$yp>|Q5wrqJS8EY^c z~L0e(55^BE2k6pKiDh{!3 z35+W~w$_wi-Os7JF9;^nJ4D&g?RTm>#-~&ugz%=?8C4mM>b$0fwQwZ&KTFhFmDbYL zegR8*oYRwTjKzY>!(dS><^+Nuwn`DZxvVMs3L}o@Z zdk2PcO(iOPX5U0h+}(UkO})LOUGQUqQbTJ%(J-}3ULI@YsG|d~6I)T%AjW;Ap`P%( zIh9kDtoMzq+hT+jf2%3a4MKuu>6BC5;i6>TA$(F9T1F=MRPWuQ1p5tP&C08Ir+eE< z;sCq&L`-jZQNp{mVU7F8jP;zYLM!BhnvJB!wBdD+SC`rIrSA~u9n>@XNI8GT+J;QR z5SuP$D~gnxmgt7t)7n-ySZjR2R}n4o3W!Y`zHxa36e)S#Xd8Tewm!*a(*}B`T_BN@ zBA0D6vrff2cUmR+VnU+sv8*A6vp5$Px44fWRbZjD>bgyV%Q8|us9Wcz5B!E@ow zyK|GUG=Ue+t+$GjGJFyxgJ66-V~y<8X~i@3&ZixnkGh7ZTRR36m8DiSd)JIB*4K}C z*9M5jCl0MmSAm25BhS)H8a0BG5xjK4pegF3qG3E4i>0>R^*ohS+!7RCGct>-YO$O$ zfq6^V_Jws@`!|fG85QXWed@zh1qH0CACvQ%q~=Dq7Z0C+n^E=K2rCYoQH^Qlk%u!# zI7)TYoY2gL`;;G16u2XT4Rz2W9JFAtWUJxICCT68CreNU4KfywpSBvFVo?0uRzvG0 z$bZ}f*zmiW-ZK#XwAHZmpVqR-%YR<;0lQ#jk_miniV6H21o&b`&GJpqhwQ*U!;R2C z@L8#!KHxoH=KH6+H7$O0?Drn*1#~B&Ze$!kee63MQ2gKf)_|*K7FCO1NCa>}rI3ha zzzIG}|1Z8d0cx=#0|k8I972J<72t2aW&M z(}USA{Ln`*BWpRpVTZoK6MRlhby*rC75ao4yDA3!eg^n;1t_v-+=Fjy_#g_jhP`)LV{3Otafa3p^KWs0Scrv2%ffBf%F4{o*%CegdJV$8b~>R9n4wp1Q_Q-R(J_3H_GN|F2{6 U{GH1! Date: Thu, 5 Dec 2024 23:04:13 +0100 Subject: [PATCH 140/208] transform: Move surface-parented objects along when the parent objects changes height or geometry. --- .../VPT/IMainRenderableComponent.cs | 2 ++ .../VPT/Light/LightComponent.cs | 2 -- .../VPT/MainRenderableComponent.cs | 20 +++++++++------- .../MetalWireGuide/MetalWireGuideComponent.cs | 6 ----- .../VPT/Playfield/PlayfieldComponent.cs | 2 -- .../VPT/Plunger/PlungerComponent.cs | 2 -- .../VPT/Ramp/RampComponent.cs | 24 +++++++++++++++---- .../VPT/Rubber/RubberComponent.cs | 2 -- .../VPT/Surface/SurfaceComponent.cs | 8 +++++-- 9 files changed, 40 insertions(+), 28 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs index 62f81fcec..ffec71b8f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs @@ -27,6 +27,8 @@ public interface IMainRenderableComponent : IMainComponent void UpdateTransforms(); void CopyFromObject(GameObject go); + + Transform transform { get; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index a378cfb8b..9b8e2fee7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -71,8 +71,6 @@ public class LightComponent : MainRenderableComponent, ILampDeviceCom public override bool HasProceduralMesh => false; - public override bool OverrideTransform => false; - protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = null; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 2dbe29ce1..ae12e73c6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -17,6 +17,7 @@ using System; using System.Collections.Generic; using System.Linq; +using FluentAssertions.Specialized; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -27,13 +28,7 @@ namespace VisualPinball.Unity public abstract class MainRenderableComponent : MainComponent, IMainRenderableComponent where TData : ItemData { - public virtual bool CanBeTransformed => true; - - public virtual bool OverrideTransform => true; - - //public abstract void SetTransform(Vector3 position, Vector3 scale, Quaternion rotation); public abstract void CopyFromObject(GameObject go); - //public virtual void SetTransform(Vector3 position, Vector3 scale, Quaternion rotation) { } ///

/// Component type of the child class. @@ -92,8 +87,6 @@ public UnityEngine.Mesh GetUnityMesh() return null; } - public virtual void OnPlayfieldHeightUpdated() => UpdateTransforms(); - protected void ParentToSurface(string surfaceName, Vertex2D center, Dictionary components) { if (!string.IsNullOrEmpty(surfaceName)) { @@ -163,6 +156,17 @@ private static void CopyMaterialName(MeshRenderer mr, string[] materialNames, st } } + protected void SetChildrenZPosition(Func getHeight) + { + var children = GetComponentsInChildren(); + foreach (var child in children) { + if (ReferenceEquals(child, this)) { + continue; + } + child.transform.SetZPosition(getHeight(child.transform.localPosition.TranslateToVpx())); + } + } + #region Tools protected static void MoveDragPointsTo(DragPointData[] dragPoints, Vector3 destination) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs index 3dc6dfbfe..f5e76519f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs @@ -104,12 +104,6 @@ private void Awake() #endregion - #region Transformation - - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - - #endregion - #region Conversion public override IEnumerable SetData(MetalWireGuideData data) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs index 0e7010e04..610aecdca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs @@ -71,8 +71,6 @@ public class PlayfieldComponent : MainRenderableComponent public override ItemType ItemType => ItemType.Playfield; public override string ItemName => "Playfield"; - public override bool CanBeTransformed => false; - public override bool HasProceduralMesh => true; public override TableData InstantiateData() => new TableData(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index fc280738f..a2656af7a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -105,8 +105,6 @@ public float4x4 TransformationWithinPlayfield [NonSerialized] private float4x4 _playfieldToWorld; - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public override void UpdateTransforms() { base.UpdateTransforms(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 366fa2052..3a4582bff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -116,8 +116,6 @@ public class RampComponent : MainRenderableComponent, IRampData, ISurf protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - #endregion #region Runtime @@ -146,11 +144,18 @@ private void Start() [NonSerialized] private float4x4 _playfieldToWorld; - public float Height(Vector2 pos) { + public override void UpdateTransforms() + { + base.UpdateTransforms(); + SetChildrenZPosition(center => Height(center, Vector3.zero)); + } + public float Height(Vector2 pos) => Height(pos, transform.localPosition.TranslateToVpx()); + + public float Height(Vector2 pos, Vector3 diff) { var vVertex = new RampMeshGenerator(this).GetCentralCurve(); var t = transform.localPosition.TranslateToVpx(); - Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x - t.x, pos.y - t.y), false, out var vOut, out var iSeg); + Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x - diff.x, pos.y - diff.y), false, out var vOut, out var iSeg); if (iSeg == -1) { return 0.0f; // Object is not on ramp path @@ -214,6 +219,17 @@ public float Height(Vector2 pos) { public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + public void UpdateChildrenTransforms() + { + var children = GetComponentsInChildren(); + foreach (var child in children) { + if (ReferenceEquals(child, this)) { + continue; + } + child.transform.SetZPosition(HeightTop); + } + } + #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index 1550d3d4d..94ae78911 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -112,8 +112,6 @@ private void Start() #region Transformation - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index a714a8bdb..d5496d350 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -94,11 +94,15 @@ private void Start() public float Height(Vector2 _) => HeightTop; - public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); + public override void UpdateTransforms() + { + base.UpdateTransforms(); + SetChildrenZPosition(_ => HeightTop); + } + #endregion #region Conversion From 9dea9322ab83b799f29616ab2e7fb34c5b6f81b3 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 5 Dec 2024 23:07:27 +0100 Subject: [PATCH 141/208] fix: Don't translate kicker z-position twice. --- .../VisualPinball.Unity/VPT/Kicker/KickerApi.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 7cee4435f..8ad417af8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -230,8 +230,8 @@ protected override void CreateColliders(ref ColliderReference colliders, // reduce the hit circle radius because only the inner circle of the kicker should start a hit event var radius = MainComponent.Radius * (ColliderComponent.LegacyMode ? ColliderComponent.FallThrough ? 0.75f : 0.6f : 1f); - colliders.Add(new CircleCollider(float2.zero, radius, MainComponent.Position.z, - MainComponent.Position.z + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle), translateWithinPlayfieldMatrix); + colliders.Add(new CircleCollider(float2.zero, radius, 0, + ColliderComponent.HitHeight, GetColliderInfo(), ColliderType.KickerCircle), translateWithinPlayfieldMatrix); } #endregion From 139d5dbb692ffce30faf1513c7f6461ae15fd844 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 6 Dec 2024 13:02:10 +0100 Subject: [PATCH 142/208] rubber: Fix thickness in generator. --- VisualPinball.Engine/VPT/Rubber/RubberMeshGenerator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VisualPinball.Engine/VPT/Rubber/RubberMeshGenerator.cs b/VisualPinball.Engine/VPT/Rubber/RubberMeshGenerator.cs index 59e24e81d..d6a4eb7df 100644 --- a/VisualPinball.Engine/VPT/Rubber/RubberMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Rubber/RubberMeshGenerator.cs @@ -80,7 +80,7 @@ private Mesh GetMesh(float playfieldHeight, float meshHeight, int detailLevel, i { var mesh = new Mesh(); // i dont understand the calculation of splineaccuracy here /cupiii - var accuracy = (int)(10.0f * 1.2f); + var accuracy = (int)(10.0f * 1.3f); if (acc != -1) { // hit shapes and UI display have the same, static, precision accuracy = acc; @@ -137,8 +137,8 @@ private Mesh GetMesh(float playfieldHeight, float meshHeight, int detailLevel, i var ringsY = new float[numSegments]; for (int i = 0; i < numSegments; i++) { - ringsX[i] = -1.0f * (float)System.Math.Sin(System.Math.PI * 2 * i / numSegments) * _data.Thickness; - ringsY[i] = -1.0f * (float)System.Math.Cos(System.Math.PI + System.Math.PI * 2 * i / numSegments) * _data.Thickness; + ringsX[i] = -1.0f * (float)System.Math.Sin(System.Math.PI * 2 * i / numSegments) * _data.Thickness * 0.5f; + ringsY[i] = -1.0f * (float)System.Math.Cos(System.Math.PI + System.Math.PI * 2 * i / numSegments) * _data.Thickness * 0.5f; } var verticesIndex = 0; From 51c7d9139dc0a7f4958ac84b0c9e4b5bce3132bc Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Dec 2024 00:23:19 +0100 Subject: [PATCH 143/208] assets: Move kicker meshes into the asset library. --- .../Assets/Art/Meshes/Kicker.meta | 8 - .../Assets/Art/Meshes/Kicker/Cup 1.mesh | 166 ------------------ .../Assets/Art/Meshes/Kicker/Cup 1.mesh.meta | 8 - .../Assets/Art/Meshes/Kicker/Cup 2.mesh | 166 ------------------ .../Assets/Art/Meshes/Kicker/Cup 2.mesh.meta | 8 - .../Assets/Art/Meshes/Kicker/Gottlieb.mesh | 166 ------------------ .../Art/Meshes/Kicker/Gottlieb.mesh.meta | 8 - .../Assets/Art/Meshes/Kicker/Hole.mesh | 166 ------------------ .../Assets/Art/Meshes/Kicker/Hole.mesh.meta | 8 - .../Assets/Art/Meshes/Kicker/Simple Hole.mesh | 166 ------------------ .../Art/Meshes/Kicker/Simple Hole.mesh.meta | 8 - .../Assets/Art/Meshes/Kicker/Williams.mesh | 166 ------------------ .../Art/Meshes/Kicker/Williams.mesh.meta | 8 - 13 files changed, 1052 deletions(-) delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh.meta delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh delete mode 100644 VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh.meta diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker.meta deleted file mode 100644 index 06ccdf90c..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d2a952a9541966f4aa044e5ecf845e53 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh deleted file mode 100644 index 9087fa7f7..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Cup 1 - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 774 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 373 - localAABB: - m_Center: {x: 0, y: 0, z: -0.423592} - m_Extent: {x: 1.215, y: 1.215, z: 0.6} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 0 - m_KeepIndices: 0 - m_IndexFormat: 0 - m_IndexBuffer: 00000100020003000400050006000700080007009300940009000700060007000900930006000a0009009500930009000b0009000a000900960095000c0009000b000900970096000d0009000c000900980097000e0009000d0009000e0098000f0010001100120010000f0012000f0015001400120015001300140015001600140013001600130019001800160019001700180019001a00180017001a0017001d001c001a001d001b001c001d001e001c001b001e001b00210020001e0021001f002000210020001f00440044001f007600200044007200720024004800230048002400220023002400250023002200250022004900ae0025004900ac00ae004900ab00ae00ac00ab00ac00ad00ad00fa00ab00ce00ca00fa00aa00ca00ce00aa00ce00fe00a800ca00aa00a800aa00a900a700a800a900a600a700a900a400a700a600a400a600a500a300a400a500a200a300a500a000a300a200a000a200a1009f00a000a1009e009f00a1009c009f009e009c009e009d0099009c009d009a0099009d0099009a009b0026002700280029002a002b002c002d002e002f003000310031004d002f0032003300340035003600370037004e003500380039003a003b003c003d003e003f0040004100420043004500460047004a004b004c004f005000510052005300540055005600570058005700560059005a005b005c005b005a005d005e005f0060005f005e0061006200630064006300620065006600670068006700660069006a006b006c006d006e006f007000710073007400750077007800790079007a0077007b007c007d007c007b007e007f00800081008200810080008300840085008500840086008700880089004c0188004b014b01880039014b0139014a013b014a0139013901880038013c013b013a013a013b013901390137013a0139013801370137013801360138013501360149013501380135014901480135013301360148014701350135013401330135014701340133013401320146013401470134013101320131013401460131012f013201460145013101310130012f013101450130012f0130012e0144013001450130012d012e012d01300144012d012b012e01440143012d012d012c012b012d0143012c012b012c012a0142012c0143012c0129012a0129012c014201290127012a0142014101290129012801270129014101280127012801260140012801410128012301260123012801400125012601230140013f012301230124012501240123013f013d0124013f01250124014e013d013e01240124014d014e0124013e014d0165014d013e014d014f014e014d01650166014d0150014f0150014d0166014f0150015201660167015001500151015201510150016701510153015201670168015101510154015301540151016801530154015601680169015401540155015601690155015401550169016a01550157015601580155016a01550158015701570158015a01580159015a016b015901580159015b015a0159016b016c0159015c015b015c0159016c015b015c015e016c016d015c015c015d015e016d015d015c015d015f015e015d016d016e015d0160015f015d016e0160015f01600162016e016f017001700160016e017001710160016001610162017101610160016301620161016101710174016401630161016101740172016401610172017201730164018a008b008c008d008e008f00900091009200af00b000b100b200b300b400b500b600b700b800b900ba00d500ba00b900bb00bc00bd00be00bf00c000d600c000bf00c100c200c300c400c500c600c700c800c900cb00cc00cd00cf00d000d100d200d300d400d700d800d900da00db00dc00dd00de00df00de00dd00e000e100e200e300e200e100e400e500e600e700e600e500e800e900ea00eb00ea00e900ec00ed00ee00ef00ee00ed00f000f100f200f300f400f500f600f700f800f900fb00fc00fd00ff000001010101010201ff00030104010501040103010601070108010901080107010a010b010c010d010e010c010b010f01100111011101100112011301140115011401220115011601170118011601180119011a011b011c011b011d011c011e011f01200121011f011e01 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 373 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 11936 - _typelessdata: 9659243e7a5078bfe2e7dfbe022b07bed6c57d3f000000806aa3f23e5709f6bd00000000b8c87dbfbc0283bf022b07bed6c57d3f000000800000003f99d6cebe00000080b8c87dbfe2e7dfbe022b07bed6c57d3f000000800000003f5709f6bd00000000b8c87dbfbc0283bfcb1007bed6c57d3f000000800000003f99d6cebe9659243e7a5078bfe2e7dfbecb1007bed6c57d3f000000806aa3f23e5709f6bd317c243e7a5078bfbc0283bfcb1007bed6c57d3f0000008088a0f23e99d6cebe0e9f243e016bd5bebc0283bf00000000000000000000803f8b6f743fde77dcbe00000000b8c87dbfbc0283bf00000000000000000000803fa7ae6c3fd9435bbe317c243e7a5078bfbc0283bf00000000000000000000803fe76d743f1b8263be00000000bd370638bc0283bf00000000000000000000803fa7ae6c3fef7116bfcf6b8c3ef0df9cbebc0283bf00000000000000000000803fd5e8793f09c6f1be0f7daf3ee36e10bebc0283bf00000000000000000000803f82367d3f2cd608bf1687a33ed410153dbc0283bf00000000000000000000803f16167c3fd8f319bf897c573e3fa9363ebc0283bf00000000000000000000803fd4d4763f05a627bf000000007dcb7c3ebc0283bf00000000000000000000803fa7ae6c3f4e412ebfea5a833efb2275bfe2e7dfbe00000000000000000000803f9ab3863e761823be0e9f243eb58a46bfe2e7dfbe00000000000000000000803fd55d793ec03d6fbe9659243e7a5078bfe2e7dfbe00000000000000000000803fad4f793e21e71dbee487b23e012f33bfe2e7dfbe00000000000000000000803f8256903e377087be0873333fe27333bfe2e7dfbe00000000000000000000803f732eb53eda3787bea27dfc3e10cf16bfe2e7dfbe00000000000000000000803f0d729f3e8c9f9ebe3bc6fd3e9fc85bbfe2e7dfbe00000000000000000000803f28b59f3e12874cbe069f1a3fe7a8e3bee2e7dfbe00000000000000000000803fc809ab3ea1d6bcbea722753f745e83bee2e7dfbe00000000000000000000803f5304d03e8c2de4be82752c3fc2888dbee2e7dfbe00000000000000000000803f5453b23e6c06e0be19c85b3fdac8fdbee2e7dfbe00000000000000000000803f9ca8c53e4e2ab2be098b323f2e71c4bde2e7dfbe00000000000000000000803fafcfb43e7ee402bfa722753f0f5e833ee2e7dfbe00000000000000000000803f5304d03e76c227bf82752c3f3141ad3de2e7dfbe00000000000000000000803f5453b23ec5c515bf86c87d3fbd3706b6e2e7dfbe00000000000000000000803fb98cd33ea7ec0cbf069f1a3f7270813ee2e7dfbe00000000000000000000803fc809ab3eab5d27bf0873333fb073333fe2e7dfbe00000000000000000000803f732eb53e603d56bfc958fd3eaa65cb3ee2e7dfbe00000000000000000000803fe09e9f3e467936bf19c85b3f75c8fd3ee2e7dfbe00000000000000000000803f9ca8c53e26c440bfea5a833ec922753fe2e7dfbe00000000000000000000803f9ab3863e301371bf2dce383e10e9133fe2e7dfbe00000000000000000000803f397d7d3e8e5a49bf6496d53ebb0b643fe2e7dfbe00000000000000000000803fd07f973ec2176abf0000000097fe193fe2e7dfbe00000000000000000000803f49bd573ed8d64bbf0e9f243eb58a46bfe2e7dfbe4ca68abeba492c3f6e34303fd55d793ec03d6fbee487b23e012f33bfe2e7dfbe4ca68abeba492c3f6e34303f8256903e377087be0e9f243e016bd5be6bbb49bf4ca68abeba492c3f6e34303fd55d793e44a8c2bea27dfc3e10cf16bfe2e7dfbe2cd4fabe60e5003ff931363f0d729f3e8c9f9ebecf6b8c3ef0df9cbe6bbb49bf2cd4fabe60e5003ff931363fb08d883ef8c1d9be0e9f243e016bd5be6bbb49bf2cd4fabe60e5003ff931363fd55d793e44a8c2bea27dfc3e10cf16bfe2e7dfbe499d10bfb6f3dd3ed3bc333f0d729f3e8c9f9ebe069f1a3fe7a8e3bee2e7dfbe499d10bfb6f3dd3ed3bc333fc809ab3ea1d6bcbecf6b8c3ef0df9cbe6bbb49bf499d10bfb6f3dd3ed3bc333fb08d883ef8c1d9be82752c3fc2888dbee2e7dfbe3d9b25bf4c37893e27c2363f5453b23e6c06e0be0f7daf3ee36e10be6bbb49bf3d9b25bf4c37893e27c2363f63b78f3e4758fcbecf6b8c3ef0df9cbe6bbb49bf3d9b25bf4c37893e27c2363fb08d883ef8c1d9be82752c3fc2888dbee2e7dfbe6abc34bfc976be3dd3bc333f5453b23e6c06e0be098b323f2e71c4bde2e7dfbe6abc34bfc976be3dd3bc333fafcfb43e7ee402bf0f7daf3ee36e10be6bbb49bf6abc34bfc976be3dd3bc333f63b78f3e4758fcbe82752c3f3141ad3de2e7dfbea5bd31bfec2fbbbd27c2363f5453b23ec5c515bf1687a33ed410153d6bbb49bfa5bd31bfec2fbbbd27c2363fe9458d3e1bbb10bf0f7daf3ee36e10be6bbb49bfa5bd31bfec2fbbbd27c2363f63b78f3e4758fcbe82752c3f3141ad3de2e7dfbe226c28bf1f858bbed3bc333f5453b23ec5c515bf069f1a3f7270813ee2e7dfbe226c28bf1f858bbed3bc333fc809ab3eab5d27bf1687a33ed410153d6bbb49bf226c28bf1f858bbed3bc333fe9458d3e1bbb10bfc958fd3eaa65cb3ee2e7dfbeb6f30dbf23dbd9be5917373fe09e9f3e467936bf897c573e3fa9363e6bbb49bfb6f30dbf23dbd9be5917373fe7e0813ec8941fbf1687a33ed410153d6bbb49bfb6f30dbf23dbd9be5917373fe9458d3e1bbb10bfe487b23ec712023fe2e7dfbeda1bdcbe772d11bf98dd333f8256903ef11042bf897c573e3fa9363e6bbb49bfda1bdcbe772d11bf98dd333fe7e0813ec8941fbf6496d53e6492e93ee2e7dfbeda1bdcbe772d11bf98dd333fd07f973e40a33cbf2dce383e10e9133fe2e7dfbe07ce59bee25827bfccee393f397d7d3e8e5a49bf000000007dcb7c3e6bbb49bf07ce59bee25827bfccee393f49bd573e9dbe26bf897c573e3fa9363e6bbb49bf07ce59bee25827bfccee393fe7e0813ec8941fbfc958fd3e6492e93ee2e7dfbe00000000000000000000803fe09e9f3e40a33cbf2dce383e10e9133fe2e7dfbe5bd3bcbdc13933bf7d3f353f397d7d3e8e5a49bf0000000097fe193fe2e7dfbe5bd3bcbdc13933bf7d3f353f49bd573ed8d64bbf000000007dcb7c3e6bbb49bf5bd3bcbdc13933bf7d3f353f49bd573e9dbe26bfe487b23ec712023fe2e7dfbe00000000000000000000803f8256903ef11042bf0000000086c87d3fe2e7dfbe00000000000000000000803f49bd573ea69b74bf0e9f243e016bd5be6bbb49bf371ae0bebc05123f85eb313fd55d793e44a8c2bee487b23e012f33bfe2e7dfbe371ae0bebc05123f85eb313f8256903e377087bea27dfc3e10cf16bfe2e7dfbe371ae0bebc05123f85eb313f0d729f3e8c9f9ebe069f1a3fe7a8e3bee2e7dfbe3d9b25bf4c37893e27c2363fc809ab3ea1d6bcbe098b323f2e71c4bde2e7dfbea5bd31bfec2fbbbd27c2363fafcfb43e7ee402bf069f1a3f7270813ee2e7dfbe69000fbf992ad8be27c2363fc809ab3eab5d27bfc958fd3eaa65cb3ee2e7dfbe69000fbf992ad8be27c2363fe09e9f3e467936bf1687a33ed410153d6bbb49bf69000fbf992ad8be27c2363fe9458d3e1bbb10bf897c573e3fa9363e6bbb49bf4c3789becba125bf27c2363fe7e0813ec8941fbfe487b23ec712023fe2e7dfbe4c3789becba125bf27c2363f8256903ef11042bf2dce383e10e9133fe2e7dfbe4c3789becba125bf27c2363f397d7d3e8e5a49bfcf6b8c3ef0df9cbe6bbb49bfce8832bfa779373f0000008043c9503ffab551bfcf6b8c3ef0df9cbebc0283bfce8832bfa779373f0000008043c9503f93df7ebf0e9f243e016bd5be6bbb49bfce8832bfa779373f00000080e1b45c3ffab551bf0e9f243e016bd5bebc0283bfce8832bfa779373f00000080e1b45c3f93df7ebf0f7daf3ee36e10be6bbb49bfb6846cbf41f1c33e000000800663483ffab551bf0f7daf3ee36e10bebc0283bfb6846cbf41f1c33e000000800663483f93df7ebfcf6b8c3ef0df9cbe6bbb49bfb6846cbf41f1c33e0000008043c9503ffab551bfcf6b8c3ef0df9cbebc0283bfb6846cbf41f1c33e0000008043c9503f93df7ebf1687a33ed410153d6bbb49bf64cc7dbfcba105be000000804f1f413ffab551bf1687a33ed410153dbc0283bf64cc7dbfcba105be000000804f1f413f93df7ebf0f7daf3ee36e10be6bbb49bf64cc7dbfcba105be000000800663483ffab551bf0f7daf3ee36e10bebc0283bf64cc7dbfcba105be000000800663483f93df7ebf897c573e3fa9363e6bbb49bfb5154bbf51da1bbf00000080ee433a3ffab551bf897c573e3fa9363ebc0283bfb5154bbf51da1bbf00000080ee433a3f93df7ebf1687a33ed410153d6bbb49bfb5154bbf51da1bbf000000804f1f413ffab551bf1687a33ed410153dbc0283bfb5154bbf51da1bbf000000804f1f413f93df7ebf000000007dcb7c3e6bbb49bfc9769ebe2f6e73bf00000080fd15323ffab551bf000000007dcb7c3ebc0283bfc9769ebe2f6e73bf00000080fd15323f93df7ebf897c573e3fa9363e6bbb49bfc9769ebe2f6e73bf00000080ee433a3ffab551bf897c573e3fa9363ebc0283bfc9769ebe2f6e73bf00000080ee433a3f93df7ebf9659243e7a5078bfe2e7dfbe000080bf3480b73a17b751b9d4487b3fc9e537bf0e9f243eb58a46bfe2e7dfbe000080bf3480b73a17b751b9d044703fc9e537bf317c243e7a5078bfbc0283bf000080bf3480b73a17b751b9a6477b3f93df7ebf317c243e7a5078bfbc0283bf000080bf17b7513917b75139a6477b3f93df7ebf0e9f243eb58a46bfe2e7dfbe000080bf17b7513917b75139d044703fc9e537bf0e9f243e016bd5be6bbb49bf000080bf17b7513917b75139e1b45c3ffab551bf317c243e7a5078bfbc0283bf000080bf17b7513900000080a6477b3f93df7ebf0e9f243e016bd5be6bbb49bf000080bf17b7513900000080e1b45c3ffab551bf0e9f243e016bd5bebc0283bf000080bf17b7513900000080e1b45c3f93df7ebf6496d53e6492e93ee2e7dfbe00000000000000000000803fd07f973e40a33cbf6496d53e6492e93ee2e7dfbe2d43dcbece1911bf98dd333fd07f973e40a33cbf897c573e3fa9363e6bbb49bf2d43dcbece1911bf98dd333fe7e0813ec8941fbfc958fd3eaa65cb3ee2e7dfbe2d43dcbece1911bf98dd333fe09e9f3e467936bfc958fd3e5cc85b3fe2e7dfbe00000000000000000000803fe09e9f3e89b766bf6496d53e08743e3fbc9505be000080bf000000000000008023f53e3f7adf2cbf6496d53ebb0b643fbc9505be000080bf000000000000008023f53e3fdba33fbf6496d53ebb0b643fe2e7dfbe000080bf0000000000000080a08a2f3fdba33fbf6496d53e6492e93ee2e7dfbe000080bf0000000000000080a08a2f3f282706bfc958fd3e5cc85b3fe2e7dfbe0000803f0000000000000080f4dc523f12be3bbfc958fd3e08743e3fbc9505be0000803f00000000000000807172433f7adf2cbfc958fd3e6492e93ee2e7dfbe0000803f0000000000000080f4dc523f861d02bfc958fd3e5cc85b3fbc9505be0000803f00000000000000807172433f12be3bbfc958fd3e6492e93ee2e7dfbe00000000f5b93abfa01a2f3f7172433f24ef0cbfc958fd3e08743e3fbc9505be00000000f5b93abfa01a2f3f7172433f7adf2cbf6496d53e6492e93ee2e7dfbe00000000f5b93abfa01a2f3f23f53e3f24ef0cbf6496d53e08743e3fbc9505be00000000f5b93abfa01a2f3f23f53e3f7adf2cbfc958fd3e08743e3fbc9505be00000000000000000000803f7172433f7adf2cbfc958fd3e5cc85b3fbc9505be00000000000000000000803f7172433f12be3bbf6496d53e08743e3fbc9505be00000000000000000000803f23f53e3f7adf2cbf6496d53ebb0b643fbc9505be00000000000000000000803f23f53e3fdba33fbfc958fd3e5cc85b3f97ac8a3a6f81c4bef1636cbf000000800e6baa3da54c2abd6496d53ebb0b643fbc9505be6f81c4bef1636cbf0000008072c08e3d17f373bdc958fd3e5cc85b3fbc9505be6f81c4bef1636cbf000000800e6baa3d17f373bda72275bf745e83bee2e7dfbe64cc7d3fcba1053e000000803f55353f5709f6bd86c87dbfbd3706b6e2e7dfbe64cc7d3fcba1053e000000800000403f5709f6bd86c87dbfbd3706b697ac8a3a64cc7d3fcba1053e000000800000403fa54c2abd00000000b8c87dbfbc0283bf022b073ed6c57d3f000000800000003f99d6cebe965924be7a5078bfe2e7dfbe022b073ed6c57d3f000000804bae063f5709f6bd00000080b8c87dbfe2e7dfbe022b073ed6c57d3f000000800000003f5709f6bd965924be7a5078bfe2e7dfbecb10073ed6c57d3f000000804bae063f5709f6bd00000000b8c87dbfbc0283bfcb10073ed6c57d3f000000800000003f99d6cebe317c24be7a5078bfbc0283bfcb10073ed6c57d3f00000080bcaf063f99d6cebe0e9f24be016bd5bebc0283bf00000000000000000000803fd5ed643fde77dcbe317c24be7a5078bfbc0283bf00000000000000000000803fd5ed643f1b8263becf6b8cbef0df9cbebc0283bf00000000000000000000803f79745f3f09c6f1be0f7dafbee36e10bebc0283bf00000000000000000000803fdd265c3f2cd608bf1687a3bed410153dbc0283bf00000000000000000000803f49475d3fd8f319bf897c57be3fa9363ebc0283bf00000000000000000000803f8b88623f05a627bf0e9f24beb58a46bfe2e7dfbe00000000000000000000803fbd1c363ec03d6fbeea5a83befb2275bfe2e7dfbe00000000000000000000803fa113223e761823be965924be7a5078bfe2e7dfbe00000000000000000000803fe52a363e21e71dbee487b2be012f33bfe2e7dfbe00000000000000000000803f8ecd0e3e377087be3bc6fdbe9fc85bbfe2e7dfbe00000000000000000000803f8120e03d12874cbe087333bfe27333bfe2e7dfbe00000000000000000000803f583b8a3dda3787bea27dfcbe10cf16bfe2e7dfbe00000000000000000000803ff12ce13d8c9f9ebe069f1abfe7a8e3bee2e7dfbe00000000000000000000803f03ceb23da1d6bcbe19c85bbfdac8fdbee2e7dfbe00000000000000000000803f6aa5103d4e2ab2bea72275bf745e83bee2e7dfbe00000000000000000000803fb01e773c8c2de4be82752cbfc2888dbee2e7dfbe00000000000000000000803fd2a7953d6c06e0be098b32bf2e71c4bde2e7dfbe00000000000000000000803f67b68b3d7ee402bf86c87dbfbd3706b6e2e7dfbe00000000000000000000803ffd11063ca7ec0cbfa72275bf0f5e833ee2e7dfbe00000000000000000000803fb01e773c76c227bf82752cbf3141ad3de2e7dfbe00000000000000000000803fd2a7953dc5c515bf069f1abf7270813ee2e7dfbe00000000000000000000803f03ceb23dab5d27bf19c85bbf75c8fd3ee2e7dfbe00000000000000000000803f6aa5103d26c440bf087333bfb073333fe2e7dfbe00000000000000000000803f583b8a3d603d56bfe487b2bec712023fe2e7dfbe00000000000000000000803f8ecd0e3ef11042bfea5a83bec922753fe2e7dfbe00000000000000000000803fa113223e301371bf6496d5bebb0b643fe2e7dfbe00000000000000000000803f357b003ec2176abf2dce38be10e9133fe2e7dfbe00000000000000000000803f59fd313e8e5a49bfe487b2be012f33bfe2e7dfbe4ca68a3eba492c3f6e34303f8ecd0e3e377087be0e9f24beb58a46bfe2e7dfbe4ca68a3eba492c3f6e34303fbd1c363ec03d6fbe0e9f24be016bd5be6bbb49bf4ca68a3eba492c3f6e34303fbd1c363e44a8c2becf6b8cbef0df9cbe6bbb49bf2cd4fa3e60e5003ff931363f315f1e3ef8c1d9bea27dfcbe10cf16bfe2e7dfbe2cd4fa3e60e5003ff931363ff12ce13d8c9f9ebe0e9f24be016bd5be6bbb49bf2cd4fa3e60e5003ff931363fbd1c363e44a8c2be069f1abfe7a8e3bee2e7dfbe499d103fb6f3dd3ed3bc333f03ceb23da1d6bcbea27dfcbe10cf16bfe2e7dfbe499d103fb6f3dd3ed3bc333ff12ce13d8c9f9ebecf6b8cbef0df9cbe6bbb49bf499d103fb6f3dd3ed3bc333f315f1e3ef8c1d9be0f7dafbee36e10be6bbb49bf3d9b253f4c37893e27c2363fcc0b103e4758fcbe82752cbfc2888dbee2e7dfbe3d9b253f4c37893e27c2363fd2a7953d6c06e0becf6b8cbef0df9cbe6bbb49bf3d9b253f4c37893e27c2363f315f1e3ef8c1d9be098b32bf2e71c4bde2e7dfbe6abc343fc976be3dd3bc333f67b68b3d7ee402bf82752cbfc2888dbee2e7dfbe6abc343fc976be3dd3bc333fd2a7953d6c06e0be0f7dafbee36e10be6bbb49bf6abc343fc976be3dd3bc333fcc0b103e4758fcbe1687a3bed410153d6bbb49bfa5bd313fec2fbbbd27c2363fbfee143e1bbb10bf82752cbf3141ad3de2e7dfbea5bd313fec2fbbbd27c2363fd2a7953dc5c515bf0f7dafbee36e10be6bbb49bfa5bd313fec2fbbbd27c2363fcc0b103e4758fcbe069f1abf7270813ee2e7dfbe226c283f1f858bbed3bc333f03ceb23dab5d27bf82752cbf3141ad3de2e7dfbe226c283f1f858bbed3bc333fd2a7953dc5c515bf1687a3bed410153d6bbb49bf226c283f1f858bbed3bc333fbfee143e1bbb10bf897c57be3fa9363e6bbb49bfb6f30d3f23dbd9be5917373f06b92b3ec8941fbfc958fdbeaa65cb3ee2e7dfbeb6f30d3f23dbd9be5917373fa379e03d467936bf1687a3bed410153d6bbb49bfb6f30d3f23dbd9be5917373fbfee143e1bbb10bf897c57be3fa9363e6bbb49bfda1bdc3e772d11bf98dd333f06b92b3ec8941fbfe487b2bec712023fe2e7dfbeda1bdc3e772d11bf98dd333f8ecd0e3ef11042bf6496d5be6492e93ee2e7dfbeda1bdc3e772d11bf98dd333f357b003e40a33cbfc958fdbeaa65cb3ee2e7dfbe00000000000000000000803fa379e03d467936bf000000007dcb7c3e6bbb49bf07ce593ee25827bfccee393f49bd573e9dbe26bf2dce38be10e9133fe2e7dfbe07ce593ee25827bfccee393f59fd313e8e5a49bf897c57be3fa9363e6bbb49bf07ce593ee25827bfccee393f06b92b3ec8941fbfc958fdbe6492e93ee2e7dfbe00000000000000000000803fa379e03d40a33cbf0000000097fe193fe2e7dfbe5bd3bc3dc13933bf7d3f353f49bd573ed8d64bbf2dce38be10e9133fe2e7dfbe5bd3bc3dc13933bf7d3f353f59fd313e8e5a49bf000000007dcb7c3e6bbb49bf5bd3bc3dc13933bf7d3f353f49bd573e9dbe26bfe487b2be012f33bfe2e7dfbe371ae03ebc05123f85eb313f8ecd0e3e377087be0e9f24be016bd5be6bbb49bf371ae03ebc05123f85eb313fbd1c363e44a8c2bea27dfcbe10cf16bfe2e7dfbe371ae03ebc05123f85eb313ff12ce13d8c9f9ebe069f1abfe7a8e3bee2e7dfbe3d9b253f4c37893e27c2363f03ceb23da1d6bcbe098b32bf2e71c4bde2e7dfbea5bd313fec2fbbbd27c2363f67b68b3d7ee402bfc958fdbeaa65cb3ee2e7dfbe69000f3f992ad8be27c2363fa379e03d467936bf069f1abf7270813ee2e7dfbe69000f3f992ad8be27c2363f03ceb23dab5d27bf1687a3bed410153d6bbb49bf69000f3f992ad8be27c2363fbfee143e1bbb10bfe487b2bec712023fe2e7dfbe4c37893ecba125bf27c2363f8ecd0e3ef11042bf897c57be3fa9363e6bbb49bf4c37893ecba125bf27c2363f06b92b3ec8941fbf2dce38be10e9133fe2e7dfbe4c37893ecba125bf27c2363f59fd313e8e5a49bf0e9f24be016bd5bebc0283bfce88323fa779373f00000080e947073f93df7ebfcf6b8cbef0df9cbe6bbb49bfce88323fa779373f000000808733133ffab551bf0e9f24be016bd5be6bbb49bfce88323fa779373f00000080d847073ffab551bfcf6b8cbef0df9cbebc0283bfce88323fa779373f000000808733133f93df7ebfcf6b8cbef0df9cbebc0283bfb6846c3f41f1c33e000000808733133f93df7ebf0f7dafbee36e10be6bbb49bfb6846c3f41f1c33e00000080c4991b3ffab551bfcf6b8cbef0df9cbe6bbb49bfb6846c3f41f1c33e000000808733133ffab551bf0f7dafbee36e10bebc0283bfb6846c3f41f1c33e00000080c4991b3f93df7ebf0f7dafbee36e10bebc0283bf64cc7d3fcba105be00000080c4991b3f93df7ebf1687a3bed410153d6bbb49bf64cc7d3fcba105be000000808bdd223ffab551bf0f7dafbee36e10be6bbb49bf64cc7d3fcba105be00000080c4991b3ffab551bf1687a3bed410153dbc0283bf64cc7d3fcba105be000000808bdd223f93df7ebf1687a3bed410153dbc0283bfb5154b3f51da1bbf000000808bdd223f93df7ebf897c57be3fa9363e6bbb49bfb5154b3f51da1bbf00000080edb8293ffab551bf1687a3bed410153d6bbb49bfb5154b3f51da1bbf000000808bdd223ffab551bf897c57be3fa9363ebc0283bfb5154b3f51da1bbf00000080edb8293f93df7ebf897c57be3fa9363ebc0283bfc9769e3e2f6e73bf00000080edb8293f93df7ebf000000007dcb7c3e6bbb49bfc9769e3e2f6e73bf00000080fd15323ffab551bf897c57be3fa9363e6bbb49bfc9769e3e2f6e73bf00000080edb8293ffab551bf000000007dcb7c3ebc0283bfc9769e3e2f6e73bf00000080fd15323f93df7ebf0e9f24beb58a46bfe2e7dfbe0000803f3480b73a17b751b9f46fe73ec9e537bf965924be7a5078bfe2e7dfbe0000803f3480b73a17b751b90e68d13ec9e537bf317c24be7a5078bfbc0283bf0000803f3480b73a17b751b96a6ad13e93df7ebf0e9f24beb58a46bfe2e7dfbe0000803f17b7513917b75139f46fe73ec9e537bf317c24be7a5078bfbc0283bf0000803f17b7513917b751396a6ad13e93df7ebf0e9f24be016bd5be6bbb49bf0000803f17b7513917b75139d847073ffab551bf0e9f24be016bd5be6bbb49bf0000803f17b7513900000000d847073ffab551bf317c24be7a5078bfbc0283bf0000803f17b75139000000006a6ad13e93df7ebf0e9f24be016bd5bebc0283bf0000803f17b7513900000000e947073f93df7ebf6496d5be6492e93ee2e7dfbe00000000000000000000803f357b003e40a33cbf897c57be3fa9363e6bbb49bf2d43dc3ece1911bf98dd333f06b92b3ec8941fbf6496d5be6492e93ee2e7dfbe2d43dc3ece1911bf98dd333f357b003e40a33cbfc958fdbeaa65cb3ee2e7dfbe2d43dc3ece1911bf98dd333fa379e03d467936bfc958fdbe5cc85b3fe2e7dfbe00000000000000000000803fa379e03d89b766bf6496d5be08743e3fbc9505be0000803f00000000000000807172433f289cbdbe6496d5be6492e93ee2e7dfbe0000803f0000000000000080f4dc523f085760be6496d5bebb0b643fe2e7dfbe0000803f0000000000000080f4dc523fea24e3be6496d5bebb0b643fbc9505be0000803f00000000000000807172433fea24e3bec958fdbe08743e3fbc9505be000080bf000000000000008023f53e3f289cbdbec958fdbe5cc85b3fe2e7dfbe000080bf0000000000000080a08a2f3f5859dbbec958fdbe6492e93ee2e7dfbe000080bf0000000000000080a08a2f3f7f3050bec958fdbe5cc85b3fbc9505be000080bf000000000000008023f53e3f5859dbbe6496d5be08743e3fbc9505be00000000f5b93abfa01a2f3f7172433f289cbdbec958fdbe6492e93ee2e7dfbe00000000f5b93abfa01a2f3f23f53e3ff7767bbe6496d5be6492e93ee2e7dfbe00000000f5b93abfa01a2f3f7172433ff7767bbec958fdbe08743e3fbc9505be00000000f5b93abfa01a2f3f23f53e3f289cbdbe6496d5bebb0b643fbc9505be00000000000000000000803f7172433fea24e3bec958fdbe08743e3fbc9505be00000000000000000000803f23f53e3f289cbdbe6496d5be08743e3fbc9505be00000000000000000000803f7172433f289cbdbec958fdbe5cc85b3fbc9505be00000000000000000000803f23f53e3f5859dbbe240c83bddc676dbfa47203be00000000c286673f6c78da3ed6c8163ff06af1be6f48833ddc676dbfa47203be00000000c286673f6c78da3ee95f1a3ff06af1be240c83bd6d0303bf733081bf00000000c286673f6c78da3ed6c8163f4ab81cbf6f48833d6d0303bf733081bf00000000c286673f6c78da3e13661a3f4cc31cbf6f48833ddc676dbfa47203be000080bf0000000000000080280c263f6b7df1be6f48833d6d0303bf733081bf000080bf000000000000008013661a3f4cc31cbfe947833d3e9775bf100583bf000080bf000000000000008088f1263f0e134dbf240c83bddc676dbfa47203be00000000aed87f3f2a3a12bdd6c8163ff06af1be6f48833ddc676dbfa47203be00000000aed87f3f2a3a12bde95f1a3ff06af1bee947833d3e9775bf100583bf00000000aed87f3f2a3a12bde95f1a3f7f8411be240c83bd3e9775bf100583bf00000000aed87f3f2a3a12bdd6c8163f7f8411be240c83bddc676dbfa47203be0000803f0000000000000080d00f0b3ff375f1be240c83bd3e9775bf100583bf0000803f00000000000000805e2a0a3f520f4dbf240c83bd6d0303bf733081bf0000803f0000000000000080d6c8163f4ab81cbfaa0c83bd5c0303bf100583bf0000803f0000000000000080d6c8163ffb074dbf240c83bd6d0303bf733081bf00000000000080bf00000080d6c8163f4ab81cbfe947833d5c0303bf100583bf00000000000080bf0000008013661a3f0e134dbf6f48833d6d0303bf733081bf00000000000080bf0000008013661a3f4cc31cbfaa0c83bd5c0303bf100583bf00000000000080bf00000080d6c8163ffb074dbfe947833d5c0303bf100583bf000080bf000000000000008013661a3f0e134dbfea5a833efb2275bf97ac8a3ad6566cbe287e5c3f30bbe73e69abea3ea54c2abd00000080b8c87dbf97ac8a3a000000007446643f30bbe73e0000003fa54c2abd000000801f859bbf4ca4343e00000080b6f31d3fd578493f0000003fa69bc4bac1fda03e863896bf4ca4343e668823be7593183fd578493f47abea3ea69bc4bacf831b3f2daf86bf4ca4343eb6f39dbee3c7083fd578493fe755d53ea69bc4ba3bc6fd3e9fc85bbf97ac8a3ae63fe4bee6ae453f30bbe73e0856d53ea54c2abd0873333fe27333bf97ac8a3a736821bf7368213f30bbe73e8600c03ea54c2abd7cef5b3f45f05bbf4ca4343eb762dfbeb762df3ed578493f8600c03ea69bc4ba04af863f1f851bbf4ca4343ee3c708bfb6f39d3ed578493f04abaa3ea69bc4ba19c85b3fdac8fdbe97ac8a3ae6ae45bfe63fe43e30bbe73e04abaa3ea54c2abda722753f745e83be97ac8a3a287e5cbfd6566c3e30bbe73e8255953ea54c2abd7e38963f8e01a1be4ca4343e759318bf6688233ed578493f8255953ea69bc4ba1f859b3f000000804ca4343eb6f31dbf00000080d578493f0000803ea69bc4ba86c87d3fbd3706b697ac8a3a744664bf0000000030bbe73e0000803ea54c2abda722753f0f5e833e97ac8a3a287e5cbfd6566cbe30bbe73efc54553ea54c2abd7e38963f8e01a13e4ca4343e759318bf668823bed578493ffc54553ea69bc4ba04af863f1f851b3f4ca4343ee3c708bfb6f39dbed578493ff8a92a3ea69bc4ba19c85b3f75c8fd3e97ac8a3ae6ae45bfe63fe4be30bbe73ef8a92a3ea54c2abd0873333fb073333f97ac8a3a933a21bfc58f21bf4bc8e73ee7fdff3da54c2abd7cef5b3f45f05b3f4ca4343e492edfbeb762dfbef085493fe7fdff3da69bc4bacf831b3f2daf863f4ca4343e9be69dbe90a008bf0c93493f65a8aa3da69bc4bac958fd3e5cc85b3f97ac8a3a5474e4be17d93ebfa470fd3e0e6baa3da54c2abdea5a833ec922753f97ac8a3ab1bf6cbe0d715cbf4bc8e73ebaa42a3da54c2abdc1fda03e8638963f4ca4343e668823be759318bfd578493fc6a52a3da69bc4ba0000008086c87d3f97ac8a3a00000000744664bf30bbe73e00000000a54c2abd000000001f859b3f4ca4343e00000000b6f31dbfd578493f00000000a69bc4ba9659243e7a5078bfe2e7dfbeddb504be7fd97d3ffaed6b3a6aa3f23e5709f6bd00000080b8c87dbfe2e7dfbe17b751b90000803f52499d390000003f5709f6bdea5a833efb2275bfe2e7dfbeaf2584be5452773f6f12033a69abea3e5709f6bd3bc6fd3e9fc85bbfe2e7dfbe000000bf2db25d3f000000800856d53e5709f6bd0873333fe27333bfe2e7dfbe810435bf8104353f000000808600c03e5709f6bd19c85b3fdac8fdbee2e7dfbe2db25dbf0000003f0000008004abaa3e5709f6bda722753f745e83bee2e7dfbe394577bf6f81843e000000808255953e5709f6bd86c87d3fbd3706b6e2e7dfbe000080bf00000000000000800000803e5709f6bda722753f0f5e833ee2e7dfbe394577bf6f8184be00000080fc54553e5709f6bd19c85b3f75c8fd3ee2e7dfbe2db25dbf000000bf00000080f8a92a3e5709f6bd0873333fb073333fe2e7dfbea1d634bf613235bf00000080e7fdff3d5709f6bdc958fd3e5cc85b3fe2e7dfbe76711bbfe86a4bbf000000800e6baa3d5709f6bdc958fd3e5cc85b3fbc9505be76711bbfe86a4bbf000000800e6baa3d17f373bd0000000086c87d3fe2e7dfbe00000000000080bf00000080000000005709f6bdea5a833ec922753fe2e7dfbe14d084beab3e77bf00000080baa42a3d5709f6bd6496d53ebb0b643fe2e7dfbe6f81c4bef1636cbf0000008072c08e3d5709f6bdea5a83befb2275bf97ac8a3ad6566c3e287e5c3f30bbe73e4caa0a3fa54c2abdc1fda0be863896bf4ca4343e6688233e7593183fd578493f5caa0a3fa69bc4bacf831bbf2daf86bf4ca4343eb6f39d3ee3c7083fd578493f0d55153fa69bc4ba3bc6fdbe9fc85bbf97ac8a3ae63fe43ee6ae453f30bbe73efc54153fa54c2abd087333bfe27333bf97ac8a3a7368213f7368213f30bbe73ebdff1f3fa54c2abd7cef5bbf45f05bbf4ca4343eb762df3eb762df3ed578493fbdff1f3fa69bc4ba04af86bf1f851bbf4ca4343ee3c7083fb6f39d3ed578493f7eaa2a3fa69bc4ba19c85bbfdac8fdbe97ac8a3ae6ae453fe63fe43e30bbe73e7eaa2a3fa54c2abda72275bf745e83be97ac8a3a287e5c3fd6566c3e30bbe73e3f55353fa54c2abd7e3896bf8e01a1be4ca4343e7593183f6688233ed578493f3f55353fa69bc4ba1f859bbf000000804ca4343eb6f31d3f00000080d578493f0000403fa69bc4ba86c87dbfbd3706b697ac8a3a89d25e3ff4fd54bc88f4fb3e0000403fa54c2abda72275bf0f5e833e97ac8a3a287e5c3fd6566cbe30bbe73ec1aa4a3fa54c2abd7e3896bf8e01a13e4ca4343e7593183f668823bed578493fc1aa4a3fa69bc4ba04af86bf1f851b3f4ca4343ee3c7083fb6f39dbed578493f8255553fa69bc4ba19c85bbf75c8fd3e97ac8a3ae6ae453fe63fe4be30bbe73e8255553fa54c2abd087333bfb073333f97ac8a3a2041213faa8221bf67d5e73e4300603fa54c2abd7cef5bbf45f05b3f4ca4343eb762df3eb762dfbed578493f4300603fa69bc4bacf831bbf2daf863f4ca4343e48bf9d3e39b408bf7e8c493ff3aa6a3fa69bc4bac958fdbe5cc85b3f97ac8a3a5dfee33e8fc245bf4bc8e73e9eb26a3fa54c2abdea5a83bec922753f97ac8a3ae8d96c3e0d715cbf4bc8e73ea455753fa54c2abdc1fda0be8638963f4ca4343e9ca2233e598618bf637f493fa455753fa69bc4ba000000001f859b3f4ca4343e00000000b6f31dbfd578493f0000803fa69bc4ba0000008086c87d3f97ac8a3a00000000744664bf30bbe73e0000803fa54c2abd965924be7a5078bfe2e7dfbe5d6d053ef2d27d3f6f12033a4bae063f5709f6bdea5a83befb2275bfe2e7dfbe41f1833ee258773f000000804caa0a3f5709f6bd3bc6fdbe9fc85bbfe2e7dfbe0000003f2db25d3f00000080fc54153f5709f6bd087333bfe27333bfe2e7dfbe8104353f8104353f00000080bdff1f3f5709f6bd19c85bbfdac8fdbee2e7dfbe2db25d3f0000003f000000807eaa2a3f5709f6bda72275bf745e83bee2e7dfbe2575723f014da43e000000803f55353f5709f6bd86c87dbfbd3706b6e2e7dfbe64cc7d3fcba105be000000800000403f5709f6bda72275bf0f5e833ee2e7dfbe3945773f6f8184be00000080c1aa4a3f5709f6bd19c85bbf75c8fd3ee2e7dfbe2db25d3f000000bf000000808255553f5709f6bd087333bfb073333fe2e7dfbea1d6343f613235bf000000804300603f5709f6bdc958fdbe5cc85b3fe2e7dfbe76711b3fe86a4bbf000000809eb26a3f5709f6bdc958fdbe5cc85b3fbc9505be2c65093f470358bf000000809eb26a3f17f373bd6496d5bebb0b643fbc9505be6f81c43ef1636cbf00000080f2276e3f17f373bdea5a83bec922753fe2e7dfbe14d0843eab3e77bf00000080a455753f5709f6bd0000000086c87d3fe2e7dfbe00000000000080bf000000800000803f5709f6bd6496d5bebb0b643fe2e7dfbe6f81c43ef1636cbf00000080f2276e3f5709f6bd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: -0.423592} - m_Extent: {x: 1.215, y: 1.215, z: 0.6} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh.meta deleted file mode 100644 index 4c5c39b28..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 1.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bb56bbc5a2df3ab4095d0a24ddad5fa0 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh deleted file mode 100644 index db5c926f2..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Cup 2 - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 2094 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 657 - localAABB: - m_Center: {x: 0.0014860034, y: 0.0055050254, z: -0.31478402} - m_Extent: {x: 1.003061, y: 1.003104, z: 0.321336} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 0 - m_KeepIndices: 0 - m_IndexFormat: 0 - m_IndexBuffer: 6e006f00700071006f006e0000000100020003000100000001001500020004000300000002001500140005000300040015001300140006000500040014001300120007000500060013001100120008000500070012001100100009000800070011000f0010000a000900070010000f000e000b0009000a000f000d000e000c000b000a000e000d000c000d000b000c001600170018001900170016001a00180017001900160061001a0063001800610016001c00670061001c001a0062006300640062001a00650062006400660065006400630067001b001b0067001c0063001b00180016001b001c0018001b0016001d001e001f0046001e00470048001e0046001d001f0024004e001e0048001f00210024001f001e004f002400210023004f001e004e0020001e001d001d00250020002100220023004e0022004f0020002500260025002300260023002200260022004e00260026004e0030004e004c0030004c002d0030004d002d004c004d004c002a002a004c00490027004d002a0049004a002a004b004a0049002a002800270030002d002c002700280029002c002f00300030002f0028002f00290028002b002c002d00270029002e002e002b002d0029002b002e0031003200330037003300320034003100330038003700320035003100340039003700380036003500340038003a0039003400440036003b003a0038004400430036003b003c003a003600430040003d003c003b004300420040003e003c003d0040004200410041003e0040003f003e003d0040003e003f003f0045004000500051005200530050005200520051005f005400530052005f00510060005500530054005f0060005e005600550054005f005e005b005700550056005e005d005b005800570056005d005c005b005900580056005c005a005b0059005a0058005b005a005900680069006a006b00690068006c006b0068006d006b006c007200730074007300770074007500730072007500780073007900780075007a007800790076007500720072007b00760076007b007c007d007e007f0080007e007d0081007e00800080007d00ab0082007e008100ab007d00aa008300820081008400820083008500820084008600850084008700850086007d00a800aa007d00ac00a800aa00a800a900a900a800a700a800a400a700a700a400a600a600a400a500a500a400a300a400a100a300a300a100a0009e00a000a100a200a0009e00ad00a2009e00ae00ad009e009e00af00ae009d00af009e009a009d009e009f009d009a00b0009f009a009a00b100b0009c00b1009a009c009a00980098009a009b0099009c0098009900980095008800940085008800850087008900880087008a00880089008b0088008a008c0088008b008d0088008c008e0088008d008f008e008d0090008e008f0091008e00900092008e009100930092009100950092009300960092009500970096009500950098009700c00096009700c000c1009600c10092009600be00c000970097009800be00c100c2009200be009800bf00c900bf009800ca00c9009800cb00ca0098008101c900ca00cc00ca00cb008201c9008101cd00cc00cb008301820181010201cc00cd0084018201830103010201cd008501840183010401020103018601840185010501040103018701860185010601040105018801860187010701060105018901880187010801060107018a01880189010901080107018b018a0189010a01080109018c018b0189010b010a0109018d018b018c01c7000a010b018e018d018c01c800c7000b018f018d018e01c800c600c70090018f018e01c700c600c50091018f019001c6009200c500920191019001c5009200c400930191019201c4009200c3009401930192019501930194019601950194019701950196019801970196019901970198019a01990198019b0199019a019c019b019a019d019b019c019e019d019c019f019d019e01a0019f019e01a1019f01a001a2019f01a101a301a201a101a401a301a101a501a301a401a601a501a401a701a501a601a801a701a601a901a701a801aa01a901a801ab01a901aa01ac01ab01aa01ad01ab01ac01ae01ad01ac01af01ad01ae01b001af01ae01b101af01b001b201b101b001b301b101b201b401b301b201b501b301b401b601b501b401b701b501b601b801b701b601b901b701b801ba01b901b801bb01b901ba01bc01bb01ba01bd01bb01bc01be01bd01bc01bf01bd01be01c001bf01be01c200c3009200d300c200d200d400c200d300d200c200ce00d500c200d400d600d500d400ce00c200d100d100d000ce00d000be00ce00ce00be00cf00cf00be000101c300c2005c01c200d5005c01be000001010101010001ff00c3005c015f01c3005f01800180015f017f015f015c017f017f015c017e010001fe00ff00ff00fe00fd005c015e017e01fe00fb00fd00fd00fb00fc00fc00fb00f9005b015e015c015c01d5005b015e015a017e015b015a015e017e015a017d01d500d7005b015b01d7005a01d700d500d600d800d700d600d900d700d8005a015d017d017d015d017c014d015d015a015d014d017c015a01d7004d017c014d017b017b014d017a01da00d700d9004d01d700da00db00da00d9004d014c017a014d01da004c017a014c017901dc00da00db004c014b0179014c01da004b0179014b017801dd00da00dc004b01da00dd00de00dd00dc00df00dd00de004b014a0178014b01dd004a014a014e01780178014e0177014a01dd00e000e0004e014a01e000dd00df004e014301770143014e01e000770143017601760143017501e100e000df00e1004301e000e200e100df00e300e100e200430142017501e10042014301750142017401e400e100e3004201e100e400e500e400e3004201490174014201e4004901740149017301e600e400e5004901480173014901e40048017301480172014801e4004701480147017201e700e400e6004701e400e700e800e700e600e900e700e8004701460172014701e7004601720146017101ea00e700e9004601450171014601e7004501710145017001eb00e700ea004501e700eb00ec00eb00ea004501440170014501eb004401700144016f01ed00eb00ec00ee00ed00ec00ef00ed00ee00f000ed00ef004401eb004101440141016f014101eb00ed006f0141016e016e0141016d01f100ed00f000f200f100f000f300f100f200410159016d016d0159016c014101ed005501410155015901ed00f1005501590158016c015501580159016c0158016b01550154015801580154016b015501f10054016b0154016a01f400f100f300f500f400f300f600f400f500540157016a016a01570169015401f1005301530157015401f100f40053015701560169015301560157016901560168015301400156015601400168015301f4004001680140016701f700f400f600f400f7004001f800f700f600f900f700f80040013f0167014001f7003f0167013f016601fa00f700f9003f01f700fa00fb00fa00f9003f013c0166013f01fa003c013c01fa00fb0066013c0165013c01fb003d01fe003d01fb003c013e0165013e013c013d0165013e01640164013e016301fe0050013d013e014f0163013d014f013e0150014f013d0163014f0162014f0150016201fe0000015001500151016201000151015001620151016101520151010001510152016101be0052010001610152016001be00bf005201bf0060015201b200b300b400b500b300b200b600b700b800b900b700b600ba00bb00bc00bd00bb00ba000c010d010e010f010d010c0116010f010c01140116010c011701160114010d012e010e010e012e011201120110010e012f012e010d010e01100111010d0120012f0110011501110115011301110111011301140113012d01140114012d012c012c012b01140114012b0117012b012a0117012a012901170129012801170128012701170127012601170117012601250125012301170117012301240120010d011d011d011c0120011d010d011f011a011c011d010d0122011f011e011c011a01220121011f0119011e011a011f0121011801180119011a011b011901180130011b01180131013001180118012101320132013101180132013301310134013301320135013401320132013601350137013601320132013801370139013801320132013a0139013b013a013201c101c201c301c2010302c301c401c201c101c30103020202c501c401c101030201020202c601c401c501020201020002c701c401c6010102ff010002c801c701c6010002ff01fd01c901c701c801ff01fe01fd01ca01c901c801fe01fc01fd01cb01c901ca01fd01fc01fb01cc01cb01ca01fc01fa01fb01cd01cb01cc01fb01fa01f901ce01cd01cc01fa01f801f901cf01cd01ce01f901f801f701d001cf01ce01f801f601f701d101d001ce01f701f601f501d201d001d101f601f401f501d301d001d201f501f401f301d401d301d201f401f201f301d501d401d201f301f201f101d601d401d501f201f001f101d701d401d601f101f001ef01d801d701d601f001ed01ef01d901d701d801ef01ed01ee01da01d901d801ee01ed01ec01db01d901da01ed01eb01ec01dc01db01da01ec01eb01ea01dd01db01dc01eb01e901ea01de01dd01dc01ea01e901e801df01dd01de01e901e601e801e001df01de01e801e601e701e101df01e001e701e601e501e201e101e001e601e401e501e301e201e001e501e401e301e401e201e3010402050206020702050204020802050207020902050208020a020b020c020d020b020a020e020f02100211020f020e02120211020e020e021302120214021502160217021502140215021802160217021402280216021802190214022602280218021a0219022802260227021b0219021a022702260224021c0219021b022602250224021d0219021c022502230224021e021d021c022402230222021f021d021e0223022102220220021f021e0222022102200221021f02200229022a022b022c022a0229022d022c02290229022e022d022f0230023102320230022f023302340235023602340233023702380239023a02380237023b0238023a023c0238023b023d023e023f0240023e023d02410242024302440242024102450246024702480245024702470246025a02490245024802460259025a024a02450249025a02590258024b024a0249025902570258024c024a024b024d024c024b024e024c024d024f024e024d0250024e024f02510250024f0252025002510253025202510254025202530255025402530256025502530257025502560258025702560256025e02580258025e025f025f025e025b025e025c025b025b025c025d0260025d025c0261025d0260026202610260026302610262026402650266026702650264026802660265026402790267026902660268026702790278026a02690268027902770278026b0269026a027802770276026c026b026a027702750276026d026b026c027602750274026e026d026c027502730274026f026d026e0274027302720270026f026e0273027102720271026f0270027202710270027a027b027c027d027a027c027c027b0290027e027a027d027b028f0290027f027e027d0290028f028e0280027e027f028f028d028e02810280027f028e028d028b028202800281028d028c028b028302820281028c028a028b028402820283028b028a0289028502840283028a0288028902860284028502890288028702870286028502880286028702 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 657 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 21024 - _typelessdata: 2f690c3e0b2aaa3b6a671cbfa2b477bf470378be4f40933d4703283f4c37c9be94850f3e3659a3bcc3d822bf77be7fbf5af5b93c516b1abdf5db273f17b7d1bebe16043ec87e96bdbf631cbf96b26cbfd0b3b93eb29def3d2c65293fb8afe3be6e31df3d492c893d31d322bf9fab4dbf956518bf865a533ccdcc2c3fe17ab4be7d94d13d1538993d3c661cbfd95f36bf9ca233bfe02d103cb6f32d3f4ed1b1becdab3a3d64e9e33d31d322bfde71aabee56171bf9c3322bca779373f4260a5bef5f53c3d1766e13d9d671cbf6076afbea54e70bf75021abd3945373fef38a5be38bd8bbc50a9f23d40691cbf8195033e7fd97dbfb37b723cd712423fc139a3bedc4b9abcb306ef3d31d322bff2b0103e1ff47cbffe437a3d986e423f1ceba2be7deba3bda587c13de1d122bfc139033ffa7e5abf80b7c0bd96b24c3fb150abbe4f5ccebdaa619f3d24641cbf1ac03b3f5f072ebfb37bf23b57ec4f3f3c4eb1be844afcbda25e303d0fd322bfd49a663fb37bd2be4df30ebe1d5a543fda1bbcbe6e6a10bef83592bc2c661cbf643b7f3f2cd49abd68916d3c4694563f97ffd0bed95a0fbe9a4312bd1cd022bfb6847c3ff931263ea857cabc8273563f07f0d6be9965efbd3997c2bd8c671cbf5bb15f3f10e9f73e5af5393ddc46533fcc5debbe3d62d4bd2387e8bdfed622bf69003f3fa8572a3f363cbd3c29cb503f3c4ef1beaab7a6bd24f007be06671cbfbbb80d3fe17a543fdfe08b3d1b2f4d3fec51f8be7e190cbdd92720be6fd422bf1ff48c3e4f1e763fa69bc4bad8f0443fee7cffbe83c38bbc566123be40691cbfde02893d44697f3f62a1563cd712423f174800bfcdab3a3da3011cbe31d322bf287e8cbe0f0b753f1136bcbda779373f925cfebe313f973d0d340fbee4661cbff90f09bf10e9573f7e8c393d8fe4323f637ff9be5e4dde3d3029debdb2d422bfa77947bff7061f3f3108acbd76e02c3f6076efbe3540e9bcde010abfbe4bb9be77be1fbf226c383f7ffb9a3eaf25443f6a4d43bf4a98e9bc35415cbf6a671cbf000080bf17b7d13817b7d138af25443fccee79bf4a98e9bcd21954bf507392bd96b21cbf22fd363e6132453faf25443ffd8774bf4a98e9bc901409bf6a671cbf3d2c34bf39d6353fc3642a3caf25443f6ade41bfc6a2e9bc0d6e5bbf3fc7a7bdde9317bfffb22bbff8c2e43eaf25443f35ef78bfdb50b13c709953bfdbf893bd6d561d3fb29d6f3ed3de403f76713b3f583974bf6631b13ce2590abf412eb9be5dfe233f66f7343f9a99993e4260353f4f4043bf94f7993e4017ad3efdf956be903117bf3d2c24bf2cd4fa3eb5150b3fb1502bbe26a6b33e8996ac3e6a671cbf76716b3ff90fc9be17b7513a0a68023fe8d92cbeb5a4b33eaf79ad3efd6858bede711a3f827326bf0d71ec3e0a68023f431c2bbe94f7993e8996ac3e6a671cbfbde334bfd42b35bf89d2de3ab5150b3fe8d92cbe26a6b33e2d40e33e043732be17b7d1385452e7beaa60643f0a68023ff0a7c6bd0f9cb33edba6083f0b9cecbd04e70c3fe926d13e516b3a3f0a68023f075f18bd5df8993ed652083f44f7ecbdf2410fbfa52cc33ef1633c3fb5150b3f9a771cbd94f7993e3eeadf3e338a35be52499d3b36cddbbe9031673fb5150b3f3bdfcfbd94f7993e3eeadf3e338a35be000080bf0000000000000000b5150b3f3bdfcfbd94f7993e2ee4093f6a671cbf5227a0be1826733f24977f3bb5150b3f4b5906bddae5b3be4017ad3efdf956be022b17bf3d2c24bf2cd4fa3e4d157c3fb1502bbe48379abe8996ac3e6a671cbfbde3343fd42b35bffaedeb3a1361733fe8d92cbeb9389abeaf79ad3efd6858bede711a3f827326bf0d71ec3e1361733f431c2bbedae5b3be8996ac3e6a671cbfc8986bbf7958c8be17b7513a4d157c3fe8d92cbe48379abe7040e33ec13632be2e90a0bb94f6e6bee17a643f1361733ff0a7c6bdde3b9abe643a083fb533ecbddbf90e3f08acbc3e5b423e3f1361733fe3a51bbd7ee3b3bebcad083f309fecbded9e0cbfb22ece3e76713b3f4d157c3f516b1abddae5b3be3eeadf3e338a35bee02d903bdaacdabea779673f4d157c3f3bdfcfbd48379abe7040e33ec13632be0000803f17b7513917b7d1381361733ff0a7c6bd48379abe2ee4093f6a671cbf6e34a03e1826733f6c09793b1361733f4b5906bd26a6b33e8996ac3e6a671cbf00000000000000000000803f0a68023fe8d92cbe94f7993e8996ac3e6a671cbf17b7d138000000800000803fb5150b3fe8d92cbe2f690c3e0b2aaa3b6a671cbffaed6bba52491d3a0000803f4703283f4c37c9bebe16043ec87e96bdbf631cbf17b7d138000000800000803f2c65293fb8afe3bedb50b13c35415cbf6a671cbf0000008017b7d1b80000803f76713b3fccee79bfdb50b13c901409bf6a671cbf17b7d1b9000000000000803f76713b3f6ade41bf7d94d13d1538993d3c661cbf00000000000000000000803fb6f32d3f4ed1b1be94f7993e2ee4093f6a671cbf00000000000000000000803fb5150b3f4b5906bdf5f53c3d1766e13d9d671cbf52499db917b751390000803f3945373fef38a5be38bd8bbc50a9f23d40691cbf17b7513917b751390000803fd712423fc139a3be48379abe2ee4093f6a671cbf00000000000000000000803f1361733f4b5906bd4f5ccebdaa619f3d24641cbf0000000017b7d1380000803f57ec4f3f3c4eb1be48379abe8996ac3e6a671cbf17b751b9000000000000803f1361733fe8d92cbe6e6a10bef83592bc2c661cbf17b751b917b7d1b80000803f4694563f97ffd0bedae5b3be8996ac3e6a671cbf00000000000000000000803f4d157c3fe8d92cbe4a98e9bc901409bf6a671cbf00000000000000000000803faf25443f6ade41bf9965efbd3997c2bd8c671cbf6f12033b17b7d1390000803fdc46533fcc5debbeaab7a6bd24f007be06671cbf00000000000000000000803f1b2f4d3fec51f8be83c38bbc566123be40691cbf17b7d138000000800000803fd712423f174800bf313f973d0d340fbee4661cbf17b751ba17b7d1380000803f8fe4323f637ff9be4a98e9bc35415cbf6a671cbf00000000000000000000803faf25443fccee79bfdb50b13c35415cbf0fd322bfd0d5763f30bb87be0000008076713b3fccee79bfdb50b13c35415cbf6a671cbfd0d5763f30bb87be0000008076713b3fccee79bf26a6b33e8996ac3e0fd322bf2db27d3fde0209be000000800a68023fe8d92cbedae5b3be8996ac3e0fd322bfd6c57dbf827306be000000804d157c3fe8d92cbe4a98e9bc35415cbf6a671cbf022b77bf0b4685be00000080af25443fccee79bf4a98e9bc35415cbf0fd322bf022b77bf0b4685be00000080af25443fccee79bfdae5b3be2ee4093f0fd322bf86c934bf7d3f353fbc74933b4d157c3f4b5906bddae5b3be3eeadf3e338a35be000080bf17b7d138000000004d157c3f3bdfcfbd26a6b33e2ee4093f0fd322bf86c9343f7d3f353f75029a3b0a68023f4b5906bd26a6b33e2d40e33e043732be0000803f17b7d13917b7d1380a68023ff0a7c6bd844afcbda25e303d0fd322bf52499db917b751b9000080bf1d5a543fda1bbcbed95a0fbe9a4312bd1cd022bf17b7d1b800000080000080bf8273563f07f0d6bedae5b3be8996ac3e0fd322bf17b7d13800000080000080bf4d157c3fe8d92cbe7deba3bda587c13de1d122bf0000008000000080000080bf96b24c3fb150abbedae5b3be2ee4093f0fd322bf0000008000000080000080bf4d157c3f4b5906bddc4b9abcb306ef3d31d322bf17b7d1b817b7d1b8000080bf986e423f1ceba2be26a6b33e2ee4093f0fd322bf0000008000000080000080bf0a68023f4b5906bdcdab3a3d64e9e33d31d322bf0000008000000080000080bfa779373f4260a5be6e31df3d492c893d31d322bf17b7d1b917b7d139000080bfcdcc2c3fe17ab4be26a6b33e8996ac3e0fd322bf0000008000000080000080bf0a68023fe8d92cbe94850f3e3659a3bcc3d822bf17b7513900000000000080bff5db273f17b7d1bedb50b13c35415cbf0fd322bf0000008000000080000080bf76713b3fccee79bf5e4dde3d3029debdb2d422bfe02d10bb17b75139000080bf76e02c3f6076efbecdab3a3da3011cbe31d322bf17b7d1b800000080000080bfa779373f925cfebe7e190cbdd92720be6fd422bf17b7d13900000080000080bfd8f0443fee7cffbe4a98e9bc35415cbf0fd322bf0000008000000080000080bfaf25443fccee79bf3d62d4bd2387e8bdfed622bf348037bb17b7d1b9000080bf29cb503f3c4ef1bedb50b13c901409bf6a671cbf3d2c343f39d6353fea95323c76713b3f6ade41bfdb50b13c35415cbf6a671cbf89d2de3a000080bf17b7513b76713b3fccee79bf895fb13cb0ae5abf7f15a0bd6210183fe9481ebf61c3033f76713b3f3e7978bf4a98e9bc35415cbf6a671cbf00000000000080bfa69b443baf25443fccee79bfdb50b13c35415cbf0fd322bf00000000000080bf0000008076713b3fccee79bf4a98e9bc35415cbf0fd322bf00000000000080bf00000080af25443fccee79bfdb50b13c35415cbf6a671cbf0000803f000000000000000076713b3fccee79bfa418203efa26adbe9fad0bbf0000003f2db25dbf52499d39c0ec5e3e68b32abf90a20e3fb820dbbde88418bfaed8ff3e48bf5dbf17b75139d9cef73d091b0ebf90a20e3f3220dbbd9fad0bbf92cbff3e48bf5dbf00000000d9cef73d091b0ebfd84466be99f50ebfe88418bf8e06003f2db25dbf17b7d13800919e3e6ff045bf022e08be105c01bf9fad0bbf0000003f2db25dbf17b7d1b83333933eb7623fbfd84466be99f50ebf9fad0bbfc520003f849e5dbf0000008000919e3e6ff045bf24b726bf9a60383e9fad0bbf2db25dbf000000bf000000004f40d33e6132d5bed84466be99f50ebfe88418bf2db25dbf000000bf0000000000919e3e6ff045bfd84466be99f50ebf9fad0bbf2db25dbf000000bf0000000000919e3e6ff045bf24b726bf9a60383ee88418bf2db25dbf000000bf000000004f40d33e6132d5bed84466be99f50ebfe88418bf0000008000000080000080bf00919e3e6ff045bfa25e903ec64e983ee88418bf0000008000000080000080bfc9e53f3e992ab8be90a20e3fb820dbbde88418bf0000008000000080000080bfd9cef73d091b0ebf91448fbec64e983ee88418bf0000008000000080000080bf2653a53e992ab8becc7ebdbec64e983ee88418bf0000008000000080000080bf857cb03e992ab8be7bbba53ec64e983ee88418bf0000008000000080000080bf58a8353e992ab8bea25e903e4032bd3ee88418bf0000008000000080000080bfc9e53f3e4b59a6be91448fbeab09ca3ee88418bf0000008000000080000080bf2653a53e5227a0be88f2053eb9a9213fe88418bf0000008000000080000080bf0b46653e280f4bbe24b726bf9a60383ee88418bf0000008000000080000080bf4f40d33e6132d5becc7ebdbe234eaf3ee88418bf0000008000000080000080bf857cb03e560eadbe88f2053ecaa9213f9fad0bbf0000008000000080000080bf0b46653e280f4bbea25e903eb6a1163f9fad0bbf0000008000000080000080bfc9e53f3ec05b60bea25e903e4032bd3e9fad0bbf0000008000000080000080bfc9e53f3e4b59a6bed95c853e73b87a3f2aad0bbf0000008000000080000080bfcba1453eb9fc07bd9acc043f80455e3fbcae0bbf0000008000000080000080bf6ff0053e57ecafbddd98be3eb6a1163f9fad0bbf0000008000000080000080bf0c93293ec05b60bee277333f64023a3f9fad0bbf0000008000000080000080bf575bb13d08ac1cbe0f0d533fddeb143fb0ad0bbf0000008000000080000080bff90f693de63f64bedd98be3ec64e983e9fad0bbf0000008000000080000080bf0c93293e992ab8bead136b3f34bdd43e9fad0bbf0000008000000080000080bf287e0c3dec2f9bbe18b27a3f889f6f3eb0ad0bbf0000008000000080000080bf2e90a03c9eefc7be90a20e3f3220dbbd9fad0bbf0000008000000080000080bfd9cef73d091b0ebfd594803f132d393d9fad0bbf0000008000000080000080bf1b2f5d3c4694f6be8c657e3ffa7f15be9fad0bbf0000008000000080000080bf26e4833c8fe412bf1475723fe04ba9be9fad0bbf0000008000000080000080bff775e03c43ad29bf3faa5d3fc00901bf9fad0bbf0000008000000080000080bf371a403d12143fbffbe8403f7ba128bfb0ad0bbf0000008000000080000080bf508d973d812652bfa418203efa26adbe9fad0bbf0000008000000080000080bfc0ec5e3e68b32abf54ad1d3fd59349bf9fad0bbf0000008000000080000080bf3f57db3d4a0c62bffa27e83e32e563bf9fad0bbf0000008000000080000080bf74b5153e52b86ebf30498d3eb49375bf9fad0bbf0000008000000080000080bf3789413ec74b77bfdd5f9d3d261e6cbf08ad0bbf26e403bfdaac9a3edf4f4dbf0f9c733e0a6872bf5b41133e10937cbf9fad0bbf0000008017b7d138000080bf2eff613ed5e77abf7bbba53ec64e983e9fad0bbf0000008000000080000080bf58a8353e992ab8be446ff1bba6627fbf9fad0bbf0000008017b7d138000080bfb8af833ea3017cbfc8b3cb3c4b1f76bf4bad0bbf24283ebea60a163f3ee849bf12147f3e197377bf8c12f4bc48a475bf3aad0bbf1d38673e01de123f7e8c49bfc217863e705f77bf82e49dbdac1b6bbf4bad0bbfc3f5083f7446943e5f294bbfdfe08b3edcd771bf390d11be10937cbf9fad0bbf0000008017b7d138000080bf7446943ed5e77abfd84466be99f50ebf9fad0bbf0000008000000080000080bf00919e3e6ff045bf022e08be105c01bf9fad0bbf0000008000000080000080bf3333933eb7623fbfbb7f94be3a5b74bf9fad0bbf0000008000000080000080bfb98da63e0bb576bf83895fbf2192f9beb0ad0bbf0000008000000080000080bf3b70ee3ec8073dbf24b726bf9a60383e9fad0bbf0000008000000080000080bf4f40d33e6132d5be249b43bfa5d924bf9fad0bbf0000008000000080000080bf09f9e03e325550bf766b5dbf07d0033fb0ad0bbf0000008000000080000080bf166aed3e789c82becc7ebdbe234eaf3e9fad0bbf0000008000000080000080bf857cb03e560eadbed19075bfdb6a9e3e4bad0bbf0000008000000080000080bf5986f83e58a8b5be959e49bf0070203f9fad0bbf0000008000000080000080bf9318e43e166a4dbecc7ebdbeb6a1163f9fad0bbf0000008000000080000080bf857cb03ec05b60be88652fbf15533d3f9fad0bbf0000008000000080000080bfab3ed73ebd5216be00ae14bf12de523f9fad0bbf0000008000000080000080bf158cca3ef5dbd7bd4913e7befe2b673f9fad0bbf0000008000000080000080bf516bba3e96438bbd91448fbeb6a1163f9fad0bbf0000008000000080000080bf2653a53ec05b60be452a8cbe39f2783f9fad0bbf0000008000000080000080bf8a8ea43edf4f0dbd7ae1aebd00e6803f9fad0bbf0000008000000080000080bfdf4f8d3e05a392bc0397473d1a1a813f9fad0bbf0000008000000080000080bf4772793e26e483bc91448fbeab09ca3e9fad0bbf0000008000000080000080bf2653a53e5227a0be7b3380bfe4f5803d9fad0bbf0000008000000080000080bf5b42fe3e0e2df2bee0847ebfe8bd01be9fad0bbf0000008000000080000080bf8863fd3e857c10bfe86673bfa7b2a0be9fad0bbf0000008000000080000080bf2bf6f73e6b9a27bf48a520bf2dd046bf9fad0bbf0000008000000080000080bfe02dd03e80b760bf87bfeebe5be961bfb0ad0bbf0000008000000080000080bf2d43bc3ed6c56dbfcc7ebdbeb6a1163facacfdbe00000000000080bf00000080857cb03ec05b60be91448fbeb6a1163f9fad0bbf00000000000080bf000000802653a53ec05b60be91448fbeb6a1163facacfdbe00000000000080bf000000802653a53ec05b60becc7ebdbeb6a1163f9fad0bbf00000000000080bf00000080857cb03ec05b60bedd98be3eb6a1163facacfdbe000080bf00000000000000000c93293ec05b60bedd98be3ec64e983e9fad0bbf000080bf00000000000000000c93293e992ab8bedd98be3ec64e983eacacfdbe000080bf00000000000000000c93293e992ab8bedd98be3eb6a1163f9fad0bbf000080bf00000000000000000c93293ec05b60bea25e903eb6a1163facacfdbe00000000000080bf00000080c9e53f3ec05b60bedd98be3eb6a1163f9fad0bbf00000000000080bf000000800c93293ec05b60bedd98be3eb6a1163facacfdbe00000000000080bf000000800c93293ec05b60bea25e903eb6a1163f9fad0bbf00000000000080bf00000080c9e53f3ec05b60be40a289bd5af46ebf29b2d63bb762df3ea245d63efaed4b3f68b38a3e0ad773bf1aa8acbd7c7d61bff702b3bb6d561d3feeebe03e30bb273fa8358d3e9fab6dbfb2825fbc8db676bfd542c93b8104453e7a367b3f5f29cbbb3867843e67d577bf49490f3dc51f75bfd542c93b728acebe713d6a3fed0d3ebbff217d3e591777bfc501943dcd066dbfe8d9ac3bdb8afdbe68b3ca3efdf6453f5839743e1ceb72bf38c0ac3d587162bfaf5a99bb006f21bfbf0edc3eeb73253fa089703e68916dbf5ab7e13dd63b30bfd4d628bef6977dbfa8c60b3ebc74933b8c4a6a3e190456bfaddee13da83a30bfacacfdbedb8a7dbff1630c3ebc74933c8c4a6a3e190456bfa418203efa26adbe9fad0bbff2d27dbf2575023e17b7d1bcc0ec5e3e68b32abf13b64f3e389e0f3ddaadfdbe6d567dbf4f4013beb37b72bbbde3543ed93df9be18b14f3ec1e5313c9fad0bbf000080bfd044583b52499dbacff7533e1214ffbe1283e0bd8db830bfa03329bee63f543f2d210f3f17b751bb1748903e8bfd55bf18ccdfbdb62b30bfacacfdbe68b36a3ff163cc3ea323393c1748903e8bfd55bf022e08be105c01bf9fad0bbf7fd97d3fa69b043e3b70ce3b3333933eb7623fbfd5594dbea4c42e3cacacfdbec9e57f3fbc74933b1973d73c3f579b3e2d21ffbeabec4bbe6006233dd6ac0bbf11367c3f9fab2dbe62a1d63c11c79a3e6210f8bef8fd1b3ca6627fbfd542c93bd044583b6f12833a0000803f5396813ea3017cbf390d11be10937cbfd542c93be02d90ba5f29cbbb0000803f7446943ed5e77abfb2825fbc8db676bfd542c93b4ed1113c82e2c73b72f97f3f3867843e67d577bf49490f3dc51f75bfd542c93b2db21d3d82e2c7bc77be7f3fff217d3e591777bf5b41133e10937cbfd542c93b27a009bbca54413c72f97f3f2eff613ed5e77abfb79a953e185b74bfd542c93bed0d3ebb2497ff3b0000803fa4703d3e0bb576bfcfd8ef3e5be961bf384bc93be02d10bb3480373b0000803fbc05123ed6c56dbfed7df23ee2014dbfe25cc33b67d567bdf1f4ca3d04567e3fce19113ebc7463bfb613213fc5e746bf713ac93b3480b7ba17b7d13a0000803f86c9d43d9cc460bfb935353fb62f1cbf34bdc43be5d0a2bd05a3923d728a7e3f3255b03dffb24bbf732d3a3f4a962fbfd542c93ba69bc4ba3480b73a0000803f0ad7a33de6ae55bf0c21533f157211bf713ac93b17b7d1bae02d903a0000803f1ea7683d22fd46bfc32e5e3f8c11b1be61a8c33b1d5ae4bda8574a3d7b147e3f371a403d03782bbf9e266b3f98c0cdbe384bc93be02d10bbe02d903a0000803f4d150c3d257532bf18b27a3ff47062bed542c93b6f1203bb17b7d1390000803f2e90a03cf6281cbfbaf86f3fb11449bda69bc43bb150ebbd3480b73b764f7e3f2506013debe206bfff94803f621404bdd542c93b27a009bb000000000000803f1b2f5d3ca1d604bfd26e7c3f512e5d3eaa29c93b16fbcbbc27a089bb57ec7f3fe3a59b3c5f98ccbe43e5633f78f15e3eeb1e593b204171be257582bdd044783f9eef273da8c6cbbea60b513fa0a9ef3eb6dac33bf31fd2bd46b673bdb22e7e3f6b2b763d32e68ebeb6f2623f522cf73e37e2c93b52491dbb52499dba0000803f567d2e3dbada8abe7a19413f1bbc2b3fd542c93b0000000052491dba0000803f7424973db53738bed5090c3fb476473f1329cd3b000080bd55c1a8bd1b9e7e3fca54013ea8570abec43e1d3fa33e4d3f713ac93bc364aa3a17b7d13a0000803ff628dc3dd734efbd912ce83eaa2b673f0e32c93b6f1203ba17b7d1390000803f74b5153e96438bbd69702b3e44506d3fd658c23bcb10c7bcfc18f3bd091b7e3f645d5c3efaed6bbd41458d3e06f2783f713ac93bfaed6bbac3642abb0000803f3789413edf4f0dbd514bb33d00e6803f713ac93b17b7d1b8348037bb0000803f57ec6f3e05a392bc7ac63ebd1a1a813fd542c93b52499d39faedebba0000803fe78c883e26e483bcd54033be72356e3fa260c63be02d903cd191dcbdc9767e3f6210983ed8f074bd354184bea6b87a3f8010c93b52499d393480b7ba0000803f4182a23eb9fc07bd484f01bf74094b3f7e54c33b6519623d5a64bbbd728a7e3f09f9c03e6c09f9bd58c6eebe902f653f0e32c93b6f12833a27a009bb0000803f2d43bc3e73d792bd00ae14bf12de523fd542c93b17b7d13a27a009bb0000803f158cca3ef5dbd7bd168832bfda573a3f384bc93b17b7d13a17b7d1ba0000803fe3c7d83ebf0e1cbedd263cbf506f163f7debc33b35efb83ddf4f8dbd04567e3f91eddc3eaeb662be959e49bf0070203fd542c93b6f12033ba69bc4ba0000803f9318e43e166a4dbec4955fbf020f003f713ac93bfaedeb3afaed6bba0000803f3b70ee3e143f86bed99661bf838aa23e97c5c43b33c4b13d93a902bd32e67e3fa01aef3e0f9cb3beec6d73bf7e1aa73ed542c93b6c09f93a52491dba0000803f4703f83ee926b1be6a847ebf3cf70e3ed542c93b6f12033b17b7d1b90000803f8863fd3e643bdfbef4c46fbfc0779b3ca704c43b89d2de3db37b72bb567d7e3f3d9bf53ee414fdbe9c3380bf47024dbdd542c93be02d103b17b751390000803f5b42fe3e3d0a07bfb98a75bf040398bee318c93b27a0093b52491d3a0000803f5986f83e265325bf466163bf583891bee0f3c33b228ef53de63f243db6f37d3fa913f03eefc923bfab0946bf579604bfe25cc33b38f8c23d8a8e643d3b707e3f2effe13ef2b040bf6caf55bfd2560dbf9aeac93b52491d3b17b7d13a0000803f9e5ee93e14d044bf20622fbfe90e3abf384bc93b17b7d13a17b7d13a0000803fab3ed73e158c5abf7b3110bf371c3ebf3526c43bbc05923d1058b93de9487e3f82e2c73e31085cbfdae1f7beb03c60bf713ac93b1b2f5d3c8c4a6a3ce5f27f3f3f35be3e287e6cbf0fb9b9beb8ad55bfdeaa6b3b7aa5ac3d984c553e4772793f295caf3e67d567bfba2e8cbe929375bfd542c93bfaed6bba96438b3c72f97f3f8a8ea43ec74b77bfaf273abe97c7ba3dacacfdbe39d6653f499de0be52491d3d6c09993ecc5debbefc1813be541f183eaab60bbf029a383f204131bfa167b33c211f943e64ccddbe4b02f4bdd9b52d3e959afdbefbcb0e3f547454bf5f294b3ce926913eacadd8be63450dbdbc08533ea2b40bbfd2005e3eb1e179bf0e4f2fbc94f6863e3bdfcfbecafa4dbb3447563ec2a3fdbe27a0893ce5f27fbf583934bca52c833ea01acfbe82ac873d6bf1493e9fad0bbf07ce99be3d2c74bf17b751bab459753e575bd1becb9ec43db9e03c3eacacfdbe448becbe38f862bf567daebc7b146e3efd87d4be4ab60a3e4297203eb0ad0bbfccee29bf60763fbfea95323c5474643e3f57dbbe5779223ecc61073ecdacfdbebe9f4abf645d1cbfea95b2bc52b85e3eca54e1bed9973c3e0d50ba3d9fad0bbf470368bfb537d8be05a392bc3e79583ee86aebbe91448fbec64e983eacacfdbe17b751b9000000000000803f2653a53e992ab8becafa4dbb3447563ec2a3fdbe17b7d13817b7d1380000803fa52c833ea01acfbe91448fbeb6a1163facacfdbe17b7d1b8000000000000803f2653a53ec05b60be4b02f4bdd9b52d3e959afdbe6f1203ba6f12033a0000803fe926913eacadd8be5c0599be47cc243facacfdbe00000000000000000000803ff9a0a73e39d645becc7ebdbeb6a1163facacfdbe00000000000000000000803f857cb03ec05b60be498446bee6ad2e3facacfdbe00000000000000000000803ff5b99a3e6ade31be8670fcbe6329023facacfdbe00000000000000000000803f098abf3e014d84becc7ebdbec64e983eacacfdbe00000000000000000000803f857cb03e992ab8be304ad0bef75a143facacfdbe00000000000000000000803f0f0bb53ef8c264beaf273abe97c7ba3dacacfdbe17b7d1b917b751b90000803f6c09993ecc5debbed5594dbea4c42e3cacacfdbe00000000000000000000803f3f579b3e2d21ffbedd98be3ec64e983eacacfdbe00000000000000000000803f0c93293e992ab8be6ce9013fb9fefd3ecdacfdbe00000000000000000000803fcc7f083e705f87bedd98be3eb6a1163facacfdbe00000000000000000000803f0c93293ec05b60becb811a3fed28be3eacacfdbe00000000000000000000803f8ab0e13dc217a6be5070a13eaa10233facacfdbe00000000000000000000803f10e9373e4c3749bea25e903eb6a1163facacfdbe17b7d138000000800000803fc9e53f3ec05b60beadf9d13e232e143facacfdbe00000000000000000000803f8941203e9d1165bea25e903ec64e983eacacfdbe17b7d138000000800000803fc9e53f3e992ab8bed5052c3e38db303facacfdbe0000000017b7d1380000803ff6285c3e8d972ebe5779223ecc61073ecdacfdbe00000000000000000000803f52b85e3eca54e1becb9ec43db9e03c3eacacfdbe52499d396f1203ba0000803f7b146e3efd87d4beaf0b3fbeeee82bbfacacfdbe00000000000000000000803f07ce993e26e453bf18ccdfbdb62b30bfacacfdbe00000000000000000000803f1748903e8bfd55bf4c539cbe70b320bfacacfdbe00000000000000000000803f9565a83ee9484ebf89d0e0be76180bbfacacfdbe00000000000000000000803f19e2b83e26e443bfa62611bffd10d3be69acfdbe00000000000000000000803f5e4bc83ebc7433bf79ac29bf76c26bbeacacfdbe00000000000000000000803faa60d43ec4421dbf2efe32bfb56c8dbdacacfdbe00000000000000000000803f35efd83e9e5e09bf6d8e33bff3712d3dacacfdbe00000000000000000000803f4772d93eab3ef7be815b2fbf61162a3eacacfdbe00000000000000000000803f7424d73ec7bad8be7d5d26bf28ef8b3eacacfdbe00000000000000000000803fe10bd33e2428bebe1aa517bf069cc53eacacfdbe00000000000000000000803f8cb9cb3e4182a2be6a6c8fbdae0d353facacfdbe0000000017b7d1380000803fe86a8b3e9d8026be87c5283d3273353facacfdbe0000000017b7d1380000803f5a647b3ef8c224be6808273fbc598b3eacacfdbe00000000000000000000803f39b4c83dad69bebebabd303ff2961b3eacacfdbe00000000000000000000803febe2b63d2d43dcbe13b64f3e389e0f3ddaadfdbe00000000000000000000803fbde3543ed93df9beb490343f1bf2cfbbacacfdbe00000000000000000000803f7b83af3d6ea301bff298313f4dbdeebdacacfdbe00000000000000000000803fc66db43d80480fbf7d79293fa0e072beacacfdbe00000000000000000000803f8104c53d091b1ebf3b1c1d3f45bcadbeacacfdbe00000000000000000000803f88f4db3d2cd42abffad30a3f9086e3beacacfdbe00000000000000000000803f0000003e14ae37bf2bc2ed3eadda05bfacacfdbe00000000000000000000803f73d7123e378941bf5989b93e292019bfacacfdbe00000000000000000000803f9f3c2c3e4ca64abf9eb4603e47cb29bfacacfdbe00000000000000000000803f60764f3e93a952bfaddee13da83a30bfacacfdbe00000000000000000000803f8c4a6a3e190456bf994b5abf0d7287be2176a6bbf628dc3ecd3b0e3eaa60643ffaedeb3e053421bfccf13abfdd4108bff609a0ba04568e3e1b9e5e3e098a6f3fa301dc3e13f241bf6a6744bf31d3d6be05db88bc39b4183fd3dea03ee4143d3f1b0de03e3d2c34bfd23963bf9dd5a2bd6fd8b6bb61c3f33ead695e3df2b0603fc520f03e3a230abf2c4463bf3883ff3df6798cbbc58ff13e27a089bdb30c613f05c5ef3ee5d0e2be89b277be3cf85d3f93e4b9bb508d173e67d5e7bece19613f499da03ee926b1bd2a8e2f3f6ff1143f61889cbb9fcdcabe5e4ba8be917e5b3fa323b93d9ca263be9d854d3fb6bbcf3e118aadbbed9edcbe956548beaa82613f0000803ded0d9ebeffeb5cbdeae7653f342db1bbc0ec9e3cad69febe091b5e3f43ad893e05c58fbd919be13d45f3643f98f8a3bb9eef27bda9a4eebeb840623f55306a3e2aa993bda35b6f3e44855e3ff9db9ebbca5401be95d4e9be006f613f03784b3ee7fba9bd52d7b23e1f84543fd2fd9cbb82e247be8f53f4be3f575b3f32e62e3e1826d3bd75e7f93e22e0403f5587dcbb7dd073bedcd7c1be66f7643f96b20c3ebc9610be639a193fc47a2b3f4c5299bb7fd99dbe9fabadbed881633f66f7e43d8f5334beb1a3653f82c8223d05dc73bb8fc2f5be567d2ebda54e603f77be1f3dd95ff6bec85b623f20b604be12c0cdbb86c9f4be0ad7a33dc9e55f3fc5fe323d09f910bfa5f5573f916195be9a07b0bb52b8debef54a193e6a4d633f1a515a3d2b1825bfbbd5433f6abeeabe0307b4bb07f0d6beb7d1803e643b5f3fe10b933d105839bfda595c3f8271403ed21a83bc894110bf6d56fdbdce19513fcb10473da167d3be50a926bf5dfd14bfe0f684bc9643eb3e64ccdd3e107a463f8fe4d23e759348bf18b408bf7d7935bffdf9b6bb0b24883e075fb83e4bea643f3867c43ee78c58bf611cc4be3a7648bf931e86bc66f7843e61c3033f772d513f85ebb13e736861bf1a366abe124a5bbfc24eb1bb3d9b153ee02d003f516b5a3f2d219f3e88856abfa69b50bfbd34cd3e64cd48bad881b33e696f30bee3a56b3f5ddce63e492e9fbe28622dbf923c173feed0b0bb73d7b23e764f9ebe986e623f5917d73e522760be6343ffbe6230433f02d880ba143f463ee8d98cbe4013713f0000c03e287e0cbe85ee56bf51da833e37df88bcc7ba283f6ff045be910f3a3f713dea3e9c33c2be674746bfff3fd63efa6184bc2a3a023f46b693beceaa4f3f8126e23ebe9f9abe9cc517bfd5d0263ff3c98abce5f2df3ece1901bf1b9e3e3fdfe0cb3e6ea341be363ad7bef5f7463f636188bcf4fd943efd8714bf3cbd423fd95fb63e4bea04be626a1f3fd27323bf10eb8dbb0a68a2be73d7b23e17b7613f1973d73d857c50bfc843ef3e15c844bf1250e1ba8f5334be2c65993e1b0d703fce88123e7b835fbfd194953ecec557bfe4f475bbcac342be04e70c3f371a503fb6f33d3eacad68bfa92f2b3ff79010bfb49080bc55300abfeeebe03e67d5373f13f2c13d666646bffb92f53e2c2e3abfc90391bc9643abbe1973f73ea01a4f3fbb270f3e68b35abfae10563e121358bfa13193bce02d10be14d0243fa089403fbc05523ebe3069bf1283e0bd8db830bfa03329befa7e2a3e24281e3f6abc443f1748903e8bfd55bf01a797befce321bff38d28be55308a3e4260153f9318443f3945a73e89d24ebf8465dcbe51f40cbf8ecb28bed578c93e3cbd023fb8af433fbec1b73e6abc44bf85950abf300de3becea528be1b9efe3e211fd43e1826433f5d6dc53ec28637bf15ad1cbfd926adbe89d028be20d20f3f6f12a33ebc74433f2063ce3e68b32abfef7229bf25ec6bbe1c2329befa7e1a3f08ac5c3efd87443f8f53d43edf4f1dbfd5b132bf2c83aabd9fac28bee5d0223fe7fba93d3867443facadd83e431c0bbf6d8e33bff3712d3d89d028beb3ea233f4260e5bcfd87443f4772d93eab3ef7bef6ee2fbf9d4b213e437428be3bdf1f3f053411bea69b443ffe65d73e2cd4dabe7d5d26bf28ef8b3e89d028be6744193fb5a679bef853433fe10bd33e2428bebe130a19bf8e75c13e349e28be5a640b3f8d97aebe3d2c443ff163cc3e8a8ea4be443200bf2654003fbd8e28be4c37e93ea60ae6be6abc443fdc68c03edd2486be304ad0bef75a143f89d028be9c33c23e07f006bf21b0423f0f0bb53ef8c264be6dfd9cbe67f0233f820029bec520903ea69b14bf8195433fe78ca83e14ae47be498446bee6ad2e3f89d028beeb73353ef6971dbf1895443ff5b99a3e6ade31be718e9abdff06353f7a8e28be70ce883d136123bf1d5a443f4d158c3e0bb526be162f963dfb07353f0ad628be257582bddc4623bf6f81443fd881733ed49a26be43e3493ef59e2e3f89d028be107a36be3b701ebf0ad7433fbde3543ea1f831bea7079d3e9a41243f3f0029be96218ebe8a8e14bfea04443f910f3a3ecb1047beadf9d13e232e143f89d028be2497bfbed42b05bffd87443f8941203e9d1165be6f0c013fae0e003f134528bea857eabe9318e4be66f7443fba6b093e827386bec2de183f4f76c33e17d728beb5a609bf0ebeb0be4bea443ff8c2e43d6688a3be6808273fbc598b3e89d028be0c9319bf333373be8195433f39b4c83dad69bebe793d303f9a24263ec28928be418222bf4df30ebe5c8f423f7e1db83dd0b3d9bed1b0343f5873803ba0e228be014d24bf89d2debb8f53443f7b83af3dc05b00bff298313fc7bceebd89d028be6ea321bf8c4aea3d1d5a443fc66db43d80480fbfd2512a3f4e7b6abeb1c328be917e1bbfad695e3e0f9c433f1361c33de4141dbf3b1c1d3f45bcadbe89d028beaed80fbf098a9f3e3d2c443f88f4db3d2cd42abf0a6a0c3fa8e5dfbeb1c328bea4dffebeaa82d13e61c3433f91edfc3d27c236bf2bc2ed3e9dda05bf89d028be7502dabea323f93edc46433f73d7123e378941bf700bbe3ef9d617bf7dea28be9f3cacbe12a50d3ffc18433f1e162a3ee7fb49bfb950693ead3029bf1e8c28be742457be9a081b3fe17a443f166a4d3ed34d52bf5ab7e13dd63b30bfd4d628be60e510beae47213f4a7b433f8c4a6a3e190456bfaf0b3fbeeee82bbfacacfdbe0e4f8f3e8fc2753fa69bc43a07ce993e26e453bf01a797befce321bff38d28be3d2cd43e50fc683ff4fdd4bb3945a73e89d24ebf4c539cbe70b320bfacacfdbe2575e23e3d9b653f5f294b3b9565a83ee9484ebf8465dcbe51f40cbf8ecb28be7f6a1c3fdaac4a3ffaed6bbabec1b73e6abc44bf89d0e0be76180bbfacacfdbeb7d1203f022b473fca54c13b19e2b83e26e443bf85950abf300de3becea528be46b6433f8104253f99bb96bb5d6dc53ec28637bfa62611bffd10d3be69acfdbe6dc54e3f79e9163f55c1283c5e4bc83ebc7433bf15ad1cbfd926adbe89d028be7b835f3fb5a6f93e7cf230bb2063ce3e68b32abf79ac29bf76c26bbeacacfdbef775703fb29daf3e6f12033caa60d43ec4421dbfef7229bf25ec6bbe1c2329be5396713f9e5ea93e99bb16bb8f53d43edf4f1dbfd5b132bf2c83aabd9fac28bebbb87d3f029a083e52499dbaacadd83e431c0bbf2efe32bfb56c8dbdacacfdbeb22e7e3ffc18f33d7cf2303b35efd83e9e5e09bf6d8e33bff3712d3d89d028be5bb17f3ff0a746bdfaed6bba4772d93eab3ef7be6d8e33bff3712d3dacacfdbeb29d7f3fae4761bdc364aa3a4772d93eab3ef7bef6ee2fbf9d4b213e437428be75027a3ff6285cbe17b7d1b9fe65d73e2cd4dabe815b2fbf61162a3eacacfdbef085793ff8c264bea69bc43a7424d73ec7bad8be7d5d26bf28ef8b3e89d028be3b016d3f1c7cc1be52491dbae10bd33e2428bebe7d5d26bf28ef8b3eacacfdbe9a776c3fca32c4be3480b73ae10bd33e2428bebe130a19bf8e75c13e349e28be3e79583f1ea708bf348037baf163cc3e8a8ea4be1aa517bf069cc53eacacfdbe62a1563fac8b0bbf5f294b3b8cb9cb3e4182a2be443200bf2654003fbd8e28be508d373f257532bf6f1283badc68c03edd2486be8670fcbe6329023facacfdbe787a353f8a8e34bf6f12033b098abf3e014d84be304ad0bef75a143f89d028be4faf143f4e6250bf348037ba0f0bb53ef8c264be304ad0bef75a143facacfdbed881133f933a51bfc364aa3a0f0bb53ef8c264be6dfd9cbe67f0233f820029bed26fdf3e4b5966bf52499db9e78ca83e14ae47be5c0599be47cc243facacfdbeffb2db3eab3e67bfa69bc43af9a0a73e39d645be498446bee6ad2e3f89d028be29ed8d3efdf675bf6f1203baf5b99a3e6ade31be498446bee6ad2e3facacfdbe569f8b3e304c76bfe02d903af5b99a3e6ade31be718e9abdff06353f7a8e28bef38ed33da9a47ebf17b7d1384d158c3e0bb526be6a6c8fbdae0d353facacfdbe1826d33da9a47ebf52499db9e86a8b3e9d8026be162f963dfb07353f0ad628becb10c7bdfbcb7ebf5f294bbbd881733ed49a26be87c5283d3273353facacfdbe20636ebd97907fbf17b7513a5a647b3ef8c224bed5052c3e38db303facacfdbec7297abe423e78bf52499d3bf6285c3e8d972ebe43e3493ef59e2e3f89d028be20638ebe54e375bffaed6bbabde3543ea1f831bea7079d3e9a41243f3f0029bea470ddbe42cf66bf6f12033b910f3a3ecb1047be5070a13eaa10233facacfdbeee7cdfbebd5266bf6c09f93a10e9373e4c3749beadf9d13e232e143f89d028bea1d614bf174850bf6f1203ba8941203e9d1165beadf9d13e232e143facacfdbeabcf15bf24974fbf52499d3a8941203e9d1165be6f0c013fae0e003f134528bebec137bfb84032bf17b75139ba6b093e827386be6ce9013fb9fefd3ecdacfdbe674439bf65aa30bf3480373bcc7f083e705f87bec2de183f4f76c33e17d728be67d557bfb5a609bfe02d90baf8c2e43d6688a3becb811a3fed28be3eacacfdbeba6b59bfe71d07bfa69bc43a8ab0e13dc217a6be6808273fbc598b3e89d028be96b26cbf6f12c3bec364aaba39b4c83dad69bebe6808273fbc598b3eacacfdbebf7d6dbf492ebfbea69bc43a39b4c83dad69bebe793d303f9a24263ec28928bec7297abf2c6559be17b751ba7e1db83dd0b3d9bebabd303ff2961b3eacacfdbe48e17abfdfe04bbe5f294b3bebe2b63d2d43dcbed1b0343f5873803ba0e228be000080bf52499dbb3480b7ba7b83af3dc05b00bfb490343f1bf2cfbbacacfdbe000080bf89d2de3b3480373b7b83af3d6ea301bff298313fc7bceebd89d028beda1b7cbffca9313e52491dbac66db43d80480fbff298313f4dbdeebdacacfdbe51da7bbfc74b373ec364aa3ac66db43d80480fbfd2512a3f4e7b6abeb1c328be053471bf1f85ab3e52499db91361c33de4141dbf7d79293fa0e072beacacfdbe696f70bf20d2af3ea69bc43a8104c53d091b1ebf3b1c1d3f45bcadbe89d028be12145fbfec2ffb3e52491dba88f4db3d2cd42abf3b1c1d3f45bcadbeacacfdbe20635ebf12a5fd3e3480b73a88f4db3d2cd42abf0a6a0c3fa8e5dfbeb1c328bed0d546bf2041213f17b751b991edfc3d27c236bffad30a3f9086e3beacacfdbeb45945bf6f12233fa69bc43a0000003e14ae37bf2bc2ed3e9dda05bf89d028be70ce28bff775403f348037ba73d7123e378941bf2bc2ed3eadda05bfacacfdbe14ae27bf006f413f3480b73a73d7123e378941bf700bbe3ef9d617bf7dea28be4faf04bf63ee5a3fe02d90ba1e162a3ee7fb49bf5989b93e292019bfacacfdbec1ca01bf08ac5c3fed0d3e3b9f3c2c3e4ca64abfb950693ead3029bf1e8c28bee78ca8bea5bd713ffaed6bba166a4d3ed34d52bf9eb4603e47cb29bfacacfdbeefc9a3be5c8f723f52491d3b60764f3e93a952bf5ab7e13dd63b30bfd4d628bee78c68be8351793ffaed6bba8c4a6a3e190456bfaddee13da83a30bfacacfdbe1dc965bed578793f000000008c4a6a3e190456bfd594803f132d393d9fad0bbf92cb7f3f52491d3d9fabadbb1b2f5d3c4694f6beff94803f621404bdd542c93b3bdf7f3ffe65f7bcb37bf23b1b2f5d3ca1d604bf8c657e3ffa7f15be9fad0bbf560e7d3f63ee1abe5f29cbbb26e4833c8fe412bfd26e7c3f512e5d3eaa29c93b910f7a3f63ee5a3e6c09f93be3a59b3c5f98ccbe18b27a3f889f6f3eb0ad0bbf4c37793f55306a3ebc7493bb2e90a03c9eefc7bead136b3f34bdd43e9fad0bbf3a236a3f840dcf3e17b751bb287e0c3dec2f9bbeb6f2623f522cf73e37e2c93b986e623ffbcbee3ebc74133c567d2e3dbada8abe0f0d533fddeb143fb0ad0bbf33c4513faeb6123f89d2debbf90f693de63f64be7a19413f1bbc2b3fd542c93b09f9403f2731283f6c09f93b7424973db53738bee277333f64023a3f9fad0bbf4013313f8cdb383f6c09f9bb575bb13d08ac1cbec43e1d3fa33e4d3f713ac93b0d711c3fbe9f4a3f3b70ce3bf628dc3dd734efbd9acc043f80455e3fbcae0bbf9c33023f7f6a5c3f4ed111bc6ff0053e57ecafbd912ce83eaa2b673f0e32c93b5d6de53e2fdd643fd044583b74b5153e96438bbdd95c853e73b87a3f2aad0bbf55c1883e7dae763f82e2c7bbcba1453eb9fc07bd41458d3e06f2783f713ac93b3b018d3ec217763fe02d903b3789413edf4f0dbd514bb33d00e6803f713ac93b1214bf3da4df7e3ffaed6b3a57ec6f3e05a392bc0397473d1a1a813f9fad0bbffca9713d97907f3fc364aaba4772793e26e483bc7ae1aebd00e6803f9fad0bbf1214bfbda4df7e3ffaed6bbadf4f8d3e05a392bc7ac63ebd1a1a813fd542c93bfca971bd97907f3fc364aa3ae78c883e26e483bc354184bea6b87a3f8010c93b8a8e84be3945773fed0d3e3b4182a23eb9fc07bd452a8cbe39f2783f9fad0bbf3b018dbec217763fe02d90bb8a8ea43edf4f0dbd4913e7befe2b673f9fad0bbfd3bce3be0b46653fc364aaba516bba3e96438bbd58c6eebe902f653f0e32c93bde02e9be41f1633f52499d3a2d43bc3e73d792bd00ae14bf12de523f9fad0bbf2aa913bf5c20513f6f1283ba158cca3ef5dbd7bd00ae14bf12de523fd542c93b984c15bf72f94f3ffaed6b3a158cca3ef5dbd7bd88652fbf15533d3f9fad0bbf7b832fbf355e3a3ffaed6bbaab3ed73ebd5216be168832bfda573a3f384bc93b1c7c31bfcc7f383ffaed6b3ae3c7d83ebf0e1cbe959e49bf0070203f9fad0bbf029a48bf840d1f3f6f1283ba9318e43e166a4dbe959e49bf0070203fd542c93b07ce49bf4d841d3ffaed6b3a9318e43e166a4dbe766b5dbf07d0033fb0ad0bbf04565ebf64ccfd3e52499dba166aed3e789c82bec4955fbf020f003f713ac93b77be5fbfe3c7f83e52499d3a3b70ee3e143f86bed19075bfdb6a9e3e4bad0bbfaa6074bf5986983e04e78cbb5986f83e58a8b5beec6d73bf7e1aa73ed542c93bce8872bf0ad7a33ebc74933b4703f83ee926b1be6a847ebf3cf70e3ed542c93bbbb87dbf5e4b083e9fabad3b8863fd3e643bdfbe7b3380bfe4f5803d9fad0bbf60767fbfddb5843db37bf2bb5b42fe3e0e2df2be9c3380bf47024dbdd542c93b60767fbfddb584bdb37bf23b5b42fe3e3d0a07bfe0847ebfe8bd01be9fad0bbf2db27dbf029a08be52499dbb8863fd3e857c10bfb98a75bf040398bee318c93b2aa973bfadfa9cbe6c09f93b5986f83e265325bfe86673bfa7b2a0be9fad0bbfb37b72bf9318a4bebc7493bb2bf6f73e6b9a27bf83895fbf2192f9beb0ad0bbf643b5fbfa392fabe7cf230bb3b70ee3ec8073dbf6caf55bfd2560dbf9aeac93bfdf655bf448b0cbfbc74133c9e5ee93e14d044bf249b43bfa5d924bf9fad0bbf38f842bf54e325bf3b70cebb09f9e03e325550bf20622fbfe90e3abf384bc93ba9a42ebf5f293bbf4ed1113cab3ed73e158c5abf48a520bf2dd046bf9fad0bbfc52020bf30bb47bf9fabadbbe02dd03e80b760bfdae1f7beb03c60bf713ac93b2bf6f7be72f95fbf04e78c3b3f35be3e287e6cbf87bfeebe5be961bfb0ad0bbf9fabedbe3cbd62bf0ad7a3bb2d43bc3ed6c56dbfbb7f94be3a5b74bf9fad0bbf10e997bee17a74bf6f1203bab98da63e0bb576bfba2e8cbe929375bfd542c93bd71292beb45975bf6f12033a8a8ea43ec74b77bf390d11be10937cbf9fad0bbf3d9b15bec4427dbffaed6bba7446943ed5e77abf390d11be10937cbfd542c93bf2d20dbedb8a7dbffaed6b3a7446943ed5e77abf446ff1bba6627fbf9fad0bbf3480b7bb000080bf17b7d1bab8af833ea3017cbff8fd1b3ca6627fbfd542c93b3480b73b000080bf17b7d13a5396813ea3017cbf5b41133e10937cbf9fad0bbff2d20d3edb8a7dbffaed6bba2eff613ed5e77abf5b41133e10937cbfd542c93b3d9b153ec4427dbffaed6b3a2eff613ed5e77abf30498d3eb49375bf9fad0bbfd7a3903eaf9475bf348037ba3789413ec74b77bfb79a953e185b74bfd542c93ba2b4973e6f8174bf52499d3aa4703d3e0bb576bffa27e83e32e563bf9fad0bbf27c2e63efd8764bf27a009bb74b5153e52b86ebfcfd8ef3e5be961bf384bc93b2063ee3e5c8f62bffaedeb3abc05123ed6c56dbf54ad1d3fd59349bf9fad0bbf287e1c3f31994abf89d2deba3f57db3d4a0c62bfb613213fc5e746bf713ac93b20631e3f151d49bf6f1203ba86c9d43d9cc460bffbe8403f7ba128bfb0ad0bbf24973f3f7ac729bf04e78cbb508d973d812652bf732d3a3f4a962fbfd542c93b27a0393fa54e30bf52491d3a0ad7a33de6ae55bf0c21533f157211bf713ac93b58ca523fae4711bfe71da73b1ea7683d22fd46bf3faa5d3fc00901bf9fad0bbf9a775c3f651902bf5f29cbbb371a403d12143fbf9e266b3f98c0cdbe384bc93b23db693f1748d0be5f29cb3b4d150c3d257532bf1475723fe04ba9be9fad0bbfce19713f3108acbe5f29cbbbf775e03c43ad29bf18b27a3ff47062bed542c93b1058793fd50968be5f29cb3b2e90a03cf6281cbfa25e903ec64e983ee88418bf000000000000803f00000080c9e53f3e992ab8be7bbba53ec64e983e9fad0bbf000000000000803f0000008058a8353e992ab8be7bbba53ec64e983ee88418bf000000000000803f0000008058a8353e992ab8bea25e903ec64e983eacacfdbe000000000000803f00000080c9e53f3e992ab8bedd98be3ec64e983eacacfdbe000000000000803f000000800c93293e992ab8bedd98be3ec64e983e9fad0bbf000000000000803f000000800c93293e992ab8be91448fbec64e983eacacfdbe000000000000803f000000802653a53e992ab8becc7ebdbec64e983ee88418bf000000000000803f00000080857cb03e992ab8becc7ebdbec64e983eacacfdbe000000000000803f00000080857cb03e992ab8be91448fbec64e983ee88418bf000000000000803f000000802653a53e992ab8becc7ebdbe234eaf3e9fad0bbf0000803f0000000000000000857cb03e560eadbecc7ebdbec64e983ee88418bf0000803f0000000000000000857cb03e992ab8becc7ebdbe234eaf3ee88418bf0000803f0000000000000000857cb03e560eadbecc7ebdbec64e983eacacfdbe0000803f0000000000000000857cb03e992ab8becc7ebdbeb6a1163facacfdbe0000803f0000000000000000857cb03ec05b60becc7ebdbeb6a1163f9fad0bbf0000803f0000000000000000857cb03ec05b60bea418203efa26adbe9fad0bbf6f12833a6f1203ba0000803fc0ec5e3e68b32abf3dd4b6bca9a313bed5950bbf17b751ba17b751390000803f5d6d853ef31f12bf022e08be105c01bf9fad0bbf17b7d1b9348037ba0000803f3333933eb7623fbf6b2d8c3d912804be9fad0bbffaedeb3a17b7513a0000803fa1d6743e499d10bf11ffd0bd02d7d5bd9fad0bbfc364aaba17b7d1380000803fd7348f3ef6970dbfabec4bbe6006233dd6ac0bbf17b7d1b817b7d1390000803f11c79a3e6210f8be0c3a11be91b932bd9fad0bbf24977f3b0ad7233b0000803f0612943e6b2b06bff66218be9fcae93c9fad0bbf17b75139000000800000803fbde3943ef5b9fabea697f8bd8d60c33d9fad0bbf52499dba89d2de3a0000803fc58f913ec364eabefc1813be541f183eaab60bbfa69bc4ba27a0093b0000803f211f943e64ccddbe3b1d88bdb683113e9fad0bbfc364aabae02d103b0000803fbada8a3ef706dfbe63450dbdbc08533ea2b40bbffaed6bba6c09f93a0000803f94f6863e3bdfcfbe0d6c953b85b5213e9fad0bbf52491dbac364aa3a0000803f9c33823eec2fdbbe82ac873d6bf1493e9fad0bbf17b751b952499d390000803fb459753e575bd1becc9a983d2e540e3eb0ad0bbf52499db96f12833a0000803fa167733e92cbdfbe4ab60a3e4297203eb0ad0bbf52491d3a0ad7233b0000803f5474643e3f57dbbe3ae90d3e0953943dd4990bbffaed6b3bfaedeb3a0000803f1d5a643e3255f0bed9973c3e0d50ba3d9fad0bbf3480b73b0ad7233b0000803f3e79583ee86aebbe18b14f3ec1e5313c9fad0bbf6f12033be02d90ba0000803fcff7533e1214ffbeb7991a3ebd007bbc9fad0bbf52491d3ae02d90ba0000803f9cc4603e3cbd02bf9be3fc3dfa45a9bd9fad0bbf00000000000000000000803f8b6c673e63ee0abfa25e903e4032bd3e9fad0bbf0000803f0000000000000000c9e53f3e4b59a6bea25e903ec64e983ee88418bf0000803f0000000000000000c9e53f3e992ab8bea25e903e4032bd3ee88418bf0000803f0000000000000000c9e53f3e4b59a6bea25e903ec64e983eacacfdbe0000803f0000000000000000c9e53f3e992ab8bea25e903eb6a1163facacfdbe0000803f0000000000000000c9e53f3ec05b60bea25e903eb6a1163f9fad0bbf0000803f0000000000000000c9e53f3ec05b60be88f2053eb9a9213fe88418bfbbb85d3fc9e5ff3e000000800b46653e280f4bbea25e903e4032bd3e9fad0bbfbbb85d3fc9e5ff3e00000080c9e53f3e4b59a6bea25e903e4032bd3ee88418bfbbb85d3fc9e5ff3e00000080c9e53f3e4b59a6be88f2053ecaa9213f9fad0bbf12a55d3f371a003f000000800b46653e280f4bbe7bbba53ec64e983ee88418bfbbb85d3fe5f2ff3e0000008058a8353e992ab8be90a20e3f3220dbbd9fad0bbfbbb85d3fe5f2ff3e00000080d9cef73d091b0ebf90a20e3fb820dbbde88418bfbbb85d3fe5f2ff3e00000080d9cef73d091b0ebf7bbba53ec64e983e9fad0bbfbbb85d3fe5f2ff3e0000008058a8353e992ab8be91448fbec64e983ee88418bf000080bf00000000000000002653a53e992ab8be91448fbeab09ca3e9fad0bbf000080bf00000000000000002653a53e5227a0be91448fbeab09ca3ee88418bf000080bf00000000000000002653a53e5227a0be91448fbec64e983eacacfdbe000080bf00000000000000002653a53e992ab8be91448fbeb6a1163facacfdbe000080bf00000000000000002653a53ec05b60be91448fbeb6a1163f9fad0bbf000080bf00000000000000002653a53ec05b60be88f2053ecaa9213f9fad0bbfaed8ffbe48bf5d3f000000800b46653e280f4bbe91448fbeab09ca3ee88418bfaed8ffbe48bf5d3f000000802653a53e5227a0be91448fbeab09ca3e9fad0bbfaed8ffbe48bf5d3f000000802653a53e5227a0be88f2053eb9a9213fe88418bfaed8ffbe48bf5d3f000000800b46653e280f4bbe24b726bf9a60383ee88418bf8e0600bf2db25d3f000000804f40d33e6132d5becc7ebdbe234eaf3e9fad0bbf8e0600bf2db25d3f00000080857cb03e560eadbecc7ebdbe234eaf3ee88418bf8e0600bf2db25d3f00000080857cb03e560eadbe24b726bf9a60383e9fad0bbf8e0600bf2db25d3f000000804f40d33e6132d5be1fbc06be4ca55f3dbd1812bf00916ebf4772b93e4b5986bc6154923e6abcf4be73be08bee46ae4bc441912bfacad78bfb37b72be48507cbce10b933ee63f04bfd5200cbe16f634bc874fcabe44fa7dbf2497ffbdf085493ca167933e0e2d02bf8d4503bedb696b3d874fcabe91ed6cbfdcd7c13ecdcc4c3c7c61923e46b6f3beccd1a3bd1ff8f83d594ecabe613215bf8e06503fc6dc353c9f3c8c3e2fdde4befe277fbd3fac073ece1812bfa167f3be772d613f744694bc3a238a3ed34de2bec2a4f8bb28b9133e874fcabe70ce88bdb7627f3fe02d903cb8af833eed0ddebe85941f3ce5b8133e441912bfde02893db7627f3fe02d90bc5396813eed0ddebe54a7833dcfbd073ed750cabe2731e83e211f643f24977f3cd95f763eb840e2be3812a83dfe0af93deb1912bf5839143f65aa503fbbb88dbcd712723e2fdde4be8109fc3df60b963d874fcabec0ec5e3fac8bfb3e295c8f3cf9a0673e20d2efbe5187053e11186b3d441912bf6d566d3f92cbbf3e24977fbc1dc9653e46b6f3be24610f3e1538d93ba94fcabe72f97f3f52491dbc8c4a6a3c6688633e1b0d00bf22530e3e9a7c33bc541912bf29ed7d3f5c2001be17b751bcd3bc633e0e2d02bf92b3f03d3e4097bd1a51cabe80484f3fdd2416bf3108ac3cb1e1693e0c9309bf08b0e83daebb99bd441912bf17d94e3fb4c816bf3ee859bcb1e1693ee7fb09bf4e7c753d77bdf4bd441912bf3255d03e07ce69bf006f81bc22fd763e44690fbf336dff3cdae305bec349cabe0e2d323e5b423ebfcf66253f24287e3ec05b10bf0589edbc2ae505be091b12bfaa6054bea8577abff241cfbcf931863ec05b10bfc5ab6cbd77bdf4bd874fcabe2d439cbe894130bf226c283f95d4893e44690fbfdf4fcdbdad6eb5bd541912bf029a38bf3c4e31bf55c1a8bc4df38e3ee3a50bbff756e4bd9a9999bd874fcabe295c4fbfc21716bffaed6b3c3255903ee7fb09bfd5200cbe16f634bc874fcabe00000000000000000000803fa167933e0e2d02bf24610f3e1538d93ba94fcabe0000008017b7d1b80000803f6688633e1b0d00bf8d4503bedb696b3d874fcabe00000000000000000000803f7c61923e46b6f3be92b3f03d3e4097bd1a51cabe0000000017b7d1380000803fb1e1693e0c9309bff756e4bd9a9999bd874fcabe17b7d138000000800000803f3255903ee7fb09bf8109fc3df60b963d874fcabe00000000000000000000803ff9a0673e20d2efbeccd1a3bd1ff8f83d594ecabe0000008017b7d1b80000803f9f3c8c3e2fdde4be54a7833dcfbd073ed750cabe17b7d13817b751390000803fd95f763eb840e2bec2a4f8bb28b9133e874fcabe17b7d138000000800000803fb8af833eed0ddebe22530e3e9a7c33bc541912bf17b7d1b8000000000000803fd3bc633e0e2d02bf5b971a3e9fcae93c441912bf6f12833a3480373a0000803f9cc4603ef5b9fabe5187053e11186b3d441912bf865a533c99bb163c72f97f3f1dc9653e46b6f3be726e133e91b932bd441912bfe02d903ae02d90ba0000803f0a68623e6b2b06bfd21de43d86e4e43dba2c12bff775603ca323393ce5f27f3fd5e76a3e5ddce6be3812a83dfe0af93deb1912bfb9fc873ca323b93cc9e57f3fd712723e2fdde4beaddc0b3d53061e3e441912bffaed6b3a52491d3a0000803f363c7d3ebf0edcbe85941f3ce5b8133e441912bf3480373a89d2deba0000803f5396813eed0ddebe797679bdd521173e121512bf52499d39b37b72bb0000803fe7fb893e1b9edebefe277fbd3fac073ece1812bffaed6bbaa69b44bb0000803f3a238a3ed34de2be3e3f0cbe2a37913dd02b12bf4d158cbc11c73a3ce5f27f3f1826933ef2b0f0be1fbc06be4ca55f3dbd1812bf16fbcbbc04e70c3c57ec7f3f6154923e6abcf4be526518bebd007bbc441912bfc364aaba17b751390000803fbde3943e3cbd02bf73be08bee46ae4bc441912bf00000000000000000000803fe10b933ee63f04bf577bf8bdfa45a9bd441912bf00000000000000000000803fc58f913e63ee0abfdf4fcdbdad6eb5bd541912bf17b7d13817b7d1380000803f4df38e3ee3a50bbfe09e87bd943304be441912bf3480373a17b7513a0000803fbada8a3e499d10bf0589edbc2ae505be091b12bf3480373a17b7513b0000803ff931863ec05b10bf0d6c953b384c14be441912bf52499d3952499db90000803f9c33823ece8812bf4e7c753d77bdf4bd441912bfd044583c645ddcbc3bdf7f3f22fd763e44690fbff99fbc3d145fedbdcb3012bfac8b5b3c4d158cbce5f27f3f7b836f3e52b80ebf08b0e83daebb99bd441912bf4d158c3c27a089bc57ec7f3fb1e1693ee7fb09bfb7991a3ebd007bbc9fad0bbfd2007ebf6de7fb3dc6dcb53c9cc4603e3cbd02bf3ae90d3e0953943dd4990bbf068165bfffb2dbbe1cebe23d1d5a643e3255f0be5b971a3e9fcae93c441912bfba497cbff5db17be0b24a8bd9cc4603ef5b9fabe726e133e91b932bd441912bfee7c6fbffd87b43e0f0bb5bc0a68623e6b2b06bf9be3fc3dfa45a9bd9fad0bbf17d94ebfddb5143f151dc93d8b6c673e63ee0abff99fbc3d145fedbdcb3012bffa7e1abf88f44b3f4a7b03bd7b836f3e52b80ebf6b2d8c3d912804be9fad0bbfefc9c3be68226c3fac8b5b3da1d6743e499d10bf0d6c953b384c14be441912bf10e9b7bd1b9e7e3ff5db57bd9c33823ece8812bf3dd4b6bca9a313bed5950bbf1d38273e7f6a7c3fde93073d5d6d853ef31f12bfe09e87bd943304be441912bfca32e43e61c3633f5ebac9bdbada8a3e499d10bf11ffd0bd02d7d5bd9fad0bbf3a232a3f643b3f3f0c93a93cd7348f3ef6970dbf577bf8bdfa45a9bd441912bfce19513f2f6e133f728a0ebdc58f913e63ee0abf0c3a11be91b932bd9fad0bbf1b0d703f7cf2b03e4df30e3d0612943e6b2b06bf526518bebd007bbc441912bf44fa7d3fff21fd3da4dfbebcbde3943e3cbd02bff66218be9fcae93c9fad0bbf0d717c3f3b010dbec976be3dbde3943ef5b9fabe3e3f0cbe2a37913dd02b12bf2bf6673fd044d8bec6dcb5bc1826933ef2b0f0bea697f8bd8d60c33d9fad0bbf5c8f423fd3bc23bf8cb9eb3dc58f913ec364eabe797679bdd521173e121512bf8cb9cb3e68b36abf295c0fbde7fb893e1b9edebe3b1d88bdb683113e9fad0bbffe65f73e12145fbfe8d9acbdbada8a3ef706dfbe0d6c953b85b5213e9fad0bbf143f463cc0ec7ebf35efb83d9c33823eec2fdbbeaddc0b3d53061e3e441912bff90f69be674479bf9c3322bc363c7d3ebf0edcbecc9a983d2e540e3eb0ad0bbf3ee8f9be567d5ebf2f6ea33da167733e92cbdfbed21de43d86e4e43dba2c12bfd9ce37bfaed82fbf1dc9e5bdd5e76a3e5ddce6be - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0.0014860034, y: 0.0055050254, z: -0.31478402} - m_Extent: {x: 1.003061, y: 1.003104, z: 0.321336} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh.meta deleted file mode 100644 index 729740715..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Cup 2.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 72f2dad654fb0174e8297830217b596c -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh deleted file mode 100644 index 8b6c20756..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Gottlieb - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 6300 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 2333 - localAABB: - m_Center: {x: -0.0053590536, y: 0.001954496, z: -0.487102} - m_Extent: {x: 1.009431, y: 1.0101185, z: 0.504131} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 0 - m_KeepIndices: 0 - m_IndexFormat: 0 - m_IndexBuffer: c305c405c505000001000200020042000000030002000100000042004100010004000300420040004100050004000100410040003f0006000400050040003e003f000700060005003f003e003b000800070005003e003c003b003b003c003d000900070008000a00090008000b0009000a000c000b000a000e000b000d000d000b000c0043000d000c00440043000c004500430044004600450044004700450046004800470046004900470048004a00490048004b0049004a004c004b004a004d004b004c004e004b004d004f004e004d000f001000110012000f00110011007d00120013000f00120012007d007c00130014000f007d007b007c001500140013007c007b0079001600150013007b007a00790017001500160079007a0078001800170016007600790078001900170018007600780077001a00190018007700750076001b0019001a007600750074001c001b001a007500730074001d001b001c001e001d001c00740073007200730070007200720070007100710070006f0070006b006f006f006b006e006e006b006d006b006c006d001f002000210022001f00210023001f0022002400230022002500230024002600250024002700250026002800270026002900270028002a00290028002b0029002a002c002b002a002d002b002c002e002d002c002f002d002e0030002f002e0031002f0030003200310030003300310032003400330032003500330034003600350034003700350036003800370036003900380036003a00380039005000510052005300510050005400530050005500530054005600550054005700550056005800570056005900570058005a00590058005b0059005a005c005b005a005d005b005c005e005b005d005f005e005d0060005e005f0061005e0060006200610060006300610062006400630062006500630064006600630065006700660065006800660067006900680067006a00680069007e007f008000820080007f0081007f007e00830082007f007e00f20081008400820083008100f200f100850082008400f200f000f100860085008400f100f000ee00870085008600f000ef00ee00880087008600ef00ed00ee00890087008800ee00ed00ec008a0089008800ed00eb00ec008b008a008800ec00eb00e9008c008a008b00eb00ea00e9008d008c008b00ea00e800e9008e008c008d00e900e800e6008f008e008d00e800e700e60090008e008f00e700e500e600910090008f00e600e500e400920091008f00e500e200e400930091009200e400e200e300940093009200e300e200e100950093009400e200e000e100960095009400e100e000df00970095009600e000de00df00980097009600df00de00dd00990097009800de00dc00dd009a0099009800dd00dc00da009b0099009a00dc00db00da009c009b009a00db00d900da009d009c009a00da00d900d8009e009c009d00d900d700d8009f009e009d00d800d700d600a0009f009d00d700d500d600a1009f00a000d600d500d400a200a100a000d500d300d400a300a100a200d400d300d100a400a100a300d300d200d100a500a400a300d200d000d100a600a500a300d100d000cf00a700a500a600d000ce00cf00a800a700a600cf00ce00cd00a900a700a800ce00cc00cd00aa00a900a800cd00cc00cb00ab00a900aa00cc00ca00cb00ac00ab00aa00cb00ca00c900ad00ac00aa00ca00c800c900ae00ac00ad00c900c800c700af00ae00ad00c800c600c700b000ae00af00c700c600c500b100b000af00c600c400c500b200b000b100c500c400c300b300b000b200c400c200c300b400b300b200c300c200c100b500b300b400c200c000c100b600b500b400c100c000bf00b700b600b400c000be00bf00b800b600b700bf00be00bc00b900b800b700be00bd00bc00ba00b800b900bd00ba00bc00bb00ba00b900bc00ba00bb00f300f400f500f600f500f400f5006501f300f700f500f600f30065016401f800f700f600650163016401f900f700f800640163016201fa00f900f800630161016201fb00f900fa00620161016001fc00fb00fa0061015f016001fd00fb00fc0060015f015e01fe00fd00fc005f015c015e01ff00fd00fe005e015c015d010001fd00ff005d015c015b0101010001ff005c015a015b010201000101015b015a0159010301020101015a01580159010401020103015901580157010501040103015801560157010601040105015701560155010701060105015601540155010801070105015501540153010901070108015401520153010a01090108015301520151010b0109010a015201500151010c0109010b01510150014f010d010c010b0150014e014f010e010c010d014f014e014d010f010e010d014e014c014d0110010f010d014d014c014b0111010f0110014c014a014b011201110110014b014a0149011301110112014a01470149011401130112014901470148011501140112014801470146011601140115014701450146011701160115014601450143011801160117014501440143011901180117014401420143011a01180119014301420141011b011a0119014201400141011c011a011b01410140013f011d011c011b0140013e013f011e011d011b013f013e013c011f011d011e013e013d013c0120011f011e013d013b013c0121011f0120013c013b013a0122011f0121013b0139013a012301220121013a01390137012401220123013901380137012501240123013801360137012601240125013701360135012701260125013601330135012801260127013501330134012901280127013401330132012a01290127013301310132012b0129012a013201310130012c012b012a0131012f0130012d012b012c0130012f012e012e012d012c012f012d012e0166016701680169016601680168016701dc016a01660169016701db01dc016b016a016901dc01db01da016c016a016b01db01d901da016d016c016b01da01d901d8016e016c016d01d901d701d8016f016e016d01d801d701d60170016f016d01d701d501d60171016f017001d601d501d301720171017001d501d401d301730171017201d401d201d301740173017201d301d201cf01750173017401760173017501d201d001cf01cf01d001d1017701780179017a01780177017b017a0177017c017a017b017d017c017b017e017c017d017f017e017d0180017e017f01810180017f018201800181018301820181018401820183018501840183018601840185018701860185018801870185018901870188018a01890188018b0189018a018c018b018a018d018b018c018e018d018c018f018d018e0190018f018e0191018f0190019201910190019301910192019401930192019501940192019601970198019901960198019a01960199019b019a0199019c019a019b019d019c019b019e019c019d019f019e019d01a0019e019f01a101a0019f01a201a001a101a301a201a101a401a201a301a501a401a301a601a401a501a701a601a501a801a601a701a901a801a701aa01a801a901ab01aa01a901ac01aa01ab01ad01ac01ab01ae01ad01ab01af01ad01ae01b001af01ae01b101af01b001b201b301b401b501b301b201b601b501b201b701b601b201b801b601b701b901b601b801ba01b901b801bb01b901ba01bc01bb01ba01bd01bb01bc01be01bd01bc01bf01bd01be01c001bf01be01c101bf01c001c201c101c001c301c101c201c401c301c201c501c301c401c601c501c401c701c501c601c801c701c601c901c701c801ca01c901c801cb01c901ca01cc01cb01ca01cd01cb01cc01ce01cd01cc01dd01de01df01e001de01dd01e101e201e301e401e101e301e201e501e601eb01e601e501e701e501e201ea01e701e901ec01ea01e901ed01ec01e901e901e701e801e801e701e201e801ee01ef01f501ef01ee01f601f501ee01f001ee01e801e201f001e801f801f001f701f901f801f701fa01f901f701f701f001f101f101f001e201f201f101e201f301f101f201f401f101f301fb01f401f301fc01fb01f301e201ff01f201ff01fd01f201f201fd01fe010402fe01fd0105020402fd010002ff01e2010602ff0100020702ff01060208020702060209020802060201020002e2010202000201020302000202020a02030202020b020a0202020c020a020b02e2010f020102e1010f02e2010f020d02010201020d020e0210020e020d02110210020d02e10114020f02140212020f020f02120213021902130212021a021302190215021402e1011b02140215021c0214021b021d021c021b021e021c021d021f021e021d0216021502e101170215021602180215021702200218021702210220021702e101240216022402220216021602220223022902230222022a022902220225022402e1012b02240225022c0224022b022d022c022b022e022d022b0226022502e1012702250226022802250227022f0228022702300228022f02e1013302260234023302e10135023402e1013302310226022602310232023902320231023a02390231023b02330234023c0233023b023d023c023b023602340235023702360235023802360237023e023f02400240024c023e02430240023f023e024c024b02440243023f024b024a023e0241023f023e0242023f0241023e024a024702450244023f024a02490247024602440245024702460245024902480247024802460247024d024e024f0251024f024e0252024f02510253025202510250024e024d02840253025102840251024e024e025002780250024d027802780284024e024d028102760278024d027602830289025302760275027802750284027802530282028302820253028402830282028a0275028202840275027602770277027c0275027d027702760254025502560257025502540255025702580254026802570254028502680285028602680268025b02570257025b0258025b0259025802580259025a025c025d025e025c025f025d025f025c025e025d025f0260025e026702620261025e0262025e0261025f02610260025f026a026002610263026402650266026402630265026402690266026302700265026902870288028702690265027302630273026502870270026302730273028702880273027b02700273027e027b0288027e0273027e0288028c028b028c0288026b026c026d026e026c026b026e026f026c026e026b02710271026b02720271027202740271027402790271027a026e027a026f026e0279027a02710279026f027a027f026f02790280027f0279028d028e028f0290028e028d0291029202930293029702910296029702930295029702960294029502960294029802950299029a029b029c029a0299029d029e029f02a0029e029d02a102a202a302a502a102a302a402a502a302a102a602a202a202a602a702a802a902aa02ab02a902a802ac02ad02ae02af02ad02ac02b002b102b202b002b502b102b602b502b002b102b302b202b202b302b402b702b802b902ba02b702b902bb02bc02bd02be02bb02bd02bf02c002c102bf02c102c402c402c502bf02c002c302c102c202c302c002c602c702c802c902c602c802c8028303c902c9028303820382038303840385038303c80286038503c802870385038603880385038703890388038703d6028803d702d702d402d602d402d502d6028a038803d6028b038a03d6028c038a038b038d038c038b03e2028c038d038e038c03e202e4028e03e202e202e302e402ca02cb02cc02cd02ca02cc02ce02cf02d002d102cf02ce02d102ce02d202d102d202d302d802d902da02db02d902d802dc02dd02de02df02dc02de02dc02df02e002df02e102e002e502e602e702e802e602e502e902ea02eb02ec02ea02e902ed02ee02ef02ed02ef02f502f502ef02f202f202f602f502f602f202f102f002f102f202f102f002f302f402f302f002f702f802f902fa02f802f702fb02fc02fd02fe02fc02fb02ff02000301030003ff02030302030303ff020103000304030503010304030603070308030903070306030a030b030c030d030b030a030e030f03100311030f030e0312031303140312031703130318031703120315031403130316031403150319031a031b031c031a0319031d031e031f0320031e031d03210320031d032203200321032303200322032403230322039a03990324032403990325032503230324032603230325032703260325032803260327032903260328032a03260329032b032a032903a803a7032b032b03a7032c032c032a032b032d032a032c032e032d032c032f032d032e0330032d032f033103320333033403310333033503310334033603310335033703360335033803360337033903360338033a03360339033b0336033a033c033b033a033d033b033c033e033b033d033f033b033e0340033b033f03410340033f034203400341034303400342034403430342034503430344034603430345034703430346034803470346034903470348034a03470349034b034a0349034c034a034b034d034a034c034e034a034d034f034e034d0350034e034f0351034e03500352034e03510353034e03520354034e0353035503540353035603540355035703540356035803540357035903580357035a03580359035b0358035a035c0358035b035d035c035b035e035c035d035f035c035e0360035f035e0361035f03600362035f03610363035f03620364035f03630365035f03640366035f03650367035f0366036803670366036903670368036a03670369036b0367036a036c0367036b036d036c036b036e036c036d036f036c036e0370036f036e0371036f03700372036f03710373036f03720374036f03730375036f0374037603750374037703750376037803750377037903750378037a03750379037b037a0379037c037a037b037d037a037c037e037a037d037f037e037d0380037e037f0381037e0380038f039003910392038f03910393038f0392039403950396039703950394039803950397039b039c039d03a2039d039c039e039c039b039b039f039e039e039f03a1039f03a003a103a303a403a503a603a403a303a903aa03ab03ab03aa03ac03a903ad03aa03ad03a903b103b103b203ad03ae03af03b003ae03b303af03af03b303b403b503b603b703b803b503b703b903ba03bb03bc03b903bb03bc03bb03bd03be03bc03bd03bf03be03bd03c003be03bf03c103c203c303c403c203c103c503c603c703c803c603c503c903ca03cb03c903cc03ca03cc03c903cd03cb03ca03ce03d003cb03ce03cf03d003ce03d103d203d303d403d203d103d503d603d703d803d603d503d903da03db03da03dc03db03db03dc03dd03d903df03da03e003df03d903de03df03e003e103e203e303e403e203e103e503e603e703e803e603e503e903ea03eb03ec03ea03e903ec03f003ea03eb03ef03e903ef03eb03ed03ed03ee03ef03f103f203f303f403f203f103f503f603f703f803f603f503f903fa03fb03fb030204f9030204fb03fd03fc03fd03fb03fd03fc03fe03fe03ff03fd03ff03fe03000400040104ff030304040405040604040403040704080409040a04080407040b040c040d040d040c041004100411040d040e040c040b040f040e040b040f0412040e041304140415041604140413041704140416041804170416041904170418041a04190418041b0419041a041c0419041b041d041c041b041e041c041d041f041c041e0420041c041f04210420041f042204200421042304220421042404220423042504220424042604250424042704250426042804250427042904250428042a04290428042b0429042a042c042b042a042d042b042c042e042b042d042f042e042d0430042e042f04310430042f04320431042f043304310432043404310433043504340433043604340435043704360435043804360437043904360438043a04390438043b0439043a043c043b043a043d043b043c043e043b043d043f043e043d0440043e043f04410440043f044204400441044304420441044404420443044504420444044604470448044904470446044a044b044c044d044b044a044e044c044b044f044c044e0450044f044e0451044f0450045204510450045304510452045404530452045504530454045604550454045704550456045804570456045904570458045a04590458045b0459045a045c045b045a045d045b045c045e045d045c045f045d045e0460045f045e0461045f0460046204610460046304610462046404630462046504630464046604650464046704680469046a04680467046b046a0467046c046a046b046d04690468046e0469046d046f046e046d0470046e046f04710470046f047204700471047304720471047404730471047504730474047604750474047704750476047804770476047904770478047a04790478047b0479047a047c047b047a047d047b047c047e047d047c047f047e047c0480047e047f0481047e0480048204810480048304810482048404830482048504830484048604850484048704880489048c0489048b048b0489048804be04c4048b04c504be04bd048b04bd04be048b04bf04bd048b048804bf04bd04bf04b5048a0488048704b604bf04880488048a04b604b504bf04b6048a048704b9048a04b904b6048704bc04b904b904bc04ba04ba04b304b904b604b904b304b304b504b604b304b404b5048d048e048f0490048e048d04c30490048d0491048e0490049004c304a3049204910490049004a3049204910492049304a304940492049204940493049404950493049604970498049704a2049d049c0497049d04990498049704a5049b049c0499049a04980498049a049b049a04990497049c049b049a0497049c049a049e049f04a004a1049f049e04a0049f04a404a1049e04ab04a004a404c004a404c104c004a004ae049e04ae04a004c004ab049e04ae04c004c104c204ae04c004c204ae04af04ab04c204af04ae04af04b004ab04af04c204c704c604c704c204a604a704a804a904a704a604a904aa04a704ac04a604ad04a904a604ac04b804aa04a904ac04b804a904bb04aa04b804b704bb04b804b704b804ac04ac04b204b704b104b204ac04c804c904ca04cb04c904c804cc04cd04ce04cf04cd04cc04d004d104d204d104d504d204d304d104d004d404d304d004d604d704d804d604e004d70430052f05d804d8042f052b05d604e104e204e304e104d6042d052c052b052e052c052d052c0526052b052b052605d804e404e304d604e504e304e404e804e504e704e704e504e604e604e504e404e904e404d604ea04e404e904ec04ea04eb04eb04ea04e904ed04e904d604ee04e904ed04f104ee04f004f004ee04ef04ef04ee04ed04f204ed04d604f304ed04f204f604f304f504f504f304f404f404f304f204f704f204d604f804f204f704f904f804f704fa04f804f904fb04f804fa04fc04fb04fa04fd04f704d604fe04f704fd040105fe0400050005fe04ff04ff04fe04fd04da04fd04d6040205fd04da0403050205da04040502050305050504050305060504050505da04d604d904d904d604d804db04da04d904dc04da04db04dd04dc04db04de04dd04db04df04dd04de040705d904d8040805d90407050b0508050a050a05080509050905080507050c050705d8040d0507050c050e050d050c050f050d050e0510050d050f05110510050f0512050c05d80413050c05120516051305150515051305140514051305120517051205d8041805120517051905180517051a05180519051b051a0519051c051705d8041d0517051c0520051d051f051f051d051e051e051d051c0521051c05d80426052105d80422051c0521052705210526052305220521052805270526052405220523052505220524052905270528052a052705290531053205330534053205310531053305400535053405310533053d05400536053405350540053d053f053705340536053f053d053e053805340537053e053d053c053905380537053d053a053c053a05380539053c053a053b053b053a05390541054205430543054805410542054105440544054505420545054405460544054705460549054a054b054b054c05490549054c054d054e054f05500551054f054e0552055305540554055a05520559055a05540554055805590559055805570557055b0559055b05570556055505560557055c055d055e055f055d055c056005610562056305610560056405650566056505640567056c056705640565056a0566056a0569056605680569056a0568056b0569056d056e056f0570056e056d05710572057305740572057105750576057705770576057b057c0577057b057c057d0577057d057c057a057c0578057a05780579057a057e057f05800581057f057e0582058305840585058305820586058705880587058b0588058b058c0588058c058b058a0589058a058b058d058e058f0590058e058d05910592059305940592059105950596059705970596059a05960595059905980599059505990598059b059c059d059e059f059c059e05a005a105a205a305a105a005a405a505a605a705a505a405a805a705a405a905a705a805a905a805aa05ab05ac05ad05ae05ac05ab05d005cf05ad05ad05cf05ab05cf05cd05ab05ab05cd05ce05ce05cd05cc05cd05ca05cc05cc05ca05cb05cb05ca05bd05bd05ca05bb05bb05bc05bd05be05bc05bb05ca05c905bb05bb05c905c605c905c705c605c605c705c805af05b005b105b205b005af05b305b405b505b705b305b505b705b505b805b605b405b305b605ba05b405b605b905ba05bf05c005c105c205c005bf05d105d205d305d405d205d105d505d205d405d605d505d405d705d505d605d805d505d705d905d805d705da05d805d905db05d805da05dc05d805db05dd05dc05db05de05dc05dd05df05dc05de05e005dc05df05e105dc05e005e205e105e005e305e105e205e405e105e305e505e105e405e605e105e505e705e105e605e805e705e605e905e705e805ea05e705e905eb05e705ea05ec05eb05ea05ed05eb05ec05ee05eb05ed05ef05eb05ee05f005eb05ef05f105eb05f005f205f105f005f305f105f205f405f105f305f505f105f405f605f105f505f705f105f605f805f105f705f905f105f805fa05f105f905fb05fa05f905fc05fa05fb05fd05fa05fc05fe05fd05fc05ff05fd05fe050006fd05ff050106fd0500060206fd0501060306020601060406020603060506020604060606020605060706060605060806060607060906060608060a06060609060b060a0609060c060a060b060d060a060c060d060e060a060f060e060d0610060e060f0611060e0610061206110610061306110612061406110613061506110614061606150614061706150616061806150617061906180617061a06180619061b0618061a061c0618061b061d0618061c061e061d061c061f061d061e0620061d061f0621061d06200622061d0621062306220621062406220623062506260627062806260625062906280625062a0628062906290681062a06810682062a062b0628062a062c062b062a062d062b062c062e062b062d062f062b062e0630062b062f06310630062f062f067406310674067506310632063006310633063006320634063306320635063606370638063606350639063a063b063c063a0639063d063e063f0640063d063f0641063d06400642063d0641064306420641064406420643064506440643064606440645064706460645064806460647064906480647064a06480649064b0648064a064c064b064a064d064b064c064e064d064c064f064e064c0650064e064f0651064e0650065206510650065306510652065406530652065506540652065606540655065706540656065806570656065906580656065a06580659065b0658065a065c065b065a065d065b065c065e065b065d065f065e065d0660065e065f0661065e0660066206610660066306610662066406630662066506630664066606650664066706650666066806650667066906680667066a06680669066b066a0669066c066b0669066d066b066c066e066b066d066f0670067106720670066f06730672066f06760677067806780677067c0678067b0676067b0679067606760679067a067d067e067f0680067d067f068306840685068506840689068606830685068506880686068706860688068a068b068c068d068a068c068e068d068c068f069006910692068f0691069306940695069906950694069306970694069606970693069806970696069a069b069c069d069b069a069e069f06a006a1069f069e06a206a306a406a706a206a406a506a706a406a506a606a706a506a806a606a606a806a906aa06ab06ac06ad06ab06aa06ae06af06b006b106af06ae06b206b306b406b206b406b806b306b206b606b506b306b606b506b606b706b706b906b506ba06bb06bc06bd06bb06ba06be06bf06c006c106bf06be06c206c306c406c206ca06c306c306ca06cb06c806c206c406c906c806c406c506c606c706c706cc06c506cd06cc06c706ce06cf06d006d106cf06ce06d206d306d406d306dc06d406d306d206d506d506d206d706d506d706d606d606d706d806d906da06db06db06dd06d906de06dd06db06df06e006e106e206e006df06e306e406e506e406e306e606e606e706e406e506e406e806e806e906e506ea06e906e806eb06ec06ed06ee06ec06eb06ef06f006f106f206f006ef06f306f406f506f606f706f806f906f706f606fa06fb06fc06fd06fb06fa06fe06ff060007ff061b07000700071b071c070107ff06fe0602070107fe060307010702070407030702070507060707070807050707070907050708070a07050709070b070a0709070c070a070b070d070e070f0710070e070d07110710070d071207100711071307120711071407150716071707150714071807170714071907180714071a07180719071d071e071f0725071f071e0720071e071d07260725071e07210720071d07270725072607220721071d072807270726072907270728072307210722072407230722072a072b072c0792072c072b072b072a07430792072b0773072b074307730791072c079207730784079207910792078407730743076b077907840773076b077907730793079107840779076b077607930794079107a60784077907950791079407910795072c0784078507930785078407a607940793078f079407960795078f07960794072e079507960795072e072c072e079607350793078d078f078f078d0790078d079307850734072e0735073507360734072d072c072e072e0734072d072c072d072a0737073407360737073607380739073407370739072d0734073907370740072d073a072a072d0739073a078d0783078e073a073907440740074407390783078d078007850780078d0782078307800781078007850780078107820781078507a60782078107a707a607a7078107a707a607a5072a073a074607440746073a07460743072a077a07a507a607a5077a07750778077a07a60779077807a60776077807790776077a077807760775077a07740775077607740776076d07710772076d076807a307710771076d0768076c076d0776076b076c077607670768076d076c0767076d0764076c076b076c07640767076b074307640767076507680765076707640765076607680765075e07660755076407430743074607550764075b07650765075b075e07640755075c0764075c075b075d075e075b075b075c075d075e075a07a1075a075e075d075d075c0754075a075d075407550754075c0753075a0754075a07530750074a0754075507530754074a074a0755074607500753075107500751075207560752075107560751074c07510753074b074b074c0751074a074b07530747074c074b0745074a0746074a0745074b0744074507460747074b074207450742074b074407420745074107470742074407400742074007410742072f0730073107320730072f0732073307300731073007330733078a073107890731078a0789078a078b078907970731078b078c0789078c07970789078c078607970786078c0787078607870788073b073c073d079b073d073c079c073d079b079d079c079b079e079b073c073c073f079e073f073c073b073e073f073b073b0749073e0748073e0749079f073e074807480749074f074d074e074f075707580759079a0759075807a0075807570759075f07570757075f0761075f0760076107620760075f076307600762076307a20760076907630762076a07630769076e076f07700770076f07a4076f076e07770777076e07980798079907770798077c0799077c0798077d077b077c077d077e077b077d077f077b077e07a807a907aa07ab07aa07a907aa07c307a807ac07aa07ab07a807c307c207ad07ac07ab07c307c007c207ae07ac07ad07c207c007c107af07ae07ad07c107c007bf07b007ae07af07c007bd07bf07b107b007af07bf07bd07be07b207b007b107be07bd07bc07b307b207b107bd07bb07bc07b407b207b307bc07bb07ba07b507b207b407bb07b907ba07b607b507b407ba07b907b807b707b507b607b907b707b807b807b707b607c407c507c607c507cd07c607c407c807c507c507cc07cd07c707c807c407cd07cc07cf07c707ca07c807cc07ce07cf07c907ca07c707cb07ca07c907cf07ce07d107d007d107ce07d107d007d307d207d307d007d307d207d407d207dc07d407d407dc07dd07d507d607d707d707de07d507e607d707d607e607e707d707e607e407e707e407e507e707e407e207e507e307e507e207e207df07e307df07e107e307df07e007e107e107e007f107e007f007f107d807d907da07d807db07d907e807e907ea07eb07e907e807e807ea07f307ea07f207f307f307f207030802080308f20703080208010802080008010801080008fe070008ff07fe07fe07ff07fd07fc07fd07ff07fd07fc07fb07fc07fa07fb07fa07f907fb07f807f907fa07f907f807f707f807f507f707f707f507f407f407f507f607f407f6070408f40704080608040805080608040807080508ec07ed07ee07ef07ed07ec07080809080a0808080c08090809080b080a081d080a080b081d081e080a081d081c081e081d081b081c0818081c081b0818081b0819081708180819081a081808170814081a08170816081a0814081408150816080d080e080f080f0820080d080f080e082108200811080d0821080e08220810080d08110821082208130912081008110813091409210813081208110813081f0812081509140913091609150913091509160910091609110910090f0910091109110919090f0918090f0919091809190908090609180908090709060908090509060907091c09050907091c09f30805091c09df08f308de08f308df08dd08de08df08de08dd08e908e908dd08ea08e808e908ea08e808ea081b091a09e8081b09e7081a091b09e708e4081a09e508e408e708e308e408e5081709e308e5081709e608e3081709e108e608e008e608e108e008e108e208e2081209e008e2080e0912090e09eb081209ec08eb080e09eb08ec08ed08f408ed08ec08f408f108ed08f208f108f408ee08f108f208ee08f208ef08ee08ef08f008ef08f508f008f508f908f008f508f608f908f608f808f908f608f708f808fb08f808f708fa08fb08f708fa080009fb08ff080009fa08ff0801090009ff08fe080109fe08fd080109fc08fd08fe080209fd08fc080209fc080309020903090409040909090209090904090a090a090b0909090b090a090c090b090c090d090c0926080d090c092408260826082408230823082408250825082a08230823082a08270827082a082b0827082b0828082708280829082c082d082e082d0836082e08370836082d083808360837083908380837083a08380839083b083a08390835083b0839083c083b08350833083c0835083508340833082f082d082c0834082f0833082d082f085208340881082f0833082f083208810880082f0832082f08310880087f082f0831082f08300830082f082c082f087f087e087e0855082f08550853082f0852082f0841082f085308540854085308560841082f0851085708560853085108500841085808570853085908580853085a08590853085b085a08530850084f0841085c085b08530841084f084e085d085b085c084e084d0841085e085d085c084d084c0841085f085e085c0841084c084b0860085f085c084b084a0841085c08610860084a0849084108620861085c08410849084808630862085c08480847084108640863085c08470846084108650864085c084108460845085c0866086508450844084108670866085c084408430841086808660867084108430842086908660868083d08410842086a0866086908820841083d086b0866086a083f0882083d086c086b086a083d083e083f0840083e083d086d086c086a086e086d086a086f086e086a0870086e086f08710870086f08720871086f087308710872087408730872087508730874087608750874087708750876087808750877087908780877087a08780879087b087a0879087c087a087b087d087c087b088308840885088608830885088708830886088908850884088808830887088a088908840883088808930884088b088a089308880890088c088b088408900888089208920891089008900891088e0884088d088c0890088e088f0884088e088d088f088e088408840883089b089e088f0884089f089e0884088408a0089f088408a108a008a208a10884088408a308a2089a0884089b08a408a3088408a508a308a408a608a308a5089c0884089a08a708a308a6089d089c089a08a808a708a6089a08cf089d08a908a708a808d008cf089a08aa08a708a908d108d0089a08ab08a708aa089a08d208d108ac08a708ab08d308d2089a08ad08a708ac08d408d3089a08ae08a708ad08af08a708ae08b008a708af089a08d508d408b108a708b008d608d5089a08b208b108b008d708d6089a08b308b108b2089a08d808d708b408b108b308d908d8089a08b508b108b408da08d9089a08b608b108b5089a08db08da08b608b708b108dc08db089a08b808b708b6089508dc089a08b608b908b8089a0899089508b608ba08b908950899089808bb08ba08b608980897089508bc08ba08bb08950897089608940895089608bd08ba08bc08be08ba08bd08bf08ba08be08c008bf08be08c108bf08c008c208bf08c108c308c208c108c408c208c308c508c208c408c608c508c408c708c608c408c808c608c708c908c808c708ca08c908c708cb08c908ca08cc08cb08ca08cd08cb08cc08cd08ce08cb08 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 2333 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 74656 - _typelessdata: b610b43e8e066c3f876d8b3cb7627fbed95f26bf4bc8373f44fa2d3faeb6a2bebac0753ea854753ff2608b3c325530be88f42bbfb072383f2db22d3fe3c798be40a48f3e0057723faf08bebebd5256be5bb12fbf6154323f36ab3e3fd1919cbe43015b3ed3c1763f4fe8bdbe795828be204131bf0ad7333fe4833e3f7dae96bec7113b3e6478783fd767bebeadfa5cbeb07228bfacad383fe4833e3f98dd93be6ac0003eb0e47a3f0b638b3cee5ac2bd2e9030bf4bc8373f4d842d3fa9a48ebedd45383eeca2783f7a3805bf302a29bded0dbebb05c57fbfd8f0443f46b693bedf6c933dd15b7c3f5a4b05bfd57869bdc58f31bf67d537bf2fdd443f1e168abe17610a3dccef7c3f3c678b3cae47e1bcb84032bf6b9a373f32772d3f666686be4dbaadbc651a7d3f5a4b05bfa323393cee5a32bff9a037bfa1d6443f8ab081be687873bd98c17c3f23658b3c52491d3d651932bff9a0373f32772d3fa3017cbee76decbd3f917b3fad4b05bfbc74933da5bd31bfc74b37bf2fdd443fb37b72be95d31ebe363b7a3f3c678b3cd0d5d63da54e30bf87a7373f4d842d3f9fcd6abe2e2034be1747793f7adebdbeb30c313e4b5936bf96212e3fad693e3f8b6c67be04ab3abe2920793f163005bf97ff103d2b1815bc20d27fbfbde3443fc28667be4d483b3e327577bfd767bebe197357bee3c7283f70ce383fecc0593f637f59bebfd53a3e257877bfe03005bfc8983bbdd734ef3be9b77fbfe02d603f2c6559be5e13023e44db79bf234c05bf0e2db2bd0534313ffe6537bf5227603ff85363be5512f93d950e7abf23658b3cc898bbbd696f303f4703383fc7ba483ff8c264be5724763ec64e74bf23658b3c696f30be6de72b3fcc7f383fa7e8483fceaa4fbe86015b3e61c175bf4fe8bdbe423e28beae47313f0ad7333fecc0593fcff753be2ba58f3eb85871bf390cbebe863856beceaa2f3f6154323fb1e1593ff01648be910db43e1c066bbfb9718b3c24977fbe4b59263fd9ce373f302a493f6de73bbe4fcdc53e246167bfd767bebee9b78fbe19e2283f2575323fe3365a3feb7335be8baadf3e575b61bfd95e8b3c1b0da0bed26f1f3f6b9a373f4772493f1ff42cbe1abff03ebddf5cbfd767bebe45d8b0be0ebe203f5c8f323ffa7e5a3f0bb526be779e043f33a955bf55698b3cc976bebe62a1163f4bc8373f7ac7493f52b81ebed0b80c3fe96150bfd767bebe7b83cfbe1973173f0a68323fbada5a3fe3c718be4a5e1d3f3c3044bf6e6b8b3c52b8debee3360a3f3e79383ffe434a3f29ed0dbe2a1d243f0d8a3ebfb667bebece88f2bebada0a3f6ea3313fcc5d5b3fde0209be0210333f935230bfea758b3c2db2fdbe516bfa3e4bc8373fdaac4a3f5c8f02bec9733d3f863d25bfd767bebed5e70abf21b0f23e3789313fc4b1ae3e492ebfbe2a1d243f0d8a3ebfb667bebece88f2bebada0a3f6ea3313f6900af3e2fddc4be0210333f935230bfea758b3c2db2fdbe516bfa3e4bc8373f7aa58c3e6f12c3be03e9423f3f8e1ebf3c678b3cf1f40abff706df3e67d5373fd6568c3e76e0bcbe42ce4f3fae280dbfd767bebe6b9a17bf295ccf3e6154323f0456ae3ed0d5b6be2ca0543f05fd05bfa75a8b3c863816bf363cbd3eb072383f16fb8b3e7446b4be573d5c3f3b8cf1bed767bebeca5421bfbb27af3e2575323f7b14ae3ee9b7afbea6b4623fe353d8be0b638b3c65aa20bf88859a3e30bb373f8cb98b3eb150abbe88f3683ff17ebcbed767bebed5e72abf95d4893e8ab0313f0de0ad3e8273a6be20ed6b3fb859acbef2608b3c30bb27bfe926713e4bc8373f1f858b3e9ca2a3be747e723ffdf582bed767bebee56131bf2506413e0e2d323f9fabad3e7f6a9cbe70b3743f72dd64be0b638b3cb1bf2cbf8941203e1ea7383fcc5d8b3e477299be2ca0783f80d812bed767bebe9d8036bfce88d23d5396313f4d84ad3e615492be95637a3f17d9aebd23658b3cdc6830bf6a4d733d19e2383f96438b3e711b8dbe8b6c7b3fca51003bd767bebeacad38bf52499d39ae47313f3277ad3e0b4685be01da7a3f834f933d3c678b3cc52030bf5e4b48bd9e5e393f96438b3eb6f37dbe4da0783f45da163ed767bebe107a36bf1826d3bde09c313f4d84ad3e696f70be70b3743f82e5683e3c678b3c645d2cbfc05b20be50fc383fcc5d8b3e2eff61be747e723fe0f6843ed767bebe736831bf006f41bef31f323f9fabad3ef6285cbe6a146a3fb0cbb83e6e6b8b3c7d3f25bf5bb17fbee3c7383f3a928b3eccee49be88f3683f8979be3ed767bebe0c022bbf03098abec58f313f0de0ad3eb9fc47bee04b5d3f53b3ef3e0b638b3ccdcc1cbf67d5a7be0b24383fc3d38b3e107a36be6c405c3f4390f33ed767bebe378921bf7b83afbe8126323f7b14ae3e462535be29084e3fe0f2103f0b638b3c5c2011bf3a92cbbe39b4383f68228c3e2fdd24be42ce4f3f452c0e3fb667bebe67d517bfd734cfbe0e2d323f0456ae3e42cf26be47723d3f643c263fd767bebe96430bbf3c4ef1be17b7313fc4b1ae3ee2e915be4390373fd1cb2c3ff2608b3c539601bfaa82f1befed4383f5f988c3e053411beabd0283f1d383b3fd767bebeec51f8be394507bf7c61323f6900af3e713d0abe6c5a1d3f7731453fa75a8b3cb762dfbed0b309bf1ea7383fa01a2f3fd0b3b9beabd0283f1d383b3fd767bebeec51f8be394507bf7c61323fa54e403f64ccbdbe4390373fd1cb2c3ff2608b3c539601bfaa82f1befed4383f098a2f3fb762bfbed270163f726f4a3ff967bebe234adbbe6a4d13bf7c61323f3bdf3f3f508db7be0aa0043f4bae563fd95e8b3cad69bebef0a716bf4bc8373f8d972e3f3c4eb1be2385fa3e143e5b3fd767bebe35efb8be295c1fbfa5bd313fb7623f3f4df3aebe8eaddf3eff5a623f876d8b3c24979fbe24971fbfde93373f5b422e3f5530aabe78d3c53ecc60683fd767bebe7cf290be70ce28bf4547323f69003f3f6ff0a5be3ee859beaa80773f8f1bbebe76e01c3e58ca32bf38f8323fc9763e3f65aa60bed49c7cbe4a7d753f23658b3c0e4f2f3e5bd32cbfa2b4373f12a52d3fe3365abef305a5bec6bf6f3fd767bebe67d5673ee9482ebf6154323f52b83e3fcdcc4cbeb664adbe1f4c6e3ff2608b3cd7a3703e82e227bf87a7373f7fd92d3ff08549be2a1cd1be3b1a673ff967bebec6dc953e348027bf4182323f4df33e3fff213dbe556bd9be4834653f6e6b8b3c3480973e1c7c21bff9a0373f091b2e3fecc039be8272fbbe3c6a5c3fd767bebee258b73e44691fbf6519323ff2413f3f44fa2dbedbc401bfa5165a3fa75a8b3c0681b53e2c6519bf4bc8373fad692e3f68b32abe52621bbf361d493fd767bebe0a68e23e5c8f12bf0ebe303f20d23f3fe3c718be2eae15bfd1054d3f03788b3c45d8d03ebc9610bff9a0373f6dc52e3f645d1cbe384c28bf14243e3f5c548b3c4d15ec3e1dc905bfde93373fd7342f3f17d90ebef9123abf850b2d3fb667bebe5917073f9a77fcbeb30c313fa089403f931804beca6b39bf10922d3f3c678b3c9c33023fc5fef2be10e9373f098a2f3f948705be93ab4cbfd1ae163fa75a8b3ca4df0e3f2a3ad2be029a383ffb5c8d3ebbb81dbff9123abf850b2d3fb667bebe5917073f9a77fcbeb30c313f8e06b03e60e520bfca6b39bf10922d3f3c678b3c9c33023fc5fef2be10e9373fb22e8e3e33c421bf46ec53bf7fa00c3fb667bebe5eba193fe948cebeb7d1303fa01aaf3efaed1bbf96ce5fbf950ff13e876d8b3cdfe01b3f9e5ea9be7593383f448b8c3eb07218bfe84d65bf39f0da3ef967bebee6ae253f92cb9fbe4a0c323f0456ae3e2b8716bf603b6cbfbe2ebb3e0b638b3c787a253ff8c284bea2b4373f16fb8b3ed3bc13bf6b9970bf1631a43ed767bebe20632e3f0de06dbea5bd313f0de0ad3e17b711bf51f573bf9b1c8e3e0b638b3c5a642b3f94f646be508d373f71ac8b3e92cb0fbf3c6678bff5d5553ed767bebe9318343fe25817be13f2313f4d84ad3e96b20cbfa98679bf68973f3e3c678b3ca01a2f3fb81e05bebec1373fe86a8b3e8cb90bbffca97dbfb85b923dd767bebee3c7383f82e247bd0ebe303fdf4fad3e2b8706bfefe37cbf2234c23d3c678b3c5396313fde0289bd6b9a373f96438b3ede9307bf4f037ebfca51003b3c678b3c0a68323f17b7513a6b9a373f7a368b3ea16703bfa7037dbf2c9bb9bdd767bebe2bf6373f02bc853d933a313fdf4fad3ee483febe00e47cbfe336babd3c678b3c3789313f4b59863d14ae373f96438b3ec976febedb8679bfc8983bbe0b638b3cf2412f3fef38053ef9a0373fe86a8b3e6b2bf6be943077bf5b4369bed767bebed881333f8d972e3e933a313f6891ad3e0e2df2be51f573bfb91b8cbef2608b3cbada2a3f713d4a3ed9ce373f71ac8b3e5f07eebeaa626abf8eb1c3bed767bebe11c72a3fe02d903e2e90303f9621ae3e1d5ae4be22196abfd238c4be3c678b3cf853233ffa7e8a3e7593383f4d158c3ee63fe4bec6c25cbff793f9be03788b3cb5151b3fbb27af3e82e2373f7aa58c3e2cd4dabe0c0657bfca8906bfb667bebeb6841c3fe63fc43e933a313f4df3ae3e1973d7bed10250bf99d810bf6e6b8b3cdcd7113f4d84cd3e6b9a373fa8358d3e61c3d3befef241bf5d3523bfb667bebed6560c3f1748f03e933a313f3bdfaf3efb5ccdbeb35d41bfe5b423bf323b8b3cde93073fb9fce73e6b9a373ff2d28d3e8d28cdbe880e31bf5f0c35bf67808b3c075ff83e0000003f87a7373f20638e3eb072c8be06a124bf54aa40bfd767bebe2063ee3eed0d0e3f857c303fec2f5b3f6b9ab7befef241bf5d3523bfb667bebed6560c3f1748f03e933a313f1ac05b3f40a4bfbe880e31bf5f0c35bf67808b3c075ff83e0000003f87a7373fa3924a3f7f6abcbe132b1fbf8acc44bf67808b3c3f35de3e917e0b3f14ae373f3a234a3fe2e9b5be44dd0bbf43cb52bf6e6b8b3caf25c43ebde3143f30bb373fecc0493f4df3aebe2d5bfbbe2fc35bbfd767bebed044b83e45d8203f2e90303f6c785a3f0c93a9be94c0eebe3fe35ebf3c678b3c6ff0a53e9be61d3ff9a0373f4772493fde93a7be618dc3bef70469bfa75a8b3cb072883e4faf243f30bb373f302a493faed89fbe8447bbbe04af6abfd767bebe70ce883e68b32a3f6519323f910f5a3f3f359ebe5bb696bef51071bf23658b3cbc96503e88852a3f14ae373fc3f5483ff5db97bee5608ebea54c72bfb667bebe96434b3e1b0d303f58ca323f23db593f863896be6fbb50bef1f476bff2608b3c166a0d3e52b82e3f30bb373f70ce483fb29d8fbe2fdc59be266d76bf2923bebe74b5153e8126323f5dfe333f5eba593f174890be2ff834be394478bf7adebdbeb3ea333e7dae363f849e2d3fb5a6593f3b018dbe601fbdbdc8437bbf3c678b3c22fd763d9cc4303f5986383f39b4483faf9485bef9d938be863a78bf113505bfff21fd3c6519e23b3bdf7fbfc520603f287e8cbeb37e73bd05c17bbf5a4b05bf7958283d17b7313f2bf637bf371a603fee5a82be658fd03cc1027cbf3c678b3c52499dbca1f8313f2bf6373facad483ffdf675be2ff7093daeef7bbf5a4b05bf1cebe2bc8126323fa2b437bf371a603fea0474be84477b3f05c2ce3ce03005bf2b1815bc00000080000080bfce19513e6ff025bf83f97f3fcceec9bd5a4b05bf3411363f71ac8bbdfc1833bfd509683e863826bfa8727a3f698da1bdf03005bf2b1815bc52491d3a000080bf2eff613e5ddc26bf6685803fb378313d5a4b05bf79e9363fd734ef3caaf132bf97904f3e2fdd24bfc763763f219146be013105bf99bb16bcfaedeb3a000080bf0f9c733eec5128bf452f7b3ff0be5abe5a4b05bfd712323f2c6519be26e433bf5a647b3eb9fc27bffbe8743f302b9cbe5a4b05bfa4702d3ff6975dbecff733bfd42b853e95d429bf79af6e3f6ea49cbe013105bf04e70cbced0d3e3b000080bf780b843e68b32abf64746c3fcfa0c9be384b05bf705f273ffb3a90beefc933bfd6568c3e31082cbf8e3a663f11ffc8be123105bff4fdd4bbb37b723b000080bf280f8b3ee8d92cbfcbda5e3f916200bf494b05bf89d21e3ff46cb6be01de32bf6abc943e0e4f2fbf12fb5c3fe4dceebe3f3a05bfed0dbebbed0d3e3b000080bffca9913e9b552fbfc383523fe12409bf013105bf52491dbce71da73b72f97fbfd9ce973e651932bfa1684e3f445019bf8c4b05bfd34d123f508dd7be8f5334bff6289c3eaaf132bf1dad463f93c919bfc42705bf04e70cbc1ea7e83b72f97fbfda1b9c3ec66d34bf1d1d3f3f17122cbf494b05bf7424073f211ff4beb3ea33bf3789a13ef93136bfc72a393f14ea29bf233105bf5f29cbbbf4fdd43b000080bf0000a03e5ddc36bf8f52293f53cc41bf494b05bfd7a3f03eccee09bf38f832bf8b6ca73edaac3abf3351283f8eb23abf233105bf82e2c7bbf4fdd43b000080bfc1a8a43e1a513abfe200163fb0ca49bf333105bfed0dbebb6c09f93b000080bf39b4a83e44fa3dbf83690c3f57cd57bf5a4b05bf0309ca3e029a18bf530533bf3b01ad3e4e6240bffb5b023f481b57bf233105bf75029abb006f013c000080bf16fbab3ec1ca41bf1406e53ef69666bf5a4b05bf014da43e5c8f22bf26e433bf696fb03e984c45bfa609db3e238562bf123105bf24977fbb4b59063c000080bf8d97ae3e1dc945bfc0b4b83e506d70bf6a4b05bf0ad7833e9a9929bf061234bf6154b23e477249bffb75af3ed9ed6bbf013105bf17b751bb96430b3c000080bf696fb03e23db49bf4ab68a3ee62478bf5a4b05bf6f81443eb7622fbfb3ea33bfbc74b33e9fab4dbfed47823ecd3e73bf013105bf99bb16bbe02d103c000080bfaa82b13ed2004ebf66861d3e10e67ebf5a4b05bf423ee83d068135bf0e2d32bf61c3b33ee10b53bfbfd53a3e257877bfe03005bfc8983bbdd734ef3be9b77fbf4ed1b13ee92651bf5e13023e44db79bf234c05bf0e2db2bd0534313ffe6537bffca9b13e061254bf60924a3c850b81bf5a4b05bffe65773cb07238bf8e7531bfb37bb23e2c6559bf2ff7093daeef7bbf5a4b05bf1cebe2bc8126323fa2b437bf45d8b03ed04458bfb37e73bd05c17bbf5a4b05bf7958283d17b7313f2bf637bff241af3e645d5cbfd926d5bdfe6280bf5a4b05bf711b8dbd107a36bf93a932bffb3ab03ee4835ebff9d938be863a78bf113505bfff21fd3c6519e23b3bdf7fbffaedab3e17b761bf8f704abeae477dbf494b05bf29ed0dbee10b33bfd88133bf4d84ad3ece8862bf91d6a0bea9c075bf494b05bf643b5fbe3f352ebffc1833bfc3f5a83efe6567bf52f094bebc5771bff03005bf27a0093bbc74133c000080bf67d5a73ebd5266bf01f6c1be465b69bf013105bf4260653b728a0e3c000080bfb8afa33eccee69bfd978d8bee7e36abf6a4b05bf591797be4f1e26bf668833bf8195a33e51da6bbfbc57edbe39455fbfe03005bfe02d903b27a0093c000080bfa4df9e3e6d566dbf8e7307bf8fa85cbf494b05bff706bfbe2d431cbf01de32bf91ed9c3e1b0d70bfb24c0bbf2c2d53bfbe3005bf9fabad3b2497ff3b000080bf637f993e857c70bfa32325bfc5ac47bf6a4b05bff016e8be9be60dbf21b032bfcff7933ee17a74bf3ca21ebfe73a45bff03005bf5839b43b1ea7e83b000080bfd881933e865a73bfc6f834bf295a31bf013105bfac8bdb3b6519e23b000080bfb1508b3e9d8076bfe1963fbfc7bc2ebf5a4b05bf143f06bff54af9bee5d032bf95d4893eb53778bf419b48bfaed91abfbe3005bfd734ef3bed0dbe3b000080bf4182823ebe3079bf2b6c52bff79117bf6a4b05bf1ceb12bfbec1d7be61c333bfb7d1803e83c07abfdba559bf095102bff03005bf6f12033c52499d3b000080bf0e2d723e96437bbf6ac15fbf874e03bf6a4b05bf287e1cbf9a99b9be931834bf0e2d723ef1637cbf081e6bbfa2d3dbbe384b05bf9d1125bfe3369abe0ad733bfd34d623e12a57dbf367867bf8baacfbe123105bf27a0093cb37b723b000080bfe4835e3e5bd37cbf2ead76bf8fe4a2be494b05bf64cc2dbf787a65be38f832bf4d844d3e36ab7ebfe84a70bf7443a3be013105bf27a0093ca69b443b000080bf3b704e3e849e7dbfadfc76bf5bb26abef03005bf075f183c99bb163b000080bfed0d3e3e5f077ebf06bd7fbfa20839be5a4b05bf98dd33bfa77907bec5fe32bffd87343e12147fbfee5c7cbfbcafeabd013105bfe3a51b3ce02d903a000080bfd578293eb6f37dbf19a981bf6f8184bd5a4b05bfc6dc35bf16fb4bbdd3bc33bfe5f21f3e6dc57ebfa10f7ebf0feece3c2f3605bf143f463c17b751b972f97fbfce19113e560e7dbfa4e481bfdb31f53c5a4b05bfd95f36bf5af5b93cf38e33bf97900f3eed0d7ebf959d80bf7bfa183e5a4b05bfd8f034bf63eeda3d530533bfd95ff63dd1917cbf57787bbfee06113ef03005bfbc74133c6f1283ba000080bf6c78fa3d71ac7bbfdffc76bfd6ad6e3e013105bf075f183c27a009bb000080bff628dc3d3a237abf406c79bf446d933e5a4b05bf5bb12fbf832f4c3ee10b33bfcc7fc83d910f7abfe84a70bf5a47a53ef03005bfe02d103c5f294bbb000080bf36abbe3d423e78bf4c356fbfb2a1cb3e494b05bfd9ce27bfbbb88d3e98dd33bf2fdda43d8b6c77bfb47667bf96b1d13e123105bfb9fc073c6c0979bb000080bf5305a33d8bfd75bf2aac64bfd847f73e5a4b05bf20d21fbf6900af3e7dd033bf03098a3dbde374bf7fa359bfb150033fe03005bfb9fc073c0ad7a3bb000080bf01de823d3cbd72bf4c8954bf1bd5153f5a4b05bfb45915bf3333d33efc1833bfabcf553d933a71bfbf9948bfb1db1b3fbe3005bfb37bf23bca54c1bb000080bf16fb4b3df7066fbf95d841bf88122d3f8c4b05bff01608bfea95f23ed3bc33bf5227203dc4426dbff60c39bf59fd2d3f233105bfd044d83b17b7d1bb000080bf789c223d1ac06bbf23d830bfd2553e3f494b05bfe258f7becba1053f41f133bf211ff43c7ac769bf0fd627bfda8d3e3ff03005bfa69bc43b4260e5bb000080bfff21fd3cd04468bfe9441ebfc7f34d3f6a4b05bf88f4dbbeae47113f7dd033bfea95b23c341166bf6d5310bf7ff5503ff03005bf5839b43b6f1203bc000080bfea95b23c819563bfd3da04bf11375f3f5a4b05bfc729babefb5c1d3f333333bf6519623cce1961bfe25aedbe4e43603f123105bf04e78c3bb9fc07bc000080bfb37b723cc4b15ebf2b89d4bedac56c3f384b05bf0f9c93be27c2263f2aa933bf6f12033c4d155cbf05f9c1beee5a6a3f013105bf89d25e3b04e70cbc000080bf7cf2303cbe9f5abf3d0e9bbe26aa773f5a4b05bf2bf657be1b9e2e3f4f4033bfbc74933b27c256bf07ea94bec957723ff03005bfc3642a3b04e70cbc000080bfb9fc073c9d8056bf04ab3abe2920793f163005bf97ff103d2b1815bc20d27fbfd734ef3b378951bfa60e42bef7ad7e3f5a4b05bf6ff005bedc46333f2aa933bf89d25e3b378951bfe76decbd3f917b3fad4b05bfbc74933da5bd31bfc74b37bf2497ff3b925c4ebf174890bdf94b813f5a4b05bf5bb13fbdf5db373fa5bd31bf52499d3bf6284cbf4dbaadbc651a7d3f5a4b05bfa323393cee5a32bff9a037bf2e90203cc7294abf2c103d3db856813f5a4b05bf75021a3d6b9a373ff31f32bfb9fc073cb00347bfdf6c933dd15b7c3f5a4b05bfd57869bdc58f31bf67d537bf3ee8593c341146bf6a85293e4d677f3f5a4b05bf4772f93df8c2343f5c8f32bf68916d3c6ade41bfdd45383eeca2783f7a3805bf302a29bded0dbebb05c57fbf9c33a23c401341bf6154823e753e743ff03005bffaedebba728a0ebc000080bfcdcccc3c7b143ebf19e48e3ef88c783f5a4b05bfbada4a3ec0ec2e3f26e433bfec51b83c560e3dbffb75af3e81ed6c3f013105bf5f294bbb04e70cbc000080bf6ff0053d516b3abfc0b0bc3e94a3703f5a4b05bff0a7863e6c09293f931834bfdaacfa3c9e5e39bfaa0cdb3e5d86633f123105bf24977fbb4b5906bc000080bf7a362b3d94f636bf46d0e83e6b9f663f494b05bff931a63e2eff213fcff733bf0ad7233d54e335bfdf6c073fb210553f123105bfe71da7bb4850fcbb000080bfd3de603dc5fe32bf726c093f2d965a3f384b05bf2fddc43e2c65193fefc933bff2414f3d93a932bff86f223fe8a3483f494b05bf151de93ec4420d3f01de32bfb98d863d4df32ebf3cf6233fa6b73f3f233105bf3b70cebb4260e5bb000080bf2b18953dfbcb2ebf8fde3c3f35b62f3f5a4b05bfb4c8063f075ff83e3cbd32bf32e6ae3d7a362bbf492c393fabed2a3ff03005bfd044d8bba69bc4bb000080bf5917b73da8c62bbf9755483fbda9183f123105bfd734efbb82e2c7bb000080bfcff7d33d7ac729bf598b533f2252133f6a4b05bf62a1163f6076cf3e8a1f33bfd122db3d423e28bf683d543f925b073f5f2705bfca5441bc89d2debb72f97fbf8c4aea3d598628bf1f825e3ff640eb3e013105bf9c3322bc27a089bb72f97fbfb7d1003ea77927bf7506663fea08e83e494b05bfcff7233f5dfea33e21b032bfb81e053ec21726bfcc46673ffbebc53e713a05bf006f01bcc3642abb000080bff2b0103e666626bfb7f1733f67d4a43e5a4b05bfd6c52d3fd509683ee5d032bf2db21d3e86c924bfdeaf6e3f4da29e3ef03005bf17b7d1bb5f294bbb000080bf09f9203e74b525bf8a06753f15c7613e123105bf99bb16bc99bb16bb000080bf2041313ecf6625bf08017d3f9be33c3e5a4b05bf9318343f014d043e1ceb32bf4694363eaa6024bff52f793fde54043e123105bfe3a51bbce02d90ba000080bf6ea3413e068125bf6e85803f7a6e21bd3c678b3c226c383ffca9f1bc7368313fb840123f9be67dbf94f97f3f56f2d13d3c678b3c87a7373fe10b933d7368313f4d150c3f32e67ebf01da7a3f834f933d3c678b3cc52030bf5e4b48bd9e5e393f363c0d3fbbb87dbfbfd2793ff04c783e23658b3c4f40333fe8d92c3e5396313fabcf053ff7067fbf70b3743f82e5683e3c678b3c645d2cbfc05b20be50fc383fbd52063fd2007ebfdcf3703f8c15b53e3c678b3c645d2c3fc9767e3eb840323f80b7003fc9767ebf6a146a3fb0cbb83e6e6b8b3c7d3f25bf5bb17fbee3c7383f6e34003f52497dbfe273673f4faee13e3c678b3ccf66253f9b559f3e986e323ff54af93edb8a7dbfe04b5d3f53b3ef3e0b638b3ccdcc1cbf67d5a7be0b24383fa245f63e31087cbfd1e75b3f4129063fb9718b3c560e1d3f12a5bd3ece88323f3c4ef13e2d437cbf29084e3fe0f2103f0b638b3c5c2011bf3a92cbbe39b4383fcdccec3e55307abf236a4e3f7b4a1a3f876d8b3c8a1f133fa301dc3ed34d323f27a0e93e31997abfd1cd3a3fc0ed313f3c678b3c143f063fc807fd3eaa82313ff775e03e2bf677bf4390373fd1cb2c3ff2608b3c539601bfaa82f1befed4383f13f2e13e1d3877bf6806253fe658463f75568b3c04e7ec3ee8d90c3fa1f8313f6210d83ebde374bf6c5a1d3f7731453fa75a8b3cb762dfbed0b309bf1ea7383f4703d83ed88173bf38670c3f14d0583fd2738b3c4c37c93edaac1a3f8e75313f3255d03e933a71bf0aa0043f4bae563fd95e8b3cad69bebef0a716bf4bc8373f857cd03eaed86fbf3e59d93e488c6a3f0b638b3cc9769e3ed49a263f1c7c313f5e4bc83ed6566cbf8eaddf3eff5a623f876d8b3c24979fbe24971fbfde93373f7a36cb3e7aa56cbfb610b43e8e066c3f876d8b3cb7627fbed95f26bf4bc8373f9d80c63e4c3769bf2ee4a13eb98e753f0b638b3c9f3c6c3e9be62d3fee5a323faeb6c23e2bf667bfbac0753ea854753ff2608b3c325530be88f42bbfb072383f5396c13e8a8e64bf3f8e663e38317c3f3c678b3c30bb273e789c323fce88323fdbf9be3eaf2564bff339073efd4d803f3c678b3c5c20c13dc217363fd34d323f88f4bb3ee02d60bf6ac0003eb0e47a3f0b638b3cee5ac2bd2e9030bf4bc8373f48bfbd3ee9b75fbfed4a4b3c9c8b813f3c678b3c0e4f2f3cb072383faa82313fd93db93e7ffb5abf17610a3dccef7c3f3c678b3cae47e1bcb84032bf6b9a373fac8bbb3e8cb95bbf687873bd98c17c3f23658b3c52491d3d651932bff9a0373fac1cba3e6b9a57bf1f8004be5796803f3c678b3c58a8b5bd9031373fe561313f508db73ec1a854bf95d31ebe363b7a3f3c678b3cd0d5d63da54e30bf87a7373f2c65b93e6a4d53bfa69a89be44307a3f3c678b3ca4703dbe2a3a323fc58f313f6b9ab73ee9484ebfd49c7cbe4a7d753f23658b3c0e4f2f3e5bd32cbfa2b4373f637fb93ea01a4fbfb664adbe1f4c6e3ff2608b3cd7a3703e82e227bf87a7373f516bba3e9fcd4abfd42ac2be5da4703f876d8b3cb07288be68b32a3f8126323f35efb83e151d49bf556bd9be4834653f6e6b8b3c3480973e1c7c21bff9a0373fbf0ebc3e99bb46bf75e7f9bee2b1633f3c678b3c20d2afbe13f2213f17b7313fc898bb3e26e443bfdbc401bfa5165a3fa75a8b3c0681b53e2c6519bf4bc8373fc976be3e3cbd42bf784316bf02d5533f3c678b3c0612d4be8638163f6519323f8048bf3e69003fbf2eae15bfd1054d3f03788b3c45d8d03ebc9610bff9a0373f6ea3c13ea4df3ebfd4602ebff4bf403f0b638b3c4694f6be6c09093f6ea3313fe63fc43e713d3abf384c28bf14243e3f5c548b3c4d15ec3e1dc905bfde93373f787ac53e5f293bbfca6b39bf10922d3f3c678b3c9c33023fc5fef2be10e9373fe7fbc93e14ae37bf5b4143bf297b2b3f876d8b3cd57809bfb3eaf33e2a3a323f0309ca3ee2e935bf93ab4cbfd1ae163fa75a8b3ca4df0e3f2a3ad2be029a383fa089d03e9ca233bf2b6c52bffb93183fea758b3c1d5a14bf992ad83e2575323f6076cf3e21b032bfecc25fbfd34b043f3c678b3c7fd91dbfd122bb3e4182323feb73d53ee9b72fbf96ce5fbf950ff13e876d8b3cdfe01b3f9e5ea9be7593383f637fd93e295c2fbf8a1f6bbf3acedd3eb9718b3c666626bf917e9b3e6154323f88f4db3e560e2dbf603b6cbfbe2ebb3e0b638b3c787a253ff8c284bea2b4373fa5bde13e48502cbffbac76bfdee3a43e23658b3cbb272fbfc286673eaa82313f4beae43ec7292abf51f573bf9b1c8e3e0b638b3c5a642b3f94f646be508d373fa7e8e83e713d2abf38bd7fbf42073d3e3c678b3c7d3f35bf9565083e3789313ffb3af03e197327bfa98679bf68973f3e3c678b3ca01a2f3fb81e05bebec1373f4e62f03e598628bfefe37cbf2234c23d3c678b3c5396313fde0289bd6b9a373f992af83e022b27bf19a981bfd4818c3d23658b3c1d3837bf3a924b3dee5a323f7502fa3e39d625bf4f037ebfca51003b3c678b3c0a68323f17b7513a6b9a373f1b0d003ff93126bfa4e481bfb323d5bc23658b3c8b6c37bf5305a3bc4182323fce19013f66f724bf00e47cbfe336babd3c678b3c3789313f4b59863d14ae373f211f043f3d9b25bf6dfd80bf417ef6bd23658b3c4f1e36bfc58fb1bdce88323fef38053fe17a24bfdb8679bfc8983bbe0b638b3cf2412f3fef38053ef9a0373fb537083fcf6625bf5be97dbf11fd5abe23658b3cdc4633bff6971dbe986e323fba6b093f386724bf51f573bfb91b8cbef2608b3cbada2a3f713d4a3ed9ce373f48500c3f3d9b25bf59a677bf0b289cbe876d8b3c32e62ebf52b85ebe2575323f68910d3fddb524bf22196abfd238c4be3c678b3cf853233ffa7e8a3e7593383fe561113ff46c26bf83346fbfaa9dc9be3c678b3cfed428bf05c58fbe4182323f8ab0113f5d6d25bfbcad64bff243f5beea758b3ce92621bf3255b0bed34d323f74b5153f2b8726bfc6c25cbff793f9be03788b3cb5151b3fbb27af3e82e2373fbd52163f67d527bf018754bf06d714bf6e6b8b3c143f16bfbd52d6be7368313f4ca61a3fcc7f28bfd10250bf99d810bf6e6b8b3cdcd7113f4d84cd3e6b9a373f3a231a3f9e5e29bfb35d41bfe5b423bf323b8b3cde93073fb9fce73e6b9a373f64cc1d3f96432bbf0e833dbfadf630bfd95e8b3c827306bfdb8afdbee926313fc520203f1f852bbf880e31bf5f0c35bf67808b3c075ff83e0000003f87a7373f2041213f4d842dbf61c422bf709749bfa06f8b3cf0a7e6be696f10bf5c20313fd42b253f9b552fbf132b1fbf8acc44bf67808b3c3f35de3e917e0b3f14ae373fe17a243fa91330bf44dd0bbf43cb52bf6e6b8b3caf25c43ebde3143f30bb373fa779273faaf132bf40d904bfa0365ebff2608b3cadfabcbe00911ebfe561313f9a99293f7dd033bf94c0eebe3fe35ebf3c678b3c6ff0a53e9be61d3ff9a0373f55302a3fc21736bf508cd4be69c56bbf876d8b3c0f0b95be273128bfbc05323f3fc62c3f10e937bf618dc3bef70469bfa75a8b3cb072883e4faf243f30bb373f5f982c3fd57839bf85099bbe4caa76bf23658b3c9a9959bee02d30bf5396313f7b832f3f448b3cbf5bb696bef51071bf23658b3cbc96503e88852a3f14ae373f36ab2e3fe4143dbf6fbb50bef1f476bff2608b3c166a0d3e52b82e3f30bb373fdc68303fd3de40bf800b42beb8ad7dbf3c678b3ccc7f08be8a8e34bf2a3a323ffca9313f3c4e41bf601fbdbdc8437bbf3c678b3c22fd763d9cc4303f5986383fbc05323fe6ae45bfde1ec4bda67c80bf3c678b3c01de82bd79e936bfee5a323fc5fe323f265345bf1f679aba5c0181bf3c678b3c52491d3bde9337bf0a68323f41f1333fd57849bf658fd03cc1027cbf3c678b3c52499dbca1f8313f2bf6373f1826333f2cd44abfd444bf3df66280bf23658b3c984c953d99bb36bf6154323f6f81343f9fab4dbf5512f93d950e7abf23658b3cc898bbbd696f303f4703383f0f9c333f2d214fbf8a21593ed3117cbf23658b3c3f351e3e41f133bfa5bd313fc1a8343f6f1253bf5724763ec64e74bf23658b3c696f30be6de72b3fcc7f383f8195333f8a8e54bf16f9a53e48dc73bf6e6b8b3c32776d3e64cc2dbfee5a323f5839343f5e4b58bf910db43e1c066bbfb9718b3c24977fbe4b59263fd9ce373f38f8323f7ac759bf2cefd23e43e66abf75568b3ca2b4973e591727bf4182323f2f6e333f0d715cbf8baadf3e575b61bfd95e8b3c1b0da0bed26f1f3f6b9a373fbc05323f7fd95dbf371cfe3e73da5fbfd95e8b3c8638b63ef2411fbfce88323fb840323fa08960bf779e043f33a955bf55698b3cc976bebe62a1163f4bc8373f80b7303f4ed161bf207d133f38d952bfa75a8b3c46b6d33e8bfd15bf0a68323f80b7303fe17a64bf4a5e1d3f3c3044bf6e6b8b3c52b8debee3360a3f3e79383fe4832e3ff0a766bf26a7263f7bf743bf3c678b3c567dee3ee3a50bbf7c61323f89d22e3fd04468bf0210333f935230bfea758b3c2db2fdbe516bfa3e4bc8373fdfe02b3f280f6bbf7f4f383f865a33bf75568b3c7dd0033fb762ffbeb37b323fd1912c3f51da6bbf03e9423f3f8e1ebf3c678b3cf1f40abff706df3e67d5373f2c65293f925c6ebf9755483f373421bf75568b3cd7340f3fcba1e5be2575323f75022a3fd7346fbf2ca0543f05fd05bfa75a8b3c863816bf363cbd3eb072383f8fc2253f9c3372bfbb98563f08940dbf3c678b3c4c37193f43adc9beea95323f7424273f615472bf7cf0623fed82f1be55698b3c6519223ffaedabbe4182323fea04243fb81e75bfa6b4623fe353d8be0b638b3c65aa20bf88859a3e30bb373ff7e4213f068175bf064b6d3f21b1c5bed95e8b3c637f293f8d288dbe0a68323fd7a3203ff9a077bf20ed6b3fb859acbef2608b3c30bb27bfe926713e4bc8373fc9761e3fd9ce77bf048d753f711c98be876d8b3ce9b72f3fd0d556be2a3a323f560e1d3f07ce79bf70b3743f72dd64be0b638b3cb1bf2cbf8941203e1ea7383f95d4193f713d7abf08017d3f44e038be23658b3c0681353fdc4603beaa82313f423e183f31087cbf95637a3f17d9aebd23658b3cdc6830bf6a4d733d19e2383fea04143f645d7cbfa4e481bfb323d5bc23658b3c8b6c37bf5305a3bc4182323f857c603f97ff00bf19a981bfd4818c3d23658b3c1d3837bf3a924b3dee5a323f857c603f7d3f05bfa4e481bfdb31f53c5a4b05bfd95f36bf5af5b93cf38e33bf70ce483fd88103bf19a981bf6f8184bd5a4b05bfc6dc35bf16fb4bbdd3bc33bf70ce483fe483febe6dfd80bf417ef6bd23658b3c4f1e36bfc58fb1bdce88323f1283603f637ff9be06bd7fbfa20839be5a4b05bf98dd33bfa77907bec5fe32bf8cdb483f0612f4be5be97dbf11fd5abe23658b3cdc4633bff6971dbe986e323f2e90603fce19f1be2ead76bf8fe4a2be494b05bf64cc2dbf787a65be38f832bf35ef483f14aee7be59a677bf0b289cbe876d8b3c32e62ebf52b85ebe2575323f499d603fa7e8e8be83346fbfaa9dc9be3c678b3cfed428bf05c58fbe4182323ff2b0603fd3dee0be081e6bbfa2d3dbbe384b05bf9d1125bfe3369abe0ad733bff90f493f12a5ddbebcad64bff243f5beea758b3ce92621bf3255b0bed34d323f29cb603fbe30d9be6ac15fbf874e03bf6a4b05bf287e1cbf9a99b9be931834bf302a493f4f1ed6be018754bf06d714bf6e6b8b3c143f16bfbd52d6be7368313f09f9603f72f9cfbe2b6c52bff79117bf6a4b05bf1ceb12bfbec1d7be61c333bff54a493f4df3cebee1963fbfc7bc2ebf5a4b05bf143f06bff54af9bee5d032bf637f493f42cfc6be0e833dbfadf630bfd95e8b3c827306bfdb8afdbee926313f0534613fa60ac6bea32325bfc5ac47bf6a4b05bff016e8be9be60dbf21b032bf643b2f3f40a41fbf0e833dbfadf630bfd95e8b3c827306bfdb8afdbee926313fcb10473fea9522bfe1963fbfc7bc2ebf5a4b05bf143f06bff54af9bee5d032bf5bb12f3f418222bf61c422bf709749bfa06f8b3cf0a7e6be696f10bf5c20313f4694463f0e4f1fbf8e7307bf8fa85cbf494b05bff706bfbe2d431cbf01de32bf1b9e2e3f31991abf40d904bfa0365ebff2608b3cadfabcbe00911ebfe561313ffdf6453fe3361abfd978d8bee7e36abf6a4b05bf591797be4f1e26bf668833bfb22e2e3f6ff015bf508cd4be69c56bbf876d8b3c0f0b95be273128bfbc05323f228e453fe6ae15bf91d6a0bea9c075bf494b05bf643b5fbe3f352ebffc1833bf0de02d3f5c2011bf85099bbe4caa76bf23658b3c9a9959bee02d30bf5396313fef38453f80b710bf8f704abeae477dbf494b05bf29ed0dbee10b33bfd88133bf849e2d3f16fb0bbf800b42beb8ad7dbf3c678b3ccc7f08be8a8e34bf2a3a323ff4fd443f71ac0bbfd926d5bdfe6280bf5a4b05bf711b8dbd107a36bf93a932bf4d842d3fd9ce07bfde1ec4bda67c80bf3c678b3c01de82bd79e936bfee5a323fbde3443f348007bf60924a3c850b81bf5a4b05bffe65773cb07238bf8e7531bf32772d3fea9502bf1f679aba5c0181bf3c678b3c52491d3bde9337bf0a68323fa1d6443f4f4003bfd444bf3df66280bf23658b3c984c953d99bb36bf6154323f2fdd443fd200febe66861d3e10e67ebf5a4b05bf423ee83d068135bf0e2d32bf4d842d3f226cf8be8a21593ed3117cbf23658b3c3f351e3e41f133bfa5bd313f66f7443f6a4df3be4ab68a3ee62478bf5a4b05bf6f81443eb7622fbfb3ea33bf2db22d3fd6c5edbe16f9a53e48dc73bf6e6b8b3c32776d3e64cc2dbfee5a323f6132453f151de9bec0b4b83e506d70bf6a4b05bf0ad7833e9a9929bf061234bf9be62d3fcba1e5be2cefd23e43e66abf75568b3ca2b4973e591727bf4182323f5d6d453f4013e1be1406e53ef69666bf5a4b05bf014da43e5c8f22bf26e433bf24282e3f2db2ddbe371cfe3e73da5fbfd95e8b3c8638b63ef2411fbfce88323f02bc453f1058d9be83690c3f57cd57bf5a4b05bf0309ca3e029a18bf530533bf00912e3fc66dd4be207d133f38d952bfa75a8b3c46b6d33e8bfd15bf0a68323fc217463fbc05d2be8f52293f53cc41bf494b05bfd7a3f03eccee09bf38f832bf492e2f3fe7fbc9be26a7263f7bf743bf3c678b3c567dee3ee3a50bbf7c61323f9d80463f280fcbbe7f4f383f865a33bf75568b3c7dd0033fb762ffbeb37b323f94f6463fa69bc4be1d1d3f3f17122cbf494b05bf7424073f211ff4beb3ea33bf5bb12f3fa52cc3be9755483f373421bf75568b3cd7340f3fcba1e5be2575323ffa7e6a3f448b7cbf7f4f383f865a33bf75568b3c7dd0033fb762ffbeb37b323f83c06a3fa01a7fbf1d1d3f3f17122cbf494b05bf7424073f211ff4beb3ea33bf1826533fc9767ebfa1684e3f445019bf8c4b05bfd34d123f508dd7be8f5334bfe5d0523f5f297bbfbb98563f08940dbf3c678b3c4c37193f43adc9beea95323f55306a3f151d79bfcbda5e3f916200bf494b05bf89d21e3ff46cb6be01de32bfb37b523fb4c876bf7cf0623fed82f1be55698b3c6519223ffaedabbe4182323f3ee8693feb7375bf64746c3fcfa0c9be384b05bf705f273ffb3a90beefc933bf0e2d523f13f271bf064b6d3f21b1c5bed95e8b3c637f293f8d288dbe0a68323fd0b3693fe09c71bffbe8743f302b9cbe5a4b05bfa4702d3ff6975dbecff733bf2eff513f44fa6dbf048d753f711c98be876d8b3ce9b72f3fd0d556be2a3a323ff085693f849e6dbf452f7b3ff0be5abe5a4b05bfd712323f2c6519be26e433bf6ade513f23db69bf08017d3f44e038be23658b3c0681353fdc4603beaa82313f9e5e693f075f68bf83f97f3fcceec9bd5a4b05bf3411363f71ac8bbdfc1833bf33c4513f4faf64bf6e85803f7a6e21bd3c678b3c226c383ffca9f1bc7368313f6744693f4a0c62bf6685803fb378313d5a4b05bf79e9363fd734ef3caaf132bfa5bd513f764f5ebf94f97f3f56f2d13d3c678b3c87a7373fe10b933d7368313ff54a693f71ac5bbf08017d3f9be33c3e5a4b05bf9318343f014d043e1ceb32bf4ed1513fb9fc57bfbfd2793ff04c783e23658b3c4f40333fe8d92c3e5396313fba6b693f426055bfb7f1733f67d4a43e5a4b05bfd6c52d3fd509683ee5d032bfbc05523f4ed151bfdcf3703f8c15b53e3c678b3c645d2c3fc9767e3eb840323f27a0693f4e6250bf7506663fea08e83e494b05bfcff7233f5dfea33e21b032bf6154523f6de74bbfe273673f4faee13e3c678b3ccf66253f9b559f3e986e323f95d4693f9a774cbfd1e75b3f4129063fb9718b3c560e1d3f12a5bd3ece88323f910f6a3f39b448bf598b533f2252133f6a4b05bf62a1163f6076cf3e8a1f33bfaeb6523ff46c46bf236a4e3f7b4a1a3f876d8b3c8a1f133fa301dc3ed34d323f355e6a3fd42b45bf8fde3c3f35b62f3f5a4b05bfb4c8063f075ff83e3cbd32bfa52c533f4ed141bfd1cd3a3fc0ed313f3c678b3c143f063fc807fd3eaa82313f68b36a3f8ab041bff86f223fe8a3483f494b05bf151de93ec4420d3f01de32bf9eef373f6c787abfd1cd3a3fc0ed313f3c678b3c143f063fc807fd3eaa82313faed84f3f96217ebf8fde3c3f35b62f3f5a4b05bfb4c8063f075ff83e3cbd32bf3e79383fc9767ebf6806253fe658463f75568b3c04e7ec3ee8d90c3fa1f8313f44694f3f48e17abf38670c3f14d0583fd2738b3c4c37c93edaac1a3f8e75313fc0ec4e3f666676bf726c093f2d965a3f384b05bf2fddc43e2c65193fefc933bf1973373ffdf675bf46d0e83e6b9f663f494b05bff931a63e2eff213fcff733bf7424373f0e2d72bf3e59d93e488c6a3f0b638b3cc9769e3ed49a263f1c7c313fc9764e3ff2b070bfc0b0bc3e94a3703f5a4b05bff0a7863e6c09293f931834bfebe2363fcd3b6ebf2ee4a13eb98e753f0b638b3c9f3c6c3e9be62d3fee5a323fb22e4e3fa8c66bbf19e48e3ef88c783f5a4b05bfbada4a3ec0ec2e3f26e433bf7dae363fc7296abf3f8e663e38317c3f3c678b3c30bb273e789c323fce88323fd2004e3ff9a067bf6a85293e4d677f3f5a4b05bf4772f93df8c2343f5c8f32bf2b87363f810465bff339073efd4d803f3c678b3c5c20c13dc217363fd34d323f9be64d3f2f6e63bf2c103d3db856813f5a4b05bf75021a3d6b9a373ff31f32bf8273363fb29d5fbfed4a4b3c9c8b813f3c678b3c0e4f2f3cb072383faa82313f7fd94d3f5f075ebf174890bdf94b813f5a4b05bf5bb13fbdf5db373fa5bd31bf107a363fc3645abf1f8004be5796803f3c678b3c58a8b5bd9031373fe561313f29ed4d3f14ae57bfa60e42bef7ad7e3f5a4b05bf6ff005bedc46333f2aa933bf4694363f9d1155bfa69a89be44307a3f3c678b3ca4703dbe2a3a323fc58f313f24284e3f1c7c51bf3d0e9bbe26aa773f5a4b05bf2bf657be1b9e2e3f4f4033bf42cf363f1b0d50bfd42ac2be5da4703f876d8b3cb07288be68b32a3f8126323fad694e3f5f984cbf2b89d4bedac56c3f384b05bf0f9c93be27c2263f2aa933bfe71d373fb5154bbf75e7f9bee2b1633f3c678b3c20d2afbe13f2213f17b7313ffbcb4e3f67d547bfd3da04bf11375f3f5a4b05bfc729babefb5c1d3f333333bf3480373fb98d46bf784316bf02d5533f3c678b3c0612d4be8638163f6519323ff2414f3f4a7b43bfe9441ebfc7f34d3f6a4b05bf88f4dbbeae47113f7dd033bfb9fc373f9c3342bfd4602ebff4bf403f0b638b3c4694f6be6c09093f6ea3313f92cb4f3fd26f3fbf23d830bfd2553e3f494b05bfe258f7becba1053f41f133bfb072383f12143fbf95d841bf88122d3f8c4b05bff01608bfea95f23ed3bc33bfd578493f696f20bfd4602ebff4bf403f0b638b3c4694f6be6c09093f6ea3313f2041613fea9522bf23d830bfd2553e3f494b05bfe258f7becba1053f41f133bf0c93493fea9522bf5b4143bf297b2b3f876d8b3cd57809bfb3eaf33e2a3a323f5c20613f522720bf4c8954bf1bd5153f5a4b05bfb45915bf3333d33efc1833bff54a493fd6561cbf2b6c52bffb93183fea758b3c1d5a14bf992ad83e2575323f09f9603f76e01cbfecc25fbfd34b043f3c678b3c7fd91dbfd122bb3e4182323f45d8603f835119bf2aac64bfd847f73e5a4b05bf20d21fbf6900af3e7dd033bf151d493fd9ce17bf8a1f6bbf3acedd3eb9718b3c666626bf917e9b3e6154323f0ebe603faf9415bf4c356fbfb2a1cb3e494b05bfd9ce27bfbbb88d3e98dd33bfde02493f5dfe13bffbac76bfdee3a43e23658b3cbb272fbfc286673eaa82313f499d603fbc9610bf406c79bf446d933e5a4b05bf5bb12fbf832f4c3ee10b33bfa7e8483f840d0fbf38bd7fbf42073d3e3c678b3c7d3f35bf9565083e3789313fa089603fde710abf959d80bf7bfa183e5a4b05bfd8f034bf63eeda3d530533bffed4483f8cdb08bf83a384bd174878bf871605bfcff7133f41f113bf4a7b13bffb5c0d3f849e4dbd2ff834be394478bf7adebdbeb3ea333e7dae363f849e2d3f3e79083fbf0e9cbdf9d938be863a78bf113505bfff21fd3c6519e23b3bdf7fbf3e79083f3b704ebd83a384bd174878bf7adebdbe61c3133f0ad713bfefc9133ffb5c0d3fe3a59bbd55df59bef8c25c3fd767bebedcd761bfdc68a0becff7b33e68917d3f8fc2b5be2fdc59bef08b5bbfd767bebe57ec5fbf5b429e3e2d21bf3e68917d3f637f59bd2fdc59be266d76bf2923bebe74b5153e8126323f5dfe333ff6977d3f96210ebd3ee859beaa80773f8f1bbebe76e01c3e58ca32bf38f8323ff6977d3f492ebfbe2fdc59be083d53bf1ea7e0be4b5926bf93a902bf6e34103f68b37a3ffca971bd3cdd59becf835bbf7afde1be8cdb08bfec51383f5c8fe23e88857a3f3ee859bd2fdc59be251f4bbf382cc5be4faf74bf17b7d1ba107a963eadfa7c3f26e483bd2fdc59be7fdd31bf382cc5beddb574bf17b7513af46c963eadfa7c3f5452a7bd2fdc59be74403abfb43ae1be863826bf1d5a04bf6dc50e3f31997a3f76719bbd72dc59be59a742bfc85ce1bef46c26bffd87043f925c0e3f31997a3f05c58fbd2fdc59be5e8257bfe485e4be189524bf136143bd46b6433fa8577a3f8a8e64bd2fdc59be462640bfdae5e3be477229bf363c3d3ee7fb393fa8577a3f4f4093bd72dc59be59193dbf9813e4be621028bf79e926be448b3c3f1a517a3f07f096bd2fdc59bee92a21bfabb0e1be9ca223bf1058f9be7958183fa3927a3fc976bebd72dc59bed8d829bf0264e0be07ce29bfdf4f0d3f575b013fdaac7a3f7c61b2bd2fdc59be959b18bf382cc5be6f8174bf17b751b9a2b4973eadfa7c3f83c0cabd2fdc59be57b3febe382cc5bee17a74bf17b7513910e9973eadfa7c3fb22eeebd2fdc59be0a30ccbe382cc5bec66d74bf000000007e1d983eadfa7c3fa7e808be2fdc59beef1dddbeef57e1bedd2426bf5dfe03bfd7340f3fa3927a3f01de02be2fdc59bea5dcedbe1b46e1be9d8026bf66f7043f0de00d3f4ca67a3fb5a6f9bd2fdc59be2dea27bf6727e3be5eba29bf226c983eaed82f3f516b7a3f7d3fb5bd2fdc59be10e824bf5359e4be591727bf8ab061bd8e75413f1a517a3f35efb8bd2fdc59be30da07bf6667e1be787a25bf4ed101bfa1f8113fa3927a3f6519e2bd2fdc59be702810bf9c53e1be4b5926bf6f81043fe4830e3f31997a3f62a1d6bd72dc59beaa9d0dbfb8e5e3bea8572abf03784b3eb537383fa8577a3fac1cdabd2fdc59be71020bbf7043e4be470328bf5e4b08be96213e3f1a517a3f64ccddbd2fdc59be1abfe8be82e6e3be19e228bfa3923a3e4ca63a3fa8577a3fdb8afdbd72dc59bea77ae2be04ffe3beb07228bfb1e129be31083c3f1a517a3f80b700be2fdc59beeab0aabe1e6ce1bebd5226bf061204bfc0ec0e3fbe9f7a3f86c914be2fdc59beb743bbbe4757e1be666626bfc1a8043f764f0e3f31997a3f17d90ebe2fdc59be36ac99be382cc5bec66d74bf00000080d044983eadfa7c3fbe9f1abe2fdc59bed2514ebe382cc5be189574bf52499d3a7424973eadfa7c3fd6562cbe2fdc59be5794d2bd382cc5be547474bf000000807e1d983eadfa7c3fed0d3ebe2fdc59be9f1e0bbe2e55e1be4b5926bf189504bfad690e3fa3927a3f7e1d38be2fdc59be800f2ebe077ae0beecc029bf560e0d3f17b7013fdaac7a3fa1f831be72dc59bec232b6be18ebe3be598628bf0f9c333e5a643b3fa8577a3f60e510be2fdc59beb9abafbe64e9e3bee3c728bf204131beb1503b3fa8577a3f73d712be2fdc59beb95570be3068e1be6ff025bfc13903bfe02d103fa3927a3f304c26be2fdc59be57b188be1c61e1bebd5226bf1d5a043fc4b10e3fa3927a3f65aa20be72dc59be60b083be3fe6e3be477229bf24283e3e3ee8393fa8577a3fd34d22be2fdc59bef4867bbed718e4beec5128bfc0ec1ebeb1bf3c3f1a517a3faf2524be2fdc59bea08b26beea24e3be5a642bbf6519a23ea3012c3f516b7a3f6a4d33be2fdc59be304915be85e9e3be31992abf053451bede93373fa8577a3fd95f36be2fdc59becf101ebe7749e4beac8b2bbf022b873dc4423d3f1a517a3fa1d634be2fdc59bede1d19bd3068e1bea60a26bff38e03bfe9b70f3fa3927a3f5eba49be2fdc59be86e28ebdb455e1be2b8726bfd8f0043f0de00d3f31997a3f5dfe43be2fdc59beff5888bb382cc5beaa6074bf17b7d1b97593983eadfa7c3f3bdf4fbe72dc59be04e576bd2cd7e3bea32329bf499d403e03093a3fa8577a3f39d645be2fdc59beb91b44bd8907e4be423e28bfaeb622beed9e3c3f1a517a3f14ae47be2fdc59be63b87a3d876fe1be99bb26bf613205bf166a0d3fa3927a3fac8b5bbe2fdc59be126bf13c6f9be1be39d625bfaeb6023f0ebe103fa3927a3f190456be2fdc59be2b88c13d382cc5be386774bf17b7d1b8075f983eadfa7c3f539661be2fdc59bebccb453e382cc5be34a274bfa69bc4ba07f0963eadfa7c3f6a4d73be2fdc59be2b69953e382cc5be34a274bf6f12833ab4c8963eadfa7c3f418282be2fdc59be4a7e843e085ae1bec21726bf41f103bf0e4f0f3fa3927a3fdbf97ebe2fdc59bedee4673eba6be1be07f026bf9487053fe8d90c3fa3927a3f105879be2fdc59be404e583d3eb0e3bed93d29bfa60a46be0c93393fa8577a3f07ce59be72dc59be3c66203d51f5e3be5e4b28bf083d1b3e1ff43c3f1a517a3fd04458be2fdc59be1dae253e7768e0bea8572abf925c0ebff706ff3ef5b97a3f44fa6dbe2fdc59be9ef0023e8274e1be4b5926bf211f043fa4df0e3fa3927a3ff9a067be2fdc59be1edf1e3e56f5e2be3a922bbf3108acbe2c65293ffa7e7a3f1ff46cbe2fdc59bec5ab0c3e60e6e3be158c2abfa8354d3e82e2373fa8577a3f674469be2fdc59bedd3f163eb746e4bebada2abf304ca6bdbf7d3d3f1a517a3f431c6bbe72dc59be9f1d703eaa9be3bed57829bf29ed4d3efed4383fa8577a3f7ffb7abe72dc59be514c7e3ea8fce3bede9327bf88f41bbedb8a3d3f1a517a3f5bd37cbe2fdc59be31d3b63e0395e1be418222bf8fe4f2be68221c3fa3927a3f795888be2fdc59be6c94a53e1283e0be5eba29bf1ff40c3fdcd7013fdaac7a3f265385be2fdc59beffecc73e382cc5bec66d74bf17b751b9d044983eadfa7c3fe86a8bbe2fdc59be4c70fa3e382cc5be386774bf6f1203bad044983eadfa7c3f744694be2fdc59be107a163f382cc5befd8774bf17b7d139508d973eadfa7c3fff219dbe2fdc59beebfe0d3f8061e1be0bb526bf0b4605bffb5c0d3fa3927a3fac1c9abe2fdc59beedd3053fed9de1be8fc225bf5c8f023f09f9103fa3927a3fe25897be2fdc59be234ca93eea24e3be7a362bbf2e90a03e448b2c3f516b7a3f8bfd85be72dc59be76c0ad3eec4de4be42cf26bf3ee859bc8126423f1a517a3f79e986be2fdc59be7f84e93e6059e1bec21726bfb3ea03bf295c0f3fa3927a3f204191be72dc59becafbd83e166ee1bed95f26bf3d2c043ffbcb0e3fa3927a3f20638ebe72dc59bedee6dd3edae5e3bea32329bf5917373ebe9f3a3fa8577a3fd7348fbe72dc59be1c24e43e47ffe3be3e7928bf302a29bebf0e3c3f1a517a3fe02d90be2fdc59be90c00b3f77bae3be4c3729bf14d044be43ad393fa8577a3fd93d99be72dc59be6555083fbaf8e3becc7f28bf63ee1a3ecdcc3c3f1a517a3f3e7998be2fdc59bef354273f5245e1bec21726bf780b04bfd7340f3fa3927a3f5305a3be2fdc59bef5d81e3fba2de1bed49a26bf4260053ffb5c0d3f31997a3f371aa0be2fdc59bef9bb2f3f382cc5bee17a74bf348037ba10e9973eadfa7c3f8bfda5be2fdc59bea0fd483f382cc5bec66d74bf348037ba992a983eadfa7c3f32e6aebe2fdc59be2e8c5c3fe2ead8befdf615bf88854abf8f53343e234a7b3f8fc2b5be72dc59bebfbb513f1c0be2be0e2d22bfe0beee3eed0d1e3f158c7a3fbc05b2be72dc59be68ea593f713ae1bee2e925bfe483febeb8af133f31997a3f86c9b4be72dc59be4720563fc765e4be265325bfefc943bd6f12433f1a517a3f865ab3be2fdc59be6477213f21e9e3bec3f528bfb7d1403ec7293a3fa8577a3f09f9a0be2fdc59be397e243faa0fe4be226c28bf0b4625be48503c3f1a517a3ff7e4a1be2fdc59bec98f403f7f4ee1be7dd023bfd122fbbe1973173fa3927a3fdfe0abbe72dc59bebc5c383feeb0e1be41f123bfa392fa3ec286173f4ca67a3fa7e8a8be2fdc59be022e3c3ff984e4bedc4623bffaedeb3ad42b453fa8577a3f713daabe2e2034be1747793f7adebdbeb30c313e4b5936bf96212e3f6e34703fcac3c2be2ff834be394478bf7adebdbeb3ea333e7dae363f849e2d3f8941703fc7ba38bfa98417be5f604ebf7adebdbe3f570bbf57ec6fbe3f354e3f32e66e3f575b31bf3ee859beaa80773f8f1bbebe76e01c3e58ca32bf38f8323fdcd7713f1361c3be2fdc59be266d76bf2923bebe74b5153e8126323f5dfe333fdcd7713f226c38bf74ee06be7c4652bf7adebdbec52030be3b010dbf4013513f5b426e3f4a0c32bf77f5eabda1a351bf7adebdbeb5159b3e014d04bfadfa4c3f68916d3ff7e431bf83a384bd174878bf7adebdbe61c3133f0ad713bfefc9133fb1506b3f55c138bf68cacebd21074dbf7adebdbe6666163fde714abe19e2483f1ff46c3f5c2031bf83a384bd5648793f7adebdbeb8af133f780b143fb8af133fb1506b3faeb6c2be9e78cebd3083553f7adebdbea3011c3f33c4313ea60a463f1ff46c3f0e4fcfbeccb7debdefc8583f7adebdbe6ea3e13ecd3bee3e6f81443fc4426d3fb22ecebefdf700be952c5b3f7adebdbec6dcb53ccac3123ffca9513f5f076e3f3277cdbe08e80ebe959e593f7adebdbe6a4dd3be34a2f43e9d80463f8d976e3f29edcdbe045418be8ee5553f7adebdbec3d31bbfa1f8313ef931463fdbf96e3fd734cfbe9e78cebd3083553f7adebdbea3011c3f33c4313ea60a463fc4b12e3e4260e5bba0e1cdbd08024cbfd61e8ebe88f47b3ffaedebbdccee093ea4df3e3e4f1e96be68cacebd21074dbf7adebdbe6666163fde714abe19e2483ffbcb2e3e2b8796be9e0ccebd54aa543ffcc88dbee3a57b3fcb10073e38f8023edbf93e3e6c09f9bba167d3bdfa294ebf1fd68bbec7ba683f68b3cabe8104053e92cb3f3eebe296be77f5eabda1a351bf7adebdbeb5159b3e014d04bfadfa4c3f32e62e3e197397be0327dbbd3d824fbff45289be3a234a3f05c50fbfff217d3eb7d1403efe6597be718defbd8db751bf876c88bef775e03ebc9660bfb9fc473e143f263f295c8fbd77f5eabda1a351bf7adebdbeb5159b3e014d04bfadfa4c3fa245263fcf6655bd0327dbbd3d824fbff45289be3a234a3f05c50fbfff217d3ed49a263f728a8ebd3f7005beea5b52bfcd5987be4182e2bdecc079bfee5a423e58a8253f72f98fbd74ee06be7c4652bf7adebdbec52030be3b010dbf4013513faf94253ff4fd54bd045513be67d14fbf83df86bebada5abf13f2c1beeb73b53e7d3f253f986e92bda98417be5f604ebf7adebdbe3f570bbf57ec6fbe3f354e3fd42b253fabcf55bdc8d10cbea04e51bf15aa83be24b9fcbe5a641bbfd26f1f3fb459253f73d792bd7e3818be780a4dbfe3198cbed57879bfbd5216be7b142e3e4bc8773fe86aabbda98417be5f604ebf7adebdbe3f570bbf57ec6fbe3f354e3f8195733fe7fba9bd045513be67d14fbf83df86bebada5abf13f2c1beeb73b53ed044783f9eefa7bd403318be16a4553f240f8cbebe3079bf3f571b3eb29d2f3e6b9a773fb6f3bdbe045418be8ee5553f7adebdbec3d31bbfa1f8313ef931463f8195733fd200bebe725313beb16a583f92eb86be6de75bbfc807bd3e58a8b53eb537783fa4dfbebed7f80cbe6de5593f94bf83be65aa00bf96b21c3f2d431c3fe78c783fcf66b5be83c105be41f1573fd2ab81be348037be643b1f3eacad783f23db793e591797bec8d10cbea04e51bf15aa83be24b9fcbe5a641bbfd26f1f3f90a0783e426065bb193705be2d5b4fbfebad81be3a230abea69b44be8cdb783f23db793e52499dbbd7f80cbe6de5593f94bf83be65aa00bf96b21c3f2d431c3f90a0783ed9ce97bec8d10cbea04e51bf15aa83be24b9fcbe5a641bbfd26f1f3fe78c783f228ef5bde275fdbd935551bf051a84bea089703e265335bf516b2a3f6ff0253f986e92bde275fdbd935551bf051a84bea089703e265335bf516b2a3fb5157b3e24977fbb08e80ebe959e593f7adebdbe6a4dd3be34a2f43e9d80463f8195733fdbf9bebed7f80cbe6de5593f94bf83be65aa00bf96b21c3f2d431c3f8941003f4faf94bd08e80ebe959e593f7adebdbe6a4dd3be34a2f43e9d80463fa54e003f871659bd725313beb16a583f92eb86be6de75bbfc807bd3e58a8b53ec05b003f98dd93bdd75105bed0f05a3fae9a87be52b81ebe90a0783fd93d393eaed8ff3ee09c91bdfdf700be952c5b3f7adebdbec6dcb53ccac3123ffca9513f2497ff3ed04458bd749b00be90f5583fe28f82be0b46253eee5a023f226c583f11c77a3e197397be1172febdef375a3f80f184be31996a3e713d4a3fc58f113f9b55ff3e2aa993bd749b00be90f5583fe28f82be0b46253eee5a023f226c583fd26fff3ebde394bdd7a4fbbd21e5573f00fe81be1e168a3ef5db573ea089703fec2f7b3e07f096bea27aebbdd8d5583fe2e684be645d0c3f9a080b3f58ca223fc976fe3e4f4093bdab25ddbd7ec9563f958286be26e4433f05c54f3e7f6a1c3f6ea3413e82e2c7bb4980dabda514583ff4e189befb5c4d3f4d840d3f42cf663e499d403ec364aabba27aebbdd8d5583fe2e684be645d0c3f9a080b3f58ca223f8126423e2e90a0bb207ad2bdea21563f179f8abeff216d3f34a2743e7d3f953ea54e403ef4fdd4bb5de2e8bd94c1593fb1c388be57ec0f3ff1634c3fe4145d3ead69fe3e053491bde3defcbdf3c95a3f54e187be8a8e843ea1f8713f16fb4b3e643bff3e736891bda27aebbdd8d5583fe2e684be645d0c3f9a080b3f58ca223f7f6a7c3ee25897be3b56e9bdf31e573f36e883be3a920b3f3e79183e3333533f2575423e3480b7bb5de2e8bd94c1593fb1c388be57ec0f3ff1634c3fe4145d3eca54413ebc7493bb3b56e9bdf31e573f36e883be3a920b3f3e79183e3333533fc8077d3eb4c896beccb7debdefc8583f7adebdbe6ea3e13ecd3bee3e6f81443fd200fe3e871659bd4980dabda514583ff4e189befb5c4d3f4d840d3f42cf663eb6f3fd3e05c58fbdccb7debdefc8583f7adebdbe6ea3e13ecd3bee3e6f81443ffbcb2e3e0ad7a3bbe830dfbd317e4ebfd10886be1ac03b3f77be5fbe86c9243f13f2413e3d0a97be64cde8bd698e50bf1a4d86be8716193f9fab1dbf6a4d033f13f2413e6b9a97bec6a3d4bd76544dbfa04e89bea52c633fa8c60bbe006fe13e80b7403e62a196be64cde8bd698e50bf1a4d86be8716193f9fab1dbf6a4d033f6666263f97ff90bd88d9ebbd50fe4ebfb08e83be1ceb023f35ef78bec5fe523fa245263f986e92bd6807fcbd863b4fbf09fc81bed881733e560e6dbe1c7c713f7ffb7a3e2e90a0bb88d9ebbd50fe4ebfb08e83be1ceb023f35ef78bec5fe523fed9e7c3ee71da7bb718defbd8db751bf876c88bef775e03ebc9660bfb9fc473e933a413ed9ce97be88d9ebbd50fe4ebfb08e83be1ceb023f35ef78bec5fe523f5c8f423e348097bee830dfbd317e4ebfd10886be1ac03b3f77be5fbe86c9243fa4707d3e3480b7bbab25ddbd7ec9563f958286be26e4433f05c54f3e7f6a1c3f48bf7d3e560e8dbe83a384bd5648793f871605bf2aa9133f7446143fbc7413bfdcd7713f492ebfbe83a384bd174878bf7adebdbe61c3133f0ad713bfefc9133fb1506b3f4a7b03bd83a384bd174878bf871605bfcff7133f41f113bf4a7b13bfdcd7713f4a7b03bd83a384bd5648793f7adebdbeb8af133f780b143fb8af133fb1506b3f492ebfbe994554bf7577a53e6269e0beb22e3e3ff697bd3e6dc50e3f1a512a3ff46c16be2fdc59be6c94a53e1283e0be5eba29bf1ff40c3fdcd7013f1a512a3fad695ebd2fdc59be234ca93eea24e3be7a362bbf2e90a03e448b2c3f158c2a3fad695ebd2fdc59be31d3b63e0395e1be418222bf8fe4f2be68221c3f569f2b3fad695ebd8bde51bf444cb13eef1ae4befe65173fe5d0a2be2db23d3fb5152b3fc1a824be72dc59be76c0ad3eec4de4be42cf26bf3ee859bc8126423f7ffb2a3fad695ebda18653bfc13ba93ec329e3bebf0e2c3f728a8e3d24b93c3f158c2a3f54e325bed1b050bfbdc6b63e9886e1beddb5043fb5a629bf355e0a3fc8982b3f780b24be31b64cbfffecc73e382cc5be9d80563ff5b9fabeb4c8763e0ebe203fa4703dbe2fdc59be31d3b63e0395e1be418222bf8fe4f2be68221c3f6a4d233f865a93be2fdc59beffecc73e382cc5bec66d74bf17b751b9d044983e29cb203fbc7493bed1b050bfbdc6b63e9886e1beddb5043fb5a629bf355e0a3fdc46233fa3923abe8a3948bf3b56d93efe0de2beb81e353fa089703e4ca62a3ff163ec3e492e3fbe2fdc59beffecc73e382cc5bec66d74bf17b751b9d044983ec58ff13e8fe492be72dc59becafbd83e166ee1bed95f26bf3d2c043ffbcb0e3ff163ec3e58ca92be31b64cbfffecc73e382cc5be9d80563ff5b9fabeb4c8763ec58ff13e48503cbebede45bfb5c1e13ef050e4be4a7b133f865a93be0ad7433f68b3aa3e211ff4bc72dc59bedee6dd3edae5e3bea32329bf5917373ebe9f3a3f0378ab3ea52c03be72dc59be1c24e43e47ffe3be3e7928bf302a29bebf0e3c3f158caa3ea52c03be2fdc59be7f84e93e6059e1bec21726bfb3ea03bf295c0f3f27a0a93ea52c03beb5a443bff162e93eb378e1be0534f13ec8072dbfce19113f5ebaa93eb6f3fdbc8a3948bf3b56d93efe0de2beb81e353fa089703e4ca62a3fa8c6ab3e8c4aeabc72dc59becafbd83e166ee1bed95f26bf3d2c043ffbcb0e3f6822ac3ea52c03be5a463ebf4c70fa3e382cc5be73d7423f075f18bf9318843ed42b253f7ac7a9bd2fdc59be7f84e93e6059e1bec21726bfb3ea03bf295c0f3f4bc8273fb3ea33be2fdc59be4c70fa3e382cc5be386774bf6f1203bad044983e6132253f211f34beb5a443bff162e93eb378e1be0534f13ec8072dbfce19113fa2b4273f1361c3bd5f4338bfd2e5053f74d4e1be86c9343fb072283e1748303f1ceb423feb7325bf2fdc59be4c70fa3e382cc5be386774bf6f1203bad044983e4260453fd6563cbf2fdc59beedd3053fed9de1be8fc225bf5c8f023f09f9103f8fe4423fba493cbf5a463ebf4c70fa3e382cc5be73d7423f075f18bf9318843e4260453f6f8124bf2fdc59be90c00b3f77bae3be4c3729bf14d044be43ad393fe258373ffdf6f5bdb8e434bfed620a3f0e4ee4be1a510a3fe09cb1bee63f443fa779373f70ce08bd72dc59be6555083fbaf8e3becc7f28bf63ee1a3ecdcc3c3f30bb373f8fc2f5bd5f4338bfd2e5053f74d4e1be86c9343fb072283e1748303f2731383f6e3400bd2fdc59beedd3053fed9de1be8fc225bf5c8f023f09f9103fb537383ffdf6f5bdc03e32bff8c50d3fcedee1be5986d83e189534bf6ea3113f07f0363fe56121bd2fdc59beebfe0d3f8061e1be0bb526bf0b4605bffb5c0d3fb003373ffdf6f5bd2fdc59beebfe0d3f8061e1be0bb526bf0b4605bffb5c0d3fdd24163fc4422dbe2fdc59be107a163f382cc5befd8774bf17b7d139508d973ef38e133f32772dbe84f22abfcc79163f452dc5be86c9243f9d1135bf0681953e6688133f3480b7bdc03e32bff8c50d3fcedee1be5986d83e189534bf6ea3113f6ff0153f9fabadbd2fdc59be107a163f382cc5befd8774bf17b7d139508d973e31991a3f69002fbe2fdc59bef5d81e3fba2de1bed49a26bf4260053ffb5c0d3fd509183ffbcb2ebeb93323bf3acd1e3f8731e1be9d80363ff628dc3de561313f6210183faa60d4bd84f22abfcc79163f452dc5be86c9243f9d1135bf0681953e31991a3fa392babd2fdc59be6477213f21e9e3bec3f528bfb7d1403ec7293a3f26e4433ffdf6f5bd2fdc59be397e243faa0fe4be226c28bf0b4625be48503c3f2f6e433ffdf6f5bdd4991fbf7c61223fa262e4be9318043f1d5aa4beb1504b3f46b6433fa69b44bd2fdc59bef354273f5245e1bec21726bf780b04bfd7340f3fc5fe423ffdf6f5bd47e51abf94db263f9222e2bea392ba3ebde334bf234a1b3f38f8423f60e550bdb93323bf3acd1e3f8731e1be9d80363ff628dc3de561313f211f443f7f6a3cbd2fdc59bef5d81e3fba2de1bed49a26bf4260053ffb5c0d3fe63f443ffdf6f5bd2fdc59bef354273f5245e1bec21726bf780b04bfd7340f3fbada3a3fdfe03bbf2fdc59bef9bb2f3f382cc5bee17a74bf348037ba10e9973e5e4b383f16fb3bbfeeb510bf73bb2f3f452dc5be27a0093f12a54dbfc139833e2bf6373ff6282cbf47e51abf94db263f9222e2bea392ba3ebde334bf234a1b3f31993a3f713d2abf2fdc59bef9bb2f3f382cc5bee17a74bf348037ba10e9973e08ac3c3ffdf6f5bd72dc59bebc5c383feeb0e1be41f123bfa392fa3ec286173ffe433a3f228ef5bd5e8405bf2e56383f36eae1be6b2b263ff77560bcaeb6423f55303a3f948785bdeeb510bf73bb2f3f452dc5be27a0093f12a54dbfc139833e08ac3c3ffbcb6ebdda92fdbe8cf63c3f6478e4bea835cd3e5af5f9be107a463ffaed6b3d4260e5bc2fdc59bec98f403f7f4ee1be7dd023bfd122fbbe1973173faf25643dc0ec9ebdb535f2be6b9d403f1f2ee1be04568e3e3a924bbfccee093faf25643d499d00bd2fdc59be022e3c3ff984e4bedc4623bffaedeb3ad42b453fb29d6f3d52b89ebd5e8405bf2e56383f36eae1be6b2b263ff77560bcaeb6423f22fd763dcb10c7bc72dc59bebc5c383feeb0e1be41f123bfa392fa3ec286173f90a0783de17a94bd2fdc59bec98f403f7f4ee1be7dd023bfd122fbbe1973173f3a233a3f4d153cbf2fdc59bea0fd483f382cc5bec66d74bf348037ba992a983ede93373f832f3cbf2638d5be15fe483f9d2cc5bec729ba3ee2e965bf363c7d3efe65373fe10b33bfb535f2be6b9d403f1f2ee1be04568e3e3a924bbfccee093fb1e1393f575b31bf2638d5be15fe483f9d2cc5bec729ba3ee2e965bf363c7d3e39b4e83e9fcd2abd72dc59bebfbb513f1c0be2be0e2d22bfe0beee3eed0d1e3fd3bce33e091b9ebdc64eb0be1c97513fddb4e1be7e1d083fa7e808bec217563f0ad7e33e3ee859bd2fdc59bea0fd483f382cc5bec66d74bf348037ba992a983e39b4e83ee4839ebd72dc59be4720563fc765e4be265325bfefc943bd6f12433f2b87f63e2b1895bd72dc59be68ea593f713ae1bee2e925bfe483febeb8af133fb459f53e2b1895bde0d883be5682593f4a27e2bec5fe323ed57849bfa779173f984cf53e26e483bd4ab79dbe5132553f6858e4be7aa58c3ec74b07bf12a54d3f7daef63e1dc965bd72dc59bebfbb513f1c0be2be0e2d22bfe0beee3eed0d1e3fa2b4f73e2b1895bdc64eb0be1c97513fddb4e1be7e1d083fa7e808bec217563fd9cef73e20636ebde0d883be5682593f4a27e2bec5fe323ed57849bfa779173f4772393ffe433abf72dc59be68ea593f713ae1bee2e925bfe483febeb8af133f2c65393fba493cbf2fdc59be2e8c5c3fe2ead8befdf615bf88854abf8f53343eacad383f48503cbf9a7d62bf2b88c13d382cc5be6c786a3f2e90a0bb4d84cd3e39d6453d7dd093be2fdc59be63b87a3d876fe1be99bb26bf613205bf166a0d3f9a771c3d69002fbe2fdc59be2b88c13d382cc5be386774bf17b7d1b8075f983e39d6453d32e62ebe5c3d63bfb878783d8acbe1be44691f3f6a4d03bf3945173f9b551f3d211f94be267261bf037d023e5055e1be0e4f2f3f6666e63e3cbd123f4faf943dc52030be2fdc59be2b88c13d382cc5be386774bf17b7d1b8075f983e9e5ea93d211f94be2fdc59be9ef0023e8274e1be4b5926bf211f043fa4df0e3f4faf943d061294be9a7d62bf2b88c13d382cc5be6c786a3f2e90a0bb4d84cd3e9e5ea93db29d2fbe2fdc59be1dae253e7768e0bea8572abf925c0ebff706ff3ebc05223f0e4f2fbe8e0660bfe047253ef698e0be52491d3fa4df1ebf2c65f93ed712223f696f70bdcd5860bf1c071e3e2024e3beca32243f840dcfbeebe2263fb840223f8e0670bdf10d61bf58e30c3e80eee3be48bf2d3ff7e4e13db1e1393faaf1223f44fa6dbd2fdc59bec5ab0c3e60e6e3be158c2abfa8354d3e82e2373f5305233f6a4d33be18b460bf54ad153e7749e4be1058293f674429be96433b3fea95223fd7346fbd2fdc59be9ef0023e8274e1be4b5926bf211f043fa4df0e3f865a233f6a4d33be267261bf037d023e5055e1be0e4f2f3f6666e63e3cbd123fdc46233f8d286dbd2fdc59be1edf1e3e56f5e2be3a922bbf3108acbe2c65293ff31f223f333333be2fdc59bedd3f163eb746e4bebada2abf304ca6bdbf7d3d3f5c8f223f6a4d33be17615ebfbccb453e382cc5be7958683f4b5986be67d5a73e8885da3db37b32be2fdc59be1dae253e7768e0bea8572abf925c0ebff706ff3e2063ee3dea0494be2fdc59bebccb453e382cc5be34a274bfa69bc4ba07f0963ef5b9da3d211f94be8e0660bfe047253ef698e0be52491d3fa4df1ebf2c65f93efbcbee3de92631be49485cbf6b82683e520ae2bece19313fec51b83efb3a203fd95f063f454732be2fdc59bebccb453e382cc5be34a274bfa69bc4ba07f0963ec3f5083fbc7493be2fdc59bedee4673eba6be1be07f026bf9487053fe8d90c3fd95f063f865a93be17615ebfbccb453e382cc5be7958683f4b5986be67d5a73ec3f5083fb30c31be72dc59be514c7e3ea8fce3bede9327bf88f41bbedb8a3d3f075f983db07228be17495bbff819773eda59e4be575b213f840d0fbe4a7b433f50fc983daaf152bd72dc59be9f1d703eaa9be3bed57829bf29ed4d3efed4383fbf0e9c3db07228be2fdc59be4a7e843e085ae1bec21726bf41f103bf0e4f0f3f4faf943db07228be71ff59bf0f61843e7270e1be20630e3fb4c816bfc217163fbde3943dabcf55bd49485cbf6b82683e520ae2bece19313fec51b83efb3a203f52499d3d60e550bd2fdc59bedee4673eba6be1be07f026bf9487053fe8d90c3f52b89e3db07228be043a57bf2b69953e382cc5beaa60643fadfabcbe5d6d853ea01a0f3f341136be2fdc59be4a7e843e085ae1bec21726bf41f103bf0e4f0f3f17b7113f865a93be2fdc59be2b69953e382cc5be34a274bf6f12833ab4c8963e2d210f3fbc7493be71ff59bf0f61843e7270e1be20630e3fb4c816bfc217163ffca9113fea0434be994554bf7577a53e6269e0beb22e3e3ff697bd3e6dc50e3f0b24183f470338be2fdc59be2b69953e382cc5be34a274bf6f12833ab4c8963e31991a3fbc7493be2fdc59be6c94a53e1283e0be5eba29bf1ff40c3fdcd7013ff016183f865a93be043a57bf2b69953e382cc5beaa60643fadfabcbe5d6d853e31991a3f341136be2fdc59beff5888bb382cc5beaa6074bf17b7d1b97593983e6519623cc4b12ebe998463bf3b3517bd3c33e1be1e162a3f371a00bf96210e3f24977f3b211f94be2fdc59bede1d19bd3068e1bea60a26bff38e03bfe9b70f3f6c09793bfbcb2ebe88ba63bfff5888bb382cc5be1dc9753f643b5f3d287e8c3e6519623c211f94be2fdc59be404e583d3eb0e3bed93d29bfa60a46be0c93393f83c02a3fbc7493be487063bfcd95413da94de4be6132253fa8354dbd8a1f433f63ee2a3f32772dbe72dc59be3c66203d51f5e3be5e4b28bf083d1b3e1ff43c3f431c2b3fa16793be5ea163bfe066f13c58ace1be9487253f55c1e83e5bd31c3f569f2b3f10e937be2fdc59be126bf13c6f9be1be39d625bfaeb6023f0ebe103f569f2b3fbc7493be5c3d63bfb878783d8acbe1be44691f3f6a4d03bf3945173f355e2a3f583934be2fdc59be63b87a3d876fe1be99bb26bf613205bf166a0d3fc3642a3fbc7493be5ea163bfe066f13c58ace1be9487253f55c1e83e5bd31c3f0e4faf3c8d972ebe2fdc59beff5888bb382cc5beaa6074bf17b7d1b97593983eb6f3fd3c211f94be2fdc59be126bf13c6f9be1be39d625bfaeb6023f0ebe103f9fabad3c061294be88ba63bfff5888bb382cc5be1dc9753f643b5f3d287e8c3eb6f3fd3cc4b12ebef9d980be02f358bf923be4be3d2c543e2c65293f3e79383fc9e53f3f8c4a2abf2fdc59bef08b5bbfd767bebe57ec5fbf5b429e3e2d21bf3e08ac3c3f3e7928bf3cdd59becf835bbf7afde1be8cdb08bfec51383f5c8fe23eceaa3f3fcc7f28bf48dd96be259555bfb667bebe68b36a3e7e1d283f10e9373f3fc63c3f832f2cbf809a9abe76c154bfae0fe3be2db2bd3ead699e3e6e34603f3bdf3f3ff1632cbf3317a8be054e52bf001ddebe65aa103f226cf83deeeb503fee7c3f3f48bf2dbffbe5cbbe315f4abf3c68bebe9be69d3e40a41f3f82e2373fadfa3c3feeeb30bff0c0c8be3a1e4bbfe02cc5be24979f3ee3366a3f1361833edb8a3d3fd7a330bf13eee5bef04d43bfdae1dfbe39d6853eccee593fa7e8e83e3bdf3f3f6a4d33bf19c8f3be3c313fbf9467bebe9be6bd3ede93173f7424373fa8353d3f6f8134bf1e15efbebc9240bfddb2e3be02bca53efaed2b3fbe9f2a3f1748403fea0434bfae2ef7bec0083ebf444de4bef5b9da3e992ad83e08ac4c3f696f403fbde334bfc38001bf7d233abfd124e1be87a7273f52491d3c006f413f6e34403fe2e935bf556d0bbf580033bf3c68bebe1058d93e3b010d3f2bf6373f32773d3f14ae37bf25cc0cbfa5dc31bfcb2dc5be8195033f77be4f3fe9488e3e7b143e3f2bf637bfee7916bf8dd229bf1170e0beb30cb13e40a44f3fc58ff13e1283403fecc039bffd491cbfcba224bfd767bebe5839f43e58ca023fcb10373f64cc3d3f83c03abfaa6319bf543b27bf56d5e3bea470dd3ed95f063f71ac3b3f60e5403fa8573abfb2d41ebf752122bf07d1e2beacad183f2575023e48e14a3f7cf2403f7a363bbf2ac720bfbc4120bf5c8edfbe8e06403ff1f40abee6ae253f80b7403f569f3bbfe25b28bf725218bfd767bebe3867043f7c61f23e2b87363f728a4e3e9d80a6bdfd491cbfcba224bfd767bebe5839f43e58ca023fcb10373fbb274f3e083d9bbd2ac720bfbc4120bf5c8edfbe8e06403ff1f40abee6ae253fcf66553e09f9a0bdd71828bf959b18bf382cc5befc18233f4703383f567d8e3e29cb503ec217a6bde2772fbf263510bff94de1bebc74d33ef5db373f295c0f3fd0b3593e7c61b2bd8b3633bf7fa50bbf9467bebe24b90c3fa470dd3e94f6363f849e4d3eec51b8bde09f32bf734a0cbffc51e4be8a8e043f5bd3dc3e8d283d3f88855a3e5917b7bd753a34bfaf3e0abf12dde3be764f1e3f3bdf0f3e6ff0453fac1c5a3e7ffbbabd446e36bf145d07bfa569e0be0de03d3f72f94fbe9ca2233f19e2583eed0dbebdb62b3cbf57b3febe382cc5be12a53d3fbb271f3f8126823ebb274f3ef085c9bde3e240bf4f90f0bef967bebe87a7173f925cbe3e94f6363ff1634c3ef38ed3bd7a7241bf9f93eebec479e0be9790ef3e52b83e3fa167f33ebec1573e1895d4bd11e242bf25e6e9be01a3e3beb4c8063f3867043fb1bf2c3f19e2583eacadd8bd346844bfb4cae4be514ee4be36cd1b3f986e523eca32443f50fc583e645ddcbdab9446bf8d43ddbe8882e1be5986383fd95f76be6666263f87a7573eae47e1bd7d5c4bbf85e9cbbe3c68bebe5b421e3fae47a13e7958383f3a924b3efb5cedbde5254bbf0a30ccbe382cc5be77be4f3f23dbf93e8a8ea43ef2d24d3e8d28edbd0c404fbf8f35bbbe226fe1be865a033f304c263fb29d0f3f7424573e4772f9bd454752bfe352adbed767bebe39d6253fd5e78a3e143f363ff1f44a3e006f01be23dc50bf37fbb3be514ee4be19e2183f80489f3ec4423d3f2bf6573e2428febd4ef051bf88f5aebed0b9e3be3a922b3fa4dfbebc9be63d3f508d573e250601be570453bf8bc3a9bea774e0be448b3c3ffc18b3be5839143ff46c563e93a902be505456bfc47998bed767bebe151d293f7dd0733ea245363f158c4a3e39b408bede1c56bf36ac99be382cc5bed7a3603f5dfec33e7dd0933e04e74c3e5e4b08be250259bfbebc88be6f65e1be32770d3f083d1b3f0a68123fbd52563e3b700ebe40dc59bfd54083bed767bebea3012c3f97ff503e143f363f713d4a3ee02d10be3f355abf73f480bef050e4be371a203f11367c3e68913d3f7424573e29cb10be68ce5abf5cca79beecd9e3bed6c52d3fd71272bd5a643b3fd0d5563e73d712bebe835bbfeecd6fbec32be1beb98d363f5530cabe014d143fabcf553e744614beeb385ebfa5bc46be3c68bebe68912d3f76e01c3ed509383fccee493e76711bbe76a95dbfd2514ebe382cc5be9cc4603f4df30ebde17af43eba494c3e75021abe516a5fbfb2132ebeb878e0be9a081b3f2eff213f5917f73e6132553e098a1fbe7bc15fbfbc0427beed0ce3bed712223f22fdd63e8273263f4f1e563e65aa20bebf2960bf2e391ebe1f4ae4be39b4283ffe65373e0c023b3f62a1563e651922bee08660bf97e315be0802e4be64cc2d3f5bd3bcbd6c783a3f62a1563e668823be532162bf8fa6dabd3c68bebe857c303f7cf2b03d7e1d383f27a0493e431c2bbe7ff560bfac700bbe3d7ce1be1748303f1283e0be0ad7133f3d9b553ee6ae25be182362bf5794d2bd382cc5be7b146e3ff7e4213e95d4a93edfe04b3ec3d32bbe93fe62bf16a38ebdc171e1bee9b71f3f9d80063f9318143fcf66553e33c431be437563bf4ddc2abdd767bebe4a7b333f4ed1113dbd52363ff085493eb4c836be464263bf6b9b62bda04fe4be9eef273f5f070e3e29ed3d3ff46c563e8f5334be3d6363bf967840bd97e5e3beba492c3ffed438bef9a0373f8638563e341136be998463bf3b3517bd3c33e1be1e162a3f371a00bf96210e3f6132553ea2b437bec7bb63bf0341003bd767bebe0f9c333fd04458bbf46c363ff085493e36ab3ebe88ba63bfff5888bb382cc5be1dc9753f643b5f3d287e8c3ea8c64b3edb8a3dbe5ea163bfe066f13c58ace1be9487253f55c1e83e5bd31c3f984c553e4a7b43be54e262bfcb64b83dd767bebe6dc52e3f736891bd55303a3f27a0493e3b704ebe487063bfcd95413da94de4be6132253fa8354dbd8a1f433ff46c563ef0a746be5c3d63bfb878783d8acbe1be44691f3f6a4d03bf3945173f984c553e835149be9a7d62bf2b88c13d382cc5be6c786a3f2e90a0bb4d84cd3edfe04b3ef2414fbe267261bf037d023e5055e1be0e4f2f3f6666e63e3cbd123f3d9b553ecf6655bef10d61bf58e30c3e80eee3be48bf2d3ff7e4e13db1e1393f62a1563ee25857be18b460bf54ad153e7749e4be1058293f674429be96433b3f62a1563eacad58becd5860bf1c071e3e2024e3beca32243f840dcfbeebe2263fbd52563eac1c5abe944c5ebf2c65493e3c68bebe68912d3fd0b319beb537383f03094a3e539661be8e0660bfe047253ef698e0be52491d3fa4df1ebf2c65f93ecf66553e3f575bbe17615ebfbccb453e382cc5be7958683f4b5986be67d5a73eba494c3e09f960be49485cbf6b82683e520ae2bece19313fec51b83efb3a203f4f1e563e1d3867be17495bbff819773eda59e4be575b213f840d0fbe4a7b433f7424573e55306abe40dc59bf5341853ed767bebefaed2b3fe09c51be304c363fa8574a3e560e6dbe71ff59bf0f61843e7270e1be20630e3fb4c816bfc217163f8638563eb1bf6cbe043a57bf2b69953e382cc5beaa60643fadfabcbe5d6d853e04e74c3e58ca72bec25152bf9775af3e5167bebeef38253fa8578abe79e9363f280f4b3e36cd7bbe994554bf7577a53e6269e0beb22e3e3ff697bd3e6dc50e3f8638563eb53778bea18653bfc13ba93ec329e3bebf0e2c3f728a8e3d24b93c3f1973573eecc079be8bde51bf444cb13eef1ae4befe65173fe5d0a2be2db23d3f2bf6573e6de77bbed1b050bfbdc6b63e9886e1beddb5043fb5a629bf355e0a3f7424573e925c7ebe31b64cbfffecc73e382cc5be9d80563ff5b9fabeb4c8763ef2d24d3eb84082bea8fe45bff085e13e7367bebe3a921b3faa82b1beebe2363f832f4c3e0bb586be8a3948bf3b56d93efe0de2beb81e353fa089703e4ca62a3ff5db573e426085bebede45bfb5c1e13ef050e4be4a7b133f865a93be0ad7433f8716593e42cf86beb5a443bff162e93eb378e1be0534f13ec8072dbfce19113f6210583e0b2488be5a463ebf4c70fa3e382cc5be73d7423f075f18bf9318843ebb274f3e431c8bbe8e3b39bfc79f043f7367bebe772d113f33c4d1be07f0363fdf4f4d3e9fab8dbe5f4338bfd2e5053f74d4e1be86c9343fb072283e1748303f8716593e96218ebeb8e434bfed620a3f0e4ee4be1a510a3fe09cb1bee63f443f88855a3ee9b78fbec03e32bff8c50d3fcedee1be5986d83e189534bf6ea3113f9a99593e97ff90beda8f2cbfc6be143f1b68bebe8273063ff085e9be10e9373f04564e3e6a4d93be84f22abfcc79163f452dc5be86c9243f9d1135bf0681953e29cb503ecff793beb93323bf3acd1e3f8731e1be9d80363ff628dc3de561313f07ce593ef46c96be446b1dbfc289243ff967bebeebe2f63efca901bf22fd363f295c4f3ed9ce97bed4991fbf7c61223fa262e4be9318043f1d5aa4beb1504b3f63ee3a3f9e5e29bf446b1dbfc289243ff967bebeebe2f63efca901bf22fd363f14ae373f23db29bfb93323bf3acd1e3f8731e1be9d80363ff628dc3de561313f83c03a3fe3c728bf88650bbfef03343fb667bebe3e79d83e29ed0dbf3480373fc74b373fff212dbf5e8405bf2e56383f36eae1be6b2b263ff77560bcaeb6423fac1c3a3f091b2ebfda92fdbe8cf63c3f6478e4bea835cd3e5af5f9be107a463ffe433a3f295c2fbff435f3be0857403fd767bebe764fbe3e197317bf7424373fb003373fc05b30bfb535f2be6b9d403f1f2ee1be04568e3e3a924bbfccee093fe7fb393f174830bf88d9cbbe35614b3fb667bebe36cd9b3ec52020bf10e9373f27c2363f26e433bfc64eb0be1c97513fddb4e1be7e1d083fa7e808bec217563f7e8c393f143f36bf2c2da3be363d543ff967bebe234a7b3eab3e27bfe258373f4694363fc28637bf4ab79dbe5132553f6858e4be7aa58c3ec74b07bf12a54d3f7ac7393f470338bf55df59bef8c25c3fd767bebedcd761bfdc68a0becff7b33ef46c363fd6563cbf2fdc59be5e8257bfe485e4be189524bf136143bd46b6433f9031673f2cd43abf2fdc59be083d53bf1ea7e0be4b5926bf93a902bf6e34103ff5db673f569f3bbf809a9abe76c154bfae0fe3be2db2bd3ead699e3e6e34603f6b9a673f17d93ebff9d980be02f358bf923be4be3d2c543e2c65293f3e79383f94f6663fd1913cbf3cdd59becf835bbf7afde1be8cdb08bfec51383f5c8fe23e2b87663f2cd43abf2fdc59be083d53bf1ea7e0be4b5926bf93a902bf6e34103f448b1c3f7e8c39bd3317a8be054e52bf001ddebe65aa103f226cf83deeeb503f08ac1c3f02bc85bd809a9abe76c154bfae0fe3be2db2bd3ead699e3e6e34603f645d1c3f006f81bd2fdc59be251f4bbf382cc5be4faf74bf17b7d1ba107a963edbf91e3fec5138bdf0c0c8be3a1e4bbfe02cc5be24979f3ee3366a3f1361833edbf91e3f9a779cbd72dc59be59a742bfc85ce1bef46c26bffd87043f925c0e3f8941403f39b428bf2fdc59be251f4bbf382cc5be4faf74bf17b7d1ba107a963e2db23d3f029a28bfae2ef7bec0083ebf444de4bef5b9da3e992ad83e08ac4c3f1d5a143f2b8796bd72dc59be59193dbf9813e4be621028bf79e926be448b3c3f5474143fabcfd5bcc38001bf7d233abfd124e1be87a7273f52491d3c006f413f2fdd143fe4149dbd2fdc59be462640bfdae5e3be477229bf363c3d3ee7fb393f5dfe133fabcfd5bc1e15efbebc9240bfddb2e3be02bca53efaed2b3fbe9f2a3f0ad7133f29cb90bd13eee5bef04d43bfdae1dfbe39d6853eccee593fa7e8e83ed881133f03788bbd72dc59be59a742bfc85ce1bef46c26bffd87043f925c0e3f2aa9133fabcfd5bc2fdc59be74403abfb43ae1be863826bf1d5a04bf6dc50e3fbde3143fabcfd5bcc38001bf7d233abfd124e1be87a7273f52491d3c006f413f728a3e3fb003e7bd2fdc59be7fdd31bf382cc5beddb574bf17b7513af46c963e4013413f477279bd25cc0cbfa5dc31bfcb2dc5be8195033f77be4f3fe9488e3e4013413ffdf6f5bd2fdc59be74403abfb43ae1be863826bf1d5a04bf6dc50e3fe4833e3ffe437abd72dc59bed8d829bf0264e0be07ce29bfdf4f0d3f575b013fd3de403fa7e828bf2fdc59be7fdd31bf382cc5beddb574bf17b7513af46c963e20633e3f70ce28bfaa6319bf543b27bf56d5e3bea470dd3ed95f063f71ac3b3f7ffbba3d1361c3bd2fdc59be10e824bf5359e4be591727bf8ab061bd8e75413fa470bd3dae47e1bcb2d41ebf752122bf07d1e2beacad183f2575023e48e14a3feeebc03df1f4cabd2fdc59bee92a21bfabb0e1be9ca223bf1058f9be7958183fa5bdc13dae47e1bc2fdc59be2dea27bf6727e3be5eba29bf226c983eaed82f3fecc0b93dae47e1bc2fdc59bee92a21bfabb0e1be9ca223bf1058f9be7958183f3108ac3ce9b7afbd2ac720bfbc4120bf5c8edfbe8e06403ff1f40abee6ae253f0e4faf3c0a6822beb2d41ebf752122bf07d1e2beacad183f2575023e48e14a3f55c1a83caaf112beee7916bf8dd229bf1170e0beb30cb13e40a44f3fc58ff13ec74bb73dc9e5bfbd72dc59bed8d829bf0264e0be07ce29bfdf4f0d3f575b013f7e1db83dae47e1bc2fdc59be959b18bf382cc5be6f8174bf17b751b9a2b4973eff21fd3c7b83afbdd71828bf959b18bf382cc5befc18233f4703383f567d8e3eff21fd3cc28627be2fdc59be959b18bf382cc5be6f8174bf17b751b9a2b4973ef5b90a3f5530aabd2fdc59be702810bf9c53e1be4b5926bf6f81043fe4830e3f52490d3f3199aabde2772fbf263510bff94de1bebc74d33ef5db373f295c0f3fc4420d3f1e162abed71828bf959b18bf382cc5befc18233f4703383f567d8e3e68b30a3f2fdd24bee2772fbf263510bff94de1bebc74d33ef5db373f295c0f3fddb5543f8d28edbd2fdc59be702810bf9c53e1be4b5926bf6f81043fe4830e3fc1a8543fddb504bd72dc59beaa9d0dbfb8e5e3bea8572abf03784b3eb537383f8104553fddb504bde09f32bf734a0cbffc51e4be8a8e043f5bd3dc3e8d283d3fd42b553f45d8f0bd2fdc59be71020bbf7043e4be470328bf5e4b08be96213e3f787a553fddb504bd753a34bfaf3e0abf12dde3be764f1e3f3bdf0f3e6ff0453fcba1553ffc18f3bd2fdc59be30da07bf6667e1be787a25bf4ed101bfa1f8113fe2e9553fddb504bd446e36bf145d07bfa569e0be0de03d3f72f94fbe9ca2233f6ff0553f8fc2f5bd2fdc59be57b3febe382cc5bee17a74bf17b7513910e9973e39b4e83e7aa5acbd446e36bf145d07bfa569e0be0de03d3f72f94fbe9ca2233fb8afe33e325530be2fdc59be30da07bf6667e1be787a25bf4ed101bfa1f8113f8195e33e560eadbdb62b3cbf57b3febe382cc5be12a53d3fbb271f3f8126823e39b4e83e211f34be2fdc59bea5dcedbe1b46e1be9d8026bf66f7043f0de00d3f0de0cd3e2f6ea3bdb62b3cbf57b3febe382cc5be12a53d3fbb271f3f8126823ea7e8c83e0e4f2fbe2fdc59be57b3febe382cc5bee17a74bf17b7513910e9973ede02c93e5305a3bd7a7241bf9f93eebec479e0be9790ef3e52b83e3fa167f33e0de0cd3e333333be11e242bf25e6e9be01a3e3beb4c8063f3867043fb1bf2c3f6c09493fb30cf1bd2fdc59be1abfe8be82e6e3be19e228bfa3923a3e4ca63a3ff90f493f0e4fafbc346844bfb4cae4be514ee4be36cd1b3f986e523eca32443f637f493ffc18f3bd2fdc59bea5dcedbe1b46e1be9d8026bf66f7043f0de00d3f39b4483f7f6abcbc7a7241bf9f93eebec479e0be9790ef3e52b83e3fa167f33eacad483f8638d6bd72dc59bea77ae2be04ffe3beb07228bfb1e129be31083c3ff085493f0e4fafbc2fdc59beef1dddbeef57e1bedd2426bf5dfe03bfd7340f3fe7fb493f0e4fafbcab9446bf8d43ddbe8882e1be5986383fd95f76be6666263fccee493f228ef5bd2fdc59be0a30ccbe382cc5bec66d74bf000000007e1d983ee09c013fa4703dbeab9446bf8d43ddbe8882e1be5986383fd95f76be6666263f2428fe3ef01688be2fdc59beef1dddbeef57e1bedd2426bf5dfe03bfd7340f3fed0dfe3e12a53dbee5254bbf0a30ccbe382cc5be77be4f3f23dbf93e8a8ea43ee09c013f8fe492be2fdc59beb743bbbe4757e1be666626bfc1a8043f764f0e3f0b46c53e6c783abee5254bbf0a30ccbe382cc5be77be4f3f23dbf93e8a8ea43e371ac03e3c4e91be2fdc59be0a30ccbe382cc5bec66d74bf000000007e1d983e6e34c03efe433abe0c404fbf8f35bbbe226fe1be865a033f304c263fb29d0f3f0b46c53e58ca92be2fdc59beb9abafbe64e9e3bee3c728bf204131beb1503b3f73d7723fa01a7fbf23dc50bf37fbb3be514ee4be19e2183f80489f3ec4423d3fa52c733fea0464bf72dc59bec232b6be18ebe3be598628bf0f9c333e5a643b3f6a4d733fa01a7fbf0c404fbf8f35bbbe226fe1be865a033f304c263fb29d0f3f9ca2733f583964bf2fdc59beb743bbbe4757e1be666626bfc1a8043f764f0e3fd3bc733fa01a7fbf570453bf8bc3a9bea774e0be448b3c3ffc18b3be5839143f986e723fb8af63bf4ef051bf88f5aebed0b9e3be3a922b3fa4dfbebc9be63d3f21b0723f7dd063bf2fdc59beeab0aabe1e6ce1bebd5226bf061204bfc0ec0e3f986e723f560e7dbf2fdc59be36ac99be382cc5bec66d74bf00000080d044983e69001f3febe236be570453bf8bc3a9bea774e0be448b3c3ffc18b3be5839143f287e1c3f986e92be2fdc59beeab0aabe1e6ce1bebd5226bf061204bfc0ec0e3fd1911c3f22fd36bede1c56bf36ac99be382cc5bed7a3603f5dfec33e7dd0933e69001f3fbc7493be2fdc59be57b188be1c61e1bebd5226bf1d5a043fc4b10e3f4f1e163f6abc34bede1c56bf36ac99be382cc5bed7a3603f5dfec33e7dd0933ed881133f454792be2fdc59be36ac99be382cc5bec66d74bf00000080d044983ef38e133ffd8734be250259bfbebc88be6f65e1be32770d3f083d1b3f0a68123fc217163f865a93be3f355abf73f480bef050e4be371a203f11367c3e68913d3fc286273daaf152bd72dc59be60b083be3fe6e3be477229bf24283e3e3ee8393f9fcd2a3d30bb27be2fdc59bef4867bbed718e4beec5128bfc0ec1ebeb1bf3c3f2f6e233d30bb27be250259bfbebc88be6f65e1be32770d3f083d1b3f0a68123f32e62e3dabcf55bd2fdc59beb95570be3068e1be6ff025bfc13903bfe02d103f9a771c3d30bb27bebe835bbfeecd6fbec32be1beb98d363f5530cabe014d143fe3a51b3da91350bd68ce5abf5cca79beecd9e3bed6c52d3fd71272bd5a643b3f77be1f3d3c4e51bd2fdc59be57b188be1c61e1bebd5226bf1d5a043fc4b10e3fc520303d30bb27be2fdc59bed2514ebe382cc5be189574bf52499d3a7424973efb5c0d3fc58f31bebe835bbfeecd6fbec32be1beb98d363f5530cabe014d143f9fcd0a3f3cbd92be2fdc59beb95570be3068e1be6ff025bfc13903bfe02d103f11c70a3f33c431be76a95dbfd2514ebe382cc5be9cc4603f4df30ebde17af43efb5c0d3fbc7493be2fdc59be800f2ebe077ae0beecc029bf560e0d3f17b7013fde02093efca931be76a95dbfd2514ebe382cc5be9cc4603f4df30ebde17af43e48bffd3d865a93be2fdc59bed2514ebe382cc5be189574bf52499d3a7424973e2428fe3d8e7531be516a5fbfb2132ebeb878e0be9a081b3f2eff213f5917f73e70ce083e061294be516a5fbfb2132ebeb878e0be9a081b3f2eff213f5917f73e5d6dc53ebec197bd2fdc59be800f2ebe077ae0beecc029bf560e0d3f17b7013f5d6dc53efc1833be2fdc59bea08b26beea24e3be5a642bbf6519a23ea3012c3f8104c53efc1833be2fdc59becf101ebe7749e4beac8b2bbf022b873dc4423d3fe63fc43e333333bebf2960bf2e391ebe1f4ae4be39b4283ffe65373e0c023b3f5474c43e8e0670bd2fdc59be304915be85e9e3be31992abf053451bede93373f4a7bc33e333333bee08660bf97e315be0802e4be64cc2d3f5bd3bcbd6c783a3fd3bcc33ed7346fbd2fdc59be9f1e0bbe2e55e1be4b5926bf189504bfad690e3fe5d0c23efc1833be7ff560bfac700bbe3d7ce1be1748303f1283e0be0ad7133f1cebc23e44fa6dbd7bc15fbfbc0427beed0ce3bed712223f22fdd63e8273263fb81ec53e204171bd2fdc59be5794d2bd382cc5be547474bf000000807e1d983e5f98cc3d20d22fbe7ff560bfac700bbe3d7ce1be1748303f1283e0be0ad7133f7e1db83d7dd093be2fdc59be9f1e0bbe2e55e1be4b5926bf189504bfad690e3f7e1db83d8e0630be182362bf5794d2bd382cc5be7b146e3ff7e4213e95d4a93e5f98cc3d211f94be2fdc59be86e28ebdb455e1be2b8726bfd8f0043f0de00d3ff853633d061294be182362bf5794d2bd382cc5be7b146e3ff7e4213e95d4a93edd24863d20d22fbe2fdc59be5794d2bd382cc5be547474bf000000807e1d983edd24863d211f94be93fe62bf16a38ebdc171e1bee9b71f3f9d80063f9318143fd3bc633da01a2fbe464263bf6b9b62bda04fe4be9eef273f5f070e3e29ed3d3f6a4dd33ed57869bd2fdc59beb91b44bd8907e4be423e28bfaeb622beed9e3c3f58cad23e46b633be3d6363bf967840bd97e5e3beba492c3ffed438bef9a0373f986ed23ef90f69bd72dc59be04e576bd2cd7e3bea32329bf499d403e03093a3f46b6d33e46b633be93fe62bf16a38ebdc171e1bee9b71f3f9d80063f9318143f8f53d43eb1e169bd2fdc59bede1d19bd3068e1bea60a26bff38e03bfe9b70f3f85ebd13e46b633be998463bf3b3517bd3c33e1be1e162a3f371a00bf96210e3f6aded13e1ea768bd2fdc59be86e28ebdb455e1be2b8726bfd8f0043f0de00d3fc66dd43e46b633be2fdc59bef08b5bbfd767bebe57ec5fbf5b429e3e2d21bf3e04e70c3e5c20a1bee5608ebea54c72bfb667bebe96434b3e1b0d303f58ca323f006f013ebe3099be2fdc59be266d76bf2923bebe74b5153e8126323f5dfe333f04e70c3ea2b497be48dd96be259555bfb667bebe68b36a3e7e1d283f10e9373fff21fd3df853a3be8447bbbe04af6abfd767bebe70ce883e68b32a3f6519323f41f1e33d51da9bbefbe5cbbe315f4abf3c68bebe9be69d3e40a41f3f82e2373f3e79d83d1d38a7be2d5bfbbe2fc35bbfd767bebed044b83e45d8203f2e90303f7e1db83dae47a1be19c8f3be3c313fbf9467bebe9be6bd3ede93173f7424373fc807bd3d431cabbe556d0bbf580033bf3c68bebe1058d93e3b010d3f2bf6373f2fdda43d4469afbe06a124bf54aa40bfd767bebe2063ee3eed0d0e3f857c303f93a9823d83c0aabefd491cbfcba224bfd767bebe5839f43e58ca023fcb10373fbbb88d3dc66db4bee25b28bf725218bfd767bebe3867043f7c61f23e2b87363f23db793dc7bab8be8b3633bf7fa50bbf9467bebe24b90c3fa470dd3e94f6363f645d5c3d363cbdbefef241bf5d3523bfb667bebed6560c3f1748f03e933a313f5839343df4fdb4bee3e240bf4f90f0bef967bebe87a7173f925cbe3e94f6363f7dae363d5dfec3be0c0657bfca8906bfb667bebeb6841c3fe63fc43e933a313f211ff43c1214bfbe7d5c4bbf85e9cbbe3c68bebe5b421e3fae47a13e7958383f75021a3dde71cabe454752bfe352adbed767bebe39d6253fd5e78a3e143f363f4b59063d20d2cfbeaa626abf8eb1c3bed767bebe11c72a3fe02d903e2e90303f96438b3c16fbcbbe505456bfc47998bed767bebe151d293f7dd0733ea245363f4694f63cbc74d3be40dc59bfd54083bed767bebea3012c3f97ff503e143f363f1cebe23c9031d7beeb385ebfa5bc46be3c68bebe68912d3f76e01c3ed509383f16fbcb3c5bd3dcbe943077bf5b4369bed767bebed881333f8d972e3e933a313f4b59063c07ced9be532162bf8fa6dabd3c68bebe857c303f7cf2b03d7e1d383fc6dcb53cc1a8e4bea7037dbf2c9bb9bdd767bebe2bf6373f02bc853d933a313f04e78c3bc217e6be437563bf4ddc2abdd767bebe4a7b333f4ed1113dbd52363f9fabad3cfa7eeabec7bb63bf0341003bd767bebe0f9c333fd04458bbf46c363f3108ac3c3b70eebefca97dbfb85b923dd767bebee3c7383f82e247bd0ebe303f4b59863b34a2f4be54e262bfcb64b83dd767bebe6dc52e3f736891bd55303a3fa167b33cbd52f6be3c6678bff5d5553ed767bebe9318343fe25817be13f2313ffaedeb3b128300bf6b9970bf1631a43ed767bebe20632e3f0de06dbea5bd313f3b704e3c068105bf944c5ebf2c65493e3c68bebe68912d3fd0b319beb537383f5f29cb3cc9e5ffbe40dc59bf5341853ed767bebefaed2b3fe09c51be304c363f1cebe23ce5d002bfe84d65bf39f0da3ef967bebee6ae253f92cb9fbe4a0c323fc1a8a43ca8570abfc25152bf9775af3e5167bebeef38253fa8578abe79e9363f27c2063d9d8006bf46ec53bf7fa00c3fb667bebe5eba193fe948cebeb7d1303f4a7b033d77be0fbfa8fe45bff085e13e7367bebe3a921b3faa82b1beebe2363f7958283dd5e70abf8e3b39bfc79f043f7367bebe772d113f33c4d1be07f0363f3a924b3d20630ebff9123abf850b2d3fb667bebe5917073f9a77fcbeb30c313ff085493deb7315bfda8f2cbfc6be143f1b68bebe8273063ff085e9be10e9373f20636e3d933a11bf52621bbf361d493fd767bebe0a68e23e5c8f12bf0ebe303f295c8f3da8571abf446b1dbfc289243ff967bebeebe2f63efca901bf22fd363f4d158c3d5dfe13bf88650bbfef03343fb667bebe3e79d83e29ed0dbf3480373f2fdda43d0bb516bf8272fbbe3c6a5c3fd767bebee258b73e44691fbf6519323fa2b4b73d64cc1dbff435f3be0857403fd767bebe764fbe3e197317bf7424373fa470bd3d19e218bf2a1cd1be3b1a673ff967bebec6dc953e348027bf4182323ff4fdd43d5bb11fbf88d9cbbe35614b3fb667bebe36cd9b3ec52020bf10e9373f3e79d83d9fcd1abff305a5bec6bf6f3fd767bebe67d5673ee9482ebf6154323f6a4df33d053421bf2c2da3be363d543ff967bebe234a7b3eab3e27bfe258373ffd87f43d645d1cbf55df59bef8c25c3fd767bebedcd761bfdc68a0becff7b33e3b010d3e7fd91dbf3ee859beaa80773f8f1bbebe76e01c3e58ca32bf38f8323f04e70c3e5c8f22bf83a384bd5648793f7adebdbeb8af133f780b143fb8af133fec51b83cd04458bd04ab3abe2920793f163005bf97ff103d2b1815bc20d27fbf89d25e3b9cc4a0bd2e2034be1747793f7adebdbeb30c313e4b5936bf96212e3f89d25e3bf5db57bd83a384bd5648793f871605bf2aa9133f7446143fbc7413bfec51b83c772da1bd83a384bd5648793f871605bf2aa9133f7446143fbc7413bffd87043ff46c96bef9d938be863a78bf113505bfff21fd3c6519e23b3bdf7fbf1b9efe3ece8822bf04ab3abe2920793f163005bf97ff103d2b1815bc20d27fbf6dc5fe3e2b8796be83a384bd174878bf871605bfcff7133f41f113bf4a7b13bffd87043fea9522bf52f094bebc5771bff03005bf27a0093bbc74133c000080bf984cf53ee56121bf07ea94bec957723ff03005bfc3642a3b04e70cbc000080bf984cf53e19e298be01f6c1be465b69bf013105bf4260653b728a0e3c000080bf4d84ed3e72f91fbf05f9c1beee5a6a3f013105bf89d25e3b04e70cbc000080bf4d84ed3effb29bbebc57edbe39455fbfe03005bfe02d903b27a0093c000080bfa60ae63e3f351ebfe25aedbe4e43603f123105bf04e78c3bb9fc07bc000080bfa60ae63e643b9fbeb24c0bbf2c2d53bfbe3005bf9fabad3b2497ff3b000080bfc0ecde3e4d151cbf6d5310bf7ff5503ff03005bf5839b43b6f1203bc000080bf363cdd3ec1a8a4be3ca21ebfe73a45bff03005bf5839b43b1ea7e83b000080bfb537d83eb5a619bf0fd627bfda8d3e3ff03005bfa69bc43b4260e5bb000080bf0f0bd53e280fabbec6f834bf295a31bf013105bfac8bdb3b6519e23b000080bfbc96d03e4f1e16bff60c39bf59fd2d3f233105bfd044d83b17b7d1bb000080bfa01acf3e45d8b0be419b48bfaed91abfbe3005bfd734ef3bed0dbe3b000080bf5ebac93e9c3312bfbf9948bfb1db1b3fbe3005bfb37bf23bca54c1bb000080bf5ebac93eab3eb7bedba559bf095102bff03005bf6f12033c52499d3b000080bf41f1c33e0de00dbf7fa359bfb150033fe03005bfb9fc073c0ad7a3bb000080bf41f1c33ec9e5bfbe367867bf8baacfbe123105bf27a0093cb37b723b000080bf1214bf3ed93d09bfb47667bf96b1d13e123105bfb9fc073c6c0979bb000080bf1214bf3e151dc9bee84a70bf7443a3be013105bf27a0093ca69b443b000080bfbf0ebc3eb45905bfe84a70bf5a47a53ef03005bfe02d103c5f294bbb000080bfbf0ebc3e7cf2d0beadfc76bf5bb26abef03005bf075f183c99bb163b000080bfecc0b93e3c4e01bfdffc76bfd6ad6e3e013105bf075f183c27a009bb000080bfecc0b93e6c09d9beee5c7cbfbcafeabd013105bfe3a51b3ce02d903a000080bf2bf6b73ed044f8be57787bbfee06113ef03005bfbc74133c6f1283ba000080bf992ab83e933ae1bea10f7ebf0feece3c2f3605bf143f463c17b751b972f97fbfe258b73e8cb9ebbebfd53a3e257877bfe03005bfc8983bbdd734ef3be9b77fbfb072083fea9522bf6154823e753e743ff03005bffaedebba728a0ebc000080bf76710b3f226c98beed47823ecd3e73bf013105bf99bb16bbe02d103c000080bfe86a0b3ff7e421bfdd45383eeca2783f7a3805bf302a29bded0dbebb05c57fbfb072083f22fd96be49bd073ea27a77bf871605bf014d14bfcff713bf8a1f13bfa245063fea9522bf49bd073e247b783f871605bf984c15bfc139133f01de12bfa245063f22fd96befb75af3e81ed6c3f013105bf5f294bbb04e70cbc000080bf0e4f0f3f63ee9abefb75af3ed9ed6bbf013105bf17b751bb96430b3c000080bf0e4f0f3fd7a320bfaa0cdb3e5d86633f123105bf24977fbb4b5906bc000080bfe10b133f24289ebea609db3e238562bf123105bf24977fbb4b59063c000080bfe10b133f69001fbfdf6c073fb210553f123105bfe71da7bb4850fcbb000080bfa779173fc139a3befb5b023f481b57bf233105bf75029abb006f013c000080bf62a1163fc8071dbfe200163fb0ca49bf333105bfed0dbebb6c09f93b000080bf75021a3ff5b91abf3cf6233fa6b73f3f233105bf3b70cebb4260e5bb000080bff1631c3f3199aabe3351283f8eb23abf233105bf82e2c7bbf4fdd43b000080bf8d281d3f7e1d18bf492c393fabed2a3ff03005bfd044d8bba69bc4bb000080bf8e06203ffca9b1bec72a393f14ea29bf233105bf5f29cbbbf4fdd43b000080bfa913203fef3815bf9755483fbda9183f123105bfd734efbb82e2c7bb000080bf93a9223f2bf6b7be1dad463f93c919bfc42705bf04e70cbc1ea7e83b72f97fbf93a9223f651912bf683d543f925b073f5f2705bfca5441bc89d2debb72f97fbff4fd243f6dc5bebec383523fe12409bf013105bf52491dbce71da73b72f97fbff4fd243fc4b10ebf1f825e3ff640eb3e013105bf9c3322bc27a089bb72f97fbfb003273fa60ac6be12fb5c3fe4dceebe3f3a05bfed0dbebbed0d3e3b000080bf4694263f88f40bbf8e3a663f11ffc8be123105bff4fdd4bbb37b723b000080bf82e2273f4c3709bfcc46673ffbebc53e713a05bf006f01bcc3642abb000080bfec51283f8cb9cbbedeaf6e3f4da29e3ef03005bf17b7d1bb5f294bbb000080bf2c65293faa82d1be79af6e3f6ea49cbe013105bf04e70cbced0d3e3b000080bf2c65293fb45905bf8a06753f15c7613e123105bf99bb16bc99bb16bb000080bf88852a3f637fd9bec763763f219146be013105bf99bb16bcfaedeb3a000080bfbe9f2a3fb7d100bff52f793fde54043e123105bfe3a51bbce02d90ba000080bf234a2b3f6ea3e1bea8727a3f698da1bdf03005bf2b1815bc52491d3a000080bf1f852b3fd95ff6be84477b3f05c2ce3ce03005bf2b1815bc00000080000080bfffb22b3fcdccecbe1e1b613e5183553f7adebdbe27a0193fdbf93e3e7424473fcff7133e4260e5bb6151613ef4154cbfd1ca8dbeb5157b3fca5401be075f183eaf25243e6b2b96be39f2603e21074dbf7adebdbe637f193f371a40be022b473fcff7133e2b8796bed15a613ebb7b543fabcf8dbebf0e7c3f5c20c13df46c163eaf25243e4850fcbbb08c5d3ed3bd4ebf4e0a8bbe5f075e3f832fecbe92cb3f3e0b46253e591797be917c553e981651bf7adebdbe36cdbb3e857c00bf5986483f0612143e197397befb57563ebbb950bf037a89beb4c8263ff6973dbf8cdb283e17d94e3e029a88bd917c553e981651bf7adebdbe36cdbb3e857c00bf5986483f3b704e3e39b448bdb08c5d3ed3bd4ebf4e0a8bbe5f075e3f832fecbe92cb3f3e295c4f3ede9387bd0bf04d3e70ec51bf651b88beb5a6b93eb9fc67bfc0ec5e3e166a4d3e4c3789bde144443e9f7452bf7adebdbecf6655bd0b2418bf32774d3f3a924b3e5e4b48bd71e7423eea5b52bfcd5987beb6f3fdbd302a79bfa60a463e03784b3e27a089bd8f37393e7d0551bf7adebdbe925cdebebb27efbed42b453fccee493e39b448bd62853b3ea04e51bf37aa83be51dafbbe3f571bbf20d21f3f713d4a3e287e8cbdd600353ee1d04fbfb1e086be3f575bbf3f35bebe5917b73e5eba493e4d158cbd8f37393e7d0551bf7adebdbe925cdebebb27efbed42b453f0ad7633fc3f5a8bdd600353ee1d04fbfb1e086be3f575bbf3f35bebe5917b73e5986683f7ac7a9bd7cd42f3e1f2e4dbf7adebdbe4d841dbfc4422dbe2b18453f0ad7633fc442adbde21d303e29094dbfda1b8cbe674479bf645d1cbec4422d3e4703683fc442adbd2123303ea5a2553f1b118cbe4c3779bf083d1b3eb29d2f3e67d5673fad69bebeef03303eb0e5553f7adebdbe5f981cbf6ade313eaf94453f0ad7633fad69bebe6902353e3c6a583f03ed86bedfe05bbfe414bd3e58a8b53e3e79683f9b55bfbecc5d3b3e5ce5593f94bf83bee5f2ffbe1b2f1d3f645d1c3f70ce683fc6dcb5be7096423e30f1573fd2ab81befd8734bec1ca213ec7ba783fc139833e591797be62853b3ea04e51bf37aa83be51dafbbe3f571bbf20d21f3f5c8f823e426065bb9620433e1d5b4fbfebad81bef01608be5c8f42bede02793fc139833e52499dbbcc5d3b3e5ce5593f94bf83bee5f2ffbe1b2f1d3f645d1c3f5c8f823ed9ce97be62853b3ea04e51bf37aa83be51dafbbe3f571bbf20d21f3f70ce683ffe65f7bd399c493e825551bf261a84be4694763e0f9c33bfffb22b3f5f984c3edfe08bbd399c493e825551bf261a84be4694763e0f9c33bfffb22b3fd3bc833e24977fbb226f393e849e593f7adebdbe64ccddbe33c4f13efd87443f0ad7633fd26fbfbecc5d3b3e5ce5593f94bf83bee5f2ffbe1b2f1d3f645d1c3f3a92cb3e744694bd226f393e849e593f7adebdbe64ccddbe33c4f13efd87443f8cb9cb3ed04458bd6902353e3c6a583f03ed86bedfe05bbfe414bd3e58a8b53ec3d3cb3e2aa993bdd905433ed0f05a3fae9a87be3e7918be9565783fdc46433ed5e7ca3e053491bd1844443eee085b3f7adebdbed04458bd2bf6173f68914d3fd5e7ca3e197357bdcfbd473e7ff5583f259082be6ade313ea323f93eec2f5b3f9ca2833e197397bee2034b3e26185a3f7fde84be355e9a3eb30c413fb459153f713dca3ebc7493bdcfbd473e7ff5583f259082be6ade313ea323f93eec2f5b3f158cca3e4faf94bdbb284a3e0397573f28f181be2506813e3fc61c3ea69b743fd3bc833e07f096bec5cb533e12a3573f09fa83be03780b3fbb278f3ec3644a3fc1a8843e22fd96bee2034b3e26185a3f7fde84be355e9a3eb30c413fb459153faf25843e508d97bec5cb533e12a3573f09fa83be03780b3fbb278f3ec3644a3f27a0c93e2aa993bdc971573ec79f583fc97187be1904363fbb270f3f1a51da3e4c37c93ee09c91bdc971573ec79f583fc97187be1904363fbb270f3f1a51da3e0bb5263e52499dbbc5cb533e12a3573f09fa83be03780b3fbb278f3ec3644a3ff9a0273e2e90a0bb26525a3e77d6563fecbf86bea323493fd0d5563e66f7143f42cf263e82e2c7bb4f5c5e3e0b0b563f19ca89be6666663f35ef383e280fcb3e1dc9253ed044d8bb7159553e758e593f3bfd88be2c65193f5ddc463f4b59463eba6bc93e4e6290bd8ee9493ee2c95a3fb9e187be1b0d803e6f12733f8126423e8c4aca3e97ff90bdb6f65e3ec8b4563f2a388cbe0681653f50fcd83eca32043e66f7243e82e2c7bb7159553e758e593f3bfd88be2c65193f5ddc463f4b59463e1dc9253e99bb96bbff7a553e08ab593f7adebdbea301bc3e1b0d003f55c1483fd578c93ed04458bdff7a553e08ab593f7adebdbea301bc3e1b0d003f55c1483f0612143e0ad7a3bbc13b593e603d4ebf323c86be9b553f3fccee49be0a68223fe71d273e07f096be6612553e723350bf6d0086be6dc51e3f7aa50cbf9b550f3f5452273e508d97bee2ab5d3edb864dbfe62389bef7e4613fc05b20be8a1fe33e8bfd253e7dae96be90dc4a3e06494fbff20c82be03098a3e000080beed0d6e3f0ad7833e2e90a0bb6612553e723350bf6d0086be6dc51e3f7aa50cbf9b550f3fc1a8843ebc7493bbe59a523ed97a4ebf8a9383be0ad7033f1dc925be3480573fc1a8843e7cf2b0bb6612553e723350bf6d0086be6dc51e3f7aa50cbf9b550f3f04564e3e280f8bbdfb57563ebbb950bf037a89beb4c8263ff6973dbf8cdb283ef931263ea2b497bee59a523ed97a4ebf8a9383be0ad7033f1dc925be3480573f30bb273e591797bec13b593e603d4ebf323c86be9b553f3fccee49be0a68223f8104853e11c7babb26525a3e77d6563fecbf86bea323493fd0d5563e66f7143fb81e853e3b018dbe49bd073ea27a77bfd767bebe613215bfd5e71abfbada0a3fc0ecde3eca5441bdbfd53a3e257877bfe03005bfc8983bbdd734ef3be9b77fbfa392da3e984c95bd4d483b3e327577bfd767bebe197357bee3c7283f70ce383fa392da3ea5bd41bd49bd073ea27a77bf871605bf014d14bfcff713bf8a1f13bfc0ecde3e984c95bd49bd073e247b783f871605bf984c15bfc139133f01de12bf3d0a673f07ce99bdc7113b3e6478783fd767bebeadfa5cbeb07228bfacad383f4c37693f83c04abddd45383eeca2783f7a3805bf302a29bded0dbebb05c57fbf4c37693f75029abd49bd073e247b783fd767bebe265315bf431c1b3ffa7e0a3f3d0a673fa8574abd49bd073ea27a77bf871605bf014d14bfcff713bf8a1f13bfc5fe623ff853c3be49bd073ed3c1763ff703bebe2eff31bfe7fb293d14ae373f7e8c693fb07238bf49bd073e247b783f871605bf984c15bfc139133f01de12bfc5fe623fc7ba38bf49bd073e61c175bfa307bebef31f32bf0ad723bdde93373f7e8c693f26e4c3be49bd073ea27a77bfd767bebe613215bfd5e71abfbada0a3ff085693ff853c3be49bd073e247b783fd767bebe265315bf431c1b3ffa7e0a3ff085693f55c138bf8fa58f3e40f6563fb667bebe234a5b3fffb29bbeb459d53e6de77b3fb3ea33bf2ba58f3eb85871bf390cbebe863856beceaa2f3f6154323f51da7b3f14aec7bed3a58f3e48e255bfb667bebe6de75b3ff6289c3eea95d23e6de77b3f575bd1befda38f3eff5888bb382cc5bee17a743f17b7d13810e9973e9a777c3f5f070ebffda38f3e2b88c13d382cc5be86c9743f6c09f9bae2e9953e9a777c3f257512bffda38f3e9413ed3c425ce1be4b59263fc66d043f8d970e3ffbcb7e3f60760fbf1ea48f3e7b16843d7688dfbe645d2c3f0a6812bf72f9ef3ec4b17e3f5c2011bffda38f3e5a466a3d95d7e2bebe30293f35efb8be9565283f4df37e3fb7d110bffda38f3eb8011f3d0ceae3bedaac2a3f72f94f3ede93373f12147f3fe5f20fbffda38f3ed317423d5649e4bed1222b3f4faf94bd32773d3f2d217f3f325510bf40a48f3e0057723faf08bebebd5256be5bb12fbf6154323fdfe07b3f55c138bf1ea48f3e01fa513fee98e2be5227203fc9e5df3ecf66253fc0ec7e3f8a1f33bfd3a58f3ee9b8563fda59e4be30bbe73e894120bf5c8f223fa01a7f3fb3ea33bffda38f3ea0fd483f382cc5befd87743f6f1283ba3480973e9a777c3faa8231bffda38f3ef9bb2f3f382cc5be6abc743f6f12833aa245963e9a777c3f560e2dbffda38f3edaaa403ffb22e1be99bb263fcba105bf3b010d3f89d27e3f8e0630bffda38f3e3ba9373f8f34e0bef085293f711b0d3f13f2013fc4b17e3fad692ebffda38f3e6f493a3faddce3be74b5253f0c026b3e910f3a3ff7067f3fa4df2ebffda38f3ee6073e3f04e4e3be3e79283f469436be96433b3f12147f3f24972fbffda38f3e107a163f382cc5be1895743f17b7513a7424973e9a777c3f90a028bffda38f3e0459273fde38e1be9d11253f006f01bf3cbd123f89d27e3fc8982bbffda38f3e57af1e3ff2d3e0be8f53243f492eff3ed42b153f52b87e3f75022abffda38f3ea779233fe09de4bec58f213f8fc275bc2b87463fa01a7f3fd5e72abffda38f3e4c70fa3e382cc5be8a8e743f17b7d139c74b973e9a777c3fca3224bffda38f3edafe0d3fcc5fe1be304c263fca3204bf17d90e3f17d97e3f742427bfdba38f3e467a053fd9ece0be94f6263f99bb063f71ac0b3fc4b17e3f228e25bffda38f3e3b34083fefe4e3bed044283f6f12433e4ca63a3fa01a7f3fdd2426bffda38f3e84660b3fa5f9e3be226c283fc4422dbedfe03b3fa01a7f3f62a126bffda38f3effecc73e382cc5bee17a743f0000000010e9973e9a777c3f05c51fbffda38f3ea38fe93e1a4ee1be4b59263f14d004bfcd3b0e3f17d97e3faeb622bffda38f3e37dfd83e925de1bea60a263fd3bc033f97900f3fa4df7e3fae4721bffda38f3e4643de3e3f01e4be5986283f30bb273e4d153c3fa01a7f3f4ed121bffda38f3eae7ee43eb8e5e3be19e2283fc8983bbe31993a3f12147f3f454722bffda38f3e2b69953e382cc5be34a2743fe02d903a07f0963e9a777c3fb1501bbffda38f3ef5f3b63ee964e1be143f263f3d2c04bfc0ec0e3f89d27e3f764f1ebffda38f3e2096a53e3886e0be43ad293f76e00c3f2eff013f6dc57e3fb1bf1cbffda38f3e904aa93e3623e3bee86a2b3fae47a13e832f2c3ff7067f3fe4141dbffda38f3e80f0b13e97e5e3be7ffb2a3f857c50be3945373f12147f3f7fd91dbffda38f3e9e98ad3ec74ae4be76712b3f2575823d166a3d3f2d217f3f32771dbffda38f3ebccb453e382cc5bec1a8743f3480b7bab4c8963e9a777c3febe216bffda38f3eac73843e4f5de1bed95f263f8a8e04bfad690e3f17d97e3f95d419bfdba38f3e53e9673e9f71e1be2b18253fbc96003fbc74133fa4df7e3f226c18bffda38f3ef10d753e8945e4beec51283f6d56fd3d5b423e3fa01a7f3f35ef18bffda38f3ea7ea7e3e60e6e3be355e2a3fbbb84dbed509383f12147f3f2c6519bfdba38f3e09a7253e876ce0bedaac2a3f4df30ebf3fc6fc3ec4b17e3f228e15bfdba38f3eae9b023efb58e1be3411263f98dd033fd26f0f3f17d97e3fcff713bfdba38f3ee84b1f3eecdee2be1ac02b3fc58fb1be4bc8273fc0ec7e3f265315bffda38f3e66860d3e8907e4be5af5293f5b423e3e4772393fa01a7f3fe17a14bffda38f3ec8ea163e963ee4be5f292b3f849ecdbded9e3c3f12147f3f66f714bffda38f3e5794d2bd382cc5be3867743f52499d39d044983e9a777c3f0c9309bffda38f3e3acf18bdc05ee1be6666263f6f8104bfc9760e3f17d97e3fb6840cbffda38f3e2fa58ebd8464e1be6b2b263f41f1033f643b0f3fa4df7e3f431c0bbffda38f3e419d72bdab07e4be075f283f67d5273e9f3c3c3fa01a7f3fe3a50bbffda38f3e29b040bd82e6e3bef90f293f7ffb3abede713a3f12147f3fda1b0cbffda38f3ed2514ebe382cc5be34a2743f89d2de3a07f0963e9a777c3f462505bffda38f3e5da50bbe4582e1be8273263f61c303bf12140f3fa4df7e3f621008bffda38f3e74442ebe8461e0be7ac7293fc4420d3f8e75013f6dc57e3fb98d06bffda38f3e707726bea229e3be3f572b3f6519a23e4d152c3ff7067f3f79e906bffda38f3ef73e15bed1e7e3be48e12a3f4df34ebea779373f12147f3f87a707bffda38f3ef0fd1dbe624ae4bec8982b3fdcd7813d52493d3f2d217f3fc74b07bffda38f3e36ac99be382cc5bee17a743f52499db9d9ce973e9a777c3f80b700bffda38f3e7eac70beb280e1be107a263f98dd03bf4df30e3fa4df7e3f9ca203bffda38f3e7eac88bee066e1be1904263f8195033f77be0f3fa4df7e3f2a3a02bf1ea48f3e035d83beb5fde3be029a283f0b24283e16fb3b3fa01a7f3fcac302bffda38f3e033f7abe29e7e3be151d293fd95f36be68b33a3f12147f3fc13903bffda38f3e0a30ccbe382cc5bec66d743f17b7d139b537983e9a777c3f7593f8befda38f3e9a79aabe9432e1be62a1263fb45905bf88630d3f6dc57e3f1b9efebefda38f3ef915bbbecfa0e1be41f1233f51dafb3e22fd163fa4df7e3fe3a5fbbefda38f3e77a3afbe8ae4e3bede712a3fd0d556bec74b373f12147f3ff697fdbefda38f3e2507b4bea147e4bebe30293ff241cf3d925c3e3f2d217f3f5bd3fcbefda38f3e57b3febe382cc5bee17a743f52491d3ad9ce973e9a777c3fceaaefbefda38f3e6e4eddbe4277e1bed95f263f41f103bf69000f3f17d97e3f228ef5befda38f3efeeeedbe4834e1bea60a263f26e4033fd26f0f3f6dc57e3fce88f2befda38f3e6de3e7bec827e4be10e9273f2d431c3ec4423d3fa01a7f3f61c3f3be1ea48f3e7828e2be5ce3e3be3ee8293f5c2041be8351393f12147f3f6abcf4befda38f3e959b18bf382cc5bea69b743f17b751b907f0963e9a777c3f42cfe6befda38f3e97e107bfdf6ee1be6666263faf2504bffbcb0e3f17d97e3f96b2ecbefda38f3e223610bfbc40e1bec217263faf25043f2d210f3f17d97e3f95d4e9befda38f3e66670dbfe206e4be7958283f8b6c273e2d433c3fa01a7f3fd5e7eabefda38f3ee3550abf8ae4e3be1058293f35ef38bea8573a3f12147f3fdfe0ebbefda38f3e7fdd31bf382cc5be86c9743f17b7513aabcf953e9a777c3fb6f3ddbefda38f3e26fe20bf9b38e1be4260253f539601bfb840123f89d27e3f0ad7e3befda38f3e3fe029bf2d5ae0bec7ba283f234a0b3f4bea043f6dc57e3f9cc4e0befda38f3e6f4827bfd3bce3be992a283f5b427e3e143f363f840d7f3f8ab0e1befda38f3e726d24bfbf44e4bea2b4273ffed4f8bd32e63e3fa01a7f3fe5d0e2befda38f3e251f4bbf382cc5bed8f0743f89d2debaa1d6943e9a777c3f2b18d5befda38f3ebe2f3abf921fe1beb81e253f4ed101bf6154123f89d27e3f7ffbdabedba38f3e08cd42bfff25e1be9318243f4850fc3e62a1163f17d97e3f6210d8befda38f3e8e1f3ebfe09de4bed712223f62a1d6bca60a463fa01a7f3fb5a6d9bedba38f3e45da52bfa5bfdfbeccee293f64cc0dbf65aa003fe0be7e3f6154d2bee3a98f3e25b055bf7afbe3be6900cf3e32e6ee3e9e5e493f12147f3f575bd1be39f2603e21074dbf7adebdbe637f193f371a40be022b473f304c763f539631bf2ba58f3eb85871bf390cbebe863856beceaa2f3f6154323f8195733f2bf637bf40a48f3e0057723faf08bebebd5256be5bb12fbf6154323f8195733fc217c6be86015b3e61c175bf4fe8bdbe423e28beae47313f0ad7333f2b87763f55c138bf917c553e981651bf7adebdbe36cdbb3e857c00bf5986483fb4c8763fb84032bfe144443e9f7452bf7adebdbecf6655bd0b2418bf32774d3f3480773fce8832bf8f37393e7d0551bf7adebdbe925cdebebb27efbed42b453f9eef773fb84032bf49bd073e61c175bfa307bebef31f32bf0ad723bdde93373fac1c7a3f55c138bf7cd42f3e1f2e4dbf7adebdbe4d841dbfc4422dbe2b18453f7958783fe09c31bf49bd073ed3c1763ff703bebe2eff31bfe7fb293d14ae373fac1c7a3f6f81c4beef03303eb0e5553f7adebdbe5f981cbf6ade313eaf94453f7958783fa913d0be226f393e849e593f7adebdbe64ccddbe33c4f13efd87443f9eef773ffbcbcebe43015b3ed3c1763f4fe8bdbe795828be204131bf0ad7333f2b87763f6f81c4be1844443eee085b3f7adebdbed04458bd2bf6173f68914d3f3480773fe948cebeff7a553e08ab593f7adebdbea301bc3e1b0d003f55c1483fb4c8763ffbcbcebe1e1b613e5183553f7adebdbe27a0193fdbf93e3e7424473f304c763ffb3ad0beb0cb603f8ee51d3d22e1e3be9a772cbf6ea3413eebe2363f8e06603fd26f3fbffda38f3eb8011f3d0ceae3bedaac2a3f72f94f3ede93373fe5f25f3f189524bffda38f3e9413ed3c425ce1be4b59263fc66d043f8d970e3f3255603fcb1027bf20b2603f880e413d9851e4be621028bf02bc05be3f353e3f24975f3f44693fbffda38f3ed317423d5649e4bed1222b3f4faf94bd32773d3f97905f3f189524bffda38f3e5a466a3d95d7e2bebe30293f35efb8be9565283fa01a5f3f189524bf0f7c603f0adc7a3d4772e1bee5f21fbfb45905bfbde3143f12145f3fb7623fbfdee3603f40dbea3c073fe1beb1e129bf4013013fbf7d0d3f3255603f287e3cbf1ea48f3e7b16843d7688dfbe645d2c3f0a6812bf72f9ef3ef2d2cd3edaac3abefda38f3e5a466a3d95d7e2bebe30293f35efb8be9565283fe948ce3edaac3abe0f7c603f0adc7a3d4772e1bee5f21fbfb45905bfbde3143f5f07ce3e8fe492be1fbf5f3f2b88c13d382cc5bed1226bbf04e78cbc5530ca3ea7e8c83e21b092befda38f3e2b88c13d382cc5be86c9743f6c09f9bae2e9953ede02c93e11c73abefda38f3e2b88c13d382cc5be86c9743f6c09f9bae2e9953ebe9fda3e58ca92be0fb45e3f2880023e9948e1beceaa2fbf4bc8e73e33c4113f40a4df3e6de73bbedba38f3eae9b023efb58e1be3411263f98dd033fd26f0f3f77bedf3e8fe492be1fbf5f3f2b88c13d382cc5bed1226bbf04e78cbc5530ca3ea392da3e7ffb3abedba38f3eae9b023efb58e1be3411263f98dd033fd26f0f3fe86a8b3ea52c03be0fb45e3f2880023e9948e1beceaa2fbf4bc8e73e33c4113fcc5d8b3ea857cabcdf525e3fe5980c3e11e5e3be29ed2dbf4694f63df54a393ffaed8b3e16fbcbbc41455d3f228e253e5378e0be12a51dbf5bb11fbff46cf63e5f078e3e508d17bddba38f3e09a7253e876ce0bedaac2a3f4df30ebf3fc6fc3e5f078e3e1d38e7bdd5975d3f6a4e1e3ea016e3be0ad723bf0534d1be4694263fbbb88d3ea913d0bc48f95d3fd95c153e7f4fe4be151d29bfaeb622bec3d33b3fcdcc8c3e849ecdbcfda38f3ec8ea163e963ee4be5f292b3f849ecdbded9e3c3fa8358d3ea52c03befda38f3e66860d3e8907e4be5af5293f5b423e3e4772393f9f3c8c3ea52c03bedba38f3ee84b1f3eecdee2be1ac02b3fc58fb1be4bc8273f0de08d3ea52c03bedba38f3e09a7253e876ce0bedaac2a3f4df30ebf3fc6fc3e5af5f93e5bd33cbe6aa25b3fbccb453e382cc5beb9fc67bfa7e888be30bba73e0f0bf53e7c6192befda38f3ebccb453e382cc5bec1a8743f3480b7bab4c8963e2b18f53e91ed3cbe41455d3f228e253e5378e0be12a51dbf5bb11fbff46cf63e5af5f93e73d792befda38f3ebccb453e382cc5bec1a8743f3480b7bab4c8963e70ce583f068125bfb2be593f0d51653e950ce0be03783bbfeeebe03eef38053f6666563f12143fbfdba38f3e53e9673e9f71e1be2b18253fbc96003fbc74133f8638563f787a25bf6aa25b3fbccb453e382cc5beb9fc67bfa7e888be30bba73e70ce583fd26f3fbf1100583f48a77e3ef2efe3bef7061fbf21b0b2be9ca2333f0ebeb03e812602befda38f3ea7ea7e3e60e6e3be355e2a3fbbb84dbed509383fbc96b03eb003e7bc1f9f583f20ef753e9949e4bef54a29bf7dd0b3bd52b83e3f7368b13e5c8f02befda38f3eac73843e4f5de1bed95f263f8a8e04bfad690e3f3bdfaf3eb003e7bcdba38f3e53e9673e9f71e1be2b18253fbc96003fbc74133f6154b23eb003e7bc3d46593f2f696c3e17f0e2be454732bfa8574a3e499d303f4547b23e38f802befda38f3ef10d753e8945e4beec51283f6d56fd3d5b423e3f8e75b13eb003e7bcb2be593f0d51653e950ce0be03783bbfeeebe03eef38053fce88b23e8f53f4bd083d573f7d7a843ea664e1be92cb0fbf60e520bfecc0093fa913b03ea5bd01befda38f3eac73843e4f5de1bed95f263f8a8e04bfad690e3f3789013fc139a3bd577b543f2b69953e382cc5be583964bfa470bdbe02bc853e7fd9fd3efd8734befda38f3e2b69953e382cc5be34a2743fe02d903a07f0963e9be6fd3e9ca2a3bd083d573f7d7a843ea664e1be92cb0fbf60e520bfecc0093faa82013f6b2b36befda38f3e2b69953e382cc5be34a2743fe02d903a07f0963ec58ff13e9d11a5bdcf81513f4b94a53e7b86e0bea4703dbf234abb3e857c103fb1bfec3ec5fe32befda38f3e2096a53e3886e0be43ad293f76e00c3f2eff013f287eec3ec1a8a4bd577b543f2b69953e382cc5be583964bfa470bdbe02bc853ec58ff13e7d3f35befda38f3e2096a53e3886e0be43ad293f76e00c3f2eff013f2eff313f3d2cd4bccf81513f4b94a53e7b86e0bea4703dbf234abb3e857c103fa1f8313f8351c9bdfda38f3e904aa93e3623e3bee86a2b3fae47a13e832f2c3fa5bd313f3d2cd4bce0f54d3fa1b9b63e44a5e1beb3ea03bfca5421bfc1a8143f65aa303fd26fdfbdfda38f3ef5f3b63ee964e1be143f263f3d2c04bfc0ec0e3f499d303f4b5906bdfda38f3e80f0b13e97e5e3be7ffb2a3f857c50be3945373f97ff303f3d2cd4bcc3b7503ffc8ea93e234ae3be9b552fbfc1a8a43d2c65393f8ab0313f4625f5bdf2b14f3f5395ae3ecb68e4bee86a1bbfc74b77bec1ca413f2041313f46b6f3bdfda38f3e9e98ad3ec74ae4be76712b3f2575823d166a3d3f575b313f3d2cd4bcfda38f3ef5f3b63ee964e1be143f263f3d2c04bfc0ec0e3f9a99493f14d024bf83f7493fffecc73e382cc5be304c56bf3f57fbbe9031773ee71d473f71ac3bbffda38f3effecc73e382cc5bee17a743f0000000010e9973e022b473fa1d624bfe0f54d3fa1b9b63e44a5e1beb3ea03bfca5421bfc1a8143f9a99493fba493cbffda38f3effecc73e382cc5bee17a743f0000000010e9973e08acbc3e0e4fafbd8596453f63f0d83e6074e1bedd2436bffdf6753e6c09293f6b9ab73eae4721befda38f3e37dfd83e925de1bea60a263fd3bc033f97900f3f3480b73ea01aafbd83f7493fffecc73e382cc5be304c56bf3f57fbbe9031773e08acbc3ec5fe32befda38f3e37dfd83e925de1bea60a263fd3bc033f97900f3ff90f593f96430bbd8596453f63f0d83e6074e1bedd2436bffdf6753e6c09293fde02593f228ef5bdfda38f3e4643de3e3f01e4be5986283f30bb273e4d153c3f029a583f96430bbd50e1403f8274e93eb570e1bee926f1be29cb30bfb6840c3f10e9573f8e06f0bdfda38f3ea38fe93e1a4ee1be4b59263f14d004bfcd3b0e3fd9ce573f96430bbdcedd423f97ace23e9a5ce4bef31f12bf85eb91beb81e453f9565583fb37bf2bdfda38f3eae7ee43eb8e5e3be19e2283fc8983bbe31993a3f0b24583f96430bbdfda38f3ea38fe93e1a4ee1be4b59263f14d004bfcd3b0e3f8351093e3199aabd55883b3f4c70fa3e382cc5beaeb642bfacad18bff853833e48bffd3dd49a26befda38f3e4c70fa3e382cc5be8a8e743f17b7d139c74b973e2428fe3d9fcdaabd50e1403f8274e93eb570e1bee926f1be29cb30bfb6840c3f8351093e1e162abefda38f3e4c70fa3e382cc5be8a8e743f17b7d139c74b973e8ab0613d30bb27be8e79353f6af5053f98dfe1be4faf34bff9a0273e696f303f6f81843d7d3fb5bddba38f3e467a053fd9ece0be94f6263f99bb063f71ac0b3fdc46833d67d527be55883b3f4c70fa3e382cc5beaeb642bfacad18bff853833e8ab0613d560eadbd3d2a323f685e0a3f5d4fe4bede0209bf0612b4bea69b443fa9a45e3f45d8f0bdfda38f3e3b34083fefe4e3bed044283f6f12433e4ca63a3ff7065f3f591737bd8e79353f6af5053f98dfe1be4faf34bff9a0273e696f303f9b555f3fd8f0f4bd3e402f3f611a0e3fe546e1be2bf6d7be0b2438bfdf4f0d3fb22e5e3f6891edbdfda38f3e84660b3fa5f9e3be226c283fc4422dbedfe03b3f00915e3f591737bddba38f3e467a053fd9ece0be94f6263f99bb063f71ac0b3fd26f5f3ff6975dbdfda38f3edafe0d3fcc5fe1be304c263fca3204bf17d90e3f96215e3f7dae36bdfda38f3e107a163f382cc5be1895743f17b7513a7424973ef5db973eb1e169bdfda38f3edafe0d3fcc5fe1be304c263fca3204bf17d90e3fc8079d3ed57869bd3e402f3f611a0e3fe546e1be2bf6d7be0b2438bfdf4f0d3f91ed9c3ecac302be8c2d283f107a163f382cc5beb98d26bfea9532bfb5a6993ed9ce973e4850fcbd6265203fcedd1e3ffa45e1be10e937bfd42be53d05c52f3fa3014c3f1f85ebbdfda38f3e107a163f382cc5be1895743f17b7513a7424973e3b704e3ff6975dbd8c2d283f107a163f382cc5beb98d26bfea9532bfb5a6993e3b704e3ffdf6f5bdfda38f3e57af1e3ff2d3e0be8f53243f492eff3ed42b153fa3014c3f3fc65cbd2dd11d3f8b6e213f7ee3e3be36ab0ebf1d5a84bee7fb493f5227103f5227a0bdfda38f3e57af1e3ff2d3e0be8f53243f492eff3ed42b153f1283103f52499dbc6265203fcedd1e3ffa45e1be10e937bfd42be53d05c52f3f857c103fc139a3bdfda38f3ea779233fe09de4bec58f213f8fc275bc2b87463faed80f3f091b9ebcc9051b3f5227243f7841e4be82e2e7bee63f04bf03093a3f5bb10f3f2d439cbdfda38f3e0459273fde38e1be9d11253f006f01bf3cbd123ff2410f3f52499dbcf9a0173f3755273f8046e1be74b5b5befd8744bf7593083f492e0f3f992a98bdcff50d3f73bb2f3f882dc5be637f09bf4d844dbfddb5843eb9fc073ffca9f1bdfda38f3e0459273fde38e1be9d11253f006f01bf3cbd123f4faf043fd57829bef9a0173f3755273f8046e1be74b5b5befd8744bf7593083f228e053f8a1fe3bdfda38f3ef9bb2f3f382cc5be6abc743f6f12833aa245963eab3e073fe7fb29beace1023f5741383f5da7e1be3e7928bf5f29cbbb0ebe403faf25e43d204171bdfda38f3ef9bb2f3f382cc5be6abc743f6f12833aa245963ece19d13df163ccbdcff50d3f73bb2f3f882dc5be637f09bf4d844dbfddb5843ece19d13d61c353bdfda38f3e3ba9373f8f34e0bef085293f711b0d3f13f2013f8a8ee43d5f98ccbdfda38f3ee6073e3f04e4e3be3e79283f469436be96433b3fb7d1003e2a3a12bdb08efb3e19c83b3fdc49e4be9cc4e0be88f4dbbe75024a3fee5a023ebde394bd8eaef23e1bb93e3fce8be3be3108acbee86a2bbf7e8c293f0000003e728a8ebdfda38f3e6f493a3faddce3be74b5253f0c026b3e910f3a3f8195033e05a312bdfda38f3edaaa403ffb22e1be99bb263fcba105bf3b010d3fdbf9fe3d2a3a12bd861ceb3ea41b413f490ee0be96438bbe5af559bfe6aee53e48bffd3d713d8abdfda38f3e3ba9373f8f34e0bef085293f711b0d3f13f2013f14d0043e857c50bdace1023f5741383f5da7e1be3e7928bf5f29cbbb0ebe403f6f81043ee02d90bd02bacf3e7afe483f7c2cc5be910fbabe666666bf107a763eec51083fe10b13befda38f3edaaa403ffb22e1be99bb263fcba105bf3b010d3f5d6d053fc3f528be861ceb3ea41b413f490ee0be96438bbe5af559bfe6aee53e8bfd053fde0209befda38f3ea0fd483f382cc5befd87743f6f1283ba3480973eb9fc073fd57829befda38f3ea0fd483f382cc5befd87743f6f1283ba3480973ec58ff13e865a53bdf773aa3e4bab513f5325e2beeb7305bf728a0ebec286573f0d71ec3eb5157bbd1ea48f3e01fa513fee98e2be5227203fc9e5df3ecf66253f287eec3eaaf152bd02bacf3e7afe483f7c2cc5be910fbabe666666bf107a763ec58ff13e2b8796bdd3a58f3ee9b8563fda59e4be30bbe73e894120bf5c8f223f865a033fe9488ebe1ea48f3e01fa513fee98e2be5227203fc9e5df3ecf66253f780b043fe9488ebef773aa3e4bab513f5325e2beeb7305bf728a0ebec286573fea04043fd71292bef773aa3e4bab513f5325e2beeb7305bf728a0ebec286573f8bfd053f9b551fbe8fa58f3e40f6563fb667bebe234a5b3fffb29bbeb459d53ec3f5083f9e5e29bed3a58f3ee9b8563fda59e4be30bbe73e894120bf5c8f223f8fc2053f795828be29ccc33e2d094c3fb667bebe637f99be6e3420bfec51383fa7e8083fe25817be1e37ec3e46d0403f9467bebe8863bdbe4bc817bfe71d373f70ce083fba6b09be8eaef23e1bb93e3fce8be3be3108acbee86a2bbf7e8c293fe6ae053ff0a706beb08efb3e19c83b3fdc49e4be9cc4e0be88f4dbbe75024a3f228e053fa52c03bebad8083ff3e7333f9467bebe5986d8bea4700dbf82e2373f90a0083fd93df9bdace1023f5741383f5da7e1be3e7928bf5f29cbbb0ebe403f39d6053fdbf9febdb4581a3fdbe0243f9467bebed95ff6bea5bd01bfe71d373f075f083fae47e1bdc9051b3f5227243f7841e4be82e2e7bee63f04bf03093a3f7d3f053f8941e0bd2dd11d3f8b6e213f7ee3e3be36ab0ebf1d5a84bee7fb493f7cf2703e508d97bdb4581a3fdbe0243f9467bebed95ff6bea5bd01bfe71d373faf25643e2b8796bdc9051b3f5227243f7841e4be82e2e7bee63f04bf03093a3fe926713e2aa993bd6265203fcedd1e3ffa45e1be10e937bfd42be53d05c52f3f57ec6f3ebe9f9abd508b293ffe0a153f3c68bebe143f06bf5530eabef5db373f9ca2633e302aa9bd8c2d283f107a163f382cc5beb98d26bfea9532bfb5a6993e8bfd653e79e9a6bd3e402f3f611a0e3fe546e1be2bf6d7be0b2438bfdf4f0d3f0e4f6f3e3333b3bd912a363f0116053f7367bebe7cf210bf2a3ad2be94f6363f8a1f633e5bb1bfbd3d2a323f685e0a3f5d4fe4bede0209bf0612b4bea69b443f3255703e7e1db8bd8e79353f6af5053f98dfe1be4faf34bff9a0273e696f303fa01a6f3e5b42bebd55883b3f4c70fa3e382cc5beaeb642bfacad18bff853833e0b46653e83c0cabde00f433faf27e23eb667bebeac8b1bbf85ebb1bed0d5363f789c623e88f4dbbd50e1403f8274e93eb570e1bee926f1be29cb30bfb6840c3ffbcb6e3e3d0ad7bdcedd423f97ace23e9a5ce4bef31f12bf85eb91beb81e453fb29d6f3e88f4dbbd8596453f63f0d83e6074e1bedd2436bffdf6753e6c09293f20636e3e4182e2bd83f7493fffecc73e382cc5be304c56bf3f57fbbe9031773ef8c2643e2063eebddb6d4f3f6536b03e5167bebe0f0b25bf68b38abe22fd363f9c33623e492effbde0f54d3fa1b9b63e44a5e1beb3ea03bfca5421bfc1a8143f7b146e3efe43fabdf2b14f3f5395ae3ecb68e4bee86a1bbfc74b77bec1ca413fa01a6f3e92cbffbdc3b7503ffc8ea93e234ae3be9b552fbfc1a8a43d2c65393ffbcb6e3e6ea301becf81513f4b94a53e7b86e0bea4703dbf234abb3e857c103f68916d3e4a7b03be577b543f2b69953e382cc5be583964bfa470bdbe02bc853e1d5a643ede0209bee203573fc0e9853ed767bebedfe02bbf2a3a52be304c363ff7e4613e3b700ebe083d573f7d7a843ea664e1be92cb0fbf60e520bfecc0093f0de06d3ebb270fbe1100583f48a77e3ef2efe3bef7061fbf21b0b2be9ca2333ffbcb6e3e97ff10be1f9f583f20ef753e9949e4bef54a29bf7dd0b3bd52b83e3ffbcb6e3e986e12bee0835b3f5a104a3e3c68bebe849e2dbf07ce19be992a383f8ab0613e75021abe3d46593f2f696c3e17f0e2be454732bfa8574a3e499d303f7b146e3e744614beb2be593f0d51653e950ce0be03783bbfeeebe03eef38053f1ff46c3e068115be6aa25b3fbccb453e382cc5beb9fc67bfa7e888be30bba73e780b643ef5b91abe41455d3f228e253e5378e0be12a51dbf5bb11fbff46cf63e8d286d3e894120bed5975d3f6a4e1e3ea016e3be0ad723bf0534d1be4694263f7b146e3ee56121bee221603feae8b83df967bebe6dc52ebf053491bdc7293a3f5396613efb5c2dbe48f95d3fd95c153e7f4fe4be151d29bfaeb622bec3d33b3f8d976e3ef85323bedf525e3fe5980c3e11e5e3be29ed2dbf4694f63df54a393f20636e3e9d1125be0fb45e3f2880023e9948e1beceaa2fbf4bc8e73e33c4113ffb5c6d3e0bb526be1fbf5f3f2b88c13d382cc5bed1226bbf04e78cbc5530ca3e41f1633e448b2cbe0f7c603f0adc7a3d4772e1bee5f21fbfb45905bfbde3143f32776d3eea9532be20b2603f880e413d9851e4be621028bf02bc05be3f353e3f567d6e3e0f0b35beb0cb603f8ee51d3d22e1e3be9a772cbf6ea3413eebe2363fe9486e3eebe236bedee3603f40dbea3c073fe1beb1e129bf4013013fbf7d0d3f8d286d3e90a038be1afd603f0341003bd767bebe4a7b33bf6f1283ba2b87363f1c7c613eff213dbedbfb603fff5888bb382cc5be39d675bfd3de603d31088c3e0ad7633e5b423ebe3fc6603fe76e17bd4b3fe1be55302abf2497ffbe5b420e3fc4426d3e931844be0070603ffa997abdb667bebe530533bf143f463d4694363f5396613ecc7f48bec2a4603fdc6840bd11e5e3beba492cbfc74b37bea2b4373f20636e3ecba145bea983603f210163bd0550e4be598628bf6f12033e9be63d3f8d976e3ea77947be8d44603f240a8dbd90d9e1be32e61ebf3867043f5ddc163f32776d3e03094abe45655f3f5794d2bd382cc5becff773bf3789013ee8d98c3e41f1633e72f94fbeb37b5e3fa56504bed767bebea5bd31bfd0b3d93d8638363f8ab0613e86c954be47565e3f7e3808be47aadfbe66f734bf6c09f9bebc74033f560e6d3ecf6655be35ee5d3ffd6612be338ce3be11c72abf7ffb3abea7e8383fe9486e3e07f056be43395d3f149422bee319e4beceaa1fbf713d8a3e36cd3b3f567d6e3e75025abe5c735b3f5b0747be1b68bebe12a52dbf52b81e3e82e2373fc1ca613ec05b60beb4af5c3fa0c62dbe289de0beccee19bfd6c51d3f9c33023ffb5c6d3ebf0e5cbec8ea5a3fd2514ebe382cc5be304c66bf11c7ba3c643bdf3eaf25643e8ab061be28f2583fb7416dbefcabdfbe96433bbf5a64dbbe4bc8073ffb5c6d3ee71d67bee203573fbce883bed767bebea3012cbf2a3a523ef931363f2eff613ec3d36bbe193c583fec6977be3f8de3be44fa2dbf3cbd12be2731383f567d6e3e1ea768be1d76573f1afd80be1151e4be000020bf567d6e3e52b83e3f32e66e3ed5e76abe9d46563f2eab88be5c71e1be32770dbfd1221b3fb37b123f0de06d3efb5c6dbec572533f933a99bed767bebe50fc28bf0f0b753ea245363f6519623e333373be315e533f36ac99be382cc5be80b760bf0a68c23e3d9b953e5474643ea16773be8813503fadbeaabe6e88e1be6b9a37bf8ab0a1bef7061f3fb22e6e3e477279bef75b4f3fe52aaebed767bebee6ae25bf569f8b3e143f363fd34d623ea3927abeaabb4e3f8a21b1be963ee4bef85323bff697dd3da52c433f0e4f6f3effb27bbebe6a4d3f6f29b7be7ba2e3be1d5a14bfa4dfde3e4e62303fd7346f3ea4707dbef9844c3fb325bbbee469e1be6f8104bf80482f3f865a033f20636e3e12147fbe226c483f2fa7ccbe3c68bebeb22e1ebfdcd7a13e5e4b383f789c623e789c82be1268483f0a30ccbe382cc5be4df34ebfadfafc3eefc9a33e66f7643e5c8f82be7fc0433f5392ddbec503e2bee6ae35bf9d8066bed5e72a3ffbcb6e3ecba185be59de3d3f7976f1bef967bebe545217bff706bf3ecb10373f5305633ede0289be6f4a413f5e0fe6be9851e4be50fc18bf1dc9853e4a0c423f20d26f3ee71d87be942f403f2bbee9be09a9e3becb1007bfcc7f083ff54a293f7b836f3e82e287bed4b93e3f1780eebe7a8ee0bef241efbebbb83d3fd0d5f63e8d976e3ee78c88be3b6d393f57b3febe382cc5be5bd33cbf05c51f3f5dfe833eaf94653ecc5d8bbe9433303f5ef60bbfd767bebe0d710cbf3f35de3e07f0363f6688633ee9b78fbee928333f5a1208bfdb15e2bee17a34bfe5f21fbe4013313f7b836f3e3b708ebe9cdc2f3f8b500cbf3f52e4beb9fc07bfd044b83e1d5a443fa089703e57ec8fbe77d82c3fd90810bf268de1bea167d3be5ddc363fd7a3103f7b836f3e053491bebf62253f529b18bfbf2cc5be394527bfc217363fc1a8843e6666663e3d2c94be69e3203f32731dbff967bebe492effbe516bfa3eab3e373fe63f643eabcf95be60031e3f844620bf478fdfbec4b13ebfddb504bec286273fe9b76f3ed0d596be0bd31b3f026222bf0a12e3be348017bf0681153e63ee4a3f7cf2703e6b9a97bed2a6163fb03927bf7dd0e3be6dc5debefed4083f637f393fc898bb3d787a25be69e3203f32731dbff967bebe492effbe516bfa3eab3e373f3d2cd43d55c128be0bd31b3f026222bf0a12e3be348017bf0681153e63ee4a3fec2fbb3d67d527be4f23113fc4242cbfb667bebef016e8beb9fc073fab3e373f3d9bd53df77520bed5cf133fcac029bf3e94e0be0ebeb0be3b704e3fabcff53e5b42be3d8a1f23be0f0e0a3f94dc31bfcb2dc5be894100bf787a553f560e6d3e17b7d13d51da1bbe6e89f43ea2463dbfb667bebe13f2c1beaf94153f30bb373f1973d73d29cb10be5872fd3efa293abf772de1bebe3029bf99bb163bc520403f371ac03d4faf14be65fff43e5ef53cbf13f0e3be1d38e7bef628bc3ec520503fed0dbe3de09c11be5bd0eb3e58e43fbfe927e4bec520b0be4d151c3f42cf363fa4dfbe3df2d20dbe5f79e03e0a4c43bf85eddfbe022b87be07f0563fe10bf33eca54c13dbada0abe191cc53e35b34abf3c68bebee4149dbe5bb11f3f4703383facadd83d128300be7846c33e191e4bbfbf2cc5beaeb6a2be302a693f79e9863ecff7d33d371a00beb43aa13e8a9052bfadf8debe24b90cbfcac3023ef853533f38f8c23d43ade9bdd3a58f3e48e255bfb667bebe6de75b3ff6289c3eea95d23e19e2d83df628dcbde3a98f3e25b055bf7afbe3be6900cf3e32e6ee3e9e5e493f5bb1bf3dd200debd49bd073e61c175bfa307bebef31f32bf0ad723bdde93373ffc18b33e2fddc4be4d483b3e327577bfd767bebe197357bee3c7283f70ce383fce88b23ec05bc0be86015b3e61c175bf4fe8bdbe423e28beae47313f0ad7333ffc18b33ef697bdbe49bd073ea27a77bfd767bebe613215bfd5e71abfbada0a3fb37bb23e2fddc4bec7113b3e6478783fd767bebeadfa5cbeb07228bfacad383fce88b23eab3eb7be49bd073ed3c1763ff703bebe2eff31bfe7fb293d14ae373ffc18b33e3cbdb2be43015b3ed3c1763f4fe8bdbe795828be204131bf0ad7333ffc18b33e7502babe49bd073e247b783fd767bebe265315bf431c1b3ffa7e0a3fb37bb23e3cbdb2be78d3c53ecc60683fd767bebe7cf290be70ce28bf4547323f5bd37c3e89d29ebe40a48f3e0057723faf08bebebd5256be5bb12fbf6154323f30bb873e3f579bbe8fa58f3e40f6563fb667bebe234a5b3fffb29bbeb459d53e4bc8873e8104a5be29ccc33e2d094c3fb667bebe637f99be6e3420bfec51383fdb8a7d3e70cea8be1e37ec3e46d0403f9467bebe8863bdbe4bc817bfe71d373fb29d6f3eb1bfacbe2385fa3e143e5b3fd767bebe35efb8be295c1fbfa5bd313f9fcd6a3e6688a3bebad8083ff3e7333f9467bebe5986d8bea4700dbf82e2373fe5d0623e3c4eb1bed270163f726f4a3ff967bebe234adbbe6a4d13bf7c61323f2c65593e9e5ea9beb4581a3fdbe0243f9467bebed95ff6bea5bd01bfe71d373f99bb563e2b87b6beabd0283f1d383b3fd767bebeec51f8be394507bf7c61323f96b24c3ec4b1aebe508b293ffe0a153f3c68bebe143f06bf5530eabef5db373fba494c3eda1bbcbe47723d3f643c263fd767bebe96430bbf3c4ef1be17b7313f36ab3e3e4f1eb6be912a363f0116053f7367bebe7cf210bf2a3ad2be94f6363f4a7b433e8ab0c1bee00f433faf27e23eb667bebeac8b1bbf85ebb1bed0d5363fdaac3a3e39b4c8be42ce4f3f452c0e3fb667bebe67d517bfd734cfbe0e2d323f6ade313ee483bebedb6d4f3f6536b03e5167bebe0f0b25bf68b38abe22fd363f0e2d323eaa82d1be6c405c3f4390f33ed767bebe378921bf7b83afbe8126323f6744293ee6aec5be88f3683f8979be3ed767bebe0c022bbf03098abec58f313f65aa203e6900cfbee203573fc0e9853ed767bebedfe02bbf2a3a52be304c363fe8d92c3e19e2d8bee0835b3f5a104a3e3c68bebe849e2dbf07ce19be992a383fb1e1293e36abdebe747e723fe0f6843ed767bebe736831bf006f41bef31f323f75021a3e8716d9bee221603feae8b83df967bebe6dc52ebf053491bdc7293a3f79e9263e5e4be8be4da0783f45da163ed767bebe107a36bf1826d3bde09c313fe2e9153ea52ce3be8b6c7b3fca51003bd767bebeacad38bf52499d39ae47313f0612143efb3af0be1afd603f0341003bd767bebe4a7b33bf6f1283ba2b87363f8bfd253efb3af0be0070603ffa997abdb667bebe530533bf143f463d4694363f6666263ee2e9f5be2ca0783f80d812bed767bebe9d8036bfce88d23d5396313fe2e9153e5249fdbe747e723ffdf582bed767bebee56131bf2506413e0e2d323f75021a3e46b603bfb37b5e3fa56504bed767bebea5bd31bfd0b3d93d8638363f30bb273ebf0efcbe5c735b3f5b0747be1b68bebe12a52dbf52b81e3e82e2373fb1e1293e7cf200bf88f3683ff17ebcbed767bebed5e72abf95d4893e8ab0313f65aa203ec7ba08bfe203573fbce883bed767bebea3012cbf2a3a523ef931363fe8d92c3eefc903bfc572533f933a99bed767bebe50fc28bf0f0b753ea245363f0e4f2f3e58a805bf573d5c3f3b8cf1bed767bebeca5421bfbb27af3e2575323f6744293e166a0dbff75b4f3fe52aaebed767bebee6ae25bf569f8b3e143f363fd712323e348007bf226c483f2fa7ccbe3c68bebeb22e1ebfdcd7a13e5e4b383f22fd363ec7290abf42ce4f3fae280dbfd767bebe6b9a17bf295ccf3e6154323f6ade313e09f910bf59de3d3f7976f1bef967bebe545217bff706bf3ecb10373f5b423e3e88630dbfc9733d3f863d25bfd767bebed5e70abf21b0f23e3789313f36ab3e3ed42b15bf9433303f5ef60bbfd767bebe0d710cbf3f35de3e07f0363f14ae473e9cc410bf2a1d243f0d8a3ebfb667bebece88f2bebada0a3f6ea3313fa913503e27a019bf69e3203f32731dbff967bebe492effbe516bfa3eab3e373f2a3a523e0ad713bf4f23113fc4242cbfb667bebef016e8beb9fc073fab3e373fe4145d3ef46c16bfd0b80c3fe96150bfd767bebe7b83cfbe1973173f0a68323f1b0d603e3fc61cbf6e89f43ea2463dbfb667bebe13f2c1beaf94153f30bb373fe8d96c3e2c6519bf1abff03ebddf5cbfd767bebe45d8b0be0ebe203f5c8f323f7b146e3edbf91ebf4fcdc53e246167bfd767bebee9b78fbe19e2283f2575323f5bd37c3eb7d120bf191cc53e35b34abf3c68bebee4149dbe5bb11f3f4703383fff217d3ea8c61bbfd3a58f3e48e255bfb667bebe6de75b3ff6289c3eea95d23e4bc8873e48bf1dbf2ba58f3eb85871bf390cbebe863856beceaa2f3f6154323f30bb873e5c8f22bfb43aa13e8a9052bfadf8debe24b90cbfcac3023ef853533f0a68623f29ed3dbfdba38f3e45da52bfa5bfdfbeccee293f64cc0dbf65aa003fd34d623fd26f3fbfe3a98f3e25b055bf7afbe3be6900cf3e32e6ee3e9e5e493f6519623f8d973ebffda38f3e251f4bbf382cc5bed8f0743f89d2debaa1d6943e86c9643f44693fbf7846c33e191e4bbfbf2cc5beaeb6a2be302a693f79e9863e86c9643fd5e73abfdba38f3e08cd42bfff25e1be9318243f4850fc3e62a1163f12a5bd3dadfadcbdfda38f3e251f4bbf382cc5bed8f0743f89d2debaa1d6943e85ebd13df628dcbdfda38f3e8e1f3ebfe09de4bed712223f62a1d6bca60a463fc1a8643f07ce99bddba38f3e08cd42bfff25e1be9318243f4850fc3e62a1163f5dfe633f75029abd5bd0eb3e58e43fbfe927e4bec520b0be4d151c3f42cf363f3d2c643fa16733bd5872fd3efa293abf772de1bebe3029bf99bb163bc520403fd42b653f76e01cbdfda38f3ebe2f3abf921fe1beb81e253f4ed101bf6154123fef38653f07ce99bd65fff43e5ef53cbf13f0e3be1d38e7bef628bc3ec520503f86c9643f787a25bd5f79e03e0a4c43bf85eddfbe022b87be07f0563fe10bf33e0ad7633f371a40bdfda38f3e7fdd31bf382cc5be86c9743f17b7513aabcf953e2bf6173fc0ec1ebdfda38f3ebe2f3abf921fe1beb81e253f4ed101bf6154123ffa7e1a3f091b1ebd5872fd3efa293abf772de1bebe3029bf99bb163bc520403f6c781a3f08ac9cbd0f0e0a3f94dc31bfcb2dc5be894100bf787a553f560e6d3e2bf6173f0d71acbdfda38f3e3fe029bf2d5ae0bec7ba283f234a0b3f4bea043f7e1db83d1ac0dbbdfda38f3e7fdd31bf382cc5be86c9743f17b7513aabcf953e832fcc3d63eedabdfda38f3e6f4827bfd3bce3be992a283f5b427e3e143f363fb537b83e9cc4a0bdfda38f3e3fe029bf2d5ae0bec7ba283f234a0b3f4bea043f3480b73e9cc4a0bdd2a6163fb03927bf7dd0e3be6dc5debefed4083f637f393fd044b83e82e2c7bcfda38f3e726d24bfbf44e4bea2b4273ffed4f8bd32e63e3ff54ab93e9cc4a0bdfda38f3e26fe20bf9b38e1be4260253f539601bfb840123fc729ba3e9cc4a0bd0bd31b3f026222bf0a12e3be348017bf0681153e63ee4a3fd0b3b93e7a36abbcd5cf133fcac029bf3e94e0be0ebeb0be3b704e3fabcff53efe65b73ef4fdd4bcfda38f3e26fe20bf9b38e1be4260253f539601bfb840123f8941503ffdf6f5bd0bd31b3f026222bf0a12e3be348017bf0681153e63ee4a3fe02d503fe3c798bd60031e3f844620bf478fdfbec4b13ebfddb504bec286273fc05b503f23db79bdfda38f3e959b18bf382cc5bea69b743f17b751b907f0963ee5d0523f228ef5bdbf62253f529b18bfbf2cc5be394527bfc217363fc1a8843ee5d0523f8a8e64bdfda38f3e223610bfbc40e1bec217263faf25043f2d210f3f39d6a53ea52c03befda38f3e959b18bf382cc5bea69b743f17b751b907f0963e80b7a03e6f1203bebf62253f529b18bfbf2cc5be394527bfc217363fc1a8843e499da03e01de82bd77d82c3fd90810bf268de1bea167d3be5ddc363fd7a3103f02bca53e2b1895bd9cdc2f3f8b500cbf3f52e4beb9fc07bfd044b83e1d5a443fef38053f6a4df3bcfda38f3e66670dbfe206e4be7958283f8b6c273e2d433c3f6132053f840dcfbd77d82c3fd90810bf268de1bea167d3be5ddc363fd7a3103f6abc043f499d00bde928333f5a1208bfdb15e2bee17a34bfe5f21fbe4013313f54e3053f1cebe2bcfda38f3ee3550abf8ae4e3be1058293f35ef38bea8573a3f58a8053f840dcfbdfda38f3e97e107bfdf6ee1be6666263faf2504bffbcb0e3f8bfd053f840dcfbdfda38f3e223610bfbc40e1bec217263faf25043f2d210f3f6abc043f840dcfbde928333f5a1208bfdb15e2bee17a34bfe5f21fbe4013313f4b59863b3480b7bdfda38f3e57b3febe382cc5bee17a743f52491d3ad9ce973ef775603c8b6c27befda38f3e97e107bfdf6ee1be6666263faf2504bffbcb0e3ffaed6b3bc28627be3b6d393f57b3febe382cc5be5bd33cbf05c51f3f5dfe833ef775603c0e4fafbd3b6d393f57b3febe382cc5be5bd33cbf05c51f3f5dfe833ea01a0f3fecc0b9bdfda38f3efeeeedbe4834e1bea60a263f26e4033fd26f0f3f5396113fb1bf2cbefda38f3e57b3febe382cc5bee17a743f52491d3ad9ce973ebb270f3f7aa52cbed4b93e3f1780eebe7a8ee0bef241efbebbb83d3fd0d5f63e6ea3113fea95b2bdfda38f3efeeeedbe4834e1bea60a263f26e4033fd26f0f3fd5e75a3f4013e1bdd4b93e3f1780eebe7a8ee0bef241efbebbb83d3fd0d5f63e63ee5a3f9a771cbd942f403f2bbee9be09a9e3becb1007bfcc7f083ff54a293f431c5b3f75021abd6f4a413f5e0fe6be9851e4be50fc18bf1dc9853e4a0c423f917e5b3f99bb16bd1ea48f3e7828e2be5ce3e3be3ee8293f5c2041be8351393fdfe05b3ffdf6f5bdfda38f3e6de3e7bec827e4be10e9273f2d431c3ec4423d3fe86a5b3ffdf6f5bd7fc0433f5392ddbec503e2bee6ae35bf9d8066bed5e72a3fbf0e5c3f4ed111bdfda38f3e6e4eddbe4277e1bed95f263f41f103bf69000f3f11365c3ffdf6f5bd7fc0433f5392ddbec503e2bee6ae35bf9d8066bed5e72a3f0d711c3f575bb1bdfda38f3e0a30ccbe382cc5bec66d743f17b7d139b537983ef7061f3fb29d2fbefda38f3e6e4eddbe4277e1bed95f263f41f103bf69000f3f0d711c3fe9b72fbe1268483f0a30ccbe382cc5be4df34ebfadfafc3eefc9a33ef7061f3f3199aabd1268483f0a30ccbe382cc5be4df34ebfadfafc3eefc9a33eac8b4b3f02bc25bffda38f3ef915bbbecfa0e1be41f1233f51dafb3e22fd163f96214e3fd6563cbffda38f3e0a30ccbe382cc5bec66d743f17b7d139b537983e3a924b3fba493cbff9844c3fb325bbbee469e1be6f8104bf80482f3f865a033f091b4e3f9d1125bfbe6a4d3f6f29b7be7ba2e3be1d5a14bfa4dfde3e4e62303f211f343fd578e9bcfda38f3e2507b4bea147e4bebe30293ff241cf3d925c3e3f1d5a343ffdf6f5bdfda38f3ef915bbbecfa0e1be41f1233f51dafb3e22fd163fb3ea333ffdf6f5bdfda38f3e77a3afbe8ae4e3bede712a3fd0d556bec74b373f6abc343ffdf6f5bdaabb4e3f8a21b1be963ee4bef85323bff697dd3da52c433f8a8e343f1cebe2bc8813503fadbeaabe6e88e1be6b9a37bf8ab0a1bef7061f3f0f0b353f4faf14bdf9844c3fb325bbbee469e1be6f8104bf80482f3f865a033f7dd0333fb1bfecbcfda38f3e9a79aabe9432e1be62a1263fb45905bf88630d3f2b18353f4013e1bd8813503fadbeaabe6e88e1be6b9a37bf8ab0a1bef7061f3f7ffbda3e0b24a8bdfda38f3e36ac99be382cc5bee17a743f52499db9d9ce973ec9e5df3e46b633befda38f3e9a79aabe9432e1be62a1263fb45905bf88630d3f9a08db3e7dd033be315e533f36ac99be382cc5be80b760bf0a68c23e3d9b953ec9e5df3e9ca2a3bd315e533f36ac99be382cc5be80b760bf0a68c23e3d9b953e0f0bf53e5452a7bdfda38f3e7eac88bee066e1be1904263f8195033f77be0f3fe336fa3e58a835befda38f3e36ac99be382cc5bee17a743f52499db9d9ce973e2b18f53e228e35be9d46563f2eab88be5c71e1be32770dbfd1221b3fb37b123fac1cfa3e9ca2a3bd1d76573f1afd80be1151e4be000020bf567d6e3e52b83e3fcd3b2e3f8f53f4bdfda38f3e033f7abe29e7e3be151d293fd95f36be68b33a3f7fd92d3fc364aabc1ea48f3e035d83beb5fde3be029a283f0b24283e16fb3b3f764f2e3fc364aabcfda38f3e7eac70beb280e1be107a263f98dd03bf4df30e3fbe9f5a3fd26f3fbf193c583fec6977be3f8de3be44fa2dbf3cbd12be2731383f158c5a3fa60a26bf28f2583fb7416dbefcabdfbe96433bbf5a64dbbe4bc8073f83c05a3f8bfd25bf9d46563f2eab88be5c71e1be32770dbfd1221b3fb37b123f52b82e3f8fe4f2bdfda38f3e7eac88bee066e1be1904263f8195033f77be0f3fe0be2e3fc364aabc193c583fec6977be3f8de3be44fa2dbf3cbd12be2731383f9fab2d3fb459f5bdfda38f3e7eac70beb280e1be107a263f98dd03bf4df30e3f4d842d3fc364aabcfda38f3ed2514ebe382cc5be34a2743f89d2de3a07f0963ea8355d3f44693fbfc8ea5a3fd2514ebe382cc5be304c66bf11c7ba3c643bdf3ea8355d3f3d9b25bfc8ea5a3fd2514ebe382cc5be304c66bf11c7ba3c643bdf3ea5bd513f3d9b25bffda38f3e74442ebe8461e0be7ac7293fc4420d3f8e75013f7446543fd26f3fbffda38f3ed2514ebe382cc5be34a2743f89d2de3a07f0963e33c4513f44693fbfb4af5c3fa0c62dbe289de0beccee19bfd6c51d3f9c33023fe63f543f265325bf43395d3f149422bee319e4beceaa1fbf713d8a3e36cd3b3f6154923e17b7d1bcfda38f3e707726bea229e3be3f572b3f6519a23e4d152c3fbc05923ea52c03beb4af5c3fa0c62dbe289de0beccee19bfd6c51d3f9c33023fc58f913e3d2cd4bcfda38f3ef0fd1dbe624ae4bec8982b3fdcd7813d52493d3f3cbd923ea52c03befda38f3ef73e15bed1e7e3be48e12a3f4df34ebea779373fbc74933ea52c03be35ee5d3ffd6612be338ce3be11c72abf7ffb3abea7e8383f98dd933e3b70cebcfda38f3e5da50bbe4582e1be8273263f61c303bf12140f3f3d2c943ea52c03befda38f3e5da50bbe4582e1be8273263f61c303bf12140f3f029ae83e234a3bbe35ee5d3ffd6612be338ce3be11c72abf7ffb3abea7e8383f39b4e83e7c6192be47565e3f7e3808be47aadfbe66f734bf6c09f9bebc74033f5e4be83e986e92befda38f3e74442ebe8461e0be7ac7293fc4420d3f8e75013faa82913ea52c03befda38f3e5794d2bd382cc5be3867743f52499d39d044983e2f6ee33e5a643bbe45655f3f5794d2bd382cc5becff773bf3789013ee8d98c3e1361e33e8fe492be45655f3f5794d2bd382cc5becff773bf3789013ee8d98c3e33c4d13eec2f3bbefda38f3e2fa58ebd8464e1be6b2b263f41f1033f643b0f3f07f0d63e8fe492befda38f3e5794d2bd382cc5be3867743f52499d39d044983e4ed1d13e58ca92be8d44603f240a8dbd90d9e1be32e61ebf3867043f5ddc163f4694d63e48e13abec2a4603fdc6840bd11e5e3beba492cbfc74b37bea2b4373f3cbd723ff7e461bffda38f3e29b040bd82e6e3bef90f293f7ffb3abede713a3f58ca723fcb1047bfa983603f210163bd0550e4be598628bf6f12033e9be63d3fa52c733f6ade61bf3fc6603fe76e17bd4b3fe1be55302abf2497ffbe5b420e3f2575723ff7e461bffda38f3e3acf18bdc05ee1be6666263f6f8104bfc9760e3f2575723fcb1047bffda38f3e419d72bdab07e4be075f283f67d5273e9f3c3c3f4f40733fcb1047bf8d44603f240a8dbd90d9e1be32e61ebf3867043f5ddc163f46b6733ff77560bffda38f3e2fa58ebd8464e1be6b2b263f41f1033f643b0f3f46b6733fcb1047bf3fc6603fe76e17bd4b3fe1be55302abf2497ffbe5b420e3fa2b4273f6a4d93befda38f3eff5888bb382cc5bee17a743f17b7d13810e9973e6132253f5a643bbefda38f3e3acf18bdc05ee1be6666263f6f8104bfc9760e3f4bc8273f234a3bbedbfb603fff5888bb382cc5be39d675bfd3de603d31088c3ed42b253fbc7493befda38f3eff5888bb382cc5bee17a743f17b7d13810e9973ee258b73e58ca92bedee3603f40dbea3c073fe1beb1e129bf4013013fbf7d0d3fda1bbc3e355e3abefda38f3e9413ed3c425ce1be4b59263fc66d043f8d970e3f2d43bc3e8fe492bedbfb603fff5888bb382cc5be39d675bfd3de603d31088c3ec74bb73ec7293abe2bd9313dd6e3debe6189cfbe6f12733f20632ebe94f6863ed712f23d90a038bf1c0c353df371e5be1476d9becdcc7c3fae4721bea69bc43b6adef13dc3f538bf1840383d336ee2beed4ad3be5dfea33ef85353bf29eded3efca9f13d39b438bf1840383d6136d9be4eb7ccbe88639d3e2f6e83bea3926a3f492eff3decc039bf2bd9313dd6e3debe6189cfbe6f12733f20632ebe94f6863e92cbff3dc7293abf1840383d336ee2beed4ad3be5dfea33ef85353bf29eded3e492eff3d516b3abf4209333d0d8ed2be53cacbbe04e77c3f52499d3be4831e3e2497ff3d835139bf1840383d9c6ec1bea88cd7bef163ac3eab3e673ff016883edaacfa3decc039bf2bd9313d9fe3c3be0e4bd3bee63f743fae47613ea913503eb515fb3d910f3abf1aa6363d4cdec8be43c7cebe7b837f3ff2414f3d287e0c3d48e1fa3d516b3abfa33f343de202c0beb41edebe57ec7f3fa4dfbe3c9fab2dbcb515fb3df54a39bf12303a3b9cf9e5bebbd2e2be0bb5463f098a1fbfcac3c2bdd200de3d7e8c39bfdec7d13b0681e5be08abd9beaa6044bf92cb1fbf87a7173eadfadc3dc3f538bf12303a3ba14ae5bedffad8be4f40433fa3011cbf9be65d3ed200de3da7e838bfdec7d13b14cbe5be129fe3becb1047bfb1e119bf24b93cbeadfadc3d27a039bf12303a3bc764e1be7aaaebbe2575523ffaedebbe7a36abbed200de3d55303abfdec7d13bd6e3debec85beebec3f558bf0000a0bee3a5dbbe1b2fdd3da8573abf12303a3b6136d9bedc2df1be5bd35c3ff6971dbe99bbf6be3f35de3d88853abfdec7d13b1ec0d2be2827f2be295c4fbf6f12033add2416bf6519e23d4c3739bfdec7d13bd6e3debec85beebec3f558bf0000a0bee3a5dbbed34de23d39b438bf12303a3b6136d9bedc2df1be5bd35c3ff6971dbe99bbf6be8a1fe33dfed438bf12303a3b1d94d0bec5c5f1bee258473fe3a51b3e36cd1bbf8a1fe33d105839bf12303a3befadc8bef20befbec286473f764f9e3e917e0bbf8a1fe33d3ee839bfdec7d13ba800c8bed995eebec28647bf2653c53e76e0fcbe6519e23d5af539bf12303a3b2a1fc2be508de7bed34d523f74b5f53e12a59dbe8a1fe33d516b3abfdec7d13b6687c0be193ce4bec3f558bfbc74033f835109bed34de23dde713abf12303a3b3cfabfbed9ebddbe96b25c3fe561013fe2e9153d423ee83d835139bfdec7d13b6687c0be193ce4bec3f558bfbc74033f835109be8b6ce73dc3f538bf12303a3b2a1fc2be508de7bed34d523f74b5f53e12a59dbeb072e83d70ce38bfdec7d13b9c6ec1bea88cd7beb07258bf1214ff3e14d0443e1d38e73decc039bf12303a3bc1e3c3be0e4bd3be0b24583f7424d73e8c4aaa3e423ee83d910f3abfdec7d13befadc8be17d9cebe643b4fbf8351a93e075ff83e8b6ce73d516b3abf12303a3bdeafcabe91f0cdbed0d5463f64cc5d3efe65173fb072e83d88853abf12303a3b1ec0d2bee0bdcbbe20634e3f27a009bb1973173ffb5ced3d835139bfdec7d13befadc8be17d9cebe643b4fbf8351a93e075ff83e448bec3d8cdb38bf12303a3bdeafcabe91f0cdbed0d5463f64cc5d3efe65173ffb5ced3dfed438bfdec7d13b1d94d0be641fccbea2b447bfac1c1a3e03781b3fd656ec3d302a39bfdec7d13b6136d9be4eb7ccbe5bd35cbff6971dbe99bbf63ed656ec3decc039bf12303a3bd6e3debe6189cfbe3e79583f764f9ebe6dc5de3efb5ced3dc7293abfdec7d13bc764e1beb03ad2bebb274fbf8e06f0be984cb53e448bec3d355e3abfdec7d13bc764e1beb03ad2bebb274fbf8e06f0be984cb53e1b2fdd3d90a038bf12303a3bd6e3debe6189cfbe3e79583f764f9ebe6dc5de3e3f35de3d90a038bf1840383d9c6ec1bea88cd7bef163ac3eab3e673ff016883e7e1db83d713d3abf1c0c353df1f5e5bea1a2e2be3bdf7f3f93a902bd11c7ba3bc364aa3d956538bfa33f343de202c0beb41edebe57ec7f3fa4dfbe3c9fab2dbcec2fbb3d07ce39bf1c0c353df371e5be1476d9becdcc7c3fae4721bea69bc43be78ca83dfed438bf1840383d336ee2beed4ad3be5dfea33ef85353bf29eded3e7958a83d2c6539bf1aa6363d4cdec8be43c7cebe7b837f3ff2414f3d287e0c3dea95b23dfa7e3abf1840383d6136d9be4eb7ccbe88639d3e2f6e83bea3926a3f3199aa3d910f3abf4209333d0d8ed2be53cacbbe04e77c3f52499d3be4831e3ee948ae3d516b3abfbcb0353d38f5c1be643be7bed3dee03d0bb5763fd93d79bea301bc3d105839bfc91d363d69c3e1be7959ebbe0000803fac8bdbbb6c09f93ac4b1ae3dd50938bfbcb0353dda55c8beedd2eebe0000803f3480373bfaedebbaec2fbb3d39b438bfb797343d8d62d9be4013f1be72f97f3f4b5986bbc6dc35bcc5feb23d9eef37bfbb47363d0d8ed2beb51af2be20d27f3fa69b44bc4d150c3dc74bb73d0b2438bf3221e6bce8bdd9beab92d8beb6f37dbf11c7babd7dd0b33da8574a3da2b437bf10afebbcea23d8be43cadfbe295c7fbf4d848dbd348037bcf085493d273138bf7ae1cebc33c2e3be321cdfbed9ce77bff77580be24977f3b363c3d3d2bf637bfc4ccbebc3bc3e4beea93d4be6f1273bf03788bbe643b1f3e5bb13f3dab3e37bfb68391bcdb31edbe361fd7be60e560bf8c4aeabe287e0c3ea245363d394537bff855393b7710f3be12fac9beddb5e43e772d41bf3411f63e8195c33dc3643abf4cc205bb3411f6be33ddd3bea9a4eebed95f56bf0e2d923e8126c23d07ce39bf0805253b4529f9be69c6dabe0d71cc3eba6b69bf1361c33dcac3c23d477239bf7ac6bebbde8ef0beae9ecbbe0d712cbf77be1fbfbadaca3e8126c23dfe433abf2e3803bcccd1f3be1d04d5be70ce38bfd0b329bf5f294b3eeeebc03d43ad39bfa9d983bc21b0eabe54e6cebe713d5abf6dc5debeaa60943e11c73a3db4c836bf2e3803bcccd1f3be1d04d5be70ce38bfd0b329bf5f294b3ec520303d3d0a37bf7ac6bebbde8ef0beae9ecbbe0d712cbf77be1fbfbadaca3ec6dc353d9d8036bfeed030bcb570e9be2651c7bea69b44bff775e0be840def3eed0d3e3d304c36bf425d24bbcf4bedbe88a1c5bec74b07bf07ce19bf7e8c193f11c73a3d6b2b36bfe2ab9dbceb01e3bee4f3cabe62a166bf70ce88bea01aaf3eca32443df0a736bfc5e7cebc2ffadabead16d0be508d77bfb4c8f6bde6ae653e83c04a3de71d37bfa6d5103b03d2debe93a9babe3945c73e9fab8dbeeeeb603ff46cd63d19e238bfd35023bba5d7e6be9832c0bee3361abfd122dbbe9a772c3f1973d73d0c9339bf840e3a3b23dde7be8ee8bebe0534f13e7d3ff5be849e3d3f1904d63d27a039bfe5f1b4bbc345d6be47aebbbe780b24bf7c61b2bddc46433ff5dbd73db07238bf0ef53bbc2192e1bed027c2beaf9445bfcc5d8bbe8a1f133facadd83d674439bf0ef53bbc2192e1bed027c2beaf9445bfcc5d8bbe8a1f133fa779473d4f1e36bfe5f1b4bbc345d6be47aebbbe780b24bf7c61b2bddc46433f17b7513dfdf635bf18937ebc3124d7beda1dc2be0bb556bfc217a6bd23db093ff31f523d143f36bf0d6eebbcf584cdbe67d7ddbe0e4f7fbfe2e9953d1ea7e83b3d2c543d423e38bfc79fa8bc691cdabe9a07c8beb1506bbf2428febdb762bf3ea8354d3d62a136bfac56a6bc6727d3be57b3c6beac8b6bbfe02d103cb072c83ecf66553dd49a36bfb151d6bc70d1d1bec405d0befe437abfbc74933c62a1563e61c3533d394537bf191d10bc2577d0bed977bdbe44fa3dbff2414f3d431c2b3f3ee8593d341136bfc2a1b7bb4488cbbeeb70bcbeacad28bf0de02d3ec8983b3f6210d83dbec137bf840e3a3b5c3bd1beb2d6b8beb98de63e0c93293d8f53643f1904d63dd50938bfcff8bebc7b49cbbe2f33ccbe2a3a72bff0a7063ee258973e645d5c3de71d37bf933a81bce6ceccbe27d9c2be910f5abfeeeb003e9c33023f645d5c3d107a36bfc2a1b7bb4488cbbeeb70bcbeacad28bf0de02d3ec8983b3fd3de603d143f36bf4c88393befc5b7be3cdbc3bea392da3ee6ae253ffca9213f1904d63db00337bfbe831fbbfdbbbebe69adc0bebd5206bf0309ea3e67d5373fab3ed73d022b37bfd2aa163bcec2c6bea0e0babe1826d33eb8afa33ec3645a3f8638d63de25837bf32aeb8bb6de1b9be6150c6be7b832fbf2653053f9c33023fb1bf6c3d94f636bf83320dbcc4d0c2be5473c1be151d39bf6f81a43eb6841c3fb003673d2b8736bfbe831fbbfdbbbebe69adc0bebd5206bf0309ea3e67d5373f68b36a3dd49a36bf8a937bbc63eec2be8789c6be075f58bfe3c7983e6f12e33e4260653d5ddc36bffcdea6bcf452c1bee44dcebed0b369bfa323993ecd3b8e3e4260653dfe6537bf25cadebce430c8be7fbfd8be8cb97bbfe561213ec729ba3dd1225b3db9fc37bfd2aa163bcec2c6bea0e0babe1826d33eb8afa33ec3645a3ff853633df93136bffb9378bb56f0b3beb308cdbe4df30ebf6c783a3f9643cb3ef2b0d03d4c3739bf32aeb8bb6de1b9be6150c6be7b832fbf2653053f9c33023f60e5d03d07ce39bf4c88393befc5b7be3cdbc3bea392da3ee6ae253ffca9213f17d9ce3db1e139bf9e422ebc6500b8be37e3ccbe42cf46bf4f1e063f3333b33ed7346f3da77937bf6500a8bc7e1ebbbe5a2fdebe0c026bbff1f4ca3efaed6b3cb003673d598638bf3d27bdbc0d1cc0be5019d7be21b072bf1973973e20d2ef3d8a8e643d470338bf2e008dbc79eab9be0725d4be000060bf41f1e33e93a9423e1f856b3d10e937bf27663dbcf415b4bea818d7befdf645bf711b1d3f8a1f233e696f703d273138bf4d4b2c3b787daebe1d1cd4be9790cf3e1b0d603f022b873e840dcf3d029a38bfa721aabb0ebbafbe645ddcbe560e1dbfd0b3493ff5db573d60e5d03d0b2438bf27663dbcf415b4bea818d7befdf645bf711b1d3f8a1f233e17b7d13dacad38bf840e3a3b5377adbeb3d2e4bef9a0e73ebc05623f250601be840dcf3dde9337bf11e420bb198eafbed11ee7be0c020bbf6dc54e3fe86a6bbe857cd03da77937bffca5c5bcb4e9c0be5303e5be14d074bf5f078e3e363cbdbd1b0d603da7e838bf5a4b81bccf69b6be3eede0be835159bf3d0a073f022b07bdfaed6b3d70ce38bfa721aabb0ebbafbe645ddcbe560e1dbfd0b3493ff5db573dfca9713d1ea738bfb8af83bc268ab8be9a40e9be48505cbf61c3f33e105839be8b6c673df54a39bf9eea10bcb56bb2bedb85e6be645d3cbfacad283f1b0d20befbcb6e3dd93d39bf7d5b303bc0b0b4be1d3df6be857cd03e9d80363f812612bf840dcf3db00337bf6f9b29bb6e6bb3be70cef0be39d605bf5474443fed0dbebe857cd03dcb1037bf45f5d6bca46fcabe3e21ebbe302a79bf5ddc063e128340be1895543d302a39bf97c79abcb116bfbecc46efbe341166bf711bad3e4df38ebe643b5f3d9a9939bf4e292fbc41f5b7be4303f1be0a6842bf19e2083f64ccbdbef931663d95d439bf705e1c3bdee8c3beea0301bf7a36cb3eef38c53e0b4655bf39b4c83d8cdb38bffd4cbdbb01fabdbe6424fbbe97ff20bfa167f33e32771dbfcceec93d105839bf7d5b303bc0b0b4be1d3df6be857cd03e9d80363f812612bff016c83d910f3abf6f9b29bb6e6bb3be70cef0be39d605bf5474443fed0dbebe8c4a6a3d23db39bf7d5b303bc0b0b4be1d3df6be857cd03e9d80363f812612bf1dc9653d3a233abfabeae5bc670dd6be8fa5e7bed2007ebf09f920bdd712f2bdccee493d39b438bffd4cbdbb01fabdbe6424fbbe97ff20bfa167f33e32771dbff6975d3d8c4a3abf18cf20bc5034c7be66bcfdbe7fd93dbfc7297a3e57ec1fbfaaf1523d516b3abfc97292bcb91bc4be397df5be053461bf67d5873e1e16cabeacad583dccee39bf18cf20bc5034c7be66bcfdbe7fd93dbfc7297a3e57ec1fbf158cca3d1ea738bfe2e8aabcb0e8cebe2923f6be77be6fbf772da13d4df3aebe849e4d3d95d439bffce2d2bc548cd3be83faeebe9eef77bfa69bc4ba492e7fbea8574a3d674439bf8b178bbc0723cebec059fabe9fab5dbfecc0b93d6de7fbbea8354d3dac1c3abfb87622bb569dddbe62f800bf0d710cbf66f784be76714bbf8351c93d903137bffdf9b6bb21e4d4be971e01bf394527bf4850fcbce09c41bf5ebac93d30bb37bf840e3a3b6eded8be5c5502bf2731e83e4694f6bdd71262bf82e2c73d8b6c37bf4b1f3a3be7e4e5bec80800bf5452e73ef54ad9be19e248bf82e2c73db4c836bffc7010bb3f8ce8be03b1fcbe3e7908bf865a03bf11362cbf8351c93d42cf36bfccf01fbcb48fe5bee65cfabe0e4f3fbf24b9bcbe4d840dbf34a2343dd0b339bf0f432bbc66dadebe637bfdbe857c40bf7c6172be4d841dbf11c73a3d03093abfb87622bb569dddbe62f800bf0d710cbf66f784be76714bbfa3013c3d8c4a3abffc7010bb3f8ce8be03b1fcbe3e7908bf865a03bf11362cbf0e2d323d7ac739bf952bbcbc098cddbe78eff0beee5a72bf8b6c27be7b148ebe5bb13f3dd93d39bfd00a8cbc3350e1be23a4f6be5bb15fbf02bc85bed712d2be355e3a3d7e8c39bff855393bc4e8f1beacc7f5bebe30d93e903137bf7b140ebf8195c33dc28637bffc7010bb3f8ce8be03b1fcbe3e7908bf865a03bf11362cbf13f2c13d07f036bf4b1f3a3be7e4e5bec80800bf5452e73ef54ad9be19e248bfa52cc33d99bb36bf7f8722bb37fcf6be0af8e5be197317bf835149bfc6dc35be8126c23d90a038bf9a443dbc0a48f3bebe4ddfbe6a4d43bf068125bf348037ba80b7c03dc3f538bff1121cbcd1e9f1becbbeebbe3fc63cbff77520bfd3de80be80b7c03d7e1d38bf12f66dbb93fff1be59daf1bebde314bfbde334bf0456cebe13f2c13d4bc837bfb37831bc8c9eebbe0a4bf4bee09c41bf3e79f8be65aae0bea089303dd93d39bf12f66dbb93fff1be59daf1bebde314bfbde334bf0456cebe9fab2d3d105839bff1121cbcd1e9f1becbbeebbe3fc63cbff77520bfd3de80be0d712c3de78c38bf12f66dbb93fff1be59daf1bebde314bfbde334bf0456cebec4422d3da32339bf8599b6bc7711e6bed00ceabe72f96fbf083d9bbed7342fbec7ba383d1ea738bf0d6fd6bc34d9dfbe0e4ce6befa7e7abf0f0b35be3ee8d9bd371a403dcc7f38bf096f8fbce8a2e9beac8ceebea91360bffa7ecabe3b708ebe7dd0333d19e238bfd8b87ebc359befbef7afe4be6c785abf0e2d02bf68b3eabdc520303d0b2438bfc1caa1bc91f2ebbe2b31dfbede716abf849ecdbe27a089bbeb73353dd9ce37bf9a443dbc0a48f3bebe4ddfbe6a4d43bf068125bf348037ba7b142e3d14ae37bfbdfd393b7adff8bee5f1e4be14aee73e736861bfbc9610be8195c33d39b438bff9812b3b6ceccabed02702bf840dcf3e14d0443e4bea64bff016c83d075f38bfcd2129bcdb88cfbe5f7effbe05c53fbfbec1973d598628bf158cca3d7e1d38bfbe831fbbfdbbbebe69adc0bebd5206bf0309ea3e67d5373f857cd03dfe433abf425d24bbcf4bedbe88a1c5bec74b07bf07ce19bf7e8c193f1973d73dac1c3abff855393b7710f3be12fac9beddb5e43e772d41bf3411f63e8638d63d516b3abf7ac6bebbde8ef0beae9ecbbe0d712cbf77be1fbfbadaca3e87a7d73d355e3abfeed030bcb570e9be2651c7bea69b44bff775e0be840def3e3e79d83d23db39bf191d10bc2577d0bed977bdbe44fa3dbff2414f3d431c2b3facadd83dd50938bf9e422ebc6500b8be37e3ccbe42cf46bf4f1e063f3333b33e17b7d13d637f39bffb9378bb56f0b3beb308cdbe4df30ebf6c783a3f9643cb3efca9713d87a737bf9eea10bcb56bb2bedb85e6be645d3cbfacad283f1b0d20beaa82d13dde9337bf11e420bb198eafbed11ee7be0c020bbf6dc54e3fe86a6bbed7346f3dd57839bf4e292fbc41f5b7be4303f1be0a6842bf19e2083f64ccbdbea857ca3db1e139bfcd2129bcdb88cfbe5f7effbe05c53fbfbec1973d598628bfa8574a3d516b3abf4ed191bcb1ded8bebf81f9be128360bf5a64bbbdaa82f1be5c8f423d23db39bffdf9b6bb21e4d4be971e01bf394527bf4850fcbce09c41bf1361433d355e3abf0805253b4529f9be69c6dabe0d71cc3eba6b69bf1361c33d04e70c3d58a835bfbdfd393b7adff8bee5f1e4be14aee73e736861bfbc9610be075f183d863836bf12303a3b9cf9e5bebbd2e2be0bb5463f098a1fbfcac3c2bd03090a3d742437bff855393bc4e8f1beacc7f5bebe30d93e903137bf7b140ebfe561213dc74b37bf12303a3bc764e1be7aaaebbe2575523ffaedebbe7a36abbe96210e3d30bb37bf4b1f3a3be7e4e5bec80800bf5452e73ef54ad9be19e248bf9c33223d075f38bf12303a3b6136d9bedc2df1be5bd35c3ff6971dbe99bbf6bedf4f0d3d956538bf840e3a3b6eded8be5c5502bf2731e83e4694f6bdd71262bf76e01c3dd93d39bf12303a3b1d94d0bec5c5f1bee258473fe3a51b3e36cd1bbf9565083d19e238bff9812b3b6ceccabed02702bf840dcf3e14d0443e4bea64bf2a3a123d75023abf12303a3befadc8bef20befbec286473f764f9e3e917e0bbf499d003da32339bf705e1c3bdee8c3beea0301bf7a36cb3eef38c53e0b4655bf27c2063dc3643abf7d5b303bc0b0b4be1d3df6be857cd03e9d80363f812612bf8e06f03cfa7e3abf12303a3b2a1fc2be508de7bed34d523f74b5f53e12a59dbeb1bfec3cbe3039bf840e3a3b5377adbeb3d2e4bef9a0e73ebc05623f250601becdcccc3cc7293abf12303a3b3cfabfbed9ebddbe96b25c3fe561013fe2e9153df5b9da3c19e238bf4d4b2c3b787daebe1d1cd4be9790cf3e1b0d603f022b873e5839b43c477239bf12303a3bc1e3c3be0e4bd3be0b24583f7424d73e8c4aaa3e3b70ce3c423e38bf4c88393befc5b7be3cdbc3bea392da3ee6ae253ffca9213f304ca63cb53738bf12303a3bdeafcabe91f0cdbed0d5463f64cc5d3efe65173f849ecd3c87a737bfd2aa163bcec2c6bea0e0babe1826d33eb8afa33ec3645a3f55c1a83c742437bf12303a3b1ec0d2bee0bdcbbe20634e3f27a009bb1973173fabcfd53c022b37bf840e3a3b5c3bd1beb2d6b8beb98de63e0c93293d8f53643f5839b43cf46c36bfa6d5103b03d2debe93a9babe3945c73e9fab8dbeeeeb603f5d6dc53cc6dc35bf12303a3bd6e3debe6189cfbe3e79583f764f9ebe6dc5de3efaedeb3c0bb536bf840e3a3b23dde7be8ee8bebe0534f13e7d3ff5be849e3d3f645ddc3c068135bff855393b7710f3be12fac9beddb5e43e772d41bf3411f63eb6f3fd3c426035bf12303a3ba14ae5bedffad8be4f40433fa3011cbf9be65d3e006f013d99bb36bfdec7d13bf2ed59bf2508a7be04562ebfb8af43be66f7343f7958a83e378941bf2bd9313dda3757bfbc24a6be9fab2d3f11c73abd8cb93b3f54e3a53ece1941bfdec7d13b0b6056bf1827a6be60762fbf0e4f2fbcc3643a3f7958a83e401341bfdec7d13bedbb5ebfa244abbe68222cbf3e79d8beac8b1b3f7958a83e454742bf06d6313d43c65bbf7617a8bef1632c3fb22e8ebe44692f3f6ff0a53e6ade41bfdec7d13b10e662bf89d1b3be0d712cbf3b011dbf3333d33e9565a83e01de42bf1fd8313d134560bfb3cdadbe32772d3f516bfabe5f980c3f6ff0a53e418242bf2bd9313db83f63bf65ffb4be68912d3f9cc420bf9ca2c33e54e3a53ee5d042bf2bd9313dc32c04bf70b4b3beca54413f2e90a0bdd49a263f54e3a53e107a36bfdec7d13bc32c04bf70b4b3bee56141bf0a68a2bd2b87263f9565a83e107a36bf2bd9313d8ffae3be2cd3a7bea8352d3ffca9f1bd1e163a3f39d6a53e26e433bfdec7d13b7922e8be3be2a8bec52030bf7b832fbe6f81343f5e4ba83e744634bf2bd9313d205fd2be82e2a7bef2412f3fca54c13dde02393f39d6a53eaeb632bfdec7d13b5d34dcbefd4aa7be60762fbfd04458bc355e3a3f423ea83ebc7433bf2bd9313d9564c5beb743abbe0e4f2f3ff931863eed0d2e3f39d6a53e13f231bfdec7d13b7d04cebe2a8da8be6dc52ebf52491d3eebe2363f423ea83e0a6832bfdec7d13ba12ec2be44a6acbe45d830bfb459953e9e5e293f9565a83ea5bd31bf1fd8313ddc9f0bbd33150abff9a0373fa913b03ed1221b3fe2e9953da1f831bf2bd9313d94856f3d1b7f16bfc3643a3fc217863e8126223f74b5953d38f832bfdec7d13b94c27c3d5de016bfa3013cbffd87743e93a9223f71ac8b3dfc1833bfdec7d13ba81c93beefacc5be38f832bf5917b73e567d1e3f6de77b3dd3de30bf1fd8313ddc9f0bbd33150abff9a0373fa913b03ed1221b3f022b873d19e238bfdec7d13b2dafdcbcbc040bbf4f1e36bf6900af3ea8351d3fb5157b3d50fc38bf44db313d0ebf9bbe3acfc0be1ceb323fd712b23e72f91f3f022b873dc05b30bf44db313d0ebf9bbe3acfc0be1ceb323fd712b23e72f91f3f54e3a53e29cb30bfdec7d13ba81c93beefacc5be38f832bf5917b73e567d1e3f9565a83e29cb30bfdec7d13b2dafdcbcbc040bbf4f1e36bf6900af3ea8351d3fdfe08b3d13f231bf2bd9313db5c32f3edb3313bf11363c3fcba1c5bea9a40e3f74b5953d07f036bf2bd9313d4eee473eff950dbf569f3b3f54e305bf89d2de3e74b5953d82e237bfdec7d13b033e3f3e9c1610bf29ed3dbf7368f1be0612f43e71ac8b3d348037bf2bd9313ddbc00d3e587017bf3b013d3f05a352be5474243f0681953d74b535bfdec7d13b46081f3eabb015bf44fa2d3f14d0a4be55c1283f71ac8b3d4b5936bf2bd9313da7eade3d57b118bf88853a3f6f1203bdbb272f3f74b5953d34a234bfdec7d13bc7a0f33d518818bf287e3cbf14d0c4bd03782b3fdfe08b3dd8f034bf2bd9313d29cda63d184018bfb5153b3fb537f83d88f42b3f74b5953d46b633bfdec7d13bb401b83d8f8d18bff1f43abf9a089b3d64cc2d3fdfe08b3d41f133bfdec7d13bc93dcd3e9cdc6fbeaaf122bfaf25e4bee926213fd34da23ed3de30bf2bd9313dc1c5ba3e4c7092bec139333facad18bfde02c93e14d0a43ed26f2fbffad4313d1dc9cd3ef54c6fbe4a7b233fe3a5dbbef38e233f14d0a43e60e530bfdec7d13b0090bb3e105891be7dd033bf0b2418bf029ac83e0a68a23ed26f2fbfdec7d13b8316723e4f79fcbe14ae37bff8c214bf8a8ec43e780ba43dc7293abf2bd9313dc1c5ba3e4c7092bec139333facad18bfde02c93e9a089b3d228e35bfdec7d13b0090bb3e105891be7dd033bf0b2418bf029ac83e780ba43db45935bf9be2313d4af1813ed13eeebe58a8353fcc7f18bf80b7c03e51da9b3d5eba39bf9be2313d4af1813ed13eeebe58a8353fcc7f18bf80b7c03ee10b933d1a513abfdec7d13b8316723e4f79fcbe14ae37bff8c214bf8a8ec43e287e8c3d2c6539bf13d7313da46fd23ed5086dbef0a7263fed0dbe3c2a3a423ff8c2a43e204131bfdec7d13bcfa3d23e94f86cbe8bfd25bfca54c13c58ca423fd34da23e204131bfdec7d13b1f4b633f4e09e8bd8cdb28bf1214dfbe3fc61c3fd34da23eee5a42bf2bd9313d297a5c3f5e4c13be7e1d383ff2b000bf228ef53ef8c2a43e204141bf2bd9313d994a633ffa0ce8bd302a293f8a1fe3be7ffb1a3f14d0a43eee5a42bfdec7d13b7b82583f259323be835139bf6a4df3be8e06003fd34da23e128340bf2bd9313d02104f3fe19544bec8983b3f97ffd0becc5d0b3ff8c2a43ef7063fbf42d0d13b22344a3fbccf51bef5b93abf8863bdbe6a4d133fee5aa23e20633ebf1fd8313d67ba3b3ff99e71be9e5e393fcff793be3255203fa69ba43e9f3c3cbfdec7d13bab76393fb30b76be1f853bbfdc4683be006f213fb840a23e8cb93bbf2bd9313d363b2e3f611682be1a513a3f44692fbe75022a3fa69ba43e713d3abfdec7d13bfc89263f276685beccee39bf211ff4bd52492d3f0a68a23e302a39bfdec7d13b15ca163f8a3988bebe3039bf0ad7a3bb0ebe303fb840a23e07f036bf2bd9313d54351d3f95bb87be27a0393f3fc65cbd77be2f3fa69ba43e2bf637bf2bd9313dc5740d3f2f8587bede02393fd1225b3d4e62303fc1a8a43ec6dc35bfdec7d13b0490063fcff585beacad38bff085c93dee7c2f3fb840a23e2fdd34bf2bd9313dfb1efd3e460c83bee78c383f07f0163efb5c2d3ff8c2a43eb3ea33bfdec7d13b9469ec3e0bd57dbebe3039bfb6f33d3efe432a3fd34da23e01de32bf2bd9313dbb0e653ff7e4e1bdab3e273f66f7e4bd5bb13f3f14d0a43e05a342bfdec7d13b0b63673ff1d4e3bdbec127bf4d848d3ecff7333fd34da23e1ceb42bfdec7d13bad34653fd7c1e1bdf9a027bf166acdbd05c53f3fd34da23e05a342bf2bd9313ddf87673ff035e4bd226c283f39d6853e14d0343f14d0a43e1ceb42bfdec7d13b0b63673ff1d4e3bdbec127bf4d848d3ecff7333f7446943c6c783abf2bd9313dfb5c693fbef9edbd50fc283fb6840c3fdc46033fd044583ca8573abfdec7d13bf2086a3f55c2f3bddcd721bfa8352d3fe561c13e7446943cc7293abf2bd9313de04a6a3f588ffbbdfe432a3fde93373f984c553e62a1563c910f3abf2bd9313ddf87673ff035e4bd226c283f39d6853e14d0343fd044583c88853abf2bd9313d4966353fbaf659bfce19313f728a0e3fe86aebbe5305a33e780b74bf2bd9313dfe7b243fbb2a6cbf7c61223f7b14ae3e17b731bfe5d0a23e7cf270bfdec7d13bc37d243f85276cbf5c8f22bf9621ae3ec58f31bf2653a53e7cf270bf38da313d86e3453fb98843bfa52c333fba6b193f79e9c6bec139a33eb00377bfdec7d13bfe0e453f180945bfd7a330bfe7fb193f9621cebe787aa53e07f076bf1fd8313dad17533fa7e82cbff7e4313f41f1233fde93a7be8195a33e23db79bfdec7d13b5fb44f3f202833bf6ea331bf4e62203f74b5b5bee6aea53e151d79bf1fd8313d4b575c3fecd917bf3cbd323fa3922a3fa60a86bef4fd543c083d2bbf1fd8313dad17533fa7e82cbff7e4313f41f1233fde93a7bed044583cccee29bfdec7d13b096b573ffeba23bfbc0532bff016283f068195bebc74933c75022abf2bd9313dbea0613ffdbd08bfbc05323f8941303faaf152bef4fd543c76e02cbfdec7d13b1d1d673f37c4e8be2e9030bfb459353ff54a19bee02d903c0e4f2fbf2bd9313d51f6663f637ce8be780b343ff31f323f068115be3ee8593c9b552fbfdec7d13b4a7b5f3fc3670fbf5c8f32bf849e2d3f560e6dbe05a3923cf6282cbf2bd9313d151b6b3f410fb5be0534313f6b9a373f0b46a5bdf4fd543cb37b32bfdec7d13be2c86b3f3945a7bee5f22fbf7e8c393f38f842bd97ff903c4f4033bf2bd9313d34686c3fdc7e59be499d303f4c37393fa913d03c62a1563ce71d37bfdec7d13bab416c3fce5157be8a1f33bf99bb363f6a4df33c4ed1913ce71d37bfdec7d13b096b573ffeba23bfbc0532bff016283f068195be1dc9a53e55307abfdec7d13b98be333f6dc85bbf1ceb32bf280f0b3fcd3beebe2653a53ed3bc73bfdec7d13bfd87843e16f674bfb1e149bf4013813e97900fbff8c2a43eaed85fbf2bd9313dfd87843e16f674bfb1e1493f5c20813e97900fbfb840a23eaed85fbfdec7d13b890a65bf6285cbbe55302abf477239bf7ffb3abe9e5ea93e27a059bf2bd9313d842e29bfcafb64bfe3c7383fa1d624bfdcd781be27c2a63e10e977bf2bd9313dbd1d65bf5073cabed6562c3f79e936bfdc4643be5ddca63e9a9959bfdec7d13b842e29bfcafb64bf70ce38bf14d024bfdcd781be8351a93e10e977bfdec7d13b3b3765bf7231bebefe432abf7a363bbfbe9f1a3e9e5ea93ee3c758bf2bd9313db83f63bf65ffb4be68912d3f9cc420bf9ca2c33ea779a73e273158bfdec7d13b10e662bf89d1b3be0d712cbf3b011dbf3333d33eba6ba93e273158bf13d7313d946c65bfbc03c4bec9762e3f3f573bbf5839343c79e9a63e6c0959bf1fd8313db4e564bf118fbcbee9482e3fea0434bf85eb513e94f6a63e759358bfdec7d13b14cbe5be129fe3becb1047bfb1e119bf24b93cbec6dc953e8c4a4abfdec7d13b7922e8be3be2a8bec52030bf7b832fbe6f81343ff6979d3e6c784abfdec7d13b0681e5be08abd9beaa6044bf92cb1fbf87a7173e7424973e355e4abfdec7d13b5e2c0cbf5c547bbf26e433bf5bb1bfbca60a36bfa245363e6f1243bfdec7d13bd6e3debec85beebec3f558bf0000a0bee3a5dbbec66d943edaac4abfdec7d13b1ec0d2be2827f2be295c4fbf6f12033add2416bf46b6933ee86a4bbfdec7d13ba800c8bed995eebec28647bf2653c53e76e0fcbecff7933ef6284cbfdec7d13b6687c0be193ce4bec3f558bfbc74033f835109be6132953e96b24cbfdec7d13ba81c93beefacc5be38f832bf5917b73e567d1e3f3e79983e8e0650bfdec7d13ba12ec2be44a6acbe45d830bfb459953e9e5e293fb6849c3e711b4dbfdec7d13bc764e1beb03ad2bebb274fbf8e06f0be984cb53e6210983e68b34abfdec7d13b5d34dcbefd4aa7be60762fbfd04458bc355e3a3f12a59d3e234a4bbfdec7d13b6136d9be4eb7ccbe5bd35cbff6971dbe99bbf63e90a0983e234a4bbfdec7d13b7d04cebe2a8da8be6dc52ebf52491d3eebe2363f1b2f9d3e7f6a4cbfdec7d13b1d94d0be641fccbea2b447bfac1c1a3e03781b3f90a0983edfe04bbfdec7d13befadc8be17d9cebe643b4fbf8351a93e075ff83e6210983e645d4cbfdec7d13b9c6ec1bea88cd7beb07258bf1214ff3e14d0443ed0d5963e3fc64cbfdec7d13b890a65bf6285cbbe55302abf477239bf7ffb3abe3fc69c3e280f3bbfdec7d13b10e662bf89d1b3be0d712cbf3b011dbf3333d33e92cb9f3eac8b3bbfdec7d13bedbb5ebfa244abbe68222cbf3e79d8beac8b1b3f80b7a03e68223cbfdec7d13b3b3765bf7231bebefe432abf7a363bbfbe9f1a3ead699e3e5f293bbfdec7d13b0b6056bf1827a6be60762fbf0e4f2fbcc3643a3f772da13e6d563dbfdec7d13b842e29bfcafb64bf70ce38bf14d024bfdcd781be5dfe433e098a3fbfdec7d13b3c6628bf3c6b67bf3cbd32bfda1b2cbf36cd7bbe93a9423e24973fbfdec7d13b007127bfe4be69bf3cbd32bf2b8726bf6c0999beca54413e5bb13fbfdec7d13bc65026bffdf46bbf3cbd32bfaed81fbf6a4db3be371a403e92cb3fbfdec7d13bb80725bf070c6ebf3cbd32bf7e1d18bf0d71ccbedbf93e3ee5f23fbfdec7d13bee9723bf4b0270bfaeb632bfb7620fbf014de4be48bf3d3e371a40bfdec7d13b820322bf39d671bfaeb632bf02bc05bfdaacfabe24b93c3e174840bfdec7d13bad4c20bf1a8673bfaeb632bfd95ff6bea2b407bfffb23b3e857c40bfdec7d13b79751ebf6f1075bfaeb632bf5bb1dfbe053411bf11c73a3e80b740bfdec7d13bed7f1cbf827376bfaeb632bfde93c7beecc019bf5af5393e7cf240bfdec7d13b446e1abf9fad77bf3cbd32bf9621aebeae4721bfa323393e053441bfdec7d13bb74218bf45bd78bfaeb632bff38e93be4bc827bf226c383e1c7c41bfdec7d13b2eff15bfaea079bf3cbd32bfc52070be8d282dbfd9ce373ec1ca41bfdec7d13be3a513bf69567abfaeb632bf6b9a37be006f31bfc74b373e651942bfdec7d13bde3811bfa0dc7abf3cbd32bfda1bfcbd8a8e34bfebe2363e0a6842bfdec7d13b5aba0ebfd3317bbf3cbd32bf4bc887bd107a36bf4694363e3cbd42bfdec7d13bc32c04bf70b4b3bee56141bf0a68a2bd2b87263f24b99c3eec5148bfdec7d13b033e3f3e9c1610bf29ed3dbf7368f1be0612f43e029a883eceaa5fbfdec7d13b03b0313e72c17dbfaa6034bf3480b7ba58a835bfc3f5283e16fb5bbfdec7d13b46081f3eabb015bf44fa2d3f14d0a4be55c1283f8b6c873e8d975ebfdec7d13bfcfc373eb6bd7dbfb8af33bfed0dbe3c143f36bf8cdb283e832f5cbfdec7d13b6f2e3e3e8fa77dbf2aa933bf423e683dabcf35bf8cdb283ef1635cbfdec7d13b1844443e0f7f7dbf0f9c33bfa392ba3d4bea34bf8cdb283e5f985cbfdec7d13bb43d4a3e35447dbf819533bf2506013ef38e33bf8cdb283ecdcc5cbfdec7d13b431b503e12f77cbff38e33bf9d11253ea5bd31bfc3f5283e3b015dbfdec7d13bfddb553ea6977cbff38e33bf4c37493e44692fbff90f293ea8355dbfdec7d13b8316723e4f79fcbe14ae37bff8c214bf8a8ec43e8d288d3edcd761bfdec7d13b24805b3ef2257cbf668833bfc4426d3e5f982cbf302a293e166a5dbfdec7d13b3107613e05a27bbf668833bf7958883e674429bf9e5e293ef6975dbfdec7d13b2671663ef20b7bbf668833bfd0b3993e068125bf0c93293e64cc5dbfdec7d13b7bbd6b3ea6637abff38e33bf3199aa3e3c4e21bf7ac7293e44fa5dbfdec7d13b31ec703e43a979bff38e33bf11c7ba3eb1bf1cbf1e162a3eb22e5ebfdec7d13b05fd753ebadc78bf819533bfa857ca3e67d517bffa7e2a3e925c5ebfdec7d13bb3ef7a3e19fe77bf9ca233bf6c09d93e789c12bf9fcd2a3e728a5ebfdec7d13bf8c37f3e840d77bf2aa933bf79e9e63e1b2f0dbf7a362b3e52b85ebfdec7d13ba73c823ec80a76bfb8af33bf98ddf33e508d07bfbec1573eb5a659bfdec7d13bfd87843e16f674bfb1e149bf4013813e97900fbf9f3c2c3e12145fbfdec7d13b0090bb3e105891be7dd033bf0b2418bf029ac83e910f9a3e903167bfdec7d13bc93dcd3e9cdc6fbeaaf122bfaf25e4bee926213f91ed9c3e598668bfdec7d13bcfa3d23e94f86cbe8bfd25bfca54c13c58ca423fe4149d3ea7e868bfdec7d13b9469ec3e0bd57dbebe3039bfb6f33d3efe432a3fac8b9b3e4ca66abfdec7d13bc37d243f85276cbf5c8f22bf9621ae3ec58f31bf50fc583e29ed6dbfdec7d13b98be333f6dc85bbf1ceb32bf280f0b3fcd3beebe09f9603e325570bfdec7d13bfe0e453f180945bfd7a330bfe7fb193f9621cebe569f6b3e38f872bfdec7d13b5fb44f3f202833bf6ea331bf4e62203f74b5b5beea04743e4faf74bfdec7d13b0490063fcff585beacad38bff085c93dee7c2f3fc7299a3e24b96cbfdec7d13b096b573ffeba23bfbc0532bff016283f068195beda1b7c3ea60a76bfdec7d13b4a7b5f3fc3670fbf5c8f32bf849e2d3f560e6dbe6f12833e8b6c77bfdec7d13b15ca163f8a3988bebe3039bf0ad7a3bb0ebe303f1058993e32e66ebfdec7d13b1d1d673f37c4e8be2e9030bfb459353ff54a19bee7fb893e19e278bfdec7d13bfc89263f276685beccee39bf211ff4bd52492d3fbe30993e053471bfdec7d13be2c86b3f3945a7bee5f22fbf7e8c393f38f842bd6154923e910f7abfdec7d13bab76393fb30b76be1f853bbfdc4683be006f213f75029a3e2aa973bf42d0d13b22344a3fbccf51bef5b93abf8863bdbe6a4d133f88f49b3e143f76bfdec7d13bab416c3fce5157be8a1f33bf99bb363f6a4df33c1a519a3e4ca67abfdec7d13b7b82583f259323be835139bf6a4df3be8e06003f925c9e3e273178bfdec7d13bf2086a3f55c2f3bddcd721bfa8352d3fe561c13ef775a03ef5b97abfdec7d13b1f4b633f4e09e8bd8cdb28bf1214dfbe3fc61c3f4013a13eb1e179bfdec7d13b0b63673ff1d4e3bdbec127bf4d848d3ecff7333f4013a13ede717abfdec7d13bad34653fd7c1e1bdf9a027bf166acdbd05c53f3f933aa13e3a237abfdec7d13bc7a0f33d518818bf287e3cbf14d0c4bd03782b3f94f6863e1b2f5dbfdec7d13bb401b83d8f8d18bff1f43abf9a089b3d64cc2d3f1d38873ef6285cbfdec7d13b94c27c3d5de016bfa3013cbffd87743e93a9223f82e2873e234a5bbfdec7d13b2dafdcbcbc040bbf4f1e36bf6900af3ea8351d3f8cb98b3eb07258bfdec7d13bf2ed59bf2508a7be04562ebfb8af43be66f7343f772da13ee8d93cbf2bd9313d8ffae3be2cd3a7bea8352d3ffca9f1bd1e163a3f40a41f3e63ee6abf2bd9313d5e2c0cbf5c547bbf98dd333f1283c0bc341136bff4fdd43c3cbd72bf1c0c353df1f5e5bea1a2e2be3bdf7f3f93a902bd11c7ba3b1748103eac8b6bbf1c0c353df371e5be1476d9becdcc7c3fae4721bea69bc43b73d7123ee86a6bbf2bd9313dd6e3debe6189cfbe6f12733f20632ebe94f6863ef4fd143ed5e76abf4209333d0d8ed2be53cacbbe04e77c3f52499d3be4831e3e3d9b153e1e166abfc91d363d69c3e1be7959ebbe0000803fac8bdbbb6c09f93af2d20d3eb1506bbfb797343d8d62d9be4013f1be72f97f3f4b5986bbc6dc35bc832f0c3e2cd46abfbb47363d0d8ed2beb51af2be20d27f3fa69b44bc4d150c3d71ac0b3efe436abfbcb0353dda55c8beedd2eebe0000803f3480373bfaedebba832f0c3ed0b369bfbcb0353d38f5c1be643be7bed3dee03d0bb5763fd93d79be29ed0d3e4c3769bfa33f343de202c0beb41edebe57ec7f3fa4dfbe3c9fab2dbc4e62103ede0269bf44db313d0ebf9bbe3acfc0be1ceb323fd712b23e72f91f3f3d0a173ef46c66bf2bd9313d9564c5beb743abbe0e4f2f3ff931863eed0d2e3f9be61d3e35ef68bf2bd9313d9fe3c3be0e4bd3bee63f743fae47613ea913503e1826133e302a69bf1aa6363d4cdec8be43c7cebe7b837f3ff2414f3d287e0c3dbde3143e0c9369bf2bd9313d205fd2be82e2a7bef2412f3fca54c13dde02393f2d211f3eecc069bf13d7313d946c65bfbc03c4bec9762e3f3f573bbf5839343c5227203e83c07abf2bd9313dbd1d65bf5073cabed6562c3f79e936bfdc4643bed2001e3e11c77abf1fd8313db4e564bf118fbcbee9482e3fea0434bf85eb513e2eff213e31997abf2bd9313db83f63bf65ffb4be68912d3f9cc420bf9ca2c33e41f1233e8c4a7abf1fd8313d134560bfb3cdadbe32772d3f516bfabe5f980c3f4260253eb1e179bf06d6313d43c65bbf7617a8bef1632c3fb22e8ebe44692f3f6666263ef54a79bf2bd9313dda3757bfbc24a6be9fab2d3f11c73abd8cb93b3f0bb5263ecc7f78bf2bd9313dc32c04bf70b4b3beca54413f2e90a0bdd49a263f64cc1d3e4d846dbf2bd9313d5aba0ebfd3317bbf3cbd323f4bc887bd107a36bf62a1d63c6f1273bf2bd9313dde3811bfa0dc7abf3cbd323fda1bfcbd8a8e34bf3ee8d93ca16773bf1fd8313ddc9f0bbd33150abff9a0373fa913b03ed1221b3fb537f83dbf7d5dbf2bd9313d94856f3d1b7f16bfc3643a3fc217863e8126223f8cdbe83d68b35abf2bd9313d29cda63d184018bfb5153b3fb537f83d88f42b3f8bfde53db1e159bf2bd9313da7eade3d57b118bf88853a3f6f1203bdbb272f3f8a8ee43d35ef58bf2bd9313ddbc00d3e587017bf3b013d3f05a352be5474243fd42be53d67d557bf2bd9313db5c32f3edb3313bf11363c3fcba1c5bea9a40e3fb072e83d7dae56bf2bd9313d03b0313e72c17dbfaa60343f3480b7ba58a835bff4fd543c23db59bf2bd9313dfcfc373eb6bd7dbfb8af333fed0dbe3c143f36bf865a533c27a059bf2bd9313d6f2e3e3e8fa77dbf2aa9333f423e683dabcf35bf865a533cba6b59bf2bd9313d4eee473eff950dbf569f3b3f54e305bf89d2de3efb5ced3d1dc955bf2bd9313d1844443e0f7f7dbf0f9c333fa392ba3d4bea34bf865a533c4c3759bf2bd9313db43d4a3e35447dbf8195333f2506013ef38e33bf865a533cde0259bf2bd9313d431b503e12f77cbff38e333f9d11253ea5bd31bf865a533c70ce58bf2bd9313dfddb553ea6977cbff38e333f4c37493e44692fbff4fd543c029a58bf2bd9313d24805b3ef2257cbf6688333fc4426d3e5f982cbfd044583c226c58bf2bd9313d3107613e05a27bbf6688333f7958883e674429bfac8b5b3cb53758bf2bd9313d2671663ef20b7bbf6688333fd0b3993e068125bf89d25e3c470358bf2bd9313d7bbd6b3ea6637abff38e333f3199aa3e3c4e21bf6519623c67d557bf2bd9313d31ec703e43a979bff38e333f11c7ba3eb1bf1cbfb003673c87a757bf9be2313d4af1813ed13eeebe58a8353fcc7f18bf80b7c03e2575023e333353bf2bd9313d05fd753ebadc78bf8195333fa857ca3e67d517bf68916d3ca77957bf2bd9313db3ef7a3e19fe77bf0f9c333f6c09d93e789c12bfb37b723c394557bf2bd9313df8c37f3e840d77bf2aa9333f79e9e63e1b2f0dbf6c09793c591757bf2bd9313da73c823ec80a76bfb8af333f98ddf33e508d07bf499d803c07f056bf2bd9313dfd87843e16f674bfb1e1493f5c20813e97900fbfddb5843c27c256bf2bd9313dc1c5ba3e4c7092bec139333facad18bfde02c93ed044183ec4b14ebffad4313d1dc9cd3ef54c6fbe4a7b233fe3a5dbbef38e233febe2963ed88133bf13d7313da46fd23ed5086dbef0a7263fed0dbe3c2a3a423fe4831e3e04e74cbf2bd9313dfb1efd3e460c83bee78c383f07f0163efb5c2d3f07ce193e910f4abf2bd9313dfe7b243fbb2a6cbf7c61223f7b14ae3e17b731bfb459753d82e247bf2bd9313d4966353fbaf659bfce19313f728a0e3fe86aebbe287e8c3def3845bf38da313d86e3453fb98843bfa52c333fba6b193f79e9c6bee5f29f3d73d742bf1fd8313dad17533fa7e82cbff7e4313f41f1233fde93a7bea2b4b73da08940bf2bd9313dc5740d3f2f8587bede02393fd1225b3d4e62303f87a7173e470348bf1fd8313d4b575c3fecd917bf3cbd323fa3922a3fa60a86bef163cc3dc0ec3ebf2bd9313dbea0613ffdbd08bfbc05323f8941303faaf152be63eeda3d5f073ebf2bd9313d54351d3f95bb87be27a0393f3fc65cbd77be2f3f62a1163e39d645bf2bd9313d51f6663f637ce8be780b343ff31f323f068115bed7a3f03d91ed3cbf2bd9313d151b6b3f410fb5be0534313f6b9a373f0b46a5bdcba1053efaed3bbf2bd9313d363b2e3f611682be1a513a3f44692fbe75022a3f7424173ef38e43bf1fd8313d67ba3b3ff99e71be9e5e393fcff793be3255203f19e2183ee09c41bf2bd9313d34686c3fdc7e59be499d303f4c37393fa913d03ce3c7183eec2f3bbf2bd9313d02104f3fe19544bec8983b3f97ffd0becc5d0b3ff6971d3e69003fbf2bd9313d297a5c3f5e4c13be7e1d383ff2b000bf228ef53e8a1f233e91ed3cbf2bd9313de04a6a3f588ffbbdfe432a3fde93373f984c553ef8c2243e9a083bbf2bd9313d994a633ffa0ce8bd302a293f8a1fe3be7ffb1a3f9d80263e88f43bbf2bd9313dfb5c693fbef9edbd50fc283fb6840c3fdc46033f1dc9253e5f293bbf2bd9313ddf87673ff035e4bd226c283f39d6853e14d0343f9d80263e5a643bbf2bd9313dbb0e653ff7e4e1bdab3e273f66f7e4bd5bb13f3f42cf263e71ac3bbf2bd9313de3a513bf69567abfaeb6323f6b9a37be006f31bf1b2fdd3cd3bc73bf2bd9313d2eff15bfaea079bf3cbd323fc52070be8d282dbfae47e13c780b74bf2bd9313db74218bf45bd78bfaeb6323ff38e93be4bc827bff931e63c8f5374bf2bd9313d446e1abf9fad77bf3cbd323f9621aebeae4721bffaedeb3ca69b74bf2bd9313ded7f1cbf827376bfaeb6323fde93c7beecc019bfb37bf23c2fdd74bf2bd9313d79751ebf6f1075bfaeb6323f5bb1dfbe053411bf6c09f93c2b1875bf2bd9313dad4c20bf1a8673bfaeb6323fd95ff6bea2b407bf6e34003d265375bf2bd9313d820322bf39d671bfaeb6323f02bc05bfdaacfabe014d043d948775bf2bd9313dee9723bf4b0270bfaeb6323fb7620fbf014de4be9565083d74b575bf2bd9313db80725bf070c6ebf3cbd323f7e1d18bf0d71ccbe04e70c3d54e375bf2bd9313dc65026bffdf46bbf3cbd323faed81fbf6a4db3be4ed1113d190476bf2bd9313d007127bfe4be69bf3cbd323f2b8726bf6c0999be99bb163d4f1e76bf2bd9313d3c6628bf3c6b67bf3cbd323fda1b2cbf36cd7bbebf0e1c3d863876bf2bd9313d842e29bfcafb64bfe3c7383fa1d624bfdcd781bee561213d304c76bf2bd9313d24805b3ef2257cbf6688333fc4426d3e5f982cbfb840a23eed0d5ebfdec7d13b24805b3ef2257cbf668833bfc4426d3e5f982cbff8c2a43eed0d5ebf2bd9313d3107613e05a27bbf6688333f7958883e674429bfb840a23ecd3b5ebfdec7d13b03b0313e72c17dbfaa6034bf3480b7ba58a835bf14d0a43e7aa55cbf2bd9313dfcfc373eb6bd7dbfb8af333fed0dbe3c143f36bfb840a23e5bd35cbf2bd9313d03b0313e72c17dbfaa60343f3480b7ba58a835bfb840a23eed9e5cbfdec7d13b6f2e3e3e8fa77dbf2aa933bf423e683dabcf35bff8c2a43ec8075dbfdec7d13b1844443e0f7f7dbf0f9c33bfa392ba3d4bea34bff8c2a43e363c5dbf2bd9313d1844443e0f7f7dbf0f9c333fa392ba3d4bea34bfb840a23e363c5dbfdec7d13bfcfc373eb6bd7dbfb8af33bfed0dbe3c143f36bf14d0a43ee8d95cbf2bd9313db43d4a3e35447dbf8195333f2506013ef38e33bfb840a23ea4705dbfdec7d13b431b503e12f77cbff38e33bf9d11253ea5bd31bff8c2a43e12a55dbfdec7d13bfddb553ea6977cbff38e33bf4c37493e44692fbff8c2a43e7fd95dbf2bd9313dfddb553ea6977cbff38e333f4c37493e44692fbfb840a23e7fd95dbfdec7d13b5aba0ebfd3317bbf3cbd32bf4bc887bd107a36bfe6aea53ebde344bf2bd9313d5aba0ebfd3317bbf3cbd323f4bc887bd107a36bfa52ca33ebde344bfdec7d13bde3811bfa0dc7abf3cbd32bfda1bfcbd8a8e34bfe6aea53e189544bfdec7d13b2eff15bfaea079bf3cbd32bfc52070be8d282dbf02bca53eea0444bf2bd9313d2eff15bfaea079bf3cbd323fc52070be8d282dbfc139a33eea0444bfdec7d13bb74218bf45bd78bfaeb632bff38e93be4bc827bf02bca53eefc943bfdec7d13be3a513bf69567abfaeb632bf6b9a37be006f31bf02bca53e014d44bf2bd9313de3a513bf69567abfaeb6323f6b9a37be006f31bfa52ca33e014d44bfdec7d13b3107613e05a27bbf668833bf7958883e674429bff8c2a43e5b425ebf2bd9313dde3811bfa0dc7abf3cbd323fda1bfcbd8a8e34bfa52ca33e189544bf2bd9313db74218bf45bd78bfaeb6323ff38e93be4bc827bfc139a33eefc943bf2bd9313d446e1abf9fad77bf3cbd323f9621aebeae4721bfdc46a33ef38e43bf2bd9313ded7f1cbf827376bfaeb6323fde93c7beecc019bf5ddca63ea3927abfdec7d13bed7f1cbf827376bfaeb632bfde93c7beecc019bf9e5ea93ea3927abfdec7d13b446e1abf9fad77bf3cbd32bf9621aebeae4721bfba6ba93e11c77abf2bd9313d79751ebf6f1075bfaeb6323f5bb1dfbe053411bf5ddca63ea8577abfdec7d13b79751ebf6f1075bfaeb632bf5bb1dfbe053411bf9e5ea93ea8577abf2bd9313dee9723bf4b0270bfaeb6323fb7620fbf014de4be42cfa63ef08579bfdec7d13bee9723bf4b0270bfaeb632bfb7620fbf014de4be8351a93ef08579bf2bd9313d820322bf39d671bfaeb6323f02bc05bfdaacfabe42cfa63e07ce79bf2bd9313dad4c20bf1a8673bfaeb6323fd95ff6bea2b407bf5ddca63e1e167abfdec7d13bad4c20bf1a8673bfaeb632bfd95ff6bea2b407bf9e5ea93e1e167abfdec7d13b820322bf39d671bfaeb632bf02bc05bfdaacfabe8351a93e95d479bfdec7d13bb80725bf070c6ebf3cbd32bf7e1d18bf0d71ccbe8351a93ed93d79bf2bd9313db80725bf070c6ebf3cbd323f7e1d18bf0d71ccbe42cfa63ed93d79bf2bd9313dc65026bffdf46bbf3cbd323faed81fbf6a4db3be42cfa63ea7e878bfdec7d13b2671663ef20b7bbf668833bfd0b3993e068125bff8c2a43e3b705ebfdec7d13b7bbd6b3ea6637abff38e33bf3199aa3e3c4e21bff8c2a43ea9a45ebf2bd9313d7bbd6b3ea6637abff38e333f3199aa3e3c4e21bfb840a23ea9a45ebf2bd9313d31ec703e43a979bff38e333f11c7ba3eb1bf1cbfb840a23e17d95ebfdec7d13bc65026bffdf46bbf3cbd32bfaed81fbf6a4db3be8351a93e35ef78bf2bd9313d007127bfe4be69bf3cbd323f2b8726bf6c0999be42cfa63e029a78bfdec7d13b007127bfe4be69bf3cbd32bf2b8726bf6c0999be8351a93e029a78bf2bd9313d3c6628bf3c6b67bf3cbd323fda1b2cbf36cd7bbe42cfa63ed04478bfdec7d13b3c6628bf3c6b67bf3cbd32bfda1b2cbf36cd7bbe8351a93ed04478bf2bd9313d5e2c0cbf5c547bbf98dd333f1283c0bc341136bfa52ca33e613245bfdec7d13b05fd753ebadc78bf819533bfa857ca3e67d517bff8c2a43e840d5fbfdec7d13bb3ef7a3e19fe77bf9ca233bf6c09d93e789c12bff8c2a43ef2415fbf2bd9313db3ef7a3e19fe77bf0f9c333f6c09d93e789c12bfb840a23e643b5fbfdec7d13b5e2c0cbf5c547bbf26e433bf5bb1bfbca60a36bfe6aea53e613245bf2bd9313da73c823ec80a76bfb8af333f98ddf33e508d07bfb840a23e40a45fbfdec7d13ba73c823ec80a76bfb8af33bf98ddf33e508d07bff8c2a43e40a45fbfdec7d13bf8c37f3e840d77bf2aa933bf79e9e63e1b2f0dbff8c2a43ed26f5fbf2bd9313df8c37f3e840d77bf2aa9333f79e9e63e1b2f0dbfb840a23ed26f5fbf2bd9313d6f2e3e3e8fa77dbf2aa9333f423e683dabcf35bfb840a23ec8075dbfdec7d13b31ec703e43a979bff38e33bf11c7ba3eb1bf1cbff8c2a43e17d95ebf2bd9313d05fd753ebadc78bf8195333fa857ca3e67d517bfb840a23e840d5fbfdec7d13bb43d4a3e35447dbf819533bf2506013ef38e33bff8c2a43ea4705dbf2bd9313d431b503e12f77cbff38e333f9d11253ea5bd31bfb840a23e12a55dbf2bd9313d2671663ef20b7bbf6688333fd0b3993e068125bfb840a23e3b705ebf - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: -0.0053590536, y: 0.001954496, z: -0.487102} - m_Extent: {x: 1.009431, y: 1.0101185, z: 0.504131} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh.meta deleted file mode 100644 index 8a5ce6676..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Gottlieb.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: ea7c5dde6a397bc489fb4771642a1dbd -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh deleted file mode 100644 index f162f3fde..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Hole - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 288 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 192 - localAABB: - m_Center: {x: 0, y: 0, z: -0.228336} - m_Extent: {x: 0.975, y: 0.975, z: 0.25} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 000001000200030001000000040005000600070005000400080009000a000b00090008000c000d000e000c000e000f00100011001200130011001000140015001600140016001700180019001a001b00190018001c001d001e001c001e001f00200021002200230021002000240025002600240026002700280029002a002b00290028002c002d002e002c002e002f00300031003200330031003000340035003600340036003700380039003a003b00390038003c003d003e003c003e003f00400041004200430041004000440045004600440046004700480049004a004b00490048004c004d004e004c004e004f00500051005200530051005000540055005600540056005700580059005a005b00590058005c005d005e005c005e005f00600061006200600062006300640065006600650064006700680069006a006a006b0068006c006d006e006d006c006f00700071007200710070007300740075007600750074007700780079007a007a007b0078007c007d007e007d007c007f00800081008200820083008000840085008600850084008700880089008a008a008b0088008c008d008e008d008c008f00900091009200920093009000940095009600950094009700980099009a009a009b0098009c009d009e009d009c009f00a000a100a200a200a300a000a400a500a600a500a400a700a800a900aa00aa00ab00a800ac00ad00ae00ad00ac00af00b000b100b200b200b300b000b400b500b600b500b400b700b800b900ba00ba00bb00b800bc00bd00be00bd00bc00bf00 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 192 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 6144 - _typelessdata: 11a74b3fbd37863546b3b2bc7cf270beb6f3fdbcacad783f8255553e1cec2dbe5d18713fe333813eb378b13c7cf270beb6f3fdbcacad783fc1aa2a3e0000008095b6443fedd6523e46b3b2bc7cf270beb6f3fdbcacad783fc1aa2a3e1cec2dbe9a99793f00000000b378b13c7cf270beb6f3fdbcacad783f8255553e00000080f628583f9a99f93eb378b13c2e9060be5af5b9bdacad783f4300003e0000008095b6443fedd6523e46b3b2bc2e9060be5af5b9bdacad783fc1aa2a3e1cec2dbe5d18713fe333813eb378b13c2e9060be5af5b9bdacad783fc1aa2a3e000000803e5e303f76a7cb3e46b3b2bc2e9060be5af5b9bdacad783f0000003e1cec2dbe3e5e303f76a7cb3e46b3b2bcb7d140becff713beacad783f0000003e1cec2dbe6b7e303f6b7e303fb378b13cb7d140becff713beacad783f04abaa3d00000080fc00103f5001103f46b3b2bcb7d140becff713beacad783f7eaaaa3d1cec2dbef628583f9a99f93eb378b13cb7d140becff713beacad783f4300003e00000080fc00103f5001103f46b3b2bccff713beb7d140beacad783f7eaaaa3d1cec2dbe6b7e303f6b7e303fb378b13ccff713beb7d140beacad783f04abaa3d000000809a99f93ef628583fb378b13ccff713beb7d140beacad783f11ac2a3d00000080cea6cb3ea25e303f46b3b2bccff713beb7d140beacad783f04ab2a3d1cec2dbecea6cb3ea25e303f46b3b2bc5af5b9bd2e9060beacad783f04ab2a3d1cec2dbee333813e5d18713fb378b13c5af5b9bd2e9060beacad783f00000000000000805ad5523efab6443f46b3b2bc5af5b9bd2e9060beacad783f000000001cec2dbe9a99f93ef628583fb378b13c5af5b9bd2e9060beacad783f11ac2a3d000000805ad5523efab6443f46b3b2bcb6f3fdbc7cf270beacad783fefff7f3f1cec2dbee333813e5d18713fb378b13cb6f3fdbc7cf270beacad783fefff7f3f00000080000000009a99793fb378b13cb6f3fdbc7cf270beacad783f5055753f00000080bd3786b665a74b3f46b3b2bcb6f3fdbc7cf270beacad783f5055753f1cec2dbebd3786b665a74b3f46b3b2bcb6f3fd3c7cf270beacad783f5055753f1cec2dbee33381be5d18713fb378b13cb6f3fd3c7cf270beacad783fb0aa6a3f0000008073d752befab6443f46b3b2bcb6f3fd3c7cf270beacad783f9faa6a3f1cec2dbe000000009a99793fb378b13cb6f3fd3c7cf270beacad783f5055753f0000008073d752befab6443f46b3b2bc5af5b93d2e9060beacad783f9faa6a3f1cec2dbee33381be5d18713fb378b13c5af5b93d2e9060beacad783fb0aa6a3f000000809a99f9bef628583fb378b13c5af5b93d2e9060beacad783f0000603f00000080dba7cbbea25e303f46b3b2bc5af5b93d2e9060beacad783f0000603f1cec2dbedba7cbbea25e303f46b3b2bccff7133eb7d140beacad783f0000603f1cec2dbe6b7e30bf6b7e303fb378b13ccff7133eb7d140beacad783f5055553f00000080820110bf5001103f46b3b2bccff7133eb7d140beacad783f5055553f1cec2dbe9a99f9bef628583fb378b13ccff7133eb7d140beacad783f0000603f00000080820110bf5001103f46b3b2bcb7d1403ecff713beacad783f5055553f1cec2dbe6b7e30bf6b7e303fb378b13cb7d1403ecff713beacad783f5055553f00000080f62858bf9a99f93eb378b13cb7d1403ecff713beacad783fb0aa4a3f00000080c45e30bf76a7cb3e46b3b2bcb7d1403ecff713beacad783fb0aa4a3f1cec2dbec45e30bf76a7cb3e46b3b2bc2e90603e5af5b9bdacad783fb0aa4a3f1cec2dbe5d1871bfe333813eb378b13c2e90603e5af5b9bdacad783f0000403f000000801cb744bfedd6523e46b3b2bc2e90603e5af5b9bdacad783f0000403f1cec2dbef62858bf9a99f93eb378b13c2e90603e5af5b9bdacad783fb0aa4a3f000000801cb744bfedd6523e46b3b2bc7cf2703eb6f3fdbcacad783f0000403f1cec2dbe5d1871bfe333813eb378b13c7cf2703eb6f3fdbcacad783f0000403f000000809a9979bf00000000b378b13c7cf2703eb6f3fdbcacad783f6155353f0000008098a74bbfbd37863546b3b2bc7cf2703eb6f3fdbcacad783f6155353f1cec2dbe98a74bbfbd37863546b3b2bc7cf2703eb6f3fd3cacad783f6155353f1cec2dbe5d1871bfe33381beb378b13c7cf2703eb6f3fd3cacad783fb0aa2a3f000000801cb744bf24d652be46b3b2bc7cf2703eb6f3fd3cacad783fb0aa2a3f1cec2dbe9a9979bf00000000b378b13c7cf2703eb6f3fd3cacad783f6155353f000000801cb744bf24d652be46b3b2bc2e90603e5af5b93dacad783fb0aa2a3f1cec2dbe5d1871bfe33381beb378b13c2e90603e5af5b93dacad783fb0aa2a3f00000080f62858bf9a99f9beb378b13c2e90603e5af5b93dacad783f0000203f00000080c45e30bf33a7cbbe46b3b2bc2e90603e5af5b93dacad783f1100203f1cec2dbec45e30bf33a7cbbe46b3b2bcb7d1403ecff7133eacad783f1100203f1cec2dbe6b7e30bf6b7e30bfb378b13cb7d1403ecff7133eacad783f6155153f00000080820110bf2e0110bf46b3b2bcb7d1403ecff7133eacad783f6155153f1cec2dbef62858bf9a99f9beb378b13cb7d1403ecff7133eacad783f0000203f00000080820110bf2e0110bf46b3b2bccff7133eb7d1403eacad783f6155153f1cec2dbe6b7e30bf6b7e30bfb378b13ccff7133eb7d1403eacad783f6155153f000000809a99f9bef62858bfb378b13ccff7133eb7d1403eacad783fb0aa0a3f00000080dba7cbbe705e30bf46b3b2bccff7133eb7d1403eacad783fc1aa0a3f1cec2dbedba7cbbe705e30bf46b3b2bc5af5b93d2e90603eacad783fc1aa0a3f1cec2dbee33381be5d1871bfb378b13c5af5b93d2e90603eacad783f0000003f0000008073d752bec8b644bf46b3b2bc5af5b93d2e90603eacad783f1100003f1cec2dbe9a99f9bef62858bfb378b13c5af5b93d2e90603eacad783fb0aa0a3f0000008073d752bec8b644bf46b3b2bcb6f3fd3c7cf2703eacad783f1100003f1cec2dbee33381be5d1871bfb378b13cb6f3fd3c7cf2703eacad783f0000003f00000080000000009a9979bfb378b13cb6f3fd3c7cf2703eacad783fc1aaea3e00000080bd3786b644a74bbf46b3b2bcb6f3fd3c7cf2703eacad783fe3aaea3e1cec2dbebd3786b644a74bbf46b3b2bcb6f3fdbc7cf2703eacad783fe3aaea3e1cec2dbee333813e5d1871bfb378b13cb6f3fdbc7cf2703eacad783f6155d53e000000805ad5523ec8b644bf46b3b2bcb6f3fdbc7cf2703eacad783f8255d53e1cec2dbe000000009a9979bfb378b13cb6f3fdbc7cf2703eacad783fc1aaea3e000000805ad5523ec8b644bf46b3b2bc5af5b9bd2e90603eacad783f8255d53e1cec2dbee333813e5d1871bfb378b13c5af5b9bd2e90603eacad783f6155d53e000000809a99f93ef62858bfb378b13c5af5b9bd2e90603eacad783f2200c03e00000080cea6cb3e705e30bf46b3b2bc5af5b9bd2e90603eacad783f2200c03e1cec2dbecea6cb3e705e30bf46b3b2bccff713beb7d1403eacad783f2200c03e1cec2dbe6b7e303f6b7e30bfb378b13ccff713beb7d1403eacad783fc1aaaa3e00000080fc00103f2e0110bf46b3b2bccff713beb7d1403eacad783fc1aaaa3e1cec2dbe9a99f93ef62858bfb378b13ccff713beb7d1403eacad783f2200c03e00000080fc00103f2e0110bf46b3b2bcb7d140becff7133eacad783fc1aaaa3e1cec2dbe6b7e303f6b7e30bfb378b13cb7d140becff7133eacad783fc1aaaa3e00000080f628583f9a99f9beb378b13cb7d140becff7133eacad783f6155953e000000803e5e303f33a7cbbe46b3b2bcb7d140becff7133eacad783f6155953e1cec2dbe3e5e303f33a7cbbe46b3b2bc2e9060be5af5b93dacad783f6155953e1cec2dbe5d18713fe33381beb378b13c2e9060be5af5b93dacad783f2200803e0000008095b6443f24d652be46b3b2bc2e9060be5af5b93dacad783f2200803e1cec2dbef628583f9a99f9beb378b13c2e9060be5af5b93dacad783f6155953e0000008095b6443f24d652be46b3b2bc7cf270beb6f3fd3cacad783f2200803e1cec2dbe5d18713fe33381beb378b13c7cf270beb6f3fd3cacad783f2200803e000000809a99793f00000000b378b13c7cf270beb6f3fd3cacad783f8255553e0000008011a74b3fbd37863546b3b2bc7cf270beb6f3fd3cacad783f8255553e1cec2dbe11a74b3fbd37863575e8f4be64cc7dbfcba105be000000808255553e2f317ebf11a74b3fbd37863546b3b2bc64cc7dbfcba105be000000808255553e1cec2dbe95b6443fedd6523e46b3b2bc64cc7dbfcba105be00000080c1aa2a3e1cec2dbe95b6443fedd6523e75e8f4be64cc7dbfcba105be00000080c1aa2a3e2f317ebf95b6443fedd6523e75e8f4beb6846cbf41f1c3be00000080c1aa2a3e2f317ebf3e5e303f76a7cb3e46b3b2bcb6846cbf41f1c3be000000800000003e1cec2dbe3e5e303f76a7cb3e75e8f4beb6846cbf41f1c3be000000800000003e2f317ebf95b6443fedd6523e46b3b2bcb6846cbf41f1c3be00000080c1aa2a3e1cec2dbe3e5e303f76a7cb3e75e8f4be431c4bbf51da1bbf000000800000003e2f317ebf3e5e303f76a7cb3e46b3b2bc431c4bbf51da1bbf000000800000003e1cec2dbefc00103f5001103f46b3b2bc431c4bbf51da1bbf000000807eaaaa3d1cec2dbefc00103f5001103f75e8f4be431c4bbf51da1bbf000000807eaaaa3d2f317ebffc00103f5001103f75e8f4be51da1bbf431c4bbf000000807eaaaa3d2f317ebfcea6cb3ea25e303f46b3b2bc51da1bbf431c4bbf0000008004ab2a3d1cec2dbecea6cb3ea25e303f75e8f4be51da1bbf431c4bbf0000008004ab2a3d2f317ebffc00103f5001103f46b3b2bc51da1bbf431c4bbf000000807eaaaa3d1cec2dbecea6cb3ea25e303f75e8f4be41f1c3beb6846cbf0000008004ab2a3d2f317ebf5ad5523efab6443f46b3b2bc41f1c3beb6846cbf00000080000000001cec2dbe5ad5523efab6443f75e8f4be41f1c3beb6846cbf00000080000000002f317ebfcea6cb3ea25e303f46b3b2bc41f1c3beb6846cbf0000008004ab2a3d1cec2dbe5ad5523efab6443f75e8f4becba105be64cc7dbf000000800802803f2f317ebfbd3786b665a74b3f46b3b2bccba105be64cc7dbf000000805055753f1cec2dbebd3786b665a74b3f75e8f4becba105be64cc7dbf000000805055753f2f317ebf5ad5523efab6443f46b3b2bccba105be64cc7dbf00000080efff7f3f1cec2dbebd3786b665a74b3f75e8f4becba1053e64cc7dbf000000805055753f2f317ebfbd3786b665a74b3f46b3b2bccba1053e64cc7dbf000000805055753f1cec2dbe73d752befab6443f46b3b2bccba1053e64cc7dbf000000809faa6a3f1cec2dbe73d752befab6443f75e8f4becba1053e64cc7dbf000000809faa6a3f2f317ebf73d752befab6443f75e8f4be41f1c33eb6846cbf000000809faa6a3f2f317ebfdba7cbbea25e303f46b3b2bc41f1c33eb6846cbf000000800000603f1cec2dbedba7cbbea25e303f75e8f4be41f1c33eb6846cbf000000800000603f2f317ebf73d752befab6443f46b3b2bc41f1c33eb6846cbf000000809faa6a3f1cec2dbedba7cbbea25e303f75e8f4be51da1b3f431c4bbf000000800000603f2f317ebfdba7cbbea25e303f46b3b2bc51da1b3f431c4bbf000000800000603f1cec2dbe820110bf5001103f46b3b2bc51da1b3f431c4bbf000000805055553f1cec2dbe820110bf5001103f75e8f4be51da1b3f431c4bbf000000805055553f2f317ebf820110bf5001103f75e8f4be431c4b3f51da1bbf000000805055553f2f317ebfc45e30bf76a7cb3e46b3b2bc431c4b3f51da1bbf00000080b0aa4a3f1cec2dbec45e30bf76a7cb3e75e8f4be431c4b3f51da1bbf00000080b0aa4a3f2f317ebf820110bf5001103f46b3b2bc431c4b3f51da1bbf000000805055553f1cec2dbec45e30bf76a7cb3e75e8f4beb6846c3f41f1c3be00000080b0aa4a3f2f317ebfc45e30bf76a7cb3e46b3b2bcb6846c3f41f1c3be00000080b0aa4a3f1cec2dbe1cb744bfedd6523e46b3b2bcb6846c3f41f1c3be000000800000403f1cec2dbe1cb744bfedd6523e75e8f4beb6846c3f41f1c3be000000800000403f2f317ebf1cb744bfedd6523e75e8f4be64cc7d3fcba105be000000800000403f2f317ebf98a74bbfbd37863546b3b2bc64cc7d3fcba105be000000806155353f1cec2dbe98a74bbfbd37863575e8f4be64cc7d3fcba105be000000806155353f2f317ebf1cb744bfedd6523e46b3b2bc64cc7d3fcba105be000000800000403f1cec2dbe98a74bbfbd37863575e8f4be64cc7d3fcba1053e000000806155353f2f317ebf98a74bbfbd37863546b3b2bc64cc7d3fcba1053e000000806155353f1cec2dbe1cb744bf24d652be46b3b2bc64cc7d3fcba1053e00000080b0aa2a3f1cec2dbe1cb744bf24d652be75e8f4be64cc7d3fcba1053e00000080b0aa2a3f2f317ebf1cb744bf24d652be75e8f4beb6846c3f41f1c33e00000080b0aa2a3f2f317ebfc45e30bf33a7cbbe46b3b2bcb6846c3f41f1c33e000000801100203f1cec2dbec45e30bf33a7cbbe75e8f4beb6846c3f41f1c33e000000801100203f2f317ebf1cb744bf24d652be46b3b2bcb6846c3f41f1c33e00000080b0aa2a3f1cec2dbec45e30bf33a7cbbe75e8f4be431c4b3f51da1b3f000000801100203f2f317ebfc45e30bf33a7cbbe46b3b2bc431c4b3f51da1b3f000000801100203f1cec2dbe820110bf2e0110bf46b3b2bc431c4b3f51da1b3f000000806155153f1cec2dbe820110bf2e0110bf75e8f4be431c4b3f51da1b3f000000806155153f2f317ebf820110bf2e0110bf75e8f4be51da1b3f431c4b3f000000806155153f2f317ebfdba7cbbe705e30bf46b3b2bc51da1b3f431c4b3f00000080c1aa0a3f1cec2dbedba7cbbe705e30bf75e8f4be51da1b3f431c4b3f00000080c1aa0a3f2f317ebf820110bf2e0110bf46b3b2bc51da1b3f431c4b3f000000806155153f1cec2dbedba7cbbe705e30bf75e8f4be41f1c33eb6846c3f00000080c1aa0a3f2f317ebfdba7cbbe705e30bf46b3b2bc41f1c33eb6846c3f00000080c1aa0a3f1cec2dbe73d752bec8b644bf46b3b2bc41f1c33eb6846c3f000000801100003f1cec2dbe73d752bec8b644bf75e8f4be41f1c33eb6846c3f000000801100003f2f317ebf73d752bec8b644bf75e8f4becba1053e64cc7d3f000000801100003f2f317ebfbd3786b644a74bbf46b3b2bccba1053e64cc7d3f00000080e3aaea3e1cec2dbebd3786b644a74bbf75e8f4becba1053e64cc7d3f00000080e3aaea3e2f317ebf73d752bec8b644bf46b3b2bccba1053e64cc7d3f000000801100003f1cec2dbebd3786b644a74bbf75e8f4becba105be64cc7d3f00000080e3aaea3e2f317ebfbd3786b644a74bbf46b3b2bccba105be64cc7d3f00000080e3aaea3e1cec2dbe5ad5523ec8b644bf46b3b2bccba105be64cc7d3f000000808255d53e1cec2dbe5ad5523ec8b644bf75e8f4becba105be64cc7d3f000000808255d53e2f317ebf5ad5523ec8b644bf75e8f4be41f1c3beb6846c3f000000808255d53e2f317ebfcea6cb3e705e30bf46b3b2bc41f1c3beb6846c3f000000802200c03e1cec2dbecea6cb3e705e30bf75e8f4be41f1c3beb6846c3f000000802200c03e2f317ebf5ad5523ec8b644bf46b3b2bc41f1c3beb6846c3f000000808255d53e1cec2dbecea6cb3e705e30bf75e8f4be51da1bbf431c4b3f000000802200c03e2f317ebfcea6cb3e705e30bf46b3b2bc51da1bbf431c4b3f000000802200c03e1cec2dbefc00103f2e0110bf46b3b2bc51da1bbf431c4b3f00000080c1aaaa3e1cec2dbefc00103f2e0110bf75e8f4be51da1bbf431c4b3f00000080c1aaaa3e2f317ebffc00103f2e0110bf75e8f4be431c4bbf51da1b3f00000080c1aaaa3e2f317ebf3e5e303f33a7cbbe46b3b2bc431c4bbf51da1b3f000000806155953e1cec2dbe3e5e303f33a7cbbe75e8f4be431c4bbf51da1b3f000000806155953e2f317ebffc00103f2e0110bf46b3b2bc431c4bbf51da1b3f00000080c1aaaa3e1cec2dbe3e5e303f33a7cbbe75e8f4beb6846cbf41f1c33e000000806155953e2f317ebf3e5e303f33a7cbbe46b3b2bcb6846cbf41f1c33e000000806155953e1cec2dbe95b6443f24d652be46b3b2bcb6846cbf41f1c33e000000802200803e1cec2dbe95b6443f24d652be75e8f4beb6846cbf41f1c33e000000802200803e2f317ebf95b6443f24d652be75e8f4be64cc7dbfcba1053e000000802200803e2f317ebf11a74b3fbd37863546b3b2bc64cc7dbfcba1053e000000808255553e1cec2dbe11a74b3fbd37863575e8f4be64cc7dbfcba1053e000000808255553e2f317ebf95b6443f24d652be46b3b2bc64cc7dbfcba1053e000000802200803e1cec2dbe - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0, y: 0, z: -0.228336} - m_Extent: {x: 0.975, y: 0.975, z: 0.25} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh.meta deleted file mode 100644 index 881530ec9..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Hole.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 150f393297c4c0d4e99b7b1780264a44 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh deleted file mode 100644 index 72f97b254..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Simple Hole - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 126 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 42 - localAABB: - m_Center: {x: -0.011613518, y: -0.006360501, z: -0.31498098} - m_Extent: {x: 0.91528654, y: 0.92403054, z: 0.308946} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 1 - m_KeepIndices: 1 - m_IndexFormat: 0 - m_IndexBuffer: 00000100020003000100000001000400020024000300000002000400050025000300240004000600050022002500240005000600070021002500220006000800070020002100220007000800090023002100200008000a00090023002000290009000a000b002300290028000a000c000b00280029001f000b000c000d0028001f001e000c000e000d001c001e001f000d000e000f001b001e001c000e0010000f001a001b001c000f00100011001d001b001a001000120011001d001a0027001100120013001d0027002600120014001300260027001900130014001500260019001800140016001500170018001900150016001700160018001700 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 42 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 1344 - _typelessdata: 17f599befd335f3faeb91fbfc9e59f3e333373bf000000005ce4fe3e6bf214bdb66815bf425d383f40c1c5bb764f1e3f302a49bf00000000d8d4a93e32597cbfb66815bf425d383faeb91fbf764f1e3f302a49bf00000000d8d4a93e6bf214bdb2f499be0d345f3f40c1c5bbc9e59f3e333373bf000000005ce4fe3e32597cbf1a304cbff52ef63e40c1c5bb0c93593febe206bf000000003b8b3e3e32597cbf1a304cbff52ef63eaeb91fbf0c93593febe206bf000000003b8b3e3e6bf214bd0bb56abf001c3b3e40c1c5bb31997a3f736851be0000000036759e3d32597cbf0bb56abf001c3b3eaeb91fbf31997a3f736851be0000000036759e3d6bf214bd52496dbf34820dbe40c1c5bb88637d3fbc05123e00000000815fa33c32597cbf52496dbf34820dbeaeb91fbf88637d3fbc05123e00000000815fa33c6bf214bd2c9d53bfc4ebe2be40c1c5bbe09c613f85ebf13e00000000815fa33c32597cbf1b9d53bfc4ebe2beaeb91fbfe09c613f85ebf13e00000000815fa33c6bf214bd43c920bffe0d32bf40c1c5bb4ca62a3f17d93e3f0000008036759e3d32597cbf43c920bffe0d32bfaeb91fbf4ca62a3f17d93e3f0000008036759e3d6bf214bd16deb5be5b5e5dbf40c1c5bb2428be3e2db26d3f000000803b8b3e3e32597cbf37deb5be9e5e5dbfaeb91fbf2428be3e2db26d3f000000803b8b3e3e6bf214bd6ec027bd1b2e6ebf40c1c5bb499d003d3bdf7f3f00000080d8d4a93e32597cbf6ec027bd1b2e6ebfaeb91fbf499d003d3bdf7f3f00000080d8d4a93e6bf214bd2cba8d3e9b7562bf40c1c5bbc9e59fbe3333733f000000805ce4fe3e32597cbf4dba8d3ecd7562bfaeb91fbfc9e59fbe3333733f000000805ce4fe3e6bf214bd0e4b0f3fe09e3bbf40c1c5bb764f1ebf302a493f0000008001fa293f32597cbf1f4b0f3f239f3bbfaeb91fbf764f1ebf302a493f0000008001fa293f6bf214bd6212463fd9b2fcbe40c1c5bb0c9359bfebe2063f00000080726e4f3f32597cbf7312463f5fb3fcbeaeb91fbf0c9359bfebe2063f00000080726e4f3f6bf214bda697643f792248be40c1c5bb31997abf7368513e00000080c6156b3f32597cbfb797643f792248beaeb91fbf31997abf7368513e00000080c6156b3f6bf214bd1d57673f6ff0153eaeb91fbf91ed7cbf091b1ebe0000008045d67a3f6bf214bdc77f4d3f0168dc3e40c1c5bb522760bfc74bf7be0000008071c9793f32597cbfc77f4d3f0168dc3eaeb91fbf522760bfc74bf7be0000008045d67a3f6bf214bd0c57673ff6f0153e40c1c5bb91ed7cbf091b1ebe0000008071c9793f32597cbfefab1a3f60cc2e3f40c1c5bb4ca62abf89d23ebf00000000c6156b3f32597cbfefab1a3f60cc2e3faeb91fbf4ca62abf89d23ebf00000000c6156b3f6bf214bd942fa03e410b5d3faeb91fbfc9e5bfbda4df7ebf000000009d814d3f6bf214bd9ca4993ef3c85b3f40c1c5bb9d11a53dbb277fbf00000000b8944b3f32597cbf9ca4993ef3c85b3faeb91fbf9d11a53dbb277fbf00000000b8944b3f6bf214bd942fa03e410b5d3f40c1c5bb5bb1bfbda4df7ebf000000009d814d3f32597cbf9cc58b3c6cec6a3faeb91fbf499d00bd3bdf7fbf0000000001fa293f6bf214bd9cc58b3c6cec6a3f40c1c5bb499d00bd3bdf7fbf0000000001fa293f32597cbfdc2b673fbb7b003e40c1c5bb3bdf7fbf499d003d0000008071c9793f32597cbfed2b673faf7a003eaeb91fbf3bdf7fbf499d003d0000008045d67a3f6bf214bd6ea3a93e431d5a3f40c1c5bbebe206bf0c9359bf00000000726e4f3f32597cbf90a3a93e111d5a3faeb91fbf79e906bf0c9359bf00000000726e4f3f6bf214bd - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: -0.011613518, y: -0.006360501, z: -0.31498098} - m_Extent: {x: 0.91528654, y: 0.92403054, z: 0.308946} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh.meta deleted file mode 100644 index dabc35443..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Simple Hole.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9ccd9443a4106414aadb5e709477e55f -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh deleted file mode 100644 index 8df07057e..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh +++ /dev/null @@ -1,166 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!43 &4300000 -Mesh: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: Williams - serializedVersion: 10 - m_SubMeshes: - - serializedVersion: 2 - firstByte: 0 - indexCount: 3582 - topology: 0 - baseVertex: 0 - firstVertex: 0 - vertexCount: 1243 - localAABB: - m_Center: {x: 0.010875046, y: 0.008525968, z: -0.558532} - m_Extent: {x: 1.028691, y: 1.012251, z: 0.571795} - m_Shapes: - vertices: [] - shapes: [] - channels: [] - fullWeights: [] - m_BindPose: [] - m_BoneNameHashes: - m_RootBoneNameHash: 0 - m_BonesAABB: [] - m_VariableBoneCountWeights: - m_Data: - m_MeshCompression: 0 - m_IsReadable: 1 - m_KeepVertices: 0 - m_KeepIndices: 0 - m_IndexFormat: 0 - m_IndexBuffer: 55015601570158015601550100000100020003000100000001001f00020002001f001c001f001d001c001c001d001e000400050006000700050004000800070004000900070008000a00090008000b0009000a000c000b000a000d000e000f0010000d000f0011000d0010001200110010001300110012001400130012001500130014001600170018001900170016001a00190016001b0019001a002000210022002100670022002300210020006800670021002400230020006600670068002500230024002600230025006900660068006a00660069002700280029002a00270029002b0027002a002c002b002a002b002c002d002d002c002f002e002d002f0030002e002f0031002e0030003100300032003200340031003300340032003500340033003300ad003500ac003500ad00ab00ac00ad003600370038003900360038003700400038003a00360039003700410040003700420041004b004a00360036004a00490036004900470036004700480043004200370044004200430045004400430046004500430046003f0045004e004d0046004d004c00460046004c003b003b003f0046003f003b003e003e003b003d003d003b003c003c003b003a003b0036003a0036004800570057005600360036005600370058005700480056004f0037005900580048004f00500037005a00580059005100370050005b005a0059005200370051005c005a005b005300370052005d005c005b005400530052005500540052005e005c005d005f005e005d0060005f005d0061005f0060006000650061006100650062006200630061006400610063006b006c006d006e006b006d006f006b006e0070006f006e0071006f007000720071007000710072007300740071007300750074007300760074007500770074007600cb0077007600780079007a007b0078007a0083007a0079007c0078007b008400830079007d0078007c007e0078007d008b008c00790079008d008b008e008d0079008f008e0079007f0078007e0080007f007e0081007f00800082007f008100820081008a008a008100890090008f007900890081008500a50089008500850081008800880087008500870086008500860084008500850084007900900092008f009100900079007800910079009300920090009f0091007800940092009300a0009f007800950092009400a100a0007800960092009500a200a1007800a300a100a200a400a300a2009700920096009800970096009900970098009a00920097009b009a0097009c009b0097009d009b009c009e009b009d00a600a700a800a800a700a900aa00a900a700a600a800ae00ae00af00a600ae00b300af00b000b100b200b400b000b200b500b000b400b500b400b700b600b500b700b600b700b900b800b600b900ba00bb00bc00bc00ca00ba00ba00bd00bb00bd00ba00bf00be00bd00bf00be00bf00c000c000c100be00c000c200c100c200c000c400c300c200c400c400c900c300c300c900c600c600c900c700c500c600c700c800c500c700c500c800ce00c500ce00cf00cf00ce00cd00cc00cd00ce00d000d100d200d0001d01d1001d01d400d100d300d000d200d400d500d100d300d2001501d600d100d500d700d600d500d800d600d700d900d800d700da00d900d700db00d900da00dc00db00da00dd00db00dc00de00dd00dc00df00de00dc00e000df00dc00e100df00e000e200e100e000e300e100e200e400e100e300e500e100e400e600e100e500e700e100e600e800e100e700e900e100e800ea00e900e800e8001c01ea00ea001c0119011c011a01190119011a011b01eb00e900ea00ec00e900eb00ed00e900ec00ee00e900ed00ef00e900ee00f000e900ef00f100e900f000f200e900f100f300e900f200f400f300f200f500f400f200f600f400f500f700f600f500f800f700f500f900f700f800fa00f900f800fb00fa00f800fc00fb00f800fd00fb00fc00fc00fe00fd00fd00fe00ff00fe000001ff000101ff000001020101010001030101010201fe000401000105010401fe0006010401050107010601050108010701050109010801050105011f0109011f011e0109011e010a0109010a010b0109010c0109010b010d010c010b010e010c010d010f010e010d0110010e010f01110110010f01120110011101130112011101140112011301150112011401d2001201150116011201d20017011601d20018011601170120012101220123012001220124012001230125012401230126012401250127012601250128012601270129012801270129012a01280128012a012b012c012b012a012d012c012a012e012c012d012f012e012d0130012e012f01310130012f013201300131013301320131013401350136013701340136013801340137013901380137013a01380139013b013a0139013c013a013b013d013c013b013e013f01400141013f013e01420140013f01430142013f014401420143014501420144014601450144014701450146014801470146014901470148014a01490148014b014c014d014e014b014d014f014b014e0150014f014e0151014f01500152015101500153015101520154015301520159015a015b015c0159015b015d0159015c015e0159015d015f015e015d0160015e015f01610160015f016101620160016001620163016401630162016501640162016601640165016701660165016801660167016901680167016a01680169016b016c016d016e016b016d016f016b016e0170016f016e0171016f01700172017101700173017101720174017301720175017601770178017601750179017a017b017c017a0179017d017c0179017e017d0179017f017d017e0180017d017f01810180017f01820181017f018301810182018401830182018501830184018601850184018701850186018801850187018901880187018a01880189018b018a0189018c018a018b018d018c018b018e018c018d018f018e018d0190018e018f019101920193019401920191019501940191019601940195019701960195019801960197019901980197019a01980199019b019a0199019c019a019b019d019a019c019e019d019c019f019e019c01a0019e019f01a101a0019f01a201a001a101a301a201a101a401a201a301a501a401a301a601a401a501a701a401a601a801a901aa01ab01a801aa01ac01a801ab01ad01a801ac01ae01ad01ac01af01ae01ac01b001ae01af01b101ae01b001b201b101b001b301b101b201b401b301b201b501b301b401b601b501b401b701b501b601b801b701b601b901b701b801ba01b901b801bb01b901ba01bc01bb01ba01bd01bb01bc01be01bd01bc01bf01c001c101c201bf01c101c301c201c101c401c201c301c501c401c301c601c401c501c701c601c501c801c601c701c901c801c701ca01c801c901cb01ca01c901cc01ca01cb01cd01cc01cb01ce01cc01cd01cf01ce01cd01d001ce01cf01d101d001cf01d201d001d101d301d201d101d401d201d301d501d401d301d601d401d501d701d601d501d801d901da01dc01da01d901dd01dc01d901d901de01dd01db01d801da01d801df01d901d801db012702e001df01d801db0126022702e101df01e001270226022502e201df01e101260224022502e301e201e101250224022202e401e301e101240223022202e501e301e401230220022202e601e501e401220220022102e701e601e401210220021f02e801e601e70120021e021f02e901e801e7011f021e021d02ea01e801e9011e021b021d02eb01ea01e9011c021d021b02ec01eb01e9011a021c021b02ed01eb01ec0115021a021b02ee01ed01ec0114021a021502ef01ed01ee01150212021402f001ed01ef01140212021302f101ed01f001130212021102f201ed01f101150216021202f301ed01f201170212021602160218021702190218021602f401ed01f30112020d021102f501ed01f40111020d021002f601ed01f50110020d020f02f701ed01f6010f020d020e02f801ed01f7010e020d020b020d020c020b020c020a020b020b020a0208020a02090208020902070208020802070204020702060204020602050204020502030204020402030201020302020201020202f80101020102f80100020002f801ff01ff01f801fe01fe01f801fd01fd01f801fc01fc01f801fb01fb01f801fa01fa01f801f901f901f801f701280229022a022b022902280232022a0229022c022b02280233022a0232022d022b022c023402330232022e022d022c023502330234022f022d022e0236023502340230022f022e0231022f02300237023502360238023702360239023a023b023b023a027702390264023a0276023b02770276027702780276027802790265026602640264023902650279027a0276027b027a02790239026e0265026d0265026e027a027b027c0280027c027b027c02800244026d026e026b026d026b026a026a026b026902440243027c027c0243027a0269026b0267026702680269024202430244024402450242026c02680267026b026c0267026b026e026c026e0239026c026c02600268026c0239024e0260026c024e023b024e02390276024e023b02610260024d0260024e024d0248024e0276024d024e0248027a0248027602430248027a0242024802430242024502480248024a024d0245024a0248023c023d023e0298023d023c023c023e025f02710298023c023c025f0271029802710297025f0270027102700297027102630270025f02970270029602960270026f02700263026f0296026f02940263025e026f0294026f025e0262025e0263025e0262025d025c0294025e025d025c025e0294025c0295025c025d025a0295025c025b025a025b025c023f0240024102410240028b02460240023f028c0241028b029e028c029f028c029e024102a00241029e024102a0023f023f02a00247023f0247024602a1024702a0024702490246024702a1024902a2024902a1024b02460249024902a2024c0249024c024b024b024c0275024f025002510250024f02520252025302500290024f0251024f029102520291024f029002910254025202530252025402920254029102730290025102510272027302900273028e0274027302720274028e0273028e0274028f025302540255025402920255025502560253029202570255025602550257029302570292025702930258025602570258025802590256027d027e027f0281027d028202990281029a027d02810283029902830281027d0283027e029b028302990283029b027e029b0284027e0284027f027e029c0284029b027f028402850284029c028502850286027f029c02870285028702860285029d0287029c0287028802860287029d0289028802870289029d028d028902880289028a028d028a028902a302a402a502a602a402a302af02a502a402a702a602a302b002a502af02a802a602a702b102b002af02a902a802a702b202b002b102aa02a802a902b302b202b102ab02a802aa02b402b202b302b502b402b302ac02ab02aa02ad02ab02ac02ae02ad02ac02b602b702b802b902b702b602c002b802b702ba02b902b602c102b802c002bb02b902ba02c202c102c002bc02bb02ba02c302c102c202bd02bb02bc02c402c302c202be02bd02bc02bf02bd02be02c502c402c202c602c402c502c702c602c502c802c602c702c902ca02cb02cc02c902cb02cd02c902cc02ce02cd02cc02cf02cd02ce02d002cf02ce02d102cf02d002d202d102d002d302d102d202d402d302d202d502d602d702d802d502d702d902d502d802da02d902d802db02d902da02dc02db02da02dd02db02dc02de02dd02dc02df02e002e102e202df02e102e302df02e202e402e302e202e502e302e402e602e502e402e702e502e602e802e702e602e902ea02eb02ec02e902eb02ed02e902ec02ee02ed02ec02ef02ed02ee02f002ef02ee02f102ef02f002f202f102f002f302f102f202f402f302f202f502f302f402f602f502f402f702f802f902f9026b03f702fa02f902f802f7026b036a03fb02fa02f8026b0368036a03fc02fa02fb026a0368036903fd02fc02fb02690368036703fe02fc02fd02680366036703ff02fe02fd026703660365030003fe02ff0266036403650301030003ff02650364036303020300030103640362036303030302030103630362036103040302030303620360036103050304030303610360035f0306030403050360035e035f030703060305035f035e035d030803060307035e035c035d030903080307035d035c035b030a03090307035c035a035b030b0309030a035b035a0359030c030b030a035a03580359030d030b030c035903580357030e030d030c035803560357030f030d030e0357035603550310030f030e0356035403550311030f03100355035403530312031103100354035103530313031103120353035103520314031103130352035103500315031403130351034f03500316031403150350034f034e031703160315034f034d034e031803160317034e034d034c031903180317034d034b034c031a03180319034c034b034a031b031a0319034b0349034a031c031a031b034a03490348031d031c031b034903470348031e031c031d034803470346031f031e031d0347034403460320031e031f03460344034503210320031f03450344034303220320032103440342034303230322032103430342034103240322032303420340034103250322032403410340033f0326032503240340033d033f032703250326033f033d033e032803270326033e033d033c032903270328033d033b033c032a03290328033c033b033a032b0329032a033b0339033a032c032b032a033a03390338032d032b032c033903370338032e032d032c033803370336032f032d032e0337033503360330032f032e0336033503340331032f0330033503330334033203310330033403330332033303310332036c036d036e036f036d036c0370036e036d036c03e0036f0371036e0370036f03e003df03720371037003e003de03df03730371037203df03de03dd03740371037303de03dc03dd03750374037303dd03dc03db03760374037503dc03da03db03770376037503db03da03d903780376037703da03d803d903790378037703d903d803d7037a0378037903d803d603d7037b037a037903d703d603d5037c037a037b03d603d403d5037d037c037b03d503d403d2037e037c037d03d403d303d2037f037e037d03d303d103d20380037e037f03d203d103d003810380037f03d103cf03d003820380038103d003cf03ce03830382038103cf03cd03ce03840382038303ce03cd03cc03850384038303cd03cb03cc03860384038503cc03cb03ca03870386038503cb03c803ca03880386038703ca03c803c903890388038703c903c803c7038a0388038903c803c603c7038b0388038a03c703c603c5038c038b038a03c603c403c5038d038b038c03c503c403c3038e038d038c03c403c203c3038f038d038e03c303c203c10390038f038e03c203c003c10391038f039003c103c003bf03920391039003c003be03bf03930391039203bf03be03bd03940393039203be03bc03bd03950393039403bd03bc03bb03960395039403bc03ba03bb03970395039603bb03ba03b903980395039703ba03b703b903990398039703b903b703b8039a0398039903b803b703b6039b039a039903b703b503b6039c039a039b03b603b503b4039d039c039b03b503b303b4039e039c039d03b403b303b2039f039c039e03b303b103b203a0039f039e03b203b103b003a1039f03a003b103af03b003a203a103a003b003af03ae03a303a103a203af03ad03ae03a403a303a203ae03ad03ac03a503a303a403ad03ab03ac03a603a503a403ac03ab03aa03a703a503a603ab03a903aa03a803a703a603aa03a903a803a903a703a803e103e203e303e403e203e103e503e403e103e603e403e503e2035804e303e30358045704580456045704570456045504560454045504550454045304540452045304530452045104520450045104510450044f0450044e044f044f044e044d044e044c044d044d044c044b044c044a044b044b044a0449044a0448044904490448044704480446044704470446044504460444044504450444044304440442044304430442043f04420440043f043f0440044104e703e803e903ea03e803e703eb03ea03e703ec03ea03eb03ed03ec03eb03ee03ec03ed03ef03ee03ed03f003ee03ef03f103f003ef03f203f003f103f303f203f103f403f303f103f503f303f403f603f303f503f703f603f503f803f603f703f903f803f703fa03f803f903fb03fa03f903fc03fa03fb03fd03fc03fb03fe03fc03fd03ff03fe03fd030004fe03ff0301040004ff030204000401040304020401040404020403040504060407040804060405040904080405040a04080409040b040a0409040c040a040b040d040c040b040e040c040d040f040e040d0410040e040f04110410040f041204100411041304120411041404120413041504140413041604140415041704160415041804160417041904180417041a04180419041b041a0419041c041a041b041d041c041b041e041c041d041f041e041d0420041e041f04210420041f042204200421042304240425042604240423042704260423042804260427042904280427042a04280429042b042a0429042c042a042b042d042c042b042e042c042d042f042e042d0430042e042f04310430042f043204300431043304320431043404320433043504340433043604340435043704360435043804370435043904370438043a04370439043b043a0439043c043a043b043d043c043b043e043c043d0459045a045b045a04d9045b045b04d904da045c045a0459045d045c0459045e045c045d045f045e045d0460045e045f04610460045f046204600461046304620461046404620463046504640463046604640465046704660465046804660467046904680467046a04680469046b046a0469046c046a046b046d046c046b046e046c046d046f046e046d0470046e046f04710470046f047204700471047304720471047404720473047504760477047804760475047904780475047a04780479047b047a0479047c047a047b047d047c047b047e047c047d047f047e047d0480047e047f04810480047f048204800481048304820481048404820483048504840483048604840485048704860485048804860487048904880487048a04880489048b048a0489048c048a048b048d048c048b048e048c048d048f048e048d0490048e048f04910490048f049204900491049304920491049404920493049504960497049804960495049904980495049a04980499049b049a0499049c049a049b049d049c049b049e049c049d049f049e049d04a0049e049f04a104a0049f04a204a004a104a304a204a104a404a204a304a504a404a304a604a404a504a704a604a504a804a604a704a904a804a704aa04a804a904ab04aa04a904ac04aa04ab04ad04ac04ab04ae04ac04ad04af04ae04ad04b004ae04af04b104b004af04b204b004b104b304b204b104b404b204b304b504b404b304b604b404b504b704b604b504b804b604b704b904ba04bb04bc04ba04b904bd04bc04b904be04bc04bd04bf04be04bd04c004be04bf04c104c004bf04c204c004c104c304c204c104c404c204c304c504c404c304c604c404c504c704c604c504c804c604c704c904c804c704ca04c804c904cb04ca04c904cc04ca04cb04cd04cc04cb04ce04cc04cd04cf04ce04cd04d004ce04cf04d104d004cf04d204d004d104d304d204d104d404d204d304d504d404d304d604d404d504d704d604d504d804d604d704 - m_VertexData: - serializedVersion: 3 - m_VertexCount: 1243 - m_Channels: - - stream: 0 - offset: 0 - format: 0 - dimension: 3 - - stream: 0 - offset: 12 - format: 0 - dimension: 3 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 24 - format: 0 - dimension: 2 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - - stream: 0 - offset: 0 - format: 0 - dimension: 0 - m_DataSize: 39776 - _typelessdata: 4ca7593f7829353dd61d81bfca54113fd42b453fa1d694be6744793f333313bf1f81573fce1a3cbd98be85bf77be1f3fd9ce47bfbf0e1c3db7627f3f469416bfab93573f842a353d8c2b86bfa01a1f3fd9ce473f280f8b3d6744793f94f616bfc0045a3fce1a3cbd2eca80bf44fa0d3f2b1845bf6ea3a1beb7627f3f8a1f13bf359b5f3f7829353d83bf7bbf22fdb63e2f6e433fecc009bf6210783f57ecafbdc0045a3fce1a3cbd2eca80bf44fa0d3f2b1845bf6ea3a1be13f2713fbf0e9cbd4ca7593f7829353dd61d81bfca54113fd42b453fa1d694be6210783f2cd49abd423e603fce1a3cbd2d5b7bbfaeb6a23e4faf44bf3f350ebf13f2713fea95b2bded9c663f842a353db03b79bfcdcccc3d5227403f903127bf6210783f3a92cbbd29426a3fce1a3cbdd7d978bf54e325bd99bb46bf250621bf13f2713f3ee8d9bd5ad66d3f842a353d349d79bf705f47bec5fe423f3f351ebf6210783fd509e8bda0c4733fce1a3cbdbb9a7cbf6f12c3bec5fe42bf6b2b06bf13f2713fdbf9febde882763f842a353d6c077fbf62a1f6be27c2463fc520d0be6210783fb7d100be3e5e783fce1a3cbd401781bfcba115bf522740bf48bf9dbed2007e3f5f988cbea0c4733fce1a3cbdbb9a7cbf6f12c3bec5fe42bf6b2b06bff7067f3f5f988cbee882763f842a353d6c077fbf62a1f6be27c2463fc520d0be8d977e3fb8afa3be71737a3f842a353de04884bf2f6e23bf98dd433f9fcdaabdba497c3fb8afa3beb2837a3fce1a3cbd5da884bfdd2426bf257542bf567d2ebd4d157c3f5f988cbeb98a793f842a353d6efa87bfaed81fbf0ebe403f6132553e355e7a3fb8afa3bee65d793fce1a3cbdde3988bf7fd91dbf0ebe40bf8cb96b3e713d7a3f5f988cbe41f3753f7829353dde1d8bbfa089f0be6a4d433f2f6ee33e6744793fb8afa3be8b6e753fce1a3cbd35638bbf42cfe6be2f6e43bf04e7ec3e6744793f5f988cbe2e8e6e3f842a353d28818dbf386744be2b18453f36cd1b3ff6287c3fb9fc87be8b6e753fce1a3cbd35638bbf42cfe6be2f6e43bf04e7ec3ecd3b7e3ff7e461be41f3753f7829353dde1d8bbfa089f0be6a4d433f2f6ee33ead697e3fb9fc87be23d76d3fce1a3cbd2c9d8dbf1ea728bed42b45bf48bf1d3f16fb7b3ff7e461befc55643f842a353dbcb08dbfe8d92c3ed9ce473f1e161a3f07ce793fb9fc87be7784633fce1a3cbd9f908dbf3a234a3ed9ce47bff5db173fd0b3793ff7e461befeba5b3f842a353d5ad78abfd9cef73ea60a463f8e75d13e6744793f6de71bbf7784633fce1a3cbd9f908dbf3a234a3ed9ce47bff5db173fb7627f3fdbf91ebffc55643f842a353dbcb08dbfe8d92c3ed9ce473f1e161a3f6744793fd7341fbf57425b3fce1a3cbdb58c8abf857c003fa60a46bfc217c63eb7627f3fac8b1bbfbfb62abfce1a3cbd035d8fbfa16773bef2412fbfdc6830bf80b7703ff93176bf3d2a22bf842a353d1e8c90bfdaacfabd24b92c3fa8573abf31996a3f13f271bf48171fbfce1a3cbd87a690bf4faf94bd295c2fbf0c9339bf80b7703ff77570bf9c5133bf842a353d9a788dbf107ab6bea4702d3fddb524bf31996a3f355e7abfbe1634bfce1a3cbdaf268dbf09f9c0be57ec2fbfdbf91ebf80b7703f2cd47abf755b3ebfce1a3cbd234a89bf547404bf48bf2dbf5d6d05bf80b7703fb22e7ebfbe853dbf842a353d109489bf7fd9fdbec9e52f3fb9fc07bf31996a3f89d27ebf005544bf842a353d9ca885bf7e8c19bf9b552f3f7dd0d3beb7627f3f1e167abfbe853dbf842a353d109489bf7fd9fdbec9e52f3fb9fc07bfb7627f3f3f357ebf755b3ebfce1a3cbd234a89bf547404bf48bf2dbf5d6d05bf6744793f4d847dbf9b1f47bfce1a3cbdb16b83bf651922bff2412fbffed4b8be6744793f4bc877bf7f2e4abf7829353d4a2780bf52492dbfb29d2f3f1ea788beb7627f3fe17a74bf793e4bbfce1a3cbd66167dbfd7342fbfae4731bf43ad69be6744793f73d772bff35750bf7829353d1fd869bf401331bf0f9c333f7b832fbef2417f3f5e4b68bf191e57bf7829353d78d24abff38e33bfbc74333f931804be643b7f3f759358bff62454bfce1a3cbd0ebd59bfce8832bf6a4d33bfd1221bbe6744793f09f960bf98a159bfce1a3cbdb8ce3bbf744634bf819533bf8941e0bd8351793f986e52bf09c35cbf7829353dce5325bf66f734bfb8af333fea95b2bdc0ec7e3f273148bf2f865ebfce1a3cbd910f16bfa24536bfc5fe32bf948785bd27a0793f29ed3dbf4aee60bfce1a3cbd5281c3be9a083bbf7b142ebf24977f3d6744793f86c924bf2c0d60bf7829353d60e401bfc28637bf9c33323f516b1abd0e4f7f3f744634bf9b9260bf7829353d6ea2bebeb5153bbf50fc283fd712323eb7627f3ff38e23bf3d2a22bf842a353d1e8c90bfdaacfabd24b92c3fa8573abf74b5353fb1502bbe069c293f9d2c353ded481dbf02bc053f61c3333f87a7f73e4ed1613f075f48bfab93573f842a353d8c2b86bfa01a1f3fd9ce473f280f8b3d1904463f02bc65bffeba5b3f842a353d5ad78abfd9cef73ea60a463f8e75d13e46b6433f7e1d68bffc55643f842a353dbcb08dbfe8d92c3ed9ce473f1e161a3f5c8f423f7aa56cbfdf8c7e3f842a353d7e7290bf7b836f3e107a263f6c0939bf6ade413f7ac779bf2e8e6e3f842a353d28818dbf386744be2b18453f36cd1b3f01de423f539671bf41f3753f7829353dde1d8bbfa089f0be6a4d433f2f6ee33e014d443fd8f074bfb98a793f842a353d6efa87bfaed81fbf0ebe403f6132553e1904463f827376bf71737a3f842a353de04884bf2f6e23bf98dd433f9fcdaabd2bf6473f666676bf4ca7593f7829353dd61d81bfca54113fd42b453fa1d694beacad483fa60a66bf359b5f3f7829353d83bf7bbf22fdb63e2f6e433fecc009bf88854a3f029a68bfed9c663f842a353db03b79bfcdcccc3d5227403f903127bfe86a4b3f51da6bbf018a753f842a353d89ce72bf62a1063faf94353f3255f03eca54513fec2f6bbf5ad66d3f842a353d349d79bf705f47bec5fe423f3f351ebf03784b3fd26f6fbfe882763f842a353d6c077fbf62a1f6be27c2463fc520d0befe434a3f98dd73bfddb6833f7829353d3f5383bf60e5203f569f2b3fe7fbc93efed4483f5bd37cbf7f2e4abf7829353d4a2780bf52492dbfb29d2f3f1ea788bec8073d3f7ac7a9bdf35750bf7829353d1fd869bf401331bf0f9c333f7b832fbe3333433f6f8184bd005544bf842a353d9ca885bf7e8c19bf9b552f3f7dd0d3be1a513a3f39d6c5bdbe853dbf842a353d109489bf7fd9fdbec9e52f3fb9fc07bfb072383fd3bce3bd9c5133bf842a353d9a788dbf107ab6bea4702d3fddb524bf27c2363ff01608be5a2e833f842a353ddc9d8dbf5c8f123ff085293f508df7bebc74433f88637dbf1ae0843f842a353dc3478abf4772393f95d4293f000040be984c453fc4b17ebf6d03853f842a353df48986bf0d713c3f910f2a3f4bea043eab3e473f3b707ebff2cfac3d842a353dbc751ebfcb10c7bd423e383f72f92f3fdf4f5d3f5bd3fcbe7b86803e842a353da7b115bfec5138bede93373ff1632c3fae47613fb45915bfa3c8ba3e842a353d594c0cbfb37b72bede93373fd9ce273f2fdd643f014d24bf63b8023f7829353d97c4f9beaeb622bef1f42a3fc7293a3f302a693f1ceb32bf8cbc0c3f7829353dc8eafebe27a0e93e363c2d3fb3ea133f8cdb683f029a38bf79b0093f2d23353d9961fbbe569f8b3efb5c2d3f4df32e3f302a693fe25837bf55fa053f5226353d2a90f9be52499d3da4702d3f96433b3f2c65693f4bea34bf0d7001bd7829353d1af920bfa69bc4bc5452373fea95323f71ac5b3fed0ddebe48fceabd7829353d98fb20bfabcfd53c423e383fe09c313f431c5b3f70cec8be69e57ebe7829353da9a51dbfd34de23d302a393f567d2e3fd1225b3f30bba7be191e57bf7829353d78d24abff38e33bfbc74333f931804be083d4b3fa32339bdafb0b8be7829353d333717bfb840423e3480373fa8c62b3f31085c3f674489be09c35cbf7829353dce5325bf66f734bfb8af333fea95b2bd865a533fb1bfecbc018ae1be7829353dd09d10bfd8f0743e9565383f7dae263fa8355d3f8cdb68be2c0d60bf7829353d60e401bfc28637bf9c33323f516b1abd44fa5d3fac8b5bbc190111bf7829353dfb7602bfb8afa33e8351393fb6841c3f0000603f302a29be23f635bf7829353d6c26d7be567dce3e2731383f0ebe103f4bea643f7daeb6bd9b9260bf7829353d6ea2bebeb5153bbf50fc283fd712323e99bb663f0ad7a3bb57054ebf842a353d0dabb0be14d0c43e48bf2d3fe02d203f151d693f9b551fbd1f9f5cbf7829353d853fb3beab3ed7be2d432c3f36cd1b3f226c683f5bb13fbc4da256bf7829353de5d1adbe08ac1cbe31992a3f2cd43a3f302a693f0e4fafbc952a51bf6b28353dc9e7adbed26f1f3ed5e72a3fc3643a3f9e5e693f6e3400bd68af5ebf7829353d564ab7bed50918bf08ac2c3f499de03ed9ce673f2497ffbb6610813fce1a3cbd077b8fbf0309ca3e234a2bbf053421bf80b7703f92cbffbd2ba57b3fce1a3cbd8eae90bffe65f73de86a2bbfc8983bbf80b7703fcdcc0cbedf8c7e3f842a353d7e7290bf7b836f3e107a263f6c0939bf31996a3f394507be5a2e833f842a353ddc9d8dbf5c8f123ff085293f508df7be31996a3f8d97eebd4e5f833fce1a3cbd55508dbf3480173fbf0e2cbf0ad7e3be80b7703f448becbd71ca843fce1a3cbd74958abff5db373f55302abfce8852be13f2713f986e12be4e5f833fce1a3cbd55508dbf3480173fbf0e2cbf0ad7e3be13f2713f5f980cbe5a2e833f842a353ddc9d8dbf5c8f123ff085293f508df7be6210783f5f980cbe1ae0843f842a353dc3478abf4772393f95d4293f000040be6210783f865a13be8010853fce1a3cbd3cda86bf68913d3f5af529bf3d9bd53d13f2713f89d21ebe6d03853f842a353df48986bf0d713c3f910f2a3f4bea043e6210783fe5f21fbe0fd0833fce1a3cbd757583bffc18233f7ffb2abf66f7c43e13f2713f1ff42cbeddb6833f7829353d3f5383bf60e5203f569f2b3fe7fbc93e6210783f68912dbe018a753f842a353d89ce72bf62a1063faf94353f3255f03e1904763f0b2468bedbe0283f0c203cbd3d811cbfb459053fefc933bf226cf83ed712723f371ae0be069c293f9d2c353ded481dbf02bc053f61c3333f87a7f73e6210783f6e34e0be8cbc0c3f7829353dc8eafebe27a0e93e363c2d3fb3ea133fd509783fea0404bf3ae80a3fb5183cbdcb4dfcbec1cac13e992a28bf07f0263fa1f8713f4bea04bfdbe0283f0c203cbd3d811cbfb459053fefc933bf226cf83e3fc62c3f3f355ebe48171fbfce1a3cbd87a690bf4faf94bd295c2fbf0c9339bfd7a3003f2aa953bf1f81573fce1a3cbd98be85bf77be1f3fd9ce47bfbf0e1c3d933a113faaf1d2bdc0045a3fce1a3cbd2eca80bf44fa0d3f2b1845bf6ea3a1be98dd133f3b70cebd423e603fce1a3cbd2d5b7bbfaeb6a23e4faf44bf3f350ebfcba1153fc7bab8bd29426a3fce1a3cbdd7d978bf54e325bd99bb46bf250621bf9d80163f05a392bda0c4733fce1a3cbdbb9a7cbf6f12c3bec5fe42bf6b2b06bfc6dc153fd04458bd0fd0833fce1a3cbd757583bffc18233f7ffb2abf66f7c43e61c3133fa69b44bc3e5e783fce1a3cbd401781bfcba115bf522740bf48bf9dbe1895143f7b142ebdb2837a3fce1a3cbd5da884bfdd2426bf257542bf567d2ebd58ca123fe3c718bd8010853fce1a3cbd3cda86bf68913d3f5af529bf3d9bd53dd712123fed0dbebb57425b3fce1a3cbdb58c8abf857c003fa60a46bfc217c63e17d90e3f5c20c1bd7784633fce1a3cbd9f908dbf3a234a3ed9ce47bff5db173ff6970d3f091b9ebd2ba57b3fce1a3cbd8eae90bffe65f73de86a2bbfc8983bbf448b0c3f45d8f0bc23d76d3fce1a3cbd2c9d8dbf1ea728bed42b45bf48bf1d3f64cc0d3fd6566cbd8b6e753fce1a3cbd35638bbf42cfe6be2f6e43bf04e7ec3ebb270f3f34a234bde65d793fce1a3cbdde3988bf7fd91dbf0ebe40bf8cb96b3e60e5103f9a9919bd4e5f833fce1a3cbd55508dbf3480173fbf0e2cbf0ad7e3be1b9e0e3f075f18bc71ca843fce1a3cbd74958abff5db373f55302abfce8852bec520103f9fabadbbbe1634bfce1a3cbdaf268dbf09f9c0be57ec2fbfdbf91ebfdcd7013f3b705ebfbfb62abfce1a3cbd035d8fbfa16773bef2412fbfdc6830bf09f9003f7e8c59bf755b3ebfce1a3cbd234a89bf547404bf48bf2dbf5d6d05bf9ca2033f98dd63bf9b1f47bfce1a3cbdb16b83bf651922bff2412fbffed4b8bef46c063fa7e868bf793e4bbfce1a3cbd66167dbfd7342fbfae4731bf43ad69be8cdb083f1f856bbf7eaa2abece1a3cbd394420bf46b6733d55c138bfa089303fa60a263fc58f21bf7d2164bbce1a3cbda0c320bf787a25bd226c38bf2041313f022b273f36cd0bbff62454bfce1a3cbd0ebd59bfce8832bf6a4d33bfd1221bbef7e4113f4ed171bf8106abbece1a3cbdc63219bf7aa52c3e8cdb38bf1ac02b3ff0a7263ff93136bffa7efabece1a3cbd7ec80bbfde718a3efed438bf5305233f151d293f03784bbffc5124bfce1a3cbde6e7eebe4772b93ea32339bf2b87163f6d562d3faed85fbf5d3341bfce1a3cbdd3ddc5bed122db3e27c236bf9be60d3fe561313f5f076ebf80f158bfce1a3cbd1404afbe32778dbe03092abf4ed1313f41f1333f917e7bbf89614fbfce1a3cbd61faaebe1b0da03ecc7f28bf0e4f2f3fe63f343fd0d576bf56d553bfce1a3cbd4563adbe4260e53b08ac2cbf3b013d3f1d5a343f151d79bf98a159bfce1a3cbdb8ce3bbf744634bf819533bf8941e0bd8351193fdd2476bf2f865ebfce1a3cbd910f16bfa24536bfc5fe32bf948785bdaf25243f48e17abff86b5ebfce1a3cbd058cb6be91ed0cbfccee29bfe09c013fc5fe323f48bf7dbf622f60bfce1a3cbde09dbcbee5f22fbf76e02cbfde02893ed712323fa9a47ebf4aee60bfce1a3cbd5281c3be9a083bbf7b142ebf24977f3d4013313f6dc57ebfcc7b0c3ece1a3cbd3b381cbf000000bed50938bf840d2f3f8716293fc74bf7bed522823ece1a3cbd018a15bf925c3ebe621038bf76712b3fa3012c3f2bf6d7bea60fd53ece1a3cbd4d4c07bfc13983be545237bff931263f933a313f29edadbe3ae80a3fb5183cbdcb4dfcbec1cac13e992a28bf07f0263f0612343fd7a390beb729023fce1a3cbd0211fabe34a234beffb22bbfb072383f3d2c343f23db99be9eed053fc1193cbd7193f9bee3c7983df1632cbfba493c3faa60343f068195be6610813fce1a3cbd077b8fbf0309ca3e234a2bbf053421bffb5c0d3f295c8fbc4da256bf7829353de5d1adbe08ac1cbe31992a3f2cd43a3f0c027b3f68b32abe1f9f5cbf7829353d853fb3beab3ed7be2d432c3f36cd1b3f448b7c3fd5e72abe80f158bfce1a3cbd1404afbe32778dbe03092abf4ed1313fc8987b3f759358bef86b5ebfce1a3cbd058cb6be91ed0cbfccee29bfe09c013f711b7d3f3e7958be68af5ebf7829353d564ab7bed50918bf08ac2c3f499de03e52497d3f31992abef86b5ebfce1a3cbd058cb6be91ed0cbfccee29bfe09c013ff54a793f2eff21bf68af5ebf7829353d564ab7bed50918bf08ac2c3f499de03eb7627f3f2eff21bf622f60bfce1a3cbde09dbcbee5f22fbf76e02cbfde02893e6744793f38f822bf56d553bfce1a3cbd4563adbe4260e53b08ac2cbf3b013d3ffe437a3f759358be952a51bf6b28353dc9e7adbed26f1f3ed5e72a3fc3643a3fecc0793f68b32abe57054ebf842a353d0dabb0be14d0c43e48bf2d3fe02d203fb7627f3f7d3f05bf952a51bf6b28353dc9e7adbed26f1f3ed5e72a3fc3643a3f295c7f3f341106bf89614fbfce1a3cbd61faaebe1b0da03ecc7f28bf0e4f2f3f6744793f74b505bf89614fbfce1a3cbd61faaebe1b0da03ecc7f28bf0e4f2f3f637f793f759358be5d3341bfce1a3cbdd3ddc5bed122db3e27c236bf9be60d3ff085793ff54af9be23f635bf7829353d6c26d7be567dce3e2731383f0ebe103fd7347f3f728aeebe190111bf7829353dfb7602bfb8afa33e8351393fb6841c3fd7347f3fa7e8c8befc5124bfce1a3cbde6e7eebe4772b93ea32339bf2b87163fd578793f1ac0dbbe018ae1be7829353dd09d10bfd8f0743e9565383f7dae263f12147f3fbbb8adbefa7efabece1a3cbd7ec80bbfde718a3efed438bf5305233fd578793fc74bb7be8106abbece1a3cbdc63219bf7aa52c3e8cdb38bf1ac02b3f4703783f5c8f12bfafb0b8be7829353d333717bfb840423e3480373fa8c62b3f4547723fc4b10ebf018ae1be7829353dd09d10bfd8f0743e9565383f7dae263fb840723fb07208bf69e57ebe7829353da9a51dbfd34de23d302a393f567d2e3f8126723f083d1bbf48fceabd7829353d98fb20bfabcfd53c423e383fe09c313f4547723f910f2abf7eaa2abece1a3cbd394420bf46b6733d55c138bfa089303f67d5773ffd8724bf7d2164bbce1a3cbda0c320bf787a25bd226c38bf2041313f4703783f3e7938bf0d7001bd7829353d1af920bfa69bc4bc5452373fea95323f4547723f41f133bff2cfac3d842a353dbc751ebfcb10c7bd423e383f72f92f3f8126723fcac342bf7b86803e842a353da7b115bfec5138bede93373ff1632c3f0a68723fb5a659bfcc7b0c3ece1a3cbd3b381cbf000000bed50938bf840d2f3f30bb773f19e248bf63b8023f7829353d97c4f9beaeb622bef1f42a3fc7293a3fbc05723f03787bbfa3c8ba3e842a353d594c0cbfb37b72bede93373fd9ce273faeb6723fba6b69bfa60fd53ece1a3cbd4d4c07bfc13983be545237bff931263f4bc8773f20d26fbfb729023fce1a3cbd0211fabe34a234beffb22bbfb072383f6210783fc8987bbfd522823ece1a3cbd018a15bf925c3ebe621038bf76712b3f6210783fa7e858bffa7efabece1a3cbd7ec80bbfde718a3efed438bf5305233f6210783f075f08bf79b0093f2d23353d9961fbbe569f8b3efb5c2d3f4df32e3f82e2773f3d9b05bf3ae80a3fb5183cbdcb4dfcbec1cac13e992a28bf07f0263fd509783f17d97ebf79b0093f2d23353d9961fbbe569f8b3efb5c2d3f4df32e3f6519723fe0be7ebf9eed053fc1193cbd7193f9bee3c7983df1632cbfba493c3fd509783f9fab7dbf55fa053f5226353d2a90f9be52499d3da4702d3f96433b3f13f2713f88637dbfa91338bfb8e72dbf990e39bf39d605bf522700bfd7a330bfaa82913e1d3887be1a4f4cbf231225be780e39bf1a51da3de3c7283f728a3ebfcb10a73e13f2a1be85d16c3dd33127be780e39bf4547f23df5b93a3f287e2cbf9fcd8a3eff21ddbedc7f18bff56949bf780e39bf08acdcbe41f113bf736831bff085893ec3f588beaa9e64bf71e5dcbe780e39bf66f724bfc217a6be204131bf8941a03efa7e8abe424072bf66859abe880e39bf32e62ebff90f69bee09c31bf0bb5a63e17d98ebe44fb58bfd42814be780e39bf80b7a03e9d80163fa4df3ebf6744a93e098a9fbe39d279bf615141be780e39bf061234bfce1911bed34d32bfc3d3ab3e7dd093be96eb61bf1920f1bd780e39bf7d3ff53eaa60f43e5f983cbfcc5dab3e52b89ebe568067bf71e3b6bd780e39bf8638163f8cdba83edf4f3dbf711bad3edbf99ebe30297ebf860296bd780e39bf7e1d38bf645d5cbd575b31bfbc96b03e3ee899bec2896cbfea931cbd780e39bf0681253ff241cf3d539641bf9790af3e09f9a0be33537ebffcffb83d780e39bf39b438bf0000803da08930bf2b87b63eaf25a4be1ea46bbf4b56c53c780e39bf26e4233faf9425be894140bf33c4b13e66f7a4be35cf65bffef3943d780e39bfa323093f0bb5c6be72f93fbf58cab23e0c93a9befcfe59bf53cdec3d780e39bf0c93a93ebc9610bfaa8241bf3cbdb23e295cafbe19e575bf2b6e8c3e780e39bf789c32bfca32443e80b730bff628bc3e85ebb1befd324cbf25ad083e780e39bf8716d93d70ce28bf728a3ebf17b7b13e5839b4bea03567bf62dbda3e780e39bffe6527bf910f9a3e8ab031bfc9e5bf3eb762bfbe43e458bf4ca5073f780e39bffaed1bbf9c33c23e615432bfdcd7c13e27a0c9bed7bf47bfa62b203f670e39bf3bdf0fbf9d80e63e6ea331bf8a1fc33e7446d4be37a633bfdf52363faa0e39bf5c2001bf21b0023f454732bfd3bcc33e2d21dfbea2461dbfa7254a3f670e39bf6f12e3be3c4e113f539631bf9ca2c33e0309eabea5d7febee1275e3f670e39bfa245b6bef775203f006f31bf93a9c23e22fdf6be4627b3be5c02703f880e39bfa54e80be9fab2d3f29cb30bfa54ec03e58ca02bf1990493f82fd073e780e39bfbada0abe2aa923bf33c441bf234a7b3e5c2011bf11c5ac3e1902703f880e39bf13f2813e363c2d3feeeb30bf7ac7a93ebe9f1abfe771f83ee1275e3f670e39bf2b87b63ee02d203f6ea331bf2575a23e849e1dbf94121a3f13284a3f670e39bfae47e13ec1ca113fa5bd31bf1ac09b3e643b1fbf3178303fa857363f780e39bf772d013f21b0023fb84032bf3411963ec9e51fbf658f443f0b28203f880e39bfc9e50f3f54e3e53e4ed131bf3255903e3bdf1fbf64b1553fa8a7073f780e39bffaed1b3f9c33c23ed34d32bf4ca68a3ef2411fbf5b06643f53cfda3e880e39bf42cf263f76719b3e13f231bf9d11853eb6f31dbf24f16e3f2f30a33e670e39bf567d2e3f9d11653eee5a32bfb7627f3e4d151cbfd106783fcd783b3e780e39bf143f363f6e34003e60e530bf6a4d733e50fc18bf3df2573ff65edc3d780e39bf8126c2befa7e0abf522740bfc6dc753e615412bf6686613f9a5f8d3d780e39bfff210dbfb684bcbeceaa3fbfd7a3703e0a6812bf07d07b3f0e87a53c780e39bfacad383f96430b3cae4731bf6666663ee17a14bf2bfb663f253ca13c780e39bf9d1125bf97ff10bea54e40bf569f6b3ee09c11bf035d673f2d4231bd780e39bf4faf24bf910ffa3d1c7c41bfe71d673e5bb10fbf0003793fcc6212be780e39bf94f6363f8ab0e1bdb7d130bf083d5b3e4df30ebf8924623f4370bcbd780e39bf744614bfe02db03e1b2f3dbfc1a8643e4d840dbf12685c3fbfbbf5bd780e39bfc5fef2bed044f83ebf0e3cbfaf25643edfe00bbffb1e553f2f3211be780e39bf68b3aabe3411163fc8073dbf5474643ee3360abf910e6f3f807f9abe780e39bfbb272f3f54e365be6ea331bff31f523e39b408bfd559493f150324be780e39bfb98d06bea245263fe9b73fbff931663ef01608bfca6b613f71e5dcbe780e39bf0b46253fe5d0a2bea5bd31bf832f4c3e58ca02bf0282193fb9e127be780e39bf9be61dbe31993a3f11c72abf6ade713e575b01bf079af73e0adb8fbe780e39bfc7293abf992a183eac8b2bbf7cf2703ef38ef3be14400d3f64933cbe780e39bf499dc0be5396213f12a52dbf0f9c733e5b42febec381003f33a670be780e39bfbc7423bf910fba3e2db22dbf3333733e6b9af7be6e17063f028053be880e39bf39d605bf5305033fe4832ebf7dd0733e48e1fabed6aaf53e641ee1be780e39bf061224bf4f40133eb30c41bf8bfd653e151de9be5c394f3f5a660dbf780e39bf2bf6173ff31fd2be933a31bfb9fc473ea323f9be6ec3e83e50e0fdbe780e39bf88850abf9c33c23e522740bf2f6e633e5dfee3be1406d53e408408bf780e39bfd191bcbe711b0d3fceaa3fbfc139633ef706dfbebebdbb3e22fa0dbf780e39bf3fc61cbe2653253fee7c3fbf66f7643efe43dabe842aa53e2bbd0ebf780e39bf508d173d3fc62c3f08ac3cbfc286673e22fdd6be1daff83e89ea59bf780e39bfabcfb53e539621bf128330bfde93473e8e06d0be15adac3e0f7d6bbf780e39bfc05b803e32772dbf97ff30bf832f4c3ee561c1beb728933e2ffc0cbf780e39bf643b5f3e211f243f645d3cbf8c4a6a3ef4fdd4be581a383e54e276bf780e39bf3a920b3e107a36bfc52030bf865a533e61c3b3be849b7c3e8cdc07bf780e39bf7958c83e7ac7093fa01a3fbf567d6e3ed881d3bec8b68c3cb7287bbf780e39bf5305a33cacad38bf933a31bf08ac5c3e705fa7be304c563e8ffafbbe780e39bfc4420d3fdaacba3e8e0640bf46b6733e6a4dd3be999ffbbd967779bf780e39bf7a36abbdde9337bf5c2031bf6666663eed0d9ebe76523f3efeb9e0be780e39bfb459253f4d840d3e6e3440bf35ef783e2b18d5be118c73be511474bf780e39bfd50928bee5d032bf615432bf44696f3ec74b97be7710b3be527d6bbf880e39bfae4781be5f982cbf8ab031bfd93d793e17b791be4f79f4be58ad5cbf780e39bf3b70aebe454722bfa5bd31bf1ceb823e0d718cbec5723b3e97758fbe780e39bfde713a3f1d38273ec3642abf4182823ead69debe75cde43d927839be780e39bf1c7cc13ed8f0243fe3362abf9565883e2497dfbeaca8213edc6765be780e39bf5f291b3f91eddc3e280f2bbf2653853e8941e0bee84b4f3e11c8793f780e39bf1a511a3e211f343fc1ca31bfceaaaf3e1d3817bfb54e9cbd86207f3f780e39bfcb1047bd302a393f325530bf637fb93e091b0ebffb76b23db4777e3f780e39bf492e7f3df5db373f006f31bf211fb43ed3bc13bf65175cbe65c8793f780e39bf76711bbe9318343fa5bd31bf8863bd3e39b408bf3f6f52bfed630dbf990e39bf67d517bfea95d2be772d31bf637f993e82e287be98fa193f56b845bf340e39bf006fe13e539611bf6ade31bf9487453e91eddcbec6dd343feaeb2dbf880e39bf7d3f053f4e6200bf60e530bfcba1453e0309eabec381003f33a670be780e39bfbc7423bf910fba3e2db22dbf8351c93e143f26bf079af73e0adb8fbe780e39bfc7293abf992a183eac8b2bbf9e5ec93e1d5a24bf3480f73ec1738fbeb21213bfde713abf643b1f3ebada2a3fd7a3d03e547424bf81ed003f98db6dbeb21213bfc58f21bf65aac03e12a52d3fbc96d03e107a26bf6e17063f028053be880e39bf39d605bf5305033fe4832ebf6744c93e82e227bfe3aa063ff83251beb21213bfc5fe02bf54e3053fe4832e3fbc96d03ef01628bf14400d3f64933cbe780e39bf499dc0be5396213f12a52dbf6744c93e7e8c29bfbbf20d3fe7e33abeb21213bf5af5b9be4a7b233f9fab2d3fa089d03e7ac729bf0282193fb9e127be780e39bf9be61dbe31993a3f11c72abf6744c93ec3d32bbfeeb4193f271628beb21213bfd0d516be1a513a3f03782b3fa089d03efaed2bbf118e493f763624beb21213bf71ac0bbe2b18253f1283403ff2b0d03eaaf132bfd559493f150324be780e39bfb98d06bea245263fe9b73fbf9e5ec93e73d732bffb1e553f2f3211be780e39bf68b3aabe3411163fc8073dbf9e5ec93e810435bfc4d1553f2c820fbeb21213bf0ebeb0be5839143fe4143d3ff2b0d03eef3835bf12685c3fbfbbf5bd780e39bfc5fef2bed044f83ebf0e3cbf9e5ec93e7dae36bf87fb5c3f0c1ff1bda11213bfd044f8bec5fef23ebf0e3c3fd7a3d03eebe236bf8924623f4370bcbd780e39bf744614bfe02db03e1b2f3dbf9e5ec93eec5138bf8a90623f01dab6bdb21213bf863816bf8cdba83edf4f3d3fd7a3d03e598638bf035d673f2d4231bd780e39bf4faf24bf910ffa3d1c7c41bf6744c93e8c4a3abfb399673fde921cbdb21213bf068125bf6076cf3d5396413fa089d03efa7e3abf2bfb663f253ca13c780e39bf9d1125bf97ff10bea54e40bfd712f23ee8d9acbd035d673f2d4231bd780e39bf4faf24bf910ffa3d1c7c41bff31ff23e11c7babdb399673fde921cbdb21213bf068125bf6076cf3d5396413f9fcdea3e5af5b9bd42b4663fbd34c53cb21213bf26e423bfaf9425be8941403f83c0ea3e7a36abbd6686613f9a5f8d3d780e39bfff210dbfb684bcbeceaa3fbfbc05f23e2c6599bd1ade603f55fb943dc21213bf55300abf9d11c5beceaa3f3f83c0ea3ebec197bd3df2573ff65edc3d780e39bf8126c2befa7e0abf522740bfbc05f23e02bc85bd97fd563f01a5e13db21213bfbe9fbabec4420dbf8e06403f83c0ea3e931884bd1990493f82fd073e780e39bfbada0abe2aa923bf33c441bff31ff23e426065bdc85c493f9bc9073eb21213bfb81e05bed8f024bf7cf2403f9fcdea3e8a8e64bd1990493f82fd073e780e39bfbada0abe2aa923bf33c441bfec2ffb3e8fc2b5be59504cbff9d9083eb21213bf6076cf3d151d29bf3b703e3ff38ef33e97ff10befd324cbf25ad083e780e39bf8716d93d70ce28bf728a3ebf48e1fa3e4ed111bec85c493f9bc9073eb21213bfb81e05bed8f024bf7cf2403fb3eaf33ecf66b5befcfe59bf53cdec3d780e39bf0c93a93ebc9610bfaa8241bf48e1fa3ef01608be1bb956bf44f8f73db21213bf711b8d3eb22e1ebf287e3c3f0f9cf33e95d409be16845ebf645cd13db21213bf08acdc3e74b505bf645d3c3f2aa9f33edc4603be35cf65bffef3943d780e39bfa323093f0bb5c6be72f93fbf7ffbfa3e6c09f9bd1d7766bfd5598d3db21213bf567d0e3fbe9fbabea01a3f3f46b6f33e8fc2f5bd1ea46bbf4b56c53c780e39bf26e4233faf9425be894140bfb515fb3e42cfe6bd29eb6bbfec4ca13cb21213bf9d11253f97ff10bea54e403f7dd0f33ed3bce3bdc2896cbfea931cbd780e39bf0681253ff241cf3d539641bfec2ffb3e1ac0dbbd014d6cbf904a31bdb21213bf4faf243ffe43fa3d1c7c413fb3eaf33eac1cdabd568067bf71e3b6bd780e39bf8638163f8cdba83edf4f3dbf3411d63e50fc98bdc2896cbfea931cbd780e39bf0681253ff241cf3d539641bf3411d63e569fabbd014d6cbf904a31bdb21213bf4faf243ffe43fa3d1c7c413ffbcbce3ee7fba9bd341467bffb74bcbdb21213bf7446143ffb3ab03e1b2f3d3fe0bece3e2bf697bd96eb61bf1920f1bd780e39bf7d3ff53eaa60f43e5f983cbf3411d63e71ac8bbd005861bf5ebef5bdb21213bf295cef3e23dbf93e96b23c3fe0bece3ede718abd44fb58bfd42814be780e39bf80b7a03e9d80163fa4df3ebf3411d63efe6577bdedf457bf554f16beb21213bf8716993e7593183f6dc53e3ffbcbce3e211f74bd1a4f4cbf231225be780e39bf1a51da3de3c7283f728a3ebf4f1ed63e3d0a57bd48314cbfd6e524beb21213bf840dcf3d7ac7293f7fd93d3ffbcbce3e62a156bd1a4f4cbf231225be780e39bf1a51da3de3c7283f728a3ebf2bf6f73e80b760bf23a46e3dbc0427beb21213bff9a0e73d7ffb3a3f0d712c3f643bff3e295c7fbf85d16c3dd33127be780e39bf4547f23df5b93a3f287e2cbf2bf6f73e492e7fbf48314cbfd6e524beb21213bf840dcf3d7ac7293f7fd93d3f643bff3e60e560bf75cde43d927839be780e39bf1c7cc13ed8f0243fe3362abf17b7d13e348027bf85d16c3dd33127be780e39bf4547f23df5b93a3f287e2cbf33c4d13e0b4625bf23a46e3dbc0427beb21213bff9a0e73d7ffb3a3f0d712c3f6c09d93e426025bf629dca3d5ce333beb21213bf19e2983e5a642b3f96212e3f50fcd83e94f626bfd579043eba2f47beb21213bfc4b1ee3ed7a3103fe9482e3f50fcd83ee78c28bfaca8213edc6765be780e39bf5f291b3f91eddc3e280f2bbffca9d13ec3642abf384c243e713a69beb21213bf44691f3f3b70ce3e71ac2b3f35efd83edaac2abfc5723b3e97758fbe780e39bfde713a3f1d38273ec3642abffca9d13ee4142dbf213f3b3ebedc8fbeb21213bfe3363a3f643b1f3e431c2b3f35efd83e1b2f2dbf8e1e3f3e9f20e1beb21213bf0612243f4f40133eb30c413f6c09d93e8fe432bf76523f3efeb9e0be780e39bfb459253f4d840d3e6e3440bf33c4d13e58ca32bf304c563e8ffafbbe780e39bfc4420d3fdaacba3e8e0640bf33c4d13e984c35bf1ced583e04e2fdbeb21213bffa7e0a3f9c33c23e5227403f6c09d93eaf9435bf849b7c3e8cdc07bf780e39bf7958c83e7ac7093fa01a3fbf17b7d13e4bc837bf9c35803ee88408bfb21213bf7502ba3edb8a0d3f72f93f3f6c09d93e621038bfb728933e2ffc0cbf780e39bf643b5f3e211f243f645d3cbf17b7d13eecc039bf23159e3e46980ebfb21213bf6210d83d9d11253f4ed1413f35efd83e6c783abf842aa53e2bbd0ebf780e39bf508d173d3fc62c3f08ac3cbffca9d13ebada3abfbebdbb3e22fa0dbf780e39bf3fc61cbe2653253fee7c3fbfee7cff3ec3f538bf842aa53e2bbd0ebf780e39bf508d173d3fc62c3f08ac3cbfee7cff3edaac3abf23159e3e46980ebfb21213bf6210d83d9d11253f4ed1413fb537f83e158c3abfde01be3ea2b20dbfb21213bfe6ae25be26e4233f8941403f992af83ee3c738bf1406d53e408408bf780e39bfd191bcbe711b0d3fceaa3fbfd26fff3e469436bf31eed63e90db07bfb21213bfb81ec5bec7290a3fceaa3f3f992af83e4b5936bf6ec3e83e50e0fdbe780e39bf88850abf9c33c23e522740bfd26fff3e931834bf0514ea3ea5f9fbbeb21213bfc4420dbfdaacba3e8e06403f992af83e26e433bfd6aaf53e641ee1be780e39bf061224bf4f40133eb30c41bfee7cff3efca931bf0491f53efab6e0beb21213bfb45925bf4d840d3e6e34403fb537f83ee09c31bfd6aaf53e641ee1be780e39bf061224bf4f40133eb30c41bf091bbe3ed42b85be3480f73ec1738fbeb21213bfde713abf643b1f3ebada2a3f38f8c23e7b146ebe079af73e0adb8fbe780e39bfc7293abf992a183eac8b2bbf5305c33e2fdd84be0491f53efab6e0beb21213bfb45925bf4d840d3e6e34403f091bbe3e8d976ebeb54e9cbd86207f3f780e39bfcb1047bd302a393f325530bf9790ef3e27a079bf98a4b23d62a17e3fb21213bf280f8b3dc7ba383f4e62303f07f0f63e29ed7dbffb76b23db4777e3f780e39bf492e7f3df5db373f006f31bfe9b7ef3ee0be7ebff1489cbd1ff67e3fb21213bf5d6d45bd7958383f0534313fd0d5f63e27a079bf97365cbe4be5793fb21213bf88851abe6abc343f5c20313f7daef63eea0474bf65175cbe65c8793f780e39bf76711bbe9318343fa5bd31bf4469ef3e780b74bf4627b3be5c02703f880e39bfa54e80be9fab2d3f29cb30bf0e4fef3e44fa6dbff0a2a7be11c4713f7f1213bf68b36abeed0d2e3f6154323f62a1f63ef7066fbf081edfbe4e7f663fb21213bf13f2a1be8fc2253faa82313f4694f63e7ac769bfa5d7febee1275e3f670e39bfa245b6bef775203f006f31bff241ef3e469466bfdcba13bf4c6d513fc21213bf0681d5beab3e173fb7d1303f2b87f63eb84062bfa2461dbfa7254a3f670e39bf6f12e3be3c4e113f539631bfd734ef3e1b0d60bf72bf33bf1a6c363f901213bf1c7c01bf9ca2033f3c4e313f2b87f63e31995abf37a633bfdf52363faa0e39bf5c2001bf21b0023f454732bff241ef3e31995abfd7bf47bfa62b203f670e39bf3bdf0fbf9d80e63e6ea331bff241ef3eef3855bf6e6b4bbf79741b3fa11213bf73d712bf1214df3e5396313f4694f63e3d2c54bf43e458bf4ca5073f780e39bffaed1bbf9c33c23e615432bf0e4fef3e72f94fbfa20a5fbf1bd8fa3eb21213bfeeeb20bf0f9cb33e17b7313f62a1f63e29ed4dbfa03567bf62dbda3e780e39bffe6527bf910f9a3e8ab031bf4469ef3ef1f44abf0c3d6ebf69c3b93ea11213bf11362cbfaf25843eaa82313f99bbf63e621048bf19e575bf2b6e8c3e780e39bf789c32bfca32443e80b730bf9790ef3e386744bfd2ac78bfe5416a3ec21213bfc66d34bf89d21e3e933a313febe2f63e05a342bf33537ebffcffb83d780e39bf39b438bf0000803da08930bfe9b7ef3e0e4f3fbfbabf7ebf7445893db21213bff90f39bfe7fb293d2e90303f22fdf63e0e4f3fbf30297ebf860296bd780e39bf7e1d38bf645d5cbd575b31bf280feb3ed49aa6bebabf7ebf7445893db21213bff90f39bfe7fb293d2e90303fce88f23ee926b1be33537ebffcffb83d780e39bf39b438bf0000803da08930bf9643eb3ebc05b2bee1987dbf8a91c5bdb21213bfde9337bf742497bd7368313f6154f23eddb5a4be39d279bf615141be780e39bf061234bfce1911bed34d32bff1f4ea3ec8079dbe784677bfbb0f70beb21213bf05a332bf6b2b36bee09c313f2a3af23e50fc98be424072bf66859abe880e39bf32e62ebff90f69bee09c31bfbadaea3e8fe492beb7d469bf0b5fc7bea11213bf105829bf4f1e96bef2b0303ff31ff23ede718abeaa9e64bf71e5dcbe780e39bf66f724bfc217a6be204131bf9fcdea3ef93186bea39252bf21770dbfc21213bfbe3019bffca9d1bea54e303fbc05f23e7c6172be3f6f52bfed630dbf990e39bf67d517bfea95d2be772d31bf83c0ea3e7c6172bea91338bfb8e72dbf990e39bf39d605bf522700bfd7a330bf83c0ea3ecff753be72f93bbf535c29bfa11213bf759308bf107af6bebc05323fbc05f23e992a58be9c8826bf26373ebf6f1213bf728aeebe24b90cbfaa82313fd712f23e93a942bedc7f18bff56949bf780e39bf08acdcbe41f113bf736831bf9fcdea3e341136be39b404bff4e056bfc21213bf371ac0be091b1ebf7cf2303f0e2df23eaf9425be4f79f4be58ad5cbf780e39bf3b70aebe454722bfa5bd31bfd5e7ea3ef6971dbe4241c9bef1f466bfa11213bffb3a90be302a29bf6519323f4547f23e5f070ebe7710b3be527d6bbf880e39bfae4781be5f982cbf8ab031bf0c02eb3e27c206bedc8285be33e272bfb21213bfda1b3cbec5fe32bfd3de303f7c61f23e2041f1bd118c73be511474bf780e39bfd50928bee5d032bf615432bf5f29eb3e8c4aeabd999ffbbd967779bf780e39bf7a36abbdde9337bf5c2031bf9643eb3e3b70cebd5edbfbbd645879bfb21213bf0de0adbd07f036bfa5bd313fea95f23ea857cabde2218c3c3f537bbfb21213bf9c33a23cf08539bf3255303f986ef23eb1501bbf5edbfbbd645879bfb21213bf0de0adbd07f036bfa5bd313fea95f23ef2411fbf999ffbbd967779bf780e39bf7a36abbdde9337bf5c2031bfb150eb3ef2411fbfc8b68c3cb7287bbf780e39bf5305a33cacad38bf933a31bf5f29eb3eb1501bbf581a383e54e276bf780e39bf3a920b3e107a36bfc52030bf0c02eb3eb81e15bf3a02383ea6b876bfb21213bff1f40a3e58a835bf97ff303f4547f23e462515bfebc9ac3ea5a46bbfb21213bf5396813e24282ebf371a303f0e2df23e764f0ebf15adac3e0f7d6bbf780e39bfc05b803e32772dbf97ff30bfbadaea3e764f0ebf1daff83e89ea59bf780e39bfabcfb53e539621bf128330bf9fcdea3eb00307bfc380013f37e156bfb21213bf0091be3e4df31ebf499d303fd712f23efdf605bf98fa193f56b845bf340e39bf006fe13e539611bf6ade31bf83c0ea3e2e9000bf2a58233f54383ebf7f1213bfb29def3e645d0cbf7368313fbc05f23e24b9fcbec6dd343feaeb2dbf880e39bf7d3f053f4e6200bf60e530bf83c0ea3eea04f4bef8e1403fbd8e20bfa11213bfe4830e3f287eecbe0ebe303fd712f23e4d84edbe5c394f3f5a660dbf780e39bf2bf6173ff31fd2be933a31bf9fcdea3e4beae4bea8fc5b3fcb47f2bec21213bf2041213f58a8b5bed3de303f0e2df23ed191dcbeca6b613f71e5dcbe780e39bf0b46253fe5d0a2bea5bd31bfd5e7ea3e226cd8bec0ec6a3fa400b1be6f1213bf234a2b3fd42b85be9c33323f4547f23e857cd0be910e6f3f807f9abe780e39bfbb272f3f54e365be6ea331bf0c02eb3e7aa5ccbe6214743f630870bec21213bf73d7323ffc1833bee09c313f7c61f23ecb10c7be0003793fcc6212be780e39bf94f6363f8ab0e1bdb7d130bf7a36eb3e1b0dc0be34667a3f809bc5bdb21213bf705f373f75029abd5396313fce88f23e24b9bcbe07d07b3f0e87a53c780e39bfacad383f96430b3cae4731bfcc5deb3e2bf6b7bed1b17b3f1f9ea53cb21213bfd9ce373f96430b3c0e2d323f083dfb3e849e1dbf34667a3f809bc5bdb21213bf705f373f75029abd5396313f3f57fb3e72f91fbf07d07b3f0e87a53c780e39bfacad383f96430b3cae4731bfcff7f33ef6971dbf64b1793f7e190c3eb21213bfebe2363fed9ebc3d5396313fd122fb3e835119bfd106783fcd783b3e780e39bf143f363f6e34003e60e530bf7dd0f33ea77917bff088723f925e8c3eb21213bfc58f313f6f81443ea5bd313f9a08fb3e46b613bf24f16e3f2f30a33e670e39bf567d2e3f9d11653eee5a32bf46b6f33ea5bd11bffeb9683f25ccc43eb21213bf23db293f0c028b3eb37b323f63eefa3e52b80ebf5b06643f53cfda3e880e39bf42cf263f76719b3e13f231bf2aa9f33eed9e0cbfc3d75b3f1bd8fa3ec21213bf2e90203fc66db43e4ed1313f48e1fa3ed57809bf64b1553fa8a7073f780e39bffaed1b3f9c33c23ed34d32bf0f9cf33ee25807bfbd39483f4b731b3fb21213bffc18133f9be6dd3ea5bd313f48e1fa3e530503bf658f443f0b28203f880e39bfc9e50f3f54e3e53e4ed131bff38ef33e85eb01bff4a8343f0e16323fa11213bfca32043f2d21ff3ed34d323f48e1fa3ed122fbbe3178303fa857363f780e39bf772d013f21b0023fb84032bff38ef33e35eff8be5dc11e3fb56b463fd31213bf6744e93ee4830e3f4ed1313f48e1fa3e1748f0be94121a3f13284a3f670e39bfae47e13ec1ca113fa5bd31bf0f9cf33e7b14eebe2978063f7cd4573fe41213bf6ff0c53e83c01a3fd34d323f63eefa3eaf94e5bee771f83ee1275e3f670e39bf2b87b63ee02d203f6ea331bf2aa9f33ee561e1be4ab8d83e1381663f7f1213bf925c9e3e4694263f3789313f7ffbfa3e083ddbbe11c5ac3e1902703f880e39bf13f2813e363c2d3feeeb30bf61c3f33e6a4dd3be54ac8a3e90a3753fb21213bf03784b3e13f2313fd3de303fd122fb3e849ecdbee84b4f3e11c8793f780e39bf1a511a3e211f343fc1ca31bfb3eaf33ef016c8be98a4b23d62a17e3fb21213bf280f8b3dc7ba383f4e62303f234afb3ef775c0befb76b23db4777e3f780e39bf492e7f3df5db373f006f31bfea04f43e65aac0bea8fc5b3fcb47f2bec21213bf2041213f58a8b5bed3de303ff7e4e13d423e88beeeb4193f271628beb21213bfd0d516be1a513a3f03782b3fb37bf23d96b2acbe3480f73ec1738fbeb21213bfde713abf643b1f3ebada2a3f3cbdd23d8d97aebe0491f53efab6e0beb21213bfb45925bf4d840d3e6e34403f1058b93ddd24a6be81ed003f98db6dbeb21213bfc58f21bf65aac03e12a52d3fd191dc3d8e06b0bee3aa063ff83251beb21213bfc5fe02bf54e3053fe4832e3f6688e33d72f9afbebbf20d3fe7e33abeb21213bf5af5b9be4a7b233f9fab2d3f8c4aea3da01aafbe118e493f763624beb21213bf71ac0bbe2b18253f1283403fb81e053ed26f9fbec0ec6a3fa400b1be6f1213bf234a2b3fd42b85be9c33323f91edfc3d280f8bbe6214743f630870bec21213bf73d7323ffc1833bee09c313f151d093e17d98ebec4d1553f2c820fbeb21213bf0ebeb0be5839143fe4143d3fba6b093e91ed9cbe87fb5c3f0c1ff1bda11213bfd044f8bec5fef23ebf0e3c3fcdcc0c3ef6289cbe34667a3f809bc5bdb21213bf705f373f75029abd5396313f984c153ed8f094be8a90623f01dab6bdb21213bf863816bf8cdba83edf4f3d3f4e62103ef6289cbeb399673fde921cbdb21213bf068125bf6076cf3d5396413fcf66153e64cc9dbed1b17b3f1f9ea53cb21213bfd9ce373f96430b3c0e2d323f52b81e3eec2f9bbe42b4663fbd34c53cb21213bf26e423bfaf9425be8941403f75021a3e3789a1be64b1793f7e190c3eb21213bfebe2363fed9ebc3d5396313f1d38273e5c8fa2be1ade603f55fb943dc21213bf55300abf9d11c5beceaa3f3f645d1c3e6ff0a5be97fd563f01a5e13db21213bfbe9fbabec4420dbf8e06403fadfa1c3ed5e7aabef088723f925e8c3eb21213bfc58f313f6f81443ea5bd313fc520303e287eacbec85c493f9bc9073eb21213bfb81e05bed8f024bf7cf2403f76711b3e8e06b0befeb9683f25ccc43eb21213bf23db293f0c028b3eb37b323f107a363e228eb5bec3d75b3f1bd8fa3ec21213bf2e90203fc66db43e4ed1313f917e3b3eb762bfbebd39483f4b731b3fb21213bffc18133f9be6dd3ea5bd313f0000403e8cb9cbbef4a8343f0e16323fa11213bfca32043f2d21ff3ed34d323fee5a423ef46cd6be5dc11e3fb56b463fd31213bf6744e93ee4830e3f4ed1313fdc46433eae47e1be2978063f7cd4573fe41213bf6ff0c53e83c01a3fd34d323f01de423e832fecbe4ab8d83e1381663f7f1213bf925c9e3e4694263f3789313f5c20413e22fdf6be54ac8a3e90a3753fb21213bf03784b3e13f2313fd3de303f48503c3e58ca02bf98a4b23d62a17e3fb21213bf280f8b3dc7ba383f4e62303fea04343e158c0abff1489cbd1ff67e3fb21213bf5d6d45bd7958383f0534313fc3642a3ed7a310bf59504cbff9d9083eb21213bf6076cf3d151d29bf3b703e3f8e06703d418212bf97365cbe4be5793fb21213bf88851abe6abc343f5c20313ff775203ed42b15bff0a2a7be11c4713f7f1213bf68b36abeed0d2e3f6154323fe258173eb07218bf081edfbe4e7f663fb21213bf13f2a1be8fc2253faa82313fdf4f0d3ed1221bbfdcba13bf4c6d513fc21213bf0681d5beab3e173fb7d1303f24b9fc3d29ed1dbf72bf33bf1a6c363f901213bf1c7c01bf9ca2033f3c4e313f8863dd3d97901fbf6e6b4bbf79741b3fa11213bf73d712bf1214df3e5396313f13f2c13de5f21fbfa20a5fbf1bd8fa3eb21213bfeeeb20bf0f9cb33e17b7313f0bb5a63d44691fbf0c3d6ebf69c3b93ea11213bf11362cbfaf25843eaa82313fdfe08b3db6f31dbfd2ac78bfe5416a3ec21213bfc66d34bf89d21e3e933a313faf25643d569f1bbf1bb956bf44f8f73db21213bf711b8d3eb22e1ebf287e3c3f4182623d2aa913bf16845ebf645cd13db21213bf08acdc3e74b505bf645d3c3fcf66553de63f14bfbabf7ebf7445893db21213bff90f39bfe7fb293d2e90303fe8d92c3dd9ce17bf1d7766bfd5598d3db21213bf567d0e3fbe9fbabea01a3f3fefc9433d386714bf29eb6bbfec4ca13cb21213bf9d11253f97ff10bea54e403f0e4f2f3d61c313bf014d6cbf904a31bdb21213bf4faf243ffe43fa3d1c7c413fbf0e1c3d2eff11bfe1987dbf8a91c5bdb21213bfde9337bf742497bd7368313f8fc2f53cc5fe12bf341467bffb74bcbdb21213bf7446143ffb3ab03e1b2f3d3f7368113d57ec0fbf005861bf5ebef5bdb21213bf295cef3e23dbf93e96b23c3f96210e3de9480ebf784677bfbb0f70beb21213bf05a332bf6b2b36bee09c313f3108ac3c96210ebfedf457bf554f16beb21213bf8716993e7593183f6dc53e3f4df30e3d832f0cbf48314cbfd6e524beb21213bf840dcf3d7ac7293f7fd93d3f0681153d1e160abfb7d469bf0b5fc7bea11213bf105829bf4f1e96bef2b0303fcdcc4c3c30bb07bfa39252bf21770dbfc21213bfbe3019bffca9d1bea54e303f5839b43bd26fffbe72f93bbf535c29bfa11213bf759308bf107af6bebc05323f99bb163b3cbdf2be9c8826bf26373ebf6f1213bf728aeebe24b90cbfaa82313fe02d903a82e2e7be23a46e3dbc0427beb21213bff9a0e73d7ffb3a3f0d712c3f575bb13da1d6d4be39b404bff4e056bfc21213bf371ac0be091b1ebf7cf2303f89d2de3a90a0d8be4241c9bef1f466bfa11213bffb3a90be302a29bf6519323f27a0893ba8c6cbbe8e1e3f3e9f20e1beb21213bf0612243f4f40133eb30c413f2b87963d5a64bbbe213f3b3ebedc8fbeb21213bfe3363a3f643b1f3e431c2b3f0de0ad3d8104c5be629dca3d5ce333beb21213bf19e2983e5a642b3f96212e3fc66db43de926d1bed579043eba2f47beb21213bfc4b1ee3ed7a3103fe9482e3f0f0bb53df2d2cdbe384c243e713a69beb21213bf44691f3f3b70ce3e71ac2b3f0f9cb33d43adc9bedc8285be33e272bfb21213bfda1b3cbec5fe32bfd3de303f04e70c3cb762bfbe1ced583e04e2fdbeb21213bffa7e0a3f9c33c23e5227403fbc96903df46cb6be5edbfbbd645879bfb21213bf0de0adbd07f036bfa5bd313fd7346f3c46b6b3bee2218c3c3f537bbfb21213bf9c33a23cf08539bf3255303fec51b83cf90fa9be9c35803ee88408bfb21213bf7502ba3edb8a0d3f72f93f3f97908f3d8e75b1be3a02383ea6b876bfb21213bff1f40a3e58a835bf97ff303f9565083ded0d9ebe23159e3e46980ebfb21213bf6210d83d9d11253f4ed1413fe10b933ddfe0abbeebc9ac3ea5a46bbfb21213bf5396813e24282ebf371a303fa3013c3df4fd94bec380013f37e156bfb21213bf0091be3e4df31ebf499d303fdb8a7d3dc4428dbede01be3ea2b20dbfb21213bfe6ae25be26e4233f8941403f2cd49a3da779a7be31eed63e90db07bfb21213bfb81ec5bec7290a3fceaa3f3fe63fa43def38a5be2a58233f54383ebf7f1213bfb29def3e645d0cbf7368313fbf7d9d3dc3f588be0514ea3ea5f9fbbeb21213bfc4420dbfdaacba3e8e06403f567dae3dc1a8a4bef8e1403fbd8e20bfa11213bfe4830e3f287eecbe0ebe303fa470bd3d1d3887bed00d6d3e8accc4be2ec84abf62a136bfee7c3f3e76e02cbfa301fc3e2b1895bd5f0bda3e9814d7be2ec84abf569f3b3f5396a1bdadfa2cbfb459f53ea857cabdccd3693e1cd3d3be2ec84abf7aa53cbf250601bd76e02cbf48e1fa3e96218ebde066d93ed7f9c7be2ec84abfd93d393fe02d103eadfa2cbf4694f63e3bdfcfbdacfe783ec5e7b6be2ec84abf44691fbf04e7cc3e68222cbf5bd3fc3e091b9ebd1957d43ecfbbb9be2ec84abf39d6253f62a1b63ed6562cbf4703f83ece88d2bdbc76893e27a5a8be2ec84abfa4dfdebe71ac1b3f5af529bf363cfd3e3108acbd86e8c83e1f2cabbe2ec84abfc6dcf53e4182123fc7292abfd0b3f93ece19d1bd03439e3e73a09fbe2ec84abfdf4f0dbe5f983c3fd57829bf76e0fc3e1136bcbda7ecb43e5d89a0be2ec84abf70ce483e151d393f7e8c29bfc898fb3ef085c9bd6135d63e46b4e5be2ec84abf88632d3f2b1895be1ff42cbfaa60f43e8126c2bd999b6f3e3baae2be2ec84abfe09c31bfa52c83beba492cbf7e8cf93e95d489bd2e39ce3e508ef2be2ec84abfcd3b0e3fac1cfabe2d432cbf61c3f33e7e1db8bdd4f1803ee3c5f2be2ec84abf9b550fbf6d56fdbe3a232abff5dbf73e4c3789bd8543bf3e80b9febe2ec84abfd6c5ad3efa7e2abf910f2abf0f9cf33ee7fba9bdd122933e8e2100bf2ec84abf1a519abe5bb12fbf477229bfabcff53ee0be8ebd035fa93e894302bf2ec84abff4fdd43c77be3fbfd57829bf8f53f43e516b9abd7383d13ef434e0beb48edabe19e2b83ede9307be48506c3fcdcccc3e789ca2bd45f5d63ea8a6d4bee5b3dcbee8d92c3fe56121bdd1913c3fba49cc3ec364aabd5b23d23e32c8cdbe3048dabe7ffb7a3ed7346f3c992a783f0378cb3ec364aabd836cd93ee3fdd8bed93de1bef31f723fbc0512becf66953ee78ce83ede712abfa113da3e1827cebe4a98e1be7dd0733fbd52963d1973973eb9fce73e6c782abf45f5d63ea8a6d4bee5b3dcbee8d92c3fe56121bdd1913c3f7958e83e1e162abf8f1b8e3e336aa6be677fe0be0681f5bea3014c3ff628bc3ea301fc3e2d214fbff67a973ec4d1a5be9eebdbbef9a067be9a99193fc66d443fd191fc3e6dc54ebf3c30983e3881a1be0762e1be04568ebe355e6a3fbde3943eadfafc3e492e4fbf00fe893e41f0b0beeab0dabe1a515abec3648a3ec05b703f9d11c53ebbb88dbd2bd9993ea88fa8be475adabe08ac9cbd5474643ee3c7783f4260c53ebe9f9abdf67a973ec4d1a5be9eebdbbef9a067be9a99193fc66d443f8104c53e2cd49abdaebc843e6b62b1be0989dcbea16703bf0309ea3eccee393f2fddc43e280f8bbdaebc843e6b62b1be0989dcbea16703bf0309ea3eccee393fa392fa3efbcb4ebf29b4843edc0eadbe2d78e1beb1e129bf21b0323f5eba893e48e1fa3ed7344fbfd93f7f3ee25bc0bef25ddabef31f92be2506013ec139733faf94c53e933a81bdeeed763e62f8b8be4f78e1beddb554bf1826f33eaa60943e637ff93e492e4fbfe4d9753e6020c0bedd44ddbe522730bfa60a863e363c2d3f4260c53edb8a7dbde4d9753e6020c0bedd44ddbe522730bfa60a863e363c2d3f35eff83e89d24ebfe7526c3ea6f2c6bed190e1be3bdf6fbf3a924b3efc18933eb537f83e492e4fbf842b703e419ad1bef489dcbe45d830bf6c09793cf90f393f9d80c63e431c6bbd0f80783ed8bad4be1a51dabeb45995bebf0e1cbdc1a8743f94f6c63eb1bf6cbde129743ebb80e7bedd99e1beff215dbf234adbbe4bc8873eea95f23ede713abf5e117c3e1632e7be7afbdbbe4d150cbf4df38ebe75024a3f21b0f23e48e13abf8c496f3e177ee0be707de0be061264bfcac382be8941c03e85ebf13e158c3abf4223803e1630f1beeb54e1be295c3fbfb4c816bf363c9d3e2aa9f33ede713abfde55873ec24ff4becc96dcbe3a23eabedb8afdbe711b3d3f8f53f43ebada3abf6f11883e0d17f9bec89ae1bed49a16bf6ade41bf2041913e6abcf43ede713abf5eba913e0a31ffbe7155e1be76e0dcbe55305abf3480973e8fc2f53e6c783abf51c0963e5724febe0396dcbe54e365beb3ea23bfbf0e3c3ffdf6f53e2cd43abfb24b9c3ead6e01bf7899e1beb98d46be840d6fbf75029a3e62a1f63e6c783abf8ca2a73e1e1602bf2657e1be62a156bc583974bff54a993e7424f73e88853abfbdc5ab3e817900bf0f7cdcbe03098a3d05c52fbf8351393f22fdf63ebada3abfbdc5ab3e817900bf0f7cdcbe03098a3d05c52fbf8351393f0456ee3eac1c2abf8ca2a73e1e1602bf2657e1be62a156bc583974bff54a993efbcbee3ede712abff626b63e4a0d01bf997ee1be29cb903ef90f69bfbe9f9a3e849eed3e6c782abffb23bc3eabe9fabe8272dbbeb29d6f3e8863fdbe143f563fe8d9ec3e1e162abf2860c33eba84fbbe5d6ee0be643bff3e835149bff5b9ba3eba49ec3ec3642abfe5b6d53e4face3be172adfbe7424473fe5d0a2bef5b90a3fd578e93efe432abf5e117c3e1632e7be7afbdbbe4d150cbf4df38ebe75024a3fb072c83e89d25ebd8c496f3e177ee0be707de0be061264bfcac382be8941c03ef9a0c73e643b5fbd601eca3e65fbf0be0ceadbbed3bce33e4547d2be1ac04b3f5f29eb3e03092abf87c4cd3e18eaf0be5becdebe76712b3f759308bfe63f043fbadaea3efe432abfe5b6d53e4face3be172adfbe7424473fe5d0a2bef5b90a3fc442cd3e789ca2bd601eca3e65fbf0be0ceadbbed3bce33e4547d2be1ac04b3f849ecd3e4f1e96bd87c4cd3e18eaf0be5becdebe76712b3f759308bfe63f043f9fabcd3e76719bbdb3b6913eff58f8be34bfdabe151d49be2731a8beb6846c3fbadaca3e1dc965bdde55873ec24ff4becc96dcbe3a23eabedb8afdbe711b3d3fe7fbc93ef6975dbd51c0963e5724febe0396dcbe54e365beb3ea23bfbf0e3c3f569fcb3ed49a66bdbdc5ab3e817900bf0f7cdcbe03098a3d05c52fbf8351393f560ecd3e6de77bbdbbd1a73eadfafcbecd3fdabe8fc275bcc66d94be66f7743f287ecc3efe437abd8b18863eb91becbe6b4adabe6f1243be64cc1dbe2731783f6744c93ef85363bdfb23bc3eabe9fabe8272dbbeb29d6f3e8863fdbe143f563f6891cd3e27a089bdd995c63e7c60efbed750dabe2e90203ebf0e1cbe07ce793fdf4fcd3e984c95bd5ccac93e0726f7be581be3be31992a3f5af539bf31082c3e3a92eb3e4ca62abfbc5bd13e6f2eeebe1f67e2be4013513fa3920abf3b014d3ede71ea3ea3922abf5401d73ed0b9e3be0dfae2bee8d96c3fb3eab3be3cbd123e9e5ee93ebe9f2abf842b703e419ad1bef489dcbe45d830bf6c09793cf90f393f0ebef03e2cd43abf1f676a3e2bddd5bede76e1be0b4675bfe78ca8bdf1638c3e97fff03e6c783abfe7526c3ea6f2c6bed190e1be3bdf6fbf3a924b3efc18933ea913f03e6c783abf842b703e419ad1bef489dcbe45d830bf6c09793cf90f393f7e1df83efbcb4ebfcd03c83e234db4be9966dabed34d623eacad583e46b6733fde02c93e57ecafbdf376d43ed6e5c4be4ad4dbbe98dd133ff90f293e7aa54c3ff1f4ca3e57ecafbd3fabcc3ebd39b4be1c98dcbe48500c3fc74bd73e8716393f6744c93eea95b2bdf94dc13e2f4da9be5000ddbed3bcc33e07f0163f4f1e363f4bc8c73ee926b1bd2122b53e494ca8bee564dabe5ddcc63de0be8e3e1895743f94f6c63ee86aabbd7a8db53e63f1a3beec12ddbecf66553e499d303f1c7c313ff0a7c63e560eadbd1ff2a63e1e17a5be18b2dabe4260e5bb2cd4da3e8b6c673f6ff0c53e5474a4bdf376d43ed6e5c4be4ad4dbbe98dd133ff90f293e7aa54c3f35eff83e2db24dbf20d1d43e0edabbbebf7ee0be8638563f8e06d03e88f4bb3e23dbf93e166a4dbf3fabcc3ebd39b4be1c98dcbe48500c3fc74bd73e8716393f63eefa3ebbb84dbf4d82a73ed927a0be0e2cdfbeb84082bc8716593ff9a0073fcba1c53ec217a6bda113da3e1827cebe4a98e1be7dd0733fbd52963d1973973ed044f83efb5c4dbf45f5d63ea8a6d4bee5b3dcbee8d92c3fe56121bdd1913c3f7e1df83ebbb84dbf94ded73ea1d8c2bee99ae1be7f6a6c3f83c08a3e280f8b3ebe30f93e6d564dbf365bd13e9146b5be6f9be1be7a364b3f287e0c3fdd24863ea392fa3edf4f4dbf95efc93e5a9dacbe9c53e1be8104253f5474343f508d973ee3a5fb3e6d564dbff94dc13e2f4da9be5000ddbed3bcc33e07f0163f4f1e363f7f6afc3e2db24dbf1b9bbd3e3413a4be49bbe1bef54ad93e44fa5d3f9487853ec807fd3edf4f4dbf7a8db53e63f1a3beec12ddbecf66553e499d303f1c7c313ff697fd3e2db24dbf2dccb23e978ba0be6957e1be50fc583eaed86f3f04568e3eb6f3fd3efb5c4dbf4d82a73ed927a0be0e2cdfbeb84082bc8716593ff9a0073fc976fe3e68914dbf4d82a73ed927a0be0e2cdfbeb84082bc8716593ff9a0073f3f35fe3e4df34ebf740ca83e39ec9ebe0d1de3be0ad7a3bbdb8a7d3fa8350d3e764ffe3e0e4f4fbf740ca83e39ec9ebe0d1de3be0ad7a3bbdb8a7d3fa8350d3e0091fe3ea8354dbfccd3693e1cd3d3be2ec84abf7aa53cbf250601bd76e02cbf45d8f03e910f2abfd00d6d3e8accc4be2ec84abf62a136bfee7c3f3e76e02cbfa913f03e910f2abf999b6f3e3baae2be2ec84abfe09c31bfa52c83beba492cbf0e2df23e03092abfd4f1803ee3c5f2be2ec84abf9b550fbf6d56fdbe3a232abfb3eaf33e03092abfd122933e8e2100bf2ec84abf1a519abe5bb12fbf477229bffdf6f53e03092abf035fa93e894302bf2ec84abff4fdd43c77be3fbfd57829bf9031f73e1e162abf8543bf3e80b9febe2ec84abfd6c5ad3efa7e2abf910f2abf96b2ec3ed5e73abf035fa93e894302bf2ec84abff4fdd43c77be3fbfd57829bfa9a4ee3e48e13abf2e39ce3e508ef2be2ec84abfcd3b0e3fac1cfabe2d432cbfbadaea3ed5e73abf6135d63e46b4e5be2ec84abf88632d3f2b1895be1ff42cbfd578e93ed5e73abf5f0bda3e9814d7be2ec84abf569f3b3f5396a1bdadfa2cbf5e4be83e48e13abfe066d93ed7f9c7be2ec84abfd93d393fe02d103eadfa2cbfe3c7f83e91ed3cbf5f0bda3e9814d7be2ec84abf569f3b3f5396a1bdadfa2cbf2bf6f73e91ed3cbf1957d43ecfbbb9be2ec84abf39d6253f62a1b63ed6562cbfac1cfa3e04e73cbf86e8c83e1f2cabbe2ec84abfc6dcf53e4182123fc7292abf51dafb3e04e73cbfa7ecb43e5d89a0be2ec84abf70ce483e151d393f7e8c29bf7fd9fd3e91ed3cbf03439e3e73a09fbe2ec84abfdf4f0dbe5f983c3fd57829bfbf7dfd3e24975fbfa7ecb43e5d89a0be2ec84abf70ce483e151d393f7e8c29bf0091fe3e24975fbfbc76893e27a5a8be2ec84abfa4dfdebe71ac1b3f5af529bf5a64fb3eb29d5fbfacfe783ec5e7b6be2ec84abf44691fbf04e7cc3e68222cbf7e8cf93eb29d5fbfd00d6d3e8accc4be2ec84abf62a136bfee7c3f3e76e02cbfec51f83e24975fbf1346eb3ede3cd5be2ec84abf7a363b3f52491dbd764f2ebfef38e53e8cb92bbf58ae473e3f6fd2be2ec84abfcc5d3bbf6f1203bcad692ebfac8bdb3e668823bff9f7e93ecff7c3be2ec84abf87a7373f8941203ed6c52dbff931e63ede712abfbb7f4c3e7594e3be2ec84abfddb534bf4df34ebe64cc2dbfdaacda3e2fdd24bfcc26e83e6248e6be2ec84abf789c323f0ad763be04562ebf41f1e33e24b92cbf3b705e3e5da9f7be2ec84abfba491cbfd9ced7bee3a52bbfac1cda3eb4c826bfabd0e03e7bf9f5be2ec84abf764f1e3f8cdbc8be764f2ebf2575e23efb5c2dbf6fbad53eeca501bf2ec84abf6d56fd3e83c00abf0de02dbfb7d1e03e12a52dbff69b813e3a0505bf2ec84abf0612d4bed2001ebf96432bbf1a51da3ea32329bf8f1dc43ef7b007bf2ec84abf12149f3e6de72bbf11362cbf6dc5de3e88632dbf6287993e83160abf2ec84abf668823be105839bf36cd2bbf5a64db3e083d2bbf7ae1ae3ef5830abf2ec84abf5474a43d96433bbf6d562dbf76e0dc3ed1912cbffb774d3e6748bdbe2ec84abf61c333bf8cb96b3e9a772cbfe414dd3e454722bfb14de23e3e5bafbe2ec84abf4a7b233f9b55bf3e11362cbf5ddce63ee78c28bf2d41663ed36ca6be2ec84abf615412bfd712f23e71ac2bbfb762df3e006f21bf5892d43e27f89ebe2ec84abfc74bf73e89d20e3f3fc62cbf79e9e63e62a126bf6230873e82e495be2ec84abf3411b6bee71d273f083d2bbfdcd7e13e1c7c21bfaf08be3e1c2391be2ec84abf58ca723ee2e9353f0c9329bfa60ae63eaa6024bf9bff9f3ead188ebe2ec84abf764f9ebd12a53d3f9fcd2abfaf25e43e7c6122bf5743ea3e207bc5bed61e3ebf5986383f849e0d3e0de02d3fdd24e63efca931bf44a7473e0535ccbed61e3ebf645d3cbff931663d3fc62c3f88f4db3e871639bfc72ceb3ebdc6d6bed61e3ebfd5e73a3faf2564bd04562e3fb81ee53edc6830bf7c2b523e7b83b7bed61e3ebf96b22cbf86c9943ebbb82d3ff697dd3e713d3abfd060e33ebf43b1bed61e3ebfb98d263faa60b43e11362c3f5ddce63e4a7b33bfca16693eb5c2a4bed61e3ebfa8350dbfd200fe3e71ac2b3f2497df3e2cd43abfa2b6d53e7101a0bed61e3ebf89d2fe3ecc5d0b3fe8d92c3f94f6e63e787a35bf5322893eb9e094bed61e3ebfb1e1a9be8c4a2a3f083d2b3f4a0ce23ef5b93abf035cc03e22fe91bed61e3ebf0b24883e8a1f333fecc0293ff931e63e87a737bf3c2ea23ecee28dbed61e3ebf849e4dbd925c3e3fdaac2a3f014de43e5eba39bfb7b74b3ec616e2bed61e3ebf666636bf591737be2db22d3ff5b9da3ea77937bf69aae73e1fbfe7bed61e3ebf0534313f6abc74be04562e3fefc9e33ed26f2fbf9a0b5c3eafd2f5bed61e3ebfaed81fbf96b2ccbe36cd2b3fac1cda3ecba135bf94f9df3e0b44f7bed61e3ebf6de71b3f1748d0be764f2e3fd34de23e89d22ebf4696d43ed42a02bfd61e3ebff46cf63e44fa0dbfd6c52d3f65aae03e8d972ebff59d7f3e886404bfd61e3ebf9b55dfbe1e161abf96432b3ffe43da3edc4633bfb81dc23e362208bfd61e3ebf0f9c933e3b702ebf11362c3f0091de3e32e62ebfba65973e41d809bfd61e3ebf363c3dbe9eef37bf71ac2b3f234adb3ee92631bfa758ad3ee6960abfd61e3ebf4772793d1ac03bbf6d562d3f3fc6dc3e77be2fbf5743ea3e207bc5bed61e3ebf5986383f849e0d3e0de02d3fdc68c03e66f7e4bdc72ceb3ebdc6d6bed61e3ebfd5e73a3faf2564bd04562e3fdc68c03efaedebbd1346eb3ede3cd5be2ec84abf7a363b3f52491dbd764f2ebfd200be3e6822ecbdf9f7e93ecff7c3be2ec84abf87a7373f8941203ed6c52dbfb6f3bd3e41f1e3bdd060e33ebf43b1bed61e3ebfb98d263faa60b43e11362c3fc05bc03e62a1d6bdb14de23e3e5bafbe2ec84abf4a7b233f9b55bf3e11362cbfb6f3bd3ef4fdd4bda2b6d53e7101a0bed61e3ebf89d2fe3ecc5d0b3fe8d92c3fc05bc03ef0a7c6bd5892d43e27f89ebe2ec84abfc74bf73e89d20e3f3fc62cbfb6f3bd3ecba1c5bd035cc03e22fe91bed61e3ebf0b24883e8a1f333fecc0293fdc68c03e0f0bb5bdaf08be3e1c2391be2ec84abf58ca723ee2e9353f0c9329bfb6f3bd3ea167b3bd3c2ea23ecee28dbed61e3ebf849e4dbd925c3e3fdaac2a3fdc68c03e5452a7bd9bff9f3ead188ebe2ec84abf764f9ebd12a53d3f9fcd2abfd200be3e5452a7bd5322893eb9e094bed61e3ebfb1e1a9be8c4a2a3f083d2b3fc05bc03ece1911be3c2ea23ecee28dbed61e3ebf849e4dbd925c3e3fdaac2a3fdc68c03e07ce19be9bff9f3ead188ebe2ec84abf764f9ebd12a53d3f9fcd2abfd200be3e871619be6230873e82e495be2ec84abf3411b6bee71d273f083d2bbfb6f3bd3e4e6210beca16693eb5c2a4bed61e3ebfa8350dbfd200fe3e71ac2b3fc05bc03e4bc807be2d41663ed36ca6be2ec84abf615412bfd712f23e71ac2bbfb6f3bd3e94f606be7c2b523e7b83b7bed61e3ebf96b22cbf86c9943ebbb82d3fdc68c03e6e3400befb774d3e6748bdbe2ec84abf61c333bf8cb96b3e9a772cbfb6f3bd3e24b9fcbd44a7473e0535ccbed61e3ebf645d3cbff931663d3fc62c3fdc68c03e4625f5bd58ae473e3f6fd2be2ec84abfcc5d3bbf6f1203bcad692ebfd200be3ed881f3bdb7b74b3ec616e2bed61e3ebf666636bf591737be2db22d3fdc68c03e917e3bbe44a7473e0535ccbed61e3ebf645d3cbff931663d3fc62c3fdc68c03e250641be58ae473e3f6fd2be2ec84abfcc5d3bbf6f1203bcad692ebfd200be3e499d40bebb7f4c3e7594e3be2ec84abfddb534bf4df34ebe64cc2dbfb6f3bd3eb5153bbe9a0b5c3eafd2f5bed61e3ebfaed81fbf96b2ccbe36cd2b3fc05bc03eea0434be3b705e3e5da9f7be2ec84abfba491cbfd9ced7bee3a52bbfb6f3bd3e333333bef59d7f3e886404bfd61e3ebf9b55dfbe1e161abf96432b3fc05bc03e68b32abef69b813e3a0505bf2ec84abf0612d4bed2001ebf96432bbfb6f3bd3eb1e129beba65973e41d809bfd61e3ebf363c3dbe9eef37bf71ac2b3fdc68c03e0a6822be6287993e83160abf2ec84abf668823be105839bf36cd2bbfd200be3ec1ca21bea758ad3ee6960abfd61e3ebf4772793d1ac03bbf6d562d3fdc68c03eb00367beba65973e41d809bfd61e3ebf363c3dbe9eef37bf71ac2b3ff775c03e8c4a6abe6287993e83160abf2ec84abf668823be105839bf36cd2bbfd200be3e8c4a6abe7ae1ae3ef5830abf2ec84abf5474a43d96433bbf6d562dbfd200be3ed49a66beb81dc23e362208bfd61e3ebf0f9c933e3b702ebf11362c3fc05bc03e522760be8f1dc43ef7b007bf2ec84abf12149f3e6de72bbf11362cbfb6f3bd3ed26f5fbe4696d43ed42a02bfd61e3ebff46cf63e44fa0dbfd6c52d3fc05bc03ed04458be6fbad53eeca501bf2ec84abf6d56fd3e83c00abf0de02dbfb6f3bd3ebec157be94f9df3e0b44f7bed61e3ebf6de71b3f1748d0be764f2e3fc05bc03ebc0552beabd0e03e7bf9f5be2ec84abf764f1e3f8cdbc8be764f2ebfb6f3bd3eaa8251be69aae73e1fbfe7bed61e3ebf0534313f6abc74be04562e3fdc68c03e832f4cbecc26e83e6248e6be2ec84abf789c323f0ad763be04562ebfb6f3bd3e71ac4bbec72ceb3ebdc6d6bed61e3ebfd5e73a3faf2564bd04562e3fdc68c03ecb1047be1346eb3ede3cd5be2ec84abf7a363b3f52491dbd764f2ebfd200be3ef0a746bef88b593c107a80bfa0873abff775603c508d37bf0a6832bf1904d63d44697fbf6808a7bd462480bfa0873abf8b6c67bdab3e37bf0e2d32bf1361c33d7b837fbfd7dcd1bc060f7bbfa0873abf89d25e3c2a3a323f30bb37bf5f07ce3d3b707ebf4c6bf3bd257679bfa0873abf9c33a23dce19313f30bb37bf36cdbb3d04567ebf6ea74dbeaa497cbfa0873abfa8350dbe810435bfc58f31bfc3d3ab3d69007fbfc11d58be909f75bfa0873abff38e133e20632e3f30bb37bf0c93a93d7fd97dbf42cda0bef1bc74bfa0873abfadfa5cbe0e4f2fbf9c3332bf984c953d9be67dbfba4b9abebc936fbfb1873abf86c9543e3a232a3f30bb37bf508d973d1ff47cbf6f64cebef6436cbfb1873abf72f98fbea7e828bf0a6832bf4a7b833dd1917cbf4949c7be295c67bf90873abfccee893e3867243fa2b437bfdd24863de3a57bbf1d1efabe57b461bfa0873abf4df3aebe736821bf0a6832bf8a8e643d2cd47abf5681f2beba125dbfa0873abf4c37a93e7aa51c3f2bf637bf8c4a6a3dccee79bf38da11bf892555bfa0873abf29edcdbe5e4b18bf0e2d32bfefc9433d39b478bf3d6412bf19ca4dbf90873abfbadaca3e1c7c113f90a038bf38f8423de25877bf0b7b2abf859542bfb1873abf7368f1be8cb90bbfca5431bf52491d3d068175bf07272abf1de33abfc2873abfc3d3eb3eb459053fb9fc37bf76e01c3d211f74bf0d6d44bfedf328bfa0873abfc7290abfd8f0f4beca5431bfb003e73cb30c71bf6b453bbfb41e2abfd3873abf5c8f023fce88f23ed9ce37bf006f013d933a71bf0bb54abfeed117bfc2873abf4d840d3fc7bad83ebec137bf60e5d03c5f076ebf09c156bfe84d11bfb1873abfb4c816bf2a3ad2be0e2d32bf7a36ab3c91ed6cbfd15758bf282904bfc2873abf5917173fc807bd3ebec137bf0ad7a33c158c6abf399a63bfda73f9bea0873abf371a20bf61c3b3be0a6832bf499d803cf54a69bfd50964bf97a8debea0873abfb7621f3f92cb9f3ea2b437bfdaac7a3c5ddc66bf747e6ebf1c07cebec2873abfd9ce27bf0f0b95be7c6132bf3480373c5d6d65bfa9bc6dbfe4f3b2beb1873abff931263fee7c7f3e9eef37bfa323393caaf162bf0a4c77bf76c1a0beb1873abfed0d2ebfb1e169be0a6832bf6c09f93be56161bfbde576bff0da75beb1873abfc8982b3f69002f3e8cdb38bf8fc2f53b9be65dbff5f47dbf64e963bea0873abfcac332bff9a027be7c6132bf75029a3b8d285dbf2b3181bf795c04beb1873abf54e335bf82e2c7bd0a6832bfc3642a3b8cdb58bf4c6d7dbf6972d1bda0873abf3bdf2f3f2f6ea33da7e838bfe02d903b508d57bfcb4782bf711e0ebda0873abf14ae37bf6519e2bc0e2d32bfc364aa3a547454bf36e57ebff889833ca0873abf2eff313f99bb16bbb9fc37bf17b7513b4a0c52bfd71482bf2446af3da0873abfd50938bf431c6b3dca5431bf6f12833a17d94ebf89957dbf3cfadf3da0873abfb30c313f4f1e96bd9eef37bfd044583b9fab4dbf52d77ebf7de7673ea0873abf547434bf51da1b3e575b31bf52491d3bb53748bf48fe78bfb076643eb1873abf24b92c3f9a771cbe8cdb38bf99bb963bec5148bfdc9c76bf9982ad3e90873abfb6f32dbf9fab6d3e0e2d32bf2e90a03b8fe442bf0dc46ebf4df3b63ea0873abf787a253fd93d79be151d39bf2497ff3b2eff41bf0b9b6dbf33a4da3e90873abf3d0a27bf2c65993e9c3332bf2497ff3b52b83ebf09c45fbf0531f83ea0873abf9a081b3f3a23aabe151d39bf17b7513c6de73bbfac555fbf49a1083f90873abf68911dbf1283c03eca5431bf17b7513c27a039bf6e4f4cbfaa431a3fa0873abfa4700d3f5839d4be151d39bf2e90a03c143f36bfa54c4abfdc62263f90873abfb7620fbf029ae83e575b31bfc1a8a43cea0434bf36cc34bfd95c353fa0873abf3f57fb3e637ff9be19e238bf8a8ee43c5c2031bf0ef735bf43553c3fb1873abf325500bf8195033f9c3332bf40a4df3c20d22fbf0ebb23bfa5654c3fb1873abfaf94e5be80480f3f0a6832bfde710a3db1bf2cbf9e0c1ebfb265493fc2873abfc807dd3e917e0bbf470338bfbc74133d363c2dbf121710bf19ae5a3fa0873abfb1e1c9be9e5e193f0a6832bfc286273de7fb29bf75910abfe640573fc2873abfd26fbf3e4f1e16bf2bf637bfc520303d158c2abf7154f6bead17673fa0873abf431cabbeaeb6223f812632bff0a7463dc28627bf19cae1be73d8653fa0873abf917e9b3e295c1fbf1ea738bf62a1563da2b427bfb56fbebef4fe733fa0873abf931884be7f6a2c3fca5431bf45d8703df4fd24bf1fa2a9be166a713fa0873abf55306a3e82e227bf273138bfdc68803d265325bf3f8f71be66bb7e3fb1873abff9a027be5dfe333fe92631bf05a3923d3cbd22bf941262bee290793fb1873abf2b87163e560e2dbf8cdb38bfe2e9953d9ca223bf8d2ac3bd603d823fa0873abfb9fc87bdd9ce373f575b31bf7b14ae3de56121bfcd1ea8bd3cd87e3fa0873abff853633d857c30bfa7e838bf0ebeb03d7c6122bf0151b03c6ea2823fa0873abf04e78c3c30bb373f9c3332bf14d0c43deeeb20bf2ded143d293d7f3fa0873abf1b2fddbc6ade31bfb9fc37bff016c83d2eff21bf9757ee3dc3ba813fb1873abf33c4b13d143f363f0a6832bf87a7d73db30c21bf9fb0043e73137d3fb1873abfefc9c3bd2e9030bf30bb37bf1a51da3d0e2d22bfa818573e0f627f3fb1873abf3fc61c3e865a333f0a6832bf1e16ea3d539621bfccb3623ecdad783fb1873abf780b24bebf7d2dbf30bb37bf448bec3d58ca22bf19599a3ee71b793fb1873abf2d215f3e32e62e3f0a6832bf4850fc3dce8822bf863b9f3edb19723f90873abf0bb566beb07228bf9eef37bf925cfe3defc923bf1ff5c73e1aa4703f90873abf3bdf8f3e35ef283f7c6132bfcb10073e26e423bfc902d63eab23673fa0873abfb53798bec52020bfacad38bf95d4093e3d9b25bf16bdf33e7b14663fa0873abf6900af3e006f213f7c6132bf97900f3ecba125bf2994053f0b62583f90873abfbf7dbdbe107a16bf992a38bfaa60143e470328bf21ac0e3fe580593f90873abf0d71cc3ecc7f183f0a6832bfbec1173e30bb27bf96961d3fb45b473fa0873abfa4dfdebe3ee809bf1ea738bf64cc1d3ef1f42abfc635223fa6094b3f90873abfd509e83ee9480e3f0a6832bf098a1f3ee3362abfcb4d343fddd23a3fa0873abfb7d1003fe5d0023f0a6832bf42cf263e3b012dbfc8ed333f80d8323f90873abf1b9efebeb537f8be273138bf42cf263ec9762ebf93c6443f8200293fd3873abf9a770c3fd656ec3e986e32bffb5c2d3e371a30bf5d50473f7fa01c3fb1873abf76e00cbf7daed6be8cdb38bf567d2e3e7c6132bff486533f42b5153fa0873abf22fd163f97ffd03eee5a32bfa167333ed88133bf971c5b3f5567fd3ea0873abf75021abff2d2adbe871639bf7dae363e87a737bff46a603f130e013fb1873abfa913203f46b6b33e986e32bfc7ba383e022b37bffd4a6b3f8bc2d63ea0873abfec51283f7dd0933e0e2d32bf363c3d3e0c023bbfe17c6a3fc095bc3ea0873abfddb524bf2eff81be19e238bfff213d3ea4703dbff739763f1c0c9d3ea0873abf6e34303fd0b3593e378931bfdcd7413e6e3440bf2db0733fe5d0823eb1873abf68222cbffca931beb53738bf933a413eea9542bf1c427d3f0cb2453eb1873abfd8f0343f9487053ea1f831bfef38453e5d6d45bfb37c793f9207123ea0873abf40a42fbfee7cbfbd39b438bf26e4433e67d547bfe696803ff819973da0873abfd509383f371a403dc58f31bf022b473ef1f44abf82ac7b3f2a6fc73ca0873abf33c431bf5bb13fbc273138bf2653453edf4f4dbf94a1803f40c230bda0873abfde93373f4d150cbd9c3332bf4bc8473edc6850bf058c7a3f3ba7b9bda0873abfc52030bf98dd933d8cdb38bf2653453e21b052bfcae27e3f65e40cbea0873abfe6ae353fcff7d3bd0a6832bf705f473ea1d654bf5470743f0b2a6abeb1873abf31082cbf5452273ea7e838bfb8af433ebe3059bfc8407a3f32596cbea0873abf0a68323f68912dbe0a6832bf6ff0453ed93d59bfc26b733f1bbba4beb1873abf68912d3f7b836fbe0a6832bfefc9433edb8a5dbf944b6b3fd103afbec2873abf99bb26bfd93d793eb9fc37bf80b7403e728a5ebf5d6c6a3fa3e8d1beb1873abf9031273ff5db97be7c6132bf80b7403ea5bd61bf32c8613fe7e3dabea0873abf000020bfff219d3e30bb37bfdb8a3d3e05a362bfbd555f3f1b2ffdbea0873abf0e4f1f3fd95fb6be986e32bf91ed3c3e1dc965bf3140563fbb5e02bfc2873abff5db17bfbe9fba3ebec137bf4772393eb98d66bff04e523fe81513bfa0873abfdd24163fa167d3be7c6132bfb537383e27a069bf5cc7483f572416bf90873abfad690ebff46cd63e30bb37bf34a2343efe436abf2768433f524426bfa0873abf1f850b3ffbcbeebeee5a32bf8fe4323e363c6dbf637f393f4f9028bfa0873abffc1803bf7cf2f03e9eef37bfa01a2f3e48bf6dbf55bf323f1ff537bf90873abfb762ff3e98dd03bf986e32bfe8d92c3e499d70bf5f79243f193b3dbfb1873abf423ee8be8bfd053f1ea738bf8b6c273efca971bf8b8a203fee0748bfa0873abf1dc9e53e643b0fbf7c6132bff931263e46b673bf93560c3fbf9e4fbfc2873abf1ea7c8bec5fe123fd50938bfe4831e3e7d3f75bf90da0c3ff35956bf7f873abf95d4c93e2c6519bf7c6132bf89d21e3e107a76bfddd1ef3e880f5cbfb1873abff1f4aabe0d711c3f30bb37bf07f0163ea2b477bf8cf3ef3ef5b962bfa0873abf832fac3e0e2d22bf0a6832bf3d0a173ea7e878bfebc9c43ef08866bfa0873abfb1bf8cbeefc9233f30bb37bf17d90e3e07ce79bf2d05c43e8b196dbfa0873abf711b8d3e637f29bf0a6832bf17d90e3e7ffb7abf1ff5973e8df06ebfb1873abfbec157be43ad293f2bf637bf8273063e1f857bbfad4e963e4e6175bfb1873abf2c65593eb7622fbf7c6132bf143f063e96b27cbfefe23d3e8b6d76bfb1873abf70ce08be44fa2d3f1ea738bf9031f73de4147dbf567d4e3e3e7a7bbfb1873abf99bb163eb8af33bf0a6832bfdaacfa3dd2007ebf774cdd3d3a587fbfa0873abf54e3a53df46c36bf0a6832bfb072e83dc0ec7ebf02d68a3dbf657abfa0873abfae4761bd006f313fb9fc37bff775e03d091b7ebf20b80abd3f027bbff9f4583cc0ec9e3c0e2d323f30bb373f3480973e2eff21bf8274b13ca37380bff9f4583c9c33a23cc28637bf0a68323ffe439a3eb30c21bf6efa733df48b7abfc7f0583ca77947bde09c313f9eef373fbf0e9c3ed34d22bfe2e995bd933780bfc7f0583c16fb4bbde25837bf2a3a323f3d9b953eeeeb20bf4566ee3ddf177fbfc7f0583ca1f8b13d143f36bf0a68323fc0ec9e3e539621bf1b13323e2efe76bf96ec583c378901be04562e3f1ea7383f6ea3a13ec13923bff8e3563e5b077bbff9f4583c08ac1c3e865a33bf0a68323f2f6ea33ece8822bf84679a3e5abc74bf2bf9583c9b555f3ec0ec2ebf0a68323f82e2a73e26e423bf57e8933ebc936fbff9f4583c736851be3a232a3fb9fc373f1d38a73e86c924bff8f9c73eb7426cbfc7f0583c3bdf8f3ea7e828bf0a68323f832fac3ecba125bfc0eac03e295c67bfc7f0583ce7fb89be1d5a243fbec1373f0378ab3e107a26bf16bdf33eb3b661bff9f4583cfbcbae3e8e7521bf0a68323f1748b03e30bb27bfbe16ec3e8c115dbf6bd3583cb072a8beff211d3fa2b4373f7b83af3ee78c28bf59a70e3f242955bf5512593cf163cc3ee78c18bf7c61323f3d2cb43e55302abfef900a3f86c950bfeb1e593ca60ac6becff7133f9eef373f865ab33ef1f42abf5933223f8aaf46bf5512593cd509e83e5b420ebf986e323fbec1b73e3b012dbf9834223f1b2d3fbff9f4583c5d6de5beab3e073f90a0383f87a7b73ead692ebf5e4b343f817736bf0e4d593c29cb003fcac302bf2575323fb515bb3ec52030bf3010383fb41e2abfc7f0583ca1f801bfbc74f33e10e9373fffb2bb3e0a6832bfefc8443ff19d24bf5512593c287e0c3fba49ecbe986e323f091bbe3ed88133bf2c82473f5ad417bf5512593cbf7d0dbfc7bad83ebec1373fe483be3ec6dc35bf8f8a533f555011bf5512593c94f6163f7cf2d0be0a68323f80b7c03e022b37bf9622553f282904bf6bd3583c022b17bf1b2fbd3e87a7373feeebc03e0c9339bf8868603f006ff9be8e01593ca913203f7dd0b3be0a68323f5305c33e9a083bbf62d9603f97a8debe5512593c0e4f1fbf92cb9f3e4bc8373f38f8c23e32773dbfc24c6b3f69fdcdbef9f4583c67d5273ff4fd94be7c61323f4beac43e12143fbf22896a3f40f6b2bec7f0583c863826bfb7627f3e9eef373fc1a8c43e378941bf971b743f32aea0bef9f4583c5f072e3fb1e169be0a68323f6666c63edc4643bf3eb4733fcec775bef9f4583cc8982bbf69002f3e19e2383fc217c63e99bb46bffdbf7a3ff70564be32e4583ccac3323f30bb27be7c61323fa779c73e6b9a47bfdc2f7f3fe46a04bec7f0583c54e3353ff016c8bd7c61323ff016c83e16fb4bbfb03a7a3f2177d1bdc7f0583c3bdf2fbf2f6ea33da7e8383fb003c73ea8354dbf75ae803f2f6f0ebdc7f0583c14ae373f1cebe2bc0e2d323f5e4bc83edc6850bfeeb27b3f46d1833cf9f4583cc1ca31bf52499dbb2731383fcb10c73e3cbd52bf917b803f944faf3dc7f0583c10e9373fad695e3dc58f313f82e2c73e190456bfecdf793f6b2b063ef9f4583c40a42fbf7d3fb5bd8cdb383f8273c63ef01658bfaddb7c3fd9214e3e2bf9583ca69b343f96b20c3e2eff313f5ddcc63e5a645bbfe1ec723fc977893ec7f0583c9a082bbf348037be19e2383ff8c2c43e3b705ebfdb50753f24d6a23ec7f0583cb29d2f3f09f9603ec58f313fb81ec53e9cc460bff31c693f62dbc23ed6c6583c86c924bfcba185be992a383faeb6c23e819563bf59696a3f9eb2da3e6bd3583c6b9a273fd0d5963e9c33323fe5d0c23e1dc965bf93905c3fbd35f83e9dd7583cbada1abf3a92abbe35ef383f0000c03eec5168bfc45b5f3fb3eb023fd6c6583c295c1f3ff46cb63e7c61323f1283c03e27a069bf944c523fb073173f7940593c3411163f865ad33e2575323f7fd9bd3e363c6dbfbd1d493f7c421a3ff9f4583c7b140ebff38ed3befed4383fa301bc3e12a56dbf5e63433f76a42a3f5512593c917e0b3fa9a4ee3e2575323f11c7ba3e499d70bf8c66353fac53313f0fb6583cbc9600bfabcff5be7e1d383f6210b83e17b771bf2fc4323fe7523c3fb22f593c9b55ff3e0ad7033fb37b323f1973b73e46b673bf5ce71f3fde76453f64e8583cdcd7e1be55c108bf90a0383f61c3b33ed42b75bf2f88203f49634c3fc7f0583ccba1e53ef2410f3f0a68323f7dd0b33e107a76bf59df0c3fb3b15a3feb1e593c95d4c93ef54a193f2575323f57ecaf3ea7e878bf295c073f4243573f32e4583c1283c0be068115bf992a383f32e6ae3e423e78bfb3eeef3edb18673fd6c6583cba49ac3e0e2d223fee5a323fc3d3ab3e7ffb7abf5a64db3e45d7653f32e4583c76719bbeb7621fbf1ea7383f95d4a93e4ca67abfc2f6c33eaf79713feb1e593c3b018d3ed578293f2575323fc286a73e96b27cbfe239a33ead6a713f96ec583c1ff46cbe82e227bf2bf6373f1d5aa43e448b7cbf8a3b963edbc0793ff209593c2c65593e9b552f3f986e323f6f12a33ed2007ebfe9ee6a3edc2e783f5dfd583c7ac729beff212dbf30bb373fe5f29f3ef6977dbf02b74e3e45d57f3f8e01593cd0d5163eb8af333f0a68323fe4839e3ec0ec7ebf8d0d0d3e08c77c3ff9f4583c6076cfbdc05b30bfbec1373f5a649b3ecd3b7ebf0743dd3df0da813f96ec583ce6aea53d8273363f7c61323f23db993e44697fbfbcb3363d9a237f3ff9f4583c70ce08bddcd731bf9eef373fb4c8963e3b707ebf6138573cd2a8823fc7f0583c0e4f2f3cd9ce373f0e2d323f6132953e7b837fbf8e3c90bd6f0e7f3ff9f4583cefc9433d65aa30bf8cdb383fe926913e96217ebfa25fdbbdf818823ff9f4583c992a98bd87a7373fca54313f0e4f8f3e69007fbf8b4e56bece337a3f96ec583ce0be0e3e166a2dbfa7e8383fc3648a3e1ff47cbf575f7dbefb067e3f96ec583c8e0630bed881333fe926313fcc7f883e68917dbfa1a3a5be4419723f3acf583c6688633e3e7928bf0b24383f2fdd843e96437bbfdf16c4beace1723f5dfd583c67d587be569f2b3fe561313f13f2813e083d7bbfe46adcbe1423673ff209593c7e1d983e371a20bf39b4383fee7c7f3e50fc78bf4419fabe4d13663fd6c6583c7b14aebe13f2213f8126323fecc0793e39b478bf9cc408bfa565583f5512593cd191bc3eebe216bf6210383f228e753e4f1e76bf6de111bf4a7d593fd6c6583cf163ccbe7593183fee5a323fa1f8713e143f76bf075f1cbf18b24a3ff9f4583cbe9fda3ed1910cbf10e9373fe9486e3ebc7473bf126b25bfe50a4b3fb22f593c9eefe7bee9480e3f2575323f9fcd6a3e2f6e73bf3baa32bfdd78373fcfdb583c3e79f83e4850fcbe19e2383fc217663ee9b76fbfd07b37bfa5d73a3f0fb6583ce09c01bf986e023ff31f323faf25643e325570bfd7334cbfda04243f2bf9583c499d10bf4260e53ee561313f08ac5c3e88f46bbf39804abf48a51c3f08cb583c9f3c0c3f6b9ad7bef90f393fbf7d5d3e4ca66abfd6e560bfe204063f1c23593ce4831ebfc807bd3e7368313f984c553ebd5266bf384e5ebf2f6cfd3ec7f0583c910f1a3ff2d2adbef90f393ff46c563e810465bf9e7c6ebf8bc2d63e8e01593ca2b427bfbd52963e2a3a323f857c503ee56161bf85b16dbf5587bc3ec7f0583c6abc243f2e9080be151d393fce19513edbf95ebf0a4c77bfd175a93e08cb583ce4832ebf1d38673e8126323f4d844d3e8d285dbf585478bfce16703e96ec583c9f3c2c3f780b24bea7e8383fbbb84d3e1ea758bf2d7a7fbfe7015c3ef9f4583cbde334bfcff7133eca54313f280f4b3e14ae57bf535c7dbff8c6f03d96ec583c45d8303f5305a3bdb9fc373f832f4c3e182653bf343082bfce00973df9f4583c992a38bf83c04a3d575b313f95d4493eb30c51bfcade7ebfc425c73cc7f0583cbc05323f728a0ebc9eef373f4d154c3efbcb4ebfeb3a82bfa77430bdf9f4583cde9337bf4d150cbd9c33323f713d4a3ec8984bbf5ebe7dbf4ab3b9bdc7f0583cc520303f98dd933d8cdb383f711b4d3e477249bfb30a81bf51dd0cbe5dfd583c58a835bfcff7d3bd0a68323f3a924b3e1d3847bf90a177bfd23a6abec7f0583cbf0e2c3f5452273e19e2383fa913503ee10b43bfd6747dbf9f3c6cbe5dfd583c7c6132bf68912dbe0a68323fbbb84d3e8fe442bf079b76bf9ad0a4be32e4583cdb8a2dbfe9b76fbe7c61323f29cb503ec4b13ebfdd7d6ebfd103afbecfdb583c27c2263fa323793e2bf6373fcff7533e7fd93dbf399c6dbf7cedd1be32e4583c903127bfd9ce97be7c61323f4faf543edaac3abf12fb64bf9fe8dabe8e01593ce5f21f3fff219d3e4bc8373f6210583eccee39bfdb8962bf1b2ffdbef9f4583c9b551fbfd95fb6be986e323ff54a593e42cf36bf117359bf4f5c02bf6bd3583c82e2173fdaacba3e30bb373f76e05c3e863836bf6a8555bf1f1113bf0fb6583cc6dc15bf0f0bd5bef31f323f1b9e5e3e333333bf0ef94bbfea2116bf0fb6583cc9760e3f107ad63e14ae373f0a68623eaeb632bf3d6442bfb4382bbf240e593cfed408bf4703f8be3c4e313f9d80663e69002fbf42b23cbfbc9228bf0e4d593ce10b033f29cbf03ed509383fe78c683eee7c2fbf11ab27bf583c3dbfc7f0583c5e4be83ea60a063f029a383fb30c713efaed2bbfd92728bf8f8d44bfb91a593ce948eebe1ff40cbf7368313f7cf2703e31992abf05870fbf809d4fbf6bd3583c1ea7c83e8a1f133f10e9373fa3927a3e55c128bf381210bf585656bfeb1e593c280fcbbea32319bfb840323f6c787a3ec28627bfbd37f6beb6105cbf6bd3583c280fab3ef1631c3fbec1373fae47813ef0a726bf4b59f6beb7b862bff9f4583c9f3cacbef31f22bf0a68323fca54813eeb7325bfa92fcbbe598866bf5512593c96b28c3e61c3233fbec1373f9487853ed8f024bfa46fcabe4c186dbff9f4583c8d288dbef08529bf7c61323fcba1853ed3bc23bf26569ebe8df06ebff209593c516b5a3e43ad293fbec1373fb1e1893e0f9c23bf10b29cbe4e6175bf8e01593c99bb56be20d22fbf8126323f1e168a3e986e22bf916260be662d75bfc7f0583c2c65193e7b142e3f30bb373f20638e3e93a922bfc8b541bef5da7cbf2bf9583c02bc05beb45935bfc58f313f57ec8f3e575b21bf4c1702be613679bfc7f0583c7b14ae3deeeb303f30bb373f8fe4923ef31f22bfd7dcd1bc060f7bbfa0873abf89d25e3c2a3a323f30bb37bf3d0a973e000080be6efa733df48b7abfc7f0583ca77947bde09c313f9eef373f9d11653ea16773be02d68a3dbf657abfa0873abfae4761bd006f313fb9fc37bf07f0963e0e2d72be20b80abd3f027bbff9f4583cc0ec9e3c0e2d323f30bb373f0b46653e499d80be4c6bf3bd257679bfa0873abf9c33a23dce19313f30bb37bf7424973ed42b85be4c1702be613679bfc7f0583c7b14ae3deeeb303f30bb373f4260653ed42b85bec11d58be909f75bfa0873abff38e133e20632e3f30bb37bf5bd3bc3e1ea7e8bc4c1702be613679bfc7f0583c7b14ae3deeeb303f30bb373f5986983e26e483bc4c6bf3bd257679bfa0873abf9c33a23dce19313f30bb37bf91edbc3e8fc275bc916260be662d75bfc7f0583c2c65193e7b142e3f30bb373f226c983efca9f1bcba4b9abebc936fbfb1873abf86c9543e3a232a3f30bb37bf3fc6bc3e32e62ebd26569ebe8df06ebff209593c516b5a3e43ad293fbec1373fec51983e7dd033bd4949c7be295c67bf90873abfccee893e3867243fa2b437bf08acbc3eb1bf6cbda92fcbbe598866bf5512593c96b28c3e61c3233fbec1373fd044983ed71272bd5681f2beba125dbfa0873abf4c37a93e7aa51c3f2bf637bfed9ebc3e99bb96bdbd37f6beb6105cbf6bd3583c280fab3ef1631c3fbec1373f992a983e9a9999bd3d6412bf19ca4dbf90873abfbadaca3e1c7c113f90a038bfd191bc3e371ac0bd05870fbf809d4fbf6bd3583c1ea7c83e8a1f133f10e9373f992a983e7ffbbabd11ab27bf583c3dbfc7f0583c5e4be83ea60a063f029a383f7e1d983ed49ae6bd07272abf1de33abfc2873abfc3d3eb3eb459053fb9fc37bfb684bc3e8cb9ebbd6b453bbfb41e2abfd3873abf5c8f023fce88f23ed9ce37bfb684bc3e705f07be42b23cbfbc9228bf0e4d593ce10b033f29cbf03ed509383f7e1d983e70ce08be0bb54abfeed117bfc2873abf4d840d3fc7bad83ebec137bfb684bc3e19e218be0ef94bbfea2116bf0fb6583cc9760e3f107ad63e14ae373f7e1d983e1a511abed15758bf282904bfc2873abf5917173fc807bd3ebec137bfb684bc3e55302abe117359bf4f5c02bf6bd3583c82e2173fdaacba3e30bb373f7e1d983e1f852bbed50964bf97a8debea0873abfb7621f3f92cb9f3ea2b437bfd191bc3eb5153bbe12fb64bf9fe8dabe8e01593ce5f21f3fff219d3e4bc8373f992a983eb6843cbea9bc6dbfe4f3b2beb1873abff931263fee7c7f3e9eef37bfed9ebc3e71ac4bbedd7d6ebfd103afbecfdb583c27c2263fa323793e2bf6373fb537983e3b014dbebde576bff0da75beb1873abfc8982b3f69002f3e8cdb38bf08acbc3e643b5fbe90a177bfd23a6abec7f0583cbf0e2c3f5452273e19e2383fd044983e401361be4c6d7dbf6972d1bda0873abf3bdf2f3f2f6ea33da7e838bf5bd3bc3eeb7375be5ebe7dbf4ab3b9bdc7f0583cc520303f98dd933d8cdb383f226c983e591777be36e57ebff889833ca0873abf2eff313f99bb16bbb9fc37bf91edbc3edc4683becade7ebfc425c73cc7f0583cbc05323f728a0ebc9eef373f5986983eefc983be89957dbf3cfadf3da0873abfb30c313f4f1e96bd9eef37bf43ade93e7f6abcbecade7ebfc425c73cc7f0583cbc05323f728a0ebc9eef373f4260c53ef5dbb7be36e57ebff889833ca0873abf2eff313f99bb16bbb9fc37bf7ac7e93e2bf6b7be535c7dbff8c6f03d96ec583c45d8303f5305a3bdb9fc373f0b46c53e76e0bcbe48fe78bfb076643eb1873abf24b92c3f9a771cbe8cdb38bf0c93e93e14d0c4be585478bfce16703e96ec583c9f3c2c3f780b24bea7e8383fb81ec53ecba1c5be0dc46ebf4df3b63ea0873abf787a253fd93d79be151d39bfba6be93e72f9cfbe85b16dbf5587bc3ec7f0583c6abc243f2e9080be151d393f66f7c43e60e5d0be09c45fbf0531f83ea0873abf9a081b3f3a23aabe151d39bf8351e93ea301dcbe384e5ebf2f6cfd3ec7f0583c910f1a3ff2d2adbef90f393f2fddc43eadfadcbe6e4f4cbfaa431a3fa0873abfa4700d3f5839d4be151d39bf4c37e93e1ea7e8be39804abf48a51c3f08cb583c9f3c0c3f6b9ad7bef90f393f14d0c43e43ade9be36cc34bfd95c353fa0873abf3f57fb3e637ff9be19e238bf302ae93e58a8f5be3baa32bfdd78373fcfdb583c3e79f83e4850fcbe19e2383ff8c2c43e7daef6be9e0c1ebfb265493fc2873abfc807dd3e917e0bbf470338bf302ae93ec05b00bf075f1cbf18b24a3ff9f4583cbe9fda3ed1910cbf10e9373ff8c2c43ef2b000bf75910abfe640573fc2873abfd26fbf3e4f1e16bf2bf637bf4c37e93e4faf04bf9cc408bfa565583f5512593cd191bc3eebe216bf6210383ff8c2c43e0f0b05bf19cae1be73d8653fa0873abf917e9b3e295c1fbf1ea738bf6744e93eccee09bfe46adcbe1423673ff209593c7e1d983e371a20bf39b4383f14d0c43e516b0abf1fa2a9be166a713fa0873abf55306a3e82e227bf273138bf8351e93ebb270fbfa1a3a5be4419723f3acf583c6688633e3e7928bf0b24383f4beac43eee7c0fbf941262bee290793fb1873abf2b87163e560e2dbf8cdb38bfba6be93ecff713bf8b4e56bece337a3f96ec583ce0be0e3e166a2dbfa7e8383f8104c53e386714bfcd1ea8bd3cd87e3fa0873abff853633d857c30bfa7e838bff085e93eba6b19bf8e3c90bd6f0e7f3ff9f4583cefc9433d65aa30bf8cdb383fb81ec53e07ce19bf2ded143d293d7f3fa0873abf1b2fddbc6ade31bfb9fc37bf43ade93ef6971dbfbcb3363d9a237f3ff9f4583c70ce08bddcd731bf9eef373f0b46c53ef2d21dbf9fb0043e73137d3fb1873abfefc9c3bd2e9030bf30bb37bf7ac7e93e72f91fbf8d0d0d3e08c77c3ff9f4583c6076cfbdc05b30bfbec1373f4260c53ee5f21fbfccb3623ecdad783fb1873abf780b24bebf7d2dbf30bb37bf9c33623e9fabadbc8d0d0d3e08c77c3ff9f4583c6076cfbdc05b30bfbec1373f637f193e9c3322bc9fb0043e73137d3fb1873abfefc9c3bd2e9030bf30bb37bf0a68623e04e70cbce9ee6a3edc2e783f5dfd583c7ac729beff212dbf30bb373ff54a193eec51b8bc863b9f3edb19723f90873abf0bb566beb07228bf9eef37bf2eff613e96210ebde239a33ead6a713f96ec583c1ff46cbe82e227bf2bf6373f8716193e98dd13bdc902d63eab23673fa0873abfb53798bec52020bfacad38bfc1ca613ef5db57bd5a64db3e45d7653f32e4583c76719bbeb7621fbf1ea7383f19e2183e1b0d60bd2994053f0b62583f90873abfbf7dbdbe107a16bf992a38bf5396613e061294bd295c073f4243573f32e4583c1283c0be068115bf992a383facad183ee25897bd96961d3fb45b473fa0873abfa4dfdebe3ee809bf1ea738bf1c7c613ec807bdbd5ce71f3fde76453f64e8583cdcd7e1be55c108bf90a0383f7593183ea5bdc1bdc8ed333f80d8323f90873abf1b9efebeb537f8be273138bfe561613eb072e8bd8c66353fac53313f0fb6583cbc9600bfabcff5be7e1d383f7593183efaedebbd5d50473f7fa01c3fb1873abf76e00cbf7daed6be8cdb38bfe561613e5eba09bebd1d493f7c421a3ff9f4583c7b140ebff38ed3befed4383f7593183e4d150cbe971c5b3f5567fd3ea0873abf75021abff2d2adbe871639bfe561613e0ad723be93905c3fbd35f83e9dd7583cbada1abf3a92abbe35ef383f7593183ef93126bee17c6a3fc095bc3ea0873abfddb524bf2eff81be19e238bf1c7c613edb8a3dbef31c693f62dbc23ed6c6583c86c924bfcba185be992a383facad183e234a3bbee1ec723fc977893ec7f0583c9a082bbf348037be19e2383f19e2183e174850be2db0733fe5d0823eb1873abf68222cbffca931beb53738bf8ab0613ece8852beb37c793f9207123ea0873abf40a42fbfee7cbfbd39b438bff7e4613e54e365beecdf793f6b2b063ef9f4583c40a42fbf7d3fb5bd8cdb383f8716193e9eef67be82ac7b3f2a6fc73ca0873abf33c431bf5bb13fbc273138bf6519623e90a078beeeb27b3f46d1833cf9f4583cc1ca31bf52499dbb2731383ff54a193e910f7abe058c7a3f3ba7b9bda0873abfc52030bf98dd933d8cdb38bf0a68623e0ad783beb03a7a3f2177d1bdc7f0583c3bdf2fbf2f6ea33da7e8383f9a99193eca3284be5470743f0b2a6abeb1873abf31082cbf5452273ea7e838bf22fd963ea857cabcb03a7a3f2177d1bdc7f0583c3bdf2fbf2f6ea33da7e8383f4260653e2e9020bc058c7a3f3ba7b9bda0873abfc52030bf98dd933d8cdb38bf5917973e728a0ebc3eb4733fcec775bef9f4583cc8982bbf69002f3e19e2383f9d11653ed044d8bc944b6b3fd103afbec2873abf99bb26bfd93d793eb9fc37bfd0d5963e32e62ebd22896a3f40f6b2bec7f0583c863826bfb7627f3e9eef373f2fdd643e583934bd32c8613fe7e3dabea0873abf000020bfff219d3e30bb37bfb4c8963eb1bf6cbd62d9603f97a8debe5512593c0e4f1fbf92cb9f3e4bc8373fc1a8643eb37b72bd3140563fbb5e02bfc2873abff5db17bfbe9fba3ebec137bf99bb963e2b8796bd9622553f282904bf6bd3583c022b17bf1b2fbd3e87a7373f8a8e643e9a9999bd5cc7483f572416bf90873abfad690ebff46cd63e30bb37bf7dae963e7e1db8bd2c82473f5ad417bf5512593cbf7d0dbfc7bad83ebec1373f5474643eec2fbbbd637f393f4f9028bfa0873abffc1803bf7cf2f03e9eef37bf62a1963e1a51dabd3010383fb41e2abfc7f0583ca1f801bfbc74f33e10e9373f5474643e8863ddbd5f79243f193b3dbfb1873abf423ee8be8bfd053f1ea738bf62a1963e5c8f02be9834223f1b2d3fbff9f4583c5d6de5beab3e073f90a0383f1d5a643e14d004be93560c3fbf9e4fbfc2873abf1ea7c8bec5fe123fd50938bf62a1963eacad18beef900a3f86c950bfeb1e593ca60ac6becff7133f9eef373f1d5a643e1a511abeddd1ef3e880f5cbfb1873abff1f4aabe0d711c3f30bb37bf62a1963ee7fb29bebe16ec3e8c115dbf6bd3583cb072a8beff211d3fa2b4373f5474643e569f2bbeebc9c43ef08866bfa0873abfb1bf8cbeefc9233f30bb37bf7dae963eb5153bbec0eac03e295c67bfc7f0583ce7fb89be1d5a243fbec1373f8a8e643eb6843cbe1ff5973e8df06ebfb1873abfbec157be43ad293f2bf637bf99bb963e71ac4bbe57e8933ebc936fbff9f4583c736851be3a232a3fb9fc373fc1a8643e711b4dbeefe23d3e8b6d76bfb1873abf70ce08be44fa2d3f1ea738bfd0d5963e2d215fbe1b13323e2efe76bf96ec583c378901be04562e3f1ea7383f2fdd643e772d61be8d2ac3bd603d823fa0873abfb9fc87bdd9ce373f575b31bfa69bc43ab29d6fbe6138573cd2a8823fc7f0583c0e4f2f3cd9ce373f0e2d323f984c953dc9e57fbe0151b03c6ea2823fa0873abf04e78c3c30bb373f9c3332bffaedeb3a128380bea25fdbbdf818823ff9f4583c992a98bd87a7373fca54313fbde3943dd6c56dbe3f8f71be66bb7e3fb1873abff9a027be5dfe333fe92631bfc364aa3a759358be575f7dbefb067e3f96ec583c8e0630bed881333fe926313f7446943d62a156beb56fbebef4fe733fa0873abf931884be7f6a2c3fca5431bfe02d903ac9e53fbedf16c4beace1723f5dfd583c67d587be569f2b3fe561313f98dd933d48bf3dbe7154f6bead17673fa0873abf431cabbeaeb6223f812632bf6f12833a55302abe4419fabe4d13663fd6c6583c7b14aebe13f2213f8126323f2aa9933d1ea728be121710bf19ae5a3fa0873abfb1e1c9be9e5e193f0a6832bf6f12833a759318be6de111bf4a7d593fd6c6583cf163ccbe7593183fee5a323f2aa9933d3d0a17be0ebb23bfa5654c3fb1873abfaf94e5be80480f3f0a6832bffaed6b3a5ddc06be126b25bfe50a4b3fb22f593c9eefe7bee9480e3f2575323f2aa9933d265305be0ef735bf43553c3fb1873abf325500bf8195033f9c3332bf6f12833a8c4aeabdd07b37bfa5d73a3f0fb6583ce09c01bf986e023ff31f323f2aa9933d1d38e7bda54c4abfdc62263f90873abfb7620fbf029ae83e575b31bf6f12833a1214bfbdd7334cbfda04243f2bf9583c499d10bf4260e53ee561313f98dd933d11c7babdac555fbf49a1083f90873abf68911dbf1283c03eca5431bf52499d3a4d158cbdd6e560bfe204063f1c23593ce4831ebfc807bd3e7368313f0612943d4bc887bd0b9b6dbf33a4da3e90873abf3d0a27bf2c65993e9c3332bfc364aa3a5d6d45bd9e7c6ebf8bc2d63e8e01593ca2b427bfbd52963e2a3a323fe17a943d5bb13fbddc9c76bf9982ad3e90873abfb6f32dbf9fab6d3e0e2d32bfa69bc43a022b07bd0a4c77bfd175a93e08cb583ce4832ebf1d38673e8126323f4faf943db84002bd52d77ebf7de7673ea0873abf547434bf51da1b3e575b31bf89d2de3a499d80bc2d7a7fbfe7015c3ef9f4583cbde334bfcff7133eca54313f984c953d426065bcd71482bf2446af3da0873abfd50938bf431c6b3dca5431bf6c09f93afaedebba343082bfce00973df9f4583c992a38bf83c04a3d575b313f74b5953dfaedebbacb4782bf711e0ebda0873abf14ae37bf6519e2bc0e2d32bf27a0c93e27a079bf343082bfce00973df9f4583c992a38bf83c04a3d575b313fb22eee3ef6977dbfd71482bf2446af3da0873abfd50938bf431c6b3dca5431bf7ac7c93e7b147ebfeb3a82bfa77430bdf9f4583cde9337bf4d150cbd9c33323f5f07ee3e674479bf2b3181bf795c04beb1873abf54e335bf82e2c7bd0a6832bff085c93ec6dc75bfb30a81bf51dd0cbe5dfd583c58a835bfcff7d3bd0a68323f29eded3eeb7375bff5f47dbf64e963bea0873abfcac332bff9a027be7c6132bfd578c93e6ade71bfd6747dbf9f3c6cbe5dfd583c7c6132bf68912dbe0a68323f0de0ed3e8e7571bf0a4c77bf76c1a0beb1873abfed0d2ebfb1e169be0a6832bf9e5ec93ebbb86dbf079b76bf9ad0a4be32e4583cdb8a2dbfe9b76fbe7c61323fd6c5ed3e52496dbf747e6ebf1c07cebec2873abfd9ce27bf0f0b95be7c6132bf8351c93e477269bf399c6dbf7cedd1be32e4583c903127bfd9ce97be7c61323fbbb8ed3ede0269bf399a63bfda73f9bea0873abf371a20bf61c3b3be0a6832bf6744c93e2b1865bfdb8962bf1b2ffdbef9f4583c9b551fbfd95fb6be986e323fbbb8ed3e34a264bf09c156bfe84d11bfb1873abfb4c816bf2a3ad2be0e2d32bf6744c93e65aa60bf6a8555bf1f1113bf0fb6583cc6dc15bf0f0bd5bef31f323f9fabed3efb3a60bf0d6d44bfedf328bfa0873abfc7290abfd8f0f4beca5431bf6744c93e7a365bbf3d6442bfb4382bbf240e593cfed408bf4703f8be3c4e313f9fabed3e31995abf0b7b2abf859542bfb1873abf7368f1be8cb90bbfca5431bf8351c93e34a254bfd92728bf8f8d44bfb91a593ce948eebe1ff40cbf7368313fbbb8ed3e780b54bf38da11bf892555bfa0873abf29edcdbe5e4b18bf0e2d32bf9e5ec93e643b4fbf381210bf585656bfeb1e593c280fcbbea32319bfb840323fd6c5ed3efbcb4ebf1d1efabe57b461bfa0873abf4df3aebe736821bf0a6832bfba6bc93ed1224bbf4b59f6beb7b862bff9f4583c9f3cacbef31f22bf0a68323ff2d2ed3ef5b94abf6f64cebef6436cbfb1873abf72f98fbea7e828bf0a6832bfd578c93e903147bfa46fcabe4c186dbff9f4583c8d288dbef08529bf7c61323f29eded3e42cf46bf42cda0bef1bc74bfa0873abfadfa5cbe0e4f2fbf9c3332bf0c93c93e4a7b43bf10b29cbe4e6175bf8e01593c99bb56be20d22fbf8126323f44faed3efc1843bf6ea74dbeaa497cbfa0873abfa8350dbe810435bfc58f31bf5ebac93ef2413fbfc8b541bef5da7cbf2bf9583c02bc05beb45935bfc58f313f9621ee3e6dc53ebf6808a7bd462480bfa0873abf8b6c67bdab3e37bf0e2d32bf95d4c93e6de73bbfe2e995bd933780bfc7f0583c16fb4bbde25837bf2a3a323fcd3bee3e51da3bbff88b593c107a80bfa0873abff775603c508d37bf0a6832bfd42bc53ecd3baebee2e995bd933780bfc7f0583c16fb4bbde25837bf2a3a323f5ebae93e5839b4be6808a7bd462480bfa0873abf8b6c67bdab3e37bf0e2d32bf2653c53e4fafb4be8274b13ca37380bff9f4583c9c33a23cc28637bf0a68323f0c93e93ed6c5adbe774cdd3d3a587fbfa0873abf54e3a53df46c36bf0a6832bf9d11c53e3945a7be4566ee3ddf177fbfc7f0583ca1f8b13d143f36bf0a68323fd578e93e27c2a6be567d4e3e3e7a7bbfb1873abf99bb163eb8af33bf0a6832bf8104c53eaed89fbef8e3563e5b077bbff9f4583c08ac1c3e865a33bf0a68323fba6be93e80489fbead4e963e4e6175bfb1873abf2c65593eb7622fbf7c6132bf4beac43e470398be84679a3e5abc74bf2bf9583c9b555f3ec0ec2ebf0a68323f8351e93ee25897be2d05c43e8b196dbfa0873abf711b8d3e637f29bf0a6832bf2fddc43e05c58fbef8f9c73eb7426cbfc7f0583c3bdf8f3ea7e828bf0a68323f6744e93ea01a8fbe8cf3ef3ef5b962bfa0873abf832fac3e0e2d22bf0a6832bf14d0c43e394587be16bdf33eb3b661bff9f4583cfbcbae3e8e7521bf0a68323f4c37e93ed49a86be90da0c3ff35956bf7f873abf95d4c93e2c6519bf7c6132bff8c2c43e363c7dbe59a70e3f242955bf5512593cf163cc3ee78c18bf7c61323f4c37e93e36cd7bbe8b8a203fee0748bfa0873abf1dc9e53e643b0fbf7c6132bff8c2c43e1f856bbe5933223f8aaf46bf5512593cd509e83e5b420ebf986e323f302ae93e55306abe55bf323f1ff537bf90873abfb762ff3e98dd03bf986e32bff8c2c43e07ce59be5e4b343f817736bf0e4d593c29cb003fcac302bf2575323f302ae93e3e7958be2768433f524426bfa0873abf1f850b3ffbcbeebeee5a32bf14d0c43e273148beefc8443ff19d24bf5512593c287e0c3fba49ecbe986e323f4c37e93e27c246bef04e523fe81513bfa0873abfdd24163fa167d3be7c6132bf14d0c43e7dae36be8f8a533f555011bf5512593c94f6163f7cf2d0be0a68323f6744e93eb45935bebd555f3f1b2ffdbea0873abf0e4f1f3fd95fb6be986e32bf2fddc43eaf9425be8868603f006ff9be8e01593ca913203f7dd0b3be0a68323f8351e93ee63f24be5d6c6a3fa3e8d1beb1873abf9031273ff5db97be7c6132bf66f7c43ef4fd14bec24c6b3f69fdcdbef9f4583c67d5273ff4fd94be7c61323f9e5ee93e61c313bec26b733f1bbba4beb1873abf68912d3f7b836fbe0a6832bf8104c53eb81e05be971b743f32aea0bef9f4583c5f072e3fb1e169be0a68323fd578e93e26e403bec8407a3f32596cbea0873abf0a68323f68912dbe0a6832bfb81ec53efaedebbdfdbf7a3ff70564be32e4583ccac3323f30bb27be7c61323f0c93e93e43ade9bdcae27e3f65e40cbea0873abfe6ae353fcff7d3bd0a6832bfef38c53eceaacfbddc2f7f3fe46a04bec7f0583c54e3353ff016c8bd7c61323f43ade93e166acdbd94a1803f40c230bda0873abfde93373f4d150cbd9c3332bf2653c53e355ebabd75ae803f2f6f0ebdc7f0583c14ae373f1cebe2bc0e2d323f7ac7e93e11c7babde696803ff819973da0873abfd509383f371a403dc58f31bf2cd49a3dc74b77be75ae803f2f6f0ebdc7f0583c14ae373f1cebe2bc0e2d323f2b87163ef85383be94a1803f40c230bda0873abfde93373f4d150cbd9c3332bf76719b3defc983be917b803f944faf3dc7f0583c10e9373fad695e3dc58f313f8638163e8fc275be1c427d3f0cb2453eb1873abfd8f0343f9487053ea1f831bf516b9a3d9d1165beaddb7c3fd9214e3e2bf9583ca69b343f96b20c3e2eff313f1904163e0ad763bef739763f1c0c9d3ea0873abf6e34303fd0b3593e378931bf75029a3d4ed151bedb50753f24d6a23ec7f0583cb29d2f3f09f9603ec58f313fe2e9153e72f94fbefd4a6b3f8bc2d63ea0873abfec51283f7dd0933e0e2d32bf07ce993d91ed3cbe59696a3f9eb2da3e6bd3583c6b9a273fd0d5963e9c33323f74b5153ec8983bbef46a603f130e013fb1873abfa913203f46b6b33e986e32bf9a99993dfaed2bbec45b5f3fb3eb023fd6c6583c295c1f3ff46cb63e7c61323f74b5153efa7e2abef486533f42b5153fa0873abf22fd163f97ffd03eee5a32bf9a99993d516b1abe944c523fb073173f7940593c3411163f865ad33e2575323f3d9b153e871619be93c6443f8200293fd3873abf9a770c3fd656ec3e986e32bf9a99993d70ce08be5e63433f76a42a3f5512593c917e0b3fa9a4ee3e2575323f3d9b153e705f07becb4d343fddd23a3fa0873abfb7d1003fe5d0023f0a6832bf9a99993db22eeebd2fc4323fe7523c3fb22f593c9b55ff3e0ad7033fb37b323f3d9b153eb150ebbdc635223fa6094b3f90873abfd509e83ee9480e3f0a6832bf9a99993d83c0cabd2f88203f49634c3fc7f0583ccba1e53ef2410f3f0a68323f74b5153ef016c8bd21ac0e3fe580593f90873abf0d71cc3ecc7f183f0a6832bf07ce993d9eefa7bd59df0c3fb3b15a3feb1e593c95d4c93ef54a193f2575323f74b5153e0b46a5bd16bdf33e7b14663fa0873abf6900af3e006f213f7c6132bf75029a3d6ff085bdb3eeef3edb18673fd6c6583cba49ac3e0e2d223fee5a323fabcf153edc4683bd1ff5c73e1aa4703f90873abf3bdf8f3e35ef283f7c6132bfe3369a3dccee49bdc2f6c33eaf79713feb1e593c3b018d3ed578293f2575323f1904163e810445bd19599a3ee71b793fb1873abf2d215f3e32e62e3f0a6832bfbe9f9a3dbada0abd8a3b963edbc0793ff209593c2c65593e9b552f3f986e323f4f1e163e6ff005bda818573e0f627f3fb1873abf3fc61c3e865a333f0a6832bf9a089b3d091b9ebc02b74e3e45d57f3f8e01593cd0d5163eb8af333f0a68323fbd52163ee2e995bc9757ee3dc3ba813fb1873abf33c4b13d143f363f0a6832bf76719b3d11c7babb0743dd3df0da813f96ec583ce6aea53d8273363f7c61323f2b87163e75029abb0743dd3df0da813f96ec583ce6aea53d8273363f7c61323f74b5953dd42b85be9757ee3dc3ba813fb1873abf33c4b13d143f363f0a6832bf6c09f93ad42b85be - m_CompressedMesh: - m_Vertices: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_UV: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Normals: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Tangents: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_Weights: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_NormalSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_TangentSigns: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_FloatColors: - m_NumItems: 0 - m_Range: 0 - m_Start: 0 - m_Data: - m_BitSize: 0 - m_BoneIndices: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_Triangles: - m_NumItems: 0 - m_Data: - m_BitSize: 0 - m_UVInfo: 0 - m_LocalAABB: - m_Center: {x: 0.010875046, y: 0.008525968, z: -0.558532} - m_Extent: {x: 1.028691, y: 1.012251, z: 0.571795} - m_MeshUsageFlags: 0 - m_BakedConvexCollisionMesh: - m_BakedTriangleCollisionMesh: - m_MeshMetrics[0]: 1 - m_MeshMetrics[1]: 1 - m_MeshOptimizationFlags: 1 - m_StreamData: - serializedVersion: 2 - offset: 0 - size: 0 - path: diff --git a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh.meta b/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh.meta deleted file mode 100644 index 5d56c80d5..000000000 --- a/VisualPinball.Unity/Assets/Art/Meshes/Kicker/Williams.mesh.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bb28ab2e121914047b7c0529a0c20fc9 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 0 - userData: - assetBundleName: - assetBundleVariant: From 058fd92d72a3e1e9c12a0504a12e5b623a20af77 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 9 Dec 2024 00:23:53 +0100 Subject: [PATCH 144/208] doc: Start documentation about asset library. --- .../creators-guide/editor/asset-library.md | 173 ++++++++++++++++++ .../asset-library-example-1-maps.png | Bin 0 -> 256254 bytes .../asset-library-example-1-uvmap.jpg | Bin 0 -> 390711 bytes .../asset-library-example-2-maps.png | Bin 0 -> 307468 bytes .../asset-library-example-2-uvmap.jpg | Bin 0 -> 455685 bytes .../asset-library-example-geo.jpg | Bin 0 -> 60827 bytes 6 files changed, 173 insertions(+) create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library.md create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-maps.png create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-uvmap.jpg create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-2-maps.png create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-2-uvmap.jpg create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-geo.jpg diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library.md new file mode 100644 index 000000000..c5b10953c --- /dev/null +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library.md @@ -0,0 +1,173 @@ +--- +uid: asset_library +title: Asset Library +description: The asset library is a collection of all the assets used in your pinball project, such as prefabs, 3D models, materials and sounds. +--- +# Asset Library + +The asset library is a centrally maintained collection of pinball assets that table authors can use in their table builds. It comes with a browser, a tagging system, search and preview. + +## Contributing + +All assets are free to use, and anybody can contribute to it. + +### Material Separation + +When starting off, you usually already have some geometry, and in case you're about to contribute multiple assets, you're probably wondering how apply your materials. When dealing with multiple objects, there are generally **two approaches** how to apply materials to them. + +Let's say we want to add a bunch of hit targets. This is the geometry we have: + +![target geometry](asset-library/asset-library-example-geo.jpg) + +With both approaches, these files will be generated: + +- `Hit Targets.fbx` - The exported `.fbx` file. +- `Hit Target - Rectangle.prefab` - The Unity prefab of the rectangular hit target. +- `Hit Target - Rectangle, Slim.prefab` - The prefab of the slim hit target. +- `Hit Target - Round.prefab` - The round hit target + +Let's look at how the two approaches apply materials to these objects. + +#### Approach 1 - One material per object + +This way of applying materials is to use a single material for every object. This works best if no part of the object contains any transparency or subsurface scattering or any other special attributes that are not part of Unity's PBR workflow. + +![uv map](asset-library/asset-library-example-1-uvmap.jpg) + +The UV mapping in this case is quite standard - map the entire object to one texture set. + +After creating the materials and baking them out to the maps, it renders as if multiple materials were applied to the object, but technically, it's a single material: + +![texture maps](asset-library/asset-library-example-1-maps.png) +
*The base map, mask map and normal map of the rectangle hit target material.
* + +This would result in the following files: + +- `Drop Target - Rectangle.mat` - The Unity material applied to the entire mesh +- `Drop Target - Rectangle - BaseMap.png` - The color map of the material +- `Drop Target - Rectangle - MaskMap.png` - The mask map of the material +- `Drop Target - Rectangle - Normal.png` - The normal map of the material + +> [!NOTE] +> For brevity, we'll skip the other two materials that cover the slim and round hit target. + +#### Approach 2 - Texture atlas + +Instead of grouping per object, we could also group by "physical" material, i.e. one material for the plastic, one for the metal, and one for the rubber, but applied to all three objects. + +![uv map](asset-library/asset-library-example-2-uvmap.jpg) + +Here are the texture maps for the plastic material. It's valid for all three hit targets, and what's called a *texture atlas*: + +![texture maps](asset-library/asset-library-example-2-maps.png) + +This would result in the following files: + +- `Drop Targets - Plastic.mat` - The Unity material +- `Drop Targets - Plastic - BaseMap.png` - The color map of the plastic material +- `Drop Targets - Plastic - MaskMap.png` - The mask map of the plastic material +- `Drop Targets - Plastic - Normal.png` - The normal map of the plastic material + +> [!NOTE] +> For brevity, we'll skip the other two materials that cover the metal and rubber material. + +#### Which Approach is Best? + +Both approaches have pros and cons. In terms of performance, the pros and cons depend on whether the scene (a pinball table) is likely to include many of the objects you're creating. If that's the case, then the better approach is #2, the texture atlas: + +- Less draw calls, as multiple objects can share the same material and texture set. +- More optimized memory, since a single material and texture set can be more efficient than multiple individual materials and textures. +- More batching-friendly, as objects using the same material can be dynamically batched by Unity. + +There is also a non-technical advantage: It's easier to create variations of your materials. For instance, if you want to create a worn version of the plastic, you'd simply bake out a new set of maps which you can then apply independently to your objects, versus having to do it individually for each object. + +However, if the objects are less likely to be all present in the scene, then approach #1 probably the better choice: + +- More optimized memory, since none of the textures are unused +- Easier UV-mapping, since UV-mapping multiple objects to a single map can be more challenging and less trivial to understand than to a single object. + +In general practice, for small objects that have lots of variations and that are likely to find themselves together on the same table, we're using a texture atlas. This includes screws, rods, washers, spacers, posts, and so on. For more complex objects, we're using a single material or even multiple materials per object. This includes bumpers, targets, flippers, gates, spinners, etc. These are generally objects that don't have any variation per table. + +## Naming Conventions + +Asset naming is somehow important because it can help the user figure out the relations between assets. There are several things to name: + +- **Prefab** - The name of the Unity prefab that shows up in the asset browser. +- **Model File** - The model file, i.e. the `.fbx` or `.obj` files. +- **Material Slot** - The names of the material slots within the model file. +- **Unity Material** - The name of the material within Unity. +- **Texture File** - The name of the various maps generated per material. + +> [!NOTE] +> There is also the original model source of the program used to model, e.g. Blender. How to name this is up to the user, since that's not typically a file that is part of the project. However, for the asset library, we're including the `.blend` files, and they have a naming convention, too. + +To complicate things more, the relations between prefabs, models, and materials can different. + +- A model file can contain multiple meshes, e.g. we keep all VPX gates in one .fbx file. +- A prefab might consists of multiple meshes, sometimes from multiple files, e.g. the bumper ring and base are common in many bumper variations. +- A mesh might use multiple materials, but a material can also be used for multiple meshes (see the two aproaches above). +- A material usually uses multiple textures, or maps, but in the vast majority of cases, these maps are specific to the material. + +### Prefabs + +Prefabs are the files that appear in the asset library. They link the mesh together with the materials and the Unity components necessary for the prefab to work. We name them as descriptively as possible: + +``` + () +``` + +The `` goes from more to less specific, using commas. The `` is the least specific part of the description. If an asset is an original VPX asset, `` is "VPX". + +Examples: +- `Bumpers/Williams/Bumper (Williams).prefab` +- `Kickers/VPX/Kicker, Cup 2 (VPX).prefab` +- `Hardware/6 Gauge/Hex Nut, Flanged (6-32).prefab` +- `Posts/Plastic Posts/Post, Plastic, Star, Double, Rubber Rings, Collidable (1.0625in).prefab` + +### Model Files + +Model files are what's exported from your 3D software. They are usually `.fbx`, `.glb` or `.obj` files, although `.fbx` is (still) recommended. These are where Unity pulls the mesh data from. + +Their naming isn't essential since they are rarely user facing, and we usually keep only one model file per folder, so they're named after their folder name, but still in a way it makes it clear what they are. + +Examples: +- `Bumpers/Williams/Bumper (Williams).fbx` +- `Kickers/VPX/Kicker, Cup 2.fbx` +- `Hardware/6 Gauge/Hardware 6-gauge.fbx` +- `Posts/Plastic Posts/Plastic Posts.fbx` + +### Material Slots + +Material slots are how the materials in the model file are named. If you're using Substance Painter, that's the default way how your generated maps will be named. However, this is only relevant if you're using multiple materials for an object, since one-object materials (approach 1 above) have the same name as the prefab. + +For texture atlases, the slots should be named as short as possible and describe the material, for example `Base` for the bumper base, or `Cap`. + +For materials that cover the entire object, they should be named like the object. + +### Unity Materials + +Unity materials are visible by the table creators, so it's important to name them in a way that makes clear what they are. Depending on whether you're using texture atlases or not, they should include the name of the material. + +Examples: + +- `Bumpers/Williams/Bumper (Williams) - Base.mat` +- `Bumpers/Williams/Bumper (Williams) - Ring.mat` +- `Kickers/VPX/Kicker, Cup 2.mat` +- `Hardware/6 Gauge/Hardware 6-gauge - Metal.mat` +- `Hardware/6 Gauge/Hardware 6-gauge - Nylon.mat` +- `Posts/Plastic Posts/Plastic Post - Metal.mat` +- `Posts/Plastic Posts/Plastic Post - Plastic, Red.mat` +- `Posts/Plastic Posts/Plastic Post - Plastic, Yellow.mat` + +### Texture Maps + +Texture files are closely related to a material, so we'll name them the same and append the type of map. + +Examples: + +- `Bumpers/Williams/Bumper (Williams) - Base - BaseMap.png` +- `Bumpers/Williams/Bumper (Williams) - Base - Normal.png` +- `Kickers/VPX/Kicker, Cup 2 - BaseMap.png` +- `Hardware/6 Gauge/Hardware 6-gauge - Metal - BaseMap.png` +- `Posts/Plastic Posts/Plastic Post - Plastic, Red - MaskMap.png` +- `Posts/Plastic Posts/Plastic Post - Plastic, Red - Normal.png` diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-maps.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-maps.png new file mode 100644 index 0000000000000000000000000000000000000000..d460a7253d79b0c098e01f7cc2015c8ab320b1c3 GIT binary patch literal 256254 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%0DKjtk1`3U<)d{pWWKZTWG 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 Hu0mjf0gg1D literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-uvmap.jpg b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-1-uvmap.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b1fcc5cba5b5390c86a491945b710ff593cbaa96 GIT binary patch literal 390711 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
Ml;HEaVq-k62%a|>{upN$(X5wbb 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%0Dx;s~5{`ki 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 H%zpSAV_2%N literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-2-maps.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/asset-library/asset-library-example-2-maps.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1e7da81275796daa31c16cf529b5c215c8e20c GIT binary patch literal 307468 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,Hv1->+Cv&4q((y+aZ&E4tH3ULK0Jmg7-U*PRI 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%0AwkbOI?W* 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

/// - private void GenerateSpinnerCollider(ref ColliderReference colliders) + /// + private void GenerateSpinnerCollider(ref ColliderReference colliders, float zPosition) { const float halfLength = 40f; @@ -60,8 +61,8 @@ private void GenerateSpinnerCollider(ref ColliderReference colliders) ); // todo probably broke surface - var lineSeg0 = new LineCollider(v1, v2, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); - var lineSeg1 = new LineCollider(v2, v1, -2f * PhysicsConstants.PhysSkin, 0, _api.GetColliderInfo()); + var lineSeg0 = new LineCollider(v1, v2, zPosition + -2f * PhysicsConstants.PhysSkin, zPosition, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, zPosition + -2f * PhysicsConstants.PhysSkin, zPosition, _api.GetColliderInfo()); colliders.Add(new SpinnerCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); } From 0d7bb077f8879e211ca8fd74554966afdb05d1de Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 14 Dec 2024 11:14:28 +0100 Subject: [PATCH 155/208] chore: Formatting --- .../VPT/Spinner/SpinnerColliderComponent.cs | 4 ++-- .../VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index 040384596..c48f2a234 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -32,7 +32,7 @@ public class SpinnerColliderComponent : ColliderComponent _isKinematic; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index e3cbadcd0..6645039d1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -36,8 +36,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Spinner")] - public class SpinnerComponent : MainRenderableComponent, - ISwitchDeviceComponent, IRotatableAnimationComponent + public class SpinnerComponent : MainRenderableComponent, ISwitchDeviceComponent, IRotatableAnimationComponent { #region Data From 843ffea6cd4e3d8c00658fd075f635464fa3f10e Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 14 Dec 2024 11:15:18 +0100 Subject: [PATCH 156/208] gate: Add paramters to move collider down and on y-axis. --- .../VPT/Gate/GateColliderInspector.cs | 6 ++++++ .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 2 +- .../VPT/Gate/GateColliderComponent.cs | 8 ++++++++ .../VPT/Gate/GateColliderGenerator.cs | 19 +++++++++++-------- 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs index 197ab4711..097b3d31b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Gate/GateColliderInspector.cs @@ -25,6 +25,8 @@ namespace VisualPinball.Unity.Editor public class GateColliderInspector : ColliderInspector { private SerializedProperty _isKinematicProperty; + private SerializedProperty _zLowProperty; + private SerializedProperty _distanceProperty; private SerializedProperty _angleMinProperty; private SerializedProperty _angleMaxProperty; private SerializedProperty _elasticityProperty; @@ -38,6 +40,8 @@ protected override void OnEnable() base.OnEnable(); _isKinematicProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent._isKinematic)); + _zLowProperty = serializedObject.FindProperty(nameof(GateColliderComponent.ZLow)); + _distanceProperty = serializedObject.FindProperty(nameof(GateColliderComponent.Distance)); _angleMinProperty = serializedObject.FindProperty(nameof(GateColliderComponent._angleMin)); _angleMaxProperty = serializedObject.FindProperty(nameof(GateColliderComponent._angleMax)); _elasticityProperty = serializedObject.FindProperty(nameof(GateColliderComponent.Elasticity)); @@ -56,6 +60,8 @@ public override void OnInspectorGUI() BeginEditing(); PropertyField(_isKinematicProperty, "Movable"); + PropertyField(_zLowProperty, "Z-Low"); + PropertyField(_distanceProperty, "Distance"); PropertyField(_angleMinProperty, "Close Angle"); PropertyField(_angleMaxProperty, "Open Angle"); PropertyField(_elasticityProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index 569d1ab18..0042d02e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -107,7 +107,7 @@ public void Lift(float speed, float angleDeg) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); + var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, ColliderComponent.ZLow, ColliderComponent.Distance, translateWithinPlayfieldMatrix); if (ColliderComponent._isKinematic) { colliderGenerator.GenerateColliders(ref kinematicColliders); } else { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index b28449b90..40b07e45a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -36,6 +36,14 @@ public class GateColliderComponent : ColliderComponent, [ToolboxItem("Angle of bracket/plate when closed")] public float _angleMin; + [Range(-75, -50f)] + [ToolboxItem("Bottom Z position of the gate collider, relative to the axis.")] + public float ZLow = -50f; + + [Range(-50, 50)] + [ToolboxItem("Distance in gate direction of the collider.")] + public float Distance = 0f; + [Min(0)] [ToolboxItem("How much damping is applied during movement")] public float Damping = 0.985f; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 00f19dd40..32c602900 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -28,15 +28,18 @@ internal class GateColliderGenerator private readonly GateApi _api; private readonly float4x4 _matrix; - private const float ZLow = -2f * PhysicsConstants.PhysSkin; + private readonly float _zLow; + private readonly float _dist; private const float ZHigh = 0; - internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderData collData, float4x4 matrix) + internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderData collData, float zLow, float dist, float4x4 matrix) { _api = gateApi; _data = data; _collData = collData; _matrix = matrix; + _zLow = zLow; + _dist = dist; } internal void GenerateColliders(ref ColliderReference colliders) // var height = table.GetSurfaceHeight(_data.Surface, _data.Center.X, _data.Center.Y); @@ -68,11 +71,11 @@ private void GenerateGateCollider(ref ColliderReference colliders) // position, we generate them relative to the origin and then transform them. const float halfLength = 10f; - var v1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), 0); - var v2 = new float2(halfLength + PhysicsConstants.PhysSkin, 0); + var v1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), _dist); + var v2 = new float2(halfLength + PhysicsConstants.PhysSkin, _dist); - var lineSeg0 = new LineCollider(v1, v2, ZLow, ZHigh, _api.GetColliderInfo()); - var lineSeg1 = new LineCollider(v2, v1, ZLow, ZHigh, _api.GetColliderInfo()); + var lineSeg0 = new LineCollider(v1, v2, _zLow, ZHigh, _api.GetColliderInfo()); + var lineSeg1 = new LineCollider(v2, v1, _zLow, ZHigh, _api.GetColliderInfo()); colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo()), _matrix); } @@ -107,7 +110,7 @@ private void GenerateBracketColliders(ref ColliderReference colliders) colliders.Add(new CircleCollider( new float2(halfLength, 0), 1f, - ZLow, + _zLow, 0, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate ), _matrix); @@ -115,7 +118,7 @@ private void GenerateBracketColliders(ref ColliderReference colliders) colliders.Add(new CircleCollider( new float2(-halfLength, 0), 1f, - ZLow, + _zLow, 0, _api.GetColliderInfo(ItemType.Invalid) // hack to not treat this hit circle as gate ), _matrix); From 0272957314994eca1999d5370f14f74d278193f3 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 15 Dec 2024 11:16:08 +0100 Subject: [PATCH 157/208] flipper: Make kinematic. --- .../VPT/Flipper/FlipperColliderInspector.cs | 3 +++ .../VisualPinball.Unity/Game/PhysicsState.cs | 7 +++++-- .../Game/PhysicsStaticCollision.cs | 2 +- .../Physics/Collider/ColliderReference.cs | 4 ++++ .../Physics/Collision/ContactPhysics.cs | 2 +- .../VisualPinball.Unity/VPT/Flipper/FlipperApi.cs | 2 +- .../VPT/Flipper/FlipperCollider.cs | 2 +- .../VPT/Flipper/FlipperColliderComponent.cs | 13 ++++++++++++- 8 files changed, 28 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs index 11c51aa95..eed1d90f7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs @@ -37,6 +37,7 @@ public class FlipperColliderInspector : ColliderInspector octree, ref Native #region States - internal ref FlipperState GetFlipperState(int colliderId) => ref FlipperStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref FlipperState GetFlipperState(int colliderId, ref NativeColliders colliders) => ref FlipperStates.GetValueByRef(colliders.GetItemId(colliderId)); internal ref PlungerState GetPlungerState(int colliderId) => ref PlungerStates.GetValueByRef(Colliders.GetItemId(colliderId)); @@ -148,6 +148,9 @@ internal void Transform(int colliderId, float4x4 matrix) case ColliderType.Gate: KinematicColliders.Gate(colliderId).Transform(KinematicCollidersAtIdentity.Gate(colliderId), matrix); break; + case ColliderType.Flipper: + KinematicColliders.Flipper(colliderId).Transform(KinematicCollidersAtIdentity.Flipper(colliderId), matrix); + break; } } @@ -211,7 +214,7 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt ball.CollisionEvent.HitTime, false, false, false); case ColliderType.Flipper: - ref var flipperState = ref GetFlipperState(colliderId); + ref var flipperState = ref GetFlipperState(colliderId, ref colliders); ref var flipperCollider = ref colliders.Flipper(colliderId); return flipperCollider.HitTest(ref newCollEvent, ref InsideOfs, ref flipperState.Hit, in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ball, ball.CollisionEvent.HitTime); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index d48b1e31e..9db906ee2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -113,7 +113,7 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.Flipper: - ref var flipperState = ref state.GetFlipperState(colliderId); + ref var flipperState = ref state.GetFlipperState(colliderId, ref colliders); ref var flipperCollider = ref colliders.Flipper(colliderId); flipperCollider.Collide(ref ball, ref ball.CollisionEvent, ref flipperState.Movement, ref state.EventQueue, in ball.Id, in flipperState.Tricks, in flipperState.Static, diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 6de4141c7..efb443692 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -131,6 +131,10 @@ public void TransformToIdentity(NativeParallelHashMap itemIdToTra ref var gateCollider = ref GateColliders.GetElementAsRef(lookup.Index); gateCollider.Transform(GateColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Flipper: + ref var flipperCollider = ref FlipperColliders.GetElementAsRef(lookup.Index); + flipperCollider.Transform(FlipperColliders[lookup.Index], math.inverse(matrix)); + break; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs index b6b712a09..fac7aa738 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs @@ -37,7 +37,7 @@ internal static void Update(ref ContactBufferElement contact, ref BallState ball ref var collHeader = ref state.GetColliderHeader(ref colliders, collEvent.ColliderId); if (collHeader.Type == ColliderType.Flipper) { ref var flipperCollider = ref colliders.Flipper(collEvent.ColliderId); - ref var flipperState = ref state.GetFlipperState(collEvent.ColliderId); + ref var flipperState = ref state.GetFlipperState(collEvent.ColliderId, ref colliders); flipperCollider.Contact(ref ball, ref flipperState.Movement, in collEvent, in flipperState.Static, in flipperState.Velocity, hitTime, in gravity); } else { Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in gravity); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index 42d35f5d8..d8fb0f93f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -233,7 +233,7 @@ protected override void CreateColliders(ref ColliderReference colliders, } // and add ColliderComponent.Overshoot to the endangle so that the bounding box is correctly calculated - ColliderId = colliders.Add( + ColliderId = (ColliderComponent._isKinematic ? kinematicColliders : colliders).Add( new FlipperCollider( MainComponent.Height, MainComponent.FlipperRadiusMax, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 573e8d590..5ea74047c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -942,7 +942,7 @@ public static bool IsTransformable(float4x4 matrix) return !rotated && !scaled; } - private void Transform(FlipperCollider flipperCollider, float4x4 matrix) + public void Transform(FlipperCollider flipperCollider, float4x4 matrix) { #if UNITY_EDITOR if (!IsTransformable(matrix)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 25a77164f..80dab921e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Flipper Collider")] [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] - public class FlipperColliderComponent : ColliderComponent + public class FlipperColliderComponent : ColliderComponent, IKinematicColliderComponent { #region Data @@ -78,6 +78,17 @@ public class FlipperColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter); + #region IKinematicColliderComponent + + [Tooltip("If set, transforming this object during gameplay will transform the colliders as well.")] + public bool _isKinematic; + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + public float4x4 TransformationWithinPlayfield => MainComponent.LocalToWorldPhysicsMatrix; + + #endregion + #region FlipperTricks /// /// If set, apply Flipper Tricks Physics (nFozzy/RothBauerW) From 096411c39c1a5fb67486282c466dc57236c9dbb9 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 21 Dec 2024 22:12:23 +0100 Subject: [PATCH 158/208] physics: Add support items that are both kinematic and non-transformable. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 5 +- .../Game/PhysicsKinematics.cs | 7 +- .../VisualPinball.Unity/Game/PhysicsState.cs | 49 +++++++++++--- .../Game/PhysicsUpdateJob.cs | 8 +-- .../Physics/Collider/CircleCollider.cs | 8 ++- .../Physics/Collider/ColliderReference.cs | 43 +++++++++--- .../Physics/Collider/Line3DCollider.cs | 8 ++- .../Physics/Collider/LineCollider.cs | 8 ++- .../Physics/Collider/LineSlingshotCollider.cs | 8 ++- .../Physics/Collider/LineZCollider.cs | 9 ++- .../Physics/Collider/PointCollider.cs | 9 ++- .../Physics/Collider/TriangleCollider.cs | 8 ++- .../Physics/NativeColliders.cs | 66 ++++++++++++++++++- .../VPT/Flipper/FlipperCollider.cs | 7 +- .../VPT/Gate/GateCollider.cs | 7 +- .../VPT/Plunger/PlungerCollider.cs | 7 +- .../VPT/Spinner/SpinnerCollider.cs | 7 +- 17 files changed, 218 insertions(+), 46 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index e66ee2c7b..1c1a595e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -293,6 +293,7 @@ private void Update() KinematicColliders = _kinematicColliders, KinematicCollidersAtIdentity = _kinematicCollidersAtIdentity, KinematicColliderLookups = _kinematicColliderLookups, + KinematicTransforms = _kinematicTransforms.Ref, UpdatedKinematicTransforms = _updatedKinematicTransforms.Ref, NonTransformableColliderMatrices = _nonTransformableColliderMatrices.Ref, InsideOfs = _insideOfs, @@ -315,8 +316,8 @@ private void Update() var env = _physicsEnv.Ref[0]; var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref _kinematicColliders, - ref _kinematicCollidersAtIdentity, ref _updatedKinematicTransforms.Ref, ref _nonTransformableColliderMatrices.Ref, - ref _kinematicColliderLookups, ref events, + ref _kinematicCollidersAtIdentity, ref _kinematicTransforms.Ref, ref _updatedKinematicTransforms.Ref, + ref _nonTransformableColliderMatrices.Ref, ref _kinematicColliderLookups, ref events, ref _insideOfs, ref _ballStates.Ref, ref _bumperStates.Ref, ref _dropTargetStates.Ref, ref _flipperStates.Ref, ref _gateStates.Ref, ref _hitTargetStates.Ref, ref _kickerStates.Ref, ref _plungerStates.Ref, ref _spinnerStates.Ref, ref _surfaceStates.Ref, ref _triggerStates.Ref, ref _disabledCollisionItems.Ref, ref _swapBallCollisionHandling); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs index 4aa1b9ae3..07eaa2001 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs @@ -16,6 +16,7 @@ using NativeTrees; using Unity.Collections; +using Unity.Mathematics; using Unity.Profiling; using VisualPinball.Unity.Collections; @@ -42,13 +43,13 @@ internal static void TransformColliders(ref PhysicsState state) PerfMarkerTransform.End(); } - internal static NativeOctree CreateOctree(ref NativeColliders kinematicColliders, in AABB playfieldBounds) + internal static NativeOctree CreateOctree(ref PhysicsState state, in AABB playfieldBounds) { PerfMarkerBallOctree.Begin(); var octree = new NativeOctree(playfieldBounds, 1024, 10, Allocator.TempJob); - for (var i = 0; i < kinematicColliders.Length; i++) { - octree.Insert(i, kinematicColliders.GetAabb(i)); + for (var i = 0; i < state.KinematicCollidersAtIdentity.Length; i++) { + octree.Insert(i, state.KinematicCollidersAtIdentity.GetAabb(i, ref state.KinematicTransforms)); } PerfMarkerBallOctree.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 40fac9c02..4c8e3cae7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -29,7 +29,8 @@ internal struct PhysicsState internal NativeColliders Colliders; internal NativeColliders KinematicColliders; internal NativeColliders KinematicCollidersAtIdentity; - internal NativeParallelHashMap UpdatedKinematicTransforms; // transformations of the items, in vpx space. + internal NativeParallelHashMap UpdatedKinematicTransforms; // updated transformations of the items, in vpx space. + internal NativeParallelHashMap KinematicTransforms; // transformations of the items, in vpx space. internal NativeParallelHashMap NonTransformableColliderMatrices; internal NativeParallelHashMap KinematicColliderLookups; @@ -51,6 +52,7 @@ internal struct PhysicsState public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref NativeColliders colliders, ref NativeColliders kinematicColliders, ref NativeColliders kinematicCollidersAtIdentity, + ref NativeParallelHashMap kinematicTransforms, ref NativeParallelHashMap updatedKinematicTransforms, ref NativeParallelHashMap nonTransformableColliderMatrices, ref NativeParallelHashMap kinematicColliderLookups, ref NativeQueue.ParallelWriter eventQueue, @@ -67,6 +69,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native Colliders = colliders; KinematicColliders = kinematicColliders; KinematicCollidersAtIdentity = kinematicCollidersAtIdentity; + KinematicTransforms = kinematicTransforms; UpdatedKinematicTransforms = updatedKinematicTransforms; NonTransformableColliderMatrices = nonTransformableColliderMatrices; KinematicColliderLookups = kinematicColliderLookups; @@ -123,7 +126,14 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native #region Transform internal bool HasNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) => NonTransformableColliderMatrices.ContainsKey(colliders.GetItemId(colliderId)); - internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) => ref NonTransformableColliderMatrices.GetValueByRef(colliders.GetItemId(colliderId)); + internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) + { + var itemId = colliders.GetItemId(colliderId); + if (colliders.KinematicColliders) { + return ref KinematicTransforms.GetValueByRef(itemId); + } + return ref NonTransformableColliderMatrices.GetValueByRef(itemId); + } internal void Transform(int colliderId, float4x4 matrix) { @@ -131,25 +141,46 @@ internal void Transform(int colliderId, float4x4 matrix) { case ColliderType.Bumper: case ColliderType.Circle: - KinematicColliders.Circle(colliderId).Transform(KinematicCollidersAtIdentity.Circle(colliderId), matrix); + var circleCollider = KinematicColliders.Circle(colliderId); + if (circleCollider.Header.IsTransformed) { + circleCollider.Transform(KinematicCollidersAtIdentity.Circle(colliderId), matrix); + } break; case ColliderType.Point: - KinematicColliders.Point(colliderId).Transform(KinematicCollidersAtIdentity.Point(colliderId), matrix); + var pointCollider = KinematicColliders.Point(colliderId); + if (pointCollider.Header.IsTransformed) { + pointCollider.Transform(KinematicCollidersAtIdentity.Point(colliderId), matrix); + } break; case ColliderType.Line3D: - KinematicColliders.Line3D(colliderId).Transform(KinematicCollidersAtIdentity.Line3D(colliderId), matrix); + var line3DCollider = KinematicColliders.Line3D(colliderId); + if (line3DCollider.Header.IsTransformed) { + line3DCollider.Transform(KinematicCollidersAtIdentity.Line3D(colliderId), matrix); + } break; case ColliderType.Triangle: - KinematicColliders.Triangle(colliderId).Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); + var triangleCollider = KinematicColliders.Triangle(colliderId); + if (triangleCollider.Header.IsTransformed) { + triangleCollider.Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); + } break; case ColliderType.Spinner: - KinematicColliders.Spinner(colliderId).Transform(KinematicCollidersAtIdentity.Spinner(colliderId), matrix); + var spinnerCollider = KinematicColliders.Spinner(colliderId); + if (spinnerCollider.Header.IsTransformed) { + spinnerCollider.Transform(KinematicCollidersAtIdentity.Spinner(colliderId), matrix); + } break; case ColliderType.Gate: - KinematicColliders.Gate(colliderId).Transform(KinematicCollidersAtIdentity.Gate(colliderId), matrix); + var gateCollider = KinematicColliders.Gate(colliderId); + if (gateCollider.Header.IsTransformed) { + gateCollider.Transform(KinematicCollidersAtIdentity.Gate(colliderId), matrix); + } break; case ColliderType.Flipper: - KinematicColliders.Flipper(colliderId).Transform(KinematicCollidersAtIdentity.Flipper(colliderId), matrix); + var flipperCollider = KinematicColliders.Flipper(colliderId); + if (flipperCollider.Header.IsTransformed) { + flipperCollider.Transform(KinematicCollidersAtIdentity.Flipper(colliderId), matrix); + } break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index 24cca7598..f02cce6b9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -19,7 +19,6 @@ using Unity.Jobs; using Unity.Mathematics; using VisualPinball.Engine.Common; -using UnityEngine; namespace VisualPinball.Unity { @@ -38,6 +37,7 @@ internal struct PhysicsUpdateJob : IJob public NativeColliders Colliders; public NativeColliders KinematicColliders; public NativeColliders KinematicCollidersAtIdentity; + public NativeParallelHashMap KinematicTransforms; public NativeParallelHashMap UpdatedKinematicTransforms; public NativeParallelHashMap NonTransformableColliderMatrices; @@ -64,8 +64,8 @@ public void Execute() { var env = PhysicsEnv[0]; var state = new PhysicsState(ref env, ref Octree, ref Colliders, ref KinematicColliders, - ref KinematicCollidersAtIdentity, ref UpdatedKinematicTransforms, ref NonTransformableColliderMatrices, - ref KinematicColliderLookups, ref Events, + ref KinematicCollidersAtIdentity, ref KinematicTransforms, ref UpdatedKinematicTransforms, + ref NonTransformableColliderMatrices, ref KinematicColliderLookups, ref Events, ref InsideOfs, ref Balls, ref BumperStates, ref DropTargetStates, ref FlipperStates, ref GateStates, ref HitTargetStates, ref KickerStates, ref PlungerStates, ref SpinnerStates, ref SurfaceStates, ref TriggerStates, ref DisabledCollisionItems, ref SwapBallCollisionHandling); @@ -73,7 +73,7 @@ public void Execute() // create octree of kinematic-to-ball collision. should be okay here, since static colliders don't transform more than once per frame. PhysicsKinematics.TransformColliders(ref state); - var kineticOctree = PhysicsKinematics.CreateOctree(ref state.KinematicColliders, in PlayfieldBounds); + var kineticOctree = PhysicsKinematics.CreateOctree(ref state, in PlayfieldBounds); while (env.CurPhysicsFrameTime < InitialTimeUsec) // loop here until current (real) time matches the physics (simulated) time { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index 59dd7f1bb..e01682630 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -271,7 +271,7 @@ public void Transform(CircleCollider circle, float4x4 matrix) ZLow = t.z + circle.ZLow * s.z; } - public CircleCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y + Radius, ZLow)); var p2 = matrix.MultiplyPoint(new float3(Center.x + Radius, Center.y - Radius, ZLow)); @@ -285,8 +285,12 @@ public CircleCollider TransformAabb(float4x4 matrix) var min = math.min(p1, math.min(p2, math.min(p3, math.min(p4, math.min(p5, math.min(p6, math.min(p7, p8))))))); var max = math.max(p1, math.max(p2, math.max(p3, math.max(p4, math.max(p5, math.max(p6, math.max(p7, p8))))))); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + return new Aabb(min, max); + } + public CircleCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index efb443692..989f6e2d4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -17,7 +17,6 @@ using System; using Unity.Collections; using Unity.Mathematics; -using UnityEngine; using VisualPinball.Unity.Collections; namespace VisualPinball.Unity @@ -109,31 +108,59 @@ public void TransformToIdentity(NativeParallelHashMap itemIdToTra case ColliderType.Bumper: case ColliderType.Circle: ref var circleCollider = ref CircleColliders.GetElementAsRef(lookup.Index); - circleCollider.Transform(CircleColliders[lookup.Index], math.inverse(matrix)); + if (circleCollider.Header.IsTransformed) { + circleCollider.Transform(CircleColliders[lookup.Index], math.inverse(matrix)); + } else { + circleCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Point: ref var pointCollider = ref PointColliders.GetElementAsRef(lookup.Index); - pointCollider.Transform(PointColliders[lookup.Index], math.inverse(matrix)); + if (pointCollider.Header.IsTransformed) { + pointCollider.Transform(PointColliders[lookup.Index], math.inverse(matrix)); + } else { + pointCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Line3D: ref var line3DCollider = ref Line3DColliders.GetElementAsRef(lookup.Index); - line3DCollider.Transform(Line3DColliders[lookup.Index], math.inverse(matrix)); + if (line3DCollider.Header.IsTransformed) { + line3DCollider.Transform(Line3DColliders[lookup.Index], math.inverse(matrix)); + } else { + line3DCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Triangle: ref var triangleCollider = ref TriangleColliders.GetElementAsRef(lookup.Index); - triangleCollider.Transform(TriangleColliders[lookup.Index], math.inverse(matrix)); + if (triangleCollider.Header.IsTransformed) { + triangleCollider.Transform(TriangleColliders[lookup.Index], math.inverse(matrix)); + } else { + triangleCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Spinner: ref var spinnerCollider = ref SpinnerColliders.GetElementAsRef(lookup.Index); - spinnerCollider.Transform(SpinnerColliders[lookup.Index], math.inverse(matrix)); + if (spinnerCollider.Header.IsTransformed) { + spinnerCollider.Transform(SpinnerColliders[lookup.Index], math.inverse(matrix)); + } else { + spinnerCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Gate: ref var gateCollider = ref GateColliders.GetElementAsRef(lookup.Index); - gateCollider.Transform(GateColliders[lookup.Index], math.inverse(matrix)); + if (gateCollider.Header.IsTransformed) { + gateCollider.Transform(GateColliders[lookup.Index], math.inverse(matrix)); + } else { + gateCollider.TransformAabb(math.inverse(matrix)); + } break; case ColliderType.Flipper: ref var flipperCollider = ref FlipperColliders.GetElementAsRef(lookup.Index); - flipperCollider.Transform(FlipperColliders[lookup.Index], math.inverse(matrix)); + if (flipperCollider.Header.IsTransformed) { + flipperCollider.Transform(FlipperColliders[lookup.Index], math.inverse(matrix)); + } else { + flipperCollider.TransformAabb(math.inverse(matrix)); + } break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index ae69c2938..59d0e209d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -156,12 +156,16 @@ public void Transform(Line3DCollider line3D, float4x4 matrix) // the above also transforms the aabbs. } - public Line3DCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(_v1); var p2 = matrix.MultiplyPoint(_v2); + return new Aabb(math.min(p1, p2), math.max(p1, p2)); + } - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); + public Line3DCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index da9abaa40..d7b8d6b88 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -274,7 +274,7 @@ public void Transform(LineCollider lineCollider, float4x4 matrix) CalculateBounds(); } - public LineCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(new float3(V1, ZLow)); var p2 = matrix.MultiplyPoint(new float3(V1, ZHigh)); @@ -284,8 +284,12 @@ public LineCollider TransformAabb(float4x4 matrix) var min = math.min(p1, math.min(p2, math.min(p3, p4))); var max = math.max(p1, math.max(p2, math.max(p3, p4))); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + return new Aabb(min, max); + } + public LineCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index 508ff4319..8e6bbcf93 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -177,7 +177,7 @@ public void Transform(LineSlingshotCollider lineCollider, float4x4 matrix) CalculateBounds(); } - public LineSlingshotCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(new float3(V1, ZLow)); var p2 = matrix.MultiplyPoint(new float3(V1, ZHigh)); @@ -187,8 +187,12 @@ public LineSlingshotCollider TransformAabb(float4x4 matrix) var min = math.min(p1, math.min(p2, math.min(p3, p4))); var max = math.max(p1, math.max(p2, math.max(p3, p4))); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + return new Aabb(min, max); + } + public LineSlingshotCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 652aa60d4..f4b01518c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -173,13 +173,16 @@ public LineZCollider Transform(float4x4 matrix) return this; } - public LineZCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(new float3(XY, ZLow)); var p2 = matrix.MultiplyPoint(new float3(XY, ZHigh)); + return new Aabb(math.min(p1, p2), math.max(p1, p2)); + } - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(math.min(p1, p2), math.max(p1, p2))); - + public LineZCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index 74969b486..552cfab07 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -148,10 +148,15 @@ public void Transform(PointCollider point, float4x4 matrix) TransformAabb(matrix); } - public PointCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p = matrix.MultiplyPoint(P); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(p, p)); + return new Aabb(p, p); + } + + public PointCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index 10de5e4c6..bd6f11ff4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -194,7 +194,7 @@ public void Transform(TriangleCollider triangle, float4x4 matrix) CalculateBounds(); } - public TriangleCollider TransformAabb(float4x4 matrix) + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(Rgv0); var p2 = matrix.MultiplyPoint(Rgv1); @@ -203,8 +203,12 @@ public TriangleCollider TransformAabb(float4x4 matrix) var min = math.min(p1, math.min(p2, p3)); var max = math.max(p1, math.max(p2, p3)); - Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb(min, max)); + return new Aabb(min, max); + } + public TriangleCollider TransformAabb(float4x4 matrix) + { + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs index 82e62447d..fea9bd3e4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs @@ -21,6 +21,7 @@ using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; using Unity.Collections; +using Unity.Mathematics; using Unity.Profiling; using VisualPinball.Engine.VPT; @@ -469,8 +470,71 @@ public Aabb GetAabb(int index) case ColliderType.Spinner: return UnsafeUtility.ArrayElementAsRef(m_SpinnerColliderBuffer, lookup.Index).Bounds.Aabb; case ColliderType.Triangle: return UnsafeUtility.ArrayElementAsRef(m_TriangleColliderBuffer, lookup.Index).Bounds.Aabb; case ColliderType.Plane: return UnsafeUtility.ArrayElementAsRef(m_PlaneColliderBuffer, lookup.Index).Bounds.Aabb; + default: + throw new ArgumentException($"Unknown lookup type."); + } + } + + public Aabb GetAabb(int index, ref NativeParallelHashMap kinematicTransforms) + { + if (index < 0 || index >= m_Length) { + throw new IndexOutOfRangeException($"Invalid index {index} when looking up collider."); + } + var lookup = UnsafeUtility.ReadArrayElement(m_LookupBuffer, index); + switch (lookup.Type) { + case ColliderType.Circle: { + var collider = UnsafeUtility.ArrayElementAsRef(m_CircleColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Flipper: { + var collider = + UnsafeUtility.ArrayElementAsRef(m_FlipperColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Gate: { + var collider = UnsafeUtility.ArrayElementAsRef(m_GateColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Line3D: { + var collider = UnsafeUtility.ArrayElementAsRef(m_Line3DColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.LineSlingShot: { + var collider = UnsafeUtility.ArrayElementAsRef(m_LineSlingshotColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Line: { + var collider = UnsafeUtility.ArrayElementAsRef(m_LineColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.LineZ: { + var collider = UnsafeUtility.ArrayElementAsRef(m_LineZColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Plunger: { + var collider = UnsafeUtility.ArrayElementAsRef(m_PlungerColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Point: { + var collider = UnsafeUtility.ArrayElementAsRef(m_PointColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Spinner: { + var collider = UnsafeUtility.ArrayElementAsRef(m_SpinnerColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Triangle: { + var collider = UnsafeUtility.ArrayElementAsRef(m_TriangleColliderBuffer, lookup.Index); + return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); + } + case ColliderType.Plane: + { + // planes don't transform + return UnsafeUtility.ArrayElementAsRef(m_PlaneColliderBuffer, lookup.Index).Bounds.Aabb; + } + default: + throw new ArgumentException($"Unknown lookup type."); } - throw new ArgumentException($"Unknown lookup type."); } public bool IsTransformed(int index) => GetHeader(index).IsTransformed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 5ea74047c..17d214c2f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -961,9 +961,14 @@ public FlipperCollider Transform(float4x4 matrix) return this; } + public Aabb GetTransformedAabb(float4x4 matrix) + { + return Bounds.Aabb.Transform(matrix); + } + public FlipperCollider TransformAabb(float4x4 matrix) { - Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 0d9c26cf2..29fb209e1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -140,9 +140,14 @@ public void Transform(GateCollider collider, float4x4 matrix) Bounds = collider.LineSeg0.Bounds; } + public Aabb GetTransformedAabb(float4x4 matrix) + { + return Bounds.Aabb.Transform(matrix); + } + public GateCollider TransformAabb(float4x4 matrix) { - Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index dbfabf09a..f3578f1f2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -107,9 +107,14 @@ private void Transform(PlungerCollider collider, float4x4 matrix) JointBase1 = collider.JointBase1.Transform(matrix); } + public Aabb GetTransformedAabb(float4x4 matrix) + { + return Bounds.Aabb.Transform(matrix); + } + public PlungerCollider TransformAabb(float4x4 matrix) { - Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index b39e85cfb..577ec6d69 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -143,9 +143,14 @@ public void Transform(SpinnerCollider collider, float4x4 matrix) Bounds = collider.LineSeg0.Bounds; } + public Aabb GetTransformedAabb(float4x4 matrix) + { + return Bounds.Aabb.Transform(matrix); + } + public SpinnerCollider TransformAabb(float4x4 matrix) { - Bounds = new ColliderBounds(Header.ItemId, Header.Id, Bounds.Aabb.Transform(matrix)); + Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); return this; } From 18911b77c6ac4008655f5bca5023ae7d47539322 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 21 Dec 2024 23:57:58 +0100 Subject: [PATCH 159/208] chore: Clean up TransformationWithinPlayfield. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 8 ++++++-- .../VPT/Bumper/BumperColliderComponent.cs | 1 - .../VPT/Bumper/BumperComponent.cs | 6 ------ .../VPT/Flipper/FlipperCollider.cs | 15 +++++++++++++-- .../VPT/Flipper/FlipperColliderComponent.cs | 1 - .../VPT/Gate/GateColliderComponent.cs | 1 - .../VisualPinball.Unity/VPT/Gate/GateComponent.cs | 13 ------------- .../VPT/IKinematicColliderComponent.cs | 2 +- .../VPT/Plunger/PlungerColliderComponent.cs | 2 -- .../VPT/Plunger/PlungerComponent.cs | 5 ----- .../VPT/Primitive/PrimitiveColliderComponent.cs | 1 - .../VPT/Ramp/RampColliderComponent.cs | 1 - .../VisualPinball.Unity/VPT/Ramp/RampComponent.cs | 3 --- .../VPT/Rubber/RubberColliderComponent.cs | 1 - .../VPT/Rubber/RubberComponent.cs | 6 ------ .../VPT/Spinner/SpinnerColliderComponent.cs | 1 - .../VPT/Spinner/SpinnerComponent.cs | 14 -------------- .../VPT/Surface/SurfaceColliderComponent.cs | 2 -- .../VPT/Surface/SurfaceComponent.cs | 3 --- .../VPT/Trigger/TriggerColliderComponent.cs | 1 - 20 files changed, 20 insertions(+), 67 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 1c1a595e0..61fd7308c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -101,6 +101,7 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private Player _player; [NonSerialized] private PhysicsMovements _physicsMovements; [NonSerialized] private IKinematicColliderComponent[] _kinematicColliderComponents; + [NonSerialized] private float4x4 _worldToPlayfield; private static ulong NowUsec => (ulong)(Time.timeAsDouble * 1000000); @@ -233,8 +234,11 @@ private void Start() _kinematicColliders = new NativeColliders(ref kinematicColliders, Allocator.Persistent); // get kinetic collider matrices + _worldToPlayfield = playfield.transform.worldToLocalMatrix; foreach (var coll in _kinematicColliderComponents) { - _kinematicTransforms.Ref[coll.ItemId] = coll.TransformationWithinPlayfield; + if (coll.IsKinematic) { + _kinematicTransforms.Ref[coll.ItemId] = coll.TranslateWithinPlayfieldMatrix(_worldToPlayfield); + } } _kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent); @@ -272,7 +276,7 @@ private void Update() continue; } var lastTransformationMatrix = _kinematicTransforms.Ref[coll.ItemId]; - var currTransformationMatrix = coll.TransformationWithinPlayfield; + var currTransformationMatrix = coll.TranslateWithinPlayfieldMatrix(_worldToPlayfield); if (lastTransformationMatrix.Equals(currTransformationMatrix)) { continue; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 12881d017..c93b54a09 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -51,7 +51,6 @@ public class BumperColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 3fa972fa6..8ac82e3dc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -106,7 +106,6 @@ private void Awake() private void Start() { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; if (IsHardwired) { WireMapping wireMapping = new() { Description = $"Hardwired bumper '{name}'", @@ -149,9 +148,6 @@ private void Start() #region Transformation - [NonSerialized] - private float4x4 _playfieldToWorld; - public void OnSurfaceUpdated() => UpdateTransforms(); public override void UpdateTransforms() @@ -167,8 +163,6 @@ public void UpdateTransforms(Quaternion xz) transform.rotation = xz * y; // localRotation? } - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 17d214c2f..6af5c03c9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -14,9 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +using System.Runtime.CompilerServices; using NLog; using Unity.Collections; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; using Logger = NLog.Logger; @@ -928,7 +930,7 @@ public void Collide(ref BallState ball, ref CollisionEventData collEvent, ref Fl #region Transformation - public static bool IsTransformable(float4x4 matrix) + public static bool IsTransformable(float4x4 matrix, bool printDetails = false) { // position: fully transformable: 3d (center + ZLow) // scale: none @@ -939,13 +941,22 @@ public static bool IsTransformable(float4x4 matrix) var rotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; var scaled = math.abs(scale.x - 1) > Collider.Tolerance || math.abs(scale.y - 1) > Collider.Tolerance || math.abs(scale.z - 1) > Collider.Tolerance; + if (printDetails) { + if (rotated) { + Debug.LogWarning($"Flipper rotation is at {rotation.x}/{rotation.y}, but only z-rotation is supported."); + } + if (scaled) { + Debug.LogWarning($"Flipper scale is at {scale.x}/{scale.y}/{scale.z}, but no scale is supported."); + } + } + return !rotated && !scaled; } public void Transform(FlipperCollider flipperCollider, float4x4 matrix) { #if UNITY_EDITOR - if (!IsTransformable(matrix)) { + if (!IsTransformable(matrix, true)) { throw new System.InvalidOperationException($"Matrix {matrix} cannot transform flipper."); } #endif diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 80dab921e..eaa63b47a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -85,7 +85,6 @@ public class FlipperColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.LocalToWorldPhysicsMatrix; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index 40b07e45a..539603cae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -85,7 +85,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index da4494be1..20f5bf2b6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -135,11 +135,6 @@ private void Awake() .ToArray(); } - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Wiring @@ -156,14 +151,6 @@ private void Start() #endregion - #region Transformation - - [NonSerialized] - private float4x4 _playfieldToWorld; - - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs index 2c66c5bbe..9d31b0fc3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs @@ -26,6 +26,6 @@ public interface IKinematicColliderComponent /// /// Transformation matrix of the collider in the scene, in VPX space. /// - public float4x4 TransformationWithinPlayfield { get; } + public float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index 605b3df4a..dabe7c58b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -67,8 +67,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 79c809028..e73a16aad 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -80,11 +80,6 @@ private void Awake() } } - // public float4x4 TransformationWithinPlayfield - // => math.mul(Physics.VpxToWorld, transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld)); - public float4x4 TransformationWithinPlayfield - => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - #endregion #region Wiring diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index 15e4a7644..c8186f2f7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -66,7 +66,6 @@ public class PrimitiveColliderComponent : ColliderComponent _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index a5a2aa3df..18e616657 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -66,7 +66,6 @@ public class RampColliderComponent : ColliderComponent, public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 38354f946..7d6f30446 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -216,9 +216,6 @@ public float Height(Vector2 pos, Vector3 diff) { // } // } - public float4x4 TransformationWithinPlayfield - => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - public void UpdateChildrenTransforms() { var children = GetComponentsInChildren(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 0cd2f263c..fc03535f6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -65,7 +65,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index 84c64a6f7..ad6454b2b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -110,12 +110,6 @@ private void Start() #endregion - #region Transformation - - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - - #endregion - #region Conversion public override IEnumerable SetData(RubberData data) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index c48f2a234..301cbfab1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -47,7 +47,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; #endregion } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 6645039d1..90a6e0bb1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -134,11 +134,6 @@ private void Awake() .ToArray(); } - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Wiring @@ -153,15 +148,6 @@ private void Start() #endregion - #region Transformation - - [NonSerialized] - private float4x4 _playfieldToWorld; - - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - - #endregion - #region Conversion public override IEnumerable SetData(SpinnerData data) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 9e4868aef..e551fdecb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -78,8 +78,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationWithinPlayfield; - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index ba223f4a6..c6a54a30a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -94,9 +94,6 @@ private void Start() public float Height(Vector2 _) => HeightTop; - public float4x4 TransformationWithinPlayfield - => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); - public override void UpdateTransforms() { base.UpdateTransforms(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index 917fc2368..f4f0da1f9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -61,7 +61,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P public bool IsKinematic => _isKinematic; public int ItemId => MainComponent.gameObject.GetInstanceID(); - public float4x4 TransformationWithinPlayfield => MainComponent.TransformationMatrix; #endregion } From 774b4e2d165a0a93e70767e0d94f1dea03f1ee9a Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 22 Dec 2024 23:37:41 +0100 Subject: [PATCH 160/208] debug: Add a prettier float4x4 print. --- .../VisualPinball.Unity/Extensions/MathExtensions.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index a9ec3aae4..4173f2efd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -58,6 +58,11 @@ public static void RotationAroundAxis(ref this float3x3 m, float3 axis, float rS m.c2.z = axis.z * axis.z + rCos * (1.0f - axis.z * axis.z); } + public static string ToReadableString(this float4x4 m) + { + return $"t: {GetTranslation(m)}, r: {GetRotationVector(m)}, s: {GetScale(m)}"; + } + public static float3 GetScale(this float4x4 m) { return new float3( From ff7d2889846cc8485ae8ba5333eb0f99ce92389a Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 23 Dec 2024 00:42:17 +0100 Subject: [PATCH 161/208] chore: Clean up many (hopefully) unnecessary transformations. --- .../VPT/HitTarget/HitTargetMeshGenerator.cs | 5 ----- VisualPinball.Engine/VPT/IMeshGenerator.cs | 2 -- .../VisualPinball.Unity/Physics/Physics.cs | 17 +++++------------ .../VisualPinball.Unity/VPT/Bumper/BumperApi.cs | 9 ++++----- .../VisualPinball.Unity/VPT/CollidableApi.cs | 2 -- .../VPT/ColliderComponent.cs | 2 +- .../VPT/HitTarget/HitTargetColliderComponent.cs | 3 +-- .../VPT/HitTarget/TargetComponent.cs | 8 -------- .../VPT/Primitive/PrimitiveColliderComponent.cs | 2 +- .../VPT/Primitive/PrimitiveComponent.cs | 9 --------- .../VPT/Trigger/TriggerComponent.cs | 1 - 11 files changed, 12 insertions(+), 48 deletions(-) diff --git a/VisualPinball.Engine/VPT/HitTarget/HitTargetMeshGenerator.cs b/VisualPinball.Engine/VPT/HitTarget/HitTargetMeshGenerator.cs index 19c30d4a7..9abbc4675 100644 --- a/VisualPinball.Engine/VPT/HitTarget/HitTargetMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/HitTarget/HitTargetMeshGenerator.cs @@ -49,11 +49,6 @@ public Mesh GetMesh(Origin origin, bool asRightHanded) return mesh.Transform(preMatrix); } - public Matrix3D GetTransformationMatrix() - { - return GetPostMatrix(_table, Origin.Original); - } - public PbrMaterial GetMaterial() { return new PbrMaterial(_table.GetMaterial(_data.Material), _table.GetTexture(_data.Image)); diff --git a/VisualPinball.Engine/VPT/IMeshGenerator.cs b/VisualPinball.Engine/VPT/IMeshGenerator.cs index 3b043c3a9..190fef2ea 100644 --- a/VisualPinball.Engine/VPT/IMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/IMeshGenerator.cs @@ -34,7 +34,5 @@ public interface IMeshGenerator string name { get; } Mesh GetMesh(); // assuming: Origin.Original, false - - Matrix3D GetTransformationMatrix(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index 4fa6e5ecb..c88de8295 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -80,18 +80,11 @@ public static class Physics /// You basically give the world-to-local transformation matrix of the item, and you'll get the /// transformation in VPX space (i.e. relative to the playfield, however it's transformed). /// - /// World-to-local transformation matrix of the item. - /// Local-to-World transformation matrix of the playfield. - /// Transformation matrix of the item in VPX space. - public static float4x4 WorldToLocalTranslateWithinPlayfield(this Matrix4x4 worldToLocal, float4x4 playfieldToWorld) - => math.mul(math.mul(WorldToVpx, math.inverse(math.mul(worldToLocal, playfieldToWorld))), VpxToWorld); - - public static float4x4 LocalToWorldTranslateWithinPlayfield(this Matrix4x4 localToWorldMatrix, float4x4 worldToPlayfield) - => LocalToWorldTranslateWithinPlayfield((float4x4)localToWorldMatrix, worldToPlayfield); - - public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorldMatrix, float4x4 worldToPlayfield) - => math.mul(math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorldMatrix)), VpxToWorld); - + /// Local-to-world transformation matrix of the item. + /// World-to-local transformation matrix of the playfield. + /// + public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorld, float4x4 worldToPlayfield) + => math.mul(math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorld)), VpxToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 95ad45d4d..47e23725f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -113,16 +113,15 @@ void IApiCoil.OnCoil(bool enabled) protected override void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var matrix = MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); var height = MainComponent.Position.z; var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); if (ColliderComponent._isKinematic) { - _switchColliderId = kinematicColliders.Add(switchCollider, matrix); - kinematicColliders.Add(rigidCollider, matrix); + _switchColliderId = kinematicColliders.Add(switchCollider, translateWithinPlayfieldMatrix); + kinematicColliders.Add(rigidCollider, translateWithinPlayfieldMatrix); } else { - _switchColliderId = colliders.Add(switchCollider, matrix); - colliders.Add(rigidCollider, matrix); + _switchColliderId = colliders.Add(switchCollider, translateWithinPlayfieldMatrix); + colliders.Add(rigidCollider, translateWithinPlayfieldMatrix); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index 71b4e29ce..38cb49fcb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs @@ -70,8 +70,6 @@ public ColliderInfo GetColliderInfo(ItemType itemType) }; } - protected float4x4 GetTransformationWithinPlayfield() => MainComponent.transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Player.PlayfieldToWorldMatrix); - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 6d0dd1510..ad1c86291 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -68,7 +68,7 @@ public abstract class ColliderComponent : SubComponent MainComponent.transform.localToWorldMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + => ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(worldToPlayfield); public abstract PhysicsMaterialData PhysicsMaterialData { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index 424027ebf..3dd3615e6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -62,7 +62,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P => (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.TransformationWithinPlayfield.TransformToVpx(); - + => base.TranslateWithinPlayfieldMatrix(worldToPlayfield).TransformToVpx(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index bd61b9a89..f84318b66 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -72,12 +72,6 @@ public float3 Size { public Mesh GetMesh() => GetDefaultMesh(); - public Matrix3D GetTransformationMatrix() - { - var t = transform; - return Matrix4x4.TRS(t.localPosition, t.localRotation, t.localScale).ToVpMatrix(); - } - #endregion #region Overrides and Constants @@ -109,8 +103,6 @@ public Matrix3D GetTransformationMatrix() protected abstract float ZOffset { get; } - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix); - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index c8186f2f7..7eb8657ed 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -74,6 +74,6 @@ protected override IApiColliderGenerator InstantiateColliderApi(Player player, P =>MainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.TransformationWithinPlayfield.TransformToVpx(); + => base.TranslateWithinPlayfieldMatrix(worldToPlayfield).TransformToVpx(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 92aeece1c..6ac232078 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -59,12 +59,6 @@ public Vector3 Position { #endregion - #region Transformation - - public float4x4 TransformationWithinPlayfield => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix); - - #endregion - #region Conversion public override IEnumerable SetData(PrimitiveData data) @@ -235,9 +229,6 @@ private void Awake() public Mesh GetMesh() => GetDefaultMesh(); - public Matrix3D GetTransformationMatrix() => - transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(Playfield.transform.localToWorldMatrix).ToVpMatrix(); - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 5d1a5eb96..2351d6c21 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -133,7 +133,6 @@ private void Start() public Vector2 Center => Position; // todo remove? public void OnSurfaceUpdated() => UpdateTransforms(); - public float4x4 TransformationMatrix => transform.worldToLocalMatrix.WorldToLocalTranslateWithinPlayfield(_playfieldToWorld); #endregion From e1d650bbb24eb087282a76cc6134d582ea53fd26 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 24 Dec 2024 00:40:43 +0100 Subject: [PATCH 162/208] chore: Rename TranslateWithinPlayfieldMatrix to GetLocalToPlayfieldMatrixInVpx. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 6 +++--- .../VisualPinball.Unity/Physics/Physics.cs | 2 +- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 8 ++++---- .../VPT/Flipper/FlipperColliderComponent.cs | 4 ++-- .../VPT/HitTarget/HitTargetColliderComponent.cs | 4 ++-- .../VisualPinball.Unity/VPT/ICollidableComponent.cs | 2 +- .../VPT/IKinematicColliderComponent.cs | 2 +- .../VPT/Playfield/PlayfieldColliderComponent.cs | 2 +- .../VPT/Primitive/PrimitiveColliderComponent.cs | 4 ++-- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 61fd7308c..da8693c47 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -222,7 +222,7 @@ private void Start() _disabledCollisionItems.Ref.Add(colliderItem.ItemId); } - var translateWithinPlayfieldMatrix = colliderItem.TranslateWithinPlayfieldMatrix(playfield.transform.worldToLocalMatrix); + var translateWithinPlayfieldMatrix = colliderItem.GetLocalToPlayfieldMatrixInVpx(playfield.transform.worldToLocalMatrix); // todo check if we cannot only add those that are actually non-transformable _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; @@ -237,7 +237,7 @@ private void Start() _worldToPlayfield = playfield.transform.worldToLocalMatrix; foreach (var coll in _kinematicColliderComponents) { if (coll.IsKinematic) { - _kinematicTransforms.Ref[coll.ItemId] = coll.TranslateWithinPlayfieldMatrix(_worldToPlayfield); + _kinematicTransforms.Ref[coll.ItemId] = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); } } _kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent); @@ -276,7 +276,7 @@ private void Update() continue; } var lastTransformationMatrix = _kinematicTransforms.Ref[coll.ItemId]; - var currTransformationMatrix = coll.TranslateWithinPlayfieldMatrix(_worldToPlayfield); + var currTransformationMatrix = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); if (lastTransformationMatrix.Equals(currTransformationMatrix)) { continue; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs index c88de8295..84186c4de 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Physics.cs @@ -83,7 +83,7 @@ public static class Physics /// Local-to-world transformation matrix of the item. /// World-to-local transformation matrix of the playfield. /// - public static float4x4 LocalToWorldTranslateWithinPlayfield(this float4x4 localToWorld, float4x4 worldToPlayfield) + public static float4x4 GetLocalToPlayfieldMatrixInVpx(this float4x4 localToWorld, float4x4 worldToPlayfield) => math.mul(math.mul(WorldToVpx, math.mul(worldToPlayfield, localToWorld)), VpxToWorld); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index ad1c86291..ff122930e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -67,8 +67,8 @@ public abstract class ColliderComponent : SubComponent ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + public virtual float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => ((float4x4)MainComponent.transform.localToWorldMatrix).GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); public abstract PhysicsMaterialData PhysicsMaterialData { get; } @@ -145,7 +145,7 @@ private void OnDrawGizmos() var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; // todo optimize - var translateWithinPlayfieldMatrix = TranslateWithinPlayfieldMatrix(math.inverse(playfieldToWorld)); + var translateWithinPlayfieldMatrix = GetLocalToPlayfieldMatrixInVpx(math.inverse(playfieldToWorld)); var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); @@ -242,7 +242,7 @@ private void OnDrawGizmos() white.a = 0.01f; if (_nonTransformableColliderMesh) { - var m = ((float4x4)MainComponent.transform.localToWorldMatrix).LocalToWorldTranslateWithinPlayfield(math.inverse(playfieldToWorld)); + var m = ((float4x4)MainComponent.transform.localToWorldMatrix).GetLocalToPlayfieldMatrixInVpx(math.inverse(playfieldToWorld)); Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)m; Handles.matrix = Gizmos.matrix; Handles.color = blue; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index eaa63b47a..a682d0912 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -172,7 +172,7 @@ public class FlipperColliderComponent : ColliderComponent MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => MainComponent.LocalToWorldPhysicsMatrix.LocalToWorldTranslateWithinPlayfield(worldToPlayfield); + public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => MainComponent.LocalToWorldPhysicsMatrix.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index 3dd3615e6..ab07aa57d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -61,7 +61,7 @@ public class HitTargetColliderComponent : ColliderComponent (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => base.TranslateWithinPlayfieldMatrix(worldToPlayfield).TransformToVpx(); + public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => base.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield).TransformToVpx(); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index a0d41a418..6d314e3d4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -49,6 +49,6 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// /// The playfield's worldToLocal matrix. /// - public float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); + public float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs index 9d31b0fc3..2315aad77 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs @@ -26,6 +26,6 @@ public interface IKinematicColliderComponent /// /// Transformation matrix of the collider in the scene, in VPX space. /// - public float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield); + public float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs index 423a3dbe5..6995c8d53 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs @@ -58,6 +58,6 @@ public class PlayfieldColliderComponent : ColliderComponent MainComponent.PlayfieldApi ?? new PlayfieldApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) => float4x4.identity; + public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) => float4x4.identity; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index 7eb8657ed..e0511a353 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -73,7 +73,7 @@ public class PrimitiveColliderComponent : ColliderComponentMainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); - public override float4x4 TranslateWithinPlayfieldMatrix(float4x4 worldToPlayfield) - => base.TranslateWithinPlayfieldMatrix(worldToPlayfield).TransformToVpx(); + public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => base.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield).TransformToVpx(); } } From 02f6c4153948dcceef3dbc4c9aebad89153a6163 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 28 Dec 2024 00:48:14 +0100 Subject: [PATCH 163/208] colliders: Cleanup gizmos and render different types in different colors. --- .../VPT/ColliderInspector.cs | 8 - .../VisualPinball.Unity/Game/PhysicsEngine.cs | 48 ++-- .../Physics/Collider/ColliderReference.cs | 2 + .../VPT/ColliderComponent.cs | 257 ++++++++++-------- .../VPT/Flipper/FlipperColliderComponent.cs | 4 +- .../VisualPinball.Unity/VPT/IApi.cs | 3 +- 6 files changed, 181 insertions(+), 141 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs index fb6248fdc..a2de7e03a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs @@ -44,7 +44,6 @@ protected override void OnEnable() { ColliderComponent = target as TColliderComponent; if (ColliderComponent != null) { - ColliderComponent.ShowGizmos = true; // if no meshes active, show collider if (ColliderComponent.MainComponent && !ColliderComponent.MainComponent.GetComponentsInChildren().Any(mr => mr.enabled)) { @@ -55,13 +54,6 @@ protected override void OnEnable() base.OnEnable(); } - private void OnDestroy() - { - if (ColliderComponent != null) { - ColliderComponent.ShowGizmos = false; - } - } - public override void OnInspectorGUI() { if (ColliderComponent == null) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index da8693c47..35354060d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -49,20 +49,21 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private NativeColliders _kinematicColliders; [NonSerialized] private NativeColliders _kinematicCollidersAtIdentity; [NonSerialized] private NativeParallelHashMap _kinematicColliderLookups; - [NonSerialized] private readonly LazyInit> _physicsEnv = new(() =>new(1, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _eventQueue = new(() => new(Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _ballStates = new(() => new (0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _bumperStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _flipperStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _gateStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit>_dropTargetStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _hitTargetStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _kickerStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _plungerStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _spinnerStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _surfaceStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _triggerStates = new(() => new(0, Allocator.Persistent)); - [NonSerialized] private readonly LazyInit> _disabledCollisionItems = new(() => new(0, Allocator.Persistent)); + [NonSerialized] private NativeParallelHashMap _colliderLookups; // only used for editor debug + [NonSerialized] private readonly LazyInit> _physicsEnv = new(() => new NativeArray(1, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _eventQueue = new(() => new NativeQueue(Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _ballStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _bumperStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _flipperStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _gateStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit>_dropTargetStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _hitTargetStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _kickerStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _plungerStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _spinnerStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _surfaceStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _triggerStates = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _disabledCollisionItems = new(() => new NativeParallelHashSet(0, Allocator.Persistent)); [NonSerialized] private bool _swapBallCollisionHandling; #endregion @@ -240,6 +241,9 @@ private void Start() _kinematicTransforms.Ref[coll.ItemId] = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); } } +#if UNITY_EDITOR + _colliderLookups = colliders.CreateLookup(Allocator.Persistent); +#endif _kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent); // create identity kinematic colliders @@ -403,7 +407,12 @@ private void OnDestroy() } } _kinematicColliderLookups.Dispose(); - + using (var enumerator = _colliderLookups.GetEnumerator()) { + while (enumerator.MoveNext()) { + enumerator.Current.Value.Dispose(); + } + } + _colliderLookups.Dispose(); } #endregion @@ -420,13 +429,16 @@ public ScheduledAction(ulong scheduleAt, Action action) } } - public ICollider[] GetKinematicColliders(int itemId) + public ICollider[] GetColliders(int itemId) => GetColliders(itemId, ref _colliderLookups, ref _colliders); + public ICollider[] GetKinematicColliders(int itemId) => GetColliders(itemId, ref _kinematicColliderLookups, ref _kinematicColliders); + + private static ICollider[] GetColliders(int itemId, ref NativeParallelHashMap lookups, ref NativeColliders nativeColliders) { - ref var colliderIds = ref _kinematicColliderLookups.GetValueByRef(itemId); + ref var colliderIds = ref lookups.GetValueByRef(itemId); var colliders = new ICollider[colliderIds.Length]; for (var i = 0; i < colliderIds.Length; i++) { var colliderId = colliderIds[i]; - colliders[i] = _kinematicColliders[colliderId]; + colliders[i] = nativeColliders[colliderId]; } return colliders; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 989f6e2d4..355ae0224 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -195,9 +195,11 @@ private ICollider LookupCollider(int i) private void TrackReference(int itemId, int colliderId) { + #if !UNITY_EDITOR if (!KinematicColliders) { return; } + #endif if (!_itemIdToColliderIds.ContainsKey(itemId)) { _itemIdToColliderIds[itemId] = new NativeList(Allocator.Temp); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index ff122930e..ec791441c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -42,9 +42,6 @@ public abstract class ColliderComponent : SubComponent : SubComponent _collidersDirty = value; } - [NonSerialized] private Mesh _staticColliderMesh; - [NonSerialized] private Mesh _kinematicColliderMesh; - [NonSerialized] private Mesh _nonTransformableColliderMesh; + [NonSerialized] private Mesh _transformedColliderMesh; + [NonSerialized] private Mesh _transformedKinematicColliderMesh; + [NonSerialized] private Mesh _untransformedColliderMesh; + [NonSerialized] private Mesh _untransformedKinematicColliderMesh; + [NonSerialized] private Aabb[] _aabbs; + [NonSerialized] private readonly List _nonMeshColliders = new List(); [NonSerialized] private bool _collidersDirty; + [NonSerialized] private bool _aabbsDirty; protected abstract IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine); @@ -76,9 +77,9 @@ public virtual float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield private void Start() { - _staticColliderMesh = null; - _kinematicColliderMesh = null; - _nonTransformableColliderMesh = null; + _transformedColliderMesh = null; + _transformedKinematicColliderMesh = null; + _untransformedColliderMesh = null; _collidersDirty = true; // make enable checkbox visible } @@ -122,84 +123,51 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa #if UNITY_EDITOR private PhysicsEngine _physicsEngine; + private Player _player; private bool IsKinematic => this is IKinematicColliderComponent { IsKinematic: true }; private void OnDrawGizmos() { + if (!_player) { + _player = GetComponentInParent(); + if (!_player) { + return; + } + } + Profiler.BeginSample("ItemColliderComponent.OnDrawGizmosSelected"); var playfieldColliderComponent = GetComponentInParent(); - var overrideColliderMesh = playfieldColliderComponent && playfieldColliderComponent.ShowAllColliderMeshes; - var showColliders = ShowColliderMesh || overrideColliderMesh; + var showAllColliderMeshes = playfieldColliderComponent && playfieldColliderComponent.ShowAllColliderMeshes; + var showColliders = ShowColliderMesh || showAllColliderMeshes; - if (!(ShowGizmos || overrideColliderMesh) || !ShowAabbs && !showColliders && !ShowColliderOctree) { + // early out if nothing to draw + if (!ShowAabbs && !showColliders && !ShowColliderOctree) { Profiler.EndSample(); return; } - var player = GetComponentInParent(); - if (player == null) { - Profiler.EndSample(); - return; - } var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; - - // todo optimize - var translateWithinPlayfieldMatrix = GetLocalToPlayfieldMatrixInVpx(math.inverse(playfieldToWorld)); - + var worldToPlayfield = GetComponentInParent().transform.worldToLocalMatrix; + var localToPlayfieldMatrixInVpx = GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { - - if (Application.isPlaying && IsKinematic) { - - if (!_physicsEngine) { - _physicsEngine = GetComponentInParent(); // todo cache - } - - var colliders = _physicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); - if (showColliders) { - GenerateColliderMesh(colliders); - //_collidersDirty = false; - } - + if (Application.isPlaying) { + InstantiateRuntimeColliders(showColliders); } else { - var api = InstantiateColliderApi(player, null); - var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp); - var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp, true); - try { - api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); - - if (showColliders) { - if (IsKinematic) { - GenerateColliderMesh(ref kinematicColliders); - } else { - GenerateColliderMesh(ref colliders); - } - _collidersDirty = false; - } - - if (ShowAabbs) { - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; - for (var i = 0; i < colliders.Count; i++) { - var col = colliders[i]; - DrawAabb(col.Bounds.Aabb, i == SelectedCollider); - } - } - } finally { - colliders.Dispose(); - kinematicColliders.Dispose(); - } + InstantiateEditorColliders(showColliders, ref nonTransformableColliderMatrices, localToPlayfieldMatrixInVpx); } } + if (ShowColliderOctree) { - var api = InstantiateColliderApi(player, null); + var api = InstantiateColliderApi(_player, null); var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob); var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob, true); try { - api.CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0.1f); + api.CreateColliders(ref colliders, ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); var playfieldBounds = GetComponentInParent().Bounds; var octree = new NativeOctree(playfieldBounds, 32, 10, Allocator.Persistent); @@ -217,48 +185,44 @@ private void OnDrawGizmos() } } - if (showColliders) { - - //Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)translateFullyWithinPlayfieldMatrix; - + // aabbs + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; - // var color = Application.isPlaying && IsKinematic - // ? Color.magenta - // : IsKinematic ? new Color(0, 1, 1) : Color.green; - // Handles.color = color; - // color.a = 0.3f; - // Gizmos.color = color; - // Gizmos.DrawMesh(_colliderMesh); - // color = Color.white; - // color.a = 0.01f; - // Gizmos.color = color; - // Gizmos.DrawWireMesh(_colliderMesh); + if (showColliders) { var white = Color.white; - var blue = new Color(0, 1, 1); - var green = Color.green; - green.a = 0.3f; - blue.a = 0.3f; white.a = 0.01f; - if (_nonTransformableColliderMesh) { - var m = ((float4x4)MainComponent.transform.localToWorldMatrix).GetLocalToPlayfieldMatrixInVpx(math.inverse(playfieldToWorld)); - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)m; - Handles.matrix = Gizmos.matrix; - Handles.color = blue; - Gizmos.color = blue; - Gizmos.DrawMesh(_nonTransformableColliderMesh); - Gizmos.color = white; - Gizmos.DrawWireMesh(_nonTransformableColliderMesh); + if (_untransformedColliderMesh || _untransformedKinematicColliderMesh) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)localToPlayfieldMatrixInVpx; + if (_untransformedColliderMesh) { + Gizmos.color = ColliderColor.UntransformedColliderSelected; + Gizmos.DrawMesh(_untransformedColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_untransformedColliderMesh); + } + if (_untransformedKinematicColliderMesh) { + Gizmos.color = ColliderColor.UntransformedKineticColliderSelected; + Gizmos.DrawMesh(_untransformedKinematicColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_untransformedKinematicColliderMesh); + } } - if (_staticColliderMesh) { + if (_transformedColliderMesh || _transformedKinematicColliderMesh) { Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; - Handles.matrix = Gizmos.matrix; - Gizmos.color = green; - Gizmos.DrawMesh(_staticColliderMesh); - Gizmos.color = white; - Gizmos.DrawWireMesh(_staticColliderMesh); + if (_transformedColliderMesh) { + Gizmos.color = ColliderColor.TransformedColliderSelected; + Gizmos.DrawMesh(_transformedColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_transformedColliderMesh); + } + if (_transformedKinematicColliderMesh) { + Gizmos.color = ColliderColor.TransformedKineticColliderSelected; + Gizmos.DrawMesh(_transformedKinematicColliderMesh); + Gizmos.color = white; + Gizmos.DrawWireMesh(_transformedKinematicColliderMesh); + } } DrawNonMeshColliders(); @@ -270,7 +234,73 @@ private void OnDrawGizmos() Profiler.EndSample(); } - private void GenerateColliderMesh(ref ColliderReference colliders) + private void InstantiateRuntimeColliders(bool createMesh) + { + if (!_physicsEngine) { + _physicsEngine = GetComponentInParent(); // todo cache + } + + if (createMesh) { + if (IsKinematic) { + var kinematicColliders = _physicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); + _transformedColliderMesh = null; + _untransformedColliderMesh = null; + GenerateColliderMesh(kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + } else { + var colliders = _physicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); + _transformedKinematicColliderMesh = null; + _untransformedKinematicColliderMesh = null; + GenerateColliderMesh(colliders, out _transformedColliderMesh, out _untransformedColliderMesh); + } + _collidersDirty = false; + } + + // if (ShowAabbs) { + // var count = IsKinematic ? colliders.Length : kinematicColliders.Length; + // var c = IsKinematic ? colliders : kinematicColliders; + // _aabbs = new Aabb[count]; + // for (var i = 0; i < count; i++) { + // _aabbs[i] = c[i].Bounds.Aabb; + // } + // } + } + + private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashMap nonTransformableColliderMatrices, float4x4 localToPlayfieldMatrixInVpx) + { + var api = InstantiateColliderApi(_player, _physicsEngine); + var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp); + var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp, true); + try { + api.CreateColliders(ref colliders, ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); + + if (createMesh) { + if (IsKinematic) { + _transformedColliderMesh = null; + _untransformedColliderMesh = null; + GenerateColliderMesh(ref kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + } else { + _transformedKinematicColliderMesh = null; + _untransformedKinematicColliderMesh = null; + GenerateColliderMesh(ref colliders, out _transformedColliderMesh, out _untransformedColliderMesh); + } + _collidersDirty = false; + } + + if (ShowAabbs) { + var count = IsKinematic ? colliders.Count : kinematicColliders.Count; + var c = IsKinematic ? colliders : kinematicColliders; + _aabbs = new Aabb[count]; + for (var i = 0; i < count; i++) { + _aabbs[i] = c[i].Bounds.Aabb; + } + } + } finally { + colliders.Dispose(); + kinematicColliders.Dispose(); + } + } + + private void GenerateColliderMesh(ref ColliderReference colliders, out Mesh transformedMesh, out Mesh untransformedMesh) { var color = Color.green; Handles.color = color; @@ -354,29 +384,29 @@ private void GenerateColliderMesh(ref ColliderReference colliders) // todo Line3DCollider if (vertices.Count > 0) { - _staticColliderMesh = new Mesh { + transformedMesh = new Mesh { name = $"{name} (static collider)", vertices = vertices.ToArray(), triangles = indices.ToArray(), normals = normals.ToArray() }; } else { - _staticColliderMesh = null; + transformedMesh = null; } if (verticesNonTransformable.Count > 0) { - _nonTransformableColliderMesh = new Mesh { + untransformedMesh = new Mesh { name = $"{name} (non-transformable colliders)", vertices = verticesNonTransformable.ToArray(), triangles = indicesNonTransformable.ToArray(), normals = normalsNonTransformable.ToArray() }; } else { - _nonTransformableColliderMesh = null; + untransformedMesh = null; } } - private void GenerateColliderMesh(IEnumerable colliders) + private void GenerateColliderMesh(IEnumerable colliders, out Mesh transformedMesh, out Mesh untransformedMesh) { var color = Color.magenta; Handles.color = color; @@ -476,27 +506,26 @@ private void GenerateColliderMesh(IEnumerable colliders) // todo Line3DCollider if (vertices.Count > 0) { - _staticColliderMesh = new Mesh { + transformedMesh = new Mesh { name = $"{name} (static collider)", vertices = vertices.ToArray(), triangles = indices.ToArray(), normals = normals.ToArray() }; } else { - _staticColliderMesh = null; + transformedMesh = null; } if (verticesNonTransformable.Count > 0) { - _nonTransformableColliderMesh = new Mesh { + untransformedMesh = new Mesh { name = $"{name} (static collider)", vertices = verticesNonTransformable.ToArray(), triangles = indicesNonTransformable.ToArray(), normals = normalsNonTransformable.ToArray() }; } else { - _nonTransformableColliderMesh = null; + untransformedMesh = null; } - } private void DrawNonMeshColliders() @@ -694,9 +723,15 @@ void ICollidableComponent.GetColliders(Player player, PhysicsEngine physicsEngin internal static class ColliderColor { - internal static readonly Color Aabb = new Color32(255, 0, 252, 50); - internal static readonly Color SelectedAabb = new Color32(255, 0, 252, 255); - internal static readonly Color Collider = new Color32(0, 255, 75, 50); - internal static readonly Color SelectedCollider = new Color32(0, 255, 75, 255); + internal static readonly Color Aabb = new Color32(255, 255, 255, 50); + internal static readonly Color SelectedAabb = new Color32(255, 255, 255, 128); + internal static readonly Color TransformedCollider = new Color32(0, 255, 75, 50); + internal static readonly Color TransformedColliderSelected = new Color32(0, 255, 75, 128); + internal static readonly Color TransformedKineticCollider = new Color32(255, 255, 0, 50); + internal static readonly Color TransformedKineticColliderSelected = new Color32(255, 255, 0, 128); + internal static readonly Color UntransformedCollider = new Color32(0, 255, 255, 50); + internal static readonly Color UntransformedColliderSelected = new Color32(0, 255, 255, 128); + internal static readonly Color UntransformedKineticCollider = new Color32(255, 50, 50, 50); + internal static readonly Color UntransformedKineticColliderSelected = new Color32(255, 50, 50, 128); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index a682d0912..aa633c350 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -172,7 +172,7 @@ public class FlipperColliderComponent : ColliderComponent MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); - public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) - => MainComponent.LocalToWorldPhysicsMatrix.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); + // public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + // => MainComponent.LocalToWorldPhysicsMatrix.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs index 69bf86656..41da4878e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs @@ -45,8 +45,7 @@ public interface IApiColliderGenerator /// List to add kinematic colliders to. /// /// - void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, - float4x4 translateWithinPlayfieldMatrix, float margin); + void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); /// /// Computes collider info based on the component data. From 074271c34877e7ca880a7e7b0970ebf1e47dbf7b Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 25 Dec 2024 23:29:27 +0100 Subject: [PATCH 164/208] colliders: Fix flipper gizmo --- .../VPT/ColliderComponent.cs | 18 +++++++++++------- .../VPT/Flipper/FlipperColliderComponent.cs | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index ec791441c..268aa42ff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -69,7 +69,10 @@ public abstract class ColliderComponent : SubComponent ((float4x4)MainComponent.transform.localToWorldMatrix).GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); + => Physics.GetLocalToPlayfieldMatrixInVpx(MainComponent.transform.localToWorldMatrix, worldToPlayfield); + + public float4x4 GetUnmodifiedLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => Physics.GetLocalToPlayfieldMatrixInVpx(MainComponent.transform.localToWorldMatrix, worldToPlayfield); public abstract PhysicsMaterialData PhysicsMaterialData { get; } @@ -150,6 +153,7 @@ private void OnDrawGizmos() var playfieldToWorld = GetComponentInParent().transform.localToWorldMatrix; var worldToPlayfield = GetComponentInParent().transform.worldToLocalMatrix; var localToPlayfieldMatrixInVpx = GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); + var unmodifiedLocalToPlayfieldMatrixInVpx = GetUnmodifiedLocalToPlayfieldMatrixInVpx(worldToPlayfield); var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; @@ -194,17 +198,17 @@ private void OnDrawGizmos() white.a = 0.01f; if (_untransformedColliderMesh || _untransformedKinematicColliderMesh) { - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)localToPlayfieldMatrixInVpx; + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld * (Matrix4x4)unmodifiedLocalToPlayfieldMatrixInVpx; if (_untransformedColliderMesh) { Gizmos.color = ColliderColor.UntransformedColliderSelected; Gizmos.DrawMesh(_untransformedColliderMesh); - Gizmos.color = white; + Gizmos.color = Application.isPlaying ? ColliderColor.UntransformedCollider : white; Gizmos.DrawWireMesh(_untransformedColliderMesh); } if (_untransformedKinematicColliderMesh) { Gizmos.color = ColliderColor.UntransformedKineticColliderSelected; Gizmos.DrawMesh(_untransformedKinematicColliderMesh); - Gizmos.color = white; + Gizmos.color = Application.isPlaying ? ColliderColor.UntransformedKineticCollider : white; Gizmos.DrawWireMesh(_untransformedKinematicColliderMesh); } } @@ -214,13 +218,13 @@ private void OnDrawGizmos() if (_transformedColliderMesh) { Gizmos.color = ColliderColor.TransformedColliderSelected; Gizmos.DrawMesh(_transformedColliderMesh); - Gizmos.color = white; + Gizmos.color = Application.isPlaying ? ColliderColor.TransformedCollider : white; Gizmos.DrawWireMesh(_transformedColliderMesh); } if (_transformedKinematicColliderMesh) { Gizmos.color = ColliderColor.TransformedKineticColliderSelected; Gizmos.DrawMesh(_transformedKinematicColliderMesh); - Gizmos.color = white; + Gizmos.color = Application.isPlaying ? ColliderColor.TransformedKineticCollider : white; Gizmos.DrawWireMesh(_transformedKinematicColliderMesh); } } @@ -437,7 +441,7 @@ private void GenerateColliderMesh(IEnumerable colliders, out Mesh tra AddFlipperCollider(vertices, normals, indices, Origin.Global); break; case FlipperCollider: - AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Global); + AddFlipperCollider(verticesNonTransformable, normalsNonTransformable, indicesNonTransformable, Origin.Original); break; // gate collider diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index aa633c350..a682d0912 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -172,7 +172,7 @@ public class FlipperColliderComponent : ColliderComponent MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); - // public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) - // => MainComponent.LocalToWorldPhysicsMatrix.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); + public override float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield) + => MainComponent.LocalToWorldPhysicsMatrix.GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); } } From da7cf1cdcd54b07e40a32207bd156ad80f52d822 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 26 Dec 2024 23:30:04 +0100 Subject: [PATCH 165/208] colliders: Document and add IsFullyTransformable. --- .../Game/PhysicsKinematics.cs | 4 +-- .../VisualPinball.Unity/Game/PhysicsState.cs | 36 +++++-------------- .../Game/PhysicsUpdateJob.cs | 2 +- .../Physics/Collider/CircleCollider.cs | 9 +++++ .../Physics/Collider/ColliderReference.cs | 2 +- .../Physics/Collider/ICollider.cs | 20 ++++++++++- .../Physics/Collider/Line3DCollider.cs | 9 +++++ .../Physics/Collider/LineCollider.cs | 10 ++++++ .../Physics/Collider/LineSlingshotCollider.cs | 9 +++++ .../Physics/Collider/LineZCollider.cs | 9 +++++ .../Physics/Collider/PlaneCollider.cs | 9 +++++ .../Physics/Collider/PointCollider.cs | 9 +++++ .../Physics/Collider/TriangleCollider.cs | 9 +++++ .../VPT/Flipper/FlipperCollider.cs | 9 +++++ .../VPT/Gate/GateCollider.cs | 9 +++++ .../VPT/Plunger/PlungerCollider.cs | 9 +++++ .../VPT/Spinner/SpinnerCollider.cs | 9 +++++ 17 files changed, 140 insertions(+), 33 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs index 07eaa2001..f82e4fb60 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs @@ -24,10 +24,10 @@ namespace VisualPinball.Unity { public static class PhysicsKinematics { - private static readonly ProfilerMarker PerfMarkerTransform = new("TransformColliders"); + private static readonly ProfilerMarker PerfMarkerTransform = new("TransformKinematicColliders"); private static readonly ProfilerMarker PerfMarkerBallOctree = new("CreateKinematicOctree"); - internal static void TransformColliders(ref PhysicsState state) + internal static void TransformKinematicColliders(ref PhysicsState state) { PerfMarkerTransform.Begin(); using var enumerator = state.UpdatedKinematicTransforms.GetEnumerator(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 4c8e3cae7..ce9262a1a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -125,7 +125,6 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native #region Transform - internal bool HasNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) => NonTransformableColliderMatrices.ContainsKey(colliders.GetItemId(colliderId)); internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) { var itemId = colliders.GetItemId(colliderId); @@ -135,17 +134,16 @@ internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref Nati return ref NonTransformableColliderMatrices.GetValueByRef(itemId); } + /// + /// Transforms a collider with a given transformation matrix. The matrix can be anything, + /// so colliders here must be 100% transformable (i.e. ICollider.IsFullyTransformable = true). + /// + /// + /// The ID of the collider + /// The transformation matrix internal void Transform(int colliderId, float4x4 matrix) { - switch (GetColliderType(ref KinematicColliders, colliderId)) - { - case ColliderType.Bumper: - case ColliderType.Circle: - var circleCollider = KinematicColliders.Circle(colliderId); - if (circleCollider.Header.IsTransformed) { - circleCollider.Transform(KinematicCollidersAtIdentity.Circle(colliderId), matrix); - } - break; + switch (GetColliderType(ref KinematicColliders, colliderId)) { case ColliderType.Point: var pointCollider = KinematicColliders.Point(colliderId); if (pointCollider.Header.IsTransformed) { @@ -164,24 +162,6 @@ internal void Transform(int colliderId, float4x4 matrix) triangleCollider.Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); } break; - case ColliderType.Spinner: - var spinnerCollider = KinematicColliders.Spinner(colliderId); - if (spinnerCollider.Header.IsTransformed) { - spinnerCollider.Transform(KinematicCollidersAtIdentity.Spinner(colliderId), matrix); - } - break; - case ColliderType.Gate: - var gateCollider = KinematicColliders.Gate(colliderId); - if (gateCollider.Header.IsTransformed) { - gateCollider.Transform(KinematicCollidersAtIdentity.Gate(colliderId), matrix); - } - break; - case ColliderType.Flipper: - var flipperCollider = KinematicColliders.Flipper(colliderId); - if (flipperCollider.Header.IsTransformed) { - flipperCollider.Transform(KinematicCollidersAtIdentity.Flipper(colliderId), matrix); - } - break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index f02cce6b9..d7ccd1ca1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -72,7 +72,7 @@ public void Execute() using var cycle = new PhysicsCycle(Allocator.Temp); // create octree of kinematic-to-ball collision. should be okay here, since static colliders don't transform more than once per frame. - PhysicsKinematics.TransformColliders(ref state); + PhysicsKinematics.TransformKinematicColliders(ref state); var kineticOctree = PhysicsKinematics.CreateOctree(ref state, in PlayfieldBounds); while (env.CurPhysicsFrameTime < InitialTimeUsec) // loop here until current (real) time matches the physics (simulated) time diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index e01682630..1195782e7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -20,6 +20,13 @@ namespace VisualPinball.Unity { + /// + /// It's actually a cylinder, aligned orthogonally to the playfield. + /// + /// + /// + /// Defined by center (float2), radius, zHigh, zLow + /// internal struct CircleCollider : ICollider { public int Id @@ -28,6 +35,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; /// diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 355ae0224..a06d1af7b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -232,7 +232,7 @@ internal int Add(CircleCollider collider, float4x4 matrix) internal int Add(FlipperCollider collider, float4x4 matrix) { - if (FlipperCollider.IsTransformable(matrix)) { + if (!KinematicColliders && FlipperCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs index 2c480eb8e..4288ebfa2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs @@ -18,7 +18,25 @@ namespace VisualPinball.Unity { public interface ICollider { - int Id { get; } + /// + /// The bounds of the collider. + /// ColliderBounds Bounds { get; } + + /// + /// If true, the collider is fully transformable and can be moved, scaled and rotated freely. + /// + /// Fully transformable colliders are relevant when the object is set to be a kinematic collider. + /// If fully transformable, the collider will be transformed if the object's transformation matrix + /// has changed. This is less expensive than projecting the ball into the collider's local space, + /// which is the case for non fully transformable colliders. + /// + /// If the collider is not kinematic but transformed in a way supported by the collider, the collider + /// will simply be transformed without the ball projection. The problem with kinematic colliders is + /// that we don't know in advance how the collider will be transformed, so we can't assume it'll be + /// within the collider's capability to transform. Thus, only fully transformable colliders can be + /// dynamically transformed without ball projection. This is what this flag is for. + /// + bool IsFullyTransformable { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index 59d0e209d..25c79745e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -20,6 +20,13 @@ namespace VisualPinball.Unity { + /// + /// A line from point A to point B in 3D space. + /// + /// + /// + /// Defined by two points (float3) + /// internal struct Line3DCollider : ICollider { public int Id @@ -33,6 +40,8 @@ public int Id } } + public bool IsFullyTransformable => true; + public ColliderHeader Header; // these are all used when casting this to LineZCollider, diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index d7b8d6b88..6c28bf84e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -21,6 +21,14 @@ namespace VisualPinball.Unity { + + /// + /// A rectangle, aligned parallel and orthogonally to the playfield (from top, looks like a line). + /// + /// + /// + /// Defined by two points (float2), zHigh, zLow + /// internal struct LineCollider : ICollider { public int Id @@ -29,6 +37,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public float2 V1; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index 8e6bbcf93..04ff104e7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -21,6 +21,13 @@ namespace VisualPinball.Unity { + /// + /// Like Line, but with different collision code. + /// + /// + /// + /// Defined by two points (float2), zHigh, zLow + /// internal struct LineSlingshotCollider : ICollider { public int Id @@ -29,6 +36,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public float2 V1; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index f4b01518c..8c69170ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -20,6 +20,13 @@ namespace VisualPinball.Unity { + /// + /// A line, aligned orthogonally to the playfield (from top, looks like a point). + /// + /// + /// + /// Defined by position (float2), zHigh, zLow + /// internal struct LineZCollider : ICollider { public int Id @@ -28,6 +35,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public float2 XY; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs index 7ff1a87ee..204073491 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs @@ -21,6 +21,13 @@ namespace VisualPinball.Unity { + /// + /// An infinite plane, aligned parallel to the playfield. + /// + /// + /// + /// Defined by normal (float3), distance to playfield + /// internal struct PlaneCollider : ICollider { public int Id @@ -29,6 +36,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; private readonly float3 _normal; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index 552cfab07..c6891215f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -20,6 +20,13 @@ namespace VisualPinball.Unity { + /// + /// A point in 3D space + /// + /// + /// + /// Defined by position (float3) + /// internal struct PointCollider : ICollider { public int Id @@ -28,6 +35,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => true; + public ColliderHeader Header; public float3 P; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index bd6f11ff4..3694eea63 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -21,6 +21,13 @@ namespace VisualPinball.Unity { + /// + /// A triangle in 3D space. + /// + /// + /// + /// Defined by three points (float3) + /// internal struct TriangleCollider : ICollider { public int Id @@ -29,6 +36,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => true; + public ColliderHeader Header; public float3 Rgv0; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 6af5c03c9..ef314d307 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -25,6 +25,13 @@ namespace VisualPinball.Unity { + /// + /// Our custom flipper collider. + /// + /// + /// + /// Defined by base radius, max radius, end radius, start angle, end angle + /// internal struct FlipperCollider : ICollider { public int Id @@ -33,6 +40,8 @@ public int Id set => Header.Id = value; } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public float3 Position; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index 29fb209e1..a827579d5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -19,6 +19,13 @@ namespace VisualPinball.Unity { + /// + /// Our custom gate collider. + /// + /// + /// + /// Defined by two Line colliders. + /// internal struct GateCollider : ICollider { public int Id @@ -32,6 +39,8 @@ public int Id } } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public LineCollider LineSeg0; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index f3578f1f2..916639f5c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -22,6 +22,13 @@ namespace VisualPinball.Unity { + /// + /// Our custom plunger collider. + /// + /// + /// + /// Defined by z-position, width, height, stroke. + /// internal struct PlungerCollider : ICollider { public int Id @@ -35,6 +42,8 @@ public int Id } } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public LineCollider LineSegBase; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index 577ec6d69..a55d8827a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -19,6 +19,13 @@ namespace VisualPinball.Unity { + /// + /// Our custom spinner collider. + /// + /// + /// + /// Defined by two Line colliders. + /// internal struct SpinnerCollider : ICollider { public int Id @@ -32,6 +39,8 @@ public int Id } } + public bool IsFullyTransformable => false; + public ColliderHeader Header; public LineCollider LineSeg0; From 369265b2ebfd16908b1b72f23a427ba0045ab3ae Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 27 Dec 2024 00:15:07 +0100 Subject: [PATCH 166/208] colliders: Add scaling support for line-z collider. --- .../Physics/Collider/LineZCollider.cs | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 8c69170ee..39913037d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -170,18 +170,50 @@ public void Collide(ref BallState ball, ref NativeQueue.ParallelWrite #endregion - public LineZCollider Transform(float4x4 matrix) + #region Transformation + + public static bool IsTransformable(float4x4 matrix) { - var t = matrix.GetTranslation(); + // position: fully transformable: 3d (XY + ZLow/ZHigh) + // scale: fully scalable + // rotation: can be z-rotated (doesn't change anything), x/y rotation is not supported. - XY += t.xy; - ZLow += t.z; - ZHigh += t.z; - CalculateBounds(); + var rotation = matrix.GetRotationVector(); + var xyRotated = math.abs(rotation.x) > Collider.Tolerance || math.abs(rotation.y) > Collider.Tolerance; + + return !xyRotated; + } + public LineZCollider Transform(float4x4 matrix) + { + Transform(this, matrix); return this; } + public void Transform(LineZCollider lineCollider, float4x4 matrix) + { + #if UNITY_EDITOR + if (!IsTransformable(matrix)) { + throw new System.InvalidOperationException($"Matrix {matrix.ToDebugString()} cannot transform line-z collider."); + } + #endif + + var t = matrix.GetTranslation(); + var s = matrix.GetScale(); + + XY = lineCollider.XY + t.xy; + ZLow = lineCollider.ZLow + t.z; + ZHigh = lineCollider.ZHigh + t.z; + if (s.z > Collider.Tolerance) { + var height = ZHigh - ZLow; + var zMid = ZLow + height * 0.5f; + var halfHeightScaled = height * s.z * 0.5f; + ZLow = zMid - halfHeightScaled; + ZHigh = zMid + halfHeightScaled; + } + CalculateBounds(); + } + public Aabb GetTransformedAabb(float4x4 matrix) { var p1 = matrix.MultiplyPoint(new float3(XY, ZLow)); @@ -189,11 +221,7 @@ public Aabb GetTransformedAabb(float4x4 matrix) return new Aabb(math.min(p1, p2), math.max(p1, p2)); } - public LineZCollider TransformAabb(float4x4 matrix) - { - Bounds = new ColliderBounds(Header.ItemId, Header.Id, GetTransformedAabb(matrix)); - return this; - } + #endregion private void CalculateBounds() { From 9c6f6cc964f46d73ab0a2a4393e45bceac3fd65b Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 28 Dec 2024 00:41:42 +0100 Subject: [PATCH 167/208] colliders: Use coherent pattern for adding line-z colliders. --- .../Extensions/MathExtensions.cs | 23 +------------------ .../Physics/Collider/ColliderReference.cs | 19 ++++++++------- .../Physics/Collision/ColliderHeader.cs | 9 ++++++++ .../VPT/Ramp/RampColliderGenerator.cs | 10 ++++---- .../VPT/Surface/SurfaceColliderGenerator.cs | 2 +- 5 files changed, 25 insertions(+), 38 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs index 4173f2efd..d4b0fbdc9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MathExtensions.cs @@ -58,11 +58,6 @@ public static void RotationAroundAxis(ref this float3x3 m, float3 axis, float rS m.c2.z = axis.z * axis.z + rCos * (1.0f - axis.z * axis.z); } - public static string ToReadableString(this float4x4 m) - { - return $"t: {GetTranslation(m)}, r: {GetRotationVector(m)}, s: {GetScale(m)}"; - } - public static float3 GetScale(this float4x4 m) { return new float3( @@ -76,22 +71,6 @@ public static float3 GetScale(this float4x4 m) public static float3 GetRotationVector(this float4x4 matrix) => new quaternion(matrix).ToEuler(); - public static bool IsPureTranslationMatrix(this float4x4 matrix) - { - // check scaling (diagonal elements) - if (math.abs(matrix.c0.x - 1.0f) > Collider.Tolerance || math.abs(matrix.c1.y - 1.0f) > Collider.Tolerance || math.abs(matrix.c2.z - 1.0f) > Collider.Tolerance) { - return false; - } - - // Check rotation (non-diagonal elements) - if (math.abs(matrix.c0.y) > Collider.Tolerance || math.abs(matrix.c0.z) > Collider.Tolerance || math.abs(matrix.c1.x) > Collider.Tolerance || - math.abs(matrix.c1.z) > Collider.Tolerance || math.abs(matrix.c2.x) > Collider.Tolerance || math.abs(matrix.c2.y) > Collider.Tolerance) { - return false; - } - - return true; - } - public static Vertex3D ToVertex3D(this Vector3 vector) { return new Vertex3D(vector.x, vector.y, vector.z); @@ -213,6 +192,6 @@ public static float3 ToEuler(this quaternion quaternion) { public static string ToDebugString(this float4x4 m) => $"{((Matrix4x4)m).ToString()}\nt: {m.GetTranslation()}\nr: {math.degrees(m.GetRotationVector())}\ns: {m.GetScale()}"; - public static string ToDebugString(this Matrix4x4 m) => ((float4x4)m).ToDebugString(); + public static string ToDebugString(this Matrix4x4 m) => ToDebugString((float4x4)m); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index a06d1af7b..77d8d80e8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -332,8 +332,16 @@ internal int Add(LineCollider collider, float4x4 matrix) return collider.Id; } - private int Add(LineZCollider collider) + internal int Add(LineZCollider collider, float4x4 matrix) { + if (!LineZCollider.IsTransformable(matrix)) { + // use line 3d collider instead + return Add(new Line3DCollider(new float3(collider.XY, collider.ZLow), new float3(collider.XY, collider.ZHigh), collider.Header.ColliderInfo), matrix); + } + + collider.Header.IsTransformed = true; + collider.Transform(matrix); + collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.LineZ, LineZColliders.Length)); @@ -446,15 +454,6 @@ internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInf } } - internal void AddLineZ(float2 xy, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - { - if (KinematicColliders || !matrix.IsPureTranslationMatrix()) { // todo support scale and z-rotation - Add(new Line3DCollider(new float3(xy.xy, zLow), new float3(xy.xy, zHigh), info), matrix); - } else { - Add(new LineZCollider(xy, zLow, zHigh, info).Transform(matrix)); - } - } - #endregion public ICollider[] ToArray() diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs index 9eaa54ad8..c34af4011 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs @@ -75,6 +75,15 @@ public void Init(ColliderInfo info, ColliderType colliderType) FireEvents = info.FireEvents; } + public ColliderInfo ColliderInfo => new ColliderInfo { + Id = Id, + ItemId = ItemId, + ItemType = ItemType, + Material = Material, + HitThreshold = Threshold, + FireEvents = FireEvents + }; + public static bool operator ==(ColliderHeader a, ColliderHeader b) => a.Equals(b); public static bool operator !=(ColliderHeader a, ColliderHeader b) => !a.Equals(b); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index 1d57d4275..61da6565c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -64,11 +64,11 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide // add joints at start and end of right wall if (i == 0) { - colliders.AddLineZ(pv2, rgHeight1[0], rgHeight1[0] + wallHeightRight, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv2, rgHeight1[0], rgHeight1[0] + wallHeightRight, _api.GetColliderInfo()), _matrix); } if (i == vertexCount - 2) { - colliders.AddLineZ(pv3, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightRight, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv3, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightRight, _api.GetColliderInfo()), _matrix); } } } @@ -84,11 +84,11 @@ internal void GenerateColliders(float tableHeight, ref ColliderReference collide // add joints at start and end of left wall if (i == 0) { - colliders.AddLineZ(pv2, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightLeft, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv2, rgHeight1[vertexCount - 1], rgHeight1[vertexCount - 1] + wallHeightLeft, _api.GetColliderInfo()), _matrix); } if (i == vertexCount - 2) { - colliders.AddLineZ(pv3, rgHeight1[0], rgHeight1[0] + wallHeightLeft, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv3, rgHeight1[0], rgHeight1[0] + wallHeightLeft, _api.GetColliderInfo()), _matrix); } } } @@ -216,7 +216,7 @@ private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float h colliders.AddLine(pv1, pv2, height1, height2 + wallHeight, _api.GetColliderInfo(), _matrix); if (pv3Exists) { - colliders.AddLineZ(pv1, height1, height2 + wallHeight, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv1, height1, height2 + wallHeight, _api.GetColliderInfo()), _matrix); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index ba23529c1..be22ac1a6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -92,7 +92,7 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield colliders.Add(new Line3DCollider(new float3(pv1.X, pv1.Y, top), new float3(pv2.X, pv2.Y, top), _api.GetColliderInfo()), _matrix); // create vertical joint between the two line segments - colliders.AddLineZ(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineZCollider(pv1.ToUnityFloat2(), bottom, top, _api.GetColliderInfo()), _matrix); // add upper and lower end points of line if (_component.HeightBottom != 0) { From 91517801b54839e3bf5c3434ae291d50c13972af Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 29 Dec 2024 00:46:20 +0100 Subject: [PATCH 168/208] colliders: Don't transform colliders that aren't fully transformable and kinetic. --- .../Physics/Collider/ColliderReference.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 77d8d80e8..0720501f1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -209,7 +209,7 @@ private void TrackReference(int itemId, int colliderId) internal int Add(CircleCollider collider, float4x4 matrix) { - if (CircleCollider.IsTransformable(matrix)) { + if (!KinematicColliders && CircleCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -254,7 +254,7 @@ internal int Add(FlipperCollider collider, float4x4 matrix) internal int Add(GateCollider collider, float4x4 matrix) { - if (GateCollider.IsTransformable(matrix)) { + if (!KinematicColliders && GateCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -289,7 +289,7 @@ internal int Add(Line3DCollider collider, float4x4 matrix) internal int Add(LineSlingshotCollider collider, float4x4 matrix) { - if (LineSlingshotCollider.IsTransformable(matrix)) { + if (!KinematicColliders && LineSlingshotCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -312,7 +312,7 @@ internal int Add(LineSlingshotCollider collider, float4x4 matrix) internal int Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only internal int Add(LineCollider collider, float4x4 matrix) { - if (LineCollider.IsTransformable(matrix)) { + if (!KinematicColliders && LineCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -334,7 +334,7 @@ internal int Add(LineCollider collider, float4x4 matrix) internal int Add(LineZCollider collider, float4x4 matrix) { - if (!LineZCollider.IsTransformable(matrix)) { + if (KinematicColliders || !LineZCollider.IsTransformable(matrix)) { // use line 3d collider instead return Add(new Line3DCollider(new float3(collider.XY, collider.ZLow), new float3(collider.XY, collider.ZHigh), collider.Header.ColliderInfo), matrix); } @@ -351,7 +351,7 @@ internal int Add(LineZCollider collider, float4x4 matrix) internal int Add(PlungerCollider collider, float4x4 matrix) { - if (PlungerCollider.IsTransformable(matrix)) { + if (!KinematicColliders && PlungerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -386,7 +386,7 @@ internal int Add(PointCollider collider, float4x4 matrix) internal int Add(SpinnerCollider collider, float4x4 matrix) { - if (SpinnerCollider.IsTransformable(matrix)) { + if (!KinematicColliders && SpinnerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); From c9869eb73cdbc369632d1ca11c7ce1292e5b82c0 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 00:54:57 +0100 Subject: [PATCH 169/208] colliders: Use coherent pattern for adding line colliders. --- .../Physics/Collider/ColliderReference.cs | 62 +++++++------------ .../VPT/Gate/GateColliderGenerator.cs | 2 +- .../VPT/Ramp/RampColliderGenerator.cs | 2 +- .../VPT/Surface/SurfaceColliderGenerator.cs | 2 +- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 0720501f1..a6967d83f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -309,27 +309,35 @@ internal int Add(LineSlingshotCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only - internal int Add(LineCollider collider, float4x4 matrix) + internal void Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only + internal void Add(LineCollider collider, float4x4 matrix) { if (!KinematicColliders && LineCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); + collider.Id = Lookups.Length; + TrackReference(collider.Header.ItemId, collider.Header.Id); + Lookups.Add(new ColliderLookup(ColliderType.Line, LineColliders.Length)); + LineColliders.Add(collider); + } else { - // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); - } - collider.Header.IsTransformed = false; - collider.TransformAabb(matrix); - } - collider.Id = Lookups.Length; - TrackReference(collider.Header.ItemId, collider.Header.Id); - Lookups.Add(new ColliderLookup(ColliderType.Line, LineColliders.Length)); - LineColliders.Add(collider); - return collider.Id; + // convert line collider to two triangle colliders + var p1 = new float3(collider.V1.xy, collider.ZLow); + var p2 = new float3(collider.V1.xy, collider.ZHigh); + var p3 = new float3(collider.V2.xy, collider.ZLow); + var p4 = new float3(collider.V2.xy, collider.ZHigh); + + var t1 = new TriangleCollider(p1, p3, p2, collider.Header.ColliderInfo); + var t2 = new TriangleCollider(p3, p4, p2, collider.Header.ColliderInfo); + + t1.Header.IsTransformed = true; + t2.Header.IsTransformed = true; + + Add(t1, matrix); + Add(t2, matrix); + } } internal int Add(LineZCollider collider, float4x4 matrix) @@ -428,32 +436,6 @@ internal int Add(TriangleCollider collider, float4x4 matrix) return collider.Id; } - internal void AddLine(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, float4x4 matrix) - { - if (!KinematicColliders && LineCollider.IsTransformable(matrix)) { - var collider = new LineCollider(v1, v2, zLow, zHigh, info); - collider.Header.IsTransformed = true; - Add(collider, matrix); - - } else { - - // convert line collider to two triangle colliders - var p1 = new float3(v1.xy, zLow); - var p2 = new float3(v1.xy, zHigh); - var p3 = new float3(v2.xy, zLow); - var p4 = new float3(v2.xy, zHigh); - - var t1 = new TriangleCollider(p1, p3, p2, info); - var t2 = new TriangleCollider(p3, p4, p2, info); - - t1.Header.IsTransformed = true; - t2.Header.IsTransformed = true; - - Add(t1, matrix); - Add(t2, matrix); - } - } - #endregion public ICollider[] ToArray() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 32c602900..b149772e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -96,7 +96,7 @@ private void GenerateLineCollider(ref ColliderReference colliders) var rgv1 = new float2(-(halfLength + PhysicsConstants.PhysSkin), 0f); var info = _api.GetColliderInfo(ItemType.Invalid); // hack to not treat this line seg as gate - colliders.AddLine(rgv0, rgv1, -2f * PhysicsConstants.PhysSkin, 0, info, _matrix); //!! = ball diameter + colliders.Add(new LineCollider(rgv0, rgv1, -2f * PhysicsConstants.PhysSkin, 0, info), _matrix); //!! = ball diameter } /// diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index 61da6565c..b26bc2ef5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -213,7 +213,7 @@ private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float h GenerateWallLineSeg((pv1 + pv2) * 0.5f, pv2, true, (height1 + height2) * 0.5f, height2, wallHeight, ref colliders); } else { - colliders.AddLine(pv1, pv2, height1, height2 + wallHeight, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineCollider(pv1, pv2, height1, height2 + wallHeight, _api.GetColliderInfo()), _matrix); if (pv3Exists) { colliders.Add(new LineZCollider(pv1, height1, height2 + wallHeight, _api.GetColliderInfo()), _matrix); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index be22ac1a6..8c3214612 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -77,7 +77,7 @@ private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfield var top = _component.HeightTop + playfieldHeight; if (!pv1.IsSlingshot) { - colliders.AddLine(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo(), _matrix); + colliders.Add(new LineCollider(pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo()), _matrix); } else { colliders.Add(new LineSlingshotCollider(_colliderComponent.SlingshotForce, pv1.ToUnityFloat2(), pv2.ToUnityFloat2(), bottom, top, _api.GetColliderInfo()), _matrix); From 1dbcbfc85a908b9ed5490e37ce19758b70302709 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 00:57:43 +0100 Subject: [PATCH 170/208] colliders: Make Add() void where possible. --- .../Physics/Collider/ColliderReference.cs | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index a6967d83f..c9d4dfc39 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -252,7 +252,7 @@ internal int Add(FlipperCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(GateCollider collider, float4x4 matrix) + internal void Add(GateCollider collider, float4x4 matrix) { if (!KinematicColliders && GateCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; @@ -272,10 +272,9 @@ internal int Add(GateCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Gate, GateColliders.Length)); GateColliders.Add(collider); - return collider.Id; } - internal int Add(Line3DCollider collider, float4x4 matrix) + internal void Add(Line3DCollider collider, float4x4 matrix) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -284,10 +283,9 @@ internal int Add(Line3DCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Line3D, Line3DColliders.Length)); Line3DColliders.Add(collider); - return collider.Id; } - internal int Add(LineSlingshotCollider collider, float4x4 matrix) + internal void Add(LineSlingshotCollider collider, float4x4 matrix) { if (!KinematicColliders && LineSlingshotCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; @@ -306,7 +304,6 @@ internal int Add(LineSlingshotCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.LineSlingShot, LineSlingshotColliders.Length)); LineSlingshotColliders.Add(collider); - return collider.Id; } internal void Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only @@ -340,11 +337,12 @@ internal void Add(LineCollider collider, float4x4 matrix) } } - internal int Add(LineZCollider collider, float4x4 matrix) + internal void Add(LineZCollider collider, float4x4 matrix) { if (KinematicColliders || !LineZCollider.IsTransformable(matrix)) { // use line 3d collider instead - return Add(new Line3DCollider(new float3(collider.XY, collider.ZLow), new float3(collider.XY, collider.ZHigh), collider.Header.ColliderInfo), matrix); + Add(new Line3DCollider(new float3(collider.XY, collider.ZLow), new float3(collider.XY, collider.ZHigh), collider.Header.ColliderInfo), matrix); + return; } collider.Header.IsTransformed = true; @@ -354,10 +352,9 @@ internal int Add(LineZCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.LineZ, LineZColliders.Length)); LineZColliders.Add(collider); - return collider.Id; } - internal int Add(PlungerCollider collider, float4x4 matrix) + internal void Add(PlungerCollider collider, float4x4 matrix) { if (!KinematicColliders && PlungerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; @@ -377,10 +374,9 @@ internal int Add(PlungerCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Plunger, PlungerColliders.Length)); PlungerColliders.Add(collider); - return collider.Id; } - internal int Add(PointCollider collider, float4x4 matrix) + internal void Add(PointCollider collider, float4x4 matrix) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -389,10 +385,9 @@ internal int Add(PointCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Point, PointColliders.Length)); PointColliders.Add(collider); - return collider.Id; } - internal int Add(SpinnerCollider collider, float4x4 matrix) + internal void Add(SpinnerCollider collider, float4x4 matrix) { if (!KinematicColliders && SpinnerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; @@ -412,7 +407,6 @@ internal int Add(SpinnerCollider collider, float4x4 matrix) TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Spinner, SpinnerColliders.Length)); SpinnerColliders.Add(collider); - return collider.Id; } internal int Add(TriangleCollider collider, float4x4 matrix) @@ -427,13 +421,12 @@ internal int Add(TriangleCollider collider, float4x4 matrix) return collider.Id; } - internal int Add(PlaneCollider collider) // used for the playfield only + internal void Add(PlaneCollider collider) // used for the playfield only { collider.Id = Lookups.Length; TrackReference(collider.Header.ItemId, collider.Header.Id); Lookups.Add(new ColliderLookup(ColliderType.Plane, PlaneColliders.Length)); PlaneColliders.Add(collider); - return collider.Id; } #endregion From 1d27b920831567ae936f08993db9ea77cb4087a8 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 01:23:54 +0100 Subject: [PATCH 171/208] physics: Do proper validations when tranforming kinetic colliders. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 2 +- .../Physics/Collider/ColliderReference.cs | 67 ++++++++++++------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 35354060d..9521bed73 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -247,7 +247,7 @@ private void Start() _kinematicColliderLookups = kinematicColliders.CreateLookup(Allocator.Persistent); // create identity kinematic colliders - kinematicColliders.TransformToIdentity(_kinematicTransforms.Ref); + kinematicColliders.TransformToIdentity(ref _kinematicTransforms.Ref); _kinematicCollidersAtIdentity = new NativeColliders(ref kinematicColliders, Allocator.Persistent); // create octree diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index c9d4dfc39..21d6e162c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -95,7 +95,7 @@ public void Dispose() public ICollider this[int i] => LookupCollider(i); - public void TransformToIdentity(NativeParallelHashMap itemIdToTransformationMatrix) + public void TransformToIdentity(ref NativeParallelHashMap itemIdToTransformationMatrix) { using var enumerator = _itemIdToColliderIds.GetEnumerator(); while (enumerator.MoveNext()) { @@ -105,62 +105,76 @@ public void TransformToIdentity(NativeParallelHashMap itemIdToTra var matrix = itemIdToTransformationMatrix[itemId]; var lookup = Lookups[colliderId]; switch (lookup.Type) { + case ColliderType.Bumper: case ColliderType.Circle: ref var circleCollider = ref CircleColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR if (circleCollider.Header.IsTransformed) { - circleCollider.Transform(CircleColliders[lookup.Index], math.inverse(matrix)); - } else { - circleCollider.TransformAabb(math.inverse(matrix)); + throw new InvalidOperationException("A transformed circle collider shouldn't have been added as a kinetic collider."); } + #endif + circleCollider.TransformAabb(math.inverse(matrix)); break; + case ColliderType.Point: ref var pointCollider = ref PointColliders.GetElementAsRef(lookup.Index); - if (pointCollider.Header.IsTransformed) { - pointCollider.Transform(PointColliders[lookup.Index], math.inverse(matrix)); - } else { - pointCollider.TransformAabb(math.inverse(matrix)); + #if UNITY_EDITOR + if (!pointCollider.Header.IsTransformed) { + throw new InvalidOperationException("Points are fully transformable, so they should always be transformed."); } + #endif + pointCollider.Transform(PointColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Line3D: ref var line3DCollider = ref Line3DColliders.GetElementAsRef(lookup.Index); - if (line3DCollider.Header.IsTransformed) { - line3DCollider.Transform(Line3DColliders[lookup.Index], math.inverse(matrix)); - } else { - line3DCollider.TransformAabb(math.inverse(matrix)); + #if UNITY_EDITOR + if (!line3DCollider.Header.IsTransformed) { + throw new InvalidOperationException("Line3D colliders are fully transformable, so they should always be transformed."); } + #endif + line3DCollider.Transform(Line3DColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Triangle: ref var triangleCollider = ref TriangleColliders.GetElementAsRef(lookup.Index); - if (triangleCollider.Header.IsTransformed) { - triangleCollider.Transform(TriangleColliders[lookup.Index], math.inverse(matrix)); - } else { - triangleCollider.TransformAabb(math.inverse(matrix)); + #if UNITY_EDITOR + if (!triangleCollider.Header.IsTransformed) { + throw new InvalidOperationException("Triangles are fully transformable, so they should always be transformed."); } + #endif + triangleCollider.Transform(TriangleColliders[lookup.Index], math.inverse(matrix)); break; + case ColliderType.Spinner: ref var spinnerCollider = ref SpinnerColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR if (spinnerCollider.Header.IsTransformed) { - spinnerCollider.Transform(SpinnerColliders[lookup.Index], math.inverse(matrix)); - } else { - spinnerCollider.TransformAabb(math.inverse(matrix)); + throw new InvalidOperationException("A transformed spinner collider shouldn't have been added as a kinetic collider."); } + #endif + spinnerCollider.TransformAabb(math.inverse(matrix)); break; + case ColliderType.Gate: ref var gateCollider = ref GateColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR if (gateCollider.Header.IsTransformed) { - gateCollider.Transform(GateColliders[lookup.Index], math.inverse(matrix)); - } else { - gateCollider.TransformAabb(math.inverse(matrix)); + throw new InvalidOperationException("A transformed gate collider shouldn't have been added as a kinetic collider."); } + #endif + gateCollider.TransformAabb(math.inverse(matrix)); break; + case ColliderType.Flipper: ref var flipperCollider = ref FlipperColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR if (flipperCollider.Header.IsTransformed) { - flipperCollider.Transform(FlipperColliders[lookup.Index], math.inverse(matrix)); - } else { - flipperCollider.TransformAabb(math.inverse(matrix)); + throw new InvalidOperationException("A transformed flipper collider shouldn't have been added as a kinetic collider."); } + #endif + flipperCollider.TransformAabb(math.inverse(matrix)); break; } } @@ -188,7 +202,7 @@ private ICollider LookupCollider(int i) case ColliderType.Triangle: return TriangleColliders.GetElementAsRef(lookup.Index); case ColliderType.Plane: return PlaneColliders.GetElementAsRef(lookup.Index); } - throw new ArgumentException($"Unknown lookup type."); + throw new ArgumentException("Unknown lookup type."); } #region Add @@ -431,6 +445,7 @@ internal int Add(TriangleCollider collider, float4x4 matrix) #endregion + // ReSharper disable once UnusedMember.Global public ICollider[] ToArray() { var array = new ICollider[Lookups.Length]; From ec62ca517c8100725eceeddf508cecc5f5210f3c Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 02:27:46 +0100 Subject: [PATCH 172/208] colliders: Add remaining AABB transformations. --- .../Physics/Collider/ColliderReference.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 21d6e162c..4f8d6ec40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -97,6 +97,11 @@ public void Dispose() public void TransformToIdentity(ref NativeParallelHashMap itemIdToTransformationMatrix) { + #if UNITY_EDITOR + if (!KinematicColliders) { + throw new InvalidOperationException("Cannot transform non-kinetic colliders to identity."); + } + #endif using var enumerator = _itemIdToColliderIds.GetEnumerator(); while (enumerator.MoveNext()) { var itemId = enumerator.Current.Key; @@ -106,6 +111,8 @@ public void TransformToIdentity(ref NativeParallelHashMap itemIdT var lookup = Lookups[colliderId]; switch (lookup.Type) { + case ColliderType.TriggerCircle: + case ColliderType.KickerCircle: case ColliderType.Bumper: case ColliderType.Circle: ref var circleCollider = ref CircleColliders.GetElementAsRef(lookup.Index); @@ -176,6 +183,43 @@ public void TransformToIdentity(ref NativeParallelHashMap itemIdT #endif flipperCollider.TransformAabb(math.inverse(matrix)); break; + + case ColliderType.LineSlingShot: + ref var slingshotCollider = ref LineSlingshotColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR + if (slingshotCollider.Header.IsTransformed) { + throw new InvalidOperationException("A transformed slingshot collider shouldn't have been added as a kinetic collider."); + } + #endif + slingshotCollider.TransformAabb(math.inverse(matrix)); + break; + + case ColliderType.Plunger: + ref var plungerCollider = ref PlungerColliders.GetElementAsRef(lookup.Index); + #if UNITY_EDITOR + if (plungerCollider.Header.IsTransformed) { + throw new InvalidOperationException("A transformed plunger collider shouldn't have been added as a kinetic collider."); + } + #endif + plungerCollider.TransformAabb(math.inverse(matrix)); + break; + + case ColliderType.Line: + #if UNITY_EDITOR + throw new InvalidOperationException("Line colliders shouldn't exist as kinetic colliders, but converted to line 3D colliders."); + #endif + case ColliderType.LineZ: + #if UNITY_EDITOR + throw new InvalidOperationException("Line-Z colliders shouldn't exist as kinetic colliders, but converted to line 3D colliders."); + #endif + case ColliderType.Plane: + #if UNITY_EDITOR + throw new InvalidOperationException("Planes cannot be be kinematic."); + #endif + case ColliderType.None: + break; + default: + throw new ArgumentOutOfRangeException(); } } } From d78893bcad4ecdbca095808ea039eaa709e6f11a Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 13:29:55 +0100 Subject: [PATCH 173/208] collider: Move IsKinematic to ColliderComponent and make it available in all inspectors. --- .../VPT/Bumper/BumperColliderInspector.cs | 5 ++--- .../VPT/ColliderInspector.cs | 14 ++++++++++++++ .../VPT/Flipper/FlipperColliderInspector.cs | 3 --- .../VPT/Gate/GateColliderInspector.cs | 5 ++--- .../VPT/ItemInspector.cs | 2 +- .../VPT/Kicker/KickerColliderInspector.cs | 2 +- .../VPT/Playfield/PlayfieldColliderInspector.cs | 2 +- .../VPT/Plunger/PlungerColliderInspector.cs | 3 --- .../VPT/Primitive/PrimitiveColliderInspector.cs | 3 --- .../VPT/Ramp/RampColliderInspector.cs | 5 ----- .../VPT/Rubber/RubberColliderInspector.cs | 3 --- .../VPT/Spinner/SpinnerColliderInspector.cs | 3 --- .../VPT/Surface/SurfaceColliderInspector.cs | 3 --- .../VPT/Trigger/TriggerColliderInspector.cs | 3 --- .../VPT/Bumper/BumperColliderComponent.cs | 10 ---------- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 11 ++++++++++- .../VPT/Flipper/FlipperColliderComponent.cs | 10 ---------- .../VPT/Gate/GateColliderComponent.cs | 10 ---------- .../VPT/Plunger/PlungerColliderComponent.cs | 10 ---------- .../VPT/Primitive/PrimitiveColliderComponent.cs | 10 ---------- .../VPT/Ramp/RampColliderComponent.cs | 10 ---------- .../VPT/Rubber/RubberColliderComponent.cs | 10 ---------- .../VPT/Spinner/SpinnerColliderComponent.cs | 9 --------- .../VPT/Surface/SurfaceColliderComponent.cs | 10 ---------- .../VPT/Trigger/TriggerColliderComponent.cs | 10 ---------- 25 files changed, 31 insertions(+), 135 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperColliderInspector.cs index 6168e070e..e3be1b588 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Bumper/BumperColliderInspector.cs @@ -28,7 +28,6 @@ public class BumperColliderInspector : ColliderInspector : Item { protected TColliderComponent ColliderComponent; + protected SerializedProperty IsKinematicProperty; + private bool _foldoutDebug = true; private bool _foldoutColliders; private string[] _currentColliders; @@ -42,6 +44,7 @@ public class ColliderInspector : Item protected override void OnEnable() { + IsKinematicProperty = serializedObject.FindProperty(nameof(ColliderComponent._isKinematic)); ColliderComponent = target as TColliderComponent; if (ColliderComponent != null) { @@ -54,6 +57,17 @@ protected override void OnEnable() base.OnEnable(); } + protected override void OnPreInspectorGUI() + { + PropertyField(IsKinematicProperty, "Movable"); + base.OnPreInspectorGUI(); + } + + protected void OnParentPreInspectorGUI() + { + base.OnPreInspectorGUI(); + } + public override void OnInspectorGUI() { if (ColliderComponent == null) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs index eed1d90f7..11c51aa95 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Flipper/FlipperColliderInspector.cs @@ -37,7 +37,6 @@ public class FlipperColliderInspector : ColliderInspector { - private SerializedProperty _isKinematicProperty; private SerializedProperty _zLowProperty; private SerializedProperty _distanceProperty; private SerializedProperty _angleMinProperty; @@ -39,7 +38,6 @@ protected override void OnEnable() { base.OnEnable(); - _isKinematicProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent._isKinematic)); _zLowProperty = serializedObject.FindProperty(nameof(GateColliderComponent.ZLow)); _distanceProperty = serializedObject.FindProperty(nameof(GateColliderComponent.Distance)); _angleMinProperty = serializedObject.FindProperty(nameof(GateColliderComponent._angleMin)); @@ -59,7 +57,8 @@ public override void OnInspectorGUI() BeginEditing(); - PropertyField(_isKinematicProperty, "Movable"); + OnPreInspectorGUI(); + PropertyField(_zLowProperty, "Z-Low"); PropertyField(_distanceProperty, "Distance"); PropertyField(_angleMinProperty, "Close Angle"); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 4d393b453..9d7b87e6c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -243,7 +243,7 @@ protected void DropDownProperty(string label, SerializedProperty prop, string[] } } - protected void OnPreInspectorGUI() + protected virtual void OnPreInspectorGUI() { if (!(target is IMainRenderableComponent)) { return; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerColliderInspector.cs index 03afaa6ec..d2049f164 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerColliderInspector.cs @@ -48,6 +48,7 @@ public override void OnInspectorGUI() } BeginEditing(); + OnPreInspectorGUI(); PropertyField(_hitAccuracyProperty); PropertyField(_hitHeightProperty, updateColliders: true); @@ -61,4 +62,3 @@ public override void OnInspectorGUI() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldColliderInspector.cs index 14d898a98..c0fe0c929 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Playfield/PlayfieldColliderInspector.cs @@ -53,7 +53,7 @@ public override void OnInspectorGUI() BeginEditing(); - OnPreInspectorGUI(); + OnParentPreInspectorGUI(); PropertyField(_gravityProperty, "Gravity Constant"); PropertyField(_frictionProperty, "Playfield Friction"); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs index b097ddeb6..3e42fbbce 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Plunger/PlungerColliderInspector.cs @@ -24,7 +24,6 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(PlungerColliderComponent)), CanEditMultipleObjects] public class PlungerColliderInspector : ColliderInspector { - private SerializedProperty _isKinematicProperty; private SerializedProperty _speedPullProperty; private SerializedProperty _speedFireProperty; private SerializedProperty _strokeProperty; @@ -38,7 +37,6 @@ public class PlungerColliderInspector : ColliderInspector { - private SerializedProperty _isKinematicProperty; private SerializedProperty _elasticityProperty; private SerializedProperty _zPosProperty; protected override void OnEnable() { base.OnEnable(); - _isKinematicProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent._isKinematic)); _elasticityProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent.Elasticity)); _zPosProperty = serializedObject.FindProperty(nameof(SpinnerColliderComponent.ZPosition)); } @@ -44,7 +42,6 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_isKinematicProperty, "Movable"); PropertyField(_elasticityProperty, updateTransforms: true); PropertyField(_zPosProperty); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs index 98eb0c182..14d746913 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Surface/SurfaceColliderInspector.cs @@ -27,7 +27,6 @@ public class SurfaceColliderInspector : ColliderInspector { - private SerializedProperty _isKinematicProperty; private SerializedProperty _hitHeightProperty; private SerializedProperty _hitCircleRadiusProperty; protected override void OnEnable() { base.OnEnable(); - _isKinematicProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent._isKinematic)); _hitHeightProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent.HitHeight)); _hitCircleRadiusProperty = serializedObject.FindProperty(nameof(TriggerColliderComponent.HitCircleRadius)); } @@ -46,7 +44,6 @@ public override void OnInspectorGUI() OnPreInspectorGUI(); - PropertyField(_isKinematicProperty, "Movable"); PropertyField(_hitHeightProperty, updateColliders: true); var meshComponent = (target as TriggerColliderComponent)!.GetComponent(); if (meshComponent && meshComponent.IsCircle) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index c93b54a09..58c007dca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -42,16 +42,6 @@ public class BumperColliderComponent : ColliderComponent _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - #endregion protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 268aa42ff..6f9617292 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -121,13 +121,22 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa }; } + #region IKinematicColliderComponent + + [Tooltip("If set, transforming this object during gameplay will transform the colliders as well.")] + public bool _isKinematic; + + public bool IsKinematic => _isKinematic; + public int ItemId => MainComponent.gameObject.GetInstanceID(); + + #endregion + #region Collider Gizmos #if UNITY_EDITOR private PhysicsEngine _physicsEngine; private Player _player; - private bool IsKinematic => this is IKinematicColliderComponent { IsKinematic: true }; private void OnDrawGizmos() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index a682d0912..e08595fa0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -78,16 +78,6 @@ public class FlipperColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter); - #region IKinematicColliderComponent - - [Tooltip("If set, transforming this object during gameplay will transform the colliders as well.")] - public bool _isKinematic; - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion - #region FlipperTricks /// /// If set, apply Flipper Tricks Physics (nFozzy/RothBauerW) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index 539603cae..f15b167d8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -77,15 +77,5 @@ public class GateColliderComponent : ColliderComponent, protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.GateApi ?? new GateApi(gameObject, player, physicsEngine); - - #region IKinematicColliderComponent - - [Tooltip("If set, transforming this object will transform the colliders as well.")] - public bool _isKinematic; - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index dabe7c58b..7a5e2db0a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -53,20 +53,10 @@ public class PlungerColliderComponent : ColliderComponent GetPhysicsMaterialData(); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.PlungerApi ?? new PlungerApi(gameObject, player, physicsEngine); - - #region IKinematicColliderComponent - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index e0511a353..dd28c17e3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -57,16 +57,6 @@ public class PrimitiveColliderComponent : ColliderComponent _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - #endregion public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index 18e616657..13c3d7d8e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -57,16 +57,6 @@ public class RampColliderComponent : ColliderComponent, [Tooltip("When hit, add a random angle between 0 and this value to the trajectory.")] public float Scatter; - [Tooltip("If set, transforming this object will transform the colliders as well.")] - public bool _isKinematic; - - #endregion - - #region IKinematicColliderComponent - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - #endregion public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction, scatterAngleDeg: Scatter, overwrite: OverwritePhysics); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index fc03535f6..977b87216 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -52,20 +52,10 @@ public class RubberColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.RubberApi ?? new RubberApi(gameObject, player, physicsEngine); - - #region IKinematicColliderComponent - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index 301cbfab1..096872214 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -40,14 +40,5 @@ public class SpinnerColliderComponent : ColliderComponent MainComponent.SpinnerApi ?? new SpinnerApi(gameObject, player, physicsEngine); - #region IKinematicColliderComponent - - [Tooltip("If set, transforming this object during gameplay will transform the colliders as well.")] - public bool _isKinematic; - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index e551fdecb..72388ef89 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -64,20 +64,10 @@ public class SurfaceColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.SurfaceApi ?? new SurfaceApi(gameObject, player, physicsEngine); - - #region IKinematicColliderComponent - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index f4f0da1f9..875587dcc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -48,20 +48,10 @@ public class TriggerColliderComponent : ColliderComponent GetPhysicsMaterialData(); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.TriggerApi ?? new TriggerApi(gameObject, player, physicsEngine); - - #region IKinematicColliderComponent - - public bool IsKinematic => _isKinematic; - public int ItemId => MainComponent.gameObject.GetInstanceID(); - - #endregion } } From 5d445d7419471f331b7516f969d5b0c37919c1d9 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 22:35:04 +0100 Subject: [PATCH 174/208] refactor: Pass the correct collider refence to the APIs instead of having them to figure out which one to use. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 6 +++++- .../VPT/Bumper/BumperApi.cs | 11 +++-------- .../VisualPinball.Unity/VPT/CollidableApi.cs | 8 +++----- .../VPT/ColliderComponent.cs | 18 +++++++++++++----- .../VPT/Flipper/FlipperApi.cs | 5 ++--- .../VisualPinball.Unity/VPT/Gate/GateApi.cs | 9 ++------- .../VPT/HitTarget/DropTargetApi.cs | 3 +-- .../VPT/HitTarget/HitTargetApi.cs | 3 +-- .../VisualPinball.Unity/VPT/IApi.cs | 3 +-- .../VPT/ICollidableComponent.cs | 8 ++++++-- .../VPT/Kicker/KickerApi.cs | 3 +-- .../VPT/MetalWireGuide/MetalWireGuideApi.cs | 3 +-- .../VPT/Playfield/PlayfieldApi.cs | 3 +-- .../VPT/Plunger/PlungerApi.cs | 9 ++------- .../VPT/Primitive/PrimitiveApi.cs | 9 ++------- .../VisualPinball.Unity/VPT/Ramp/RampApi.cs | 10 ++-------- .../VPT/Rubber/RubberApi.cs | 9 ++------- .../VPT/Spinner/SpinnerApi.cs | 9 ++------- .../VPT/Surface/SurfaceApi.cs | 10 ++-------- .../VPT/Trigger/TriggerApi.cs | 3 +-- 20 files changed, 53 insertions(+), 89 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 9521bed73..8a2527c81 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -227,7 +227,11 @@ private void Start() // todo check if we cannot only add those that are actually non-transformable _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; - colliderItem.GetColliders(_player, this, ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, 0); + if (colliderItem.IsKinematic) { + colliderItem.GetColliders(_player, this, ref kinematicColliders, translateWithinPlayfieldMatrix, 0); + } else { + colliderItem.GetColliders(_player, this, ref colliders, translateWithinPlayfieldMatrix, 0); + } } // allocate colliders diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 47e23725f..02d9e0def 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -111,18 +111,13 @@ void IApiCoil.OnCoil(bool enabled) protected override float HitThreshold => ColliderComponent.Threshold; protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + float4x4 translateWithinPlayfieldMatrix, float margin) { var height = MainComponent.Position.z; var switchCollider = new CircleCollider(new float2(0), MainComponent.Radius, height, height + 100f, GetColliderInfo(), ColliderType.Bumper); var rigidCollider = new CircleCollider(new float2(0), MainComponent.Radius * 0.5f, height, height + 100f, GetColliderInfo(), ColliderType.Circle); - if (ColliderComponent._isKinematic) { - _switchColliderId = kinematicColliders.Add(switchCollider, translateWithinPlayfieldMatrix); - kinematicColliders.Add(rigidCollider, translateWithinPlayfieldMatrix); - } else { - _switchColliderId = colliders.Add(switchCollider, translateWithinPlayfieldMatrix); - colliders.Add(rigidCollider, translateWithinPlayfieldMatrix); - } + _switchColliderId = colliders.Add(switchCollider, translateWithinPlayfieldMatrix); + colliders.Add(rigidCollider, translateWithinPlayfieldMatrix); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index 38cb49fcb..dca4b9082 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs @@ -42,16 +42,14 @@ protected CollidableApi(GameObject go, Player player, PhysicsEngine physicsEngin protected virtual bool FireHitEvents => false; protected virtual float HitThreshold => 0; - protected abstract void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); + protected abstract void CreateColliders(ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); - void IApiColliderGenerator.CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + void IApiColliderGenerator.CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { if (!ColliderComponent) { return; } - CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, margin); + CreateColliders(ref colliders, translateWithinPlayfieldMatrix, margin); } ColliderInfo IApiColliderGenerator.GetColliderInfo() => GetColliderInfo(MainComponent.ItemType); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 6f9617292..2b015d7b9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -180,9 +180,13 @@ private void OnDrawGizmos() var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob); var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob, true); try { - api.CreateColliders(ref colliders, ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); + if (IsKinematic) { + api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); + } else { + api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); + } - var playfieldBounds = GetComponentInParent().Bounds; + var playfieldBounds = GetComponentInParent().Bounds; var octree = new NativeOctree(playfieldBounds, 32, 10, Allocator.Persistent); var nativeColliders = new NativeColliders(ref colliders, Allocator.TempJob); var populateJob = new PhysicsPopulateJob { @@ -284,7 +288,11 @@ private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashM var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp); var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp, true); try { - api.CreateColliders(ref colliders, ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); + if (IsKinematic) { + api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); + } else { + api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); + } if (createMesh) { if (IsKinematic) { @@ -726,9 +734,9 @@ private static void DrawAabb(Aabb aabb, bool isSelected) #endregion void ICollidableComponent.GetColliders(Player player, PhysicsEngine physicsEngine, ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + float4x4 translateWithinPlayfieldMatrix, float margin) => InstantiateColliderApi(player, physicsEngine) - .CreateColliders(ref colliders, ref kinematicColliders, translateWithinPlayfieldMatrix, margin); + .CreateColliders(ref colliders, translateWithinPlayfieldMatrix, margin); int ICollidableComponent.ItemId => MainComponent.gameObject.GetInstanceID(); bool ICollidableComponent.IsCollidable => isActiveAndEnabled; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index d8fb0f93f..d7313ee28 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -223,8 +223,7 @@ void IApiCollidable.OnCollide(int ballId, float hit) #region Collider Generation - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { // check which side we are at var multiplicator = 0.0f; @@ -233,7 +232,7 @@ protected override void CreateColliders(ref ColliderReference colliders, } // and add ColliderComponent.Overshoot to the endangle so that the bounding box is correctly calculated - ColliderId = (ColliderComponent._isKinematic ? kinematicColliders : colliders).Add( + ColliderId = colliders.Add( new FlipperCollider( MainComponent.Height, MainComponent.FlipperRadiusMax, diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index 0042d02e0..c9a6c6546 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -104,15 +104,10 @@ public void Lift(float speed, float angleDeg) protected override bool FireHitEvents => true; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent, ColliderComponent.ZLow, ColliderComponent.Distance, translateWithinPlayfieldMatrix); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(ref kinematicColliders); - } else { - colliderGenerator.GenerateColliders(ref colliders); - } + colliderGenerator.GenerateColliders(ref colliders); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs index 005d4c1e2..98cba0a4b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs @@ -103,8 +103,7 @@ private void SetIsDropped(bool isDropped) protected override bool FireHitEvents => true; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 2a4049908..564c5400b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -60,8 +60,7 @@ internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override bool FireHitEvents => true; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new HitTargetColliderGenerator(ColliderComponent.ColliderMesh, this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs index 41da4878e..3a9a10a1a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs @@ -42,10 +42,9 @@ public interface IApiColliderGenerator /// Create colliders and add them to the provided list. /// /// List to add colliders to. - /// List to add kinematic colliders to. /// /// - void CreateColliders(ref ColliderReference colliders, ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); + void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin); /// /// Computes collider info based on the component data. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 6d314e3d4..4b579379c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -26,11 +26,10 @@ public interface ICollidableComponent /// /// /// - /// /// /// internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin); + float4x4 translateWithinPlayfieldMatrix, float margin); /// /// The unique identifier of the main item. @@ -43,6 +42,11 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// internal bool IsCollidable { get; } + /// + /// If set, this collider can be transformed during gameplay. + /// + public bool IsKinematic { get; } + /// /// The translation matrix, that will be applied in reverse to the ball /// for hit testing and collision. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 8ad417af8..80b1fbd6f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -224,8 +224,7 @@ private void KickXYZ(float angle, float speed, float inclination, float x, float #region Collider Generation - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { // reduce the hit circle radius because only the inner circle of the kicker should start a hit event var radius = MainComponent.Radius * (ColliderComponent.LegacyMode ? ColliderComponent.FallThrough ? 0.75f : 0.6f : 1f); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs index aafa74b9f..4e8edfab2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs @@ -43,8 +43,7 @@ internal MetalWireGuideApi(GameObject go, Player player, PhysicsEngine physicsEn protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => 2.0f; // hard coded threshold for now - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent), translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs index 5c477f4aa..97e668dff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs @@ -29,8 +29,7 @@ internal PlayfieldApi(GameObject go, Player player, PhysicsEngine physicsEngine) #region Collider Generation - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var info = ((IApiColliderGenerator)this).GetColliderInfo(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 90ff80eef..edcd05e21 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -143,14 +143,9 @@ private IApiCoil Coil(string deviceItem) #region Collider Generation - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - if (ColliderComponent._isKinematic) { - kinematicColliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()), translateWithinPlayfieldMatrix); - } else { - colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()), translateWithinPlayfieldMatrix); - } + colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo()), translateWithinPlayfieldMatrix); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs index d5f253986..510d972f9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs @@ -44,15 +44,10 @@ internal PrimitiveApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, ref kinematicColliders); - } else { - colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, ref colliders); - } + colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, ref colliders); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index f3e365616..61c09775f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -62,16 +62,10 @@ void IApi.OnDestroy() protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(0, ref kinematicColliders, margin); - } else { - colliderGenerator.GenerateColliders(0, ref colliders, margin); - } - + colliderGenerator.GenerateColliders(0, ref colliders, margin); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index 1fd393c86..c4fb35f9e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -44,19 +44,14 @@ internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => 2.0f; // hard coded threshold for now - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new RubberColliderGenerator( this, new RubberMeshGenerator(MainComponent), translateWithinPlayfieldMatrix ); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref kinematicColliders, margin); - } else { - colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); - } + colliderGenerator.GenerateColliders(0, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs index 127441f6c..e9c808134 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs @@ -83,15 +83,10 @@ public SpinnerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : b protected override bool FireHitEvents => true; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent, translateWithinPlayfieldMatrix); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(ref kinematicColliders, ColliderComponent.ZPosition); - } else { - colliderGenerator.GenerateColliders(ref colliders, ColliderComponent.ZPosition); - } + colliderGenerator.GenerateColliders(ref colliders, ColliderComponent.ZPosition); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index 24f94f99d..d0a11a804 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -48,19 +48,13 @@ internal SurfaceApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { if (MainComponent.DragPoints.Length == 0) { return; } var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent, translateWithinPlayfieldMatrix); - if (ColliderComponent._isKinematic) { - colliderGenerator.GenerateColliders(0, ref kinematicColliders, margin); - - } else { - colliderGenerator.GenerateColliders(0, ref colliders, margin); - } + colliderGenerator.GenerateColliders(0, ref colliders, margin); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs index 39a5d7516..b30d8e6d6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs @@ -64,8 +64,7 @@ internal TriggerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : protected override bool FireHitEvents => true; - protected override void CreateColliders(ref ColliderReference colliders, - ref ColliderReference kinematicColliders, float4x4 translateWithinPlayfieldMatrix, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { var meshComponent = GameObject.GetComponent(); var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent, translateWithinPlayfieldMatrix); From a5a3d236057c8b276244b577119633d502e32f4d Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 23:00:54 +0100 Subject: [PATCH 175/208] colliders: Always use collider reference when retrieving item states. --- .../VisualPinball.Unity/Game/PhysicsState.cs | 24 +++++++++---------- .../Game/PhysicsStaticCollision.cs | 22 ++++++++--------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index ce9262a1a..04af5577d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -99,27 +99,27 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native internal ref FlipperState GetFlipperState(int colliderId, ref NativeColliders colliders) => ref FlipperStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref PlungerState GetPlungerState(int colliderId) => ref PlungerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref PlungerState GetPlungerState(int colliderId, ref NativeColliders colliders) => ref PlungerStates.GetValueByRef(colliders.GetItemId(colliderId)); internal ref SpinnerState GetSpinnerState(int colliderId, ref NativeColliders colliders) => ref SpinnerStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref TriggerState GetTriggerState(int colliderId) => ref TriggerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref TriggerState GetTriggerState(int colliderId, ref NativeColliders colliders) => ref TriggerStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref KickerState GetKickerState(int colliderId) => ref KickerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref KickerState GetKickerState(int colliderId, ref NativeColliders colliders) => ref KickerStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal bool HasDropTargetState(int colliderId) => DropTargetStates.ContainsKey(Colliders.GetItemId(colliderId)); + internal bool HasDropTargetState(int colliderId, ref NativeColliders colliders) => DropTargetStates.ContainsKey(colliders.GetItemId(colliderId)); - internal bool HasHitTargetState(int colliderId) => HitTargetStates.ContainsKey(Colliders.GetItemId(colliderId)); + internal bool HasHitTargetState(int colliderId, ref NativeColliders colliders) => HitTargetStates.ContainsKey(colliders.GetItemId(colliderId)); - internal ref DropTargetState GetDropTargetState(int colliderId) => ref DropTargetStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref DropTargetState GetDropTargetState(int colliderId, ref NativeColliders colliders) => ref DropTargetStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref HitTargetState GetHitTargetState(int colliderId) => ref HitTargetStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref HitTargetState GetHitTargetState(int colliderId, ref NativeColliders colliders) => ref HitTargetStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref BumperState GetBumperState(int colliderId, ref NativeColliders col) => ref BumperStates.GetValueByRef(col.GetItemId(colliderId)); + internal ref BumperState GetBumperState(int colliderId, ref NativeColliders colliders) => ref BumperStates.GetValueByRef(colliders.GetItemId(colliderId)); internal ref GateState GetGateState(int colliderId, ref NativeColliders colliders) => ref GateStates.GetValueByRef(colliders.GetItemId(colliderId)); - internal ref SurfaceState GetSurfaceState(int colliderId) => ref SurfaceStates.GetValueByRef(Colliders.GetItemId(colliderId)); + internal ref SurfaceState GetSurfaceState(int colliderId, ref NativeColliders colliders) => ref SurfaceStates.GetValueByRef(colliders.GetItemId(colliderId)); #endregion @@ -231,7 +231,7 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ball, ball.CollisionEvent.HitTime); case ColliderType.Plunger: - ref var plungerState = ref GetPlungerState(colliderId); + ref var plungerState = ref GetPlungerState(colliderId, ref colliders); return colliders.Plunger(colliderId).HitTest(ref newCollEvent, ref InsideOfs, ref plungerState.Movement, in plungerState.Collider, in plungerState.Static, in ball, ball.CollisionEvent.HitTime); } @@ -240,8 +240,8 @@ internal float HitTest(ref NativeColliders colliders, int colliderId, ref BallSt private bool IsInactiveDropTarget(ref NativeColliders colliders, int colliderId) { - if (colliders.GetItemType(colliderId) == ItemType.HitTarget && HasDropTargetState(colliderId)) { - ref var dropTargetState = ref GetDropTargetState(colliderId); + if (colliders.GetItemType(colliderId) == ItemType.HitTarget && HasDropTargetState(colliderId, ref colliders)) { + ref var dropTargetState = ref GetDropTargetState(colliderId, ref colliders); if (dropTargetState.Animation.IsDropped || dropTargetState.Animation.MoveAnimation) { // QUICKFIX so that DT is not triggered twice return true; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 9db906ee2..11165c115 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -128,14 +128,14 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.LineSlingShot: - ref var surfaceState = ref state.GetSurfaceState(colliderId); + ref var surfaceState = ref state.GetSurfaceState(colliderId, ref colliders); ref var surfaceCollider = ref colliders.LineSlingShot(colliderId); surfaceCollider.Collide(ref ball, ref state.EventQueue, in surfaceState.Slingshot, in ball.CollisionEvent, ref state.Env.Random); break; case ColliderType.Plunger: - ref var plungerState = ref state.GetPlungerState(colliderId); + ref var plungerState = ref state.GetPlungerState(colliderId, ref colliders); PlungerCollider.Collide(ref ball, ref ball.CollisionEvent, ref plungerState.Movement, in plungerState.Static, ref state.Env.Random); break; @@ -145,11 +145,11 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r break; case ColliderType.TriggerCircle: - TriggerCollide(ref ball, ref state, in collHeader); + TriggerCollide(ref ball, ref state, in collHeader, ref colliders); break; case ColliderType.KickerCircle: - ref var kickerState = ref state.GetKickerState(colliderId); + ref var kickerState = ref state.GetKickerState(colliderId, ref colliders); KickerCollider.Collide(ref ball, ref state.EventQueue, ref state.InsideOfs, ref kickerState.Collision, in kickerState.Static, in kickerState.CollisionMesh, in ball.CollisionEvent, collHeader.ItemId, false); break; @@ -171,30 +171,30 @@ private static bool CollidesWithItem(ref NativeColliders colliders, ref Collider ? colliders.Triangle(colliderId).Normal() : ball.CollisionEvent.HitNormal; - if (state.HasDropTargetState(colliderId)) { - ref var dropTargetState = ref state.GetDropTargetState(colliderId); + if (state.HasDropTargetState(colliderId, ref colliders)) { + ref var dropTargetState = ref state.GetDropTargetState(colliderId, ref colliders); TargetCollider.DropTargetCollide(ref ball, ref state.EventQueue, ref dropTargetState.Animation, in normal, in ball.CollisionEvent, in collHeader, ref state.Env.Random); return true; } - if (state.HasHitTargetState(colliderId)) { - ref var hitTargetState = ref state.GetHitTargetState(colliderId); + if (state.HasHitTargetState(colliderId, ref colliders)) { + ref var hitTargetState = ref state.GetHitTargetState(colliderId, ref colliders); TargetCollider.HitTargetCollide(ref ball, ref state.EventQueue, ref hitTargetState.Animation, in normal, in ball.CollisionEvent, in collHeader, ref state.Env.Random); return true; } // trigger } else if (collHeader.ItemType == ItemType.Trigger) { - TriggerCollide(ref ball, ref state, in collHeader); + TriggerCollide(ref ball, ref state, in collHeader, ref colliders); return true; } return false; } - private static void TriggerCollide(ref BallState ball, ref PhysicsState state, in ColliderHeader collHeader) + private static void TriggerCollide(ref BallState ball, ref PhysicsState state, in ColliderHeader collHeader, ref NativeColliders colliders) { - ref var triggerState = ref state.GetTriggerState(collHeader.Id); + ref var triggerState = ref state.GetTriggerState(collHeader.Id, ref colliders); TriggerCollider.Collide(ref ball, ref state.EventQueue, ref ball.CollisionEvent, ref state.InsideOfs, ref triggerState.Animation, in collHeader); if (triggerState.FlipperCorrection.IsEnabled) { From 7719edc5602b99e26254400afc7d9c50cd2b7744 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 30 Dec 2024 23:54:52 +0100 Subject: [PATCH 176/208] refactor: Get rid of obsolete interfaces. --- .../VPT/ItemInspector.cs | 7 ++--- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 19 +++++------- .../VisualPinball.Unity/Game/PhysicsState.cs | 15 ++++----- .../Game/PhysicsUpdateJob.cs | 2 +- .../Physics/Collider/ColliderReference.cs | 3 ++ .../Physics/NativeColliders.cs | 2 +- .../VPT/Bumper/BumperColliderComponent.cs | 2 +- .../VPT/ColliderComponent.cs | 3 +- .../VPT/Flipper/FlipperColliderComponent.cs | 2 +- .../VPT/Gate/GateColliderComponent.cs | 2 +- .../VPT/ICollidableComponent.cs | 2 ++ .../VPT/IColliderComponent.cs | 23 -------------- .../VPT/IColliderComponent.cs.meta | 11 ------- .../VPT/IKinematicColliderComponent.cs | 31 ------------------- .../VPT/IKinematicColliderComponent.cs.meta | 3 -- .../VPT/MainRenderableComponent.cs | 6 ++-- .../MetalWireGuideColliderGenerator.cs | 1 - .../VPT/Plunger/PlungerColliderComponent.cs | 2 +- .../Primitive/PrimitiveColliderComponent.cs | 2 +- .../VPT/Ramp/RampColliderComponent.cs | 2 +- .../VPT/Rubber/RubberColliderComponent.cs | 2 +- .../VPT/Spinner/SpinnerColliderComponent.cs | 2 +- .../VisualPinball.Unity/VPT/SubComponent.cs | 2 +- .../VPT/Surface/SurfaceColliderComponent.cs | 2 +- .../VPT/Trigger/TriggerColliderComponent.cs | 2 +- 25 files changed, 38 insertions(+), 112 deletions(-) delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs.meta delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs delete mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 9d7b87e6c..8d54b3b85 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -16,9 +16,6 @@ using System; using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; using UnityEditor; using UnityEngine; using Object = UnityEngine.Object; @@ -122,7 +119,7 @@ protected void EndEditing() } break; - case IColliderComponent colliderComponent: + case ICollidableComponent colliderComponent: if (_collidersDirty) { colliderComponent.CollidersDirty = true; } @@ -296,7 +293,7 @@ protected virtual void FinishEdit(string label, bool dirtyMesh = true) meshItem.MainRenderableComponent.RebuildMeshes(); break; - case IColliderComponent _: + case ICollidableComponent _: Undo.RecordObject(UndoTarget, undoLabel); break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 8a2527c81..1cb73a368 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using NativeTrees; using Unity.Collections; using Unity.Jobs; @@ -101,7 +102,8 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private Player _player; [NonSerialized] private PhysicsMovements _physicsMovements; - [NonSerialized] private IKinematicColliderComponent[] _kinematicColliderComponents; + [NonSerialized] private ICollidableComponent[] _colliderComponents; + [NonSerialized] private ICollidableComponent[] _kinematicColliderComponents; [NonSerialized] private float4x4 _worldToPlayfield; private static ulong NowUsec => (ulong)(Time.timeAsDouble * 1000000); @@ -205,7 +207,8 @@ private void Awake() _physicsMovements = new PhysicsMovements(); _insideOfs = new InsideOfs(Allocator.Persistent); _physicsEnv.Ref[0] = new PhysicsEnv(NowUsec, GetComponentInChildren(), GravityStrength); - _kinematicColliderComponents = GetComponentsInChildren(); + _colliderComponents = GetComponentsInChildren(); + _kinematicColliderComponents = _colliderComponents.Where(c => c.IsKinematic).ToArray(); } private void Start() @@ -214,11 +217,10 @@ private void Start() var sw = Stopwatch.StartNew(); var playfield = GetComponentInChildren(); - var colliderItems = GetComponentsInChildren(); - Debug.Log($"Found {colliderItems.Length} collidable items."); + Debug.Log($"Found {_colliderComponents.Length} collidable items ({_kinematicColliderComponents.Length} kinematic)."); var colliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp); var kinematicColliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp, true); - foreach (var colliderItem in colliderItems) { + foreach (var colliderItem in _colliderComponents) { if (!colliderItem.IsCollidable) { _disabledCollisionItems.Ref.Add(colliderItem.ItemId); } @@ -241,9 +243,7 @@ private void Start() // get kinetic collider matrices _worldToPlayfield = playfield.transform.worldToLocalMatrix; foreach (var coll in _kinematicColliderComponents) { - if (coll.IsKinematic) { - _kinematicTransforms.Ref[coll.ItemId] = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); - } + _kinematicTransforms.Ref[coll.ItemId] = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); } #if UNITY_EDITOR _colliderLookups = colliders.CreateLookup(Allocator.Persistent); @@ -280,9 +280,6 @@ private void Update() // check for updated kinematic transforms _updatedKinematicTransforms.Ref.Clear(); foreach (var coll in _kinematicColliderComponents) { - if (!coll.IsKinematic) { // kinematic enabled? - continue; - } var lastTransformationMatrix = _kinematicTransforms.Ref[coll.ItemId]; var currTransformationMatrix = coll.GetLocalToPlayfieldMatrixInVpx(_worldToPlayfield); if (lastTransformationMatrix.Equals(currTransformationMatrix)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 04af5577d..9995d423f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.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 System; using NativeTrees; using Unity.Collections; using Unity.Mathematics; @@ -146,21 +147,17 @@ internal void Transform(int colliderId, float4x4 matrix) switch (GetColliderType(ref KinematicColliders, colliderId)) { case ColliderType.Point: var pointCollider = KinematicColliders.Point(colliderId); - if (pointCollider.Header.IsTransformed) { - pointCollider.Transform(KinematicCollidersAtIdentity.Point(colliderId), matrix); - } + pointCollider.Transform(KinematicCollidersAtIdentity.Point(colliderId), matrix); break; + case ColliderType.Line3D: var line3DCollider = KinematicColliders.Line3D(colliderId); - if (line3DCollider.Header.IsTransformed) { - line3DCollider.Transform(KinematicCollidersAtIdentity.Line3D(colliderId), matrix); - } + line3DCollider.Transform(KinematicCollidersAtIdentity.Line3D(colliderId), matrix); break; + case ColliderType.Triangle: var triangleCollider = KinematicColliders.Triangle(colliderId); - if (triangleCollider.Header.IsTransformed) { - triangleCollider.Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); - } + triangleCollider.Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); break; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index d7ccd1ca1..80160bb96 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -71,7 +71,7 @@ public void Execute() ref SurfaceStates, ref TriggerStates, ref DisabledCollisionItems, ref SwapBallCollisionHandling); using var cycle = new PhysicsCycle(Allocator.Temp); - // create octree of kinematic-to-ball collision. should be okay here, since static colliders don't transform more than once per frame. + // create octree of kinematic-to-ball collision. should be okay here, since kinetic colliders don't transform more than once per frame. PhysicsKinematics.TransformKinematicColliders(ref state); var kineticOctree = PhysicsKinematics.CreateOctree(ref state, in PlayfieldBounds); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 4f8d6ec40..485129123 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -42,6 +42,9 @@ public struct ColliderReference : IDisposable public NativeList Lookups; // collider id -> collider type + index within collider type list + /// + /// If true, then all colliders are kinematic. + /// public readonly bool KinematicColliders; // if set, populate _itemIdToColliderIds private NativeParallelHashMap> _itemIdToColliderIds; private NativeParallelHashMap _nonTransformableColliderMatrices; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs index fea9bd3e4..67376cc58 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs @@ -563,7 +563,7 @@ public ref ColliderHeader GetHeader(int index) case ColliderType.Triangle: return ref UnsafeUtility.ArrayElementAsRef(m_TriangleColliderBuffer, lookup.Index).Header; case ColliderType.Plane: return ref UnsafeUtility.ArrayElementAsRef(m_PlaneColliderBuffer, lookup.Index).Header; } - throw new ArgumentException($"Unknown lookup type."); + throw new ArgumentException("Unknown lookup type."); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 58c007dca..a1d8f2dd2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Bumper Collider")] - public class BumperColliderComponent : ColliderComponent, IKinematicColliderComponent + public class BumperColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 2b015d7b9..0d846e387 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -34,8 +34,7 @@ namespace VisualPinball.Unity { [DisallowMultipleComponent] - public abstract class ColliderComponent : SubComponent, - IColliderComponent, ICollidableComponent + public abstract class ColliderComponent : SubComponent, ICollidableComponent where TData : ItemData where TMainComponent : MainComponent { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index e08595fa0..9d044290d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Flipper Collider")] [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] - public class FlipperColliderComponent : ColliderComponent, IKinematicColliderComponent + public class FlipperColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index f15b167d8..69fa828ab 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Gate Collider")] - public class GateColliderComponent : ColliderComponent, IGateColliderData, IKinematicColliderComponent + public class GateColliderComponent : ColliderComponent, IGateColliderData { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 4b579379c..59d1e1a5b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -54,5 +54,7 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// The playfield's worldToLocal matrix. /// public float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield); + + public bool CollidersDirty { set; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs deleted file mode 100644 index 361fa3e56..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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 . - -namespace VisualPinball.Unity -{ - public interface IColliderComponent - { - public bool CollidersDirty { set; } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs.meta deleted file mode 100644 index 4a8aa261c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IColliderComponent.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 93b735d8dacffde4295725fbce2c1b8c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs deleted file mode 100644 index 2315aad77..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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 . - -using Unity.Mathematics; - -namespace VisualPinball.Unity -{ - public interface IKinematicColliderComponent - { - public bool IsKinematic { get; } - public int ItemId { get; } - - /// - /// Transformation matrix of the collider in the scene, in VPX space. - /// - public float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield); - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs.meta deleted file mode 100644 index 7fcfa17d4..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IKinematicColliderComponent.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 953c3c1b3d4c4aea9d41b5043345c551 -timeCreated: 1699050846 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index ae12e73c6..f994fbc05 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -52,10 +52,10 @@ public abstract class MainRenderableComponent : MainComponent, IMa .Select(c => (IMeshComponent) c) /*.Where(ma => ma.ItemData == _data)*/ : Array.Empty(); - private IEnumerable ColliderComponents => ColliderComponentType != null ? + private IEnumerable ColliderComponents => ColliderComponentType != null ? GetComponentsInChildren(ColliderComponentType, true) - .Select(c => (IColliderComponent) c) - /*.Where(ca => ca.ItemData == _data)*/ : Array.Empty(); + .Select(c => (ICollidableComponent) c) + /*.Where(ca => ca.ItemData == _data)*/ : Array.Empty(); public void RebuildMeshes() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs index 0a487e03f..033ecc71a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.VPT; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index 7a5e2db0a..b1d8ae794 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Plunger Collider")] - public class PlungerColliderComponent : ColliderComponent, IKinematicColliderComponent + public class PlungerColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index dd28c17e3..dba5462c1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Primitive Collider")] - public class PrimitiveColliderComponent : ColliderComponent, IKinematicColliderComponent + public class PrimitiveColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs index 13c3d7d8e..d652516ff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Ramp Collider")] - public class RampColliderComponent : ColliderComponent, IKinematicColliderComponent + public class RampColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 977b87216..ba5d54559 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Rubber Collider")] - public class RubberColliderComponent : ColliderComponent, IKinematicColliderComponent + public class RubberColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index 096872214..a54b2b669 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Spinner Collider")] - public class SpinnerColliderComponent : ColliderComponent, IKinematicColliderComponent + public class SpinnerColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/SubComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/SubComponent.cs index de9d2b98e..ee6e28458 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/SubComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/SubComponent.cs @@ -40,7 +40,7 @@ private TMainComponent FindMainComponent() if (ac != null) { return ac; } - if (this is IColliderComponent) { + if (this is ICollidableComponent) { // collider must be on the same game object return null; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 72388ef89..3c97bc593 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -23,7 +23,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Surface Collider")] - public class SurfaceColliderComponent : ColliderComponent, IKinematicColliderComponent + public class SurfaceColliderComponent : ColliderComponent { #region Data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index 875587dcc..1d78689b8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -24,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Collision/Trigger Collider")] - public class TriggerColliderComponent : ColliderComponent, IKinematicColliderComponent + public class TriggerColliderComponent : ColliderComponent { #region Data From 85a0901d3eac65cc809af6e42090abb4e12b41b1 Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 31 Dec 2024 00:50:30 +0100 Subject: [PATCH 177/208] colliders: Fix transformation for fully transformable colliders. --- .../VisualPinball.Unity/Game/PhysicsState.cs | 30 +++++++++++++++++-- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 9995d423f..463b7bb4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -28,9 +28,33 @@ internal struct PhysicsState internal PhysicsEnv Env; internal NativeOctree Octree; internal NativeColliders Colliders; + + /// + /// All kinematic colliders, with their transformation applied for fully transformable colliders, and without for the others. + /// + /// This is used for: + /// - The hit test in the narrow phase + /// - Contact resolution in each cycle + /// - + /// + /// internal NativeColliders KinematicColliders; + + /// + /// All kinematic colliders, without any transformation applied (for those fully transformable, the others aren't transformed anyway). + /// + /// It's set in PhysicsEngine.Start() through ColliderReference.TransformToIdentity() + /// + /// This is used for: + /// - Transform fully-transformable colliders in PhysicsUpdateJob.Execute() with PhysicsKinematics.TransformFullyTransformableColliders() + /// - Computing the AABBs for the octree in PhysicsUpdateJob.Execute() + /// internal NativeColliders KinematicCollidersAtIdentity; internal NativeParallelHashMap UpdatedKinematicTransforms; // updated transformations of the items, in vpx space. + + /// + /// + /// internal NativeParallelHashMap KinematicTransforms; // transformations of the items, in vpx space. internal NativeParallelHashMap NonTransformableColliderMatrices; internal NativeParallelHashMap KinematicColliderLookups; @@ -146,17 +170,17 @@ internal void Transform(int colliderId, float4x4 matrix) { switch (GetColliderType(ref KinematicColliders, colliderId)) { case ColliderType.Point: - var pointCollider = KinematicColliders.Point(colliderId); + ref var pointCollider = ref KinematicColliders.Point(colliderId); pointCollider.Transform(KinematicCollidersAtIdentity.Point(colliderId), matrix); break; case ColliderType.Line3D: - var line3DCollider = KinematicColliders.Line3D(colliderId); + ref var line3DCollider = ref KinematicColliders.Line3D(colliderId); line3DCollider.Transform(KinematicCollidersAtIdentity.Line3D(colliderId), matrix); break; case ColliderType.Triangle: - var triangleCollider = KinematicColliders.Triangle(colliderId); + ref var triangleCollider = ref KinematicColliders.Triangle(colliderId); triangleCollider.Transform(KinematicCollidersAtIdentity.Triangle(colliderId), matrix); break; } From 532d8d2dcd5eb39e362893a7ddf0d9b6a2a1319f Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 31 Dec 2024 00:50:56 +0100 Subject: [PATCH 178/208] style: Comments and naming. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 6 +++++- .../VisualPinball.Unity/Game/PhysicsKinematics.cs | 4 ++-- .../VisualPinball.Unity/Game/PhysicsUpdateJob.cs | 2 +- .../VisualPinball.Unity/Physics/NativeColliders.cs | 8 +++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 1cb73a368..bb79d7e6d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -84,12 +84,16 @@ public class PhysicsEngine : MonoBehaviour [NonSerialized] private readonly LazyInit> _updatedKinematicTransforms = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); /// - /// The current matrix to the ball will be transformed to, if it collides with a non-transformable collider. + /// The current matrix to which the ball will be transformed to, if it collides with a non-transformable collider. /// This changes as the non-transformable collider collider transforms (it's called non-transformable as in /// not transformable by the physics engine, but it can be transformed by the game). /// /// todo save inverse matrix, too /// + /// + /// This has nothing to do with kinematic transformations, it's purely to add full support for transformations + /// for items where the original physics engine doesn't. + /// [NonSerialized] private readonly LazyInit> _nonTransformableColliderMatrices = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs index f82e4fb60..27bd7251c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs @@ -27,7 +27,7 @@ public static class PhysicsKinematics private static readonly ProfilerMarker PerfMarkerTransform = new("TransformKinematicColliders"); private static readonly ProfilerMarker PerfMarkerBallOctree = new("CreateKinematicOctree"); - internal static void TransformKinematicColliders(ref PhysicsState state) + internal static void TransformFullyTransformableColliders(ref PhysicsState state) { PerfMarkerTransform.Begin(); using var enumerator = state.UpdatedKinematicTransforms.GetEnumerator(); @@ -49,7 +49,7 @@ internal static NativeOctree CreateOctree(ref PhysicsState state, in AABB p var octree = new NativeOctree(playfieldBounds, 1024, 10, Allocator.TempJob); for (var i = 0; i < state.KinematicCollidersAtIdentity.Length; i++) { - octree.Insert(i, state.KinematicCollidersAtIdentity.GetAabb(i, ref state.KinematicTransforms)); + octree.Insert(i, state.KinematicCollidersAtIdentity.GetTransformedAabb(i, ref state.KinematicTransforms)); } PerfMarkerBallOctree.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index 80160bb96..91784f30f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -72,7 +72,7 @@ public void Execute() using var cycle = new PhysicsCycle(Allocator.Temp); // create octree of kinematic-to-ball collision. should be okay here, since kinetic colliders don't transform more than once per frame. - PhysicsKinematics.TransformKinematicColliders(ref state); + PhysicsKinematics.TransformFullyTransformableColliders(ref state); var kineticOctree = PhysicsKinematics.CreateOctree(ref state, in PlayfieldBounds); while (env.CurPhysicsFrameTime < InitialTimeUsec) // loop here until current (real) time matches the physics (simulated) time diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs index 67376cc58..7fec87189 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs @@ -25,6 +25,9 @@ using Unity.Profiling; using VisualPinball.Engine.VPT; +// ReSharper disable ConvertToAutoPropertyWithPrivateSetter +// ReSharper disable ConvertToAutoProperty + namespace VisualPinball.Unity { [NativeContainer] @@ -475,7 +478,7 @@ public Aabb GetAabb(int index) } } - public Aabb GetAabb(int index, ref NativeParallelHashMap kinematicTransforms) + public Aabb GetTransformedAabb(int index, ref NativeParallelHashMap kinematicTransforms) { if (index < 0 || index >= m_Length) { throw new IndexOutOfRangeException($"Invalid index {index} when looking up collider."); @@ -527,8 +530,7 @@ public Aabb GetAabb(int index, ref NativeParallelHashMap kinemati var collider = UnsafeUtility.ArrayElementAsRef(m_TriangleColliderBuffer, lookup.Index); return collider.GetTransformedAabb(kinematicTransforms[collider.Header.ItemId]); } - case ColliderType.Plane: - { + case ColliderType.Plane: { // planes don't transform return UnsafeUtility.ArrayElementAsRef(m_PlaneColliderBuffer, lookup.Index).Bounds.Aabb; } From a8ccff8eb5dde2b66e4c2795371017d442e70c7a Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 31 Dec 2024 01:33:32 +0100 Subject: [PATCH 179/208] chore: Code doc and some renaming to stay consistent. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 12 +-- .../Game/PhysicsKinematics.cs | 3 +- .../VisualPinball.Unity/Game/PhysicsState.cs | 82 ++++++++++++++++--- .../Game/PhysicsStaticNarrowPhase.cs | 2 +- .../Game/PhysicsUpdateJob.cs | 4 +- .../Physics/Collider/ColliderReference.cs | 54 ++++++------ .../Physics/NativeColliders.cs | 6 +- .../VPT/ColliderComponent.cs | 18 ++-- 8 files changed, 121 insertions(+), 60 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index bb79d7e6d..68efd4c74 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -94,7 +94,7 @@ public class PhysicsEngine : MonoBehaviour /// This has nothing to do with kinematic transformations, it's purely to add full support for transformations /// for items where the original physics engine doesn't. /// - [NonSerialized] private readonly LazyInit> _nonTransformableColliderMatrices = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); + [NonSerialized] private readonly LazyInit> _nonTransformableColliderTransforms = new(() => new NativeParallelHashMap(0, Allocator.Persistent)); [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); [NonSerialized] private readonly Dictionary _rotatableComponent = new(); @@ -222,8 +222,8 @@ private void Start() var playfield = GetComponentInChildren(); Debug.Log($"Found {_colliderComponents.Length} collidable items ({_kinematicColliderComponents.Length} kinematic)."); - var colliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp); - var kinematicColliders = new ColliderReference(ref _nonTransformableColliderMatrices.Ref, Allocator.Temp, true); + var colliders = new ColliderReference(ref _nonTransformableColliderTransforms.Ref, Allocator.Temp); + var kinematicColliders = new ColliderReference(ref _nonTransformableColliderTransforms.Ref, Allocator.Temp, true); foreach (var colliderItem in _colliderComponents) { if (!colliderItem.IsCollidable) { _disabledCollisionItems.Ref.Add(colliderItem.ItemId); @@ -231,7 +231,7 @@ private void Start() var translateWithinPlayfieldMatrix = colliderItem.GetLocalToPlayfieldMatrixInVpx(playfield.transform.worldToLocalMatrix); // todo check if we cannot only add those that are actually non-transformable - _nonTransformableColliderMatrices.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; + _nonTransformableColliderTransforms.Ref[colliderItem.ItemId] = translateWithinPlayfieldMatrix; if (colliderItem.IsKinematic) { colliderItem.GetColliders(_player, this, ref kinematicColliders, translateWithinPlayfieldMatrix, 0); @@ -308,7 +308,7 @@ private void Update() KinematicColliderLookups = _kinematicColliderLookups, KinematicTransforms = _kinematicTransforms.Ref, UpdatedKinematicTransforms = _updatedKinematicTransforms.Ref, - NonTransformableColliderMatrices = _nonTransformableColliderMatrices.Ref, + NonTransformableColliderTransforms = _nonTransformableColliderTransforms.Ref, InsideOfs = _insideOfs, Events = events, Balls = _ballStates.Ref, @@ -330,7 +330,7 @@ private void Update() var env = _physicsEnv.Ref[0]; var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref _kinematicColliders, ref _kinematicCollidersAtIdentity, ref _kinematicTransforms.Ref, ref _updatedKinematicTransforms.Ref, - ref _nonTransformableColliderMatrices.Ref, ref _kinematicColliderLookups, ref events, + ref _nonTransformableColliderTransforms.Ref, ref _kinematicColliderLookups, ref events, ref _insideOfs, ref _ballStates.Ref, ref _bumperStates.Ref, ref _dropTargetStates.Ref, ref _flipperStates.Ref, ref _gateStates.Ref, ref _hitTargetStates.Ref, ref _kickerStates.Ref, ref _plungerStates.Ref, ref _spinnerStates.Ref, ref _surfaceStates.Ref, ref _triggerStates.Ref, ref _disabledCollisionItems.Ref, ref _swapBallCollisionHandling); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs index 27bd7251c..e320395ce 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsKinematics.cs @@ -16,7 +16,6 @@ using NativeTrees; using Unity.Collections; -using Unity.Mathematics; using Unity.Profiling; using VisualPinball.Unity.Collections; @@ -37,7 +36,7 @@ internal static void TransformFullyTransformableColliders(ref PhysicsState state ref var colliderLookups = ref state.KinematicColliderLookups.GetValueByRef(itemId); for (var i = 0; i < colliderLookups.Length; i++) { - state.Transform(colliderLookups[i], matrix); + state.TransformKinematicColliders(colliderLookups[i], matrix); } } PerfMarkerTransform.End(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs index 463b7bb4a..792ceaf72 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -14,7 +14,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System; using NativeTrees; using Unity.Collections; using Unity.Mathematics; @@ -26,16 +25,31 @@ namespace VisualPinball.Unity internal struct PhysicsState { internal PhysicsEnv Env; + + /// + /// Our static octree. + /// internal NativeOctree Octree; + + /// + /// All static colliders (the ones the original VPX physics engine supports). + /// internal NativeColliders Colliders; /// /// All kinematic colliders, with their transformation applied for fully transformable colliders, and without for the others. /// + /// Fully transformable colliders are updated at . + /// /// This is used for: - /// - The hit test in the narrow phase - /// - Contact resolution in each cycle - /// - + /// - The hit test in the narrow phase (like ) + /// - Contact resolution in each cycle (like ) + /// - Collision (like ) + /// + /// This basically adds another step to the existing static and dynamic simulation. The oct tree comes from + /// , and the narrow and collision phase is done with this. The mechanism + /// for non-transformable colliders is the same, the only difference is that in , + /// the ball-to-item-space matrix is calculated based off instead of . /// /// internal NativeColliders KinematicColliders; @@ -50,13 +64,42 @@ internal struct PhysicsState /// - Computing the AABBs for the octree in PhysicsUpdateJob.Execute() /// internal NativeColliders KinematicCollidersAtIdentity; - internal NativeParallelHashMap UpdatedKinematicTransforms; // updated transformations of the items, in vpx space. /// + /// Maps an item ID to the updated transformation matrix since the last frame of all kinematic items. + /// int is the of the collider. + /// float4x4 Updated LocalToPlayfieldMatrixInVpx of the item. + /// + internal NativeParallelHashMap UpdatedKinematicTransforms; + + /// + /// The LocalToPlayfieldMatrixInVpx of all kinematic colliders, fully transformable or not. + /// + /// + /// int is the of the collider. + /// float4x4 LocalToPlayfieldMatrixInVpx of the item. + /// + internal NativeParallelHashMap KinematicTransforms; + + /// + /// The LocalToPlayfieldMatrixInVpx of all colliders that aren't fully transformable. /// + /// This map is updated when the colliders get added at the beginning of the game with ColliderReference.Add(). + /// + /// + /// int is the of the collider. + /// float4x4 LocalToPlayfieldMatrixInVpx of the item. + /// + private readonly NativeParallelHashMap _nonTransformableColliderTransforms; + + /// + /// Maps an item ID to a list of collider IDs that reference this item, for all kinematic items. /// - internal NativeParallelHashMap KinematicTransforms; // transformations of the items, in vpx space. - internal NativeParallelHashMap NonTransformableColliderMatrices; + /// + /// Created by . + /// int is the of the collider. + /// NativeColliderIds IDs of all colliders of the item referenced by `ItemId`. + /// internal NativeParallelHashMap KinematicColliderLookups; internal NativeQueue.ParallelWriter EventQueue; @@ -79,7 +122,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native ref NativeColliders kinematicColliders, ref NativeColliders kinematicCollidersAtIdentity, ref NativeParallelHashMap kinematicTransforms, ref NativeParallelHashMap updatedKinematicTransforms, - ref NativeParallelHashMap nonTransformableColliderMatrices, + ref NativeParallelHashMap nonTransformableColliderTransforms, ref NativeParallelHashMap kinematicColliderLookups, ref NativeQueue.ParallelWriter eventQueue, ref InsideOfs insideOfs, ref NativeParallelHashMap balls, ref NativeParallelHashMap bumperStates, ref NativeParallelHashMap dropTargetStates, @@ -96,7 +139,7 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native KinematicCollidersAtIdentity = kinematicCollidersAtIdentity; KinematicTransforms = kinematicTransforms; UpdatedKinematicTransforms = updatedKinematicTransforms; - NonTransformableColliderMatrices = nonTransformableColliderMatrices; + _nonTransformableColliderTransforms = nonTransformableColliderTransforms; KinematicColliderLookups = kinematicColliderLookups; EventQueue = eventQueue; InsideOfs = insideOfs; @@ -150,13 +193,28 @@ public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref Native #region Transform + /// + /// Returns the matrix of a collider that cannot be transformed, i.e. the ball has to be projected into the + /// collider's space. + /// + /// + /// Depending on whether the collider is kinematic or not, the matrix is taken from either + /// the or . + /// + /// Basically, this is the magic that makes kinematic transformations work for non-transformable + /// items: Instead of projecting the ball with a static matrix, we'll just use the one of the + /// kinematic colliders, which is updated every frame. + /// + /// ID of the collider + /// Collider references + /// Transformation matrix internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref NativeColliders colliders) { var itemId = colliders.GetItemId(colliderId); - if (colliders.KinematicColliders) { + if (colliders.IsKinematic) { return ref KinematicTransforms.GetValueByRef(itemId); } - return ref NonTransformableColliderMatrices.GetValueByRef(itemId); + return ref _nonTransformableColliderTransforms.GetValueByRef(itemId); } /// @@ -166,7 +224,7 @@ internal ref float4x4 GetNonTransformableColliderMatrix(int colliderId, ref Nati /// /// The ID of the collider /// The transformation matrix - internal void Transform(int colliderId, float4x4 matrix) + internal void TransformKinematicColliders(int colliderId, float4x4 matrix) { switch (GetColliderType(ref KinematicColliders, colliderId)) { case ColliderType.Point: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs index 8fcee1216..88213b0e0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs @@ -63,7 +63,7 @@ ref PhysicsState state newTime = state.HitTest(ref colliders, overlappingColliderId, ref ball, ref newCollEvent, ref contacts); } - SaveCollisions(ref ball, ref newCollEvent, ref contacts, overlappingColliderId, newTime, colliders.KinematicColliders); + SaveCollisions(ref ball, ref newCollEvent, ref contacts, overlappingColliderId, newTime, colliders.IsKinematic); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs index 91784f30f..3891fc730 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -39,7 +39,7 @@ internal struct PhysicsUpdateJob : IJob public NativeColliders KinematicCollidersAtIdentity; public NativeParallelHashMap KinematicTransforms; public NativeParallelHashMap UpdatedKinematicTransforms; - public NativeParallelHashMap NonTransformableColliderMatrices; + public NativeParallelHashMap NonTransformableColliderTransforms; public NativeParallelHashMap KinematicColliderLookups; public InsideOfs InsideOfs; @@ -65,7 +65,7 @@ public void Execute() var env = PhysicsEnv[0]; var state = new PhysicsState(ref env, ref Octree, ref Colliders, ref KinematicColliders, ref KinematicCollidersAtIdentity, ref KinematicTransforms, ref UpdatedKinematicTransforms, - ref NonTransformableColliderMatrices, ref KinematicColliderLookups, ref Events, + ref NonTransformableColliderTransforms, ref KinematicColliderLookups, ref Events, ref InsideOfs, ref Balls, ref BumperStates, ref DropTargetStates, ref FlipperStates, ref GateStates, ref HitTargetStates, ref KickerStates, ref PlungerStates, ref SpinnerStates, ref SurfaceStates, ref TriggerStates, ref DisabledCollisionItems, ref SwapBallCollisionHandling); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs index 485129123..83d0cee71 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -45,11 +45,11 @@ public struct ColliderReference : IDisposable /// /// If true, then all colliders are kinematic. /// - public readonly bool KinematicColliders; // if set, populate _itemIdToColliderIds + public readonly bool IsKinematic; // if set, populate _itemIdToColliderIds private NativeParallelHashMap> _itemIdToColliderIds; - private NativeParallelHashMap _nonTransformableColliderMatrices; + private NativeParallelHashMap _nonTransformableColliderTransforms; - public ColliderReference(ref NativeParallelHashMap nonTransformableColliderMatrices, Allocator allocator, bool kinematicColliders = false) + public ColliderReference(ref NativeParallelHashMap nonTransformableColliderTransforms, Allocator allocator, bool isKinematic = false) { CircleColliders = new NativeList(allocator); FlipperColliders = new NativeList(allocator); @@ -66,9 +66,9 @@ public ColliderReference(ref NativeParallelHashMap nonTransformab Lookups = new NativeList(allocator); - KinematicColliders = kinematicColliders; + IsKinematic = isKinematic; _itemIdToColliderIds = new NativeParallelHashMap>(0, allocator); - _nonTransformableColliderMatrices = nonTransformableColliderMatrices; + _nonTransformableColliderTransforms = nonTransformableColliderTransforms; } public void Dispose() @@ -101,7 +101,7 @@ public void Dispose() public void TransformToIdentity(ref NativeParallelHashMap itemIdToTransformationMatrix) { #if UNITY_EDITOR - if (!KinematicColliders) { + if (!IsKinematic) { throw new InvalidOperationException("Cannot transform non-kinetic colliders to identity."); } #endif @@ -257,7 +257,7 @@ private ICollider LookupCollider(int i) private void TrackReference(int itemId, int colliderId) { #if !UNITY_EDITOR - if (!KinematicColliders) { + if (!IsKinematic) { return; } #endif @@ -270,14 +270,14 @@ private void TrackReference(int itemId, int colliderId) internal int Add(CircleCollider collider, float4x4 matrix) { - if (!KinematicColliders && CircleCollider.IsTransformable(matrix)) { + if (!IsKinematic && CircleCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; collider.TransformAabb(matrix); @@ -293,14 +293,14 @@ internal int Add(CircleCollider collider, float4x4 matrix) internal int Add(FlipperCollider collider, float4x4 matrix) { - if (!KinematicColliders && FlipperCollider.IsTransformable(matrix)) { + if (!IsKinematic && FlipperCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; collider.TransformAabb(matrix); @@ -315,14 +315,14 @@ internal int Add(FlipperCollider collider, float4x4 matrix) internal void Add(GateCollider collider, float4x4 matrix) { - if (!KinematicColliders && GateCollider.IsTransformable(matrix)) { + if (!IsKinematic && GateCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; @@ -348,14 +348,14 @@ internal void Add(Line3DCollider collider, float4x4 matrix) internal void Add(LineSlingshotCollider collider, float4x4 matrix) { - if (!KinematicColliders && LineSlingshotCollider.IsTransformable(matrix)) { + if (!IsKinematic && LineSlingshotCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; collider.TransformAabb(matrix); @@ -370,7 +370,7 @@ internal void Add(LineSlingshotCollider collider, float4x4 matrix) internal void Add(LineCollider collider) => Add(collider, float4x4.identity); // used for the playfield only internal void Add(LineCollider collider, float4x4 matrix) { - if (!KinematicColliders && LineCollider.IsTransformable(matrix)) { + if (!IsKinematic && LineCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); @@ -400,7 +400,7 @@ internal void Add(LineCollider collider, float4x4 matrix) internal void Add(LineZCollider collider, float4x4 matrix) { - if (KinematicColliders || !LineZCollider.IsTransformable(matrix)) { + if (IsKinematic || !LineZCollider.IsTransformable(matrix)) { // use line 3d collider instead Add(new Line3DCollider(new float3(collider.XY, collider.ZLow), new float3(collider.XY, collider.ZHigh), collider.Header.ColliderInfo), matrix); return; @@ -417,14 +417,14 @@ internal void Add(LineZCollider collider, float4x4 matrix) internal void Add(PlungerCollider collider, float4x4 matrix) { - if (!KinematicColliders && PlungerCollider.IsTransformable(matrix)) { + if (!IsKinematic && PlungerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; @@ -450,14 +450,14 @@ internal void Add(PointCollider collider, float4x4 matrix) internal void Add(SpinnerCollider collider, float4x4 matrix) { - if (!KinematicColliders && SpinnerCollider.IsTransformable(matrix)) { + if (!IsKinematic && SpinnerCollider.IsTransformable(matrix)) { collider.Header.IsTransformed = true; collider.Transform(matrix); } else { // save matrix for use during runtime - if (!_nonTransformableColliderMatrices.ContainsKey(collider.Header.ItemId)) { - _nonTransformableColliderMatrices.Add(collider.Header.ItemId, matrix); + if (!_nonTransformableColliderTransforms.ContainsKey(collider.Header.ItemId)) { + _nonTransformableColliderTransforms.Add(collider.Header.ItemId, matrix); } collider.Header.IsTransformed = false; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs index 7fec87189..15951ff34 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeColliders.cs @@ -41,7 +41,7 @@ public unsafe struct NativeColliders : IDisposable private static readonly ProfilerMarker PerfMarker = new("NativeColliders Allocation"); public int Length => m_Length; - public bool KinematicColliders => m_KinematicColliders; + public bool IsKinematic => m_IsKinematic; /// /// An array that links the collider IDs (the key) to the position in the respective collider buffer. @@ -62,7 +62,7 @@ public unsafe struct NativeColliders : IDisposable [NativeDisableUnsafePtrRestriction] private void* m_PlaneColliderBuffer; private readonly Allocator m_AllocatorLabel; - private readonly bool m_KinematicColliders; + private readonly bool m_IsKinematic; private int m_Length; // must be here, and called like that. @@ -88,7 +88,7 @@ public NativeColliders(ref ColliderReference colRef, Allocator allocator) PerfMarker.Begin(); m_Length = colRef.Lookups.Length; - m_KinematicColliders = colRef.KinematicColliders; + m_IsKinematic = colRef.IsKinematic; long size = UnsafeUtility.SizeOf() * colRef.Lookups.Length; m_LookupBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 0d846e387..b1bcc2878 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -126,6 +126,10 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa public bool _isKinematic; public bool IsKinematic => _isKinematic; + + /// + /// A unique identifier for this item, used in the physics engine to identify items. + /// public int ItemId => MainComponent.gameObject.GetInstanceID(); #endregion @@ -162,22 +166,22 @@ private void OnDrawGizmos() var worldToPlayfield = GetComponentInParent().transform.worldToLocalMatrix; var localToPlayfieldMatrixInVpx = GetLocalToPlayfieldMatrixInVpx(worldToPlayfield); var unmodifiedLocalToPlayfieldMatrixInVpx = GetUnmodifiedLocalToPlayfieldMatrixInVpx(worldToPlayfield); - var nonTransformableColliderMatrices = new NativeParallelHashMap(0, Allocator.Temp); + var nonTransformableColliderTransforms = new NativeParallelHashMap(0, Allocator.Temp); var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { if (Application.isPlaying) { InstantiateRuntimeColliders(showColliders); } else { - InstantiateEditorColliders(showColliders, ref nonTransformableColliderMatrices, localToPlayfieldMatrixInVpx); + InstantiateEditorColliders(showColliders, ref nonTransformableColliderTransforms, localToPlayfieldMatrixInVpx); } } if (ShowColliderOctree) { var api = InstantiateColliderApi(_player, null); - var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob); - var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.TempJob, true); + var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.TempJob); + var kinematicColliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.TempJob, true); try { if (IsKinematic) { api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); @@ -281,11 +285,11 @@ private void InstantiateRuntimeColliders(bool createMesh) // } } - private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashMap nonTransformableColliderMatrices, float4x4 localToPlayfieldMatrixInVpx) + private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashMap nonTransformableColliderTransforms, float4x4 localToPlayfieldMatrixInVpx) { var api = InstantiateColliderApi(_player, _physicsEngine); - var colliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp); - var kinematicColliders = new ColliderReference(ref nonTransformableColliderMatrices, Allocator.Temp, true); + var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp); + var kinematicColliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp, true); try { if (IsKinematic) { api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); From 60d098b0d2693cf48c3bb2e0c4c75d0e04f5c891 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 1 Jan 2025 01:28:43 +0100 Subject: [PATCH 180/208] kicker: Eject the ball at the correct position if movable. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 5 +++-- .../VPT/ColliderComponent.cs | 21 ++++++++++++------- .../VPT/ICollidableComponent.cs | 7 +++++++ .../VPT/Kicker/KickerColliderComponent.cs | 12 +++++++++++ 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index 68efd4c74..f8a5f8b67 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -181,11 +181,11 @@ internal void Register(T item) where T : MonoBehaviour internal Transform UnregisterBall(int ballId) { - var transform = _transforms[ballId]; + var t = _transforms[ballId]; _transforms.Remove(ballId); _ballStates.Ref.Remove(ballId); _insideOfs.SetOutsideOfAll(ballId); - return transform; + return t; } internal void EnableCollider(int itemId) @@ -291,6 +291,7 @@ private void Update() } _updatedKinematicTransforms.Ref.Add(coll.ItemId, currTransformationMatrix); _kinematicTransforms.Ref[coll.ItemId] = currTransformationMatrix; + coll.OnTransformationChanged(currTransformationMatrix); } // prepare job diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index b1bcc2878..214f73cf3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -65,6 +65,9 @@ public abstract class ColliderComponent : SubComponent public int ItemId => MainComponent.gameObject.GetInstanceID(); + public virtual void OnTransformationChanged(float4x4 currTransformationMatrix) + { + // do nothing per default. + } + #endregion #region Collider Gizmos #if UNITY_EDITOR - private PhysicsEngine _physicsEngine; private Player _player; private void OnDrawGizmos() @@ -256,18 +263,18 @@ private void OnDrawGizmos() private void InstantiateRuntimeColliders(bool createMesh) { - if (!_physicsEngine) { - _physicsEngine = GetComponentInParent(); // todo cache + if (!PhysicsEngine) { + PhysicsEngine = GetComponentInParent(); // todo cache } if (createMesh) { if (IsKinematic) { - var kinematicColliders = _physicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); + var kinematicColliders = PhysicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); _transformedColliderMesh = null; _untransformedColliderMesh = null; GenerateColliderMesh(kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); } else { - var colliders = _physicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); + var colliders = PhysicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); _transformedKinematicColliderMesh = null; _untransformedKinematicColliderMesh = null; GenerateColliderMesh(colliders, out _transformedColliderMesh, out _untransformedColliderMesh); @@ -287,7 +294,7 @@ private void InstantiateRuntimeColliders(bool createMesh) private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashMap nonTransformableColliderTransforms, float4x4 localToPlayfieldMatrixInVpx) { - var api = InstantiateColliderApi(_player, _physicsEngine); + var api = InstantiateColliderApi(_player, PhysicsEngine); var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp); var kinematicColliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp, true); try { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 59d1e1a5b..dde804ab3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -55,6 +55,13 @@ internal void GetColliders(Player player, PhysicsEngine physicsEngine, ref Colli /// public float4x4 GetLocalToPlayfieldMatrixInVpx(float4x4 worldToPlayfield); + /// + /// Executed on kinematic colliders, when the transformation has changed. This allows updating data if necessary, + /// for example the kicker center, which is relevant when spawning balls. + /// + /// + public void OnTransformationChanged(float4x4 currTransformationMatrix); + public bool CollidersDirty { set; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index 340178027..d108637f9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -49,8 +49,20 @@ public class KickerColliderComponent : ColliderComponent(); + } + public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(scatterAngleDeg: Scatter); protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => MainComponent.KickerApi ?? new KickerApi(gameObject, player, physicsEngine); + + public override void OnTransformationChanged(float4x4 currTransformationMatrix) + { + // update kicker center, so the internal collision shape is correct + ref var kickerData = ref PhysicsEngine.KickerState(ItemId); + kickerData.Static.Center = currTransformationMatrix.c3.xy; + } } } From 90caafcbd055b857c06798f05af84d55e8588e78 Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 1 Jan 2025 01:32:07 +0100 Subject: [PATCH 181/208] kicker: Fix z-position when moving during runtime. --- .../VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index d108637f9..cd4e7a5ca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -63,6 +63,7 @@ public override void OnTransformationChanged(float4x4 currTransformationMatrix) // update kicker center, so the internal collision shape is correct ref var kickerData = ref PhysicsEngine.KickerState(ItemId); kickerData.Static.Center = currTransformationMatrix.c3.xy; + kickerData.Static.ZLow = currTransformationMatrix.c3.z; } } } From 42173ce0dccce4b10143588756893bc2011c48e7 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 2 Jan 2025 22:08:45 +0100 Subject: [PATCH 182/208] kicker: Fix frozen ball position when kicker is movable. --- .../Game/PhysicsStaticCollision.cs | 15 +++++++------ .../VPT/Ball/BallMovementPhysics.cs | 21 +++++++------------ .../VPT/Kicker/KickerApi.cs | 2 +- .../VPT/Kicker/KickerCollider.cs | 11 +++++----- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs index 11165c115..1f486d6d0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -16,7 +16,6 @@ // ReSharper disable ConvertIfStatementToSwitchStatement -using System; using Unity.Mathematics; using VisualPinball.Engine.VPT; using VisualPinball.Unity.Collections; @@ -68,13 +67,13 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r TransformBallFromColliderSpace(ref colliders, ref ball, ref state, colliderId); return; } - switch (state.GetColliderType(ref colliders, colliderId)) { - case ColliderType.Circle: + case ColliderType.Circle: { ref var circleCollider = ref colliders.Circle(colliderId); circleCollider.Collide(ref ball, in ball.CollisionEvent, ref state.Env.Random); break; + } case ColliderType.Plane: ref var planeCollider = ref colliders.Plane(colliderId); @@ -148,11 +147,15 @@ private static void Collide(ref NativeColliders colliders, ref BallState ball, r TriggerCollide(ref ball, ref state, in collHeader, ref colliders); break; - case ColliderType.KickerCircle: + case ColliderType.KickerCircle: { ref var kickerState = ref state.GetKickerState(colliderId, ref colliders); - KickerCollider.Collide(ref ball, ref state.EventQueue, ref state.InsideOfs, ref kickerState.Collision, - in kickerState.Static, in kickerState.CollisionMesh, in ball.CollisionEvent, collHeader.ItemId, false); + ref var circleCollider = ref colliders.Circle(colliderId); + KickerCollider.Collide(new float3(circleCollider.Center, circleCollider.ZLow), ref ball, ref state.EventQueue, ref state.InsideOfs, + ref kickerState.Collision, + in kickerState.Static, in kickerState.CollisionMesh, in ball.CollisionEvent, collHeader.ItemId, + false); break; + } } TransformBallFromColliderSpace(ref colliders, ref ball, ref state, colliderId); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs index 205962853..317368f7b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs @@ -42,18 +42,18 @@ public static void Move(BallState ball, Transform ballTransform) 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 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); + // 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 + ")"); - + // 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); - - Quaternion q = Quaternion.LookRotation(VPZ, VPY); + Vector3.OrthoNormalize(ref vpZ, ref vpY, ref vpX); + + Quaternion q = Quaternion.LookRotation(vpZ, vpY); // flip Z axis q = FlipZAxis(q); @@ -68,7 +68,6 @@ static Quaternion FlipZAxis(Quaternion q) /* * I let these two in here, just in case we need them. - static float3x3 transpose(float3x3 or) { float3x3 or2; @@ -83,7 +82,6 @@ static float3x3 transpose(float3x3 or) 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 @@ -97,10 +95,7 @@ static Quaternion QuaternionFromMatrix(Matrix4x4 m) q.z *= Mathf.Sign(q.z * (m[1, 0] - m[0, 1])); return q; } - */ - - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 80b1fbd6f..4e93ea53e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -79,7 +79,7 @@ public void CreateBall(GameObject ballPrefab = null, float radius = 25f, float m var events = PhysicsEngine.EventQueue; ball.CollisionEvent.HitFlag = true; // HACK: avoid capture leaving kicker - KickerCollider.Collide(ref ball, ref events, ref PhysicsEngine.InsideOfs, ref kickerState.Collision, + KickerCollider.Collide(new float3(kickerState.Static.Center, kickerState.Static.ZLow), ref ball, ref events, ref PhysicsEngine.InsideOfs, ref kickerState.Collision, in kickerState.Static, in kickerState.CollisionMesh, in ball.CollisionEvent, ItemId, true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs index e9cc0b8ec..b098aaf98 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs @@ -16,6 +16,7 @@ using Unity.Collections; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; @@ -30,7 +31,7 @@ internal static class KickerCollider /// public const bool ForceLegacyMode = false; - public static void Collide(ref BallState ball, ref NativeQueue.ParallelWriter events, + public static void Collide(float3 position, ref BallState ball, ref NativeQueue.ParallelWriter events, ref InsideOfs insideOfs, ref KickerCollisionState collState, in KickerStaticState staticState, in ColliderMeshData meshData, in CollisionEventData collEvent, in int itemId, bool newBall) { @@ -102,11 +103,11 @@ public static void Collide(ref BallState ball, ref NativeQueue.Parall ball.Velocity = float3.zero; ball.AngularMomentum = float3.zero; var posZ = !staticState.FallIn - ? staticState.ZLow + ball.Radius * 2 + ? position.z + ball.Radius * 2 : staticState.FallThrough - ? staticState.ZLow - ball.Radius - 5.0f - : staticState.ZLow + ball.Radius; - ball.Position = new float3(staticState.Center.x, staticState.Center.y, posZ); + ? position.z - ball.Radius - 5.0f + : position.z + ball.Radius; + ball.Position = new float3(position.x, position.y, posZ); } else { collState.BallId = 0; // make sure From 715788466d7bfd80a55c983d72e76b686c2d9d68 Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Fri, 3 Jan 2025 09:56:16 -0500 Subject: [PATCH 183/208] ci: attempt to fix macos builds --- .github/workflows/build.yml | 18 +++++++++--------- .github/workflows/dependents.yml | 8 ++++---- .github/workflows/documentation.yml | 2 +- .github/workflows/pr.yml | 16 ++++++++-------- .github/workflows/publish.yml | 17 ++++++++--------- .github/workflows/release.yml | 4 ++-- 6 files changed, 32 insertions(+), 33 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2fe646e43..99524fa4d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,8 +29,8 @@ jobs: - os: ubuntu-latest rid: android-arm64-v8a steps: - - uses: actions/checkout@v3 - - uses: actions/setup-dotnet@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 with: dotnet-version: '3.1.x' - name: Build @@ -40,9 +40,9 @@ jobs: - run: | mkdir tmp cp -r VisualPinball.Unity/Plugins/${{ matrix.rid }} tmp - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: Plugins + name: Plugins-${{ matrix.rid }} path: tmp # test: @@ -50,12 +50,12 @@ jobs: # needs: [ build ] # runs-on: ubuntu-latest # steps: -# - uses: actions/checkout@v3 -# - uses: actions/download-artifact@v3 +# - uses: actions/checkout@v4 +# - uses: actions/download-artifact@v4 # with: # name: Plugins # path: VisualPinball.Unity/Plugins - #- uses: actions/cache@v3 + #- uses: actions/cache@v4 # with: # path: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Library # key: Library-Test-Project @@ -77,7 +77,7 @@ jobs: # with: # path: ${{ steps.test.outputs.artifactsPath }}/*.xml # access-token: ${{ secrets.GITHUB_TOKEN }} -# - uses: actions/upload-artifact@v3 +# - uses: actions/upload-artifact@v4 # if: always() # with: # name: Test results @@ -89,7 +89,7 @@ jobs: needs: [ build ] if: github.repository == 'freezy/VisualPinball.Engine' && github.ref == 'refs/heads/master' steps: - - uses: peter-evans/repository-dispatch@v1 + - uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.GH_PAT }} event-type: build-complete diff --git a/.github/workflows/dependents.yml b/.github/workflows/dependents.yml index a17386833..b180167cc 100644 --- a/.github/workflows/dependents.yml +++ b/.github/workflows/dependents.yml @@ -7,11 +7,11 @@ jobs: VisualPinball-Unity-Hdrp: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: VisualPinball.Engine - name: Checkout VisualPinball.Unity.Hdrp - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: VisualPinball/VisualPinball.Unity.Hdrp path: VisualPinball.Unity.Hdrp @@ -35,11 +35,11 @@ jobs: VisualPinball-Unity-Urp: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: path: VisualPinball.Engine - name: Checkout VisualPinball.Unity.Urp - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: VisualPinball/VisualPinball.Unity.Urp path: VisualPinball.Unity.Urp diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 839250628..2ce411ecc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -7,7 +7,7 @@ jobs: if: github.repository == 'freezy/VisualPinball.Engine' && github.ref == 'refs/heads/master' name: Build and publish documentation steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: nunit/docfx-action@v2.10.0 name: Build Documentation with: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2d51af094..64bfcd4a1 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -32,10 +32,10 @@ jobs: - os: ubuntu-latest rid: android-arm64-v8a steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v4 with: dotnet-version: '3.1.x' - name: Build @@ -45,9 +45,9 @@ jobs: - run: | mkdir tmp cp -r VisualPinball.Unity/Plugins/${{ matrix.rid }} tmp - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: - name: Plugins + name: Plugins-${{ matrix.rid }} path: tmp test: @@ -55,14 +55,14 @@ jobs: needs: [ build ] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: Plugins path: VisualPinball.Unity/Plugins - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Library key: Library-Test-Project @@ -83,7 +83,7 @@ jobs: with: path: ${{ steps.test.outputs.artifactsPath }}/*.xml access-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: Test results diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d433696bf..0bb0d59d0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,11 +7,10 @@ jobs: publish-registry: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: dawidd6/action-download-artifact@v2 + - uses: actions/checkout@v4 + - uses: actions/download-artifact@v4 with: - workflow: build - run_id: ${{ github.event.client_payload.artifacts_run_id }} + run-id: ${{ github.event.client_payload.artifacts_run_id }} name: Plugins path: VisualPinball.Unity/Plugins - run: | @@ -35,8 +34,8 @@ jobs: publish-nuget: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-dotnet@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 with: dotnet-version: '3.1.x' - name: Pack @@ -46,14 +45,14 @@ jobs: dotnet pack VisualPinball.Engine/VisualPinball.Engine.csproj -c Release -p:PackageVersion=$VERSION -o nupkg - name: Publish run: | - dotnet nuget push nupkg/VisualPinball.Resources.*.nupkg -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json - dotnet nuget push nupkg/VisualPinball.Engine.*.nupkg -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json + dotnet nuget push nupkg/VisualPinball.Resources.*.nupkg -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v4/index.json + dotnet nuget push nupkg/VisualPinball.Engine.*.nupkg -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v4/index.json dispatch: runs-on: ubuntu-latest needs: [ publish-registry, publish-nuget ] steps: - - uses: peter-evans/repository-dispatch@v1 + - uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.GH_PAT }} event-type: publish-complete diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a79c4f52b..5aa997184 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,7 +7,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Fetch next version @@ -46,7 +46,7 @@ jobs: runs-on: ubuntu-latest needs: [ release ] steps: - - uses: peter-evans/repository-dispatch@v1 + - uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.GH_PAT }} event-type: release-complete From 0b934be845ce4693cb92dce2b0920f6f81813b02 Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 3 Jan 2025 15:16:49 +0100 Subject: [PATCH 184/208] doc: First draft of units and space. --- .../creators-guide/editor/units-3d-space.md | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md new file mode 100644 index 000000000..53011f20e --- /dev/null +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md @@ -0,0 +1,85 @@ +--- +uid: units_3d_space +title: Units and 3D Space +description: VPE supports real-world units and free 3D transformations. +--- + +# Units and 3D Space + +Units describe how me measure things. For example, the units for measuring length are meters in the metric system, or feet and inches in the imperial system. There are also different systems for saving 3D data. For example, in VPX, the x-axis points to right side of the player, the y-axis towards the player, and the z-axis upwards. We call this the [orientation and handedness](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions) of the coordinate system. + +Different software use different units and orientations, and this section describes how VPE handles them. We'll also compare how VPX and VPE handle transformations. + +## Units + +IF you've already used VPX, you know that it uses its own units, defined by the ball radius: + +> In VPX, 50 units correspond to the ball diameter. + +A ball being 1 1/16 inch, we get the following relations: + +- 50 VP units = 1.0625" +- 1 VP unit = 0.02125" = 0.53975mm + +or + +- 1" = 47.05882352941176 VP units +- 1mm = 1.852709587772117 VP units + +Obviously, no other 3D software uses these units, so importing models from let's say Blender has always been a pain. + +The problem is that if we wanted to just scale everything down to meters, it would have an impact on the physics, because physics are strongly dependent on the real world size (and thus, mass) of things. Given that VPE uses VPX's physics code, which has been fine-tuned with heuristics based on VPX units, we cannot simply scale everything to real-world units and expect the same behavior in the physics simulation. + +So, we've chosen the following approach: + +> - Everything in the scene uses real-world units (meters). +> - During runtime, 3D data is converted to VPX units for the physics simulation. + +> [!note] +> ### VPX Units in the Editor +> +> Meters where a table is under 2m long and under a meter large isn't necessarily the best unit either. The best would have been millimeters, but Unity's units aren't configurable. +> +> Because of this and the fact that many table authors are still familiar with VPX units, VPE includes VPX units in the panel of each component. They can be changed in real time and will update the transformation of the object. + + +## Orientation and Handedness + +As mentioned at the beginning, the coordinate system of VPX is oriented with the Z-axis up and the origin at the top-left corner of the table. It's a [left-handed](https://en.wikipedia.org/wiki/Right-hand_rule) coordinate system. + +Blender, on the other hand (no pun intended), also has the Z-axis pointing up, but its Y-axis is pointing into the opposite direction, making is right-handed. + +Unity's coordinate system is left-handed like VPX, but oriented differently. Since the player is usually looking forward, that's where the XY-plane lies. So, Y points upwards, X to the right, and Z away from the player. + +The main impact for you as a table author is that you need to pay attention when exporting your meshes from other 3D software. For example, when exporting to FBX in Blender, you need to make sure that following mapping is set (the default *Forward* being *-Z Forward*): + +> - Forward -> Z-Forward +> - Up -> Y-Up + +## Transformations + +When we move, rotate or scale an object, we call it a *transformation*. Let's talk about how VPX and VPE handle transformations. + +In VPX, the XY-position within the playfield can be freely set for all items. For the Z-position, some objects allow free positioning, some can be parented to a surface (wall or ramp), and others have a fixed Z-position. + +In terms of rotation, some items like spinners or gates can be Z-rotated, some can be freely rotated, and some can't be rotated at all. + +Scaling support in VPX is more sparse, with most objects not being able to scale at all, or only on a given axis. + +> [!note] +> This is not to look down on VPX's transformation capabilities, they make sense in the vast majority of cases, but it's important to highlight them so we can understand how they differ from VPE. + +For quite a while, VPE implemented the same restrictions as VPX, and we spent a considerable amount of time overriding Unity's transformation tools to adhere to those limitations. + +But as a user, you could always work around them, often accidentally, by either disabling gizmos in editor, or by simply parenting an item to another object and freely transforming the parent. IF that happened, the result was a rather incoherent mess, because the visuals wouldn't correspond to the physics simulation, which was still bound by those limits. + +So, we ended up implementing full transformation support for VPE. This means that you can freely position, rotate and scale all items. You can also parent items to other objects and transform the other objects. Or the parents of those objects. In short, however the transformation hierarchy of your scene is, VPE will boil it down to one transformation during runtime, check for each item whether the physics engine supports the resulting transformation, and if not, apply the ball projection trick during collision. + +## Runtime Transformations + +In VPX, transformations are static. That means during runtime, they cannot be changed. Obviously, a flipper rotates during runtime, but the object itself is fixed in place. Some games however require some form of movement, and in VPX that's worked around by creating multiple invisible collider objects that are then toggled depending on the position of the object. + +This is of course very cumbersome and error prone. In VPE, we've extended the physics engine to be able to mark objects are movable. If an object is marked as such, it can be fully transformed during gameplay and the colliders will update accordingly. + +> [!note] +> Note however that currently, moving objects don't have a velocity, meaning that hitting a moving object only takes into account the speed of the ball, not the object. It's like the object is teleported frame by frame to its new position. From bfa6b9e6ee4df1c2044ec2a5bb7ffbbfa032c2b1 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 4 Jan 2025 15:31:24 +0100 Subject: [PATCH 185/208] doc: Spell check. --- .../creators-guide/editor/units-3d-space.md | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md index 53011f20e..6c15c6494 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md @@ -6,13 +6,13 @@ description: VPE supports real-world units and free 3D transformations. # Units and 3D Space -Units describe how me measure things. For example, the units for measuring length are meters in the metric system, or feet and inches in the imperial system. There are also different systems for saving 3D data. For example, in VPX, the x-axis points to right side of the player, the y-axis towards the player, and the z-axis upwards. We call this the [orientation and handedness](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions) of the coordinate system. +Units describe how we measure things. For example, the units for measuring length are meters in the metric system, or feet and inches in the imperial system. There are also different systems for saving 3D data. For example, in VPX, the x-axis points to the right side of the player, the y-axis towards the player, and the z-axis upwards. We call this the [orientation and handedness](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions) of the coordinate system. -Different software use different units and orientations, and this section describes how VPE handles them. We'll also compare how VPX and VPE handle transformations. +Different software uses different units and orientations, and this section describes how VPE handles them. We'll also compare how VPX and VPE handle transformations. ## Units -IF you've already used VPX, you know that it uses its own units, defined by the ball radius: +If you've already used VPX, you know that it uses its own units, defined by the ball radius: > In VPX, 50 units correspond to the ball diameter. @@ -26,9 +26,9 @@ or - 1" = 47.05882352941176 VP units - 1mm = 1.852709587772117 VP units -Obviously, no other 3D software uses these units, so importing models from let's say Blender has always been a pain. +Obviously, no other 3D software uses these units, so importing models from, let's say, Blender has always been a pain. -The problem is that if we wanted to just scale everything down to meters, it would have an impact on the physics, because physics are strongly dependent on the real world size (and thus, mass) of things. Given that VPE uses VPX's physics code, which has been fine-tuned with heuristics based on VPX units, we cannot simply scale everything to real-world units and expect the same behavior in the physics simulation. +The problem is that if we wanted to just scale everything down to meters, it would impact physics because physics is strongly dependent on the real-world size (and thus, mass) of things. Given that VPE uses VPX's physics code, which has been fine-tuned with heuristics based on VPX units, we cannot simply scale everything to real-world units and expect the same behavior in the physics simulation. So, we've chosen the following approach: @@ -37,49 +37,49 @@ So, we've chosen the following approach: > [!note] > ### VPX Units in the Editor -> -> Meters where a table is under 2m long and under a meter large isn't necessarily the best unit either. The best would have been millimeters, but Unity's units aren't configurable. -> -> Because of this and the fact that many table authors are still familiar with VPX units, VPE includes VPX units in the panel of each component. They can be changed in real time and will update the transformation of the object. +> +> Meters where a table is under 1x2 meters large isn't necessarily the best unit either. The best would have been millimeters, but Unity's units aren't configurable. +> +> Because of this and the fact that many table authors are still familiar with VPX units, VPE includes VPX units in the panel of each component. They can be changed in real-time and will update the transformation of the object. ## Orientation and Handedness As mentioned at the beginning, the coordinate system of VPX is oriented with the Z-axis up and the origin at the top-left corner of the table. It's a [left-handed](https://en.wikipedia.org/wiki/Right-hand_rule) coordinate system. -Blender, on the other hand (no pun intended), also has the Z-axis pointing up, but its Y-axis is pointing into the opposite direction, making is right-handed. +Blender, on the other hand (no pun intended), also has the Z-axis pointing up, but its Y-axis is pointing in the opposite direction, making it right-handed. -Unity's coordinate system is left-handed like VPX, but oriented differently. Since the player is usually looking forward, that's where the XY-plane lies. So, Y points upwards, X to the right, and Z away from the player. +Unity's coordinate system is left-handed like VPX, but oriented differently. Since the player is usually looking forward, that's where the XY plane lies. So, Y points upwards, X to the right, and Z away from the player. -The main impact for you as a table author is that you need to pay attention when exporting your meshes from other 3D software. For example, when exporting to FBX in Blender, you need to make sure that following mapping is set (the default *Forward* being *-Z Forward*): +The main impact for you as a table author is that you need to pay attention when exporting your meshes from other 3D software. For example, when exporting to FBX in Blender, you need to make sure that the following mapping is set (the default *Forward* being *-Z Forward*): > - Forward -> Z-Forward > - Up -> Y-Up ## Transformations -When we move, rotate or scale an object, we call it a *transformation*. Let's talk about how VPX and VPE handle transformations. +We call it a *transformation* when we move, rotate, or scale an object. Let's talk about how VPX and VPE handle transformations. -In VPX, the XY-position within the playfield can be freely set for all items. For the Z-position, some objects allow free positioning, some can be parented to a surface (wall or ramp), and others have a fixed Z-position. +In VPX, the XY position within the playfield can be freely set for all items. For the Z-position, some objects allow free positioning, some can be parented to a surface (wall or ramp), and others have a fixed Z-position. -In terms of rotation, some items like spinners or gates can be Z-rotated, some can be freely rotated, and some can't be rotated at all. +Regarding rotation, some items, like spinners or gates, can be Z-rotated, some can be freely rotated, and some can't be rotated at all. -Scaling support in VPX is more sparse, with most objects not being able to scale at all, or only on a given axis. +Scaling support in VPX is more sparse, with most objects not being able to scale at all or only on a given axis. > [!note] -> This is not to look down on VPX's transformation capabilities, they make sense in the vast majority of cases, but it's important to highlight them so we can understand how they differ from VPE. +> This is not to look down on VPX's transformation capabilities; they make sense in the vast majority of cases, but it's important to highlight them so we can understand how they differ from VPE. -For quite a while, VPE implemented the same restrictions as VPX, and we spent a considerable amount of time overriding Unity's transformation tools to adhere to those limitations. +For quite a while, VPE implemented the same restrictions as VPX, and we spent considerable time overriding Unity's transformation tools to adhere to those limitations. -But as a user, you could always work around them, often accidentally, by either disabling gizmos in editor, or by simply parenting an item to another object and freely transforming the parent. IF that happened, the result was a rather incoherent mess, because the visuals wouldn't correspond to the physics simulation, which was still bound by those limits. +However, as a user, you could always work around them, often accidentally, by either disabling gizmos in the editor or by simply parenting an item to another object and freely transforming the parent. If that happened, the result would be a rather incoherent mess because the visuals wouldn't correspond to the physics simulation, which was still bound by those limits. -So, we ended up implementing full transformation support for VPE. This means that you can freely position, rotate and scale all items. You can also parent items to other objects and transform the other objects. Or the parents of those objects. In short, however the transformation hierarchy of your scene is, VPE will boil it down to one transformation during runtime, check for each item whether the physics engine supports the resulting transformation, and if not, apply the ball projection trick during collision. +So, we ended up implementing full transformation support for VPE. That means you can freely position, rotate, and scale all items. You can also parent items to other objects and transform those objects. Or the parents of those objects. In short, however the transformation hierarchy of your scene is, VPE will boil it down to one transformation during runtime, check for each item whether the physics engine supports the resulting transformation, and if not, apply the ball projection trick during a collision. ## Runtime Transformations -In VPX, transformations are static. That means during runtime, they cannot be changed. Obviously, a flipper rotates during runtime, but the object itself is fixed in place. Some games however require some form of movement, and in VPX that's worked around by creating multiple invisible collider objects that are then toggled depending on the position of the object. +In VPX, transformations are static. That means they cannot be changed during runtime. Obviously, a flipper rotates during runtime, but the object itself is fixed in place. Some games, however, require some form of movement. In VPX, that's worked around by creating multiple invisible collider objects that are then toggled depending on the position of the object. -This is of course very cumbersome and error prone. In VPE, we've extended the physics engine to be able to mark objects are movable. If an object is marked as such, it can be fully transformed during gameplay and the colliders will update accordingly. +That's, of course, very cumbersome and error-prone. In VPE, we've extended the physics engine to be able to mark objects are movable. If an object is marked as such, it can be fully transformed during gameplay, and the colliders are updated accordingly. > [!note] -> Note however that currently, moving objects don't have a velocity, meaning that hitting a moving object only takes into account the speed of the ball, not the object. It's like the object is teleported frame by frame to its new position. +> Note that currently, moving objects don't have a velocity, meaning that hitting a moving object only takes into account the speed of the ball, not the object. It's like the object is teleported frame by frame to its new position. From 11202ee1236cb3ffa4bad69cf99cc8229d7c197b Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 4 Jan 2025 16:03:04 +0100 Subject: [PATCH 186/208] doc: More improvements. --- .../creators-guide/editor/units-3d-space.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md index 6c15c6494..f8c13af03 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md @@ -12,7 +12,7 @@ Different software uses different units and orientations, and this section descr ## Units -If you've already used VPX, you know that it uses its own units, defined by the ball radius: +If you've already used VPX, you know that it uses its own units, defined by the ball size: > In VPX, 50 units correspond to the ball diameter. @@ -34,6 +34,7 @@ So, we've chosen the following approach: > - Everything in the scene uses real-world units (meters). > - During runtime, 3D data is converted to VPX units for the physics simulation. +> - New movement data from the physics engine is converted back to real-world units. > [!note] > ### VPX Units in the Editor @@ -56,6 +57,8 @@ The main impact for you as a table author is that you need to pay attention when > - Forward -> Z-Forward > - Up -> Y-Up +TODO screenshot + ## Transformations We call it a *transformation* when we move, rotate, or scale an object. Let's talk about how VPX and VPE handle transformations. @@ -71,9 +74,9 @@ Scaling support in VPX is more sparse, with most objects not being able to scale For quite a while, VPE implemented the same restrictions as VPX, and we spent considerable time overriding Unity's transformation tools to adhere to those limitations. -However, as a user, you could always work around them, often accidentally, by either disabling gizmos in the editor or by simply parenting an item to another object and freely transforming the parent. If that happened, the result would be a rather incoherent mess because the visuals wouldn't correspond to the physics simulation, which was still bound by those limits. +However, as a user, you could always work around them, often accidentally, by either disabling gizmos in the editor or by simply parenting an item to another object and freely transforming the parent (the child always inherits transformation of its parent). If that happened, the result would be a rather incoherent mess because the visuals wouldn't correspond to the physics simulation, which was still bound by those limits. -So, we ended up implementing full transformation support for VPE. That means you can freely position, rotate, and scale all items. You can also parent items to other objects and transform those objects. Or the parents of those objects. In short, however the transformation hierarchy of your scene is, VPE will boil it down to one transformation during runtime, check for each item whether the physics engine supports the resulting transformation, and if not, apply the ball projection trick during a collision. +So, we ended up implementing full transformation support for VPE. That means you can freely position, rotate, and scale all items. You can also parent items to other objects and transform those objects. Or the parents of those objects. In short, however the transformation hierarchy of your scene is, VPE will boil it down to one transformation during runtime, check for each item whether the physics engine supports the resulting transformation, and if not, apply the [ball projection trick](https://github.com/freezy/VisualPinball.Engine/tree/master/VisualPinball.Unity/VisualPinball.Unity/Physics#unrestricted-transformations) during a collision. ## Runtime Transformations @@ -82,4 +85,6 @@ In VPX, transformations are static. That means they cannot be changed during run That's, of course, very cumbersome and error-prone. In VPE, we've extended the physics engine to be able to mark objects are movable. If an object is marked as such, it can be fully transformed during gameplay, and the colliders are updated accordingly. > [!note] -> Note that currently, moving objects don't have a velocity, meaning that hitting a moving object only takes into account the speed of the ball, not the object. It's like the object is teleported frame by frame to its new position. +> Note that currently, moving objects don't have a velocity, meaning that hitting a moving object only takes into account the speed of the ball, not the object's. It's like the object is teleported frame by frame to its new position. +> +> True collision where the object's directional and angular speed is calculated based off the last frame's position and the current one is a feature that is valuable and on the roadmap. From 2dcccc91301a10a0fc238c5c4a6e68dae6967452 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 4 Jan 2025 17:15:05 +0100 Subject: [PATCH 187/208] ci: Bump docfx to latest and update master template. --- .github/workflows/documentation.yml | 2 +- .../template/vpe/layout/_master.tmpl | 128 +++++++++--------- 2 files changed, 62 insertions(+), 68 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 2ce411ecc..bc009e154 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -8,7 +8,7 @@ jobs: name: Build and publish documentation steps: - uses: actions/checkout@v4 - - uses: nunit/docfx-action@v2.10.0 + - uses: nunit/docfx-action@v3.4.2 name: Build Documentation with: args: VisualPinball.Unity/Documentation~/docfx.json diff --git a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl index f6bc52f5f..5488db831 100644 --- a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl +++ b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl @@ -2,7 +2,6 @@ {{!include(/^public/.*/)}} {{!include(favicon.ico)}} {{!include(logo.svg)}} -{{!include(search-stopwords.json)}} @@ -15,6 +14,7 @@ {{#_description}}{{/_description}} + {{#description}}{{/description}} @@ -37,46 +37,44 @@ - {{/redirect_url}} - - + - {{^redirect_url}} - + - + - {{#_googleAnalyticsTagId}} - - - {{/_googleAnalyticsTagId}} + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + {{/redirect_url}} + + + {{^redirect_url}}
+ {{^_disableNavbar}} + {{/_disableNavbar}}
-
+
+ {{^_disableToc}}
@@ -115,68 +115,62 @@
+ {{/_disableToc}}
- + {{/_disableBreadcrumb}} +
{{!body}}
+ {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} + {{^_disableNextArticle}} {{/_disableNextArticle}} - + + {{^_disableAffix}}
- - {{^_disableContribution}} -
- {{#sourceurl}} - {{__global.improveThisDoc}} - {{/sourceurl}} - {{^sourceurl}}{{#docurl}} - {{__global.improveThisDoc}} - {{/docurl}}{{/sourceurl}} -
- {{/_disableContribution}}
+ {{/_disableAffix}}
{{#_enableSearch}} -
+
{{/_enableSearch}} -
-
+
+
{{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}}
- - - {{/redirect_url}} From 375c560c44d4638cd8d0ec1977daf1d8521cecb5 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 4 Jan 2025 17:15:24 +0100 Subject: [PATCH 188/208] doc: Add new pages to TOC. --- .../Documentation~/creators-guide/editor/units-3d-space.md | 2 +- VisualPinball.Unity/Documentation~/creators-guide/toc.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md index f8c13af03..b8d4f9f74 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md @@ -16,7 +16,7 @@ If you've already used VPX, you know that it uses its own units, defined by the > In VPX, 50 units correspond to the ball diameter. -A ball being 1 1/16 inch, we get the following relations: +A ball being 1¹⁄₁₆ inch, we get the following relations: - 50 VP units = 1.0625" - 1 VP unit = 0.02125" = 0.53975mm diff --git a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml index b6d1f7c04..dd2086181 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml +++ b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml @@ -14,10 +14,14 @@ - name: Editor items: + - name: Units and 3D Space + href: editor/units-3d-space.md - name: Unity Components href: editor/unity-components.md - name: Materials href: editor/materials.md + - name: Asset Library + href: editor/asset-library.md - name: Switch Manager href: editor/switch-manager.md - name: Coil Manager From f54642e6b69a9eacaf209b71ede3f08c77894804 Mon Sep 17 00:00:00 2001 From: freezy Date: Sun, 5 Jan 2025 00:25:40 +0100 Subject: [PATCH 189/208] kicker: Move ball along kicker if movable. --- .../VisualPinball.Unity/Game/PhysicsEngine.cs | 5 +++++ .../VisualPinball.Unity/Game/PhysicsMovements.cs | 3 +++ .../VisualPinball.Unity/VPT/Kicker/KickerApi.cs | 4 ++++ .../VPT/Kicker/KickerColliderComponent.cs | 6 ++++++ 4 files changed, 18 insertions(+) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs index f8a5f8b67..1002afb62 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -141,6 +141,9 @@ public void ScheduleAction(uint timeoutMs, Action action) internal ref SurfaceState SurfaceState(int itemId) => ref _surfaceStates.Ref.GetValueByRef(itemId); internal ref TriggerState TriggerState(int itemId) => ref _triggerStates.Ref.GetValueByRef(itemId); internal void SetBallInsideOf(int ballId, int itemId) => _insideOfs.SetInsideOf(itemId, ballId); + internal bool HasBallsInsideOf(int itemId) => _insideOfs.GetInsideCount(itemId) > 0; + internal List GetBallsInsideOf(int itemId) => _insideOfs.GetIdsOfBallsInsideItem(itemId); + internal uint TimeMsec => _physicsEnv.Ref[0].TimeMsec; internal Random Random => _physicsEnv.Ref[0].Random; internal void Register(T item) where T : MonoBehaviour @@ -201,6 +204,8 @@ internal void DisableCollider(int itemId) } } + internal Transform GetTransform(int itemId) => _transforms[itemId]; + #endregion #region Event Functions diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs index ed28c3079..7805f0096 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsMovements.cs @@ -30,6 +30,9 @@ internal void ApplyBallMovement(ref PhysicsState state, Dictionary().transform, true); } else { Hit?.Invoke(this, new HitEventArgs(ballId)); Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); OnSwitch(true); + BallMovementPhysics.Move(PhysicsEngine.BallState(ballId), ballTransform); // do the last update, since frozen balls don't get updated + ballTransform.SetParent(MainComponent.transform, true); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index cd4e7a5ca..e1cb12cdc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -64,6 +64,12 @@ public override void OnTransformationChanged(float4x4 currTransformationMatrix) ref var kickerData = ref PhysicsEngine.KickerState(ItemId); kickerData.Static.Center = currTransformationMatrix.c3.xy; kickerData.Static.ZLow = currTransformationMatrix.c3.z; + if (PhysicsEngine.HasBallsInsideOf(ItemId)) { + foreach (var ballId in PhysicsEngine.GetBallsInsideOf(ItemId)) { + ref var ball = ref PhysicsEngine.BallState(ballId); + ball.Position = currTransformationMatrix.c3.xyz; + } + } } } } From 43cdffe3c2fb6db1b20a8ed2110d8e3826eda340 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 00:25:59 +0100 Subject: [PATCH 190/208] doc: Update year. --- VisualPinball.Unity/Documentation~/docfx.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VisualPinball.Unity/Documentation~/docfx.json b/VisualPinball.Unity/Documentation~/docfx.json index c5d29bcf6..9f0efa589 100644 --- a/VisualPinball.Unity/Documentation~/docfx.json +++ b/VisualPinball.Unity/Documentation~/docfx.json @@ -42,7 +42,7 @@ "postProcessors": [ "ExtractSearchIndex" ], "globalMetadata": { "_appTitle": "VPE Documentation", - "_appFooter": "Copyright © 2023 VPE Team", + "_appFooter": "Copyright © 2025 VPE Team", "_appFaviconPath": "favicon.png", "_gitContribute": { "branch": "master" From 3c8a50f28901df6ffa9fb4accbb92159d2414da4 Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Mon, 6 Jan 2025 06:56:16 -0500 Subject: [PATCH 191/208] ci: add icu support to ubuntu runners --- .github/workflows/build.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99524fa4d..996aaf14d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,6 +29,10 @@ jobs: - os: ubuntu-latest rid: android-arm64-v8a steps: + - if: (matrix.os == 'ubuntu-latest') + name: Add icu support (ubuntu runner) + run: | + sudo apt-get install libicu-dev - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: From 60b18b475d4cb77fe8913cb25cf891e3961c375d Mon Sep 17 00:00:00 2001 From: Jason Millard Date: Mon, 6 Jan 2025 07:13:54 -0500 Subject: [PATCH 192/208] ci: temporarily switch from ubuntu-latest to ubuntu-22.04 --- .github/workflows/build.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 996aaf14d..01c3a12f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,15 +24,11 @@ jobs: rid: osx - os: macos-latest rid: ios-arm64 - - os: ubuntu-latest + - os: ubuntu-22.04 rid: linux-x64 - - os: ubuntu-latest + - os: ubuntu-22.04 rid: android-arm64-v8a steps: - - if: (matrix.os == 'ubuntu-latest') - name: Add icu support (ubuntu runner) - run: | - sudo apt-get install libicu-dev - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: From f1d17a27a33def760489da4882f6ac6939104640 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 13:39:49 +0100 Subject: [PATCH 193/208] doc: Update changelog. --- CHANGELOG.md | 1 + .../VisualPinball.Unity/Physics/README.md | 8 +- .../VPT/ISurfaceComponent.cs | 18 +- .../MetalWireGuideColliderComponent.cs | 1 - .../VPT/Trigger/TriggerAnimation.cs | 164 +++++++++--------- 5 files changed, 103 insertions(+), 89 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 684744feb..65d9ba82d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Built with Unity 2022.3.x ### Added +- Free transformation ([#500](https://github.com/freezy/VisualPinball.Engine/pull/500)) - Kinematic collisions ([#460](https://github.com/freezy/VisualPinball.Engine/pull/460)) - Flipper tricks by nFozzy ([#436](https://github.com/freezy/VisualPinball.Engine/pull/436)) - Asset Library now has thumbnails. diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md index 2520216ab..d53f60534 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/README.md @@ -178,10 +178,8 @@ approach is the following: result in a rectangle parallel and orthogonal to the playfield, which wouldn't be desired. - But more on that problem later. What's important is that for transformations - *supported by the VPX physics code*, we have a method that allows to transform - each collider, based on a matrix. -- Additionally, colliders are instantiated without a transformation matrix. That means - by default, they are placed at the origin and have no rotation or scale. + *supported by the VPX physics code*, we have a method that allows to transform each collider, based on a matrix. +- Additionally, colliders are always instantiated without any transformation. That means by default, they are placed at the origin and have no rotation or scale. - Finally, each collider gets a `TransformAABBs(float4x4)` method that only transforms the collider's axis-aligned bounding boxes. @@ -193,7 +191,7 @@ So, with all of the above, we do the following when the game starts: 3. We check which kind of transformation the VPX physics code supports for the type of collider and compare it to the transformation of playfield-to-local matrix. - If all transformations are supported, we simply transform the collider with the item's - transformation matrix. + transformation matrix. - If not, we check whether this collider might be replaceable by another type of collider that supports the transformation. For example, a line collider can be replaced by two triangle colliders, which then are 100% transformable. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs index 67c31d768..85820fc59 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ISurfaceComponent.cs @@ -1,4 +1,20 @@ -using UnityEngine; +// 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 . + +using UnityEngine; namespace VisualPinball.Unity { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs index 284d20121..d1cce7147 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs @@ -16,7 +16,6 @@ // ReSharper disable InconsistentNaming -using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.MetalWireGuide; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs index 1a60991e3..ad52cc461 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs @@ -1,82 +1,82 @@ -// 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 . - -using VisualPinball.Engine.VPT; - -namespace VisualPinball.Unity -{ - internal static class TriggerAnimation - { - internal static void Update(ref TriggerAnimationState animation, ref TriggerMovementState movement, in TriggerStaticState staticState, - float dTimeMs) - { - // var oldTimeMsec = animation.TimeMsec < dTimeMs ? animation.TimeMsec : dTimeMs; - // animation.TimeMsec = dTimeMs; - // var diffTimeMsec = dTimeMs - oldTimeMsec; - - var animLimit = staticState.Shape == TriggerShape.TriggerStar ? staticState.Radius * (float)(1.0 / 5.0) : 32.0f; - if (staticState.Shape == TriggerShape.TriggerButton) { - animLimit = staticState.Radius * (float)(1.0 / 10.0); - } - if (staticState.Shape == TriggerShape.TriggerWireC) { - animLimit = 60.0f; - } - if (staticState.Shape == TriggerShape.TriggerWireD) { - animLimit = 25.0f; - } - - var limit = animLimit * staticState.TableScaleZ; - - if (animation.HitEvent) { - animation.DoAnimation = true; - animation.HitEvent = false; - // unhitEvent = false; // Bugfix: If HitEvent and unhitEvent happen at the same time, you want to favor the unhit, otherwise the switch gets stuck down. - movement.HeightOffset = 0.0f; - animation.MoveDown = true; - } - if (animation.UnHitEvent) { - animation.DoAnimation = true; - animation.UnHitEvent = false; - animation.HitEvent = false; - //movement.HeightOffset = limit; - animation.MoveDown = false; - } - - if (animation.DoAnimation) { - var step = dTimeMs * staticState.AnimSpeed * staticState.TableScaleZ; - if (animation.MoveDown) { - step = -step; - } - movement.HeightOffset += step; - - if (animation.MoveDown) { - if (movement.HeightOffset <= -limit) { - movement.HeightOffset = -limit; - animation.DoAnimation = false; - animation.MoveDown = false; - } - - } else { - if (movement.HeightOffset >= 0.0f) { - movement.HeightOffset = 0.0f; - animation.DoAnimation = false; - animation.MoveDown = true; - } - } - } - } - } -} +// 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 . + +using VisualPinball.Engine.VPT; + +namespace VisualPinball.Unity +{ + internal static class TriggerAnimation + { + internal static void Update(ref TriggerAnimationState animation, ref TriggerMovementState movement, in TriggerStaticState staticState, + float dTimeMs) + { + // var oldTimeMsec = animation.TimeMsec < dTimeMs ? animation.TimeMsec : dTimeMs; + // animation.TimeMsec = dTimeMs; + // var diffTimeMsec = dTimeMs - oldTimeMsec; + + var animLimit = staticState.Shape == TriggerShape.TriggerStar ? staticState.Radius * (float)(1.0 / 5.0) : 32.0f; + if (staticState.Shape == TriggerShape.TriggerButton) { + animLimit = staticState.Radius * (float)(1.0 / 10.0); + } + if (staticState.Shape == TriggerShape.TriggerWireC) { + animLimit = 60.0f; + } + if (staticState.Shape == TriggerShape.TriggerWireD) { + animLimit = 25.0f; + } + + var limit = animLimit * staticState.TableScaleZ; + + if (animation.HitEvent) { + animation.DoAnimation = true; + animation.HitEvent = false; + // unhitEvent = false; // Bugfix: If HitEvent and unhitEvent happen at the same time, you want to favor the unhit, otherwise the switch gets stuck down. + movement.HeightOffset = 0.0f; + animation.MoveDown = true; + } + if (animation.UnHitEvent) { + animation.DoAnimation = true; + animation.UnHitEvent = false; + animation.HitEvent = false; + //movement.HeightOffset = limit; + animation.MoveDown = false; + } + + if (animation.DoAnimation) { + var step = dTimeMs * staticState.AnimSpeed * staticState.TableScaleZ; + if (animation.MoveDown) { + step = -step; + } + movement.HeightOffset += step; + + if (animation.MoveDown) { + if (movement.HeightOffset <= -limit) { + movement.HeightOffset = -limit; + animation.DoAnimation = false; + animation.MoveDown = false; + } + + } else { + if (movement.HeightOffset >= 0.0f) { + movement.HeightOffset = 0.0f; + animation.DoAnimation = false; + animation.MoveDown = true; + } + } + } + } + } +} From 422f77e9b0a1dacd793fd27ee1d63ce2ea21acfc Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 21:56:53 +0100 Subject: [PATCH 194/208] doc: Fix lightbox. --- .../Documentation~/template/vpe/layout/_master.tmpl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl index 5488db831..10fcf194c 100644 --- a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl +++ b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl @@ -171,6 +171,18 @@
+ + {{/redirect_url}} From 5bc47c904d335d26eb69a2c8bfc7897646cc76fb Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 21:57:13 +0100 Subject: [PATCH 195/208] doc: Add images for units and space. --- .../creators-guide/editor/blender-export.png | Bin 0 -> 9231 bytes .../creators-guide/editor/editor-units.png | Bin 0 -> 9602 bytes .../creators-guide/editor/units-3d-space.md | 19 ++++++++++-------- 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/blender-export.png create mode 100644 VisualPinball.Unity/Documentation~/creators-guide/editor/editor-units.png diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/blender-export.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/blender-export.png new file mode 100644 index 0000000000000000000000000000000000000000..1562e2cc48a25fbbf6e99387241e732a59946fa4 GIT binary patch literal 9231 zcmaKSbx<79vo7uuoS+H1*aq9+L4xZ73ybWc!Ce*&65IkI!Gi~vB{(eZ!5xBz;10oq zFsprN57pN0({=F^kG z5%|p0!U3!fQQ+q01^@v3{QM9IL`q6(2n~~!m6e^HUCF(6czF2X;em&TXLED&#fuk= zjEoHp4FdxMe0+TL^z>?KYXAQI`|{%Pb*RNmG($ac+dj$mreSCaMN=gzF6Bia1%FE01^Yd3%S3^QV>gwuDO-;MH zx@Ko*6A}{i^z^c_vewqtl$DilZf;IaPEaV+^768Ug@vrFY*<*BzrTN6TwHW?w7a`I z2m}fW3YwgpY;JB22ncX=bX;Fwx3jY|G&EFDP-ty!t*os4`}g+p@=`)VVtaer+S+<| zcQ+y;VtjnOsj2Dw{5&To2L^+=xVVUmi;s?u8XFrcxqX*rWA8=BIy^ko@@cS*?ed_e zb)tRVJGCPR^{K+e-#xxs-#Z^&ILK@K8IeEwiIgmxkhrXU`F+NqX?UBQQGlGGp9MVw z3JX_<`Gq+HV? zE_{ZHy8sD(i3les<$)-}^W^pksGdw|s!)0tu5wci4pWg-$Y7Fs)iBW2$Y5)LUG2e0 zpC1ZqfDC%7SE;@19E8`?ZpD#P4!r zMZG+#)S?t3wMThi?P8cL34@LsH(!G0SGDd-;@yjfhb%|oX*R>z>Dh?t^@OCAODe4& zVj>5Bld+r)=xL&8j2l3y{lgZs7h^6q+`bhMZ$g7gJujLOKb>SrgVb$59vj(AW9KZB zlag=CdltT}`B*=bn;mHjJ`7tF5cS?fSq+z2#C&SjPxz4Nfu~o2$qVi=xHL-dQvYF@ z_>46{Dwfs|mg+sib%nHUZZC}Hzs{RoWNHMJ9boduYJU{OG0bedQKqM5#gD8Oe*Jfs z!HRm5p|6H_z6HH#LPL^aWOKB-+l*f1hERo96|TFhb5!RsN#@`XBz?zh{0-&k7S3nw z;zDKIWPYiX6f^kqr8r?Eo#lONW=}$yA$qKLikpXPw(ZS@1JI~m^Lr;`kWwq6l>54V z+BslwE)6)@?-ylKxjfHqQj!*<&;n#`S0(X8oT~1A*GaSK^2LNN$)zqPp^9cejec7d zE+^L~VHx7I+nohYkslW<=(`SLo_k4sGW z^F(a=2)=Z@SASFi(4dnvOvj?2`Y(LH#-q7FD!<(So&D&hioM$_(BgB}GE}e#M9MO^ zL=7};*DJ3OIa`iu?m!4l^qH!rU51Qi!=a5HHR2UK^|`Nvb*m{&wO1y`0FyI__~?W< zfm}+;@)&&W<4acEH(1yf!;ZP-G0pGkve^8OznV{JGLjzD@Z2_xF1^$KrT)>g-&#>8 zwK`kKIXtw9?MuJ*@beEq>+N1W6{>3DR~0;l(@X6Q8d5H#-X`I|Y9}(* zY&SQ<9vmtRu^2|mD8^m!9`wjpmBYXT!w$Y(<15!4&~C_K{WJMN&~b?2ht6D!`jOZo z0S})>)G7bkW*@?7zc0!5)uq>eLcRhv2}`Kgsim0PySAvs-yxz2yI|wb-^Pg#!hu?O(KQ8FXs${#vzXuD1k8SW`?$%|- zosFRMq2WM(MDosC@7RCw$P3FZ7mQJyDPQ69xyt ztd7EN#=sEz1h>f<`f{+%Tyhzk{oQSg5liB_VUXp(XX*C&UlJ~t4@HdhdvW9%@T4*5 zzDfBrq2Qg5nchaqf1%ESj67_QAI%PV0ScUp-wn?))Fgj_CE?=iG>R?xx<1ig8t94bo+4>+VHcbccnaj zPYsTR8|7@$mlM9Y_>mkR@>gj-Pwr9eZBx{}G|>X1hNUVb*!JHE8>gH@>;cREM`|l) z2!?7{F54oQ-AE=Wd{UW@@4qP>Ek74Vz*@rrNn2|WB+4fmmj5xI{u)=?1}MmUHfHxc z>P>pddnw-{!)E~op7wHG4llU9-~LaN%`m{}F%QgublDAcYk_&|nH8XVLFG4j6;(Kq z!R!I(?Q{Z15*FO>L&|EY!PJs9VW;1myXMqPvZ-hKLEZoijf2v)wYRAkR5z+2cE8AU z_v|;Yw`B|PsWOZB?y(jHeTm?3sjAeD0*CA5y%0$+Kg1;ye408CL-jYA=VZ+)Z<}@@ zPyylIPws{3#`!fq{ioG8LzuU=MSmltf95TLbTqL#Lk3}dx&%ZteN@*Y=#Fkj*8IqQ zu)=EL0W~9H{a)c$yHYAD#KFa$+4JA<5WSO=_g} zH^c5C7d%F9N`Qxw34EjCX!c^YqOne=+e1N{C+$aZ^-ItC(c437jGdGD`pO*@Rd3D% z3;c&2cPFQ6iKHd8qrPv$b;UV~Z=vW;s`e)RrX;Bne?&_y{3SU*Q2t3Xmh9vWkojF~ z2HZj^OFps|p?g)^_HH;))S~ItZMY#iaIg3BrKA*zVI%4@h`j3`BO?PjS6(=OeX6l$ zq{MU51z>Ft>nJ3}L+)c{hR(cZsbVm4qoImN4e*JQ+FUsHpgoQdrar#(A(AqXZr6yq zij#q$d2L29_aF?TI?f+)bRQP>+3>;aGjN~Jnn*4#B)e^N!6}^11}CSr{G`1O7~w)5 zVC>>Py%*xpiiV@2etiw7cHRfZ*Q?x5dtRj&JRrh*NkXJX2#5 zld_8*h?1Bs5pdI9-G?p2-*yaR-Tf)`!`hd?qcYlB$yAc9cA#wrc1b-zvgCBS2{_Cf zDs8-*&xT}t&F`H9bkZR8@fMJ!VGwHvR-^72%#IjG7yH$({3o{V$e+S{(q>`ST{8vP zEaEih@0lkrA-rI{z%{lzltB990ZY9Ohfn8V$dtOa#Rx>eHvx$VFjt(I$-+k|+f`y_ zMzm|-`g*I>BSf5^)%=C;lD9WimLie`Q~-s&u7ASDETsC=QhF?Dhh(uigE~qrD46tb z>|iwTZkC)a>F0t2dw|?+aA-7Yx73+JwzWXSPEQ)g?WPKeSkdeAxXV^>sZ|<}SCw3F z>04zlYU8KgHnWFwTE_?DU7)4z@=}jp=rPjZ5$)uLe8G@luXyHT@VnRzZg%-yU;Qf- zM#GS+%iG}kL33_aQZSnNhTQNa4XSpJ83(04uXHb--*EzGdhpa-dmWTpkpFMF{9!X{ zc1z3OoHxygh9ON&SebE}z1F8@^347mM{gH=#UuKG1@dkWlTbPlTL{0CR1VrK|Doh7 zpOBjm!1MXM)VRkESJr!>W$|@6?7C)sma@Aqd(0)$o~{MB47zfSBYlm|Be`g@$Av_y zmFugEt9@6KzClR;@EN+<26!Wvf>#~U);z=Lq9__$aUTYu1b4tul*+^o#>BU9O zFN1&8sS3|tEpN%j#PSw}8`6M-Uz|}cH$82eid!tu z5Tk%W`lLF-croO{k@as^-?>}kMy#5jtt#xoQ4&NEw-bSpG5nuHXQY2i)UbPMXyn>T z{+ev#_EiD5lYM989e=&=BP$d5^P)$MFjp>uGIl-RABTe1GkHhOMI!3_gU%03D9b8Vo&MFlADO~B(uqiNu$_w*h2mjgRmLtoDKu9?{q`cAa? z4z-$*OkYeNA=#hDrIndd$A_l>B>bZYyu0TT?+|RIJe=P775MD_n3#U%$F%kNu`KhKL%J(-7$;>%Ydg=Y&pAjM=e01pYixp` z&U=Un+kNxR&%d4cmtpf?-d`29Dx0*d5oCfl#Pc#cAck&#_r8J9frjL3pSjV2rGhH3 zPlXS4vc7(1>DFD?P!q@OR=;N~B|PQD_~R;Hk9tHC>E;DB+C+4NsF9dZ*w0Zz^M2!( zAyb6bnq=8blIHZ5A5$6TRnnAL$9wA!L)07zj4bU~uGT<3bHF^C-}X0zPlMl=RQLtuBaiTfV?*@`N-oOglfH zaA@mF>EU>WIQ7R%&}!m<0$9u|s4@FT4G~&qHQ#D-g$YRkIG+XEbz!A!)jGgSh_VSs zvjoNUg`2&BRR5B2O_k+VRd4%(f#?a%;<-LWT~hqP3YnXpLDJec7C@X(#$h&t{xI6a zJE#qshaxeq4nVv5L%E8g$ADtDO%r9Z&03gIDdSndd#ZL$5b_llSbeeqqqy$3r@|is zj631V@Sd-?woOQ4M!cV{f5ap4VtSNq%{9uOrv{&k=e)KyB+SwQn0(MqIglci=$~bC z6UzeYc3QE6T;)J^xPJU&co?s3m7p0>xh1^v&&nMh|W<-|$ zOYfGaoQ4|hA|WsI{350 zFkN99GJ?b)AW<(uC9gMk3xC~RJg-P1u`v_;UBwt9B18iP1$=rNz^W$FwH-rFWgi5# z#Db~ilvm-MigKJqk&q>IDsUf4QnRYf9tF&Gz0?5|O7B`vHHA^wql9|eDF`y^Sj}9$ zNC%18!q@MpsFI{%+V8FEt)h{?rinpvT}3t>Yc zl337Qb0u}ozKh1|>?*K{=*9zq#pYfxRb^)R<-@mVn(Uhxn=k!HdJM`TGqSA zq2Aa$m(p$v3SbPHPguhgy&7J#ifApALuE3cerE~+);Av!P^_ZR0~r@WSP=!cz9HMG z@sAI?b!oR(o-j(#SWqxm^P7)&vXSgVFN4-s*vCji2wj@h|g@2g6Iu<*s`SaU%H9wL{NXCvv0iMTm#M zqu>|M&urVd-%y}OcNrt~?1l!}pa}LKAjr;U7tegM z3vOu}iT#cztS9(7f11;@%2KtU;YXLcg3mrM`n}^=hY#j|A2ky{Nk#LCHO7-pU;tqvWrUGUq~-zBUJW!8tJ)Vo=QcKp@%HQx}P~8WuigP zvPJ9%b@bm_QNk1{9bYz?j#d4lrV-w3qBc@GxKKR5Zvz)u)wn99p8?_>lnB4R=A?m< z5GTFGwu;|wqOM>4;8-V_poT8yB8+ti=1s=ZlLZ!pu>`=zgTC^* zeuzuNr>WTcM+lw7b8T}ByE*Mxo9eESst>I8c$Ct3t4({{Rb-PrMZ8IZ8bynLsrjr@ zT*QWjT-I|$6Xwbifu;N+nB(#GvAXeFQ$5uu;vUpF+jknj-#MI4Co4y*Of%Sc@cppv z)fOk;EO26Oj(A?a%s)>#h0ylBuo#?&DSRrj+d>L%LHTA2gxTyN5#aI-&H>gEZZRPxCGBu^23c27x@!N z%IF9S^=QHXyxi)ko<~1h-oS5PM@LV;mkr_WGUc!< zOP|Pem`*On1}h$~#*S^MFiw;r-@a^bILOGYYp%$|-~dp}AR>!8r^738vw7{&=##){ z@rWb*4&aT9<=-jKYfp5H-FRS@R+T0qz|Aqhp@UX4bvrKMqQ*CYuFbdxx7N=;1@%Hj z2*psVN&gbHc{=Fif1C}xbdmWGmdhTvUnm4OWi(bg(k1p$u&cD#ey2WhyErl+88ER_ zQ+@zZrxRK;z|2@&M?FQW_{iGl4{OphC5fg|ryy)3>yw<$+lOWe# zbkEl3zO=nAC1}soc1`5is;g?k|Ba#s$!{R=;qXs+zF2bzGPndANkJjz%U-0*3ic}i zu{dNG1w`mAyO%0He=qh84e3M*c&_@})YB?6N4WfF95@ea7?lPTqc2$bnRtFCwLK4# z-+}?JgL}(nKwLzJoLFpKXV(e?7ls-=42G zV$+pl;X<@Q8F1{a9LJ}yB?WcCC77A&+Rm>xSZ_yADDl}FAneK_5bhdj?V{YTuC||n zxLdY;9$>gXRWTKiA*(eX1&1QitpTGbl!VZ|xkLkrr7T=6Y4Vx2(3x-yzP%*<9CXJP z+ek=#N=S+5O~FglwhCIM>cO|3ThA%SI2oDwv~&y;4pSeN4iCU#RxVPY+4?hj?FB6b zgwJF=*;XfXj!31ZNan+zjPH-?V{XeVwT{Up0(sa7JA_2?*7?Eu0kVy`IvAKHXp@)T zf7qImJ6Y;i{Ye?BAr-^*W)G`OAse8U-d-r&4Lxdr)_$?wa8=V z++b>vH8wF6lkb+!&(G+@-n6__A}?E035YgsuvY;8Oqb2zD=XPponO+KP*CA2*uK_P z#NMq?ZOpSpHTG}U>k6Az?v1bi1%*35sws3G{^6bn=o@qv=9ljWo=#>woZ4gh&f~u$ zZL*``xPf=7S<4VxtyQ>lz4AXD-R(eoc4YwQG~Ch=ydym;Zg|B!5poJTA_&!fm2dXH znfu@O+xDGV;5IC8xl8h8{8?RBPKZ`l7Fep!Fbixxs`-1DP`?LR%#Aubc`q2PjOT6; zd2afgoV{iIjfbZnC&Z^#S5&X8l9X7-Y3o;jY*B`4;l9O>zmK$I4^WrV*4C*LRA8g` zv8f7{Hbcj}xP;9ystK^qlKx6rd9V|&S0pcm?w!HA1m^pH|DwGNBKqwdzV+-?j}((E zdmam4Ingm4DLn}h7<(^M$K1tlk;|XzuCaY1z(QQZlsr1@^1zMCh+?B?8_!#gn3)DR ztrROT{P%DrG*9-zIA@BQew`(s&3Uf>`13bB&LpPPwi(ApZma4K?&Bs6_Iq&;d_XLj zxu0rfD8P4b8i&gAOR3#@NLk~;O3MoQ_5k})l#8!FSqFk!uC$8)qnwfvt9EX=(b6r? zBaA{d!J%|E%TG$h7tPh~Jj}oTF-Mfi=ZlTXl43u2R-wyqX+w)(=3=-&?Tdu7TBfso zBU9=xlTvBGMx&+z0AzqZ<_xwsH>cmK7})p(eZq?+guAs1BURTo5+2UCgO>wgW0(p1 z2R4Cb-k#cNm+=(%oN6|~JW1Bkdn;bQHJ{*Y$Bpn~0}dczI-yj#$tCj)k;K+VaXmtP z|H+l6*jJus=LNO+k56LDEx}A9VkQ;j@Z=+jeSqf`L&V`H60(EYJ2^tV_vQyHey}Ru zBCr2Slh$bckZ$BQ%_Oc%jQV%j)TpLH)z#YC8$oryug1nL#>NtLVWCRi6l$DsKZ|k{ zAgB4^<8>Bf)y2+ey(a}xEWwL(!UBuy<;|F3cfNMOo5)EHD7bWs=9$5#vi6eOsaw+q zNs!W*#9&;7YQNW}#gp$GF5x&0uj!Cyvp+ zm^+EC0?puo8r`o}-(i8iy4B(uWKpC$^jp66OSCtP90XiBWV7G<@VW2}5eS|{yT!)4 zkgvriE1)Rg1;~m4tJ+a^V1ccPiS!>e_<7^29u?cAN;9%%j8gW$gW(YHUummK%>osP zB)Oxf4|vEWv_yUORbPN>Xm!$idC|NGC8jqKc#0=sbWFZCX-zD&%@q6BvNp@BpnF|; ziM$9-kdJ-B zwoexk)O$VyKnVid8*trNyYbmOuZ2xZwpiA0MI^PZMktwkljC!I08fS=v#+yAFS@HB z3tx<4n_N@ooCLNRSK_A5*(9_89#c-Q-gNvd)l-wcw*f*o1%WxqKb@-ZI3c`hTO8mjs83a0sWvECQ}CnXFrNCCE|eyW4hbG`}1BIV>SIC~BVYJW;bm(n+0gyN#S*;i_P6N=UcNX;S=c@T6~c zM6|>4XWWXz6Vq$8Yn*lY0*W$~=dt*l?jN=(7bwa=z%S1ZYXqA;NzoN|ed$<`?^d^d zor~)K=M{rqG34c&m0zeQOO=AgbnJ-HnHzN33IE%1g!1Iv&Dq&_?ikn`g~v&W^rfeT zoL7bv4rM3Nd1$pD4h!9W$nXn}e>o&NcKszm>E@eCGX7-SMGm>zFARy>k+@@oR*BkU`K z&Q8j5nU3FxKg5SZt5t_Z^ByJQD2DzoKGI5=qE8;{WX7~kt3LhcgQlXODUXyh3;92# CyOlct literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/editor-units.png b/VisualPinball.Unity/Documentation~/creators-guide/editor/editor-units.png new file mode 100644 index 0000000000000000000000000000000000000000..33249f65bd42a52124dd49c53d7943cadf47b0c3 GIT binary patch literal 9602 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 NhKjCowW4+8{{XEcF(Ci| literal 0 HcmV?d00001 diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md index b8d4f9f74..30e7597f1 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/units-3d-space.md @@ -6,7 +6,7 @@ description: VPE supports real-world units and free 3D transformations. # Units and 3D Space -Units describe how we measure things. For example, the units for measuring length are meters in the metric system, or feet and inches in the imperial system. There are also different systems for saving 3D data. For example, in VPX, the x-axis points to the right side of the player, the y-axis towards the player, and the z-axis upwards. We call this the [orientation and handedness](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions) of the coordinate system. +Units describe how we measure things. For example, the units for measuring length are meters in the metric system, or feet and inches in the imperial system. There are also different systems for saving 3D data. For example, in VPX, the x-axis points to the right side of the player, the y-axis down, and the z-axis towards the player. We call this the [orientation and handedness](https://en.wikipedia.org/wiki/Cartesian_coordinate_system#In_three_dimensions) of the coordinate system. Different software uses different units and orientations, and this section describes how VPE handles them. We'll also compare how VPX and VPE handle transformations. @@ -26,7 +26,7 @@ or - 1" = 47.05882352941176 VP units - 1mm = 1.852709587772117 VP units -Obviously, no other 3D software uses these units, so importing models from, let's say, Blender has always been a pain. +Obviously, no other 3D software uses these units, so importing models from, let's say, Blender, has always been a pain. The problem is that if we wanted to just scale everything down to meters, it would impact physics because physics is strongly dependent on the real-world size (and thus, mass) of things. Given that VPE uses VPX's physics code, which has been fine-tuned with heuristics based on VPX units, we cannot simply scale everything to real-world units and expect the same behavior in the physics simulation. @@ -34,14 +34,17 @@ So, we've chosen the following approach: > - Everything in the scene uses real-world units (meters). > - During runtime, 3D data is converted to VPX units for the physics simulation. -> - New movement data from the physics engine is converted back to real-world units. +> - New movement data from the physics engine is converted back and applied to real-world units. > [!note] > ### VPX Units in the Editor > -> Meters where a table is under 1x2 meters large isn't necessarily the best unit either. The best would have been millimeters, but Unity's units aren't configurable. +> Meters for elements on a pinball table which is under two meters long isn't necessarily the best unit either. The best would have been millimeters, but Unity's units aren't configurable. > -> Because of this and the fact that many table authors are still familiar with VPX units, VPE includes VPX units in the panel of each component. They can be changed in real-time and will update the transformation of the object. +> Because of this and the fact that many table authors are still familiar with VPX units, VPE includes VPX units in the panel of each component. Updating one will automatically update the other. +> +> Both real world and VPX units
+> *Real world (top) and VPX (bottom) units in the editor* ## Orientation and Handedness @@ -52,18 +55,18 @@ Blender, on the other hand (no pun intended), also has the Z-axis pointing up, b Unity's coordinate system is left-handed like VPX, but oriented differently. Since the player is usually looking forward, that's where the XY plane lies. So, Y points upwards, X to the right, and Z away from the player. +Blender FBX Export + The main impact for you as a table author is that you need to pay attention when exporting your meshes from other 3D software. For example, when exporting to FBX in Blender, you need to make sure that the following mapping is set (the default *Forward* being *-Z Forward*): > - Forward -> Z-Forward > - Up -> Y-Up -TODO screenshot - ## Transformations We call it a *transformation* when we move, rotate, or scale an object. Let's talk about how VPX and VPE handle transformations. -In VPX, the XY position within the playfield can be freely set for all items. For the Z-position, some objects allow free positioning, some can be parented to a surface (wall or ramp), and others have a fixed Z-position. +In VPX, the XY position within the playfield can be freely set for all items. For the Z-position, some objects allow free positioning, some can be parented to a surface (wall or ramp), and others have a fixed Z-position. Regarding rotation, some items, like spinners or gates, can be Z-rotated, some can be freely rotated, and some can't be rotated at all. From 0f6361e49fa767ad29dced32a30671f624a4c107 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 22:44:37 +0100 Subject: [PATCH 196/208] editor: Hide non-selected collider gizmos. --- .../VisualPinball.Unity/VPT/ColliderComponent.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 214f73cf3..505913a20 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -23,7 +23,6 @@ using Unity.Collections; using Unity.Jobs; using Unity.Mathematics; -using UnityEditor; using UnityEngine; using UnityEngine.Profiling; using VisualPinball.Engine.Game; @@ -31,6 +30,10 @@ using VisualPinball.Engine.VPT.Flipper; using Mesh = UnityEngine.Mesh; +#if UNITY_EDITOR +using UnityEditor; +#endif + namespace VisualPinball.Unity { [DisallowMultipleComponent] @@ -163,6 +166,12 @@ private void OnDrawGizmos() var showAllColliderMeshes = playfieldColliderComponent && playfieldColliderComponent.ShowAllColliderMeshes; var showColliders = ShowColliderMesh || showAllColliderMeshes; + var isSelected = Selection.gameObjects.Contains(gameObject); + if (!isSelected && !showAllColliderMeshes) { + Profiler.EndSample(); + return; + } + // early out if nothing to draw if (!ShowAabbs && !showColliders && !ShowColliderOctree) { Profiler.EndSample(); From 8426697993e9c3a03242fc9e8c3f50317076d692 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 23:16:51 +0100 Subject: [PATCH 197/208] editor: Show bounding boxes again when enabled. --- .../VPT/ColliderInspector.cs | 23 -------- .../VPT/ColliderComponent.cs | 58 ++++++++++++------- 2 files changed, 37 insertions(+), 44 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs index ec30b7b50..bca768b8d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs @@ -93,29 +93,6 @@ public override void OnInspectorGUI() } EditorGUILayout.EndFoldoutHeaderGroup(); - // individual collider list - /* - if (_foldoutColliders = EditorGUILayout.BeginFoldoutHeaderGroup(_foldoutColliders, "Colliders")) { - - var hitObjects = ColliderComponent.Colliders ?? new List(0); - _currentColliders = hitObjects - .Where(h => h != null) - .Select((h, i) => $"[{i}] {h.GetType().Name}") - .ToArray(); - - if (_currentColliders.Length == 0) { - GUILayout.Label("No colliders for this item."); - } - - _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, GUILayout.ExpandWidth(true), - GUILayout.ExpandHeight(true)); - var selectedCollider = GUILayout.SelectionGrid(ColliderComponent.SelectedCollider, _currentColliders, 1); - refresh = refresh || selectedCollider == ColliderComponent.SelectedCollider; - ColliderComponent.SelectedCollider = selectedCollider; - EditorGUILayout.EndScrollView(); - } - EditorGUILayout.EndFoldoutHeaderGroup();*/ - // refresh scene view manually if (refresh) { EditorWindow.GetWindow().Repaint(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index 505913a20..e40e03933 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -53,9 +53,6 @@ public abstract class ColliderComponent : SubComponent _collidersDirty = value; } [NonSerialized] private Mesh _transformedColliderMesh; @@ -260,48 +257,67 @@ private void OnDrawGizmos() Gizmos.DrawWireMesh(_transformedKinematicColliderMesh); } } - DrawNonMeshColliders(); } + if (ShowAabbs) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; + foreach (var aabb in _aabbs) { + DrawAabb(aabb, true); + } + } + Gizmos.matrix = Matrix4x4.identity; Handles.matrix = Matrix4x4.identity; Profiler.EndSample(); } - private void InstantiateRuntimeColliders(bool createMesh) + private void InstantiateRuntimeColliders(bool showColliders) { if (!PhysicsEngine) { PhysicsEngine = GetComponentInParent(); // todo cache } - if (createMesh) { + if (showColliders || ShowAabbs) { if (IsKinematic) { var kinematicColliders = PhysicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); _transformedColliderMesh = null; _untransformedColliderMesh = null; - GenerateColliderMesh(kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + + if (showColliders) { + GenerateColliderMesh(kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + } + + if (ShowAabbs) { + var count = kinematicColliders.Length; + _aabbs = new Aabb[count]; + for (var i = 0; i < count; i++) { + _aabbs[i] = kinematicColliders[i].Bounds.Aabb; + } + } } else { var colliders = PhysicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); _transformedKinematicColliderMesh = null; _untransformedKinematicColliderMesh = null; - GenerateColliderMesh(colliders, out _transformedColliderMesh, out _untransformedColliderMesh); + + if (showColliders) { + GenerateColliderMesh(colliders, out _transformedColliderMesh, out _untransformedColliderMesh); + } + + if (ShowAabbs) { + var count = colliders.Length; + _aabbs = new Aabb[count]; + for (var i = 0; i < count; i++) { + _aabbs[i] = colliders[i].Bounds.Aabb; + } + } } _collidersDirty = false; } - - // if (ShowAabbs) { - // var count = IsKinematic ? colliders.Length : kinematicColliders.Length; - // var c = IsKinematic ? colliders : kinematicColliders; - // _aabbs = new Aabb[count]; - // for (var i = 0; i < count; i++) { - // _aabbs[i] = c[i].Bounds.Aabb; - // } - // } } - private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashMap nonTransformableColliderTransforms, float4x4 localToPlayfieldMatrixInVpx) + private void InstantiateEditorColliders(bool showColliders, ref NativeParallelHashMap nonTransformableColliderTransforms, float4x4 localToPlayfieldMatrixInVpx) { var api = InstantiateColliderApi(_player, PhysicsEngine); var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp); @@ -313,7 +329,7 @@ private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashM api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); } - if (createMesh) { + if (showColliders) { if (IsKinematic) { _transformedColliderMesh = null; _untransformedColliderMesh = null; @@ -327,8 +343,8 @@ private void InstantiateEditorColliders(bool createMesh, ref NativeParallelHashM } if (ShowAabbs) { - var count = IsKinematic ? colliders.Count : kinematicColliders.Count; - var c = IsKinematic ? colliders : kinematicColliders; + var count = IsKinematic ? kinematicColliders.Count : colliders.Count; + var c = IsKinematic ? kinematicColliders : colliders; _aabbs = new Aabb[count]; for (var i = 0; i < count; i++) { _aabbs[i] = c[i].Bounds.Aabb; From dc6f9edd9b3c2df1cfdf771881cc854aea5474d1 Mon Sep 17 00:00:00 2001 From: freezy Date: Mon, 6 Jan 2025 23:39:50 +0100 Subject: [PATCH 198/208] editor: Show octree again when enabled. --- .../VPT/ColliderComponent.cs | 105 +++++++----------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index e40e03933..92d54e0d8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -147,6 +147,7 @@ public virtual void OnTransformationChanged(float4x4 currTransformationMatrix) #if UNITY_EDITOR private Player _player; + private NativeOctree _octree = default; private void OnDrawGizmos() { @@ -181,7 +182,7 @@ private void OnDrawGizmos() var unmodifiedLocalToPlayfieldMatrixInVpx = GetUnmodifiedLocalToPlayfieldMatrixInVpx(worldToPlayfield); var nonTransformableColliderTransforms = new NativeParallelHashMap(0, Allocator.Temp); - var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; + var generateColliders = ShowAabbs || showColliders && !HasCachedColliders || ShowColliderOctree; if (generateColliders) { if (Application.isPlaying) { InstantiateRuntimeColliders(showColliders); @@ -190,37 +191,7 @@ private void OnDrawGizmos() } } - if (ShowColliderOctree) { - - var api = InstantiateColliderApi(_player, null); - var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.TempJob); - var kinematicColliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.TempJob, true); - try { - if (IsKinematic) { - api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); - } else { - api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); - } - - var playfieldBounds = GetComponentInParent().Bounds; - var octree = new NativeOctree(playfieldBounds, 32, 10, Allocator.Persistent); - var nativeColliders = new NativeColliders(ref colliders, Allocator.TempJob); - var populateJob = new PhysicsPopulateJob { - Colliders = nativeColliders, - Octree = octree, - }; - populateJob.Run(); - Gizmos.color = Color.yellow; - octree.DrawGizmos(); - } finally { - colliders.Dispose(); - kinematicColliders.Dispose(); - } - } - - // aabbs - Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; - + // draw collider mesh if (showColliders) { var white = Color.white; @@ -260,6 +231,7 @@ private void OnDrawGizmos() DrawNonMeshColliders(); } + // draw aabbs if (ShowAabbs) { Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; foreach (var aabb in _aabbs) { @@ -267,6 +239,14 @@ private void OnDrawGizmos() } } + // draw octree + if (ShowColliderOctree && !Application.isPlaying) { + Gizmos.matrix = playfieldToWorld * (Matrix4x4)Physics.VpxToWorld; + Gizmos.color = Color.yellow; + _octree.DrawGizmos(); + _octree.Dispose(); + } + Gizmos.matrix = Matrix4x4.identity; Handles.matrix = Matrix4x4.identity; @@ -279,40 +259,34 @@ private void InstantiateRuntimeColliders(bool showColliders) PhysicsEngine = GetComponentInParent(); // todo cache } - if (showColliders || ShowAabbs) { + if (showColliders || ShowAabbs || ShowColliderOctree) { + var colliders = IsKinematic + ? PhysicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()) + : PhysicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); + if (IsKinematic) { - var kinematicColliders = PhysicsEngine.GetKinematicColliders(MainComponent.gameObject.GetInstanceID()); _transformedColliderMesh = null; _untransformedColliderMesh = null; - if (showColliders) { - GenerateColliderMesh(kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + GenerateColliderMesh(colliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); } - if (ShowAabbs) { - var count = kinematicColliders.Length; - _aabbs = new Aabb[count]; - for (var i = 0; i < count; i++) { - _aabbs[i] = kinematicColliders[i].Bounds.Aabb; - } - } } else { - var colliders = PhysicsEngine.GetColliders(MainComponent.gameObject.GetInstanceID()); _transformedKinematicColliderMesh = null; _untransformedKinematicColliderMesh = null; - if (showColliders) { GenerateColliderMesh(colliders, out _transformedColliderMesh, out _untransformedColliderMesh); } + } - if (ShowAabbs) { - var count = colliders.Length; - _aabbs = new Aabb[count]; - for (var i = 0; i < count; i++) { - _aabbs[i] = colliders[i].Bounds.Aabb; - } + if (ShowAabbs) { + var count = colliders.Length; + _aabbs = new Aabb[count]; + for (var i = 0; i < count; i++) { + _aabbs[i] = colliders[i].Bounds.Aabb; } } + _collidersDirty = false; } } @@ -320,20 +294,15 @@ private void InstantiateRuntimeColliders(bool showColliders) private void InstantiateEditorColliders(bool showColliders, ref NativeParallelHashMap nonTransformableColliderTransforms, float4x4 localToPlayfieldMatrixInVpx) { var api = InstantiateColliderApi(_player, PhysicsEngine); - var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp); - var kinematicColliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp, true); + var colliders = new ColliderReference(ref nonTransformableColliderTransforms, Allocator.Temp, IsKinematic); try { - if (IsKinematic) { - api.CreateColliders(ref kinematicColliders, localToPlayfieldMatrixInVpx, 0.1f); - } else { - api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); - } + api.CreateColliders(ref colliders, localToPlayfieldMatrixInVpx, 0.1f); if (showColliders) { if (IsKinematic) { _transformedColliderMesh = null; _untransformedColliderMesh = null; - GenerateColliderMesh(ref kinematicColliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); + GenerateColliderMesh(ref colliders, out _transformedKinematicColliderMesh, out _untransformedKinematicColliderMesh); } else { _transformedKinematicColliderMesh = null; _untransformedKinematicColliderMesh = null; @@ -343,16 +312,26 @@ private void InstantiateEditorColliders(bool showColliders, ref NativeParallelHa } if (ShowAabbs) { - var count = IsKinematic ? kinematicColliders.Count : colliders.Count; - var c = IsKinematic ? kinematicColliders : colliders; + var count = colliders.Count; _aabbs = new Aabb[count]; for (var i = 0; i < count; i++) { - _aabbs[i] = c[i].Bounds.Aabb; + _aabbs[i] = colliders[i].Bounds.Aabb; } } + + if (ShowColliderOctree) { + var playfieldBounds = GetComponentInParent().Bounds; + _octree = new NativeOctree(playfieldBounds, 32, 10, Allocator.Persistent); + var nativeColliders = new NativeColliders(ref colliders, Allocator.TempJob); + var populateJob = new PhysicsPopulateJob { + Colliders = nativeColliders, + Octree = _octree, + }; + populateJob.Run(); + } + } finally { colliders.Dispose(); - kinematicColliders.Dispose(); } } From b0ee9a187b21be54dfa2edfa80f538150a75963d Mon Sep 17 00:00:00 2001 From: freezy Date: Tue, 7 Jan 2025 23:41:58 +0100 Subject: [PATCH 199/208] kicker: Fix gizmo and clean up mesh selector. --- .../VPT/Kicker/KickerInspector.cs | 28 ++++--------------- .../VPT/Kicker/KickerComponent.cs | 5 ++-- 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs index d4ad175c6..1ec3ede64 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs @@ -29,21 +29,7 @@ namespace VisualPinball.Unity.Editor [CustomEditor(typeof(KickerComponent)), CanEditMultipleObjects] public class KickerInspector : MainInspector { - private const string MeshFolder = "Packages/org.visualpinball.engine.unity/VisualPinball.Unity/Assets/Art/Meshes/Kicker"; - - private static readonly Dictionary TypeMap = new() { - { "Cup 1", KickerType.KickerCup }, - { "Cup 2", KickerType.KickerCup2 }, - { "Gottlieb", KickerType.KickerGottlieb }, - { "Hole", KickerType.KickerHole }, - { "Simple Hole", KickerType.KickerHoleSimple }, - { "Williams", KickerType.KickerWilliams }, - { CustomMeshLabel, KickerType.KickerInvisible }, - }; - private SerializedProperty _orientationProperty; - private SerializedProperty _kickerTypeProperty; - private SerializedProperty _meshNameProperty; private SerializedProperty _coilsProperty; protected override void OnEnable() @@ -51,8 +37,6 @@ protected override void OnEnable() base.OnEnable(); _orientationProperty = serializedObject.FindProperty(nameof(KickerComponent.Orientation)); - _kickerTypeProperty = serializedObject.FindProperty(nameof(KickerComponent.KickerType)); - _meshNameProperty = serializedObject.FindProperty(nameof(KickerComponent.MeshName)); _coilsProperty = serializedObject.FindProperty(nameof(KickerComponent.Coils)); } @@ -82,11 +66,7 @@ public override void OnInspectorGUI() MainComponent.Radius = newRadius; } - if (MainComponent.KickerType == KickerType.KickerCup || - MainComponent.KickerType == KickerType.KickerWilliams) { - PropertyField(_orientationProperty, updateTransforms: true); - } - + PropertyField(_orientationProperty, updateTransforms: true); PropertyField(_coilsProperty); base.OnInspectorGUI(); @@ -100,12 +80,14 @@ private void OnSceneGUI() return; } - Handles.color = Color.cyan; - Handles.matrix = Matrix4x4.identity; + var playfield = MainComponent.GetComponentInParent(); + var worldToPlayfield = playfield ? playfield.transform.localToWorldMatrix : Matrix4x4.identity; var transform = MainComponent.transform; var localPos = MainComponent.Position; var worldPos = transform.parent == null ? localPos : localPos.TranslateToWorld(); + Handles.color = Color.cyan; + Handles.matrix = worldToPlayfield; foreach (var coil in MainComponent.Coils) { var from = MainComponent.GetBallCreationPosition().ToUnityVector3(); var l = coil.Speed == 0 ? 1f : 20f * coil.Speed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index 26a777cb4..516181f0b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -27,7 +27,6 @@ using System.Linq; using Unity.Collections; using Unity.Mathematics; -using UnityEditor; using UnityEngine; using VisualPinball.Engine.Game; using VisualPinball.Engine.Game.Engines; @@ -151,7 +150,7 @@ public override IEnumerable SetData(KickerData data) #if UNITY_EDITOR var mf = GetComponent(); if (mf) { - MeshName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(mf.sharedMesh)); + MeshName = Path.GetFileNameWithoutExtension(UnityEditor.AssetDatabase.GetAssetPath(mf.sharedMesh)); } #endif @@ -268,7 +267,7 @@ public void OnBeforeSerialize() #if UNITY_EDITOR // don't generate ids for prefabs, otherwise they'll show up in the instances. - if (PrefabUtility.GetPrefabInstanceStatus(this) != PrefabInstanceStatus.Connected) { + if (UnityEditor.PrefabUtility.GetPrefabInstanceStatus(this) != UnityEditor.PrefabInstanceStatus.Connected) { return; } var coilIds = new HashSet(); From a4836e9825c3b02e502aae8f934abd5305983aff Mon Sep 17 00:00:00 2001 From: freezy Date: Wed, 8 Jan 2025 21:11:40 +0100 Subject: [PATCH 200/208] bumper: Use component instead of mesh name to toggle during import. --- .../VPT/Kicker/KickerInspector.cs | 7 +- .../VPT/Bumper/BumperBaseComponent.cs | 27 ++++++ .../VPT/Bumper/BumperBaseComponent.cs.meta | 11 +++ .../VPT/Bumper/BumperCapComponent.cs | 28 ++++++ .../VPT/Bumper/BumperCapComponent.cs.meta | 11 +++ .../VPT/Bumper/BumperComponent.cs | 91 +++++-------------- .../VPT/Gate/GateBracketComponent.cs | 21 ++++- .../VisualPinball.Unity/VPT/MainComponent.cs | 12 +++ .../VPT/MainRenderableComponent.cs | 7 ++ 9 files changed, 139 insertions(+), 76 deletions(-) create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs.meta create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs create mode 100644 VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs index 1ec3ede64..aa9198604 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Kicker/KickerInspector.cs @@ -16,12 +16,9 @@ // ReSharper disable AssignmentInConditionalExpression -using System; -using System.Collections.Generic; using Unity.Mathematics; using UnityEditor; using UnityEngine; -using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Kicker; namespace VisualPinball.Unity.Editor @@ -81,13 +78,13 @@ private void OnSceneGUI() } var playfield = MainComponent.GetComponentInParent(); - var worldToPlayfield = playfield ? playfield.transform.localToWorldMatrix : Matrix4x4.identity; + var playfieldToWorld = playfield ? playfield.transform.localToWorldMatrix : Matrix4x4.identity; var transform = MainComponent.transform; var localPos = MainComponent.Position; var worldPos = transform.parent == null ? localPos : localPos.TranslateToWorld(); Handles.color = Color.cyan; - Handles.matrix = worldToPlayfield; + Handles.matrix = playfieldToWorld; foreach (var coil in MainComponent.Coils) { var from = MainComponent.GetBallCreationPosition().ToUnityVector3(); var l = coil.Speed == 0 ? 1f : 20f * coil.Speed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs new file mode 100644 index 000000000..7ac4eac0b --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs @@ -0,0 +1,27 @@ +// 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 . + +using UnityEngine; + +namespace VisualPinball.Unity +{ + /// + /// Just a tag component to identify and set visibility of the bumper base during import. + /// + public class BumperBaseComponent : MonoBehaviour + { + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs.meta new file mode 100644 index 000000000..99e26f20e --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperBaseComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 430ff597fd1443bfb36a4c650fcb2006 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 9c3a25b6d34883f49854c175c61e07e8, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs new file mode 100644 index 000000000..7a2241b01 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs @@ -0,0 +1,28 @@ +// 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 . + +using UnityEngine; + +namespace VisualPinball.Unity +{ + /// + /// Just a tag component to identify and set visibility of the bumper cap during import. + /// + public class BumperCapComponent : MonoBehaviour + { + + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs.meta new file mode 100644 index 000000000..fd0b8dbd8 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCapComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5fc4ac7d14c40558112c1f3669bb368 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: a9825ee77904e0349b778294bc0bb8d9, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 8ac82e3dc..bcdcc59ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -73,14 +73,10 @@ public float Orientation { public override string ItemName => "Bumper"; public override bool HasProceduralMesh => false; - public override BumperData InstantiateData() => new BumperData(); + public override BumperData InstantiateData() => new(); protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); - private const string SkirtMeshName = "bumper.skirt"; - private const string BaseMeshName = "bumper.base"; - private const string CapMeshName = "bumper.cap"; - private const string RingMeshName = "bumper.ring"; public const float DataMeshScale = 100f; @@ -157,12 +153,6 @@ public override void UpdateTransforms() transform.localScale = new Vector3(Radius * 2f, HeightScale, Radius * 2f) / DataMeshScale; } - public void UpdateTransforms(Quaternion xz) - { - var y = Quaternion.Euler(0, Orientation, 0); - transform.rotation = xz * y; // localRotation? - } - #endregion #region Conversion @@ -205,42 +195,14 @@ public override IEnumerable SetReferencedData(BumperData data, Ta UpdateTransforms(); // children visibility - foreach (var mf in GetComponentsInChildren()) { - if (mf.sharedMesh) { - var mr = mf.GetComponent(); - switch (mf.sharedMesh.name) { - case SkirtMeshName: - mf.gameObject.SetActive(data.IsSocketVisible); - if (!string.IsNullOrEmpty(data.SocketMaterial)) { - mr.sharedMaterial = materialProvider.MergeMaterials(data.SocketMaterial, mr.sharedMaterial); - } - break; - case BaseMeshName: - mf.gameObject.SetActive(data.IsBaseVisible); - if (!string.IsNullOrEmpty(data.BaseMaterial)) { - mr.sharedMaterial = materialProvider.MergeMaterials(data.BaseMaterial, mr.sharedMaterial); - } - break; - case CapMeshName: - mf.gameObject.SetActive(data.IsCapVisible); - if (!string.IsNullOrEmpty(data.CapMaterial)) { - mr.sharedMaterial = materialProvider.MergeMaterials(data.CapMaterial, mr.sharedMaterial); - } - break; - case RingMeshName: - mf.gameObject.SetActive(data.IsRingVisible); - if (!string.IsNullOrEmpty(data.RingMaterial)) { - mr.sharedMaterial = materialProvider.MergeMaterials(data.RingMaterial, mr.sharedMaterial); - } - break; - } - } - } + SetVisibilityByComponent(data.IsSocketVisible); + SetVisibilityByComponent(data.IsBaseVisible); + SetVisibilityByComponent(data.IsCapVisible); + SetVisibilityByComponent(data.IsRingVisible); return Array.Empty(); } - public override BumperData CopyDataTo(BumperData data, string[] materialNames, string[] textureNames, bool forExport) { // name and transforms @@ -251,33 +213,10 @@ public override BumperData CopyDataTo(BumperData data, string[] materialNames, s data.Orientation = Orientation; // children visibility - data.IsBaseVisible = false; - data.IsCapVisible = false; - data.IsRingVisible = false; - data.IsSocketVisible = false; - foreach (var mf in GetComponentsInChildren(true)) { - if (mf.sharedMesh) { - var mr = mf.gameObject.GetComponent(); - switch (mf.sharedMesh.name) { - case SkirtMeshName: - data.IsSocketVisible = mf.gameObject.activeInHierarchy; - CopyMaterialName(mr, materialNames, textureNames, ref data.SocketMaterial); - break; - case BaseMeshName: - data.IsBaseVisible = mf.gameObject.activeInHierarchy; - CopyMaterialName(mr, materialNames, textureNames, ref data.BaseMaterial); - break; - case CapMeshName: - data.IsCapVisible = mf.gameObject.activeInHierarchy; - CopyMaterialName(mr, materialNames, textureNames, ref data.CapMaterial); - break; - case RingMeshName: - data.IsRingVisible = mf.gameObject.activeInHierarchy; - CopyMaterialName(mr, materialNames, textureNames, ref data.RingMaterial); - break; - } - } - } + data.IsBaseVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsCapVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsRingVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsSocketVisible = CopyMaterialName(data, materialNames, textureNames); // collider var collComponent = GetComponentInChildren(); @@ -301,6 +240,18 @@ public override BumperData CopyDataTo(BumperData data, string[] materialNames, s return data; } + private bool CopyMaterialName(BumperData data, string[] materialNames, string[] textureNames) where TComponent : MonoBehaviour + { + var skirtComp = GetComponentInChildren(); + if (skirtComp) { + var mf = skirtComp.GetComponentInChildren(); + var mr = mf.gameObject.GetComponent(); + CopyMaterialName(mr, materialNames, textureNames, ref data.SocketMaterial); + return mf.gameObject.activeInHierarchy; + } + return false; + } + public override void CopyFromObject(GameObject go) { // main component diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateBracketComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateBracketComponent.cs index 38ce13612..e36b93dee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateBracketComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateBracketComponent.cs @@ -1,7 +1,26 @@ -using UnityEngine; +// 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 . + +using UnityEngine; namespace VisualPinball.Unity { + /// + /// Just a component to tag brackets so we can toggle them during import. + /// public class GateBracketComponent : MonoBehaviour { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs index 439bd0dd7..ca1142efb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs @@ -40,7 +40,19 @@ public int PlayfieldDetailLevel { } public abstract IEnumerable SetData(TData data); + + /// + /// Updates the component when all other components are updated with , so we can reference them. + /// Also, materials and textures are available here. + /// + /// Item data from the import + /// Table reference + /// Get the material from here + /// Get the texture from here + /// Reference to all other components, by name (which is unique during import) + /// A list of updated components (if this item has impact on other components) public abstract IEnumerable SetReferencedData(TData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components); + public abstract TData CopyDataTo(TData data, string[] materialNames, string[] textureNames, bool forExport); public abstract bool HasProceduralMesh { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index f994fbc05..3a7c5f5c9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -87,6 +87,13 @@ public UnityEngine.Mesh GetUnityMesh() return null; } + protected void SetVisibilityByComponent(bool isVisible) where TComponent : MonoBehaviour + { + foreach (var component in GetComponentsInChildren()) { + component.gameObject.SetActive(isVisible); + } + } + protected void ParentToSurface(string surfaceName, Vertex2D center, Dictionary components) { if (!string.IsNullOrEmpty(surfaceName)) { From c2bc96794051cb3a939493944d89e20113ff4cfa Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 9 Jan 2025 21:23:31 +0100 Subject: [PATCH 201/208] cleanup: Ditch VPX export. --- .../Toolbox/ToolboxEditor.cs | 6 +- .../VPT/Bumper/BumperComponent.cs | 38 --- .../VPT/Flipper/FlipperComponent.cs | 44 ---- .../VPT/Gate/GateComponent.cs | 43 ---- .../VPT/HitTarget/DropTargetComponent.cs | 34 --- .../VPT/HitTarget/HitTargetComponent.cs | 30 --- .../VPT/HitTarget/TargetComponent.cs | 14 -- .../VPT/Kicker/KickerComponent.cs | 29 --- .../VPT/Light/LightComponent.cs | 40 ---- .../VisualPinball.Unity/VPT/MainComponent.cs | 2 - .../VisualPinball.Unity/VPT/MeshComponent.cs | 1 - .../MetalWireGuide/MetalWireGuideComponent.cs | 39 ---- .../VPT/Playfield/PlayfieldComponent.cs | 35 --- .../VPT/Plunger/PlungerComponent.cs | 52 ----- .../VPT/Primitive/PrimitiveComponent.cs | 62 ----- .../VPT/Ramp/RampComponent.cs | 56 ----- .../VPT/Rubber/RubberComponent.cs | 38 --- .../VPT/Spinner/SpinnerComponent.cs | 39 ---- .../VPT/Surface/SurfaceComponent.cs | 42 ---- .../VPT/Table/SceneTableContainer.cs | 221 ------------------ .../VPT/Table/TableComponent.cs | 9 - .../VPT/Trigger/TriggerComponent.cs | 39 ---- .../VPT/Trough/TroughComponent.cs | 17 -- 23 files changed, 1 insertion(+), 929 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs index 107e1b2a6..e8f48c6c3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; @@ -25,6 +24,7 @@ using VisualPinball.Engine.VPT.Gate; using VisualPinball.Engine.VPT.HitTarget; using VisualPinball.Engine.VPT.Kicker; +using VisualPinball.Engine.VPT.MetalWireGuide; using VisualPinball.Engine.VPT.Plunger; using VisualPinball.Engine.VPT.Primitive; using VisualPinball.Engine.VPT.Ramp; @@ -34,7 +34,6 @@ using VisualPinball.Engine.VPT.Table; using VisualPinball.Engine.VPT.Trigger; using VisualPinball.Engine.VPT.Trough; -using VisualPinball.Engine.VPT.MetalWireGuide; using Light = VisualPinball.Engine.VPT.Light.Light; using Texture = UnityEngine.Texture; @@ -211,7 +210,6 @@ private static bool CreateButton(string label, Texture icon, float iconSize, GUI private void CreateItem(Func create, string actionName) where TItem : IItem { var tableContainer = TableComponent.TableContainer; - tableContainer.Refresh(); var item = create(tableContainer.Table); Selection.activeGameObject = CreateRenderable(item); ItemCreated?.Invoke(Selection.activeGameObject); @@ -221,14 +219,12 @@ private void CreateItem(Func create, string actionName) whe private GameObject CreateRenderable(IItem item) { var converter = new VpxSceneConverter(TableComponent); - TableComponent.TableContainer.Refresh(); return converter.InstantiateAndPersistPrefab(item).GameObject; } private void CreatePrefab(string groupName, string path) where T : Component { var converter = new VpxSceneConverter(TableComponent); - TableComponent.TableContainer.Refresh(); var parentGo = converter.GetGroupParent(groupName); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index bcdcc59ba..caf03e827 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -27,7 +27,6 @@ using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; -using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Table; @@ -203,43 +202,6 @@ public override IEnumerable SetReferencedData(BumperData data, Ta return Array.Empty(); } - public override BumperData CopyDataTo(BumperData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.Radius = Radius; - data.HeightScale = HeightScale; - data.Orientation = Orientation; - - // children visibility - data.IsBaseVisible = CopyMaterialName(data, materialNames, textureNames); - data.IsCapVisible = CopyMaterialName(data, materialNames, textureNames); - data.IsRingVisible = CopyMaterialName(data, materialNames, textureNames); - data.IsSocketVisible = CopyMaterialName(data, materialNames, textureNames); - - // collider - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - data.Threshold = collComponent.Threshold; - data.Force = collComponent.Force; - data.Scatter = collComponent.Scatter; - data.HitEvent = collComponent.HitEvent; - } else { - data.IsCollidable = false; - } - - // ring animation - var ringAnimComponent = GetComponentInChildren(); - if (ringAnimComponent) { - data.RingSpeed = ringAnimComponent.RingSpeed; - data.RingDropOffset = ringAnimComponent.RingDropOffset; - } - - return data; - } - private bool CopyMaterialName(BumperData data, string[] materialNames, string[] textureNames) where TComponent : MonoBehaviour { var skirtComp = GetComponentInChildren(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 9bc4a5b3c..5a2e3e6b4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -259,50 +259,6 @@ public override IEnumerable SetReferencedData(FlipperData data, T return Array.Empty(); } - public override FlipperData CopyDataTo(FlipperData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.StartAngle = StartAngle; - - // geometry - data.Height = _height; - data.BaseRadius = _baseRadius; - data.EndRadius = _endRadius; - data.EndAngle = EndAngle; - data.FlipperRadiusMin = FlipperRadiusMin; - data.FlipperRadiusMax = FlipperRadiusMax; - data.RubberThickness = _rubberThickness; - data.RubberHeight = _rubberHeight; - data.RubberWidth = _rubberWidth; - - // states - data.IsEnabled = IsEnabled; - data.IsDualWound = IsDualWound; - - // children visibility - var baseMesh = GetComponentInChildren(); - data.IsVisible = baseMesh && baseMesh.gameObject.activeInHierarchy; - - // collider data - var colliderComponent = gameObject.GetComponent(); - if (colliderComponent) { - data.Mass = colliderComponent.Mass; - data.Strength = colliderComponent.Strength; - data.Elasticity = colliderComponent.Elasticity; - data.ElasticityFalloff = colliderComponent.ElasticityFalloff; - data.Friction = colliderComponent.Friction; - data.Return = colliderComponent.Return; - data.RampUp = colliderComponent.RampUp; - data.TorqueDamping = colliderComponent.TorqueDamping; - data.TorqueDampingAngle = colliderComponent.TorqueDampingAngle; - data.Scatter = colliderComponent.Scatter; - } - - return data; - } - public override void CopyFromObject(GameObject go) { // main component diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 20f5bf2b6..48adf1494 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -213,49 +213,6 @@ public override IEnumerable SetReferencedData(GateData data, Tabl return Array.Empty(); } - public override GateData CopyDataTo(GateData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Center = Position.ToVertex2Dxy(); - data.Name = name; - data.Rotation = Rotation; - data.Height = Position.z; - data.Length = Length; - - data.GateType = _type; - - // visibility - foreach (var mf in GetComponentsInChildren()) { - switch (mf.gameObject.name) { - case BracketObjectName: - data.ShowBracket = mf.gameObject.activeInHierarchy; - break; - case WireObjectName: - data.IsVisible = mf.gameObject.activeInHierarchy; - break; - } - } - - // collision data - var colliderComponent = gameObject.GetComponent(); - if (colliderComponent) { - data.IsCollidable = colliderComponent.enabled; - - data.AngleMin = math.radians(colliderComponent._angleMin); - data.AngleMax = math.radians(colliderComponent._angleMax); - data.Damping = colliderComponent.Damping; - data.Elasticity = colliderComponent.Elasticity; - data.Friction = colliderComponent.Friction; - data.GravityFactor = colliderComponent.GravityFactor; - data.TwoWay = colliderComponent._twoWay; - - } else { - data.IsCollidable = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { // collider data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs index a381dd3fb..570db919b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs @@ -92,40 +92,6 @@ public override IEnumerable SetReferencedData(HitTargetData data, return Array.Empty(); } - public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) - { - base.CopyDataTo(data, materialNames, textureNames, forExport); - - // collision data - var colliderComponent = GetComponent(); - if (colliderComponent) { - data.IsCollidable = colliderComponent.enabled; - data.Threshold = colliderComponent.Threshold; - data.UseHitEvent = colliderComponent.UseHitEvent; - data.PhysicsMaterial = colliderComponent.PhysicsMaterial == null ? string.Empty : colliderComponent.PhysicsMaterial.name; - data.IsLegacy = colliderComponent.IsLegacy; - - data.OverwritePhysics = colliderComponent.OverwritePhysics; - data.Elasticity = colliderComponent.Elasticity; - data.ElasticityFalloff = colliderComponent.ElasticityFalloff; - data.Friction = colliderComponent.Friction; - data.Scatter = colliderComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - // animation data - var dropTargetAnimationComponent = GetComponent(); - if (dropTargetAnimationComponent) { - data.DropSpeed = dropTargetAnimationComponent.Speed; - data.RaiseDelay = dropTargetAnimationComponent.RaiseDelay; - data.IsDropped = dropTargetAnimationComponent.IsDropped; - } - - return data; - } - #endregion #region Runtime diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs index ae318f888..45f097e68 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs @@ -77,36 +77,6 @@ public override IEnumerable SetReferencedData(HitTargetData data, return Array.Empty(); } - public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) - { - base.CopyDataTo(data, materialNames, textureNames, forExport); - - // collision data - var colliderComponent = GetComponent(); - if (colliderComponent) { - data.IsCollidable = colliderComponent.enabled; - data.Threshold = colliderComponent.Threshold; - data.PhysicsMaterial = colliderComponent.PhysicsMaterial == null ? string.Empty : colliderComponent.PhysicsMaterial.name; - - data.OverwritePhysics = colliderComponent.OverwritePhysics; - data.Elasticity = colliderComponent.Elasticity; - data.ElasticityFalloff = colliderComponent.ElasticityFalloff; - data.Friction = colliderComponent.Friction; - data.Scatter = colliderComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - // animation data - var animationComponent = GetComponent(); - if (animationComponent) { - data.DropSpeed = animationComponent.Speed; - } - - return data; - } - #endregion #region Runtime diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index f84318b66..55e7ee558 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -127,20 +127,6 @@ public override IEnumerable SetData(HitTargetData data) return updatedComponents; } - public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Name = name; - data.Position = Position.ToVertex3D(); - data.RotZ = Rotation; - data.Size = ((Vector3)Size).ToVertex3D(); - - data.TargetType = _targetType; - data.IsVisible = GetEnabled(); - - return data; - } - public override void CopyFromObject(GameObject go) { // dt collider diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index 516181f0b..b2a52e9cb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -179,35 +179,6 @@ public override IEnumerable SetReferencedData(KickerData data, Ta return Array.Empty(); } - public override KickerData CopyDataTo(KickerData data, string[] materialNames, string[] textureNames, - bool forExport) - { - // name and transforms - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.Orientation = Orientation; - data.Radius = Radius; - - data.KickerType = KickerType; - - // todo visibility is set by the type - - var colliderComponent = gameObject.GetComponent(); - if (colliderComponent) { - data.IsEnabled = colliderComponent.enabled; - data.Scatter = colliderComponent.Scatter; - data.HitAccuracy = colliderComponent.HitAccuracy; - data.HitHeight = colliderComponent.HitHeight; - data.FallThrough = colliderComponent.FallThrough; - data.LegacyMode = colliderComponent.LegacyMode; - - } else { - data.IsEnabled = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { // main component diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 46c1c66a0..20f8e7a44 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -408,46 +408,6 @@ public override IEnumerable SetReferencedData(LightData data, Tab return Array.Empty(); } - public override LightData CopyDataTo(LightData data, string[] materialNames, string[] textureNames, bool forExport) - { - var pos = (Vector3)transform.localPosition.TranslateToVpx(); - - // name and position - data.Name = name; - data.Center = pos.ToVertex2Dxy(); - data.MeshRadius = BulbSize; - - // logical params - data.State = (int)State; - data.BlinkPattern = BlinkPattern; - data.BlinkInterval = BlinkInterval; - data.FadeSpeedUp = FadeSpeedUp; - data.FadeSpeedDown = FadeSpeedDown; - - // insert mesh - var insertMeshComponent = GetComponentInChildren(); - if (insertMeshComponent) { - data.DragPoints = insertMeshComponent.DragPoints; - } - - // visibility - data.ShowBulbMesh = false; - foreach (var mf in GetComponentsInChildren(true)) { - if (!mf.sharedMesh) { - continue; - } - - switch (mf.sharedMesh.name) { - case BulbMeshName: - case SocketMeshName: - data.ShowBulbMesh = data.ShowBulbMesh || mf.gameObject.activeInHierarchy; - break; - } - } - - return data; - } - public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs index ca1142efb..2dce769dc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs @@ -53,8 +53,6 @@ public int PlayfieldDetailLevel { /// A list of updated components (if this item has impact on other components) public abstract IEnumerable SetReferencedData(TData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components); - public abstract TData CopyDataTo(TData data, string[] materialNames, string[] textureNames, bool forExport); - public abstract bool HasProceduralMesh { get; } public abstract ItemType ItemType { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs index e125fb66e..4e3c670f9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs @@ -110,7 +110,6 @@ private void UpdateMesh() { var data = MainComponent.InstantiateData(); MainComponent.UpdateTransforms(); - MainComponent.CopyDataTo(data, null, null, false); var mesh = GetMesh(data); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs index 289a8da60..9e72d33c1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs @@ -155,45 +155,6 @@ public override IEnumerable SetReferencedData(MetalWireGuideData return Array.Empty(); } - public override MetalWireGuideData CopyDataTo(MetalWireGuideData data, string[] materialNames, string[] textureNames, bool forExport) - { - // update the name - data.Name = name; - - // geometry - data.Height = _height; - data.RotX = Rotation.x; - data.RotY = Rotation.y; - data.RotZ = Rotation.z; - data.Thickness = _thickness; - data.Bendradius = _bendradius; - data.DragPoints = DragPoints; - - // visibility - data.IsVisible = GetEnabled(); - - // collision - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - - data.HitEvent = collComponent.HitEvent; - data.HitHeight = collComponent.HitHeight; - - data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; - data.OverwritePhysics = collComponent.OverwritePhysics; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs index 610aecdca..8f43cc2e5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs @@ -175,41 +175,6 @@ public IEnumerable SetReferencedData(PrimitiveData primitiveData, return updatedComponents; } - public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) - { - var physicsEngine = GetComponentInParent(); - - // position - data.TableHeight = 0; - data.GlassHeight = GlassHeight; - data.Left = Left; - data.Right = Right; - data.Top = Top; - data.Bottom = Bottom; - data.AngleTiltMax = AngleTiltMax; - data.AngleTiltMin = AngleTiltMin; - if (physicsEngine) { - data.Gravity = physicsEngine.GravityStrength; - } - - // playfield material - data.Image = _playfieldImage; - data.PlayfieldMaterial = _playfieldMaterial; - - // collider data - var collComponent = GetComponent(); - if (collComponent) { - data.Gravity = collComponent.Gravity; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - data.DefaultScatter = collComponent.DefaultScatter; - } - - return data; - } - public override void CopyFromObject(GameObject go) { throw new Exception("Copying object data is currently only used for replacing objects. Don't replace the playfield. Refactor this if necessary in the future."); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index e73a16aad..17494007b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -24,7 +24,6 @@ using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; -using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Plunger; using VisualPinball.Engine.VPT.Table; @@ -196,57 +195,6 @@ public override IEnumerable SetReferencedData(PlungerData data, T return Array.Empty(); } - public override PlungerData CopyDataTo(PlungerData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name, geometry and position - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.Width = Width; - data.Height = Height; - data.ZAdjust = Position.z; - - // collider data - var collComponent = GetComponent(); - if (collComponent) { - data.Stroke = collComponent.Stroke; - data.SpeedPull = collComponent.SpeedPull; - data.SpeedFire = collComponent.SpeedFire; - data.MechStrength = collComponent.MechStrength; - data.ParkPosition = collComponent.ParkPosition; - data.ScatterVelocity = collComponent.ScatterVelocity; - data.MomentumXfer = collComponent.MomentumXfer; - data.IsMechPlunger = collComponent.IsMechPlunger; - data.AutoPlunger = collComponent.IsAutoPlunger; - } - - // rod mesh - var rodMesh = GetComponentInChildren(true); - if (rodMesh) { - data.TipShape = rodMesh.TipShape; - data.RodDiam = rodMesh.RodDiam; - data.RingGap = rodMesh.RingGap; - data.RingDiam = rodMesh.RingDiam; - data.RingWidth = rodMesh.RingWidth; - } - - // spring mesh - var springMesh = GetComponentInChildren(true); - if (springMesh) { - data.SpringDiam = springMesh.SpringDiam; - data.SpringGauge = springMesh.SpringGauge; - data.SpringLoops = springMesh.SpringLoops; - data.SpringEndLoops = springMesh.SpringEndLoops; - } - - // type - var hasSpringMesh = springMesh && springMesh.isActiveAndEnabled; - var hasRodMesh = rodMesh && rodMesh.isActiveAndEnabled; - data.IsVisible = hasRodMesh; - data.Type = hasSpringMesh && hasRodMesh ? PlungerType.PlungerTypeCustom : PlungerType.PlungerTypeModern; - - return data; - } - public override void CopyFromObject(GameObject go) { // primitives don't have any special params. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 6ac232078..ef9a08710 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -132,68 +132,6 @@ public override IEnumerable SetReferencedData(PrimitiveData data, return updatedComponents; } - public override PrimitiveData CopyDataTo(PrimitiveData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - var t = transform; - data.Name = name; - data.Position = Position.ToVertex3D(); - data.Size = t.localScale.ToVertex3D(); - var vpxRotation = t.localEulerAngles.TranslateToVpx(); - data.RotAndTra = new[] { - vpxRotation.x, vpxRotation.y, vpxRotation.z, - 0, 0, 0, - 0, 0, 0, - }; - - // materials - var mr = GetComponent(); - if (mr) { - CopyMaterialName(mr, materialNames, textureNames, ref data.Material, ref data.Image, ref data.NormalMap); - } - - // mesh - var meshComponent = GetComponent(); - if (meshComponent) { - data.IsVisible = GetEnabled(); - data.Sides = meshComponent.Sides; - data.Use3DMesh = !meshComponent.UseLegacyMesh; - - if (forExport && !meshComponent.UseLegacyMesh) { - var mf = GetComponent(); - if (mf) { - data.Mesh = mf.sharedMesh.ToVpMesh().TransformToVpx(); - data.NumIndices = data.Mesh.Indices.Length; - data.NumVertices = data.Mesh.Vertices.Length; - } - } - } - - // update collision - // todo at some point we need to be able to toggle collidable during gameplay, - // todo but for now let's keep things static. - var collComponent = GetComponent(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - data.IsToy = false; - - data.HitEvent = collComponent.HitEvent; - data.Threshold = collComponent.Threshold; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - data.CollisionReductionFactor = collComponent.CollisionReductionFactor; - data.OverwritePhysics = collComponent.OverwritePhysics; - - } else { - data.IsCollidable = false; - data.IsToy = true; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var primitiveComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 7d6f30446..543a54e8d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -326,62 +326,6 @@ public override IEnumerable SetReferencedData(RampData data, Tabl return Array.Empty(); } - public override RampData CopyDataTo(RampData data, string[] materialNames, string[] textureNames, bool forExport) - { - // update the name - data.Name = name; - - // geometry - data.DragPoints = DragPoints; - data.HeightTop = _heightTop; - data.HeightBottom = _heightBottom; - data.WidthTop = _widthTop; - data.WidthBottom = _widthBottom; - data.LeftWallHeightVisible = _leftWallHeightVisible; - data.RightWallHeightVisible = _rightWallHeightVisible; - - // type and uvs - data.RampType = _type; - data.ImageAlignment = _imageAlignment; - - // wire data - data.WireDiameter = _wireDiameter; - data.WireDistanceX = _wireDistanceX; - data.WireDistanceY = _wireDistanceY; - - // visibility - var floorComponent = GetComponentInChildren(); - var wireComponent = GetComponentInChildren(); - if (IsWireRamp) { - data.IsVisible = wireComponent && wireComponent.gameObject.activeInHierarchy; - } else { - data.IsVisible = floorComponent && floorComponent.gameObject.activeInHierarchy; - } - - // collider data - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - - data.LeftWallHeight = collComponent.LeftWallHeight; - data.RightWallHeight = collComponent.RightWallHeight; - - data.HitEvent = collComponent.HitEvent; - data.Threshold = collComponent.Threshold; - data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; - - data.OverwritePhysics = collComponent.OverwritePhysics; - data.Elasticity = collComponent.Elasticity; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index ad6454b2b..efdba353e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -160,44 +160,6 @@ public override IEnumerable SetReferencedData(RubberData data, Ta return Array.Empty(); } - public override RubberData CopyDataTo(RubberData data, string[] materialNames, string[] textureNames, bool forExport) - { - // update the name - data.Name = name; - - // geometry - data.Height = _height; - data.RotX = Rotation.x; - data.RotY = Rotation.y; - data.RotZ = Rotation.z; - data.Thickness = _thickness; - data.DragPoints = DragPoints; - - // visibility - data.IsVisible = GetEnabled(); - - // collision - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - - data.HitEvent = collComponent.HitEvent; - data.HitHeight = collComponent.HitHeight; - - data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; - data.OverwritePhysics = collComponent.OverwritePhysics; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Friction = collComponent.Friction; - data.Scatter = collComponent.Scatter; - - } else { - data.IsCollidable = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 90a6e0bb1..f0f1686ca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -28,7 +28,6 @@ using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; -using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Spinner; using VisualPinball.Engine.VPT.Table; @@ -193,44 +192,6 @@ public override IEnumerable SetReferencedData(SpinnerData data, T return Array.Empty(); } - public override SpinnerData CopyDataTo(SpinnerData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.Height = Position.z; - data.Length = Length; - data.Rotation = Rotation; - - // spinner props - data.Damping = Damping; - data.AngleMax = AngleMax; - data.AngleMin = AngleMin; - - // visibility - var isBracketActive = false; - var isAnythingElseActive = false; - foreach (var mf in GetComponentsInChildren()) { - switch (mf.sharedMesh.name) { - case BracketMeshName: - isBracketActive = mf.gameObject.activeInHierarchy; - break; - default: - isAnythingElseActive = isAnythingElseActive || mf.gameObject.activeInHierarchy; - break; - } - } - data.IsVisible = isAnythingElseActive || isBracketActive; - data.ShowBracket = isBracketActive; - - var collComponent = GetComponent(); - if (collComponent) { - data.Elasticity = collComponent.Elasticity; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index c6a54a30a..06dfc4a82 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -163,48 +163,6 @@ public override IEnumerable SetReferencedData(SurfaceData data, T return Array.Empty(); } - public override SurfaceData CopyDataTo(SurfaceData data, string[] materialNames, string[] textureNames, bool forExport) - { - // update the name - data.Name = name; - - // main props - data.HeightBottom = HeightBottom; - data.HeightTop = HeightTop; - data.DragPoints = DragPoints; - - // children visibility - var topMesh = GetComponentInChildren(); - data.IsTopBottomVisible = topMesh && topMesh.gameObject.activeInHierarchy; - var sideMesh = GetComponentInChildren(); - data.IsSideVisible = sideMesh && sideMesh.gameObject.activeInHierarchy; - - // collider data - var collComponent = GetComponentInChildren(); - if (collComponent) { - data.IsCollidable = collComponent.enabled; - - data.HitEvent = collComponent.HitEvent; - data.Threshold = collComponent.Threshold; - data.IsBottomSolid = collComponent.IsBottomSolid; - - data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; - data.SlingshotForce = collComponent.SlingshotForce; - data.SlingshotThreshold = collComponent.SlingshotThreshold; - - data.OverwritePhysics = collComponent.OverwritePhysics; - data.Elasticity = collComponent.Elasticity; - data.ElasticityFalloff = collComponent.ElasticityFalloff; - data.Scatter = collComponent.Scatter; - data.Friction = collComponent.Friction; - - } else { - data.IsCollidable = false; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var surfaceComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs index 2e40a2668..a5424f08f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs @@ -15,39 +15,11 @@ // along with this program. If not, see . using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; -using System.IO; using System.Linq; -using UnityEditor; -using UnityEngine; -using VisualPinball.Engine.VPT; -using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Collection; -using VisualPinball.Engine.VPT.Decal; -using VisualPinball.Engine.VPT.DispReel; -using VisualPinball.Engine.VPT.Flasher; -using VisualPinball.Engine.VPT.Flipper; -using VisualPinball.Engine.VPT.Gate; -using VisualPinball.Engine.VPT.HitTarget; -using VisualPinball.Engine.VPT.Kicker; -using VisualPinball.Engine.VPT.Light; -using VisualPinball.Engine.VPT.LightSeq; -using VisualPinball.Engine.VPT.Plunger; -using VisualPinball.Engine.VPT.Primitive; -using VisualPinball.Engine.VPT.Ramp; -using VisualPinball.Engine.VPT.Rubber; using VisualPinball.Engine.VPT.Sound; -using VisualPinball.Engine.VPT.Spinner; -using VisualPinball.Engine.VPT.Surface; using VisualPinball.Engine.VPT.Table; -using VisualPinball.Engine.VPT.TextBox; -using VisualPinball.Engine.VPT.Timer; -using VisualPinball.Engine.VPT.Trigger; -using VisualPinball.Engine.VPT.Trough; -using VisualPinball.Engine.VPT.MetalWireGuide; -using Light = VisualPinball.Engine.VPT.Light.Light; using Material = VisualPinball.Engine.VPT.Material; using Texture = VisualPinball.Engine.VPT.Texture; @@ -97,106 +69,6 @@ public override Material GetMaterial(string name) public SceneTableContainer(TableComponent ta) { _tableComponent = ta; - - } - - public void Refresh(bool forExport = false) - { - var stopWatch = Stopwatch.StartNew(); - Clear(); - if (!_tableComponent.LegacyContainer) { - _tableComponent.LegacyContainer = ScriptableObject.CreateInstance(); - _tableComponent.LegacyContainer.TableData = new TableData(); - } - WalkChildren(_tableComponent.transform, node => RefreshChild(node, forExport)); - - _tableComponent.CopyDataTo(_tableComponent.LegacyContainer.TableData, MaterialNames, TextureNames, forExport); - var playfieldComponent = _tableComponent.GetComponentInChildren(); - playfieldComponent.CopyDataTo(_tableComponent.LegacyContainer.TableData, MaterialNames, TextureNames, forExport); - - foreach (var material in _tableComponent.LegacyContainer.TableData.Materials) { - _materials[material.Name.ToLower()] = material; - } - - Logger.Info($"Refreshed {GameItems.Count()} game items and {_materials.Count} materials in {stopWatch.ElapsedMilliseconds}ms."); - } - - public override void Save(string fileName) - { - Refresh(true); - PrepareForExport(); - - base.Save(fileName); - } - - private void PrepareForExport() - { - // fetch legacy items from container (because they are not in the scene) - foreach (var decal in _tableComponent.LegacyContainer.Decals) { - _decals.Add(new Decal(decal)); - } - foreach (var dispReel in _tableComponent.LegacyContainer.DispReels) { - _dispReels[dispReel.Name] = new DispReel(dispReel); - } - foreach (var flasher in _tableComponent.LegacyContainer.Flashers) { - _flashers[flasher.Name] = new Flasher(flasher); - } - foreach (var lightSeq in _tableComponent.LegacyContainer.LightSeqs) { - _lightSeqs[lightSeq.Name] = new LightSeq(lightSeq); - } - foreach (var textBox in _tableComponent.LegacyContainer.TextBoxes) { - _textBoxes[textBox.Name] = new TextBox(textBox); - } - foreach (var timer in _tableComponent.LegacyContainer.Timers) { - _timers[timer.Name] = new Timer(timer); - } - - // count stuff and update table data - _tableComponent.LegacyContainer.TableData.NumCollections = Collections.Count; - _tableComponent.LegacyContainer.TableData.NumFonts = 0; // todo handle fonts - _tableComponent.LegacyContainer.TableData.NumGameItems = RecomputeGameItemStorageIDs(ItemDatas); - _tableComponent.LegacyContainer.TableData.NumVpeGameItems = RecomputeGameItemStorageIDs(VpeItemDatas); - _tableComponent.LegacyContainer.TableData.NumTextures = _tableComponent.LegacyContainer.Textures.Count(t => t.IsSet); - _tableComponent.LegacyContainer.TableData.NumSounds = _tableComponent.LegacyContainer.Sounds.Count(t => t.IsSet); - _tableComponent.LegacyContainer.TableData.NumMaterials = _tableComponent.LegacyContainer.TableData.Materials.Length; - - // add/merge physical materials from asset folder - #if UNITY_EDITOR - var guids = AssetDatabase.FindAssets("t:PhysicsMaterial", null); - foreach (var guid in guids) { - var assetPath = AssetDatabase.GUIDToAssetPath(guid); - var matAsset = AssetDatabase.LoadAssetAtPath(assetPath); - var name = Path.GetFileNameWithoutExtension(assetPath); - if (!_materials.ContainsKey(name.ToLower())) { - continue; - } - var matTable = _materials[name.ToLower()]; - matTable.Elasticity = matAsset.Elasticity; - matTable.ElasticityFalloff = matAsset.ElasticityFalloff; - matTable.Friction = matAsset.Friction; - matTable.ScatterAngle = matAsset.ScatterAngle; - } - _tableComponent.LegacyContainer.TableData.Materials = _materials.Values.ToArray(); - _tableComponent.LegacyContainer.TableData.NumMaterials = _materials.Count; - #endif - } - - private static int RecomputeGameItemStorageIDs(IEnumerable datas) - { - var itemDatas = datas.ToArray(); - var assignedItems = from d in itemDatas where d.StorageIndex > -1 orderby d.StorageIndex select d; - var unassignedItems = from d in itemDatas where d.StorageIndex == -1 select d; - var orderedItems = assignedItems.Concat(unassignedItems).ToArray(); - - if (orderedItems.Length != itemDatas.Length) { - throw new Exception($"Internal error, orderedItems.Length = {orderedItems.Length}, while itemDatas.Length = {itemDatas.Length}."); - } - - for (var i = 0; i < orderedItems.Length; i++) { - orderedItems[i].StorageIndex = i; - } - - return orderedItems.Length; } private IEnumerable RetrieveSounds() @@ -209,98 +81,5 @@ protected override void Clear() base.Clear(); _materials.Clear(); } - - private static void WalkChildren(IEnumerable node, Action action) - { - foreach (Transform childTransform in node) { - action(childTransform); - WalkChildren(childTransform, action); - } - } - - private void RefreshChild(Component node, bool forExport) - { - Add(node.GetComponent(), forExport); - } - - private void Add(IMainComponent comp, bool forExport) - { - if (comp == null) { - return; - } - var name = comp.name; - switch (comp) { - case BumperComponent bumperComponent: - var bumperData = bumperComponent.CopyDataTo(_tableComponent.LegacyContainer.Bumpers.ContainsKey(name) ? _tableComponent.LegacyContainer.Bumpers[name] : new BumperData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Bumper(bumperData)); - break; - case FlipperComponent flipperComponent: - var flipperData = flipperComponent.CopyDataTo(_tableComponent.LegacyContainer.Flippers.ContainsKey(name) ? _tableComponent.LegacyContainer.Flippers[name] : new FlipperData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Flipper(flipperData)); - break; - case GateComponent gateComponent: - var gatData = gateComponent.CopyDataTo(_tableComponent.LegacyContainer.Gates.ContainsKey(name) ? _tableComponent.LegacyContainer.Gates[name] : new GateData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Gate(gatData)); - break; - case TargetComponent targetComponent: - var hitTargetData = targetComponent.CopyDataTo(_tableComponent.LegacyContainer.HitTargets.ContainsKey(name) ? _tableComponent.LegacyContainer.HitTargets[name] : new HitTargetData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new HitTarget(hitTargetData)); - break; - case KickerComponent kickerComponent: - var kickerData = kickerComponent.CopyDataTo(_tableComponent.LegacyContainer.Kickers.ContainsKey(name) ? _tableComponent.LegacyContainer.Kickers[name] : new KickerData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Kicker(kickerData)); - break; - case LightComponent lightComponent: - var lightData = lightComponent.CopyDataTo(_tableComponent.LegacyContainer.Lights.ContainsKey(name) ? _tableComponent.LegacyContainer.Lights[name] : new LightData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Light(lightData)); - break; - case PlungerComponent plungerComponent: - var plungerData = plungerComponent.CopyDataTo(_tableComponent.LegacyContainer.Plungers.ContainsKey(name) ? _tableComponent.LegacyContainer.Plungers[name] : new PlungerData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Plunger(plungerData)); - break; - case PrimitiveComponent primitiveComponent: - var primitiveData = primitiveComponent.CopyDataTo(_tableComponent.LegacyContainer.Primitives.ContainsKey(name) ? _tableComponent.LegacyContainer.Primitives[name] : new PrimitiveData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Primitive(primitiveData)); - break; - case RampComponent rampComponent: - var rampData = rampComponent.CopyDataTo(_tableComponent.LegacyContainer.Ramps.ContainsKey(name) ? _tableComponent.LegacyContainer.Ramps[name] : new RampData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Ramp(rampData)); - break; - case RubberComponent rubberComponent: - var rubberData = rubberComponent.CopyDataTo(_tableComponent.LegacyContainer.Rubbers.ContainsKey(name) ? _tableComponent.LegacyContainer.Rubbers[name] : new RubberData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Rubber(rubberData)); - break; - case SpinnerComponent spinnerComponent: - var spinnerData = spinnerComponent.CopyDataTo(_tableComponent.LegacyContainer.Spinners.ContainsKey(name) ? _tableComponent.LegacyContainer.Spinners[name] : new SpinnerData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Spinner(spinnerData)); - break; - case SurfaceComponent surfaceComponent: - var surfaceData = surfaceComponent.CopyDataTo(_tableComponent.LegacyContainer.Surfaces.ContainsKey(name) ? _tableComponent.LegacyContainer.Surfaces[name] : new SurfaceData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Surface(surfaceData)); - break; - case TriggerComponent triggerComponent: - var triggerData = triggerComponent.CopyDataTo(_tableComponent.LegacyContainer.Triggers.ContainsKey(name) ? _tableComponent.LegacyContainer.Triggers[name] : new TriggerData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Trigger(triggerData)); - break; - case TroughComponent troughComponent: - var troughData = troughComponent.CopyDataTo(new TroughData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new Trough(troughData)); - break; - case MetalWireGuideComponent metalWireGuideComponent: - var metalWireGuideData = metalWireGuideComponent.CopyDataTo(_tableComponent.LegacyContainer.MetalWireGuides.ContainsKey(name) ? _tableComponent.LegacyContainer.MetalWireGuides[name] : new MetalWireGuideData(), MaterialNames, TextureNames, forExport); - Add(comp.gameObject.name, new MetalWireGuide(metalWireGuideData)); - break; - } - } - - private void Add(string name, T item) where T : IItem - { - var dict = GetItemDictionary(); - if (dict.ContainsKey(name.ToLower())) { - Logger.Warn($"{item.GetType()} {name} already added."); - } else { - dict.Add(name.ToLower(), item); - } - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs index d8c5fb7e4..f63c15142 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs @@ -105,15 +105,6 @@ public override IEnumerable SetReferencedData(TableData data, Tab return Array.Empty(); } - public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) - { - data.TableHeight = 0; - data.GlobalDifficulty = GlobalDifficulty; - data.OverridePhysics = OverridePhysics; - - return data; - } - #endregion public Vector3 GetTableCenter() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 2351d6c21..735cc150f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -191,45 +191,6 @@ public override IEnumerable SetReferencedData(TriggerData data, T return Array.Empty(); } - public override TriggerData CopyDataTo(TriggerData data, string[] materialNames, string[] textureNames, bool forExport) - { - // name and transforms - data.Name = name; - data.Center = new Vertex2D(Position.x, Position.y); - data.Rotation = Rotation; - - // geometry - data.DragPoints = DragPoints; - - // visibility - data.IsVisible = GetEnabled(); - - // mesh - var meshComponent = GetComponent(); - if (meshComponent) { - data.WireThickness = meshComponent.WireThickness; - data.Shape = meshComponent.Shape; - } - - // collider - var collComponent = GetComponent(); - if (collComponent) { - data.IsEnabled = collComponent.gameObject.activeInHierarchy; - data.HitHeight = collComponent.HitHeight; - data.Radius = collComponent.HitCircleRadius; - } else { - data.IsEnabled = false; - } - - // animation - var animComponent = GetComponent(); - if (animComponent) { - animComponent.AnimSpeed = data.AnimSpeed; - } - - return data; - } - public override void CopyFromObject(GameObject go) { var triggerComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs index 8dff2ae11..06aa57e9c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs @@ -181,23 +181,6 @@ public override IEnumerable SetReferencedData(TroughData data, Ta return Array.Empty(); } - public override TroughData CopyDataTo(TroughData data, string[] materialNames, string[] textureNames, bool forExport) - { - data.Name = name; - - data.Type = Type; - data.PlayfieldEntrySwitch = PlayfieldEntrySwitch == null ? string.Empty : PlayfieldEntrySwitch.name; - data.PlayfieldExitKicker = PlayfieldExitKicker == null ? string.Empty : PlayfieldExitKicker.name; - data.BallCount = BallCount; - data.SwitchCount = SwitchCount; - data.JamSwitch = JamSwitch; - data.RollTime = RollTime; - data.TransitionTime = TransitionTime; - data.KickTime = KickTime; - - return data; - } - #endregion #region ISwitchableDevice From 452085e56cac95389fd39832fb31dcc408e6b06d Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 9 Jan 2025 21:43:47 +0100 Subject: [PATCH 202/208] cleanup: Make UpdateVisibility() work again. --- .../VPT/ItemInspector.cs | 8 +-- .../VPT/IMainRenderableComponent.cs | 2 + .../VPT/MainRenderableComponent.cs | 5 ++ .../VPT/Ramp/RampComponent.cs | 55 +++++++++---------- .../VPT/Surface/SlingshotComponent.cs | 1 + 5 files changed, 39 insertions(+), 32 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 8d54b3b85..6da673e1e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -61,12 +61,12 @@ private void OnUndoRedoPerformed() case IMeshComponent meshItem: meshItem.MainRenderableComponent.RebuildMeshes(); meshItem.MainRenderableComponent.UpdateTransforms(); - // meshItem.MainRenderableComponent.UpdateVisibility(); + meshItem.MainRenderableComponent.UpdateVisibility(); break; case IMainRenderableComponent mainItem: mainItem.RebuildMeshes(); mainItem.UpdateTransforms(); - // mainItem.UpdateVisibility(); + mainItem.UpdateVisibility(); break; } } @@ -103,7 +103,7 @@ protected void EndEditing() meshItem.MainRenderableComponent.UpdateTransforms(); } if (_visibilityDirty) { - // meshItem.MainRenderableComponent.UpdateVisibility(); + meshItem.MainRenderableComponent.UpdateVisibility(); } break; @@ -115,7 +115,7 @@ protected void EndEditing() mainItem.UpdateTransforms(); } if (_visibilityDirty) { - // mainItem.UpdateVisibility(); + mainItem.UpdateVisibility(); } break; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs index ffec71b8f..4a7f19e7d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs @@ -25,9 +25,11 @@ public interface IMainRenderableComponent : IMainComponent ///
void RebuildMeshes(); void UpdateTransforms(); + void UpdateVisibility(); void CopyFromObject(GameObject go); + // ReSharper disable once InconsistentNaming Transform transform { get; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 3a7c5f5c9..4dfcd89ae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -110,6 +110,11 @@ public virtual void UpdateTransforms() } } + public virtual void UpdateVisibility() + { + // do nothing per default + } + protected static void CopyMaterialName(MeshRenderer mr, string[] materialNames, string[] textureNames, ref string materialName) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 543a54e8d..14209adaa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -187,34 +187,33 @@ public float Height(Vector2 pos, Vector3 diff) { return vVertex[iSeg].Z + startLength / totalLength * (topHeight - bottomHeight) + bottomHeight; } - // todo revisit - // public override void UpdateVisibility() - // { - // // visibility - // var wallComponent = GetComponentInChildren(true); - // var floorComponent = GetComponentInChildren(true); - // var wireComponent = GetComponentInChildren(true); - // var isVisible = wireComponent && wireComponent.gameObject.activeInHierarchy || - // floorComponent && floorComponent.gameObject.activeInHierarchy; - // if (IsWireRamp) { - // if (wireComponent) wireComponent.gameObject.SetActive(isVisible); - // if (floorComponent) { - // floorComponent.gameObject.SetActive(false); - // floorComponent.ClearMeshVertices(); - // } - // if (wallComponent) { - // wallComponent.gameObject.SetActive(false); - // wallComponent.ClearMeshVertices(); - // } - // } else { - // if (wireComponent) { - // wireComponent.gameObject.SetActive(false); - // wireComponent.ClearMeshVertices(); - // } - // if (floorComponent) floorComponent.gameObject.SetActive(isVisible); - // if (wallComponent) wallComponent.gameObject.SetActive(isVisible && (_leftWallHeightVisible > 0 || _rightWallHeightVisible > 0)); - // } - // } + public override void UpdateVisibility() + { + // visibility + var wallComponent = GetComponentInChildren(true); + var floorComponent = GetComponentInChildren(true); + var wireComponent = GetComponentInChildren(true); + var isVisible = wireComponent && wireComponent.gameObject.activeInHierarchy || + floorComponent && floorComponent.gameObject.activeInHierarchy; + if (IsWireRamp) { + if (wireComponent) wireComponent.gameObject.SetActive(isVisible); + if (floorComponent) { + floorComponent.gameObject.SetActive(false); + floorComponent.ClearMeshVertices(); + } + if (wallComponent) { + wallComponent.gameObject.SetActive(false); + wallComponent.ClearMeshVertices(); + } + } else { + if (wireComponent) { + wireComponent.gameObject.SetActive(false); + wireComponent.ClearMeshVertices(); + } + if (floorComponent) floorComponent.gameObject.SetActive(isVisible); + if (wallComponent) wallComponent.gameObject.SetActive(isVisible && (_leftWallHeightVisible > 0 || _rightWallHeightVisible > 0)); + } + } public void UpdateChildrenTransforms() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index 878d85c4c..a10a776a1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -166,6 +166,7 @@ private IEnumerator Animate() public IMainRenderableComponent MainRenderableComponent => this; public void UpdateTransforms() { } + public void UpdateVisibility() { } public void RebuildMeshes() { From d1cda92368eb15690de6983acc506ffb620ee455 Mon Sep 17 00:00:00 2001 From: freezy Date: Thu, 9 Jan 2025 23:31:01 +0100 Subject: [PATCH 203/208] Revert "cleanup: Ditch VPX export." This reverts commit c2bc96794051cb3a939493944d89e20113ff4cfa. --- .../Toolbox/ToolboxEditor.cs | 6 +- .../VPT/Bumper/BumperComponent.cs | 38 +++ .../VPT/Flipper/FlipperComponent.cs | 44 ++++ .../VPT/Gate/GateComponent.cs | 43 ++++ .../VPT/HitTarget/DropTargetComponent.cs | 34 +++ .../VPT/HitTarget/HitTargetComponent.cs | 30 +++ .../VPT/HitTarget/TargetComponent.cs | 14 ++ .../VPT/Kicker/KickerComponent.cs | 29 +++ .../VPT/Light/LightComponent.cs | 40 ++++ .../VisualPinball.Unity/VPT/MainComponent.cs | 2 + .../VisualPinball.Unity/VPT/MeshComponent.cs | 1 + .../MetalWireGuide/MetalWireGuideComponent.cs | 39 ++++ .../VPT/Playfield/PlayfieldComponent.cs | 35 +++ .../VPT/Plunger/PlungerComponent.cs | 52 +++++ .../VPT/Primitive/PrimitiveComponent.cs | 62 +++++ .../VPT/Ramp/RampComponent.cs | 56 +++++ .../VPT/Rubber/RubberComponent.cs | 38 +++ .../VPT/Spinner/SpinnerComponent.cs | 39 ++++ .../VPT/Surface/SurfaceComponent.cs | 42 ++++ .../VPT/Table/SceneTableContainer.cs | 221 ++++++++++++++++++ .../VPT/Table/TableComponent.cs | 9 + .../VPT/Trigger/TriggerComponent.cs | 39 ++++ .../VPT/Trough/TroughComponent.cs | 17 ++ 23 files changed, 929 insertions(+), 1 deletion(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs index e8f48c6c3..107e1b2a6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Toolbox/ToolboxEditor.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; @@ -24,7 +25,6 @@ using VisualPinball.Engine.VPT.Gate; using VisualPinball.Engine.VPT.HitTarget; using VisualPinball.Engine.VPT.Kicker; -using VisualPinball.Engine.VPT.MetalWireGuide; using VisualPinball.Engine.VPT.Plunger; using VisualPinball.Engine.VPT.Primitive; using VisualPinball.Engine.VPT.Ramp; @@ -34,6 +34,7 @@ using VisualPinball.Engine.VPT.Table; using VisualPinball.Engine.VPT.Trigger; using VisualPinball.Engine.VPT.Trough; +using VisualPinball.Engine.VPT.MetalWireGuide; using Light = VisualPinball.Engine.VPT.Light.Light; using Texture = UnityEngine.Texture; @@ -210,6 +211,7 @@ private static bool CreateButton(string label, Texture icon, float iconSize, GUI private void CreateItem(Func create, string actionName) where TItem : IItem { var tableContainer = TableComponent.TableContainer; + tableContainer.Refresh(); var item = create(tableContainer.Table); Selection.activeGameObject = CreateRenderable(item); ItemCreated?.Invoke(Selection.activeGameObject); @@ -219,12 +221,14 @@ private void CreateItem(Func create, string actionName) whe private GameObject CreateRenderable(IItem item) { var converter = new VpxSceneConverter(TableComponent); + TableComponent.TableContainer.Refresh(); return converter.InstantiateAndPersistPrefab(item).GameObject; } private void CreatePrefab(string groupName, string path) where T : Component { var converter = new VpxSceneConverter(TableComponent); + TableComponent.TableContainer.Refresh(); var parentGo = converter.GetGroupParent(groupName); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index caf03e827..bcdcc59ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -27,6 +27,7 @@ using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Table; @@ -202,6 +203,43 @@ public override IEnumerable SetReferencedData(BumperData data, Ta return Array.Empty(); } + public override BumperData CopyDataTo(BumperData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.Radius = Radius; + data.HeightScale = HeightScale; + data.Orientation = Orientation; + + // children visibility + data.IsBaseVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsCapVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsRingVisible = CopyMaterialName(data, materialNames, textureNames); + data.IsSocketVisible = CopyMaterialName(data, materialNames, textureNames); + + // collider + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + data.Threshold = collComponent.Threshold; + data.Force = collComponent.Force; + data.Scatter = collComponent.Scatter; + data.HitEvent = collComponent.HitEvent; + } else { + data.IsCollidable = false; + } + + // ring animation + var ringAnimComponent = GetComponentInChildren(); + if (ringAnimComponent) { + data.RingSpeed = ringAnimComponent.RingSpeed; + data.RingDropOffset = ringAnimComponent.RingDropOffset; + } + + return data; + } + private bool CopyMaterialName(BumperData data, string[] materialNames, string[] textureNames) where TComponent : MonoBehaviour { var skirtComp = GetComponentInChildren(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 5a2e3e6b4..9bc4a5b3c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -259,6 +259,50 @@ public override IEnumerable SetReferencedData(FlipperData data, T return Array.Empty(); } + public override FlipperData CopyDataTo(FlipperData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.StartAngle = StartAngle; + + // geometry + data.Height = _height; + data.BaseRadius = _baseRadius; + data.EndRadius = _endRadius; + data.EndAngle = EndAngle; + data.FlipperRadiusMin = FlipperRadiusMin; + data.FlipperRadiusMax = FlipperRadiusMax; + data.RubberThickness = _rubberThickness; + data.RubberHeight = _rubberHeight; + data.RubberWidth = _rubberWidth; + + // states + data.IsEnabled = IsEnabled; + data.IsDualWound = IsDualWound; + + // children visibility + var baseMesh = GetComponentInChildren(); + data.IsVisible = baseMesh && baseMesh.gameObject.activeInHierarchy; + + // collider data + var colliderComponent = gameObject.GetComponent(); + if (colliderComponent) { + data.Mass = colliderComponent.Mass; + data.Strength = colliderComponent.Strength; + data.Elasticity = colliderComponent.Elasticity; + data.ElasticityFalloff = colliderComponent.ElasticityFalloff; + data.Friction = colliderComponent.Friction; + data.Return = colliderComponent.Return; + data.RampUp = colliderComponent.RampUp; + data.TorqueDamping = colliderComponent.TorqueDamping; + data.TorqueDampingAngle = colliderComponent.TorqueDampingAngle; + data.Scatter = colliderComponent.Scatter; + } + + return data; + } + public override void CopyFromObject(GameObject go) { // main component diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 48adf1494..20f5bf2b6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -213,6 +213,49 @@ public override IEnumerable SetReferencedData(GateData data, Tabl return Array.Empty(); } + public override GateData CopyDataTo(GateData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Center = Position.ToVertex2Dxy(); + data.Name = name; + data.Rotation = Rotation; + data.Height = Position.z; + data.Length = Length; + + data.GateType = _type; + + // visibility + foreach (var mf in GetComponentsInChildren()) { + switch (mf.gameObject.name) { + case BracketObjectName: + data.ShowBracket = mf.gameObject.activeInHierarchy; + break; + case WireObjectName: + data.IsVisible = mf.gameObject.activeInHierarchy; + break; + } + } + + // collision data + var colliderComponent = gameObject.GetComponent(); + if (colliderComponent) { + data.IsCollidable = colliderComponent.enabled; + + data.AngleMin = math.radians(colliderComponent._angleMin); + data.AngleMax = math.radians(colliderComponent._angleMax); + data.Damping = colliderComponent.Damping; + data.Elasticity = colliderComponent.Elasticity; + data.Friction = colliderComponent.Friction; + data.GravityFactor = colliderComponent.GravityFactor; + data.TwoWay = colliderComponent._twoWay; + + } else { + data.IsCollidable = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { // collider data diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs index 570db919b..a381dd3fb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs @@ -92,6 +92,40 @@ public override IEnumerable SetReferencedData(HitTargetData data, return Array.Empty(); } + public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) + { + base.CopyDataTo(data, materialNames, textureNames, forExport); + + // collision data + var colliderComponent = GetComponent(); + if (colliderComponent) { + data.IsCollidable = colliderComponent.enabled; + data.Threshold = colliderComponent.Threshold; + data.UseHitEvent = colliderComponent.UseHitEvent; + data.PhysicsMaterial = colliderComponent.PhysicsMaterial == null ? string.Empty : colliderComponent.PhysicsMaterial.name; + data.IsLegacy = colliderComponent.IsLegacy; + + data.OverwritePhysics = colliderComponent.OverwritePhysics; + data.Elasticity = colliderComponent.Elasticity; + data.ElasticityFalloff = colliderComponent.ElasticityFalloff; + data.Friction = colliderComponent.Friction; + data.Scatter = colliderComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + // animation data + var dropTargetAnimationComponent = GetComponent(); + if (dropTargetAnimationComponent) { + data.DropSpeed = dropTargetAnimationComponent.Speed; + data.RaiseDelay = dropTargetAnimationComponent.RaiseDelay; + data.IsDropped = dropTargetAnimationComponent.IsDropped; + } + + return data; + } + #endregion #region Runtime diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs index 45f097e68..ae318f888 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs @@ -77,6 +77,36 @@ public override IEnumerable SetReferencedData(HitTargetData data, return Array.Empty(); } + public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) + { + base.CopyDataTo(data, materialNames, textureNames, forExport); + + // collision data + var colliderComponent = GetComponent(); + if (colliderComponent) { + data.IsCollidable = colliderComponent.enabled; + data.Threshold = colliderComponent.Threshold; + data.PhysicsMaterial = colliderComponent.PhysicsMaterial == null ? string.Empty : colliderComponent.PhysicsMaterial.name; + + data.OverwritePhysics = colliderComponent.OverwritePhysics; + data.Elasticity = colliderComponent.Elasticity; + data.ElasticityFalloff = colliderComponent.ElasticityFalloff; + data.Friction = colliderComponent.Friction; + data.Scatter = colliderComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + // animation data + var animationComponent = GetComponent(); + if (animationComponent) { + data.DropSpeed = animationComponent.Speed; + } + + return data; + } + #endregion #region Runtime diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index 55e7ee558..f84318b66 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -127,6 +127,20 @@ public override IEnumerable SetData(HitTargetData data) return updatedComponents; } + public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Name = name; + data.Position = Position.ToVertex3D(); + data.RotZ = Rotation; + data.Size = ((Vector3)Size).ToVertex3D(); + + data.TargetType = _targetType; + data.IsVisible = GetEnabled(); + + return data; + } + public override void CopyFromObject(GameObject go) { // dt collider diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index b2a52e9cb..516181f0b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -179,6 +179,35 @@ public override IEnumerable SetReferencedData(KickerData data, Ta return Array.Empty(); } + public override KickerData CopyDataTo(KickerData data, string[] materialNames, string[] textureNames, + bool forExport) + { + // name and transforms + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.Orientation = Orientation; + data.Radius = Radius; + + data.KickerType = KickerType; + + // todo visibility is set by the type + + var colliderComponent = gameObject.GetComponent(); + if (colliderComponent) { + data.IsEnabled = colliderComponent.enabled; + data.Scatter = colliderComponent.Scatter; + data.HitAccuracy = colliderComponent.HitAccuracy; + data.HitHeight = colliderComponent.HitHeight; + data.FallThrough = colliderComponent.FallThrough; + data.LegacyMode = colliderComponent.LegacyMode; + + } else { + data.IsEnabled = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { // main component diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 20f8e7a44..46c1c66a0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -408,6 +408,46 @@ public override IEnumerable SetReferencedData(LightData data, Tab return Array.Empty(); } + public override LightData CopyDataTo(LightData data, string[] materialNames, string[] textureNames, bool forExport) + { + var pos = (Vector3)transform.localPosition.TranslateToVpx(); + + // name and position + data.Name = name; + data.Center = pos.ToVertex2Dxy(); + data.MeshRadius = BulbSize; + + // logical params + data.State = (int)State; + data.BlinkPattern = BlinkPattern; + data.BlinkInterval = BlinkInterval; + data.FadeSpeedUp = FadeSpeedUp; + data.FadeSpeedDown = FadeSpeedDown; + + // insert mesh + var insertMeshComponent = GetComponentInChildren(); + if (insertMeshComponent) { + data.DragPoints = insertMeshComponent.DragPoints; + } + + // visibility + data.ShowBulbMesh = false; + foreach (var mf in GetComponentsInChildren(true)) { + if (!mf.sharedMesh) { + continue; + } + + switch (mf.sharedMesh.name) { + case BulbMeshName: + case SocketMeshName: + data.ShowBulbMesh = data.ShowBulbMesh || mf.gameObject.activeInHierarchy; + break; + } + } + + return data; + } + public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs index 2dce769dc..ca1142efb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs @@ -53,6 +53,8 @@ public int PlayfieldDetailLevel { /// A list of updated components (if this item has impact on other components) public abstract IEnumerable SetReferencedData(TData data, Table table, IMaterialProvider materialProvider, ITextureProvider textureProvider, Dictionary components); + public abstract TData CopyDataTo(TData data, string[] materialNames, string[] textureNames, bool forExport); + public abstract bool HasProceduralMesh { get; } public abstract ItemType ItemType { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs index 4e3c670f9..e125fb66e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MeshComponent.cs @@ -110,6 +110,7 @@ private void UpdateMesh() { var data = MainComponent.InstantiateData(); MainComponent.UpdateTransforms(); + MainComponent.CopyDataTo(data, null, null, false); var mesh = GetMesh(data); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs index 9e72d33c1..289a8da60 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs @@ -155,6 +155,45 @@ public override IEnumerable SetReferencedData(MetalWireGuideData return Array.Empty(); } + public override MetalWireGuideData CopyDataTo(MetalWireGuideData data, string[] materialNames, string[] textureNames, bool forExport) + { + // update the name + data.Name = name; + + // geometry + data.Height = _height; + data.RotX = Rotation.x; + data.RotY = Rotation.y; + data.RotZ = Rotation.z; + data.Thickness = _thickness; + data.Bendradius = _bendradius; + data.DragPoints = DragPoints; + + // visibility + data.IsVisible = GetEnabled(); + + // collision + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + + data.HitEvent = collComponent.HitEvent; + data.HitHeight = collComponent.HitHeight; + + data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; + data.OverwritePhysics = collComponent.OverwritePhysics; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs index 8f43cc2e5..610aecdca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs @@ -175,6 +175,41 @@ public IEnumerable SetReferencedData(PrimitiveData primitiveData, return updatedComponents; } + public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) + { + var physicsEngine = GetComponentInParent(); + + // position + data.TableHeight = 0; + data.GlassHeight = GlassHeight; + data.Left = Left; + data.Right = Right; + data.Top = Top; + data.Bottom = Bottom; + data.AngleTiltMax = AngleTiltMax; + data.AngleTiltMin = AngleTiltMin; + if (physicsEngine) { + data.Gravity = physicsEngine.GravityStrength; + } + + // playfield material + data.Image = _playfieldImage; + data.PlayfieldMaterial = _playfieldMaterial; + + // collider data + var collComponent = GetComponent(); + if (collComponent) { + data.Gravity = collComponent.Gravity; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + data.DefaultScatter = collComponent.DefaultScatter; + } + + return data; + } + public override void CopyFromObject(GameObject go) { throw new Exception("Copying object data is currently only used for replacing objects. Don't replace the playfield. Refactor this if necessary in the future."); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index 17494007b..e73a16aad 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -24,6 +24,7 @@ using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Plunger; using VisualPinball.Engine.VPT.Table; @@ -195,6 +196,57 @@ public override IEnumerable SetReferencedData(PlungerData data, T return Array.Empty(); } + public override PlungerData CopyDataTo(PlungerData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name, geometry and position + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.Width = Width; + data.Height = Height; + data.ZAdjust = Position.z; + + // collider data + var collComponent = GetComponent(); + if (collComponent) { + data.Stroke = collComponent.Stroke; + data.SpeedPull = collComponent.SpeedPull; + data.SpeedFire = collComponent.SpeedFire; + data.MechStrength = collComponent.MechStrength; + data.ParkPosition = collComponent.ParkPosition; + data.ScatterVelocity = collComponent.ScatterVelocity; + data.MomentumXfer = collComponent.MomentumXfer; + data.IsMechPlunger = collComponent.IsMechPlunger; + data.AutoPlunger = collComponent.IsAutoPlunger; + } + + // rod mesh + var rodMesh = GetComponentInChildren(true); + if (rodMesh) { + data.TipShape = rodMesh.TipShape; + data.RodDiam = rodMesh.RodDiam; + data.RingGap = rodMesh.RingGap; + data.RingDiam = rodMesh.RingDiam; + data.RingWidth = rodMesh.RingWidth; + } + + // spring mesh + var springMesh = GetComponentInChildren(true); + if (springMesh) { + data.SpringDiam = springMesh.SpringDiam; + data.SpringGauge = springMesh.SpringGauge; + data.SpringLoops = springMesh.SpringLoops; + data.SpringEndLoops = springMesh.SpringEndLoops; + } + + // type + var hasSpringMesh = springMesh && springMesh.isActiveAndEnabled; + var hasRodMesh = rodMesh && rodMesh.isActiveAndEnabled; + data.IsVisible = hasRodMesh; + data.Type = hasSpringMesh && hasRodMesh ? PlungerType.PlungerTypeCustom : PlungerType.PlungerTypeModern; + + return data; + } + public override void CopyFromObject(GameObject go) { // primitives don't have any special params. diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index ef9a08710..6ac232078 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -132,6 +132,68 @@ public override IEnumerable SetReferencedData(PrimitiveData data, return updatedComponents; } + public override PrimitiveData CopyDataTo(PrimitiveData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + var t = transform; + data.Name = name; + data.Position = Position.ToVertex3D(); + data.Size = t.localScale.ToVertex3D(); + var vpxRotation = t.localEulerAngles.TranslateToVpx(); + data.RotAndTra = new[] { + vpxRotation.x, vpxRotation.y, vpxRotation.z, + 0, 0, 0, + 0, 0, 0, + }; + + // materials + var mr = GetComponent(); + if (mr) { + CopyMaterialName(mr, materialNames, textureNames, ref data.Material, ref data.Image, ref data.NormalMap); + } + + // mesh + var meshComponent = GetComponent(); + if (meshComponent) { + data.IsVisible = GetEnabled(); + data.Sides = meshComponent.Sides; + data.Use3DMesh = !meshComponent.UseLegacyMesh; + + if (forExport && !meshComponent.UseLegacyMesh) { + var mf = GetComponent(); + if (mf) { + data.Mesh = mf.sharedMesh.ToVpMesh().TransformToVpx(); + data.NumIndices = data.Mesh.Indices.Length; + data.NumVertices = data.Mesh.Vertices.Length; + } + } + } + + // update collision + // todo at some point we need to be able to toggle collidable during gameplay, + // todo but for now let's keep things static. + var collComponent = GetComponent(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + data.IsToy = false; + + data.HitEvent = collComponent.HitEvent; + data.Threshold = collComponent.Threshold; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + data.CollisionReductionFactor = collComponent.CollisionReductionFactor; + data.OverwritePhysics = collComponent.OverwritePhysics; + + } else { + data.IsCollidable = false; + data.IsToy = true; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var primitiveComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 14209adaa..714a32f8c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -325,6 +325,62 @@ public override IEnumerable SetReferencedData(RampData data, Tabl return Array.Empty(); } + public override RampData CopyDataTo(RampData data, string[] materialNames, string[] textureNames, bool forExport) + { + // update the name + data.Name = name; + + // geometry + data.DragPoints = DragPoints; + data.HeightTop = _heightTop; + data.HeightBottom = _heightBottom; + data.WidthTop = _widthTop; + data.WidthBottom = _widthBottom; + data.LeftWallHeightVisible = _leftWallHeightVisible; + data.RightWallHeightVisible = _rightWallHeightVisible; + + // type and uvs + data.RampType = _type; + data.ImageAlignment = _imageAlignment; + + // wire data + data.WireDiameter = _wireDiameter; + data.WireDistanceX = _wireDistanceX; + data.WireDistanceY = _wireDistanceY; + + // visibility + var floorComponent = GetComponentInChildren(); + var wireComponent = GetComponentInChildren(); + if (IsWireRamp) { + data.IsVisible = wireComponent && wireComponent.gameObject.activeInHierarchy; + } else { + data.IsVisible = floorComponent && floorComponent.gameObject.activeInHierarchy; + } + + // collider data + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + + data.LeftWallHeight = collComponent.LeftWallHeight; + data.RightWallHeight = collComponent.RightWallHeight; + + data.HitEvent = collComponent.HitEvent; + data.Threshold = collComponent.Threshold; + data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; + + data.OverwritePhysics = collComponent.OverwritePhysics; + data.Elasticity = collComponent.Elasticity; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index efdba353e..ad6454b2b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -160,6 +160,44 @@ public override IEnumerable SetReferencedData(RubberData data, Ta return Array.Empty(); } + public override RubberData CopyDataTo(RubberData data, string[] materialNames, string[] textureNames, bool forExport) + { + // update the name + data.Name = name; + + // geometry + data.Height = _height; + data.RotX = Rotation.x; + data.RotY = Rotation.y; + data.RotZ = Rotation.z; + data.Thickness = _thickness; + data.DragPoints = DragPoints; + + // visibility + data.IsVisible = GetEnabled(); + + // collision + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + + data.HitEvent = collComponent.HitEvent; + data.HitHeight = collComponent.HitHeight; + + data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; + data.OverwritePhysics = collComponent.OverwritePhysics; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Friction = collComponent.Friction; + data.Scatter = collComponent.Scatter; + + } else { + data.IsCollidable = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index f0f1686ca..90a6e0bb1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -28,6 +28,7 @@ using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Spinner; using VisualPinball.Engine.VPT.Table; @@ -192,6 +193,44 @@ public override IEnumerable SetReferencedData(SpinnerData data, T return Array.Empty(); } + public override SpinnerData CopyDataTo(SpinnerData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.Height = Position.z; + data.Length = Length; + data.Rotation = Rotation; + + // spinner props + data.Damping = Damping; + data.AngleMax = AngleMax; + data.AngleMin = AngleMin; + + // visibility + var isBracketActive = false; + var isAnythingElseActive = false; + foreach (var mf in GetComponentsInChildren()) { + switch (mf.sharedMesh.name) { + case BracketMeshName: + isBracketActive = mf.gameObject.activeInHierarchy; + break; + default: + isAnythingElseActive = isAnythingElseActive || mf.gameObject.activeInHierarchy; + break; + } + } + data.IsVisible = isAnythingElseActive || isBracketActive; + data.ShowBracket = isBracketActive; + + var collComponent = GetComponent(); + if (collComponent) { + data.Elasticity = collComponent.Elasticity; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var srcMainComp = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 06dfc4a82..c6a54a30a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -163,6 +163,48 @@ public override IEnumerable SetReferencedData(SurfaceData data, T return Array.Empty(); } + public override SurfaceData CopyDataTo(SurfaceData data, string[] materialNames, string[] textureNames, bool forExport) + { + // update the name + data.Name = name; + + // main props + data.HeightBottom = HeightBottom; + data.HeightTop = HeightTop; + data.DragPoints = DragPoints; + + // children visibility + var topMesh = GetComponentInChildren(); + data.IsTopBottomVisible = topMesh && topMesh.gameObject.activeInHierarchy; + var sideMesh = GetComponentInChildren(); + data.IsSideVisible = sideMesh && sideMesh.gameObject.activeInHierarchy; + + // collider data + var collComponent = GetComponentInChildren(); + if (collComponent) { + data.IsCollidable = collComponent.enabled; + + data.HitEvent = collComponent.HitEvent; + data.Threshold = collComponent.Threshold; + data.IsBottomSolid = collComponent.IsBottomSolid; + + data.PhysicsMaterial = collComponent.PhysicsMaterial ? collComponent.PhysicsMaterial.name : string.Empty; + data.SlingshotForce = collComponent.SlingshotForce; + data.SlingshotThreshold = collComponent.SlingshotThreshold; + + data.OverwritePhysics = collComponent.OverwritePhysics; + data.Elasticity = collComponent.Elasticity; + data.ElasticityFalloff = collComponent.ElasticityFalloff; + data.Scatter = collComponent.Scatter; + data.Friction = collComponent.Friction; + + } else { + data.IsCollidable = false; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var surfaceComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs index a5424f08f..2e40a2668 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs @@ -15,11 +15,39 @@ // along with this program. If not, see . using System; +using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.IO; using System.Linq; +using UnityEditor; +using UnityEngine; +using VisualPinball.Engine.VPT; +using VisualPinball.Engine.VPT.Bumper; using VisualPinball.Engine.VPT.Collection; +using VisualPinball.Engine.VPT.Decal; +using VisualPinball.Engine.VPT.DispReel; +using VisualPinball.Engine.VPT.Flasher; +using VisualPinball.Engine.VPT.Flipper; +using VisualPinball.Engine.VPT.Gate; +using VisualPinball.Engine.VPT.HitTarget; +using VisualPinball.Engine.VPT.Kicker; +using VisualPinball.Engine.VPT.Light; +using VisualPinball.Engine.VPT.LightSeq; +using VisualPinball.Engine.VPT.Plunger; +using VisualPinball.Engine.VPT.Primitive; +using VisualPinball.Engine.VPT.Ramp; +using VisualPinball.Engine.VPT.Rubber; using VisualPinball.Engine.VPT.Sound; +using VisualPinball.Engine.VPT.Spinner; +using VisualPinball.Engine.VPT.Surface; using VisualPinball.Engine.VPT.Table; +using VisualPinball.Engine.VPT.TextBox; +using VisualPinball.Engine.VPT.Timer; +using VisualPinball.Engine.VPT.Trigger; +using VisualPinball.Engine.VPT.Trough; +using VisualPinball.Engine.VPT.MetalWireGuide; +using Light = VisualPinball.Engine.VPT.Light.Light; using Material = VisualPinball.Engine.VPT.Material; using Texture = VisualPinball.Engine.VPT.Texture; @@ -69,6 +97,106 @@ public override Material GetMaterial(string name) public SceneTableContainer(TableComponent ta) { _tableComponent = ta; + + } + + public void Refresh(bool forExport = false) + { + var stopWatch = Stopwatch.StartNew(); + Clear(); + if (!_tableComponent.LegacyContainer) { + _tableComponent.LegacyContainer = ScriptableObject.CreateInstance(); + _tableComponent.LegacyContainer.TableData = new TableData(); + } + WalkChildren(_tableComponent.transform, node => RefreshChild(node, forExport)); + + _tableComponent.CopyDataTo(_tableComponent.LegacyContainer.TableData, MaterialNames, TextureNames, forExport); + var playfieldComponent = _tableComponent.GetComponentInChildren(); + playfieldComponent.CopyDataTo(_tableComponent.LegacyContainer.TableData, MaterialNames, TextureNames, forExport); + + foreach (var material in _tableComponent.LegacyContainer.TableData.Materials) { + _materials[material.Name.ToLower()] = material; + } + + Logger.Info($"Refreshed {GameItems.Count()} game items and {_materials.Count} materials in {stopWatch.ElapsedMilliseconds}ms."); + } + + public override void Save(string fileName) + { + Refresh(true); + PrepareForExport(); + + base.Save(fileName); + } + + private void PrepareForExport() + { + // fetch legacy items from container (because they are not in the scene) + foreach (var decal in _tableComponent.LegacyContainer.Decals) { + _decals.Add(new Decal(decal)); + } + foreach (var dispReel in _tableComponent.LegacyContainer.DispReels) { + _dispReels[dispReel.Name] = new DispReel(dispReel); + } + foreach (var flasher in _tableComponent.LegacyContainer.Flashers) { + _flashers[flasher.Name] = new Flasher(flasher); + } + foreach (var lightSeq in _tableComponent.LegacyContainer.LightSeqs) { + _lightSeqs[lightSeq.Name] = new LightSeq(lightSeq); + } + foreach (var textBox in _tableComponent.LegacyContainer.TextBoxes) { + _textBoxes[textBox.Name] = new TextBox(textBox); + } + foreach (var timer in _tableComponent.LegacyContainer.Timers) { + _timers[timer.Name] = new Timer(timer); + } + + // count stuff and update table data + _tableComponent.LegacyContainer.TableData.NumCollections = Collections.Count; + _tableComponent.LegacyContainer.TableData.NumFonts = 0; // todo handle fonts + _tableComponent.LegacyContainer.TableData.NumGameItems = RecomputeGameItemStorageIDs(ItemDatas); + _tableComponent.LegacyContainer.TableData.NumVpeGameItems = RecomputeGameItemStorageIDs(VpeItemDatas); + _tableComponent.LegacyContainer.TableData.NumTextures = _tableComponent.LegacyContainer.Textures.Count(t => t.IsSet); + _tableComponent.LegacyContainer.TableData.NumSounds = _tableComponent.LegacyContainer.Sounds.Count(t => t.IsSet); + _tableComponent.LegacyContainer.TableData.NumMaterials = _tableComponent.LegacyContainer.TableData.Materials.Length; + + // add/merge physical materials from asset folder + #if UNITY_EDITOR + var guids = AssetDatabase.FindAssets("t:PhysicsMaterial", null); + foreach (var guid in guids) { + var assetPath = AssetDatabase.GUIDToAssetPath(guid); + var matAsset = AssetDatabase.LoadAssetAtPath(assetPath); + var name = Path.GetFileNameWithoutExtension(assetPath); + if (!_materials.ContainsKey(name.ToLower())) { + continue; + } + var matTable = _materials[name.ToLower()]; + matTable.Elasticity = matAsset.Elasticity; + matTable.ElasticityFalloff = matAsset.ElasticityFalloff; + matTable.Friction = matAsset.Friction; + matTable.ScatterAngle = matAsset.ScatterAngle; + } + _tableComponent.LegacyContainer.TableData.Materials = _materials.Values.ToArray(); + _tableComponent.LegacyContainer.TableData.NumMaterials = _materials.Count; + #endif + } + + private static int RecomputeGameItemStorageIDs(IEnumerable datas) + { + var itemDatas = datas.ToArray(); + var assignedItems = from d in itemDatas where d.StorageIndex > -1 orderby d.StorageIndex select d; + var unassignedItems = from d in itemDatas where d.StorageIndex == -1 select d; + var orderedItems = assignedItems.Concat(unassignedItems).ToArray(); + + if (orderedItems.Length != itemDatas.Length) { + throw new Exception($"Internal error, orderedItems.Length = {orderedItems.Length}, while itemDatas.Length = {itemDatas.Length}."); + } + + for (var i = 0; i < orderedItems.Length; i++) { + orderedItems[i].StorageIndex = i; + } + + return orderedItems.Length; } private IEnumerable RetrieveSounds() @@ -81,5 +209,98 @@ protected override void Clear() base.Clear(); _materials.Clear(); } + + private static void WalkChildren(IEnumerable node, Action action) + { + foreach (Transform childTransform in node) { + action(childTransform); + WalkChildren(childTransform, action); + } + } + + private void RefreshChild(Component node, bool forExport) + { + Add(node.GetComponent(), forExport); + } + + private void Add(IMainComponent comp, bool forExport) + { + if (comp == null) { + return; + } + var name = comp.name; + switch (comp) { + case BumperComponent bumperComponent: + var bumperData = bumperComponent.CopyDataTo(_tableComponent.LegacyContainer.Bumpers.ContainsKey(name) ? _tableComponent.LegacyContainer.Bumpers[name] : new BumperData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Bumper(bumperData)); + break; + case FlipperComponent flipperComponent: + var flipperData = flipperComponent.CopyDataTo(_tableComponent.LegacyContainer.Flippers.ContainsKey(name) ? _tableComponent.LegacyContainer.Flippers[name] : new FlipperData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Flipper(flipperData)); + break; + case GateComponent gateComponent: + var gatData = gateComponent.CopyDataTo(_tableComponent.LegacyContainer.Gates.ContainsKey(name) ? _tableComponent.LegacyContainer.Gates[name] : new GateData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Gate(gatData)); + break; + case TargetComponent targetComponent: + var hitTargetData = targetComponent.CopyDataTo(_tableComponent.LegacyContainer.HitTargets.ContainsKey(name) ? _tableComponent.LegacyContainer.HitTargets[name] : new HitTargetData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new HitTarget(hitTargetData)); + break; + case KickerComponent kickerComponent: + var kickerData = kickerComponent.CopyDataTo(_tableComponent.LegacyContainer.Kickers.ContainsKey(name) ? _tableComponent.LegacyContainer.Kickers[name] : new KickerData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Kicker(kickerData)); + break; + case LightComponent lightComponent: + var lightData = lightComponent.CopyDataTo(_tableComponent.LegacyContainer.Lights.ContainsKey(name) ? _tableComponent.LegacyContainer.Lights[name] : new LightData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Light(lightData)); + break; + case PlungerComponent plungerComponent: + var plungerData = plungerComponent.CopyDataTo(_tableComponent.LegacyContainer.Plungers.ContainsKey(name) ? _tableComponent.LegacyContainer.Plungers[name] : new PlungerData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Plunger(plungerData)); + break; + case PrimitiveComponent primitiveComponent: + var primitiveData = primitiveComponent.CopyDataTo(_tableComponent.LegacyContainer.Primitives.ContainsKey(name) ? _tableComponent.LegacyContainer.Primitives[name] : new PrimitiveData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Primitive(primitiveData)); + break; + case RampComponent rampComponent: + var rampData = rampComponent.CopyDataTo(_tableComponent.LegacyContainer.Ramps.ContainsKey(name) ? _tableComponent.LegacyContainer.Ramps[name] : new RampData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Ramp(rampData)); + break; + case RubberComponent rubberComponent: + var rubberData = rubberComponent.CopyDataTo(_tableComponent.LegacyContainer.Rubbers.ContainsKey(name) ? _tableComponent.LegacyContainer.Rubbers[name] : new RubberData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Rubber(rubberData)); + break; + case SpinnerComponent spinnerComponent: + var spinnerData = spinnerComponent.CopyDataTo(_tableComponent.LegacyContainer.Spinners.ContainsKey(name) ? _tableComponent.LegacyContainer.Spinners[name] : new SpinnerData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Spinner(spinnerData)); + break; + case SurfaceComponent surfaceComponent: + var surfaceData = surfaceComponent.CopyDataTo(_tableComponent.LegacyContainer.Surfaces.ContainsKey(name) ? _tableComponent.LegacyContainer.Surfaces[name] : new SurfaceData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Surface(surfaceData)); + break; + case TriggerComponent triggerComponent: + var triggerData = triggerComponent.CopyDataTo(_tableComponent.LegacyContainer.Triggers.ContainsKey(name) ? _tableComponent.LegacyContainer.Triggers[name] : new TriggerData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Trigger(triggerData)); + break; + case TroughComponent troughComponent: + var troughData = troughComponent.CopyDataTo(new TroughData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new Trough(troughData)); + break; + case MetalWireGuideComponent metalWireGuideComponent: + var metalWireGuideData = metalWireGuideComponent.CopyDataTo(_tableComponent.LegacyContainer.MetalWireGuides.ContainsKey(name) ? _tableComponent.LegacyContainer.MetalWireGuides[name] : new MetalWireGuideData(), MaterialNames, TextureNames, forExport); + Add(comp.gameObject.name, new MetalWireGuide(metalWireGuideData)); + break; + } + } + + private void Add(string name, T item) where T : IItem + { + var dict = GetItemDictionary(); + if (dict.ContainsKey(name.ToLower())) { + Logger.Warn($"{item.GetType()} {name} already added."); + } else { + dict.Add(name.ToLower(), item); + } + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs index f63c15142..d8c5fb7e4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/TableComponent.cs @@ -105,6 +105,15 @@ public override IEnumerable SetReferencedData(TableData data, Tab return Array.Empty(); } + public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) + { + data.TableHeight = 0; + data.GlobalDifficulty = GlobalDifficulty; + data.OverridePhysics = OverridePhysics; + + return data; + } + #endregion public Vector3 GetTableCenter() diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 735cc150f..2351d6c21 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -191,6 +191,45 @@ public override IEnumerable SetReferencedData(TriggerData data, T return Array.Empty(); } + public override TriggerData CopyDataTo(TriggerData data, string[] materialNames, string[] textureNames, bool forExport) + { + // name and transforms + data.Name = name; + data.Center = new Vertex2D(Position.x, Position.y); + data.Rotation = Rotation; + + // geometry + data.DragPoints = DragPoints; + + // visibility + data.IsVisible = GetEnabled(); + + // mesh + var meshComponent = GetComponent(); + if (meshComponent) { + data.WireThickness = meshComponent.WireThickness; + data.Shape = meshComponent.Shape; + } + + // collider + var collComponent = GetComponent(); + if (collComponent) { + data.IsEnabled = collComponent.gameObject.activeInHierarchy; + data.HitHeight = collComponent.HitHeight; + data.Radius = collComponent.HitCircleRadius; + } else { + data.IsEnabled = false; + } + + // animation + var animComponent = GetComponent(); + if (animComponent) { + animComponent.AnimSpeed = data.AnimSpeed; + } + + return data; + } + public override void CopyFromObject(GameObject go) { var triggerComponent = go.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs index 06aa57e9c..8dff2ae11 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs @@ -181,6 +181,23 @@ public override IEnumerable SetReferencedData(TroughData data, Ta return Array.Empty(); } + public override TroughData CopyDataTo(TroughData data, string[] materialNames, string[] textureNames, bool forExport) + { + data.Name = name; + + data.Type = Type; + data.PlayfieldEntrySwitch = PlayfieldEntrySwitch == null ? string.Empty : PlayfieldEntrySwitch.name; + data.PlayfieldExitKicker = PlayfieldExitKicker == null ? string.Empty : PlayfieldExitKicker.name; + data.BallCount = BallCount; + data.SwitchCount = SwitchCount; + data.JamSwitch = JamSwitch; + data.RollTime = RollTime; + data.TransitionTime = TransitionTime; + data.KickTime = KickTime; + + return data; + } + #endregion #region ISwitchableDevice From 569cda2b0ec95bdbcf3a28a5348511bc9a4a80ee Mon Sep 17 00:00:00 2001 From: freezy Date: Fri, 10 Jan 2025 00:24:35 +0100 Subject: [PATCH 204/208] chore: Cleanup unused methods. --- .../VPT/Bumper/BumperComponent.cs | 2 -- .../VPT/Plunger/PlungerComponent.cs | 8 -------- .../VPT/Ramp/RampComponent.cs | 20 ------------------- .../VPT/Surface/SurfaceComponent.cs | 8 -------- .../VPT/Trigger/TriggerComponent.cs | 11 ---------- 5 files changed, 49 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index bcdcc59ba..fd2c803aa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -144,8 +144,6 @@ private void Start() #region Transformation - public void OnSurfaceUpdated() => UpdateTransforms(); - public override void UpdateTransforms() { base.UpdateTransforms(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index e73a16aad..4b7d05399 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -97,9 +97,6 @@ private void Awake() #region Transformation - [NonSerialized] - private float4x4 _playfieldToWorld; - public override void UpdateTransforms() { base.UpdateTransforms(); @@ -108,11 +105,6 @@ public override void UpdateTransforms() GetComponent()?.CalculateBoundingBox(); } - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 714a32f8c..79b37fc6f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -132,18 +131,10 @@ private void Awake() RegisterPhysics(physicsEngine); } - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Transformation - [NonSerialized] - private float4x4 _playfieldToWorld; - public override void UpdateTransforms() { base.UpdateTransforms(); @@ -215,17 +206,6 @@ public override void UpdateVisibility() } } - public void UpdateChildrenTransforms() - { - var children = GetComponentsInChildren(); - foreach (var child in children) { - if (ReferenceEquals(child, this)) { - continue; - } - child.transform.SetZPosition(HeightTop); - } - } - #endregion #region Conversion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index c6a54a30a..24155bd52 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -80,17 +79,10 @@ private void Awake() } } - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Transformation - [NonSerialized] - private float4x4 _playfieldToWorld; public float Height(Vector2 _) => HeightTop; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index 2351d6c21..addf7af4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -105,12 +105,6 @@ private void Awake() } } - - private void Start() - { - _playfieldToWorld = Player.PlayfieldToWorldMatrix; - } - #endregion #region Wiring @@ -127,13 +121,8 @@ private void Start() #region Transformation - [NonSerialized] - private float4x4 _playfieldToWorld; - public Vector2 Center => Position; // todo remove? - public void OnSurfaceUpdated() => UpdateTransforms(); - #endregion #region Conversion From 8c5e4d848a7d0bb73e730f2324b444488a747efb Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 11 Jan 2025 00:32:50 +0100 Subject: [PATCH 205/208] cleanup: Remove "is locked" attribute that didn't have any effect. --- .../VisualPinball.Unity.Editor/Import/VpxPrefab.cs | 1 - .../VisualPinball.Unity.Editor/VPT/ItemInspector.cs | 4 ---- .../VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs | 1 - VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs | 4 ---- 4 files changed, 10 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs index ee9c4386e..8b3a84e38 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs @@ -58,7 +58,6 @@ public VpxPrefab(Object prefab, TItem item) public void SetData() { var updatedComponents = _mainComponent.SetData(_item.Data); - _mainComponent.IsLocked = _item.Data.IsLocked; _mainComponent.EditorLayer = _item.Data.EditorLayer; _mainComponent.EditorLayerName = _item.Data.EditorLayerName; _mainComponent.EditorLayerVisibility = _item.Data.EditorLayerVisibility; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs index 6da673e1e..f826ba64f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ItemInspector.cs @@ -34,7 +34,6 @@ public abstract class ItemInspector : UnityEditor.Editor private bool _transformsDirty; private bool _visibilityDirty; - private SerializedProperty _isLockedProperty; internal const string CustomMeshLabel = "Custom Mesh"; @@ -46,8 +45,6 @@ protected virtual void OnEnable() TableComponent = (target as MonoBehaviour)?.gameObject.GetComponentInParent(); PlayfieldComponent = (target as MonoBehaviour)?.gameObject.GetComponentInParent(); - - _isLockedProperty = serializedObject.FindProperty("_isLocked"); } protected virtual void OnDisable() @@ -247,7 +244,6 @@ protected virtual void OnPreInspectorGUI() } EditorGUI.BeginChangeCheck(); - PropertyField(_isLockedProperty, "Locked"); if (EditorGUI.EndChangeCheck()) { SceneView.RepaintAll(); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index 9bc4a5b3c..6dac0a04e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -633,7 +633,6 @@ private void SetupFlipperCorrection(FlipperColliderComponent colliderComponent) var poly = GetEnclosingPolygon(23, 12); triggerComponent.DragPoints = new DragPointData[poly.Count]; - triggerComponent.IsLocked = true; triggerCollider.HitHeight = 150F; // nFozzy's recommendation, but I think 50 should be ok // this was taken from the 2021 code when we had the playfield transformed to vpx world: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs index ca1142efb..ad440692f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainComponent.cs @@ -28,10 +28,6 @@ public abstract class MainComponent : ItemComponent, IMainComponent, ILayerableItemComponent where TData : ItemData { - public bool IsLocked { get => _isLocked; set => _isLocked = value; } - - [SerializeField] private bool _isLocked; - public int PlayfieldDetailLevel { get { var playfieldComponent = GetComponentInParent(); From f27ba460d372ccc0502df4ec63f379f6bfcf5812 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 11 Jan 2025 00:50:30 +0100 Subject: [PATCH 206/208] gate: Fix visibility during import. --- .../VPT/Gate/GateComponent.cs | 44 +++---------------- .../VPT/MainRenderableComponent.cs | 23 ++++++++++ 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 20f5bf2b6..4e0a16417 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -81,15 +81,7 @@ public float Length public float PosY => Position.y; public float Height => Position.z; - public bool ShowBracket { get { - foreach (var mf in GetComponentsInChildren()) { - switch (mf.gameObject.name) { - case BracketObjectName: - return mf.gameObject.activeInHierarchy; - } - } - return false; - }} + public bool ShowBracket => GetVisibilityByComponent(); #endregion @@ -105,9 +97,6 @@ public bool ShowBracket { get { protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); - public const string BracketObjectName = "Bracket"; - public const string WireObjectName = "Wire"; - public const string MainSwitchItem = "gate_switch"; #endregion @@ -151,7 +140,6 @@ private void Awake() #endregion - #region Conversion public override IEnumerable SetData(GateData data) @@ -193,22 +181,8 @@ public override IEnumerable SetReferencedData(GateData data, Tabl ParentToSurface(data.Surface, data.Center, components); // visibility - foreach (var mf in GetComponentsInChildren()) { - switch (mf.gameObject.name) { - case BracketObjectName: - mf.gameObject.SetActive(data.IsVisible && data.ShowBracket); - break; - case WireObjectName: - #if UNITY_EDITOR - _meshName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(mf.sharedMesh)); - #endif - mf.gameObject.SetActive(data.IsVisible); - break; - default: - mf.gameObject.SetActive(data.IsVisible); - break; - } - } + SetVisibilityByComponent(data.ShowBracket); + SetMeshVisibility(data.IsVisible); return Array.Empty(); } @@ -225,16 +199,8 @@ public override GateData CopyDataTo(GateData data, string[] materialNames, strin data.GateType = _type; // visibility - foreach (var mf in GetComponentsInChildren()) { - switch (mf.gameObject.name) { - case BracketObjectName: - data.ShowBracket = mf.gameObject.activeInHierarchy; - break; - case WireObjectName: - data.IsVisible = mf.gameObject.activeInHierarchy; - break; - } - } + data.ShowBracket = GetVisibilityByComponent(); + data.IsVisible = GetMeshVisibility(); // collision data var colliderComponent = gameObject.GetComponent(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 4dfcd89ae..40911efbf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -94,6 +94,29 @@ protected void SetVisibilityByComponent(bool isVisible) where TCompo } } + protected bool GetVisibilityByComponent() where TComponent : MonoBehaviour + { + var comp = GetComponentInChildren(); + return comp && comp.gameObject.activeInHierarchy; + } + + protected void SetMeshVisibility(bool isVisible) + { + foreach (var mr in GetComponentsInChildren()) { + mr.enabled = isVisible; + } + } + + protected bool GetMeshVisibility() + { + foreach (var mr in GetComponentsInChildren()) { + if (mr.enabled) { + return true; + } + } + return false; + } + protected void ParentToSurface(string surfaceName, Vertex2D center, Dictionary components) { if (!string.IsNullOrEmpty(surfaceName)) { From 5d7fb38d587f0867f1a399e5eb790fe292c4a388 Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 11 Jan 2025 00:56:55 +0100 Subject: [PATCH 207/208] spinner: Fix visibility during import. --- .../VPT/Gate/GateComponent.cs | 2 - .../VPT/Spinner/SpinnerComponent.cs | 43 +++---------------- 2 files changed, 6 insertions(+), 39 deletions(-) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 4e0a16417..326ad880b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -23,10 +23,8 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using Unity.Mathematics; -using UnityEditor; using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index 90a6e0bb1..c9e5c6df9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -80,18 +80,7 @@ public float Length [Tooltip("Minimal angle. This allows the spinner to bounce back instead of executing a 360° rotation.")] public float AngleMin; - public bool ShowBracket { - get { - foreach (var mf in GetComponentsInChildren()) { - // todo use a component instead of relying on mesh names - switch (mf.sharedMesh.name) { - case BracketMeshName: - return mf.gameObject.activeInHierarchy; - } - } - return false; - } - } + public bool ShowBracket => GetVisibilityByComponent(); #endregion @@ -107,7 +96,6 @@ public bool ShowBracket { protected override Type MeshComponentType { get; } = typeof(MeshComponent); protected override Type ColliderComponentType { get; } = typeof(ColliderComponent); - private const string BracketMeshName = "Spinner (Bracket)"; public const string SwitchItem = "spinner_switch"; #endregion @@ -165,16 +153,8 @@ public override IEnumerable SetData(SpinnerData data) AngleMin = data.AngleMin; // visibility - foreach (var mf in GetComponentsInChildren()) { - switch (mf.sharedMesh.name) { - case BracketMeshName: - mf.gameObject.SetActive(data.IsVisible && data.ShowBracket); - break; - default: - mf.gameObject.SetActive(data.IsVisible); - break; - } - } + SetVisibilityByComponent(data.ShowBracket); + SetMeshVisibility(data.IsVisible); // collider data var collComponent = GetComponent(); @@ -190,6 +170,7 @@ public override IEnumerable SetReferencedData(SpinnerData data, T { // surface ParentToSurface(data.Surface, data.Center, components); + return Array.Empty(); } @@ -208,20 +189,8 @@ public override SpinnerData CopyDataTo(SpinnerData data, string[] materialNames, data.AngleMin = AngleMin; // visibility - var isBracketActive = false; - var isAnythingElseActive = false; - foreach (var mf in GetComponentsInChildren()) { - switch (mf.sharedMesh.name) { - case BracketMeshName: - isBracketActive = mf.gameObject.activeInHierarchy; - break; - default: - isAnythingElseActive = isAnythingElseActive || mf.gameObject.activeInHierarchy; - break; - } - } - data.IsVisible = isAnythingElseActive || isBracketActive; - data.ShowBracket = isBracketActive; + data.ShowBracket = GetVisibilityByComponent(); + data.IsVisible = GetMeshVisibility(); var collComponent = GetComponent(); if (collComponent) { From c867a4266c5452086148f2faf71d2bdf4080ab8a Mon Sep 17 00:00:00 2001 From: freezy Date: Sat, 11 Jan 2025 01:10:42 +0100 Subject: [PATCH 208/208] cleanup: Remove ITargetData. --- .../VPT/HitTarget/ITargetData.cs | 25 ------------------- .../VPT/HitTarget/ITargetData.cs.meta | 11 -------- .../VPT/HitTarget/DropTargetApi.cs | 2 +- .../HitTarget/DropTargetColliderGenerator.cs | 4 +-- .../VPT/HitTarget/HitTargetApi.cs | 2 +- .../HitTarget/HitTargetColliderGenerator.cs | 5 ++-- .../VPT/HitTarget/TargetColliderGenerator.cs | 6 ++--- .../VPT/HitTarget/TargetComponent.cs | 2 +- 8 files changed, 9 insertions(+), 48 deletions(-) delete mode 100644 VisualPinball.Engine/VPT/HitTarget/ITargetData.cs delete mode 100644 VisualPinball.Engine/VPT/HitTarget/ITargetData.cs.meta diff --git a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs b/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs deleted file mode 100644 index 9f610f0c3..000000000 --- a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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 . - -namespace VisualPinball.Engine.VPT.HitTarget -{ - public interface ITargetData - { - bool IsLegacy { get; } - - int TargetType { get; } - } -} diff --git a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs.meta b/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs.meta deleted file mode 100644 index 4596aeba0..000000000 --- a/VisualPinball.Engine/VPT/HitTarget/ITargetData.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5639b9278ab940846afef61c8d5018e7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs index 98cba0a4b..b13021906 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs @@ -105,7 +105,7 @@ private void SetIsDropped(bool isDropped) protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); + var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs index 46cdedebb..9d1d4764e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs @@ -24,8 +24,8 @@ namespace VisualPinball.Unity { public class DropTargetColliderGenerator : TargetColliderGenerator { - public DropTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshProvider, float4x4 matrix) - : base(api, data, meshProvider, matrix) { } + public DropTargetColliderGenerator(IApiColliderGenerator api, TargetComponent comp, float4x4 matrix) + : base(api, comp, matrix) { } internal void GenerateColliders(ref ColliderReference colliders) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 564c5400b..20dbc8c16 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -62,7 +62,7 @@ internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) protected override void CreateColliders(ref ColliderReference colliders, float4x4 translateWithinPlayfieldMatrix, float margin) { - var colliderGenerator = new HitTargetColliderGenerator(ColliderComponent.ColliderMesh, this, MainComponent, MainComponent, translateWithinPlayfieldMatrix); + var colliderGenerator = new HitTargetColliderGenerator(ColliderComponent.ColliderMesh, this, MainComponent, translateWithinPlayfieldMatrix); colliderGenerator.GenerateColliders(ref colliders); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs index 4b906bc4f..e5d4c7881 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs @@ -27,9 +27,8 @@ public class HitTargetColliderGenerator : TargetColliderGenerator { private readonly Mesh _colliderMesh; - public HitTargetColliderGenerator(Mesh colliderMesh, IApiColliderGenerator api, ITargetData data, - IMeshGenerator meshProvider, float4x4 matrix) - : base(api, data, meshProvider, matrix) + public HitTargetColliderGenerator(Mesh colliderMesh, IApiColliderGenerator api, TargetComponent comp, float4x4 matrix) + : base(api, comp, matrix) { _colliderMesh = colliderMesh; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs index 43dbeba74..30ad193de 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs @@ -23,15 +23,13 @@ namespace VisualPinball.Unity public abstract class TargetColliderGenerator { protected readonly IApiColliderGenerator Api; - protected readonly ITargetData Data; - protected readonly IMeshGenerator MeshGenerator; + protected readonly TargetComponent Data; protected readonly float4x4 Matrix; - protected TargetColliderGenerator(IApiColliderGenerator api, ITargetData data, IMeshGenerator meshGenerator, float4x4 matrix) + protected TargetColliderGenerator(IApiColliderGenerator api, TargetComponent data, float4x4 matrix) { Api = api; Data = data; - MeshGenerator = meshGenerator; Matrix = matrix; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs index f84318b66..27b3ff281 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetComponent.cs @@ -36,7 +36,7 @@ namespace VisualPinball.Unity { public abstract class TargetComponent : MainRenderableComponent, - ISwitchDeviceComponent, ITargetData, IMeshGenerator + ISwitchDeviceComponent, IMeshGenerator { #region Data