diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d746d119e..bde2ee4e0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: with: name: Plugins path: VisualPinball.Unity/Plugins - - uses: actions/cache@v2 + - uses: actions/cache@v3.0.1 with: path: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Library key: Library-Test-Project @@ -67,14 +67,13 @@ jobs: - uses: game-ci/unity-test-runner@main id: test with: - unityVersion: '2021.2.8f1' projectPath: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~ artifactsPath: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/artifacts testMode: all customParameters: -debugCodeOptimization -enableCodeCoverage -burst-disable-compilation -coverageOptions enableCyclomaticComplexity;assemblyFilters:+VisualPinball.Engine;pathFilters:-**/VisualPinball.Engine/Math/Triangulator/**,-**/VisualPinball.Engine/Math/Mesh/** -coverageResultsPath artifacts - run: | curl -s https://codecov.io/bash | bash -s - -f ${{ steps.test.outputs.artifactsPath }}/TestProject~-opencov/EditMode/TestCoverageResults_0000.xml - - uses: MirrorNG/nunit-reporter@v1.0.11 + - uses: MirrorNG/nunit-reporter@v1.1.0 if: always() with: path: ${{ steps.test.outputs.artifactsPath }}/*.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 89602f467..fad6802c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ Built with Unity 2021.2. - Native trough component ([#229](https://github.com/freezy/VisualPinball.Engine/pull/229), [#248](https://github.com/freezy/VisualPinball.Engine/pull/248), [#256](https://github.com/freezy/VisualPinball.Engine/pull/256), [Documentation](https://docs.visualpinball.org/creators-guide/manual/mechanisms/troughs.html)). ### Changed - +- When importing, meshes are now saved as easily editable `.fbx` files instead of Unity's internal format ([#387](https://github.com/freezy/VisualPinball.Engine/pull/387)). - Revised rubber mesh generation ([#384](https://github.com/freezy/VisualPinball.Engine/pull/384)). - APIs for RGB lamps and Visual Scripting ([#382](https://github.com/freezy/VisualPinball.Engine/pull/382)). - Playfield is now rotated to the correct angle during gameplay ([#370](https://github.com/freezy/VisualPinball.Engine/pull/370)). @@ -51,8 +51,8 @@ Built with Unity 2021.2. - Put game-, mesh-, collision- animation data into separate components ([#227](https://github.com/freezy/VisualPinball.Engine/pull/227), [Documentation](https://docs.visualpinball.org/creators-guide/editor/unity-components.html)). ### Fixed - - Remaining ball spinning issue should now be solved ([#397](https://github.com/freezy/VisualPinball.Engine/pull/397)). +- Physics error when the ball would stop rotate ([#393](https://github.com/freezy/VisualPinball.Engine/pull/393)). - Finally, ball rotation is rendered correctly ([#386](https://github.com/freezy/VisualPinball.Engine/pull/386)). - Ball stuttering when rolling over dropped target ([#375](https://github.com/freezy/VisualPinball.Engine/pull/375)). - Plunger disappearing due to too small bounding box. diff --git a/VisualPinball.Engine/Math/Vertex3DNoTex2.cs b/VisualPinball.Engine/Math/Vertex3DNoTex2.cs index c2992d3c3..6c6a5dd3f 100644 --- a/VisualPinball.Engine/Math/Vertex3DNoTex2.cs +++ b/VisualPinball.Engine/Math/Vertex3DNoTex2.cs @@ -122,6 +122,19 @@ public bool Equals(Vertex3DNoTex2 other) { return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && Nx.Equals(other.Nx) && Ny.Equals(other.Ny) && Nz.Equals(other.Nz) && Tu.Equals(other.Tu) && Tv.Equals(other.Tv); } + + public bool Equals(Vertex3DNoTex2 other, int precision) + { + return Round(X, precision) == Round(other.X, precision) + && Round(Y, precision) == Round(other.Y, precision) + && Round(Z, precision) == Round(other.Z, precision) + && Round(Nx, precision) == Round(other.Nx, precision) + && Round(Ny, precision) == Round(other.Ny, precision) + && Round(Nz, precision) == Round(other.Nz, precision) + && Round(Tu, precision) == Round(other.Tu, precision) + && Round(Tv, precision) == Round(other.Tv, precision); + } + public override bool Equals(object obj) { return obj is Vertex3DNoTex2 other && Equals(other); @@ -131,5 +144,13 @@ public override int GetHashCode() { return (X, Y, Z, Nx, Ny, Nz, Tu, Tv).GetHashCode(); } + + public int GetRoundedHash(int digits) => ( + Round(X, digits), Round(Y, digits), Round(Z, digits), + Round(Nx, digits), Round(Ny, digits), Round(Nz, digits), + Round(Tu, digits), Round(Tv, digits) + ).GetHashCode(); + + private static int Round(float value, int digits) => (int)System.Math.Round(value * System.Math.Pow(10, digits)); } } diff --git a/VisualPinball.Engine/VPT/Mesh.cs b/VisualPinball.Engine/VPT/Mesh.cs index 3abba785e..142a14612 100644 --- a/VisualPinball.Engine/VPT/Mesh.cs +++ b/VisualPinball.Engine/VPT/Mesh.cs @@ -494,7 +494,7 @@ public override int GetHashCode() #region IEquatable - public bool Equals(Mesh other) + public bool SomewhatEquals(Mesh other, float trianglesToBeMatched, int precision) { if (ReferenceEquals(null, other)) { return false; @@ -502,12 +502,27 @@ public bool Equals(Mesh other) if (ReferenceEquals(this, other)) { return true; } - if (!Name.Equals(other.Name) || Vertices.Length != other.Vertices.Length - || Indices.Length != other.Indices.Length - || AnimationFrames.Count != other.AnimationFrames.Count - || !AnimationDefaultPosition.Equals(other.AnimationDefaultPosition)) { + var triangles1 = IndexTriangles(Vertices, Indices, precision); + var triangles2 = IndexTriangles(other.Vertices, other.Indices, precision); + + using var enumerator = triangles1.GetEnumerator(); + var matched = 0; + foreach (var hash in triangles1.Keys) { + if (triangles2.ContainsKey(hash)) { + matched++; + } + } + return matched / (float)triangles1.Count > trianglesToBeMatched; + } + + public bool Equals(Mesh other) + { + if (ReferenceEquals(null, other)) { return false; } + if (ReferenceEquals(this, other)) { + return true; + } for (var i = 0; i < Vertices.Length; i++) { if (!Vertices[i].Equals(other.Vertices[i])) { @@ -532,6 +547,43 @@ public bool Equals(Mesh other) return true; } + private Vertex3DNoTex2[] Find(IEnumerable vertices, Vertex3DNoTex2 vertex, int precision) + { + return vertices.Where(v => v.Equals(vertex, precision)).ToArray(); + } + + private static Dictionary IndexTriangles(IReadOnlyList vertices, IReadOnlyList indices, int precision) + { + var triangles = new Dictionary(); + for (var i = 0; i < indices.Count; i += 3) { + var hash = HashTriangle(Sort( + vertices[indices[i]], + vertices[indices[i + 1]], + vertices[indices[i + 2]] + ), precision); + triangles.Add(hash, new [] { vertices[indices[i]], vertices[indices[i + 1]], vertices[indices[i + 2]] }); + } + return triangles; + } + + private static Vertex3DNoTex2[] Sort(params Vertex3DNoTex2[] vertices) + { + var firstVertex = vertices + .OrderBy(s => s.X).ThenBy(s => s.Y).ThenBy(s => s.Z) + .ThenBy(s => s.Nx).ThenBy(s => s.Ny).ThenBy(s => s.Nz) + .ThenBy(s => s.Tu).ThenBy(s => s.Tv) + .First(); + var firstVertexIndex = Array.IndexOf(vertices, firstVertex); + return new[] { + vertices[firstVertexIndex], + vertices[(firstVertexIndex + 1) % 3], + vertices[(firstVertexIndex + 2) % 3] + }; + } + + private static int HashTriangle(IReadOnlyList vertices, int precision) + => (vertices[0].GetRoundedHash(precision), vertices[1].GetRoundedHash(precision), vertices[2].GetRoundedHash(precision)).GetHashCode(); + public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { diff --git a/VisualPinball.Unity/Assets/Editor/WindowLayouts/VPE Simple.wlt b/VisualPinball.Unity/Assets/Editor/WindowLayouts/VPE Simple.wlt index eb5f9aebd..f9bc39ea9 100644 --- a/VisualPinball.Unity/Assets/Editor/WindowLayouts/VPE Simple.wlt +++ b/VisualPinball.Unity/Assets/Editor/WindowLayouts/VPE Simple.wlt @@ -15,15 +15,15 @@ MonoBehaviour: m_PixelRect: serializedVersion: 2 x: 0 - y: 0 - width: 1920 - height: 1080 + y: 42.666668 + width: 2560 + height: 1357.3334 m_ShowMode: 4 - m_Title: Lamp Manager + m_Title: Hierarchy m_RootView: {fileID: 7} m_MinSize: {x: 875, y: 300} m_MaxSize: {x: 10000, y: 10000} - m_Maximized: 0 + m_Maximized: 1 --- !u!114 &2 MonoBehaviour: m_ObjectHideFlags: 52 @@ -41,8 +41,8 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 356 - height: 463 + width: 474.66675 + height: 587.3333 m_MinSize: {x: 201, y: 221} m_MaxSize: {x: 4001, y: 4021} m_ActualView: {fileID: 20} @@ -67,14 +67,14 @@ MonoBehaviour: - {fileID: 4} m_Position: serializedVersion: 2 - x: 1564 + x: 2085.3333 y: 0 - width: 356 - height: 1030 + width: 474.66675 + height: 1307.3334 m_MinSize: {x: 100, y: 200} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 1195 + controlID: 86 --- !u!114 &4 MonoBehaviour: m_ObjectHideFlags: 52 @@ -91,9 +91,9 @@ MonoBehaviour: m_Position: serializedVersion: 2 x: 0 - y: 463 - width: 356 - height: 567 + y: 587.3333 + width: 474.66675 + height: 720.00006 m_MinSize: {x: 231, y: 271} m_MaxSize: {x: 10001, y: 10021} m_ActualView: {fileID: 22} @@ -111,26 +111,26 @@ MonoBehaviour: m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 12006, guid: 0000000000000000e000000000000000, type: 0} - m_Name: LampManager + m_Name: ConsoleWindow m_EditorClassIdentifier: m_Children: [] m_Position: serializedVersion: 2 x: 0 - y: 747 - width: 1137 - height: 283 + y: 948 + width: 1516 + height: 359.33337 m_MinSize: {x: 102, y: 121} m_MaxSize: {x: 4002, y: 4021} - m_ActualView: {fileID: 14} + m_ActualView: {fileID: 23} m_Panes: - {fileID: 17} - {fileID: 16} - {fileID: 15} - {fileID: 14} - {fileID: 23} - m_Selected: 3 - m_LastSelected: 3 + m_Selected: 4 + m_LastSelected: 0 --- !u!114 &6 MonoBehaviour: m_ObjectHideFlags: 52 @@ -148,8 +148,8 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 140 - height: 1030 + width: 187.33333 + height: 1307.3334 m_MinSize: {x: 101, y: 121} m_MaxSize: {x: 4001, y: 4021} m_ActualView: {fileID: 18} @@ -177,8 +177,8 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 1920 - height: 1080 + width: 2560 + height: 1357.3334 m_MinSize: {x: 875, y: 300} m_MaxSize: {x: 10000, y: 10000} m_UseTopView: 1 @@ -202,11 +202,11 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 1920 + width: 2560 height: 30 m_MinSize: {x: 0, y: 0} m_MaxSize: {x: 0, y: 0} - m_LastLoadedLayoutName: 3) VPE Simple + m_LastLoadedLayoutName: VPE Simple --- !u!114 &9 MonoBehaviour: m_ObjectHideFlags: 52 @@ -223,8 +223,8 @@ MonoBehaviour: m_Position: serializedVersion: 2 x: 0 - y: 1060 - width: 1920 + y: 1337.3334 + width: 2560 height: 20 m_MinSize: {x: 0, y: 0} m_MaxSize: {x: 0, y: 0} @@ -249,12 +249,12 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 30 - width: 1920 - height: 1030 + width: 2560 + height: 1307.3334 m_MinSize: {x: 400, y: 200} m_MaxSize: {x: 32384, y: 16192} vertical: 0 - controlID: 1194 + controlID: 85 --- !u!114 &11 MonoBehaviour: m_ObjectHideFlags: 52 @@ -272,14 +272,14 @@ MonoBehaviour: - {fileID: 5} m_Position: serializedVersion: 2 - x: 140 + x: 187.33333 y: 0 - width: 1137 - height: 1030 + width: 1516 + height: 1307.3334 m_MinSize: {x: 100, y: 200} m_MaxSize: {x: 8096, y: 16192} vertical: 1 - controlID: 1259 + controlID: 76 --- !u!114 &12 MonoBehaviour: m_ObjectHideFlags: 52 @@ -297,8 +297,8 @@ MonoBehaviour: serializedVersion: 2 x: 0 y: 0 - width: 1137 - height: 747 + width: 1516 + height: 948 m_MinSize: {x: 202, y: 221} m_MaxSize: {x: 4002, y: 4021} m_ActualView: {fileID: 21} @@ -322,10 +322,10 @@ MonoBehaviour: m_Children: [] m_Position: serializedVersion: 2 - x: 1277 + x: 1703.3334 y: 0 - width: 287 - height: 1030 + width: 381.99988 + height: 1307.3334 m_MinSize: {x: 277, y: 71} m_MaxSize: {x: 4002, y: 4021} m_ActualView: {fileID: 24} @@ -353,10 +353,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 140 - y: 777 - width: 1135 - height: 262 + x: 187.33334 + y: 1020.6667 + width: 1514 + height: 338.33337 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -382,10 +382,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 200 - y: 789 - width: 1096 - height: 250 + x: 187.33334 + y: 1020.6667 + width: 1514 + height: 338.33337 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -411,10 +411,10 @@ MonoBehaviour: m_Tooltip: m_Pos: serializedVersion: 2 - x: 200 - y: 789 - width: 1096 - height: 250 + x: 187.33334 + y: 1020.6667 + width: 1514 + height: 338.33337 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -441,9 +441,9 @@ MonoBehaviour: m_Pos: serializedVersion: 2 x: 187.33334 - y: 1021.3334 - width: 1513.3334 - height: 337.6667 + y: 1020.6667 + width: 1514 + height: 338.33337 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -470,9 +470,9 @@ MonoBehaviour: m_Pos: serializedVersion: 2 x: 0 - y: 30 - width: 139 - height: 1009 + y: 72.66667 + width: 186.33333 + height: 1286.3334 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -493,7 +493,7 @@ MonoBehaviour: m_MaxSize: {x: 4000, y: 4000} m_TitleContent: m_Text: Game - m_Image: {fileID: -6423792434712278376, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: 4621777727084837110, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 @@ -526,10 +526,10 @@ MonoBehaviour: m_VRangeLocked: 0 hZoomLockedByDefault: 0 vZoomLockedByDefault: 0 - m_HBaseRangeMin: -1920 - m_HBaseRangeMax: 1920 - m_VBaseRangeMin: -1080 - m_VBaseRangeMax: 1080 + m_HBaseRangeMin: -1280 + m_HBaseRangeMax: 1280 + m_VBaseRangeMin: -720 + m_VBaseRangeMax: 720 m_HAllowExceedBaseRangeMin: 1 m_HAllowExceedBaseRangeMax: 1 m_VAllowExceedBaseRangeMin: 1 @@ -549,21 +549,21 @@ MonoBehaviour: y: 0 width: 1513.3334 height: 927.6667 - m_Scale: {x: 0.39409724, y: 0.39409724} - m_Translation: {x: 756.6667, y: 463.83334} + m_Scale: {x: 0.5911459, y: 0.5911459} + m_Translation: {x: 756.66675, y: 463.83334} m_MarginLeft: 0 m_MarginRight: 0 m_MarginTop: 0 m_MarginBottom: 0 m_LastShownAreaInsideMargins: serializedVersion: 2 - x: -1920 - y: -1176.9515 - width: 3840 - height: 2353.903 + x: -1280 + y: -784.63434 + width: 2560 + height: 1569.2687 m_MinimalGUI: 1 - m_defaultScale: 0.39409724 - m_LastWindowPixelSize: {x: 1513.3334, y: 927.6667} + m_defaultScale: 0.5911459 + m_LastWindowPixelSize: {x: 2270, y: 1391.5} m_ClearInEditMode: 1 m_NoCameraWarning: 1 m_LowResolutionForAspectRatios: 01000000000000000000 @@ -585,14 +585,14 @@ MonoBehaviour: m_MaxSize: {x: 4000, y: 4000} m_TitleContent: m_Text: Hierarchy - m_Image: {fileID: 7966133145522015247, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: -3734745235275155857, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 - x: 1564 - y: 30 - width: 355 - height: 442 + x: 2085.3335 + y: 72.66667 + width: 473.66675 + height: 566.3333 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -600,9 +600,9 @@ MonoBehaviour: m_SceneHierarchy: m_TreeViewState: scrollPos: {x: 0, y: 0} - m_SelectedIDs: e472ffff + m_SelectedIDs: f27e0000 m_LastClickedID: 0 - m_ExpandedIDs: 4acbffff8afaffff44fbffff8e660000c466000058670000ce710000 + m_ExpandedIDs: 2afbffff m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -623,7 +623,7 @@ MonoBehaviour: m_ExpandedScenes: [] m_CurrenRootInstanceID: 0 m_LockTracker: - m_IsLocked: 1 + m_IsLocked: 0 m_CurrentSortingName: TransformSorting m_WindowGUID: 4c969a2b90040154d917609493e03593 --- !u!114 &21 @@ -642,14 +642,14 @@ MonoBehaviour: m_MaxSize: {x: 4000, y: 4000} m_TitleContent: m_Text: Scene - m_Image: {fileID: 2593428753322112591, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: 8634526014445323508, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 - x: 140 - y: 30 - width: 1135 - height: 726 + x: 187.33334 + y: 72.66667 + width: 1514 + height: 927 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -660,7 +660,7 @@ MonoBehaviour: collapsed: 0 displayed: 1 snapOffset: {x: 0, y: 0} - snapOffsetDelta: {x: -101, y: -26} + snapOffsetDelta: {x: -98.666626, y: -26} snapCorner: 3 id: Tool Settings index: 0 @@ -884,9 +884,9 @@ MonoBehaviour: m_PlayAudio: 0 m_AudioPlay: 0 m_Position: - m_Target: {x: 0, y: 0, z: 0} + m_Target: {x: -0.09235178, y: -0.39078707, z: 0.6396361} speed: 2 - m_Value: {x: -0.14242351, y: -0.50864005, z: 0.013976157} + m_Value: {x: -0.09235178, y: -0.39078707, z: 0.6396361} m_RenderMode: 0 m_CameraMode: drawMode: 0 @@ -933,13 +933,13 @@ MonoBehaviour: m_GridAxis: 1 m_gridOpacity: 1 m_Rotation: - m_Target: {x: -0.45621818, y: 0.041882344, z: -0.02150258, w: -0.8886227} + m_Target: {x: -0.27419743, y: 0.019323558, z: -0.0055197487, w: -0.96147317} speed: 2 - m_Value: {x: -0.51696205, y: 0.0056466353, z: -0.0034107985, w: -0.85598516} + m_Value: {x: -0.27419743, y: 0.01932356, z: -0.0055197487, w: -0.96147317} m_Size: - m_Target: 0.83042157 + m_Target: 0.8660254 speed: 2 - m_Value: 0.80623454 + m_Value: 0.8660254 m_Ortho: m_Target: 0 speed: 2 @@ -980,14 +980,14 @@ MonoBehaviour: m_MaxSize: {x: 10000, y: 10000} m_TitleContent: m_Text: Project - m_Image: {fileID: -5467254957812901981, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: -5179483145760003458, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 - x: 1564 - y: 493 - width: 355 - height: 546 + x: 2085.3335 + y: 660 + width: 473.66675 + height: 699.00006 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -1013,14 +1013,14 @@ MonoBehaviour: m_LastFolders: - Assets m_LastFoldersGridSize: 16 - m_LastProjectPath: /Users/jmillard/VPE20211115/VisualPinball.Unity.Project.Hdrp + m_LastProjectPath: C:\Development\_vpe\vpe-testproject m_LockTracker: m_IsLocked: 0 m_FolderTreeState: scrollPos: {x: 0, y: 0} - m_SelectedIDs: a80e0100 - m_LastClickedID: 69288 - m_ExpandedIDs: 00000000a80e010000ca9a3bffffff7f + m_SelectedIDs: fe7b0000 + m_LastClickedID: 31742 + m_ExpandedIDs: 00000000fe7b0000007c0000027c0000ffffff7f m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1048,7 +1048,7 @@ MonoBehaviour: scrollPos: {x: 0, y: 0} m_SelectedIDs: m_LastClickedID: 0 - m_ExpandedIDs: 00000000a80e010000ca9a3bffffff7f + m_ExpandedIDs: 00000000fe7b0000007c0000027c0000 m_RenameOverlay: m_UserAcceptedRename: 0 m_Name: @@ -1073,8 +1073,8 @@ MonoBehaviour: m_Icon: {fileID: 0} m_ResourceFile: m_ListAreaState: - m_SelectedInstanceIDs: e472ffff - m_LastClickedInstanceID: -36124 + m_SelectedInstanceIDs: f27e0000 + m_LastClickedInstanceID: 32498 m_HadKeyboardFocusLastEvent: 0 m_ExpandedInstanceIDs: c6230000 m_RenameOverlay: @@ -1120,14 +1120,14 @@ MonoBehaviour: m_MaxSize: {x: 4000, y: 4000} m_TitleContent: m_Text: Console - m_Image: {fileID: -4327648978806127646, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: -4950941429401207979, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 - x: 140 - y: 777 - width: 1135 - height: 262 + x: 187.33334 + y: 1020.6667 + width: 1514 + height: 338.33337 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default @@ -1148,14 +1148,14 @@ MonoBehaviour: m_MaxSize: {x: 4000, y: 4000} m_TitleContent: m_Text: Inspector - m_Image: {fileID: -2667387946076563598, guid: 0000000000000000d000000000000000, type: 0} + m_Image: {fileID: -440750813802333266, guid: 0000000000000000d000000000000000, type: 0} m_Tooltip: m_Pos: serializedVersion: 2 - x: 1277 - y: 30 - width: 285 - height: 1009 + x: 1703.3334 + y: 72.66667 + width: 379.99988 + height: 1286.3334 m_ViewDataDictionary: {fileID: 0} m_OverlayCanvas: m_LastAppliedPresetName: Default diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/IVpxPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/IVpxPrefab.cs index 07e64f401..92e8b609e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/IVpxPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/IVpxPrefab.cs @@ -27,8 +27,6 @@ internal interface IVpxPrefab IMainComponent MainComponent { get; } - MeshFilter[] MeshFilters { get; } - bool ExtractMesh { get; } bool SkipParenting { get; } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPlayfieldPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPlayfieldPrefab.cs index 290f7d9a8..6e1cfaab0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPlayfieldPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPlayfieldPrefab.cs @@ -27,7 +27,6 @@ internal class VpxPlayfieldPrefab : IVpxPrefab { public GameObject GameObject { get; } public IMainComponent MainComponent => _playfieldComponent; - public MeshFilter[] MeshFilters => GameObject.GetComponents(); public bool ExtractMesh => true; public bool SkipParenting => true; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs index 7ed8389b8..661ead322 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs @@ -32,7 +32,6 @@ internal class VpxPrefab : IVpxPrefab { public GameObject GameObject { get; } public IMainComponent MainComponent => _mainComponent; - public MeshFilter[] MeshFilters => GameObject.GetComponentsInChildren(); public bool ExtractMesh { get; set; } public bool SkipParenting => false; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index 67ae2f652..154464504 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -18,7 +18,9 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using UnityEditor; +using UnityEditor.Formats.Fbx.Exporter; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; @@ -244,27 +246,55 @@ private Dictionary InstantiateGameItems() private Dictionary UpdateGameItems(Dictionary prefabLookup) { var componentLookup = prefabLookup.ToDictionary(x => x.Key, x => x.Value.MainComponent); - foreach (var prefab in prefabLookup.Values) { - prefab.SetReferencedData(_sourceTable, this, this, componentLookup); - prefab.FreeBinaryData(); + try { + // pause asset database refreshing + AssetDatabase.StartAssetEditing(); - if (prefab.ExtractMesh) { - var mfs = prefab.MeshFilters; - foreach (var mf in mfs) { - var suffix = mfs.Length == 1 ? "" : $" ({mf.gameObject.name})"; - var meshFilename = $"{prefab.GameObject.name.ToFilename()}{suffix.ToFilename()}.mesh"; + // thanks unity for not letting me pass the options to ModelExporter.ExportObject(). + var modelExporter = typeof(ModelExporter); + var optionsProp = modelExporter.GetProperty("DefaultOptions", BindingFlags.Static | BindingFlags.NonPublic); + var optionsValue = optionsProp!.GetValue(null, null); + var optionsType = optionsValue.GetType(); + var exportFormatField = optionsType.BaseType!.GetField("exportFormat", BindingFlags.Instance | BindingFlags.NonPublic); + exportFormatField!.SetValue(optionsValue, 1); // set to binary + var exportObject = modelExporter.GetMethod("ExportObjects", BindingFlags.NonPublic | BindingFlags.Static); + + // first loop: write fbx files + foreach (var prefab in prefabLookup.Values) { + prefab.SetReferencedData(_sourceTable, this, this, componentLookup); + prefab.FreeBinaryData(); + + if (prefab.ExtractMesh) { + var meshFilename = $"{prefab.GameObject.name.ToFilename()}.fbx"; var meshPath = Path.Combine(_assetsMeshes, meshFilename); if (_options.SkipExistingMeshes && File.Exists(meshPath)) { - mf.sharedMesh = AssetDatabase.LoadAssetAtPath(meshPath); continue; } if (File.Exists(meshPath)) { AssetDatabase.DeleteAsset(meshPath); } - AssetDatabase.CreateAsset(mf.sharedMesh, meshPath); + + // export via reflection, because we need binary. + exportObject!.Invoke(null, new[] { meshPath, new UnityEngine.Object[] {prefab.GameObject}, optionsValue, null }); } } + } finally { + // resume asset database refreshing + AssetDatabase.StopAssetEditing(); + AssetDatabase.Refresh(); + } + + // second loop: assign them to the game object. + foreach (var prefab in prefabLookup.Values) { + + if (prefab.ExtractMesh) { + var meshFilename = $"{prefab.GameObject.name.ToFilename()}.fbx"; + var meshPath = Path.Combine(_assetsMeshes, meshFilename); + var mf = prefab.GameObject.GetComponent(); + mf.sharedMesh = AssetDatabase.LoadAssetAtPath(meshPath); + } + // patch if (_applyPatch) { _patcher?.ApplyPatches(prefab.GameObject, _tableGo); @@ -572,8 +602,8 @@ public GameObject GetGroupParent(string name) private string GetMeshPath(string parentName, string name) { var filename = parentName == name - ? $"{parentName.ToFilename()}.mesh" - : $"{parentName.ToFilename()} ({name.ToFilename()}).mesh"; + ? $"{parentName.ToFilename()}.fbx" + : $"{parentName.ToFilename()} ({name.ToFilename()}).fbx"; return Path.Combine(_assetsMeshes, filename); } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs index 7e29591d2..5d414321a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Coil/CoilListViewItemRenderer.cs @@ -117,6 +117,7 @@ private void RenderDestination(CoilListData coilListData, Rect cellRect, Action< } else if (index == CoilDestination.Lamp) { _tableComponent.MappingConfig.AddLamp(new LampMapping { Id = coilListData.Id, + InternalId = coilListData.InternalId, Source = LampSource.Lamp, IsCoil = true, Description = coilListData.Description diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs index b6dfc3757..be6efe9ff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs @@ -107,6 +107,9 @@ protected override void OnIconClick(LampListData data, bool pressedDown) private void RenderCoilId(Dictionary lampStatuses, LampListData lampListData, Rect cellRect) { + cellRect.x += 5; + EditorGUI.LabelField(cellRect, lampListData.Id); + // add some padding cellRect.x = cellRect.width - 45; cellRect.width -= 4; @@ -134,7 +137,7 @@ private void RenderCoilId(Dictionary lampStatuses, LampListDa cellRect.x += 20; cellRect.width -= 20; - EditorGUI.LabelField(cellRect, lampListData.Id); + EditorGUI.LabelField(cellRect, lampListData.InternalId.ToString()); } protected override void RenderDeviceElement(LampListData listData, Rect cellRect, Action updateAction) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs index e74c34df3..a716ebacb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/LayoutUtility.cs @@ -28,6 +28,10 @@ public static class LayoutUtility [MenuItem("Visual Pinball/Editor/Setup Layouts", false, 511)] public static void PopulateEditorLayout() { + // to update, do your changes, save the layout back, and finally copy it from + // from: C:\Users\%USERNAME%\AppData\Roaming\Unity\Editor-5.x\Preferences\Layouts\default + // to: VisualPinball.Unity\Assets\Editor\WindowLayouts + var layouts = CollectCustomLayouts(); var unityPrefs = InternalEditorUtility.unityPreferencesFolder; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef b/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef index ed4203b5b..1cbf50624 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef @@ -4,6 +4,7 @@ "references": [ "Unity.Entities", "Unity.Entities.Hybrid", + "Unity.Formats.Fbx.Editor", "Unity.Mathematics", "Unity.Transforms", "Unity.InputSystem", @@ -22,4 +23,4 @@ "defineConstraints": [], "versionDefines": [], "noEngineReferences": false -} \ No newline at end of file +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json index 638e01344..f86ab9853 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json @@ -1,6 +1,6 @@ { "dependencies": { - "com.unity.test-framework": "1.1.29", + "com.unity.test-framework": "1.1.31", "com.unity.testtools.codecoverage": "1.1.0", "com.unity.ugui": "1.0.0", "org.visualpinball.engine.unity": "file:../../../..", diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json index 00e8d90c8..70a092599 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json @@ -1,7 +1,14 @@ { "dependencies": { + "com.autodesk.fbx": { + "version": "4.1.1", + "depth": 2, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.burst": { - "version": "1.6.3", + "version": "1.6.4", "depth": 1, "source": "registry", "dependencies": { @@ -45,8 +52,18 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.formats.fbx": { + "version": "4.1.2", + "depth": 1, + "source": "registry", + "dependencies": { + "com.unity.timeline": "1.5.2", + "com.autodesk.fbx": "4.1.1" + }, + "url": "https://packages.unity.com" + }, "com.unity.inputsystem": { - "version": "1.2.0", + "version": "1.3.0", "depth": 1, "source": "registry", "dependencies": { @@ -119,7 +136,7 @@ "url": "https://packages.unity.com" }, "com.unity.scriptablebuildpipeline": { - "version": "1.19.5", + "version": "1.19.6", "depth": 2, "source": "registry", "dependencies": {}, @@ -146,8 +163,8 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.30", - "depth": 1, + "version": "1.1.31", + "depth": 0, "source": "registry", "dependencies": { "com.unity.ext.nunit": "1.0.6", @@ -176,6 +193,18 @@ }, "url": "https://packages.unity.com" }, + "com.unity.timeline": { + "version": "1.6.4", + "depth": 2, + "source": "registry", + "dependencies": { + "com.unity.modules.director": "1.0.0", + "com.unity.modules.animation": "1.0.0", + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.particlesystem": "1.0.0" + }, + "url": "https://packages.unity.com" + }, "com.unity.ugui": { "version": "1.0.0", "depth": 0, @@ -192,9 +221,11 @@ "dependencies": { "com.unity.burst": "1.6.3", "com.unity.entities": "0.17.0-preview.42", + "com.unity.formats.fbx": "4.1.2", "com.unity.jobs": "0.8.0-preview.23", "com.unity.mathematics": "1.2.5", "com.unity.inputsystem": "1.2.0", + "com.unity.ugui": "1.0.0", "com.unity.test-framework": "1.1.30" } }, diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt index 440b1b07d..0d2217629 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.2.8f1 -m_EditorVersionWithRevision: 2021.2.8f1 (d0e5f0a7b06a) +m_EditorVersion: 2021.2.18f1 +m_EditorVersionWithRevision: 2021.2.18f1 (0c6e675195cf) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs index 3c3542a87..d30a2b119 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs @@ -57,7 +57,7 @@ public void ShouldWriteImportedMesh() var table = FileTableContainer.Load(VpxPath.Primitive); var originalMesh = table.Primitive(primitiveName).GetMesh(); - Assert.AreEqual(originalMesh, writtenMesh); + Assert.IsTrue(originalMesh.SomewhatEquals(writtenMesh, 0.9f, 4)); File.Delete(tmpFileName); Object.DestroyImmediate(go); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs index 2d2dd47fa..9f0eb786e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs @@ -136,7 +136,7 @@ private void HandleCoilEvent(object sender, CoilEventArgs coilEvent) } if (destConfig.IsLampCoil) { - _lampPlayer!.HandleCoilEvent(coilEvent.Id, coilEvent.IsEnabled); + _lampPlayer!.HandleCoilEvent(coilEvent.Id, coilEvent.InternalId, coilEvent.IsEnabled); continue; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs index c2fdad61e..096df5a82 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/IGamelogicEngine.cs @@ -213,6 +213,10 @@ public readonly struct CoilEventArgs /// public readonly string Id; + /// Internal ID of the coil. + /// + public readonly int InternalId; + /// /// State of the coil, true if the coil is under voltage, false if not. /// @@ -221,6 +225,14 @@ public readonly struct CoilEventArgs public CoilEventArgs(string id, bool isEnabled) { Id = id; + InternalId = int.TryParse(id, out var internalId) ? internalId : 0; + IsEnabled = isEnabled; + } + + public CoilEventArgs(string id, int internalId, bool isEnabled) + { + Id = id; + InternalId = internalId; IsEnabled = isEnabled; } } @@ -228,10 +240,15 @@ public CoilEventArgs(string id, bool isEnabled) public readonly struct LampEventArgs { /// - /// Id of the lamp, as defined by . + /// ID of the lamp, as defined by . /// public readonly string Id; + /// + /// Internal ID of the lamp. Some lamps have multiple internal IDs per ID, like RGBs. + /// + public readonly int InternalId; + /// /// The intensity of the light. The range is dependent on the GLE, /// i.e. PinMAME sends 0-255 or sometimes 0-8 for GI. MPF sends 0-1. @@ -252,6 +269,16 @@ public readonly struct LampEventArgs public LampEventArgs(string id, float value, LampSource source = LampSource.Lamp) { Id = id; + InternalId = int.TryParse(id, out var internalId) ? internalId : 0; + Value = value; + Source = source; + IsCoil = false; + } + + public LampEventArgs(string id, int internalId, float value, LampSource source = LampSource.Lamp) + { + Id = id; + InternalId = internalId; Value = value; Source = source; IsCoil = false; @@ -260,6 +287,7 @@ public LampEventArgs(string id, float value, LampSource source = LampSource.Lamp public LampEventArgs(string id, float value, bool isCoil, LampSource source = LampSource.Lamp) { Id = id; + InternalId = int.TryParse(id, out var internalId) ? internalId : 0; Value = value; Source = source; IsCoil = isCoil; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs index d774dcb53..55ef959d4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs @@ -41,7 +41,7 @@ public class LampPlayer /// /// Links the GLE's IDs to the mappings. /// - private readonly Dictionary> _lampMappings = new(); + private readonly Dictionary>> _lampMappings = new(); private Player? _player; private TableComponent? _tableComponent; @@ -76,9 +76,16 @@ public void OnStart() AssignLampMapping(lampMapping); - // turn it off if (_lamps.ContainsKey(lampMapping.Device)) { - HandleLampEvent(lampMapping.Id, LampStatus.Off); + // turn off non-rgb lamps, turn on rgb lamps, but set to channel to 0 + + if (lampMapping.Type != LampType.RgbMulti) { + HandleLampEvent(lampMapping.Id, lampMapping.InternalId, LampStatus.Off); + } + else { + HandleLampEvent(lampMapping.Id, lampMapping.InternalId, LampStatus.On); + HandleLampEvent(lampMapping.Id, lampMapping.InternalId, 0f); + } } } @@ -92,40 +99,44 @@ public void OnStart() private void HandleLampsEvent(object sender, LampsEventArgs lampsEvent) { foreach (var lampEvent in lampsEvent.LampsChanged) { - Apply(lampEvent.Id, lampEvent.Source, lampEvent.IsCoil, (state, lamp, mapping) => ApplyValue(lampEvent.Id, lampEvent.Value, state, lamp, mapping)); + Apply(lampEvent.Id, lampEvent.InternalId, lampEvent.Source, lampEvent.IsCoil, (state, lamp, mapping) => ApplyValue(lampEvent.Id, lampEvent.InternalId, lampEvent.Value, state, lamp, mapping)); } } private void HandleLampEvent(object sender, LampEventArgs lampEvent) { - Apply(lampEvent.Id, lampEvent.Source, lampEvent.IsCoil, (state, lamp, mapping) => ApplyValue(lampEvent.Id, lampEvent.Value, state, lamp, mapping)); + Apply(lampEvent.Id, lampEvent.InternalId, lampEvent.Source, lampEvent.IsCoil, (state, lamp, mapping) => ApplyValue(lampEvent.Id, lampEvent.InternalId, lampEvent.Value, state, lamp, mapping)); } - public void HandleLampEvent(string id, float value) + public void HandleLampEvent(string id, int internalId, float value) { - Apply(id, LampSource.Lamp, false, (state, lamp, mapping) => ApplyValue(id, value, state, lamp, mapping)); + Apply(id, internalId, LampSource.Lamp, false, (state, lamp, mapping) => ApplyValue(id, internalId, value, state, lamp, mapping)); } - public void HandleLampEvent(string id, LampStatus status) + public void HandleLampEvent(string id, int internalId, LampStatus status) { - Apply(id, LampSource.Lamp, false, (state, lamp, _) => ApplyStatus(id, status, state, lamp)); + Apply(id, internalId, LampSource.Lamp, false, (state, lamp, _) => ApplyStatus(id, status, state, lamp)); } - public void HandleLampEvent(string id, Color color) + public void HandleLampEvent(string id, int internalId, Color color) { - Apply(id, LampSource.Lamp, false, (state, lamp, _) => ApplyColor(id, color, state, lamp)); + Apply(id, internalId, LampSource.Lamp, false, (state, lamp, _) => ApplyColor(id, color, state, lamp)); } - public void HandleCoilEvent(string id, bool isEnabled) + public void HandleCoilEvent(string id, int internalId, bool isEnabled) { - Apply(id, LampSource.Lamp, true, (state, lamp, _) => ApplyStatus(id, isEnabled ? LampStatus.On : LampStatus.Off, state, lamp)); + Apply(id, internalId, LampSource.Lamp, true, (state, lamp, _) => ApplyStatus(id, isEnabled ? LampStatus.On : LampStatus.Off, state, lamp)); } - private void Apply(string id, LampSource lampSource, bool isCoil, Action action) + private void Apply(string id, int internalId, LampSource lampSource, bool isCoil, Action action) { if (_lampAssignments.ContainsKey(id)) { foreach (var component in _lampAssignments[id]) { - var mapping = _lampMappings[id][component]; + + if (!_lampMappings[id][component].ContainsKey(internalId)) { + continue; + } + var mapping = _lampMappings[id][component][internalId]; if (mapping.Source != lampSource || mapping.IsCoil != isCoil) { // so, if we have a coil here that happens to have the same name as a lamp, // or a GI light with the same name as an other lamp, skip. @@ -163,7 +174,7 @@ private void ApplyColor(string id, Color color, LampState state, IApiLamp? lamp) lamp?.OnLamp(state.Color.ToUnityColor()); } - private void ApplyValue(string id, float value, LampState state, IApiLamp? lamp, LampMapping? mapping) + private void ApplyValue(string id, int internalId, float value, LampState state, IApiLamp? lamp, LampMapping? mapping) { if (mapping == null) { // if not mapped, there is no lamp, so just save the state. @@ -188,7 +199,7 @@ private void ApplyValue(string id, float value, LampState state, IApiLamp? lamp, break; case LampType.RgbMulti: - state.SetChannel(mapping.Channel, value / 255f); // todo test + state.SetChannel(mapping.Channel, value / 255f); LampStates[id] = state; lamp?.OnLamp(state.Color.ToUnityColor()); break; @@ -220,11 +231,17 @@ private void AssignLampMapping(LampMapping lampMapping) _lampAssignments[id] = new List(); } if (!_lampMappings.ContainsKey(id)) { - _lampMappings[id] = new Dictionary(); + _lampMappings[id] = new Dictionary>(); } _lampAssignments[id].Add(lampMapping.Device); - _lampMappings[id][lampMapping.Device] = lampMapping; - LampStates[id] = new LampState(lampMapping.Device.LampStatus, lampMapping.Device.LampColor.ToEngineColor()); + if (!_lampMappings[id].ContainsKey(lampMapping.Device)) { + _lampMappings[id][lampMapping.Device] = new Dictionary(); + } + _lampMappings[id][lampMapping.Device][lampMapping.InternalId] = lampMapping; + + if (!LampStates.ContainsKey(id)) { + LampStates[id] = new LampState(lampMapping.Device.LampStatus, lampMapping.Device.LampColor.ToEngineColor()); + } } #if UNITY_EDITOR diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs index 89c88f3a8..07f67d603 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs @@ -116,9 +116,9 @@ public class Player : MonoBehaviour public Dictionary WireStatuses => _wirePlayer.WireStatuses; public float3 Gravity => _playfieldComponent.Gravity; - public void SetLamp(string lampId, float value) => _lampPlayer.HandleLampEvent(lampId, value); - public void SetLamp(string lampId, LampStatus status) => _lampPlayer.HandleLampEvent(lampId, status); - public void SetLamp(string lampId, VisualPinball.Engine.Math.Color color) => _lampPlayer.HandleLampEvent(lampId, color); + public void SetLamp(string lampId, int internalId, float value) => _lampPlayer.HandleLampEvent(lampId, internalId, value); + public void SetLamp(string lampId, int internalId, LampStatus status) => _lampPlayer.HandleLampEvent(lampId, internalId, status); + public void SetLamp(string lampId, int internalId, VisualPinball.Engine.Math.Color color) => _lampPlayer.HandleLampEvent(lampId, internalId, color); #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/Rendering/IMaterialConverter.cs b/VisualPinball.Unity/VisualPinball.Unity/Rendering/IMaterialConverter.cs index 4a009958e..f50b9082b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Rendering/IMaterialConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Rendering/IMaterialConverter.cs @@ -69,5 +69,9 @@ public interface IMaterialConverter void SetEmissiveColor(MaterialPropertyBlock propBlock, Color color); Color? GetEmissiveColor(Material material); + + void SetEmissiveIntensity(Material material, MaterialPropertyBlock propBlock, float intensity); + + float GetEmissiveIntensity(Material material); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Rendering/Standard/StandardMaterialConverter.cs b/VisualPinball.Unity/VisualPinball.Unity/Rendering/Standard/StandardMaterialConverter.cs index 1c26cd5ed..295f372de 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Rendering/Standard/StandardMaterialConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Rendering/Standard/StandardMaterialConverter.cs @@ -161,8 +161,17 @@ public void SetEmissiveColor(MaterialPropertyBlock propBlock, Color color) public Color? GetEmissiveColor(Material material) { // standard has no emissive color - return null; } + + public void SetEmissiveIntensity(Material material, MaterialPropertyBlock propBlock, float intensity) + { + // standard has no emissive materials + } + + public float GetEmissiveIntensity(Material material) + { + return 1; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index 31d115b90..98b3302a8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -93,12 +93,7 @@ public class LightComponent : MainRenderableComponent, ILampDeviceCom public IApiLamp GetApi(Player player) => _api ??= new LightApi(gameObject, player); public IEnumerable LightSources => GetComponentsInChildren(); - public Color LampColor { - get { - var src = GetComponentInChildren(); - return Color.magenta; //src == null ? Color.white : src.color; - } - } + public Color LampColor => _color; public LampStatus LampStatus => State; @@ -159,28 +154,32 @@ public override void UpdateTransforms() #region Runtime + private float _value; + private Color _color; private bool _hasLights; - private Light[] _unityLights; - private readonly List<(Renderer, Color, float)> _fullEmissions = new List<(Renderer, Color, float)>(); - private float _fullIntensity; + private readonly List<(Light, float)> _lights = new(); + private readonly List<(Renderer, float)> _materials = new(); private MaterialPropertyBlock _propBlock; public bool Enabled { set { StopAllCoroutines(); - foreach (var unityLight in _unityLights) { - unityLight.enabled = value; - } + SetLightIntensity(value ? 1 : 0); + SetMaterialIntensity(value ? 1 : 0); } } public Color Color { - get => _unityLights[0].color; + get => _color; set { - foreach (var unityLight in _unityLights) { + _color = value; + foreach (var (unityLight, _) in _lights) { unityLight.color = value; - - // todo handle insert material color + } + foreach (var (mr, intensity) in _materials) { + mr.GetPropertyBlock(_propBlock); + RenderPipeline.Current.MaterialConverter.SetEmissiveColor(_propBlock, value * intensity); + mr.SetPropertyBlock(_propBlock); } } } @@ -194,15 +193,13 @@ private void Awake() } player.RegisterLamp(this); - _unityLights = GetComponentsInChildren(); - _hasLights = _unityLights.Length > 0; + var lights = GetComponentsInChildren(); + _value = 0; + _color = lights.FirstOrDefault()?.color ?? Color.white; - // remember intensity - if (_hasLights) { - _fullIntensity = _unityLights[0].intensity; - } - // enable at 0 - foreach (var unityLight in _unityLights) { + // remember intensities + foreach (var unityLight in lights) { + _lights.Add((unityLight, unityLight.intensity)); if (FadeEnabled) { unityLight.enabled = true; unityLight.intensity = 0; @@ -212,14 +209,17 @@ private void Awake() } } - // emissive materials - _propBlock = new MaterialPropertyBlock(); + // remember material emissions + _propBlock = new MaterialPropertyBlock(); // this is just something we can recycle foreach (var mr in GetComponentsInChildren()) { - var emissiveColor = RenderPipeline.Current.MaterialConverter.GetEmissiveColor(mr.sharedMaterial); - if (emissiveColor?.a > 10f) { - _fullEmissions.Add((mr, (Color)emissiveColor, 0)); + var emissiveIntensity = RenderPipeline.Current.MaterialConverter.GetEmissiveIntensity(mr.sharedMaterial); + if (emissiveIntensity > 0) { + _materials.Add((mr, emissiveIntensity)); } + // todo set to 0 initially } + + _hasLights = _lights.Count > 0 || _materials.Count > 0; } public void FadeTo(float value) @@ -232,17 +232,9 @@ public void FadeTo(float value) StartCoroutine(nameof(Fade), value); } else { - foreach (var unityLight in _unityLights) { - if (value > 0) { - unityLight.intensity = value * _fullIntensity; - unityLight.enabled = true; - - } else { - unityLight.enabled = false; - } - } - - SetEmissions(value); + _value = value; + SetLightIntensity(value); + SetMaterialIntensity(value); } } @@ -279,55 +271,51 @@ private IEnumerator Blink(float blinkIntensity) private IEnumerator Fade(float value) { var counter = 0f; - - var a = _unityLights[0].intensity; - var b = _fullIntensity * value; - var duration = a < b - ? FadeSpeedUp * (_fullIntensity - a) / _fullIntensity - : FadeSpeedDown * (1 - (_fullIntensity - a) / _fullIntensity); + var duration = _value < value + ? FadeSpeedUp * (1 - _value) + : FadeSpeedDown * _value; if (duration == 0) { - foreach (var unityLight in _unityLights) { - unityLight.intensity = b; - } - SetEmissions(value); + _value = value; + SetLightIntensity(value); + SetMaterialIntensity(value); } else { while (counter <= duration) { counter += Time.deltaTime; var position = counter / duration; - foreach (var unityLight in _unityLights) { - unityLight.intensity = Mathf.Lerp(a, b, position); - } - yield return FadeEmissions(value, position); + var newValue = Mathf.Lerp(_value, value, position); + yield return SetIntensity(newValue); } } } - /// - /// Sets the material emissions as a LERP between the current emission and - /// a value for a given position. - /// - /// Value, between 0 and 1. End position of LERP is this value times full emission. - /// LERP position - private IEnumerator FadeEmissions(float value, float position) + private IEnumerator SetIntensity(float value) { - for (var i = 0; i < _fullEmissions.Count; i++) { - var (mr, color, lastValue) = _fullEmissions[i]; - mr.GetPropertyBlock(_propBlock); - var emission = Mathf.Lerp(lastValue, value, position); - RenderPipeline.Current.MaterialConverter.SetEmissiveColor(_propBlock, emission * color * 0.05f); - _fullEmissions[i] = (mr, color, emission); - mr.SetPropertyBlock(_propBlock); - } + _value = value; + SetLightIntensity(value); + SetMaterialIntensity(value); yield return null; } - private void SetEmissions(float value) + private void SetLightIntensity(float value) + { + foreach (var (unityLight, intensity) in _lights) { + if (value > 0) { + unityLight.intensity = intensity * value; + unityLight.enabled = true; + + } else { + unityLight.enabled = false; + } + } + } + + private void SetMaterialIntensity(float value) { - foreach (var (mr, color, lastValue) in _fullEmissions) { + foreach (var (mr, intensity) in _materials) { mr.GetPropertyBlock(_propBlock); - RenderPipeline.Current.MaterialConverter.SetEmissiveColor(_propBlock, value * color * 0.05f); + RenderPipeline.Current.MaterialConverter.SetEmissiveIntensity(mr.sharedMaterial, _propBlock, value * intensity); mr.SetPropertyBlock(_propBlock); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 9dc904815..d8840ebd2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -196,6 +196,8 @@ public override PrimitiveData CopyDataTo(PrimitiveData data, string[] materialNa var mf = GetComponent(); if (mf) { data.Mesh = mf.sharedMesh.ToVpMesh(); + data.NumIndices = data.Mesh.Indices.Length; + data.NumVertices = data.Mesh.Vertices.Length; } } } diff --git a/package.json b/package.json index 8a4be06ef..8f8f8175a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "dependencies": { "com.unity.burst": "1.6.3", "com.unity.entities": "0.17.0-preview.42", + "com.unity.formats.fbx": "4.1.2", "com.unity.jobs": "0.8.0-preview.23", "com.unity.mathematics": "1.2.5", "com.unity.inputsystem": "1.2.0",