diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a076fbb9b..2fe646e43 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: rid: android-arm64-v8a steps: - uses: actions/checkout@v3 - - uses: actions/setup-dotnet@v1 + - uses: actions/setup-dotnet@v3 with: dotnet-version: '3.1.x' - name: Build @@ -45,47 +45,48 @@ jobs: name: Plugins path: tmp - test: - name: Unit Test - needs: [ build ] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/download-artifact@v3 - with: - name: Plugins - path: VisualPinball.Unity/Plugins - - uses: actions/cache@v3 - with: - path: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Library - key: Library-Test-Project - restore-keys: | - Library-Test-Project - Library - - uses: game-ci/unity-test-runner@main - id: test - with: - 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.1.0 - if: always() - with: - path: ${{ steps.test.outputs.artifactsPath }}/*.xml - access-token: ${{ secrets.GITHUB_TOKEN }} - - uses: actions/upload-artifact@v3 - if: always() - with: - name: Test results - path: ${{ steps.test.outputs.artifactsPath }} +# test: +# name: Unit Test +# needs: [ build ] +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v3 +# - uses: actions/download-artifact@v3 +# with: +# name: Plugins +# path: VisualPinball.Unity/Plugins + #- uses: actions/cache@v3 + # with: + # path: VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Library + # key: Library-Test-Project + # restore-keys: | + # Library-Test-Project + # Library +# - uses: game-ci/unity-test-runner@main +# id: test +# with: +# #unityVersion: "2022.3.10f1" +# 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.1.0 +# if: always() +# with: +# path: ${{ steps.test.outputs.artifactsPath }}/*.xml +# access-token: ${{ secrets.GITHUB_TOKEN }} +# - uses: actions/upload-artifact@v3 +# if: always() +# with: +# name: Test results +# path: ${{ steps.test.outputs.artifactsPath }} dispatch: name: Dispatch runs-on: ubuntu-latest - needs: [ test ] + needs: [ build ] if: github.repository == 'freezy/VisualPinball.Engine' && github.ref == 'refs/heads/master' steps: - uses: peter-evans/repository-dispatch@v1 diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index b4d7731b5..2203585f1 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@v3 - - uses: nikeee/docfx-action@v1.0.0 + - uses: nunit/docfx-action@v2.4.0 name: Build Documentation with: args: VisualPinball.Unity/Documentation~/docfx.json diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 3d31df94c..67bb68ce4 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -10,7 +10,8 @@ jobs: id: license uses: game-ci/unity-request-activation-file@v2 with: - unityVersion: 2021.3.0f1 + unityVersion: 2022.3.10f1 + customImage: "unityci/editor:ubuntu-2022.3.10f1-linux-il2cpp-2" - uses: actions/upload-artifact@v2 with: name: ${{ steps.license.outputs.filePath }} diff --git a/VisualPinball.Engine.Test/Common/EngineTests.cs b/VisualPinball.Engine.Test/Common/EngineTests.cs deleted file mode 100644 index 29f9fe087..000000000 --- a/VisualPinball.Engine.Test/Common/EngineTests.cs +++ /dev/null @@ -1,67 +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; -using FluentAssertions; -using NUnit.Framework; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Engine.Test.Common -{ - public class EngineTests - { - [Test] - public void ShouldFailIfNoneSet() - { - Action act = () => EngineProvider.Get(); - act.Should().Throw() - .WithMessage("Must select VisualPinball.Engine.Test.Common.ITestEngine engine before retrieving!"); - } - - [Test] - public void ShouldProvideIfSet() - { - EngineProvider.Set("VisualPinball.Engine.Test.Common.TestEngine1"); - var engine = EngineProvider.Get(); - - engine.Should().NotBeNull(); - engine.Name.Should().Be("Engine 1"); - - EngineProvider.Set("VisualPinball.Engine.Test.Common.TestEngine2"); - engine = EngineProvider.Get(); - - engine.Should().NotBeNull(); - engine.Name.Should().Be("Engine 2"); - } - } - - internal interface ITestEngine : IEngine - { - - } - - internal class TestEngine1 : ITestEngine - { - - public string Name { get; } = "Engine 1"; - } - - internal class TestEngine2 : ITestEngine - { - - public string Name { get; } = "Engine 2"; - } -} diff --git a/VisualPinball.Engine/Common/Constants.cs b/VisualPinball.Engine/Common/Constants.cs index a9fca5a72..4bb1623e1 100644 --- a/VisualPinball.Engine/Common/Constants.cs +++ b/VisualPinball.Engine/Common/Constants.cs @@ -86,10 +86,12 @@ public static class PhysicsConstants public const float DefaultTableMaxSlope = 6.0f; // DEFAULT_TABLE_MAX_SLOPE public const float DefaultTableGravity = 0.97f; // DEFAULT_TABLE_GRAVITY + public const float GravityConst = 1.81751f; // GRAVITYCONST + /// /// trigger/kicker boundary crossing hysterisis /// - public const float StaticTime = 0.005f; // STATICTIME + public const float StaticTime = 0.02f; // STATICTIME public const float StaticCnts = 10f; // STATICCNTS /// diff --git a/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs index a0225441a..04697dc76 100644 --- a/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs +++ b/VisualPinball.Engine/Game/Engines/GamelogicEngineLamp.cs @@ -78,12 +78,12 @@ public class GamelogicEngineLamp : IGamelogicEngineDeviceItem public GamelogicEngineLamp(string id) { - Id = id; + _id = id; } public GamelogicEngineLamp(int id) { - Id = id.ToString(); + _id = id.ToString(); } } @@ -108,7 +108,7 @@ public enum LampStatus Blinking = 2, } - public class LampState + public struct LampState { public LampStatus Status { get => _status; @@ -133,11 +133,12 @@ public float Intensity { public Color Color; private LampStatus _status; - private LampStatus _lastOnStatus = LampStatus.On; + private LampStatus _lastOnStatus; public LampState(LampStatus status, Color color) { - Status = status; + _lastOnStatus = status != LampStatus.Off ? status : LampStatus.On; + _status = status; Color = color; } @@ -151,12 +152,14 @@ public LampState(float intensity) _status = LampStatus.On; Color = new Color(255, 255, 255, (int)(intensity * 255)); } + _lastOnStatus = LampStatus.On; } public LampState(Color color) { _status = color.A > 0 ? LampStatus.On : LampStatus.Off; Color = color; + _lastOnStatus = LampStatus.On; } public void SetChannel(ColorChannel channel, float value) diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png new file mode 100644 index 000000000..7442a5ea5 Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png.meta new file mode 100644 index 000000000..7efae9b57 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/ball.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 6a4a0b4dfc8e86c4b806a797951b3c29 +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_blue/physics.png b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/physics.png new file mode 100644 index 000000000..36628c047 Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/physics.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_blue/physics.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/physics.png.meta new file mode 100644 index 000000000..6df27d4e6 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_blue/physics.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 9fa698499ac650c43ad57ac3d6a6b63b +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/ball.png b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/ball.png new file mode 100644 index 000000000..b06f9ba8a Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/ball.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_gray/ball.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/ball.png.meta new file mode 100644 index 000000000..87b317754 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/ball.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 95e3a57c804bf4748aae41f15ee482b0 +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/physics.png b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/physics.png new file mode 100644 index 000000000..833e5181a Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/physics.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_gray/physics.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/physics.png.meta new file mode 100644 index 000000000..399973a30 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_gray/physics.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 3b3e5e84308143f41ab28ab8be7765a5 +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_green/ball.png b/VisualPinball.Unity/Assets/Editor/Icons/small_green/ball.png new file mode 100644 index 000000000..e5bb8f900 Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_green/ball.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_green/ball.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_green/ball.png.meta new file mode 100644 index 000000000..033351cf5 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_green/ball.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 5d0b9cba9947e3d4ab5bcb515913d088 +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: 1 + 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_green/physics.png b/VisualPinball.Unity/Assets/Editor/Icons/small_green/physics.png new file mode 100644 index 000000000..6c491079a Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_green/physics.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_green/physics.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_green/physics.png.meta new file mode 100644 index 000000000..30eeeda64 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_green/physics.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 795073756b9fd6745bdb8a4dad302cc7 +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/ball.png b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/ball.png new file mode 100644 index 000000000..dd0d086d8 Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/ball.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_orange/ball.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/ball.png.meta new file mode 100644 index 000000000..faeb91a5c --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/ball.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: 7e22e740622cf2243bfe316adbf63442 +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/physics.png b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/physics.png new file mode 100644 index 000000000..084b1f490 Binary files /dev/null and b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/physics.png differ diff --git a/VisualPinball.Unity/Assets/Editor/Icons/small_orange/physics.png.meta b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/physics.png.meta new file mode 100644 index 000000000..29fb14175 --- /dev/null +++ b/VisualPinball.Unity/Assets/Editor/Icons/small_orange/physics.png.meta @@ -0,0 +1,127 @@ +fileFormatVersion: 2 +guid: b8980903958b24841adb47db27c3344e +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/Resources/Materials/Dot Matrix Display (SRP).mat b/VisualPinball.Unity/Assets/Resources/Materials/Dot Matrix Display (SRP).mat index b5de4b958..efb2476c7 100644 --- a/VisualPinball.Unity/Assets/Resources/Materials/Dot Matrix Display (SRP).mat +++ b/VisualPinball.Unity/Assets/Resources/Materials/Dot Matrix Display (SRP).mat @@ -12,13 +12,13 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: da692e001514ec24dbc4cca1949ff7e8, type: 3} m_Name: m_EditorClassIdentifier: - version: 12 + version: 13 hdPluginSubTargetMaterialVersions: m_Keys: [] m_Values: --- !u!21 &2100000 Material: - serializedVersion: 6 + serializedVersion: 8 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -26,7 +26,11 @@ Material: m_Name: Dot Matrix Display (SRP) m_Shader: {fileID: -6465566751694194690, guid: 3dfbd2115636a284d89d71fdefd9b4d2, type: 3} - m_ShaderKeywords: _DISABLE_SSR_TRANSPARENT + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _DISABLE_SSR_TRANSPARENT + m_InvalidKeywords: [] m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 @@ -39,6 +43,7 @@ Material: - TransparentBackface - RayTracingPrepass - MOTIONVECTORS + m_LockedProperties: m_SavedProperties: serializedVersion: 3 m_TexEnvs: diff --git a/VisualPinball.Unity/Assets/Resources/Materials/Segment Display (SRP).mat b/VisualPinball.Unity/Assets/Resources/Materials/Segment Display (SRP).mat index 2f6b33f62..923011b22 100644 --- a/VisualPinball.Unity/Assets/Resources/Materials/Segment Display (SRP).mat +++ b/VisualPinball.Unity/Assets/Resources/Materials/Segment Display (SRP).mat @@ -12,13 +12,13 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: da692e001514ec24dbc4cca1949ff7e8, type: 3} m_Name: m_EditorClassIdentifier: - version: 12 + version: 13 hdPluginSubTargetMaterialVersions: m_Keys: [] m_Values: --- !u!21 &2100000 Material: - serializedVersion: 6 + serializedVersion: 8 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} @@ -26,7 +26,11 @@ Material: m_Name: Segment Display (SRP) m_Shader: {fileID: -6465566751694194690, guid: d54eb986991300e419411008eb1f597d, type: 3} - m_ShaderKeywords: _DISABLE_SSR_TRANSPARENT + m_Parent: {fileID: 0} + m_ModifiedSerializedProperties: 0 + m_ValidKeywords: + - _DISABLE_SSR_TRANSPARENT + m_InvalidKeywords: [] m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 @@ -39,6 +43,7 @@ Material: - TransparentBackface - RayTracingPrepass - MOTIONVECTORS + m_LockedProperties: m_SavedProperties: serializedVersion: 3 m_TexEnvs: diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Bumper (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Bumper (Builtin).prefab index cd97280fa..8d67014d0 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Bumper (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Bumper (Builtin).prefab @@ -11,7 +11,6 @@ GameObject: - component: {fileID: 8936098568141045026} - component: {fileID: 197865258511769274} - component: {fileID: 1813312543483188556} - - component: {fileID: 558255656085877858} m_Layer: 0 m_Name: Bumper (Builtin) m_TagString: Untagged @@ -78,19 +77,6 @@ MonoBehaviour: Force: 15 Scatter: 0 HitEvent: 1 ---- !u!114 &558255656085877858 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 642515788050218447} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 --- !u!1 &2782857395794746971 GameObject: m_ObjectHideFlags: 0 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Drop Target (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Drop Target (Builtin).prefab index b7d0cf601..eabdb6e38 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Drop Target (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Drop Target (Builtin).prefab @@ -14,7 +14,6 @@ GameObject: - component: {fileID: -6600741874494028717} - component: {fileID: 8307013672536159350} - component: {fileID: 16544014384142367} - - component: {fileID: 531720702824142340} m_Layer: 0 m_Name: Drop Target (Builtin) m_TagString: Untagged @@ -32,6 +31,7 @@ Transform: 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: 0} m_RootOrder: 0 @@ -56,7 +56,7 @@ MonoBehaviour: Rotation: 0 Size: {x: 32, y: 32, z: 32} _targetType: 1 - MeshName: + _meshName: --- !u!114 &-7285854734489305104 MonoBehaviour: m_ObjectHideFlags: 0 @@ -114,6 +114,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -144,16 +145,3 @@ MeshRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!114 &531720702824142340 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4811490838886355864} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Flipper.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Flipper.prefab index d8aa8491e..02ccd6d9e 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Flipper.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Flipper.prefab @@ -28,6 +28,7 @@ Transform: 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: 4440381279647306322} m_RootOrder: 1 @@ -44,7 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8e6fc321830c03547890119a04997404, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 45420 --- !u!33 &4778019510121147183 MeshFilter: m_ObjectHideFlags: 0 @@ -81,6 +82,7 @@ Transform: 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: 4440381279647306322} m_RootOrder: 0 @@ -97,7 +99,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 89e3c1c147bb7a049b0510709f1ad9f2, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 45406 --- !u!33 &8383588224779965405 MeshFilter: m_ObjectHideFlags: 0 @@ -117,7 +119,6 @@ GameObject: - component: {fileID: 4440381279647306322} - component: {fileID: 2330311059251221068} - component: {fileID: 8750544366699972018} - - component: {fileID: 3449526034131864867} m_Layer: 0 m_Name: Flipper m_TagString: Untagged @@ -135,6 +136,7 @@ Transform: 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: - {fileID: 5879026712924827105} - {fileID: 6818887475746504221} @@ -153,73 +155,26 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 545ad741f74d2444faf5d2882fba6b69, type: 3} m_Name: m_EditorClassIdentifier: - _data: - StoragePrefix: 0 - StorageIndex: 0 - IsLocked: 0 - EditorLayerName: - EditorLayerVisibility: 0 - Name: - BaseRadius: 0 - EndRadius: 0 - FlipperRadiusMin: 0 - FlipperRadiusMax: 0 - FlipperRadius: 0 - StartAngle: 0 - EndAngle: 0 - Height: 0 - Center: - X: 0 - Y: 0 - Image: - Surface: - Material: - RubberMaterial: - RubberThickness: 0 - RubberHeight: 0 - RubberWidth: 0 - Mass: 0 - Strength: 0 - Elasticity: 0 - ElasticityFalloff: 0 - Friction: 0 - Return: 0 - RampUp: 0 - TorqueDamping: 0 - TorqueDampingAngle: 0 - Scatter: 0 - OverridePhysics: 0 - IsVisible: 0 - IsEnabled: 0 - IsReflectionEnabled: 0 - IsTimerEnabled: 0 - TimerInterval: 0 - IsDualWound: 0 - OverrideMass: 0 - OverrideStrength: 0 - OverrideElasticity: 0 - OverrideElasticityFalloff: 0 - OverrideFriction: 0 - OverrideReturnStrength: 0 - OverrideCoilRampUp: 0 - OverrideTorqueDamping: 0 - OverrideTorqueDampingAngle: 0 - OverrideScatterAngle: 0 + _isLocked: 0 + _editorLayer: 0 + _editorLayerName: + _editorLayerVisibility: 1 Position: {x: 0, y: 0} - StartAngle: 121 + _startAngle: 121 EndAngle: 70 _surface: {fileID: 0} IsEnabled: 1 IsDualWound: 0 - Height: 50 - BaseRadius: 21.5 - EndRadius: 13 + _height: 50 + _baseRadius: 21.5 + _endRadius: 13 FlipperRadiusMin: 0 FlipperRadiusMax: 130 - FlipperRadius: 130 - RubberThickness: 7 - RubberHeight: 19 - RubberWidth: 24 + _rubberThickness: 7 + _rubberHeight: 19 + _rubberWidth: 24 + InstantiateAsPrefab: 0 + _originalRotateZ: 0 --- !u!114 &8750544366699972018 MonoBehaviour: m_ObjectHideFlags: 0 @@ -233,7 +188,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} - ShowColliderMesh: 0 + ShowColliderMesh: 1 ShowAabbs: 0 Mass: 1 Strength: 2200 @@ -246,16 +201,20 @@ MonoBehaviour: TorqueDampingAngle: 6 Scatter: 0 FlipperCorrection: {fileID: 11400000, guid: f8799be363ab20a459dd185669ece8aa, type: 2} ---- !u!114 &3449526034131864867 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8904124417643437867} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 + useFlipperTricksPhysics: 0 + SOSRampUp: 2.5 + SOSEM: 0.85 + EOSReturn: 0.055 + EOSTNew: 0.8 + EOSANew: 1 + EOSRampup: 0 + Overshoot: 3 + BumpOnRelease: 0.4 + useFlipperLiveCatch: 0 + LiveCatchDistanceMin: 40 + LiveCatchDistanceMax: 100 + LiveCatchMinimalBallSpeed: 6 + LiveCatchFullTime: 16 + LiveCatchPerfectTime: 8 + LiveCatchMinmalBounceSpeedMultiplier: 0.1 + LiveCatchInaccurateBounceSpeedMultiplier: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab index 2e7a48fcc..fe25f4acf 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Gate (Builtin).prefab @@ -11,7 +11,6 @@ GameObject: - component: {fileID: 7014708109448554416} - component: {fileID: 3468265542873711340} - component: {fileID: 3496935618586042827} - - component: {fileID: -7170736425438619631} m_Layer: 0 m_Name: Gate (Builtin) m_TagString: Gate @@ -29,6 +28,7 @@ Transform: 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: - {fileID: 6622806185196320941} - {fileID: 2980705729845053989} @@ -79,19 +79,6 @@ MonoBehaviour: Friction: 0.02 GravityFactor: 0.25 _twoWay: 0 ---- !u!114 &-7170736425438619631 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4689510019247135382} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 --- !u!1 &6497995796854627700 GameObject: m_ObjectHideFlags: 0 @@ -120,6 +107,7 @@ Transform: 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 @@ -143,6 +131,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -202,6 +191,7 @@ Transform: 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 @@ -237,6 +227,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Hit Target (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Hit Target (Builtin).prefab index f63962e24..93a9ec8c5 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Hit Target (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Hit Target (Builtin).prefab @@ -14,7 +14,6 @@ GameObject: - component: {fileID: 2121126026550932934} - component: {fileID: 8307013672536159350} - component: {fileID: 16544014384142367} - - component: {fileID: 531720702824142340} m_Layer: 0 m_Name: Hit Target (Builtin) m_TagString: Untagged @@ -32,6 +31,7 @@ Transform: 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: 0} m_RootOrder: 0 @@ -56,7 +56,7 @@ MonoBehaviour: Rotation: 0 Size: {x: 32, y: 32, z: 32} _targetType: 1 - MeshName: + _meshName: --- !u!114 &-1267520440965682989 MonoBehaviour: m_ObjectHideFlags: 0 @@ -76,10 +76,8 @@ MonoBehaviour: ElasticityFalloff: 0.5 Friction: 0.2 Scatter: 5 - IsLegacy: 0 OverwritePhysics: 1 Threshold: 2 - UseHitEvent: 1 --- !u!114 &2121126026550932934 MonoBehaviour: m_ObjectHideFlags: 0 @@ -113,6 +111,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -143,16 +142,3 @@ MeshRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!114 &531720702824142340 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4811490838886355864} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Kicker (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Kicker (Builtin).prefab index 7abd36679..4a9295d9e 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Kicker (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Kicker (Builtin).prefab @@ -13,7 +13,6 @@ GameObject: - component: {fileID: -2035473210646026864} - component: {fileID: 2254716409977562185} - component: {fileID: 9085684073653883757} - - component: {fileID: 5688332318531931923} m_Layer: 0 m_Name: Kicker (Builtin) m_TagString: Untagged @@ -31,6 +30,7 @@ Transform: 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: 0} m_RootOrder: 0 @@ -82,9 +82,8 @@ MonoBehaviour: HitAccuracy: 0.7 HitHeight: 40 FallThrough: 0 + FallIn: 1 LegacyMode: 1 - EjectAngle: 90 - EjectSpeed: 3 --- !u!33 &2254716409977562185 MeshFilter: m_ObjectHideFlags: 0 @@ -104,6 +103,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -134,16 +134,3 @@ MeshRenderer: m_SortingLayer: 0 m_SortingOrder: 0 m_AdditionalVertexStreams: {fileID: 0} ---- !u!114 &5688332318531931923 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2420819360404407618} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/MetalWireGuide.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/MetalWireGuide.prefab index 5efa4de90..453c0dfc0 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/MetalWireGuide.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/MetalWireGuide.prefab @@ -13,7 +13,6 @@ GameObject: - component: {fileID: 2107529826525245329} - component: {fileID: -3250516992663423584} - component: {fileID: 4392546155774236836} - - component: {fileID: 171621741425913877} m_Layer: 0 m_Name: MetalWireGuide m_TagString: Untagged @@ -54,7 +53,9 @@ MonoBehaviour: _editorLayerVisibility: 1 _height: 25 _thickness: 8 + _standheight: 30 Rotation: {x: 0, y: 0, z: 0} + _bendradius: 8 _dragPoints: [] --- !u!114 &2107529826525245329 MonoBehaviour: @@ -90,6 +91,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: bcc8f0e761a7bc145abb5a6f918953aa, type: 3} m_Name: m_EditorClassIdentifier: + _instanceID: 46096 --- !u!33 &4392546155774236836 MeshFilter: m_ObjectHideFlags: 0 @@ -98,16 +100,3 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5446999501863086646} m_Mesh: {fileID: 0} ---- !u!114 &171621741425913877 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5446999501863086646} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Plunger.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Plunger.prefab index 0488ff1f4..cfce987e3 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Plunger.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Plunger.prefab @@ -11,7 +11,6 @@ GameObject: - component: {fileID: 2688967276888975725} - component: {fileID: 8461769246571476234} - component: {fileID: 4360165373544367127} - - component: {fileID: 6380137805125368953} m_Layer: 0 m_Name: Plunger m_TagString: Untagged @@ -29,6 +28,7 @@ Transform: 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: - {fileID: 2983885423002777819} - {fileID: 1501205072833867318} @@ -47,65 +47,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c1591b5a0bc15604fb622d417b509185, type: 3} m_Name: m_EditorClassIdentifier: - _data: - StoragePrefix: 0 - StorageIndex: -1 - IsLocked: 0 - EditorLayerName: - EditorLayerVisibility: 1 - Name: - Type: 1 - Center: - X: 0 - Y: 0 - Width: 25 - Height: 20 - ZAdjust: 0 - Stroke: 80 - SpeedPull: 0.5 - SpeedFire: 80 - MechStrength: 85 - ParkPosition: 0.16666667 - ScatterVelocity: 0 - MomentumXfer: 1 - IsMechPlunger: 0 - AutoPlunger: 0 - AnimFrames: 1 - Material: - Image: - IsVisible: 1 - IsReflectionEnabled: 1 - Surface: - TipShape: 0 .34; 2 .6; 3 .64; 5 .7; 7 .84; 8 .88; 9 .9; 11 .92; 14 .92; 39 .84 - RodDiam: 0.6 - RingGap: 2 - RingDiam: 0.94 - RingWidth: 3 - SpringDiam: 0.77 - SpringGauge: 1.38 - SpringLoops: 8 - SpringEndLoops: 2.5 - IsTimerEnabled: 0 - TimerInterval: 0 - Color: - Red: 207 - Green: 196 - Blue: 196 - Alpha: 4 + _isLocked: 0 + _editorLayer: 0 + _editorLayerName: + _editorLayerVisibility: 1 Position: {x: 0, y: 0} Width: 25 Height: 20 - ParkPosition: 0.16666667 - MomentumXfer: 1 - ScatterVelocity: 0 - IsAutoPlunger: 0 - IsMechPlunger: 0 - SpeedFire: 80 - Type: 1 ZAdjust: 0 - Stroke: 80 - SpeedPull: 0.5 - MechStrength: 85 _surface: {fileID: 0} analogPlungerAction: {fileID: 0} --- !u!114 &4360165373544367127 @@ -121,21 +70,17 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} - ShowColliderMesh: 0 + ShowColliderMesh: 1 ShowAabbs: 0 ---- !u!114 &6380137805125368953 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3492958815385962431} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 + SpeedPull: 0.5 + SpeedFire: 80 + Stroke: 80 + ScatterVelocity: 0 + IsMechPlunger: 0 + IsAutoPlunger: 0 + MechStrength: 85 + MomentumXfer: 1 + ParkPosition: 0.16666667 --- !u!1 &3501041183407321805 GameObject: m_ObjectHideFlags: 0 @@ -164,6 +109,7 @@ Transform: 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: 2688967276888975725} m_RootOrder: 1 @@ -180,7 +126,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8506b0486aacff543b92dd7de914f936, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46138 SpringDiam: 0.77 SpringGauge: 1.38 SpringLoops: 8 @@ -221,6 +167,7 @@ Transform: 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: 2688967276888975725} m_RootOrder: 0 @@ -237,7 +184,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 6e7622b4c6837354c8190ee471830c31, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46132 RodDiam: 0.6 RingGap: 2 RingDiam: 0.94 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Primitive.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Primitive.prefab index cc743d002..74875c1ac 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Primitive.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Primitive.prefab @@ -13,7 +13,6 @@ GameObject: - component: {fileID: 8623744740865740302} - component: {fileID: -1370878250268705008} - component: {fileID: 9164173217974165609} - - component: {fileID: 4695815590852492947} m_Layer: 0 m_Name: Primitive m_TagString: Untagged @@ -31,6 +30,7 @@ Transform: 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: 0} m_RootOrder: 0 @@ -47,80 +47,16 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 151fb873f4d60994e8bde3aff9af4ca2, type: 3} m_Name: m_EditorClassIdentifier: - _data: - StoragePrefix: 0 - StorageIndex: -1 - IsLocked: 0 - EditorLayerName: - EditorLayerVisibility: 1 - Name: - Position: - X: 0 - Y: 0 - Z: 0 - Size: - X: 100 - Y: 100 - Z: 100 - NumVertices: 0 - CompressedVertices: 0 - NumIndices: 0 - CompressedIndices: 0 - CompressedAnimationVertices: - Mesh: - Name: - Vertices: [] - Indices: - AnimationDefaultPosition: 0 - RotAndTra: - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - Image: - NormalMap: - Sides: 4 - Material: - SideColor: - Red: 0 - Green: 0 - Blue: 0 - Alpha: 0 - IsVisible: 1 - IsReflectionEnabled: 1 - DrawTexturesInside: 0 - HitEvent: 1 - Threshold: 2 - Elasticity: 0.3 - ElasticityFalloff: 0.5 - Friction: 0.3 - Scatter: 0 - EdgeFactorUi: 0.25 - CollisionReductionFactor: 0 - IsCollidable: 1 - IsToy: 0 - PhysicsMaterial: - OverwritePhysics: 1 - StaticRendering: 1 - DisableLightingTop: 0 - DisableLightingBelow: 0 - Use3DMesh: 0 - BackfacesEnabled: 0 - DisplayTexture: 0 - ObjectSpaceNormalMap: 0 - MeshFileName: - DepthBias: 0 + _isLocked: 0 + _editorLayer: 0 + _editorLayerName: + _editorLayerVisibility: 1 Position: {x: 0, y: 0, z: 0} Rotation: {x: 0, y: 0, z: 0} Size: {x: 1, y: 1, z: 1} Translation: {x: 0, y: 0, z: 0} ObjectRotation: {x: 0, y: 0, z: 0} - StaticRendering: 1 + _originalRotateZ: 0 --- !u!114 &8623744740865740302 MonoBehaviour: m_ObjectHideFlags: 0 @@ -134,7 +70,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} - ShowColliderMesh: 0 + ShowColliderMesh: 1 ShowAabbs: 0 HitEvent: 1 Threshold: 2 @@ -156,7 +92,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 882edbbe53953ef4ab3b3f846c4a5d9a, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46174 UseLegacyMesh: 0 Sides: 4 --- !u!33 &9164173217974165609 @@ -167,16 +103,3 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5926445367393707725} m_Mesh: {fileID: 0} ---- !u!114 &4695815590852492947 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5926445367393707725} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Ramp.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Ramp.prefab index 7622a8682..929d78ff4 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Ramp.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Ramp.prefab @@ -28,6 +28,7 @@ Transform: 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: 3438007474298596154} m_RootOrder: 0 @@ -44,7 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 96191c0bd78913d4da1df786720fbffd, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46210 --- !u!33 &2139504653030743311 MeshFilter: m_ObjectHideFlags: 0 @@ -64,7 +65,6 @@ GameObject: - component: {fileID: 3438007474298596154} - component: {fileID: -2655459220674085019} - component: {fileID: -7808509646302655205} - - component: {fileID: 1745434368933653034} m_Layer: 0 m_Name: Ramp m_TagString: Untagged @@ -82,6 +82,7 @@ Transform: 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: - {fileID: 6727185824501835254} - {fileID: 424251909686763979} @@ -101,56 +102,21 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a5ac226119aa7a6458cda67c68713b3a, type: 3} m_Name: m_EditorClassIdentifier: - _data: - StoragePrefix: 0 - StorageIndex: -1 - IsLocked: 0 - EditorLayerName: - EditorLayerVisibility: 1 - Name: - DepthBias: 0 - DragPoints: [] - Elasticity: 0 - Friction: 0 - HitEvent: 0 - HeightBottom: 0 - HeightTop: 50 - ImageAlignment: 0 - ImageWalls: 1 - IsCollidable: 1 - IsReflectionEnabled: 1 - IsVisible: 1 - LeftWallHeight: 62 - LeftWallHeightVisible: 30 - OverwritePhysics: 1 - RampType: 0 - RightWallHeight: 62 - RightWallHeightVisible: 30 - Scatter: 0 - Image: - Material: - PhysicsMaterial: - Threshold: 0 - WidthBottom: 75 - WidthTop: 60 - WireDiameter: 8 - WireDistanceX: 38 - WireDistanceY: 88 - IsTimerEnabled: 0 - TimerInterval: 0 - Points: 0 - Type: 0 - HeightBottom: 0 - HeightTop: 50 - ImageAlignment: 0 - ImageWalls: 1 - LeftWallHeightVisible: 30 - RightWallHeightVisible: 30 - WidthBottom: 75 - WidthTop: 60 - WireDiameter: 8 - WireDistanceX: 38 - WireDistanceY: 88 + _isLocked: 0 + _editorLayer: 0 + _editorLayerName: + _editorLayerVisibility: 1 + _type: 0 + _heightBottom: 0 + _heightTop: 50 + _imageAlignment: 0 + _leftWallHeightVisible: 30 + _rightWallHeightVisible: 30 + _widthBottom: 75 + _widthTop: 60 + _wireDiameter: 8 + _wireDistanceX: 38 + _wireDistanceY: 88 _dragPoints: [] --- !u!114 &-7808509646302655205 MonoBehaviour: @@ -165,29 +131,16 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: PhysicsMaterial: {fileID: 0} - ShowColliderMesh: 0 + ShowColliderMesh: 1 ShowAabbs: 0 + HitEvent: 0 + Threshold: 0 LeftWallHeight: 62 RightWallHeight: 62 + OverwritePhysics: 1 Elasticity: 0 Friction: 0 - HitEvent: 0 - OverwritePhysics: 1 Scatter: 0 - Threshold: 0 ---- !u!114 &1745434368933653034 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 350393708356709200} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 --- !u!1 &378628362425001625 GameObject: m_ObjectHideFlags: 0 @@ -216,6 +169,7 @@ Transform: 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: 3438007474298596154} m_RootOrder: 1 @@ -232,7 +186,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4f415a8bce1d6404e914122b72e420ca, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46220 --- !u!33 &7529751573154737776 MeshFilter: m_ObjectHideFlags: 0 @@ -269,6 +223,7 @@ Transform: 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: 3438007474298596154} m_RootOrder: 2 @@ -285,7 +240,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: fb775e2709cbb994e8b981aad06a08c2, type: 3} m_Name: m_EditorClassIdentifier: - _meshCreated: 1 + _instanceID: 46218 --- !u!33 &5184614959798449138 MeshFilter: m_ObjectHideFlags: 0 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Rubber.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Rubber.prefab index 767c1f5e1..47ecdf995 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Rubber.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Rubber.prefab @@ -13,7 +13,6 @@ GameObject: - component: {fileID: -4932743686767062960} - component: {fileID: 3941825256112358932} - component: {fileID: 4392546155774236836} - - component: {fileID: 171621741425913877} m_Layer: 0 m_Name: Rubber m_TagString: Untagged @@ -31,6 +30,7 @@ Transform: 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: 0} m_RootOrder: 0 @@ -52,7 +52,6 @@ MonoBehaviour: _editorLayerName: _editorLayerVisibility: 1 _height: 25 - _hitHeight: 25 _thickness: 8 Rotation: {x: 0, y: 0, z: 0} _dragPoints: [] @@ -72,6 +71,7 @@ MonoBehaviour: ShowColliderMesh: 1 ShowAabbs: 0 HitEvent: 0 + HitHeight: 25 OverwritePhysics: 0 Elasticity: 0 ElasticityFalloff: 0 @@ -89,6 +89,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: bd020e551a441b14da4e1491aa555bac, type: 3} m_Name: m_EditorClassIdentifier: + _instanceID: 46272 --- !u!33 &4392546155774236836 MeshFilter: m_ObjectHideFlags: 0 @@ -97,16 +98,3 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 5446999501863086646} m_Mesh: {fileID: 0} ---- !u!114 &171621741425913877 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5446999501863086646} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Spinner (Builtin).prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Spinner (Builtin).prefab index 231bf36bb..3ecf933f9 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Spinner (Builtin).prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Spinner (Builtin).prefab @@ -29,6 +29,7 @@ Transform: 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: 1544373133874741422} m_RootOrder: 1 @@ -64,6 +65,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 @@ -105,7 +107,6 @@ GameObject: - component: {fileID: 1544373133874741422} - component: {fileID: 4041090927828039183} - component: {fileID: -8167358012222226486} - - component: {fileID: 8363762864822562217} m_Layer: 0 m_Name: Spinner (Builtin) m_TagString: Untagged @@ -123,6 +124,7 @@ Transform: 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: - {fileID: 6219072693502205359} - {fileID: 7410166794513211746} @@ -169,19 +171,6 @@ MonoBehaviour: ShowColliderMesh: 0 ShowAabbs: 0 Elasticity: 0.3 ---- !u!114 &8363762864822562217 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 6563151203485904801} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 --- !u!1 &7122666291571779558 GameObject: m_ObjectHideFlags: 0 @@ -210,6 +199,7 @@ Transform: 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: 1544373133874741422} m_RootOrder: 0 @@ -233,6 +223,7 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab index c38bb28aa..7ae4fbc0c 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Surface.prefab @@ -28,6 +28,7 @@ Transform: 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: 2261584146745169539} m_RootOrder: 1 @@ -44,6 +45,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ed6ad111d870e3b43a828b6086c08607, type: 3} m_Name: m_EditorClassIdentifier: + _instanceID: 46404 --- !u!33 &6530115296024031604 MeshFilter: m_ObjectHideFlags: 0 @@ -63,7 +65,6 @@ GameObject: - component: {fileID: 2261584146745169539} - component: {fileID: 4363410533903828279} - component: {fileID: 7807490122124131058} - - component: {fileID: -3256406434667294810} m_Layer: 0 m_Name: Surface m_TagString: Untagged @@ -81,6 +82,7 @@ Transform: 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: - {fileID: 7705994926884342426} - {fileID: 3323324628286901197} @@ -131,19 +133,6 @@ MonoBehaviour: ElasticityFalloff: 0 Friction: 0.3 Scatter: 0 ---- !u!114 &-3256406434667294810 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 4322651706312387308} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 --- !u!1 &8320692886569796201 GameObject: m_ObjectHideFlags: 0 @@ -172,6 +161,7 @@ Transform: 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: 2261584146745169539} m_RootOrder: 0 @@ -188,6 +178,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 75abfb7d4ccc1b14bb10b53cb3f60574, type: 3} m_Name: m_EditorClassIdentifier: + _instanceID: 46410 --- !u!33 &6035808683100513115 MeshFilter: m_ObjectHideFlags: 0 diff --git a/VisualPinball.Unity/Assets/Resources/Prefabs/Trigger.prefab b/VisualPinball.Unity/Assets/Resources/Prefabs/Trigger.prefab index 4dd907626..feb6c16e3 100644 --- a/VisualPinball.Unity/Assets/Resources/Prefabs/Trigger.prefab +++ b/VisualPinball.Unity/Assets/Resources/Prefabs/Trigger.prefab @@ -14,7 +14,6 @@ GameObject: - component: {fileID: 3195222361417257232} - component: {fileID: -2367600905824176933} - component: {fileID: 1934921143686933034} - - component: {fileID: -6194213659880748689} m_Layer: 0 m_Name: Trigger m_TagString: Untagged @@ -100,6 +99,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 12ec3c1d7933a674b867fada1788b0f4, type: 3} m_Name: m_EditorClassIdentifier: + _instanceID: 46472 Shape: 0 WireThickness: 0 --- !u!33 &1934921143686933034 @@ -110,16 +110,3 @@ MeshFilter: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 8116499157636777247} m_Mesh: {fileID: 0} ---- !u!114 &-6194213659880748689 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 8116499157636777247} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ea7d7495833204790ba1d3a8755397f8, type: 3} - m_Name: - m_EditorClassIdentifier: - ConversionMode: 1 diff --git a/VisualPinball.Unity/Documentation~/android-chrome-192x192.png b/VisualPinball.Unity/Documentation~/android-chrome-192x192.png deleted file mode 100644 index ef4b5bf66..000000000 Binary files a/VisualPinball.Unity/Documentation~/android-chrome-192x192.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/android-chrome-256x256.png b/VisualPinball.Unity/Documentation~/android-chrome-256x256.png deleted file mode 100644 index c9da1dfda..000000000 Binary files a/VisualPinball.Unity/Documentation~/android-chrome-256x256.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/api/.gitignore b/VisualPinball.Unity/Documentation~/api/.gitignore deleted file mode 100644 index f798527e6..000000000 --- a/VisualPinball.Unity/Documentation~/api/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -############### -# temp file # -############### -*.yml -.manifest diff --git a/VisualPinball.Unity/Documentation~/api/index.md b/VisualPinball.Unity/Documentation~/api/index.md deleted file mode 100644 index 618c456e3..000000000 --- a/VisualPinball.Unity/Documentation~/api/index.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: Scripting API ---- - -
-

- -*This section will contain the scripting API documentation.* - -

diff --git a/VisualPinball.Unity/Documentation~/apple-touch-icon.png b/VisualPinball.Unity/Documentation~/apple-touch-icon.png deleted file mode 100644 index ecaa8ddfc..000000000 Binary files a/VisualPinball.Unity/Documentation~/apple-touch-icon.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/advanced/camera-settings.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/advanced/camera-settings.md index 52bc9bf35..9b9d5798e 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/advanced/camera-settings.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/advanced/camera-settings.md @@ -13,7 +13,7 @@ The camera controller is setup to orbit around a focal point. While doing this i ## Usage - + To use the camera controller, select the `Camera` scene object, which is at the very top of the hierarchy. In the inspector you will find a few sliders: diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md index cc19ff34e..7bfb09ef5 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/lamp-manager.md @@ -20,7 +20,7 @@ To link each of the playfield lamps to the gamelogic engine and configure how th Physical machines have many different implementations when it comes to lighting. The vast majority of solid state machines from the 1970s until the early 2010s used a **lamp matrix**, where lamps were addressed by row/column, and each individual lamp could only be fully on or off. Historically, incandescent light bulbs were used, which resulted in a brief warm-up period until they reached full brightness (and a cool-down period when turned off). To simulate this, VPE adopted the fade-in and fade-out properties for lights from Visual Pinball. -Later machines used single colored **LEDs** that were each directly connected to a controller board (see also: [Lights vs LEDs](https://docs.missionpinball.org/en/latest/mechs/lights/lights_versus_leds.html)). Unlike matrix lamps, the intensity of LEDs could be finely controlled by the game software. +Later machines used single colored **LEDs** that were each directly connected to a controller board (see also: [Lights vs LEDs](https://missionpinball.org/mechs/lights/lights_versus_leds/)). Unlike matrix lamps, the intensity of LEDs could be finely controlled by the game software. More recently, games started using **RGB-LEDs** that can change color as well as brightness during gameplay. In VPE, these can be handled in two different ways: - As three single connections from the gamelogic engine, one for each color channel (this is what PinMAME provides, for example.) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md b/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md index dd7a4f246..f226ed7af 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/editor/wire-manager.md @@ -71,7 +71,7 @@ However, by design, there are two caveats: > [!note] -> [MPF](xref:mpf_index) has a similar feature called [Hardware Rules](https://docs.missionpinball.org/en/dev/hardware/hw_rules.html#the-solution-hardware-rules). This is the preferred way, because the gamelogic engine explicitly notifies VPE about which wires to add and remove during gameplay. +> [MPF](xref:mpf_index) has a similar feature called [Hardware Rules](https://missionpinball.org/hardware/hw_rules/#the-solution-hardware-rules). This is the preferred way, because the gamelogic engine explicitly notifies VPE about which wires to add and remove during gameplay. > > However, other gamelogic engines like PinMAME don't have this feature, that's why VPE comes with the *dynamic wire* feature that guesses when wire is active and when not. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/introduction/overview.md b/VisualPinball.Unity/Documentation~/creators-guide/introduction/overview.md index 358160409..46e9a9ef6 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/introduction/overview.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/introduction/overview.md @@ -7,22 +7,16 @@ description: VPE, the Visual Pinball Engine, is a modern, open source pinball si # Overview -The Visual Pinball Engine (which we call "VPE") is an open source pinball simulator that you can use to create pinball games for Windows, macOS, and Linux. It's based on the famous [Visual Pinball](https://sourceforge.net/projects/vpinball/) (often abbreviated "VP", or "VPX" for its latest version 10) meaning VPE's physics simulation matches that in VPX, and it can read and write the same file format. +The Visual Pinball Engine (which we call "VPE") is an open-source pinball simulator that you can use to create pinball games for Windows, macOS, and Linux. It's based on the famous [Visual Pinball](https://github.com/vpinball/vpinball) (often abbreviated "VP" or "VPX" for its latest version 10), meaning VPE's physics simulation matches that in VPX, and it can read and write the same file format. -VPE uses [Unity](https://unity.com/) for its underlying game architecture. Unity is one of the leading cross-platform game engines and provides VPE with an advanced render pipeline that produces high-quality visuals and is continuously maintained and updated to work with new hardware features. Unity also comes with an amazing editor, which VPE extends to make the table creation process as easy as possible. +VPE uses [Unity](https://unity.com/) for its underlying game architecture. Unity is one of the leading cross-platform game engines and provides VPE with an advanced render pipeline that produces high-quality visuals and is continuously maintained and updated to work with new hardware features. Unity also comes with a fantastic editor, which VPE extends to make the table creation process as easy as possible. > [!Video https://www.youtube.com/embed/JxjdZ6mohfA] *An imported .vpx file in VPE, using Unitys' High Definition Render Pipeline.* -> [!Video https://www.youtube.com/embed/wHcKd_FExsE] +> [!Video https://www.youtube.com/embed/_CfZImFl1ME] Gottlieb's *Volley*, remodeled and retextured in Unity - -> [!NOTE] -> Technically, VPE is what we call a "library". A library is not executable per se, because it needs a host application. -> -> We will provide such a host application in the future, but for now you will need to create a new Unity project and add VPE as a package in order to run it. - ## Audience -This documentation is mainly aimed at table creators ("authors"). VPE is currently not in a state where it is usable by the general public ("players"). Documentation about how to setup VPE to play will follow at a later stage. +This documentation is mainly aimed at table creators ("authors"). VPE is currently not in a state where it is usable by the general public ("players"). Documentation about how to set up VPE to play will follow at a later stage. \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/displays.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/displays.md index 7c74e1779..df986e776 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/displays.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/displays.md @@ -23,13 +23,13 @@ For example, in [MPF](xref:mpf_index) you name your displays yourself in the mac ### Editor -Add display component +Add display component VPE provides three display components, a [score reel](xref:score-reels), a segment display and a DMD. Both the segment display and the DMD component create the underlying geometry and apply a shader that renders the content of the display. In order to create one, make an empty game object in your scene and add the desired component under *Visual Pinball -> Display*. You can also create the game object with a component already assigned by right-clicking in the hierarchy and choosing *Visual Pinball -> Dot Matrix Display*. This will place the display into your scene right behind your playfield. -DMD Inspector +DMD Inspector Selecting the game object will let you customize it in the inspector, and assign the ID that links it to the gamelogic engine. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/collision-switches.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/collision-switches.md index d0483bd09..63389e314 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/collision-switches.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/collision-switches.md @@ -14,7 +14,7 @@ To create a Collision Switch: - Add the collision switch directly to a hittable game object. Select the game object you want to add it to, click on *Add Component* in the inspector and select *Visual Pinball -> Mechs -> Collision Switch*. - + To associate the collision switch with a game logic engine switch, use the [Switch Manager](xref:switch_manager) and select the switch in the *Element* column: diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/drop-target-banks.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/drop-target-banks.md index f5e48cea7..e36bdd6a4 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/drop-target-banks.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/drop-target-banks.md @@ -15,7 +15,7 @@ You can create a Drop Target Bank in two different ways. 1. If your game has a single bank drop target, or multiple single bank drop targets, it is preferred to add it directly to the drop target. Select the drop target you want to add it to, click on *Add Component* in the inspector and select *Visual Pinball -> Mechs -> Drop Target Bank*. 2. If your game has drop target banks with multiple drop targets, click on *Drop Target Bank* in the toolbox. This will add a *Drop Target Banks* hierarchy to the playfield and create a new GameObject with the right component assigned. - + To configure the drop target bank, select the total number of drop targets from the *Banks* drop down. Then, under *Playfield Links*, select each drop target belonging to the bank. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/flippers.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/flippers.md index 5ecd0009c..1da781e92 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/flippers.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/flippers.md @@ -20,7 +20,7 @@ It's possible to provide a custom mesh for the flipper by replacing the game obj ### Physics -Flipper Collider +Flipper Collider Adding the *Flipper Collider* component to the flipper makes it part of the physics simulation. Here you can tweak the various parameters. Most of the following is taken directly from [Mukuste's Wiki](https://github.com/c-f-h/vpinball/wiki/VP10-Physics#flipper-parameters). diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/lifting-gates.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/lifting-gates.md index 91177bbaf..a76436dfb 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/lifting-gates.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/lifting-gates.md @@ -6,7 +6,7 @@ description: This component makes your gate toggelable by a coil. It works by li # Lifting Gates -
+

Photo © 2022 by bord @@ -18,7 +18,7 @@ VPE provides a component that you can add to your existing gate. By doing that, ## Setup - + In order to add the lifting gate feature to a gate, select the game object of the gate and click on *Add Component* in the inspector. Select it by searching or navigating to *Visual Pinball -> Mechs -> Gate Lifter*. @@ -35,4 +35,4 @@ How fast the gate rotates from and into the decativated position. Higher is fast Adding the gate lifter component adds a coil input to the gate, i.e. you can map it to any of the gamelogic engine's coil outputs through the [Coil Manager](xref:coil_manager). > [!NOTE] -> If you're working on an EM game or an original game with [Visual Scripting](xref:uvs_index), don't forget to add the new coil to the [coil definitions](xref:uvs_setup#coils). \ No newline at end of file +> If you're working on an EM game or an original game with [Visual Scripting](xref:uvs_index), don't forget to add the new coil to the [coil definitions](xref:uvs_setup#coils). diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/light-groups.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/light-groups.md index 9dffbf031..ac9c364e2 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/light-groups.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/light-groups.md @@ -6,13 +6,13 @@ description: VPE can group and address multiple lights at once. # Light Groups -Sometimes, a game addresses multiple physical lights as one logical lamp, i.e. all lights are always toggled or faded at the same time. Typical use cases are [GI strips](https://docs.missionpinball.org/en/latest/mechs/lights/gis.html). Instead of creating a link in the [Lamp Manager](xref:lamp_manager) for each light separately, VPE ships with a component called *Lamp Group*. +Sometimes, a game addresses multiple physical lights as one logical lamp, i.e. all lights are always toggled or faded at the same time. Typical use cases are [GI strips](https://missionpinball.org/mechs/lights/gis/). Instead of creating a link in the [Lamp Manager](xref:lamp_manager) for each light separately, VPE ships with a component called *Lamp Group*. A light group is a component you can add to any GameObject. It's recommended to make it parent of the light objects it contains, but you can also keep it outside of the lights hierarchy, since it explicitly references the lights it contains. ## Setup - + To create a new light group, select the GameObject you want to add your light group to, and in the inspector click on *Add Component* and choose *Visual Pinball -> Game Item -> Light Group*. @@ -34,4 +34,4 @@ Simply clears the list. When working with lights, the GameObject with the actual light source is nested within the main object. This can make adjusting light settings for multiple lights tedious, since you have to drill into each parent in order to select the source. -This button selects all the source GameObjects for the lights in the light group. \ No newline at end of file +This button selects all the source GameObjects for the lights in the light group. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/rotators.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/rotators.md index eda42327f..1a4451d2b 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/rotators.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/rotators.md @@ -12,7 +12,7 @@ Sometimes during gameplay, you might need to rotate objects in order to recreate ## Setup -Rotator Inspector +Rotator Inspector In order to create a rotator, add the **Rotator** component to a game object by clicking *Add Component* in the inspector, then choosing *Visual Pinball -> Game Item -> Rotator*. You can use any game object, although we recommend adding it to the target that you want to rotate. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-motors.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-motors.md index 27d920d36..b88d467e4 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-motors.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-motors.md @@ -37,7 +37,7 @@ To setup a score motor, select any game object, click on *Add Component* in the Next, configure the score motor. The inspector shows the following options: - + - **Steps** defines how many steps the score motor pulses for one turn. - **Duration** defines the length of time it takes the score motor to completely cycle. @@ -47,7 +47,7 @@ Next, configure the score motor. The inspector shows the following options: > [!NOTE] > The minimum amount of `Steps` for a score motor is `5`. `Increase by 5` will not be shown under `Reel timing by increase` if `Steps` is set to 5, as all actions would be `Increase`. - + By default, the score motor is configured to: @@ -94,4 +94,4 @@ In order to hook into those switches, you'll have to create them in the GLE insp -Then, in your graphs, add your logic behind the corresponding [On Switch Changed](xref:uvs_node_reference#on-switch-changed) node(s). \ No newline at end of file +Then, in your graphs, add your logic behind the corresponding [On Switch Changed](xref:uvs_node_reference#on-switch-changed) node(s). diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-reels.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-reels.md index a265fe57b..9eadb016d 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-reels.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/score-reels.md @@ -6,7 +6,7 @@ description: How to use EM-style reels to display the score. # Score Reel Displays -Score Reels a of a Gottlieb Volley +Score Reels a of a Gottlieb Volley In electro-mechanical games, score reels are very common for displaying the player score. Typically, four to six units are mounted behind the backglass. Each reel is driven by a coil that advances the reel by one position when pulsed. The coils are driven by the playfield elements in the game, often indirectly through a score motor for multi-point scoring. @@ -39,7 +39,7 @@ In your scene, drop in your reel model and add the *Score Reel* component (not t #### Score Reel -Score Reel Inspector +Score Reel Inspector The *Score Reel* component is quick to set up. There is only one option, which is the *rotation direction*. What the score reel component gets from the display component is "turn to position X", where X is between 0 and 9, and the component's job is to animate the reel to that position. @@ -47,7 +47,7 @@ Internally, it also takes in the rotation speed, and how long it rests at the fi #### Score Reel Display -Score Reel Display Inspector +Score Reel Display Inspector This is the component on the parent game object that receives score numbers from the game and tells the individual reels to which position they need to turn to. @@ -62,7 +62,7 @@ This is the component on the parent game object that receives score numbers from ### Gamelogic Engine -Score Reel Display Inspector +Score Reel Display Inspector Score reels are primarily used in EMs, so they are typically driven by [Visual Scripting](xref:uvs_index). As with every display, the first step is to define the display in the GLE component. @@ -74,4 +74,4 @@ Next, add *Numeric* under *Supported Formats*. In Visual Scripting, use the [Update Display](xref:uvs_node_reference#update-display) node to set a new score. It's up to you whether to use a separate [event](xref:uvs_setup#events) or to subscribe to a [player variable](xref:uvs_variables) directly. -If you're using a score motor, read how to set it up correctly [here](xref:score-motors#usage). \ No newline at end of file +If you're using a score motor, read how to set it up correctly [here](xref:score-motors#usage). diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md index 9e12add05..c6785146c 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/slingshots.md @@ -15,7 +15,7 @@ VPE does provide a slingshot component that implements the rubber animation duri # Setup -Slingshot Inspector +Slingshot Inspector ### Slingshot Wall @@ -34,7 +34,7 @@ To set the start and end positions of the control points, we reference two rubbe On physical machines, the rubber is moved by an arm attached to the coil. VPE can simulate the movement of that arm by rotating a primitive across the X-axis. In the *Coil Arm* field, a reference to the primive can be set, and the total angle of rotation under *Arm Angle*. -Slingshot Animation Curve +Slingshot Animation Curve ### Animation diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/teleporters.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/teleporters.md index 77397bffe..7c2e42eb2 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/teleporters.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/teleporters.md @@ -15,7 +15,7 @@ Sometimes it's easier to teleport the ball from one place to another instead of ## Setup -Teleporter Inspector +Teleporter Inspector In order to create a new teleporter, select the GameObject you want to add it to, click on *Add Component* and select *Visual Pinball -> Game Item -> Teleporter*. You can choose any GameObject, although we recommend putting it on same GameObject as the source kicker. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md index c2b285dba..4b91ae7c9 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/manual/mechanisms/troughs.md @@ -16,7 +16,7 @@ When importing a `.vpx` file that doesn't have any troughs (which is likely, bec ## Linking to the Playfield - + To interact with the game, you must set up an **input switch** to drain the ball into the trough, and an **exit kicker** to release a new ball from the trough. This terminology may seem weird, since the ball *exits* the playfield when draining, but from the trough's perspective, that's where the ball *enters*. @@ -44,9 +44,9 @@ In this section we'll again link to the excellent MPF documentation explaining e ### Modern Mechanical - + -[Modern troughs with mechanical switches](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-2-modern-trough-with-mechanical-switches) are covered by this type. +[Modern troughs with mechanical switches](https://missionpinball.org/mechs/troughs/#option-2-modern-trough-with-mechanical-switches) are covered by this type. The ball drains from the playfield directly into the ball stack, and every ball slot has an associated switch. When a ball gets ejected, the remaining balls move down simultaneously to the next position. During that movement, their switches get first opened and then closed again when they reach the next position. The time of this movement is defined by *Roll Time*. @@ -54,9 +54,9 @@ The ball drains from the playfield directly into the ball stack, and every ball ### Modern Opto - + -[Modern troughs with optical switches](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-1-modern-trough-with-opto-sensors) work similar similar to their mechanical counterparts. However there are two differences: +[Modern troughs with optical switches](https://missionpinball.org/mechs/troughs/#option-1-modern-trough-with-opto-sensors) work similar similar to their mechanical counterparts. However there are two differences: 1. Opto switches have the inverse value of mechanical switches. That means per default, an opto switch is *closed*, and when a ball rolls through, it opens. It's kind of logical, because the ball *blocks* the beam of light thus *opening* the circuit, while a mechanical switch gets *closed* by the ball's weight. 2. Timings are different. When a ball approaches an opto switch, the switch gets triggered as soon as the ball's *front* hits the beam, while a mechanical switch gets triggered when the ball's *center* is over it. This results in very short closing times when the ball stack moves to the next position after a ball eject. @@ -70,9 +70,9 @@ We call this closing time the *transition time* - it's the time during stack tra ### Two coils and multiple switches - + -[Troughs of this type](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-3-older-style-with-two-coils-and-switches-for-each-ball) can be found in older machines from the 80s and early 90s. They consist of two parts: +[Troughs of this type](https://missionpinball.org/mechs/troughs/#option-3-older-style-with-two-coils-and-switches-for-each-ball) can be found in older machines from the 80s and early 90s. They consist of two parts: 1. A drain, the ball rolls into when leaving the playfield 2. A ball stack, where the out of play balls are held. @@ -83,9 +83,9 @@ In terms of switches, they still include a switch per ball in the stack, but als ### Two coils and one switch - + -A trough can also have [only one switch](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-4-older-style-with-two-coils-and-only-one-ball-switch) in the ball stack. +A trough can also have [only one switch](https://missionpinball.org/mechs/troughs/#option-4-older-style-with-two-coils-and-only-one-ball-switch) in the ball stack. Instead of a *Switch Count* like the previous types, you select a *Switch Position*, which is the position in the ball stack at which the ball farthest away from the eject coil sits. @@ -93,9 +93,9 @@ Instead of a *Switch Count* like the previous types, you select a *Switch Positi ### Classic single ball - + -A single ball trough may work [with](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-5-classic-single-ball-single-coil) or [without](https://docs.missionpinball.org/en/latest/mechs/troughs/#option-6-classic-single-ball-single-coil-no-shooter-lane) a shooter lane. The principle is simple: After draining, the ball is kept on the drain coil, which ejects the ball either directly into the plunger lane or back onto the playfield. +A single ball trough may work [with](https://missionpinball.org/mechs/troughs/#option-5-classic-single-ball-single-coil) or [without](https://missionpinball.org/mechs/troughs/#option-6-classic-single-ball-single-coil-no-shooter-lane) a shooter lane. The principle is simple: After draining, the ball is kept on the drain coil, which ejects the ball either directly into the plunger lane or back onto the playfield. *The animation shows single ball trough that ejects a ball and drains it a few seconds later.* diff --git a/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md b/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md index 6087321fa..23a22fb7a 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/setup/installing-vpe.md @@ -13,16 +13,11 @@ In order to start creating or modifying tables with VPE, the first thing you'll Unity uses an application called *Unity Hub* to update itself, create new projects and provide quick access to them. The install process is straight-forward and documented [here](https://docs.unity3d.com/Manual/GettingStartedInstallingHub.html) if you run into troubles. -**2021.3.0** is the latest version supported by VPE so click on *Skip Installation* in the first dialog to avoid installing the newest version of the Editor. After accepting the license agreement you will have access to Unity Hub. - -> [!NOTE] -> To install the supported version of the editor you can either -> * Paste this direct link into your browser: unityhub://2021.3.0f1/6eacc8284459 -> * Find the correct version from the [Unity release archive](https://unity3d.com/get-unity/download/archive) +**2022.3** is the recommended Unity version at the moment. Once 2023 is officially out, we'll be updating the dependencies and the documentation. You can leave all the other options unchecked during install. -Once Unity is downloaded and installed, you're ready to create a new VPE project. Click on *New Project*, be sure to have selected the 2021.3.0f1 version at the top, and you'll see the following choices: +Once Unity is downloaded and installed, you're ready to create a new VPE project. Click on *New Project*, be sure to have selected the 2022.3 version at the top, and you'll see the following choices: ![New Unity Project](unity-create-new-project.png) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/setup/running-vpe.md b/VisualPinball.Unity/Documentation~/creators-guide/setup/running-vpe.md index b98909be1..35b3235a1 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/setup/running-vpe.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/setup/running-vpe.md @@ -19,7 +19,7 @@ Now that we have the camera of the scene view somewhat aligned, we still can't s ![Imported blank table](unity-imported-table-ugly-gizmos.png) -Gizmo Size +Gizmo Size These orange artifacts are what Unity calls [Gizmo Icons](https://docs.unity3d.com/Manual/GizmosMenu.html). They are enabled by default, and since VPE uses icons for its playfield elements, they are all over the place. Unity's default gizmo size is adapted for rather large scenes and we're dealing with a pinball table, let's make them smaller by clicking on the gizmo icon in the *Scene* view, and pull the size *3D Icons* slider down until you're happy. You can additionally hide the VPE icons by clicking on *Visual Pinball -> Editor -> Disable Gizmo Icons*. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml index 9fa539b1e..b6d1f7c04 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/toc.yml +++ b/VisualPinball.Unity/Documentation~/creators-guide/toc.yml @@ -37,50 +37,50 @@ href: tutorials/index.md items: - name: Create a Playfield - href: xref:tutorial_playfield + uid: tutorial_playfield items: - name: Albedo Texture and Masks - href: xref:tutorial_playfield_1 + uid: tutorial_playfield_1 - name: Playfield Mesh - href: xref:tutorial_playfield_2 + uid: tutorial_playfield_2 - name: Texturing - href: xref:tutorial_playfield_3 + uid: tutorial_playfield_3 - name: Texturing (details) - href: xref:tutorial_playfield_3b + uid: tutorial_playfield_3b - name: Import Into Unity - href: xref:tutorial_playfield_4 + uid: tutorial_playfield_4 - name: Create Realistic Looking Plastics - href: xref:tutorial_plastics + uid: tutorial_plastics items: - name: Prepare Artwork - href: xref:tutorial_plastics_1 + uid: tutorial_plastics_1 - name: Create Mesh - href: xref:tutorial_plastics_2 + uid: tutorial_plastics_2 - name: UV-Map Mesh - href: xref:tutorial_plastics_3 + uid: tutorial_plastics_3 - name: Import Into Unity - href: xref:tutorial_plastics_4 + uid: tutorial_plastics_4 - name: Create a Backglass - href: xref:tutorial_backglass + uid: tutorial_backglass items: - name: Prepare Artwork - href: xref:tutorial_backglass_1 + uid: tutorial_backglass_1 - name: Create Mesh - href: xref:tutorial_backglass_2 + uid: tutorial_backglass_2 - name: Import into Unity - href: xref:tutorial_backglass_3 + uid: tutorial_backglass_3 - name: Make a 3D Scan Game-Ready - href: xref:tutorial_3d_scan + uid: tutorial_3d_scan items: - name: Clean Up the Mesh - href: xref:tutorial_3d_scan_1 + uid: tutorial_3d_scan_1 - name: Retopologize - href: xref:tutorial_3d_scan_2 + uid: tutorial_3d_scan_2 - name: Bake Texture Maps - href: xref:tutorial_3d_scan_3 + uid: tutorial_3d_scan_3 - name: Manual href: manual/manual.md diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/index.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/index.md index c88b98532..9d80566cb 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/index.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/index.md @@ -9,3 +9,4 @@ description: Visual Pinball for Unity - Tutorials - [Create a Playfield](xref:tutorial_playfield) - [Create Realistic Looking Plastics](xref:tutorial_plastics) - [Create a Backglass](xref:tutorial_backglass) +- [Make a 3D Scan Game-Ready](xref:tutorial_3d_scan) diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-backglass/1-prepare-artwork.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-backglass/1-prepare-artwork.md index e6f7e4445..6a65a2d5e 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-backglass/1-prepare-artwork.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-backglass/1-prepare-artwork.md @@ -16,7 +16,7 @@ Once you have your artwork secured save this as a PNG file. We'll name it `Backg ## Create the Thickness Mask - + To block the passage of light through the backglass we need to make a thickness map layer. This layer consists of pure white for the areas that are opaque and pure black for the areas that are transparent. If you have physical access to the backglass then you can scan the backside to get this mask. Otherwise, you'll have to trace what's visible on the front, and try to reconstruct elements that aren't. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/1-prepare-artwork.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/1-prepare-artwork.md index 5cc59bb12..6dca3596b 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/1-prepare-artwork.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/1-prepare-artwork.md @@ -63,7 +63,7 @@ Note the *Links* toolbox that shows the individual plastic files. Also note the ## Step 3: Export Texture and Outlines - + We'll first export the texture. In Illustrator, click on *File -> Export -> Export As...*, and make sure *Use Artboards* is checked. As type, select *PNG*. Then, enter `Plastics.png` as file name and click on *Export*, which will result in the dialog seen in the screenshot. @@ -87,7 +87,7 @@ Your document should now look like this: Don't worry about the color, it just needs to be filled in any color. I'm using magenta because it gives a good contrast to the rest of the artwork. - + Click on *File -> Save a Copy...*, enter `Plastics.svg` as file name, save as type *SVG* and make sure *Use Artboards* is unchecked this time. Click on *Save*. In the options panel, click on *More Options* and make sure all the options are disabled as shown in the screenshot. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/3-uv-map-mesh.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/3-uv-map-mesh.md index 04853325d..cab044901 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/3-uv-map-mesh.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/3-uv-map-mesh.md @@ -11,7 +11,7 @@ description: How to UV-map the texture onto your mesh in Blender We need three material slots, for the top and bottom faces, as well as for the edges. We'll split the vertices by firstly assigning everything to an "edge" slot, and then re-assigning the top and bottom faces to their own slots. - + In object mode, open *Materials* properties and remove the current material slot (hit `-`). Add three new slots (press 3× `+`), and for each slot, create a new material by hitting the *New* button when the slot is selected. Name them "top", "bottom" and "edge" and set their base color to red, green, and blue respectively. Your slots should now look like in the screenshot. @@ -21,7 +21,7 @@ Then, press `7` on the numpad to switch to top view, zoom in a bit so you can cl ![Triangle selected](blender-triangle-selected-2.png) - + Click on *Select -> Select Similar -> Coplanar*, which should result in all top faces of all plastics being selected (but *not* the bottoms ones). However, you might get the bottom faces selected too, because there's a threshold that might be too large. You can check it by rotating the camera and verify that the bottom faces are not selected. If they are, expand the parameters and set the threshold to a small enough value. Then, select the "top" material slot and hit *Assign*. The top surfaces should turn red. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/4-import-into-unity.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/4-import-into-unity.md index 6f6d1ecb0..45f2121ee 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/4-import-into-unity.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-plastics/4-import-into-unity.md @@ -23,7 +23,7 @@ We recommend unpacking the prefab. Unpacking will still reference the meshes of First, let's configure how the texture is imported. In the *Project* window, navigate to `Assets//Textures` where you saved `Plastics.png`, and select it. In the *Inspector* window, check the option *Alpha Is Transparent*. Depending on how large you've exported it, you might need to update *Max Size* as well, in our case we'll use 4096 × 4096. When you're done, hit *Apply* at the bottom. - + Next, we'll create a material of our bottom surface. In the *Project* window, navigate to `Packages/Visual Pinball Engine (HDRP)/Assets/Art/Materials/Default/Plastic`, select `Plastics Decal`, hit `Ctrl`+`C`. Navigate to your project's `Assets/
/Materials` folder, and press `Ctrl`+`V`. diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/1-textures.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/1-textures.md index a133342fd..cf0ef45d7 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/1-textures.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/1-textures.md @@ -10,7 +10,7 @@ In this first step, we'll go through how to create the various textures that we ## Texture Layout - + The geometry of the playfield has three different areas: diff --git a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md index a383b0b15..0b47c20e5 100644 --- a/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md +++ b/VisualPinball.Unity/Documentation~/creators-guide/tutorials/realistic-playfield/4-import-into-unity.md @@ -15,7 +15,7 @@ First, let's bring our files into the Unity project. There are four files that g While the import options for the FBX file are fine, we need to adjust them for the textures. - + Select the base texture and change the following options in the *Inspector*: diff --git a/VisualPinball.Unity/Documentation~/docfx.json b/VisualPinball.Unity/Documentation~/docfx.json index c6e7308c0..c5d29bcf6 100644 --- a/VisualPinball.Unity/Documentation~/docfx.json +++ b/VisualPinball.Unity/Documentation~/docfx.json @@ -1,103 +1,63 @@ { - "metadata": [ - { - "src": [ - { - "files": [ - "VisualPinball.Unity/**.csproj", - "VisualPinball.Unity.Patcher/**.csproj" - ], - "src": "../" - } - ], - "dest": "api", - "disableGitFeatures": false, - "disableDefaultFilter": false, - "filter": "filterConfig.yml" - } - ], - "build": { - "content": [ - { - "files": [ - "api/**.yml", - "api/index.md" - ] - }, - { - "files": [ - "creators-guide/**/toc.yml", - "creators-guide/**.md", - "plugins/**/toc.yml", - "plugins/**.md", - "toc.yml", - "*.md" - ] - }, - { - "files": "CHANGELOG.md", - "src": "../.." - } - ], - "resource": [ - { - "files": [ - "**/*.png", - "**/*.gif", - "**/*.jpg", - "**/*.svg", - "**/*.ico", - "**/*.webp", - "**/*.webmanifest", - "../VisualPinball.Unity.Editor/Resources/Icons/*.png", - "CNAME" - ] - }, - { - "files": "static/**/*.woff*", - "dest": "/fonts" - }, - { - "files": "static/*.css", - "dest": "/styles" - } - ], - "overwrite": [ - { - "files": [ - "apidoc/**.md" - ], - "exclude": [ - "obj/**", - "_site/**" - ] - } - ], - "dest": "_site", - "globalMetadataFiles": [], - "fileMetadataFiles": [], - "template": [ - "default", - "template/vpe", - "template/lightbox-featherlight" - ], - "postProcessors": [ "ExtractSearchIndex" ], - "globalMetadata": { - "_appTitle": "VPE Documentation", - "_appFooter": "Copyright © 2022 VPE Team", - "_gitContribute": { - "branch": "master" - } - }, - "sitemap":{ - "baseUrl": "https://docs.visualpinball.org/", - "priority": 0.5, - "changefreq": "weekly" - }, - "markdownEngineName": "markdig", - "noLangKeyword": false, - "keepFileLink": false, - "cleanupCacheHistory": false, - "disableGitFeatures": false - } -} \ No newline at end of file + "build": { + "content": [ + { + "files": [ + "creators-guide/**/toc.yml", + "creators-guide/**.md", + "plugins/**/toc.yml", + "plugins/**.md", + "toc.yml", + "*.md" + ] + }, + { + "files": "CHANGELOG.md", + "src": "../.." + } + ], + "resource": [ + { + "files": [ + "**/*.png", + "**/*.gif", + "**/*.jpg", + "**/*.svg", + "**/*.ico", + "**/*.webp", + "**/*.webmanifest", + "../VisualPinball.Unity.Editor/Resources/Icons/*.png", + "CNAME" + ] + } + ], + "output": "_site", + "globalMetadataFiles": [], + "fileMetadataFiles": [], + "template": [ + "default", + "modern", + "template/vpe" + ], + "postProcessors": [ "ExtractSearchIndex" ], + "globalMetadata": { + "_appTitle": "VPE Documentation", + "_appFooter": "Copyright © 2023 VPE Team", + "_appFaviconPath": "favicon.png", + "_gitContribute": { + "branch": "master" + }, + "_enableSearch": true + }, + "sitemap":{ + "baseUrl": "https://docs.visualpinball.org/", + "priority": 0.5, + "changefreq": "weekly" + }, + "markdownEngineName": "markdig", + "noLangKeyword": false, + "keepFileLink": false, + "cleanupCacheHistory": false, + "disableGitFeatures": false + } +} diff --git a/VisualPinball.Unity/Documentation~/favicon-16x16.png b/VisualPinball.Unity/Documentation~/favicon-16x16.png deleted file mode 100644 index 4b05796cc..000000000 Binary files a/VisualPinball.Unity/Documentation~/favicon-16x16.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/favicon-32x32.png b/VisualPinball.Unity/Documentation~/favicon-32x32.png deleted file mode 100644 index 6d35bc185..000000000 Binary files a/VisualPinball.Unity/Documentation~/favicon-32x32.png and /dev/null differ diff --git a/VisualPinball.Unity/Documentation~/favicon.ico b/VisualPinball.Unity/Documentation~/favicon.ico index 5bea928f4..8e856771e 100644 Binary files a/VisualPinball.Unity/Documentation~/favicon.ico and b/VisualPinball.Unity/Documentation~/favicon.ico differ diff --git a/VisualPinball.Unity/Documentation~/favicon.png b/VisualPinball.Unity/Documentation~/favicon.png index 4d964c565..fcdc0111e 100644 Binary files a/VisualPinball.Unity/Documentation~/favicon.png and b/VisualPinball.Unity/Documentation~/favicon.png differ diff --git a/VisualPinball.Unity/Documentation~/filterConfig.yml b/VisualPinball.Unity/Documentation~/filterConfig.yml deleted file mode 100644 index 61502fb71..000000000 --- a/VisualPinball.Unity/Documentation~/filterConfig.yml +++ /dev/null @@ -1,10 +0,0 @@ -apiRules: - - include: - hasAttribute: - uid: VisualPinball.Unity.ApiAttribute - - exclude: - uidRegex: '^VisualPinball\.Unity\..*' - type: Type - - exclude: - uidRegex: ^System\.Object - type: Type \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/index.md b/VisualPinball.Unity/Documentation~/index.md index 38d0495ac..51c3aad98 100644 --- a/VisualPinball.Unity/Documentation~/index.md +++ b/VisualPinball.Unity/Documentation~/index.md @@ -1,15 +1,496 @@ --- title: Welcome description: Visual Pinball for Unity - Documentation, Guides and Tutorials. +layout: landing +is_full: true ---
Welcome to the documentation of the - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [Start here with the creator's guide](xref:vpe-overview). *Be aware we're at the very beginning of writing documentation. You'll see weird content.* -
\ No newline at end of file + diff --git a/VisualPinball.Unity/Documentation~/plugins/index.md b/VisualPinball.Unity/Documentation~/plugins/index.md index 996f2ce04..7c55af80f 100644 --- a/VisualPinball.Unity/Documentation~/plugins/index.md +++ b/VisualPinball.Unity/Documentation~/plugins/index.md @@ -14,7 +14,7 @@ VPE has a plug-in system that allows other software to integrate with it. Plugin ## [Visual Scripting](xref:uvs_index) -For original games or EM machines, we recommend using our visual scripting package which extends [Unity's visual scripting](https://unity.com/products/unity-visual-scripting). +For original games or EM machines, we recommend using our visual scripting package which extends [Unity's Visual Scripting](https://unity.com/products/unity-visual-scripting). ## [Mission Pinball Framework](xref:mpf_index) diff --git a/VisualPinball.Unity/Documentation~/plugins/pinmame/mechs.md b/VisualPinball.Unity/Documentation~/plugins/pinmame/mechs.md index 5a3462b8c..970f82b46 100644 --- a/VisualPinball.Unity/Documentation~/plugins/pinmame/mechs.md +++ b/VisualPinball.Unity/Documentation~/plugins/pinmame/mechs.md @@ -34,7 +34,7 @@ This page is about approach #2 and describes how you can configure and use PinMA ## Setup -PinMAME Mech Inspector +PinMAME Mech Inspector PinMAME can simulate up to five custom mechs. You create one by adding the *PinMAME Mech* component onto a game object. diff --git a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/complementary-usage.md b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/complementary-usage.md index 06877aa0f..b79fb45b8 100644 --- a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/complementary-usage.md +++ b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/complementary-usage.md @@ -14,7 +14,7 @@ In this case, we'd like to keep PinMAME as the driving GLE, but add a visual scr ## Setup -Visual Scripting Bridge +Visual Scripting Bridge In order to give VPE's visual scripting nodes access to your game logic engine, we provide what we call a *Visual Scripting Bridge*. It's a component that you'll need to add to your table's game object, along with your original gamelogic engine. You can do that by selecting the game object, clicking *Add Component* in the inspector, and choosing *Visual Pinball -> Gamelogic Engine -> Visual Scripting Game Logic*. diff --git a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/setup.md b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/setup.md index 8cca136d7..42622ce48 100644 --- a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/setup.md +++ b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/setup.md @@ -18,7 +18,7 @@ So instead of interacting with playfield items directly, you need to define the ### Displays -Display Configuration +Display Configuration The first configuration you'll see in the inspector is which type of displays your game expects to find on the playfield. You can have multiple displays. For each display, you define the size and which type of formats it must be able to handle. These are the possible types: @@ -33,19 +33,19 @@ The display components that VPE provides understand multiple formats, e.g., our ### Switches -Switch Definitions +Switch Definitions In this section you define the switches that appear in the [Switch Manager](xref:switch_manager) when you auto-populate them. It's also the source for all the [switch-related nodes](xref:uvs_node_reference#switches) that VPE provides within your visual scripting graphs. ### Coils -Coil Definitions +Coil Definitions Here you define your coils. Same concept as for switches. These are the coils you'll see in the [Coil Manager](xref:coil_manager), and they are the source when looking up IDs in visual scripting's [coil nodes](xref:uvs_node_reference#coils). ### Lamps -Lamp Definitions +Lamp Definitions Same goes for the lamps. They appear in the [Lamp Manager](xref:lamp_manager) and are used in the [lamp nodes](xref:uvs_node_reference#lamps) that VPE provides. @@ -53,7 +53,7 @@ It's worth noting that VPE currently doesn't provide any tools for creating ligh ### Events -Event Definitions +Event Definitions Unity provides [Custom Events](https://docs.unity3d.com/Packages/com.unity.visualscripting@1.7/manual/vs-events-reference.html#custom-events) which allow you to globally pass data across graphs. While this is a valid approach, VPE provides its own event nodes called *Pinball Events*, which allow you to declare your events. This has the benefit of being able to simply pick an event from a drop-down when configuring the nodes, as opposed to having to remember or separately document existing event names. diff --git a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/variables.md b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/variables.md index c2ee5423e..27a296d5d 100644 --- a/VisualPinball.Unity/Documentation~/plugins/visual-scripting/variables.md +++ b/VisualPinball.Unity/Documentation~/plugins/visual-scripting/variables.md @@ -32,7 +32,7 @@ If you're testing gameplay in the editor, the current values of both player and ### Setup -Variable Definitions +Variable Definitions In the hierarchy, select the GameObject where you added the visual scripting GLE (usually the root node of the table). In there, you'll find two sections, *Player Variables* and *Table Variables*. diff --git a/VisualPinball.Unity/Documentation~/safari-pinned-tab.svg b/VisualPinball.Unity/Documentation~/safari-pinned-tab.svg deleted file mode 100644 index 426281cc1..000000000 --- a/VisualPinball.Unity/Documentation~/safari-pinned-tab.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - -Created by potrace 1.11, written by Peter Selinger 2001-2013 - - - - - diff --git a/VisualPinball.Unity/Documentation~/site.webmanifest b/VisualPinball.Unity/Documentation~/site.webmanifest index 03860619f..6de1ad0de 100644 --- a/VisualPinball.Unity/Documentation~/site.webmanifest +++ b/VisualPinball.Unity/Documentation~/site.webmanifest @@ -1,18 +1,6 @@ { "name": "Visual Pinball Engine - Documentation", "short_name": "VPE Documentation", - "icons": [ - { - "src": "/android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "/android-chrome-256x256.png", - "sizes": "256x256", - "type": "image/png" - } - ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" diff --git a/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/partials/scripts.tmpl.partial b/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/partials/scripts.tmpl.partial deleted file mode 100644 index 16b384b48..000000000 --- a/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/partials/scripts.tmpl.partial +++ /dev/null @@ -1,7 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - - - diff --git a/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/styles/plugin-featherlight.js b/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/styles/plugin-featherlight.js deleted file mode 100644 index f4283ebe8..000000000 --- a/VisualPinball.Unity/Documentation~/template/lightbox-featherlight/styles/plugin-featherlight.js +++ /dev/null @@ -1,15 +0,0 @@ -$(document).ready(function() { - //find all images, but not the logo, and add the lightbox - $('img').not('.logo').each(function(){ - var $img = $(this); - var filename = $img.attr('src') - //add cursor - $img.css('cursor','zoom-in'); - $img.css('cursor','-moz-zoom-in'); - $img.css('cursor','-webkit-zoom-in'); - - //add featherlight - $img.attr('alt', filename); - $img.featherlight(filename); - }); -}); \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/inter.css b/VisualPinball.Unity/Documentation~/template/vpe/fonts/inter.css deleted file mode 100644 index 66c7fb69c..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/fonts/inter.css +++ /dev/null @@ -1,200 +0,0 @@ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 100; - font-display: swap; - src: url("Inter-Thin.woff2?v=3.15") format("woff2"), - url("Inter-Thin.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url("Inter-ThinItalic.woff2?v=3.15") format("woff2"), - url("Inter-ThinItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLight.woff2?v=3.15") format("woff2"), - url("Inter-ExtraLight.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLightItalic.woff2?v=3.15") format("woff2"), - url("Inter-ExtraLightItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url("Inter-Light.woff2?v=3.15") format("woff2"), - url("Inter-Light.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url("Inter-LightItalic.woff2?v=3.15") format("woff2"), - url("Inter-LightItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("Inter-Regular.woff2?v=3.15") format("woff2"), - url("Inter-Regular.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url("Inter-Italic.woff2?v=3.15") format("woff2"), - url("Inter-Italic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("Inter-Medium.woff2?v=3.15") format("woff2"), - url("Inter-Medium.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 500; - font-display: swap; - src: url("Inter-MediumItalic.woff2?v=3.15") format("woff2"), - url("Inter-MediumItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBold.woff2?v=3.15") format("woff2"), - url("Inter-SemiBold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBoldItalic.woff2?v=3.15") format("woff2"), - url("Inter-SemiBoldItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url("Inter-Bold.woff2?v=3.15") format("woff2"), - url("Inter-Bold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url("Inter-BoldItalic.woff2?v=3.15") format("woff2"), - url("Inter-BoldItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBold.woff2?v=3.15") format("woff2"), - url("Inter-ExtraBold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBoldItalic.woff2?v=3.15") format("woff2"), - url("Inter-ExtraBoldItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url("Inter-Black.woff2?v=3.15") format("woff2"), - url("Inter-Black.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url("Inter-BlackItalic.woff2?v=3.15") format("woff2"), - url("Inter-BlackItalic.woff?v=3.15") format("woff"); -} - -/* ------------------------------------------------------- -Variable font. -Usage: - - html { font-family: 'Inter', sans-serif; } - @supports (font-variation-settings: normal) { - html { font-family: 'Inter var', sans-serif; } - } -*/ -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: normal; - font-named-instance: 'Regular'; - src: url("Inter-roman.var.woff2?v=3.15") format("woff2"); -} -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: italic; - font-named-instance: 'Italic'; - src: url("Inter-italic.var.woff2?v=3.15") format("woff2"); -} - - -/* -------------------------------------------------------------------------- -[EXPERIMENTAL] Multi-axis, single variable font. - -Slant axis is not yet widely supported (as of February 2019) and thus this -multi-axis single variable font is opt-in rather than the default. - -When using this, you will probably need to set font-variation-settings -explicitly, e.g. - - * { font-variation-settings: "slnt" 0deg } - .italic { font-variation-settings: "slnt" 10deg } - -*/ -@font-face { - font-family: 'Inter var experimental'; - font-weight: 100 900; - font-display: swap; - font-style: oblique 0deg 10deg; - src: url("Inter.var.woff2?v=3.15") format("woff2"); -} diff --git a/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl new file mode 100644 index 000000000..f6bc52f5f --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/layout/_master.tmpl @@ -0,0 +1,182 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{!include(/^public/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} +{{!include(search-stopwords.json)}} + + + + + {{#redirect_url}} + + {{/redirect_url}} + {{^redirect_url}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + {{#_description}}{{/_description}} + + + + + + + {{#_noindex}}{{/_noindex}} + {{#_enableSearch}}{{/_enableSearch}} + {{#_disableNewTab}}{{/_disableNewTab}} + {{#_disableTocFilter}}{{/_disableTocFilter}} + {{#docurl}}{{/docurl}} + + + + + + + + + + + + {{/redirect_url}} + + + + {{^redirect_url}} + + + + + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + + +
+ +
+ +
+
+
+
+
Table of Contents
+ +
+
+ +
+
+
+ +
+ + +
+ {{!body}} +
+ + {{^_disableNextArticle}} + + {{/_disableNextArticle}} + +
+ +
+ + + {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} +
+
+ + {{#_enableSearch}} +
+ {{/_enableSearch}} + +
+
+
+ {{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}} +
+
+
+ + + + + {{/redirect_url}} + diff --git a/VisualPinball.Unity/Documentation~/template/vpe/logo.svg b/VisualPinball.Unity/Documentation~/template/vpe/logo.svg index 6c66fa6a8..684a02604 100644 --- a/VisualPinball.Unity/Documentation~/template/vpe/logo.svg +++ b/VisualPinball.Unity/Documentation~/template/vpe/logo.svg @@ -1 +1,9 @@ - \ No newline at end of file + + diff --git a/VisualPinball.Unity/Documentation~/template/vpe/partials/affix.tmpl.partial b/VisualPinball.Unity/Documentation~/template/vpe/partials/affix.tmpl.partial deleted file mode 100644 index a31807940..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/partials/affix.tmpl.partial +++ /dev/null @@ -1,25 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - diff --git a/VisualPinball.Unity/Documentation~/template/vpe/partials/head.tmpl.partial b/VisualPinball.Unity/Documentation~/template/vpe/partials/head.tmpl.partial deleted file mode 100644 index f63967c0a..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/partials/head.tmpl.partial +++ /dev/null @@ -1,54 +0,0 @@ - - - - {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/partials/logo.tmpl.partial b/VisualPinball.Unity/Documentation~/template/vpe/partials/logo.tmpl.partial deleted file mode 100644 index 1bb847c87..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/partials/logo.tmpl.partial +++ /dev/null @@ -1,5 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - - - diff --git a/VisualPinball.Unity/Documentation~/template/vpe/partials/navbar.tmpl.partial b/VisualPinball.Unity/Documentation~/template/vpe/partials/navbar.tmpl.partial deleted file mode 100644 index 7f5e48e0c..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/partials/navbar.tmpl.partial +++ /dev/null @@ -1,20 +0,0 @@ -{{!Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information.}} - - diff --git a/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.css b/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.css new file mode 100644 index 000000000..175b1ce3a --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.css @@ -0,0 +1 @@ +.basicLightbox{position:fixed;display:flex;justify-content:center;align-items:center;top:0;left:0;width:100%;height:100vh;background:rgba(0,0,0,.8);opacity:.01;transition:opacity .4s ease;z-index:1000;will-change:opacity}.basicLightbox--visible{opacity:1}.basicLightbox__placeholder{max-width:100%;transform:scale(.9);transition:transform .4s ease;z-index:1;will-change:transform}.basicLightbox__placeholder>iframe:first-child:last-child,.basicLightbox__placeholder>img:first-child:last-child,.basicLightbox__placeholder>video:first-child:last-child{display:block;position:absolute;top:0;right:0;bottom:0;left:0;margin:auto;max-width:95%;max-height:95%}.basicLightbox__placeholder>iframe:first-child:last-child,.basicLightbox__placeholder>video:first-child:last-child{pointer-events:auto}.basicLightbox__placeholder>img:first-child:last-child,.basicLightbox__placeholder>video:first-child:last-child{width:auto;height:auto}.basicLightbox--iframe .basicLightbox__placeholder,.basicLightbox--img .basicLightbox__placeholder,.basicLightbox--video .basicLightbox__placeholder{width:100%;height:100%;pointer-events:none}.basicLightbox--visible .basicLightbox__placeholder{transform:scale(1)} \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.js b/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.js new file mode 100644 index 000000000..b2b6ccd57 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/public/basicLightbox.min.js @@ -0,0 +1 @@ +!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).basicLightbox=e()}}((function(){return function e(n,t,o){function r(c,u){if(!t[c]){if(!n[c]){var s="function"==typeof require&&require;if(!u&&s)return s(c,!0);if(i)return i(c,!0);var a=new Error("Cannot find module '"+c+"'");throw a.code="MODULE_NOT_FOUND",a}var l=t[c]={exports:{}};n[c][0].call(l.exports,(function(e){return r(n[c][1][e]||e)}),l,l.exports,e,n,t,o)}return t[c].exports}for(var i="function"==typeof require&&require,c=0;c1&&void 0!==arguments[1]&&arguments[1],t=document.createElement("div");return t.innerHTML=e.trim(),!0===n?t.children:t.firstChild},r=function(e,n){var t=e.children;return 1===t.length&&t[0].tagName===n},i=function(e){return null!=(e=e||document.querySelector(".basicLightbox"))&&!0===e.ownerDocument.body.contains(e)};t.visible=i;t.create=function(e,n){var t=function(e,n){var t=o('\n\t\t
\n\t\t\t\n\t\t
\n\t')),i=t.querySelector(".basicLightbox__placeholder");e.forEach((function(e){return i.appendChild(e)}));var c=r(i,"IMG"),u=r(i,"VIDEO"),s=r(i,"IFRAME");return!0===c&&t.classList.add("basicLightbox--img"),!0===u&&t.classList.add("basicLightbox--video"),!0===s&&t.classList.add("basicLightbox--iframe"),t}(e=function(e){var n="string"==typeof e,t=e instanceof HTMLElement==1;if(!1===n&&!1===t)throw new Error("Content must be a DOM element/node or string");return!0===n?Array.from(o(e,!0)):"TEMPLATE"===e.tagName?[e.content.cloneNode(!0)]:Array.from(e.children)}(e),n=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(null==(e=Object.assign({},e)).closable&&(e.closable=!0),null==e.className&&(e.className=""),null==e.onShow&&(e.onShow=function(){}),null==e.onClose&&(e.onClose=function(){}),"boolean"!=typeof e.closable)throw new Error("Property `closable` must be a boolean");if("string"!=typeof e.className)throw new Error("Property `className` must be a string");if("function"!=typeof e.onShow)throw new Error("Property `onShow` must be a function");if("function"!=typeof e.onClose)throw new Error("Property `onClose` must be a function");return e}(n)),c=function(e){return!1!==n.onClose(u)&&function(e,n){return e.classList.remove("basicLightbox--visible"),setTimeout((function(){return!1===i(e)||e.parentElement.removeChild(e),n()}),410),!0}(t,(function(){if("function"==typeof e)return e(u)}))};!0===n.closable&&t.addEventListener("click",(function(e){e.target===t&&c()}));var u={element:function(){return t},visible:function(){return i(t)},show:function(e){return!1!==n.onShow(u)&&function(e,n){return document.body.appendChild(e),setTimeout((function(){requestAnimationFrame((function(){return e.classList.add("basicLightbox--visible"),n()}))}),10),!0}(t,(function(){if("function"==typeof e)return e(u)}))},close:c};return u}},{}]},{},[1])(1)})); \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/public/fonts.css b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts.css new file mode 100644 index 000000000..c0ee3259e --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts.css @@ -0,0 +1,201 @@ + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 100; + font-display: swap; + src: url("fonts/Inter-Thin.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Thin.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 100; + font-display: swap; + src: url("fonts/Inter-ThinItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-ThinItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 200; + font-display: swap; + src: url("fonts/Inter-ExtraLight.woff2?v=3.15") format("woff2"), + url("fonts/Inter-ExtraLight.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 200; + font-display: swap; + src: url("fonts/Inter-ExtraLightItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-ExtraLightItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 300; + font-display: swap; + src: url("fonts/Inter-Light.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Light.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 300; + font-display: swap; + src: url("fonts/Inter-LightItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-LightItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 400; + font-display: swap; + src: url("fonts/Inter-Regular.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Regular.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 400; + font-display: swap; + src: url("fonts/Inter-Italic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Italic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 500; + font-display: swap; + src: url("fonts/Inter-Medium.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Medium.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 500; + font-display: swap; + src: url("fonts/Inter-MediumItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-MediumItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 600; + font-display: swap; + src: url("fonts/Inter-SemiBold.woff2?v=3.15") format("woff2"), + url("fonts/Inter-SemiBold.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 600; + font-display: swap; + src: url("fonts/Inter-SemiBoldItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-SemiBoldItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 700; + font-display: swap; + src: url("fonts/Inter-Bold.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Bold.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 700; + font-display: swap; + src: url("fonts/Inter-BoldItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-BoldItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 800; + font-display: swap; + src: url("fonts/Inter-ExtraBold.woff2?v=3.15") format("woff2"), + url("fonts/Inter-ExtraBold.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 800; + font-display: swap; + src: url("fonts/Inter-ExtraBoldItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-ExtraBoldItalic.woff?v=3.15") format("woff"); +} + +@font-face { + font-family: 'Inter'; + font-style: normal; + font-weight: 900; + font-display: swap; + src: url("fonts/Inter-Black.woff2?v=3.15") format("woff2"), + url("fonts/Inter-Black.woff?v=3.15") format("woff"); +} +@font-face { + font-family: 'Inter'; + font-style: italic; + font-weight: 900; + font-display: swap; + src: url("fonts/Inter-BlackItalic.woff2?v=3.15") format("woff2"), + url("fonts/Inter-BlackItalic.woff?v=3.15") format("woff"); +} + +/* ------------------------------------------------------- +Variable font. +Usage: + + html { font-family: 'Inter', sans-serif; } + @supports (font-variation-settings: normal) { + html { font-family: 'Inter var', sans-serif; } + } +*/ +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: 'Regular'; + src: url("fonts/Inter-roman.var.woff2?v=3.15") format("woff2"); +} +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: italic; + font-named-instance: 'Italic'; + src: url("fonts/Inter-italic.var.woff2?v=3.15") format("woff2"); +} + + +/* -------------------------------------------------------------------------- +[EXPERIMENTAL] Multi-axis, single variable font. + +Slant axis is not yet widely supported (as of February 2019) and thus this +multi-axis single variable font is opt-in rather than the default. + +When using this, you will probably need to set font-variation-settings +explicitly, e.g. + + * { font-variation-settings: "slnt" 0deg } + .italic { font-variation-settings: "slnt" 10deg } + +*/ +@font-face { + font-family: 'Inter var experimental'; + font-weight: 100 900; + font-display: swap; + font-style: oblique 0deg 10deg; + src: url("fonts/Inter.var.woff2?v=3.15") format("woff2"); +} diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Black.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Black.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Black.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Black.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Black.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Black.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Black.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Black.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BlackItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BlackItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BlackItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BlackItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BlackItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BlackItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BlackItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BlackItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Bold.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Bold.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Bold.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Bold.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Bold.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Bold.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Bold.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Bold.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BoldItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BoldItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BoldItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BoldItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BoldItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BoldItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-BoldItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-BoldItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBold.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBold.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBold.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBold.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBold.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBold.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBold.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBold.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBoldItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBoldItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBoldItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBoldItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBoldItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBoldItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraBoldItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraBoldItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLight.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLight.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLight.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLight.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLight.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLight.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLight.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLight.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLightItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLightItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLightItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLightItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLightItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLightItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ExtraLightItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ExtraLightItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Italic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Italic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Italic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Italic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Italic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Italic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Italic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Italic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Light.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Light.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Light.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Light.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Light.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Light.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Light.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Light.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-LightItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-LightItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-LightItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-LightItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-LightItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-LightItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-LightItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-LightItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Medium.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Medium.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Medium.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Medium.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Medium.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Medium.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Medium.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Medium.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-MediumItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-MediumItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-MediumItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-MediumItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-MediumItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-MediumItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-MediumItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-MediumItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Regular.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Regular.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Regular.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Regular.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Regular.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Regular.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Regular.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Regular.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBold.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBold.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBold.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBold.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBold.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBold.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBold.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBold.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBoldItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBoldItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBoldItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBoldItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBoldItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBoldItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-SemiBoldItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-SemiBoldItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Thin.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Thin.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Thin.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Thin.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Thin.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Thin.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-Thin.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-Thin.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ThinItalic.woff b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ThinItalic.woff similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ThinItalic.woff rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ThinItalic.woff diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ThinItalic.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ThinItalic.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-ThinItalic.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-ThinItalic.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-italic.var.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-italic.var.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-italic.var.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-italic.var.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-roman.var.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-roman.var.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter-roman.var.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter-roman.var.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter.var.woff2 b/VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter.var.woff2 similarity index 100% rename from VisualPinball.Unity/Documentation~/template/vpe/fonts/Inter.var.woff2 rename to VisualPinball.Unity/Documentation~/template/vpe/public/fonts/Inter.var.woff2 diff --git a/VisualPinball.Unity/Documentation~/template/vpe/public/main.css b/VisualPinball.Unity/Documentation~/template/vpe/public/main.css new file mode 100644 index 000000000..bdf06c3fd --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/public/main.css @@ -0,0 +1,131 @@ +@import url("fonts.css"); +@import url("https://raw.githubusercontent.com/thomaspark/bootswatch/v5/dist/zephyr/bootstrap.min.css"); + +/* layout */ +.navbar-github { /* align github link to the right */ + order: 100; + margin-left: 10px; + margin-top: 4px; +} +.navbar-github > svg { /* github logo color */ + fill: #ccc; +} +body[data-layout=landing]>main { /* landing page full width */ + display: block; +} +#vpe-halftone { /* landing page logo width */ + max-width: 720px; + display: block; +} + +/* color overrides */ +:root, +[data-bs-theme=light] { + --bs-font-sans-serif: Inter, 'Segoe UI', Tahoma, Helvetica, sans-serif; + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-link-color-rgb: 214, 99, 22 !important; + --bs-link-hover-color-rgb: 183, 74, 0 !important; + --bs-code-color: rgb(var(--bs-link-color-rgb), 1); + --vpe-body-bg-secondary: #f3f3f3; + --vpe-link-secondary: #00000080; + --vpe-link-secondary-hover: #000000aa; + --vpe-body-color-less-contrast: rgba(33, 37, 41, 0.5) +} +[data-bs-theme=dark] { + --bs-link-color-rgb: 236, 132, 61 !important; + --vpe-body-bg-secondary: #292e33; + --vpe-link-secondary: #ffffff80; + --vpe-link-secondary-hover: #ffffffaa; + --vpe-body-color-less-contrast: rgba(222, 226, 230, 0.5) +} +[data-bs-theme=dark] header { /* header slightly lighter in dark mode */ + background-color: var(--vpe-body-bg-secondary) !important; +} + +/* fix theme toggle button color */ +[data-bs-theme=light] header .dropdown a:hover, +[data-bs-theme=light] header .dropdown a:active, +[data-bs-theme=light] header .dropdown a:focus-visible, +[data-bs-theme=light] header .dropdown a:focus, +[data-bs-theme=light] header .btn.show { + color: white !important; +} + +/* don't underline links */ +a { + text-decoration: none !important; +} + +/* zephyr fixes */ +.btn-outline-secondary { /* outline button */ + color: var(--bs-secondary-color) !important; +} +[data-bs-theme=dark] .btn-outline-secondary:hover { /* outline button hover */ + color: var(--bs-body-bg) !important; +} +.link-secondary { /* TOC links on the left */ + color: var(--vpe-link-secondary) !important; +} +.link-secondary:hover { + color: var(--vpe-link-secondary-hover) !important; +} +#search-results > .sr-items .sr-item > .item-href { /* search result colors */ + color: rgba(var(--bs-link-color-rgb), 0.6); +} + +[data-bs-theme=light] i.bi.bi-search { /* search icon color */ + color: white; +} + +/* landing svg art colors */ +svg #text { /* visual pinball engine */ + fill: var(--bs-body-color); +} +svg #bg1 { /* outer */ + stroke-width: 0; + fill: var(--bs-body-bg) +} +svg #logo { /* logo */ + fill: var(--bs-body-color); +} +svg #bg2 { /* inner */ + fill: var(--vpe-body-bg-secondary); +} + +/* footer customization */ +body>footer, body[data-layout=landing]>footer { /* footer height */ + color: var(--vpe-body-color-less-contrast) !important; + background-color: var(--vpe-body-bg-secondary); + font-size: 0.8rem !important; + height: 52px; + padding-left: 10px; +} + +/* make titles bold */ +h1, h2 { + font-weight: bold; +} + +/* make tables striped */ +table.table.table-bordered > tbody > tr:nth-of-type(odd) > td { + background-color: var(--vpe-body-bg-secondary); +} +table.table.table-bordered > thead > tr > th { /* separate column header */ + border-bottom-width: 3px; +} +table.table.table-bordered > * > tr > *:nth-child(1) { /* separate row header */ + border-right-width: 3px; + font-style: italic; +} + +/* code colors */ +code { + background-color: rgb(var(--bs-link-color-rgb), 0.15); + padding: 1px 3px; + border-radius: 3px; +} + +/* image hover cursor */ +img:not(#logo):hover { + cursor: zoom-in; +} diff --git a/VisualPinball.Unity/Documentation~/template/vpe/public/zephyr.css b/VisualPinball.Unity/Documentation~/template/vpe/public/zephyr.css new file mode 100644 index 000000000..732b3c4ca --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/public/zephyr.css @@ -0,0 +1,12286 @@ +/* this is just for reference. inlcuded is the minified version directly from github. + * https://github.com/thomaspark/bootswatch/tree/v5/dist/zephyr + */ + +@charset "UTF-8"; +/*! + * Bootswatch v5.3.2 (https://bootswatch.com) + * Theme: zephyr + * Copyright 2012-2023 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*/ +/*! + * Bootstrap v5.3.2 (https://getbootstrap.com/) + * Copyright 2011-2023 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +:root, +[data-bs-theme=light] { + --bs-blue: #3459e6; + --bs-indigo: #6610f2; + --bs-purple: #6f42c1; + --bs-pink: #d63384; + --bs-red: #da292e; + --bs-orange: #f8765f; + --bs-yellow: #f4bd61; + --bs-green: #2fb380; + --bs-teal: #20c997; + --bs-cyan: #287bb5; + --bs-black: #000; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #3459e6; + --bs-secondary: #fff; + --bs-success: #2fb380; + --bs-info: #287bb5; + --bs-warning: #f4bd61; + --bs-danger: #da292e; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 52, 89, 230; + --bs-secondary-rgb: 255, 255, 255; + --bs-success-rgb: 47, 179, 128; + --bs-info-rgb: 40, 123, 181; + --bs-warning-rgb: 244, 189, 97; + --bs-danger-rgb: 218, 41, 46; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-primary-text-emphasis: #15245c; + --bs-secondary-text-emphasis: #666666; + --bs-success-text-emphasis: #134833; + --bs-info-text-emphasis: #103148; + --bs-warning-text-emphasis: #624c27; + --bs-danger-text-emphasis: #571012; + --bs-light-text-emphasis: #495057; + --bs-dark-text-emphasis: #495057; + --bs-primary-bg-subtle: #d6defa; + --bs-secondary-bg-subtle: white; + --bs-success-bg-subtle: #d5f0e6; + --bs-info-bg-subtle: #d4e5f0; + --bs-warning-bg-subtle: #fdf2df; + --bs-danger-bg-subtle: #f8d4d5; + --bs-light-bg-subtle: #fcfcfd; + --bs-dark-bg-subtle: #ced4da; + --bs-primary-border-subtle: #aebdf5; + --bs-secondary-border-subtle: white; + --bs-success-border-subtle: #ace1cc; + --bs-info-border-subtle: #a9cae1; + --bs-warning-border-subtle: #fbe5c0; + --bs-danger-border-subtle: #f0a9ab; + --bs-light-border-subtle: #e9ecef; + --bs-dark-border-subtle: #adb5bd; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-font-sans-serif: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 1rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #495057; + --bs-body-color-rgb: 73, 80, 87; + --bs-body-bg: #fff; + --bs-body-bg-rgb: 255, 255, 255; + --bs-emphasis-color: #000; + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-secondary-color: rgba(73, 80, 87, 0.75); + --bs-secondary-color-rgb: 73, 80, 87; + --bs-secondary-bg: #e9ecef; + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-tertiary-color: rgba(73, 80, 87, 0.5); + --bs-tertiary-color-rgb: 73, 80, 87; + --bs-tertiary-bg: #f8f9fa; + --bs-tertiary-bg-rgb: 248, 249, 250; + --bs-heading-color: var(--bs-primary-color); + --bs-link-color: #3459e6; + --bs-link-color-rgb: 214, 99, 22; + --bs-link-decoration: underline; + --bs-link-hover-color: #2a47b8; + --bs-link-hover-color-rgb: 42, 71, 184; + --bs-code-color: #d63384; + --bs-highlight-color: #495057; + --bs-highlight-bg: #fdf2df; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0.375rem; + --bs-border-radius-sm: 0.25rem; + --bs-border-radius-lg: 0.5rem; + --bs-border-radius-xl: 1rem; + --bs-border-radius-xxl: 2rem; + --bs-border-radius-2xl: var(--bs-border-radius-xxl); + --bs-border-radius-pill: 50rem; + --bs-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-box-shadow-lg: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); + --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-opacity: 0.25; + --bs-focus-ring-color: rgba(52, 89, 230, 0.25); + --bs-form-valid-color: #2fb380; + --bs-form-valid-border-color: #2fb380; + --bs-form-invalid-color: #da292e; + --bs-form-invalid-border-color: #da292e; +} + +[data-bs-theme=dark] { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-color-rgb: 222, 226, 230; + --bs-secondary-bg: #343a40; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-color-rgb: 222, 226, 230; + --bs-tertiary-bg: #2b3035; + --bs-tertiary-bg-rgb: 43, 48, 53; + --bs-primary-text-emphasis: #859bf0; + --bs-secondary-text-emphasis: white; + --bs-success-text-emphasis: #82d1b3; + --bs-info-text-emphasis: #7eb0d3; + --bs-warning-text-emphasis: #f8d7a0; + --bs-danger-text-emphasis: #e97f82; + --bs-light-text-emphasis: #f8f9fa; + --bs-dark-text-emphasis: #dee2e6; + --bs-primary-bg-subtle: #0a122e; + --bs-secondary-bg-subtle: #333333; + --bs-success-bg-subtle: #09241a; + --bs-info-bg-subtle: #081924; + --bs-warning-bg-subtle: #312613; + --bs-danger-bg-subtle: #2c0809; + --bs-light-bg-subtle: #343a40; + --bs-dark-bg-subtle: #1a1d20; + --bs-primary-border-subtle: #1f358a; + --bs-secondary-border-subtle: #999999; + --bs-success-border-subtle: #1c6b4d; + --bs-info-border-subtle: #184a6d; + --bs-warning-border-subtle: #92713a; + --bs-danger-border-subtle: #83191c; + --bs-light-border-subtle: #495057; + --bs-dark-border-subtle: #343a40; + --bs-heading-color: inherit; + --bs-link-color: #859bf0; + --bs-link-hover-color: #9daff3; + --bs-link-color-rgb: 133, 155, 240; + --bs-link-hover-color-rgb: 157, 175, 243; + --bs-code-color: #e685b5; + --bs-highlight-color: #dee2e6; + --bs-highlight-bg: #624c27; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + --bs-form-valid-color: #82d1b3; + --bs-form-valid-border-color: #82d1b3; + --bs-form-invalid-color: #e97f82; + --bs-form-invalid-border-color: #e97f82; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +@media (prefers-reduced-motion: no-preference) { + :root { + scroll-behavior: smooth; + } +} + +body { + margin: 0; + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size); + font-weight: var(--bs-body-font-weight); + line-height: var(--bs-body-line-height); + color: var(--bs-body-color); + text-align: var(--bs-body-text-align); + background-color: var(--bs-body-bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +hr { + margin: 1rem 0; + color: inherit; + border: 0; + border-top: var(--bs-border-width) solid; + opacity: 0.25; +} + +h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 { + margin-top: 0; + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; + color: var(--bs-heading-color); +} + +h1, .h1 { + font-size: calc(1.375rem + 1.5vw); +} +@media (min-width: 1200px) { + h1, .h1 { + font-size: 2.5rem; + } +} + +h2, .h2 { + font-size: calc(1.325rem + 0.9vw); +} +@media (min-width: 1200px) { + h2, .h2 { + font-size: 2rem; + } +} + +h3, .h3 { + font-size: calc(1.3rem + 0.6vw); +} +@media (min-width: 1200px) { + h3, .h3 { + font-size: 1.75rem; + } +} + +h4, .h4 { + font-size: calc(1.275rem + 0.3vw); +} +@media (min-width: 1200px) { + h4, .h4 { + font-size: 1.5rem; + } +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title] { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul { + padding-left: 2rem; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +b, +strong { + font-weight: bolder; +} + +small, .small { + font-size: 0.875em; +} + +mark, .mark { + padding: 0.1875em; + color: var(--bs-highlight-color); + background-color: var(--bs-highlight-bg); +} + +sub, +sup { + position: relative; + font-size: 0.75em; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +a { + color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); + text-decoration: underline; +} +a:hover { + --bs-link-color-rgb: var(--bs-link-hover-color-rgb); +} + +a:not([href]):not([class]), a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} + +pre, +code, +kbd, +samp { + font-family: var(--bs-font-monospace); + font-size: 1em; +} + +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + font-size: 0.875em; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} + +code { + font-size: 0.875em; + color: var(--bs-code-color); + word-wrap: break-word; +} +a > code { + color: inherit; +} + +kbd { + padding: 0.1875rem 0.375rem; + font-size: 0.875em; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); + border-radius: 0.25rem; +} +kbd kbd { + padding: 0; + font-size: 1em; +} + +figure { + margin: 0 0 1rem; +} + +img, +svg { + vertical-align: middle; +} + +table { + caption-side: bottom; + border-collapse: collapse; +} + +caption { + padding-top: 1rem; + padding-bottom: 1rem; + color: var(--bs-secondary-color); + text-align: left; +} + +th { + font-weight: 500; + text-align: inherit; + text-align: -webkit-match-parent; +} + +thead, +tbody, +tfoot, +tr, +td, +th { + border-color: inherit; + border-style: solid; + border-width: 0; +} + +label { + display: inline-block; +} + +button { + border-radius: 0; +} + +button:focus:not(:focus-visible) { + outline: 0; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +select { + text-transform: none; +} + +[role=button] { + cursor: pointer; +} + +select { + word-wrap: normal; +} +select:disabled { + opacity: 1; +} + +[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { + display: none !important; +} + +button, +[type=button], +[type=reset], +[type=submit] { + -webkit-appearance: button; +} +button:not(:disabled), +[type=button]:not(:disabled), +[type=reset]:not(:disabled), +[type=submit]:not(:disabled) { + cursor: pointer; +} + +::-moz-focus-inner { + padding: 0; + border-style: none; +} + +textarea { + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + float: left; + width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: calc(1.275rem + 0.3vw); + line-height: inherit; +} +@media (min-width: 1200px) { + legend { + font-size: 1.5rem; + } +} +legend + * { + clear: left; +} + +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-year-field { + padding: 0; +} + +::-webkit-inner-spin-button { + height: auto; +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px; +} + +/* rtl:raw: +[type="tel"], +[type="url"], +[type="email"], +[type="number"] { + direction: ltr; +} +*/ +::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-color-swatch-wrapper { + padding: 0; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +::file-selector-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +iframe { + border: 0; +} + +summary { + display: list-item; + cursor: pointer; +} + +progress { + vertical-align: baseline; +} + +[hidden] { + display: none !important; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: calc(1.625rem + 4.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-1 { + font-size: 5rem; + } +} + +.display-2 { + font-size: calc(1.575rem + 3.9vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-2 { + font-size: 4.5rem; + } +} + +.display-3 { + font-size: calc(1.525rem + 3.3vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-3 { + font-size: 4rem; + } +} + +.display-4 { + font-size: calc(1.475rem + 2.7vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-4 { + font-size: 3.5rem; + } +} + +.display-5 { + font-size: calc(1.425rem + 2.1vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-5 { + font-size: 3rem; + } +} + +.display-6 { + font-size: calc(1.375rem + 1.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-6 { + font-size: 2.5rem; + } +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} + +.initialism { + font-size: 0.875em; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} +.blockquote > :last-child { + margin-bottom: 0; +} + +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: 0.875em; + color: #6c757d; +} +.blockquote-footer::before { + content: "— "; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + box-shadow: var(--bs-box-shadow-sm); + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 0.875em; + color: var(--bs-secondary-color); +} + +.container, +.container-fluid, +.container-xxl, +.container-xl, +.container-lg, +.container-md, +.container-sm { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container-sm, .container { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container-md, .container-sm, .container { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container-lg, .container-md, .container-sm, .container { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container-xl, .container-lg, .container-md, .container-sm, .container { + max-width: 1140px; + } +} +@media (min-width: 1400px) { + .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container { + max-width: 1320px; + } +} +:root { + --bs-breakpoint-xs: 0; + --bs-breakpoint-sm: 576px; + --bs-breakpoint-md: 768px; + --bs-breakpoint-lg: 992px; + --bs-breakpoint-xl: 1200px; + --bs-breakpoint-xxl: 1400px; +} + +.row { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(-1 * var(--bs-gutter-y)); + margin-right: calc(-0.5 * var(--bs-gutter-x)); + margin-left: calc(-0.5 * var(--bs-gutter-x)); +} +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-top: var(--bs-gutter-y); +} + +.col { + flex: 1 0 0%; +} + +.row-cols-auto > * { + flex: 0 0 auto; + width: auto; +} + +.row-cols-1 > * { + flex: 0 0 auto; + width: 100%; +} + +.row-cols-2 > * { + flex: 0 0 auto; + width: 50%; +} + +.row-cols-3 > * { + flex: 0 0 auto; + width: 33.33333333%; +} + +.row-cols-4 > * { + flex: 0 0 auto; + width: 25%; +} + +.row-cols-5 > * { + flex: 0 0 auto; + width: 20%; +} + +.row-cols-6 > * { + flex: 0 0 auto; + width: 16.66666667%; +} + +.col-auto { + flex: 0 0 auto; + width: auto; +} + +.col-1 { + flex: 0 0 auto; + width: 8.33333333%; +} + +.col-2 { + flex: 0 0 auto; + width: 16.66666667%; +} + +.col-3 { + flex: 0 0 auto; + width: 25%; +} + +.col-4 { + flex: 0 0 auto; + width: 33.33333333%; +} + +.col-5 { + flex: 0 0 auto; + width: 41.66666667%; +} + +.col-6 { + flex: 0 0 auto; + width: 50%; +} + +.col-7 { + flex: 0 0 auto; + width: 58.33333333%; +} + +.col-8 { + flex: 0 0 auto; + width: 66.66666667%; +} + +.col-9 { + flex: 0 0 auto; + width: 75%; +} + +.col-10 { + flex: 0 0 auto; + width: 83.33333333%; +} + +.col-11 { + flex: 0 0 auto; + width: 91.66666667%; +} + +.col-12 { + flex: 0 0 auto; + width: 100%; +} + +.offset-1 { + margin-left: 8.33333333%; +} + +.offset-2 { + margin-left: 16.66666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.33333333%; +} + +.offset-5 { + margin-left: 41.66666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.33333333%; +} + +.offset-8 { + margin-left: 66.66666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.33333333%; +} + +.offset-11 { + margin-left: 91.66666667%; +} + +.g-0, +.gx-0 { + --bs-gutter-x: 0; +} + +.g-0, +.gy-0 { + --bs-gutter-y: 0; +} + +.g-1, +.gx-1 { + --bs-gutter-x: 0.25rem; +} + +.g-1, +.gy-1 { + --bs-gutter-y: 0.25rem; +} + +.g-2, +.gx-2 { + --bs-gutter-x: 0.5rem; +} + +.g-2, +.gy-2 { + --bs-gutter-y: 0.5rem; +} + +.g-3, +.gx-3 { + --bs-gutter-x: 1rem; +} + +.g-3, +.gy-3 { + --bs-gutter-y: 1rem; +} + +.g-4, +.gx-4 { + --bs-gutter-x: 1.5rem; +} + +.g-4, +.gy-4 { + --bs-gutter-y: 1.5rem; +} + +.g-5, +.gx-5 { + --bs-gutter-x: 3rem; +} + +.g-5, +.gy-5 { + --bs-gutter-y: 3rem; +} + +@media (min-width: 576px) { + .col-sm { + flex: 1 0 0%; + } + .row-cols-sm-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-sm-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-sm-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-sm-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-sm-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-sm-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-sm-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-auto { + flex: 0 0 auto; + width: auto; + } + .col-sm-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-sm-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-3 { + flex: 0 0 auto; + width: 25%; + } + .col-sm-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-sm-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-sm-6 { + flex: 0 0 auto; + width: 50%; + } + .col-sm-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-sm-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-sm-9 { + flex: 0 0 auto; + width: 75%; + } + .col-sm-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-sm-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.33333333%; + } + .offset-sm-2 { + margin-left: 16.66666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.33333333%; + } + .offset-sm-5 { + margin-left: 41.66666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.33333333%; + } + .offset-sm-8 { + margin-left: 66.66666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.33333333%; + } + .offset-sm-11 { + margin-left: 91.66666667%; + } + .g-sm-0, + .gx-sm-0 { + --bs-gutter-x: 0; + } + .g-sm-0, + .gy-sm-0 { + --bs-gutter-y: 0; + } + .g-sm-1, + .gx-sm-1 { + --bs-gutter-x: 0.25rem; + } + .g-sm-1, + .gy-sm-1 { + --bs-gutter-y: 0.25rem; + } + .g-sm-2, + .gx-sm-2 { + --bs-gutter-x: 0.5rem; + } + .g-sm-2, + .gy-sm-2 { + --bs-gutter-y: 0.5rem; + } + .g-sm-3, + .gx-sm-3 { + --bs-gutter-x: 1rem; + } + .g-sm-3, + .gy-sm-3 { + --bs-gutter-y: 1rem; + } + .g-sm-4, + .gx-sm-4 { + --bs-gutter-x: 1.5rem; + } + .g-sm-4, + .gy-sm-4 { + --bs-gutter-y: 1.5rem; + } + .g-sm-5, + .gx-sm-5 { + --bs-gutter-x: 3rem; + } + .g-sm-5, + .gy-sm-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 768px) { + .col-md { + flex: 1 0 0%; + } + .row-cols-md-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-md-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-md-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-md-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-md-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-md-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-md-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-auto { + flex: 0 0 auto; + width: auto; + } + .col-md-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-md-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-3 { + flex: 0 0 auto; + width: 25%; + } + .col-md-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-md-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-md-6 { + flex: 0 0 auto; + width: 50%; + } + .col-md-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-md-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-md-9 { + flex: 0 0 auto; + width: 75%; + } + .col-md-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-md-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-md-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.33333333%; + } + .offset-md-2 { + margin-left: 16.66666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.33333333%; + } + .offset-md-5 { + margin-left: 41.66666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.33333333%; + } + .offset-md-8 { + margin-left: 66.66666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.33333333%; + } + .offset-md-11 { + margin-left: 91.66666667%; + } + .g-md-0, + .gx-md-0 { + --bs-gutter-x: 0; + } + .g-md-0, + .gy-md-0 { + --bs-gutter-y: 0; + } + .g-md-1, + .gx-md-1 { + --bs-gutter-x: 0.25rem; + } + .g-md-1, + .gy-md-1 { + --bs-gutter-y: 0.25rem; + } + .g-md-2, + .gx-md-2 { + --bs-gutter-x: 0.5rem; + } + .g-md-2, + .gy-md-2 { + --bs-gutter-y: 0.5rem; + } + .g-md-3, + .gx-md-3 { + --bs-gutter-x: 1rem; + } + .g-md-3, + .gy-md-3 { + --bs-gutter-y: 1rem; + } + .g-md-4, + .gx-md-4 { + --bs-gutter-x: 1.5rem; + } + .g-md-4, + .gy-md-4 { + --bs-gutter-y: 1.5rem; + } + .g-md-5, + .gx-md-5 { + --bs-gutter-x: 3rem; + } + .g-md-5, + .gy-md-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 992px) { + .col-lg { + flex: 1 0 0%; + } + .row-cols-lg-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-lg-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-lg-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-lg-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-lg-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-lg-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-lg-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-auto { + flex: 0 0 auto; + width: auto; + } + .col-lg-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-lg-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-3 { + flex: 0 0 auto; + width: 25%; + } + .col-lg-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-lg-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-lg-6 { + flex: 0 0 auto; + width: 50%; + } + .col-lg-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-lg-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-lg-9 { + flex: 0 0 auto; + width: 75%; + } + .col-lg-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-lg-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-lg-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.33333333%; + } + .offset-lg-2 { + margin-left: 16.66666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.33333333%; + } + .offset-lg-5 { + margin-left: 41.66666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.33333333%; + } + .offset-lg-8 { + margin-left: 66.66666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.33333333%; + } + .offset-lg-11 { + margin-left: 91.66666667%; + } + .g-lg-0, + .gx-lg-0 { + --bs-gutter-x: 0; + } + .g-lg-0, + .gy-lg-0 { + --bs-gutter-y: 0; + } + .g-lg-1, + .gx-lg-1 { + --bs-gutter-x: 0.25rem; + } + .g-lg-1, + .gy-lg-1 { + --bs-gutter-y: 0.25rem; + } + .g-lg-2, + .gx-lg-2 { + --bs-gutter-x: 0.5rem; + } + .g-lg-2, + .gy-lg-2 { + --bs-gutter-y: 0.5rem; + } + .g-lg-3, + .gx-lg-3 { + --bs-gutter-x: 1rem; + } + .g-lg-3, + .gy-lg-3 { + --bs-gutter-y: 1rem; + } + .g-lg-4, + .gx-lg-4 { + --bs-gutter-x: 1.5rem; + } + .g-lg-4, + .gy-lg-4 { + --bs-gutter-y: 1.5rem; + } + .g-lg-5, + .gx-lg-5 { + --bs-gutter-x: 3rem; + } + .g-lg-5, + .gy-lg-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1200px) { + .col-xl { + flex: 1 0 0%; + } + .row-cols-xl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.33333333%; + } + .offset-xl-2 { + margin-left: 16.66666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.33333333%; + } + .offset-xl-5 { + margin-left: 41.66666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.33333333%; + } + .offset-xl-8 { + margin-left: 66.66666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.33333333%; + } + .offset-xl-11 { + margin-left: 91.66666667%; + } + .g-xl-0, + .gx-xl-0 { + --bs-gutter-x: 0; + } + .g-xl-0, + .gy-xl-0 { + --bs-gutter-y: 0; + } + .g-xl-1, + .gx-xl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xl-1, + .gy-xl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xl-2, + .gx-xl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xl-2, + .gy-xl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xl-3, + .gx-xl-3 { + --bs-gutter-x: 1rem; + } + .g-xl-3, + .gy-xl-3 { + --bs-gutter-y: 1rem; + } + .g-xl-4, + .gx-xl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xl-4, + .gy-xl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xl-5, + .gx-xl-5 { + --bs-gutter-x: 3rem; + } + .g-xl-5, + .gy-xl-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1400px) { + .col-xxl { + flex: 1 0 0%; + } + .row-cols-xxl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xxl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xxl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xxl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xxl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xxl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xxl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xxl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xxl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xxl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xxl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xxl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xxl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xxl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xxl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xxl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xxl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xxl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xxl-0 { + margin-left: 0; + } + .offset-xxl-1 { + margin-left: 8.33333333%; + } + .offset-xxl-2 { + margin-left: 16.66666667%; + } + .offset-xxl-3 { + margin-left: 25%; + } + .offset-xxl-4 { + margin-left: 33.33333333%; + } + .offset-xxl-5 { + margin-left: 41.66666667%; + } + .offset-xxl-6 { + margin-left: 50%; + } + .offset-xxl-7 { + margin-left: 58.33333333%; + } + .offset-xxl-8 { + margin-left: 66.66666667%; + } + .offset-xxl-9 { + margin-left: 75%; + } + .offset-xxl-10 { + margin-left: 83.33333333%; + } + .offset-xxl-11 { + margin-left: 91.66666667%; + } + .g-xxl-0, + .gx-xxl-0 { + --bs-gutter-x: 0; + } + .g-xxl-0, + .gy-xxl-0 { + --bs-gutter-y: 0; + } + .g-xxl-1, + .gx-xxl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xxl-1, + .gy-xxl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xxl-2, + .gx-xxl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xxl-2, + .gy-xxl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xxl-3, + .gx-xxl-3 { + --bs-gutter-x: 1rem; + } + .g-xxl-3, + .gy-xxl-3 { + --bs-gutter-y: 1rem; + } + .g-xxl-4, + .gx-xxl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xxl-4, + .gy-xxl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xxl-5, + .gx-xxl-5 { + --bs-gutter-x: 3rem; + } + .g-xxl-5, + .gy-xxl-5 { + --bs-gutter-y: 3rem; + } +} +.table { + --bs-table-color-type: initial; + --bs-table-bg-type: initial; + --bs-table-color-state: initial; + --bs-table-bg-state: initial; + --bs-table-color: var(--bs-emphasis-color); + --bs-table-bg: var(--bs-body-bg); + --bs-table-border-color: var(--bs-border-color); + --bs-table-accent-bg: transparent; + --bs-table-striped-color: var(--bs-emphasis-color); + --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05); + --bs-table-active-color: var(--bs-emphasis-color); + --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1); + --bs-table-hover-color: var(--bs-emphasis-color); + --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075); + width: 100%; + margin-bottom: 1rem; + vertical-align: top; + border-color: var(--bs-table-border-color); +} +.table > :not(caption) > * > * { + padding: 1rem 1rem; + color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color))); + background-color: var(--bs-table-bg); + border-bottom-width: var(--bs-border-width); + box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg))); +} +.table > tbody { + vertical-align: inherit; +} +.table > thead { + vertical-align: bottom; +} + +.table-group-divider { + border-top: calc(var(--bs-border-width) * 2) solid currentcolor; +} + +.caption-top { + caption-side: top; +} + +.table-sm > :not(caption) > * > * { + padding: 0.5rem 0.5rem; +} + +.table-bordered > :not(caption) > * { + border-width: var(--bs-border-width) 0; +} +.table-bordered > :not(caption) > * > * { + border-width: 0 var(--bs-border-width); +} + +.table-borderless > :not(caption) > * > * { + border-bottom-width: 0; +} +.table-borderless > :not(:first-child) { + border-top-width: 0; +} + +.table-striped > tbody > tr:nth-of-type(odd) > * { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} + +.table-striped-columns > :not(caption) > tr > :nth-child(even) { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} + +.table-active { + --bs-table-color-state: var(--bs-table-active-color); + --bs-table-bg-state: var(--bs-table-active-bg); +} + +.table-hover > tbody > tr:hover > * { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg); +} + +.table-primary { + --bs-table-color: #000; + --bs-table-bg: #d6defa; + --bs-table-border-color: #abb2c8; + --bs-table-striped-bg: #cbd3ee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #c1c8e1; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #c6cde7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-secondary { + --bs-table-color: #000; + --bs-table-bg: white; + --bs-table-border-color: #cccccc; + --bs-table-striped-bg: #f2f2f2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6e6e6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ececec; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-success { + --bs-table-color: #000; + --bs-table-bg: #d5f0e6; + --bs-table-border-color: #aac0b8; + --bs-table-striped-bg: #cae4db; + --bs-table-striped-color: #000; + --bs-table-active-bg: #c0d8cf; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c5ded5; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-info { + --bs-table-color: #000; + --bs-table-bg: #d4e5f0; + --bs-table-border-color: #aab7c0; + --bs-table-striped-bg: #c9dae4; + --bs-table-striped-color: #000; + --bs-table-active-bg: #bfced8; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c4d4de; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-warning { + --bs-table-color: #000; + --bs-table-bg: #fdf2df; + --bs-table-border-color: #cac2b2; + --bs-table-striped-bg: #f0e6d4; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e4dac9; + --bs-table-active-color: #000; + --bs-table-hover-bg: #eae0ce; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-danger { + --bs-table-color: #000; + --bs-table-bg: #f8d4d5; + --bs-table-border-color: #c6aaaa; + --bs-table-striped-bg: #ecc9ca; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfbfc0; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #e5c4c5; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-light { + --bs-table-color: #000; + --bs-table-bg: #f8f9fa; + --bs-table-border-color: #c6c7c8; + --bs-table-striped-bg: #ecedee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfe0e1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5e6e7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-dark { + --bs-table-color: #fff; + --bs-table-bg: #212529; + --bs-table-border-color: #4d5154; + --bs-table-striped-bg: #2c3034; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #373b3e; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #323539; + --bs-table-hover-color: #fff; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1399.98px) { + .table-responsive-xxl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +.form-label { + margin-bottom: 0.5rem; + font-weight: 500; +} + +.col-form-label { + padding-top: calc(0.5rem + var(--bs-border-width)); + padding-bottom: calc(0.5rem + var(--bs-border-width)); + margin-bottom: 0; + font-size: inherit; + font-weight: 500; + line-height: 1.5; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + var(--bs-border-width)); + padding-bottom: calc(0.5rem + var(--bs-border-width)); + font-size: 1.25rem; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + var(--bs-border-width)); + padding-bottom: calc(0.25rem + var(--bs-border-width)); + font-size: 0.875rem; +} + +.form-text { + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-secondary-color); +} + +.form-control { + display: block; + width: 100%; + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-clip: padding-box; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} +.form-control[type=file] { + overflow: hidden; +} +.form-control[type=file]:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control:focus { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + border-color: #9aacf3; + outline: 0; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.form-control::-webkit-date-and-time-value { + min-width: 85px; + height: 1.5em; + margin: 0; +} +.form-control::-webkit-datetime-edit { + display: block; + padding: 0; +} +.form-control::-moz-placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control::placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control:disabled { + background-color: var(--bs-secondary-bg); + opacity: 1; +} +.form-control::-webkit-file-upload-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +.form-control::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-control::-webkit-file-upload-button { + -webkit-transition: none; + transition: none; + } + .form-control::file-selector-button { + transition: none; + } +} +.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { + background-color: var(--bs-secondary-bg); +} +.form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: var(--bs-secondary-bg); +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.5rem 0; + margin-bottom: 0; + line-height: 1.5; + color: var(--bs-body-color); + background-color: transparent; + border: solid transparent; + border-width: var(--bs-border-width) 0; +} +.form-control-plaintext:focus { + outline: 0; +} +.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm { + min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} +.form-control-sm::-webkit-file-upload-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} +.form-control-sm::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + -webkit-margin-end: 0.5rem; + margin-inline-end: 0.5rem; +} + +.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} +.form-control-lg::-webkit-file-upload-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} +.form-control-lg::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + -webkit-margin-end: 1rem; + margin-inline-end: 1rem; +} + +textarea.form-control { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-sm { + min-height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-lg { + min-height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} + +.form-control-color { + width: 3rem; + height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); + padding: 0.5rem; +} +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control-color::-moz-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color::-webkit-color-swatch { + border: 0 !important; + border-radius: var(--bs-border-radius); +} +.form-control-color.form-control-sm { + height: calc(1.5em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +.form-control-color.form-control-lg { + height: calc(1.5em + 1rem + calc(var(--bs-border-width) * 2)); +} + +.form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + display: block; + width: 100%; + padding: 0.5rem 3rem 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 16px 12px; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + box-shadow: var(--bs-box-shadow-inset); + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-select { + transition: none; + } +} +.form-select:focus { + border-color: #9aacf3; + outline: 0; + box-shadow: var(--bs-box-shadow-inset), 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.form-select[multiple], .form-select[size]:not([size="1"]) { + padding-right: 1rem; + background-image: none; +} +.form-select:disabled { + background-color: var(--bs-secondary-bg); +} +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 var(--bs-body-color); +} + +.form-select-sm { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} + +.form-select-lg { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} + +[data-bs-theme=dark] .form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); +} + +.form-check { + display: block; + min-height: 1.5rem; + padding-left: 1.5em; + margin-bottom: 0.125rem; +} +.form-check .form-check-input { + float: left; + margin-left: -1.5em; +} + +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right; +} +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0; +} + +.form-check-input { + --bs-form-check-bg: var(--bs-body-bg); + flex-shrink: 0; + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + color-adjust: exact; + print-color-adjust: exact; +} +.form-check-input[type=checkbox] { + border-radius: 0.25em; +} +.form-check-input[type=radio] { + border-radius: 50%; +} +.form-check-input:active { + filter: brightness(90%); +} +.form-check-input:focus { + border-color: #9aacf3; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.form-check-input:checked { + background-color: #3459e6; + border-color: #3459e6; +} +.form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); +} +.form-check-input:checked[type=radio] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-check-input[type=checkbox]:indeterminate { + background-color: #3459e6; + border-color: #3459e6; + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; +} +.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { + cursor: default; + opacity: 0.5; +} + +.form-switch { + padding-left: 2.5em; +} +.form-switch .form-check-input { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + width: 2em; + margin-left: -2.5em; + background-image: var(--bs-form-switch-bg); + background-position: left center; + border-radius: 2em; + transition: background-position 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-switch .form-check-input { + transition: none; + } +} +.form-switch .form-check-input:focus { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%239aacf3'/%3e%3c/svg%3e"); +} +.form-switch .form-check-input:checked { + background-position: right center; + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0; +} +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0; +} + +.form-check-inline { + display: inline-block; + margin-right: 1rem; +} + +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.btn-check[disabled] + .btn, .btn-check:disabled + .btn { + pointer-events: none; + filter: none; + opacity: 0.65; +} + +[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e"); +} + +.form-range { + width: 100%; + height: 1.5rem; + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; +} +.form-range:focus { + outline: 0; +} +.form-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.form-range::-moz-focus-outer { + border: 0; +} +.form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + -webkit-appearance: none; + appearance: none; + background-color: #3459e6; + border: 0; + border-radius: 1rem; + box-shadow: 0 0.1rem 0.25rem rgba(0, 0, 0, 0.1); + -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } +} +.form-range::-webkit-slider-thumb:active { + background-color: #c2cdf8; +} +.form-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem; + box-shadow: var(--bs-box-shadow-inset); +} +.form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + -moz-appearance: none; + appearance: none; + background-color: #3459e6; + border: 0; + border-radius: 1rem; + box-shadow: 0 0.1rem 0.25rem rgba(0, 0, 0, 0.1); + -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } +} +.form-range::-moz-range-thumb:active { + background-color: #c2cdf8; +} +.form-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; + border-radius: 1rem; + box-shadow: var(--bs-box-shadow-inset); +} +.form-range:disabled { + pointer-events: none; +} +.form-range:disabled::-webkit-slider-thumb { + background-color: var(--bs-secondary-color); +} +.form-range:disabled::-moz-range-thumb { + background-color: var(--bs-secondary-color); +} + +.form-floating { + position: relative; +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext, +.form-floating > .form-select { + height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + min-height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + line-height: 1.25; +} +.form-floating > label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + height: 100%; + padding: 1rem 1rem; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: var(--bs-border-width) solid transparent; + transform-origin: 0 0; + transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .form-floating > label { + transition: none; + } +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext { + padding: 1rem 1rem; +} +.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder { + color: transparent; +} +.form-floating > .form-control::placeholder, +.form-floating > .form-control-plaintext::placeholder { + color: transparent; +} +.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown), +.form-floating > .form-control-plaintext:focus, +.form-floating > .form-control-plaintext:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:-webkit-autofill, +.form-floating > .form-control-plaintext:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-select { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-control-plaintext ~ label, +.form-floating > .form-select ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after { + position: absolute; + inset: 1rem 0.5rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control:focus ~ label::after, +.form-floating > .form-control:not(:placeholder-shown) ~ label::after, +.form-floating > .form-control-plaintext ~ label::after, +.form-floating > .form-select ~ label::after { + position: absolute; + inset: 1rem 0.5rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); + border-radius: var(--bs-border-radius); +} +.form-floating > .form-control:-webkit-autofill ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label { + border-width: var(--bs-border-width) 0; +} +.form-floating > :disabled ~ label, +.form-floating > .form-control:disabled ~ label { + color: #6c757d; +} +.form-floating > :disabled ~ label::after, +.form-floating > .form-control:disabled ~ label::after { + background-color: var(--bs-secondary-bg); +} + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .form-select, +.input-group > .form-floating { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} +.input-group > .form-control:focus, +.input-group > .form-select:focus, +.input-group > .form-floating:focus-within { + z-index: 5; +} +.input-group .btn { + position: relative; + z-index: 2; +} +.input-group .btn:focus { + z-index: 5; +} + +.input-group-text { + display: flex; + align-items: center; + padding: 0.5rem 1rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.5; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-tertiary-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); +} + +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text, +.input-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + border-radius: var(--bs-border-radius-lg); +} + +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text, +.input-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: var(--bs-border-radius-sm); +} + +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: 4rem; +} + +.input-group:not(.has-validation) > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), +.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3), +.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-control, +.input-group:not(.has-validation) > .form-floating:not(:last-child) > .form-select { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group.has-validation > :nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu):not(.form-floating), +.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4), +.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-control, +.input-group.has-validation > .form-floating:nth-last-child(n+3) > .form-select { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { + margin-left: calc(var(--bs-border-width) * -1); + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group > .form-floating:not(:first-child) > .form-control, +.input-group > .form-floating:not(:first-child) > .form-select { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-valid-color); +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-success); + border-radius: var(--bs-border-radius); +} + +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip, +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-control:valid, .form-control.is-valid { + border-color: var(--bs-form-valid-border-color); + padding-right: calc(1.5em + 1rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%232fb380' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.25rem) center; + background-size: calc(0.75em + 0.5rem) calc(0.75em + 0.5rem); +} +.was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.5em + 1rem); + background-position: top calc(0.375em + 0.25rem) right calc(0.375em + 0.25rem); +} + +.was-validated .form-select:valid, .form-select.is-valid { + border-color: var(--bs-form-valid-border-color); +} +.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%232fb380' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + padding-right: 5.5rem; + background-position: right 1rem center, center right 3rem; + background-size: 16px 12px, calc(0.75em + 0.5rem) calc(0.75em + 0.5rem); +} +.was-validated .form-select:valid:focus, .form-select.is-valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} + +.was-validated .form-control-color:valid, .form-control-color.is-valid { + width: calc(3rem + calc(1.5em + 1rem)); +} + +.was-validated .form-check-input:valid, .form-check-input.is-valid { + border-color: var(--bs-form-valid-border-color); +} +.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked { + background-color: var(--bs-form-valid-color); +} +.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: var(--bs-form-valid-color); +} + +.form-check-inline .form-check-input ~ .valid-feedback { + margin-left: 0.5em; +} + +.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid, +.was-validated .input-group > .form-select:not(:focus):valid, +.input-group > .form-select:not(:focus).is-valid, +.was-validated .input-group > .form-floating:not(:focus-within):valid, +.input-group > .form-floating:not(:focus-within).is-valid { + z-index: 3; +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-invalid-color); +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.875rem; + color: #fff; + background-color: var(--bs-danger); + border-radius: var(--bs-border-radius); +} + +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip, +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-control:invalid, .form-control.is-invalid { + border-color: var(--bs-form-invalid-border-color); + padding-right: calc(1.5em + 1rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23da292e'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23da292e' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.25rem) center; + background-size: calc(0.75em + 0.5rem) calc(0.75em + 0.5rem); +} +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.5em + 1rem); + background-position: top calc(0.375em + 0.25rem) right calc(0.375em + 0.25rem); +} + +.was-validated .form-select:invalid, .form-select.is-invalid { + border-color: var(--bs-form-invalid-border-color); +} +.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23da292e'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23da292e' stroke='none'/%3e%3c/svg%3e"); + padding-right: 5.5rem; + background-position: right 1rem center, center right 3rem; + background-size: 16px 12px, calc(0.75em + 0.5rem) calc(0.75em + 0.5rem); +} +.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} + +.was-validated .form-control-color:invalid, .form-control-color.is-invalid { + width: calc(3rem + calc(1.5em + 1rem)); +} + +.was-validated .form-check-input:invalid, .form-check-input.is-invalid { + border-color: var(--bs-form-invalid-border-color); +} +.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked { + background-color: var(--bs-form-invalid-color); +} +.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: var(--bs-form-invalid-color); +} + +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: 0.5em; +} + +.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid, +.was-validated .input-group > .form-select:not(:focus):invalid, +.input-group > .form-select:not(:focus).is-invalid, +.was-validated .input-group > .form-floating:not(:focus-within):invalid, +.input-group > .form-floating:not(:focus-within).is-invalid { + z-index: 4; +} + +.btn { + --bs-btn-padding-x: 1rem; + --bs-btn-padding-y: 0.5rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 0.875rem; + --bs-btn-font-weight: 500; + --bs-btn-line-height: 1.5; + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: transparent; + --bs-btn-border-width: var(--bs-border-width); + --bs-btn-border-color: transparent; + --bs-btn-border-radius: var(--bs-border-radius); + --bs-btn-hover-border-color: transparent; + --bs-btn-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-opacity: 0.65; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); + display: inline-block; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); + border-radius: var(--bs-btn-border-radius); + background-color: var(--bs-btn-bg); + box-shadow: var(--bs-btn-box-shadow); + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} +.btn:hover { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); +} +.btn-check + .btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color); +} +.btn:focus-visible { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-box-shadow), var(--bs-btn-focus-box-shadow); +} +.btn-check:focus-visible + .btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-box-shadow), var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color); + box-shadow: var(--bs-btn-active-shadow); +} +.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible { + box-shadow: var(--bs-btn-active-shadow), var(--bs-btn-focus-box-shadow); +} +.btn:disabled, .btn.disabled, fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); + pointer-events: none; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity); + box-shadow: none; +} + +.btn-primary { + --bs-btn-color: #fff; + --bs-btn-bg: #3459e6; + --bs-btn-border-color: #3459e6; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #2c4cc4; + --bs-btn-hover-border-color: #2a47b8; + --bs-btn-focus-shadow-rgb: 82, 114, 234; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #2a47b8; + --bs-btn-active-border-color: #2743ad; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #3459e6; + --bs-btn-disabled-border-color: #3459e6; +} + +.btn-secondary { + --bs-btn-color: #000; + --bs-btn-bg: #fff; + --bs-btn-border-color: #fff; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: white; + --bs-btn-hover-border-color: white; + --bs-btn-focus-shadow-rgb: 217, 217, 217; + --bs-btn-active-color: #000; + --bs-btn-active-bg: white; + --bs-btn-active-border-color: white; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #fff; + --bs-btn-disabled-border-color: #fff; +} + +.btn-success { + --bs-btn-color: #fff; + --bs-btn-bg: #2fb380; + --bs-btn-border-color: #2fb380; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #28986d; + --bs-btn-hover-border-color: #268f66; + --bs-btn-focus-shadow-rgb: 78, 190, 147; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #268f66; + --bs-btn-active-border-color: #238660; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #2fb380; + --bs-btn-disabled-border-color: #2fb380; +} + +.btn-info { + --bs-btn-color: #fff; + --bs-btn-bg: #287bb5; + --bs-btn-border-color: #287bb5; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #22699a; + --bs-btn-hover-border-color: #206291; + --bs-btn-focus-shadow-rgb: 72, 143, 192; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #206291; + --bs-btn-active-border-color: #1e5c88; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #287bb5; + --bs-btn-disabled-border-color: #287bb5; +} + +.btn-warning { + --bs-btn-color: #fff; + --bs-btn-bg: #f4bd61; + --bs-btn-border-color: #f4bd61; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #cfa152; + --bs-btn-hover-border-color: #c3974e; + --bs-btn-focus-shadow-rgb: 246, 199, 121; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #c3974e; + --bs-btn-active-border-color: #b78e49; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #f4bd61; + --bs-btn-disabled-border-color: #f4bd61; +} + +.btn-danger { + --bs-btn-color: #fff; + --bs-btn-bg: #da292e; + --bs-btn-border-color: #da292e; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #b92327; + --bs-btn-hover-border-color: #ae2125; + --bs-btn-focus-shadow-rgb: 224, 73, 77; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #ae2125; + --bs-btn-active-border-color: #a41f23; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #da292e; + --bs-btn-disabled-border-color: #da292e; +} + +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #d3d4d5; + --bs-btn-hover-border-color: #c6c7c8; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #c6c7c8; + --bs-btn-active-border-color: #babbbc; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa; +} + +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #424649; + --bs-btn-hover-border-color: #373b3e; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #4d5154; + --bs-btn-active-border-color: #373b3e; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529; +} + +.btn-outline-primary { + --bs-btn-color: #3459e6; + --bs-btn-border-color: #3459e6; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #3459e6; + --bs-btn-hover-border-color: #3459e6; + --bs-btn-focus-shadow-rgb: 52, 89, 230; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #3459e6; + --bs-btn-active-border-color: #3459e6; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #3459e6; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #3459e6; + --bs-gradient: none; +} + +.btn-outline-secondary { + --bs-btn-color: #fff; + --bs-btn-border-color: #fff; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #fff; + --bs-btn-hover-border-color: #fff; + --bs-btn-focus-shadow-rgb: 255, 255, 255; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #fff; + --bs-btn-active-border-color: #fff; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #fff; + --bs-gradient: none; +} + +.btn-outline-success { + --bs-btn-color: #2fb380; + --bs-btn-border-color: #2fb380; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #2fb380; + --bs-btn-hover-border-color: #2fb380; + --bs-btn-focus-shadow-rgb: 47, 179, 128; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #2fb380; + --bs-btn-active-border-color: #2fb380; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #2fb380; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #2fb380; + --bs-gradient: none; +} + +.btn-outline-info { + --bs-btn-color: #287bb5; + --bs-btn-border-color: #287bb5; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #287bb5; + --bs-btn-hover-border-color: #287bb5; + --bs-btn-focus-shadow-rgb: 40, 123, 181; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #287bb5; + --bs-btn-active-border-color: #287bb5; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #287bb5; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #287bb5; + --bs-gradient: none; +} + +.btn-outline-warning { + --bs-btn-color: #f4bd61; + --bs-btn-border-color: #f4bd61; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #f4bd61; + --bs-btn-hover-border-color: #f4bd61; + --bs-btn-focus-shadow-rgb: 244, 189, 97; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #f4bd61; + --bs-btn-active-border-color: #f4bd61; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #f4bd61; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f4bd61; + --bs-gradient: none; +} + +.btn-outline-danger { + --bs-btn-color: #da292e; + --bs-btn-border-color: #da292e; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #da292e; + --bs-btn-hover-border-color: #da292e; + --bs-btn-focus-shadow-rgb: 218, 41, 46; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #da292e; + --bs-btn-active-border-color: #da292e; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #da292e; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #da292e; + --bs-gradient: none; +} + +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f8f9fa; + --bs-gradient: none; +} + +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #212529; + --bs-gradient: none; +} + +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-color: var(--bs-link-hover-color); + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: 0 0 0 #000; + --bs-btn-focus-shadow-rgb: 82, 114, 234; + text-decoration: underline; +} +.btn-link:focus-visible { + color: var(--bs-btn-color); +} +.btn-link:hover { + color: var(--bs-btn-hover-color); +} + +.btn-lg, .btn-group-lg > .btn { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 1.25rem; + --bs-btn-border-radius: var(--bs-border-radius-lg); +} + +.btn-sm, .btn-group-sm > .btn { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-sm); +} + +.fade { + transition: opacity 0.15s linear; +} +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } +} +.fade:not(.show) { + opacity: 0; +} + +.collapse:not(.show) { + display: none; +} + +.collapsing { + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } +} +.collapsing.collapse-horizontal { + width: 0; + height: auto; + transition: width 0.35s ease; +} +@media (prefers-reduced-motion: reduce) { + .collapsing.collapse-horizontal { + transition: none; + } +} + +.dropup, +.dropend, +.dropdown, +.dropstart, +.dropup-center, +.dropdown-center { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 0.875rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: #dee2e6; + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width)); + --bs-dropdown-divider-bg: #e9ecef; + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: var(--bs-box-shadow); + --bs-dropdown-link-color: var(--bs-body-color); + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-link-hover-bg: #3459e6; + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #3459e6; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.5rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); + border-radius: var(--bs-dropdown-border-radius); + box-shadow: var(--bs-dropdown-box-shadow); +} +.dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--bs-dropdown-spacer); +} + +.dropdown-menu-start { + --bs-position: start; +} +.dropdown-menu-start[data-bs-popper] { + right: auto; + left: 0; +} + +.dropdown-menu-end { + --bs-position: end; +} +.dropdown-menu-end[data-bs-popper] { + right: 0; + left: auto; +} + +@media (min-width: 576px) { + .dropdown-menu-sm-start { + --bs-position: start; + } + .dropdown-menu-sm-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-sm-end { + --bs-position: end; + } + .dropdown-menu-sm-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 768px) { + .dropdown-menu-md-start { + --bs-position: start; + } + .dropdown-menu-md-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-md-end { + --bs-position: end; + } + .dropdown-menu-md-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 992px) { + .dropdown-menu-lg-start { + --bs-position: start; + } + .dropdown-menu-lg-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-lg-end { + --bs-position: end; + } + .dropdown-menu-lg-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1200px) { + .dropdown-menu-xl-start { + --bs-position: start; + } + .dropdown-menu-xl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xl-end { + --bs-position: end; + } + .dropdown-menu-xl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1400px) { + .dropdown-menu-xxl-start { + --bs-position: start; + } + .dropdown-menu-xxl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xxl-end { + --bs-position: end; + } + .dropdown-menu-xxl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +.dropup .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--bs-dropdown-spacer); +} +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--bs-dropdown-spacer); +} +.dropend .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropend .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-toggle::after { + vertical-align: 0; +} + +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--bs-dropdown-spacer); +} +.dropstart .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropstart .dropdown-toggle::after { + display: none; +} +.dropstart .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropstart .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropstart .dropdown-toggle::before { + vertical-align: 0; +} + +.dropdown-divider { + height: 0; + margin: var(--bs-dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1; +} + +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; + border-radius: var(--bs-dropdown-item-border-radius, 0); +} +.dropdown-item:hover, .dropdown-item:focus { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); +} +.dropdown-item.active, .dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg); +} +.dropdown-item.disabled, .dropdown-item:disabled { + color: var(--bs-dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x); + margin-bottom: 0; + font-size: 0.875rem; + color: var(--bs-dropdown-header-color); + white-space: nowrap; +} + +.dropdown-item-text { + display: block; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color); +} + +.dropdown-menu-dark { + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: #dee2e6; + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: #e9ecef; + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #3459e6; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + flex: 1 1 auto; +} +.btn-group > .btn-check:checked + .btn, +.btn-group > .btn-check:focus + .btn, +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn-check:checked + .btn, +.btn-group-vertical > .btn-check:focus + .btn, +.btn-group-vertical > .btn:hover, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} + +.btn-group { + border-radius: var(--bs-border-radius); +} +.btn-group > :not(.btn-check:first-child) + .btn, +.btn-group > .btn-group:not(:first-child) { + margin-left: calc(var(--bs-border-width) * -1); +} +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn.dropdown-toggle-split:first-child, +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:nth-child(n+3), +.btn-group > :not(.btn-check) + .btn, +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} +.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after { + margin-left: 0; +} +.dropstart .dropdown-toggle-split::before { + margin-right: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group.show .dropdown-toggle { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.btn-group.show .dropdown-toggle.btn-link { + box-shadow: none; +} + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) { + margin-top: calc(var(--bs-border-width) * -1); +} +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn ~ .btn, +.btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav { + --bs-nav-link-padding-x: 1rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: #495057; + --bs-nav-link-hover-color: #495057; + --bs-nav-link-disabled-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: none; + border: 0; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .nav-link { + transition: none; + } +} +.nav-link:hover, .nav-link:focus { + color: var(--bs-nav-link-hover-color); +} +.nav-link:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(52, 89, 230, 0.25); +} +.nav-link.disabled, .nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + pointer-events: none; + cursor: default; +} + +.nav-tabs { + --bs-nav-tabs-border-width: var(--bs-border-width); + --bs-nav-tabs-border-color: var(--bs-border-color); + --bs-nav-tabs-border-radius: 0; + --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color); + --bs-nav-tabs-link-active-color: #3459e6; + --bs-nav-tabs-link-active-bg: var(--bs-body-bg); + --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg); + border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color); +} +.nav-tabs .nav-link { + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); + border: var(--bs-nav-tabs-border-width) solid transparent; + border-top-left-radius: var(--bs-nav-tabs-border-radius); + border-top-right-radius: var(--bs-nav-tabs-border-radius); +} +.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color); +} +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color); +} +.nav-tabs .dropdown-menu { + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills { + --bs-nav-pills-border-radius: var(--bs-border-radius); + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #3459e6; +} +.nav-pills .nav-link { + border-radius: var(--bs-nav-pills-border-radius); +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg); +} + +.nav-underline { + --bs-nav-underline-gap: 1rem; + --bs-nav-underline-border-width: 0.125rem; + --bs-nav-underline-link-active-color: var(--bs-emphasis-color); + gap: var(--bs-nav-underline-gap); +} +.nav-underline .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--bs-nav-underline-border-width) solid transparent; +} +.nav-underline .nav-link:hover, .nav-underline .nav-link:focus { + border-bottom-color: currentcolor; +} +.nav-underline .nav-link.active, +.nav-underline .show > .nav-link { + font-weight: 700; + color: var(--bs-nav-underline-link-active-color); + border-bottom-color: currentcolor; +} + +.nav-fill > .nav-link, +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; +} + +.nav-justified > .nav-link, +.nav-justified .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; +} + +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100%; +} + +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} + +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.85rem; + --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); + --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8); + --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3); + --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-padding-y: 0.3125rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 1.25rem; + --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-nav-link-padding-x: 0.75rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 1.25rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2873, 80, 87, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15); + --bs-navbar-toggler-border-radius: var(--bs-border-radius); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); +} +.navbar > .container, +.navbar > .container-fluid, +.navbar > .container-sm, +.navbar > .container-md, +.navbar > .container-lg, +.navbar > .container-xl, +.navbar > .container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; +} +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap; +} +.navbar-brand:hover, .navbar-brand:focus { + color: var(--bs-navbar-brand-hover-color); +} + +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.navbar-nav .nav-link.active, .navbar-nav .nav-link.show { + color: var(--bs-navbar-active-color); +} +.navbar-nav .dropdown-menu { + position: static; +} + +.navbar-text { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-navbar-color); +} +.navbar-text a, +.navbar-text a:hover, +.navbar-text a:focus { + color: var(--bs-navbar-active-color); +} + +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; +} + +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); + border-radius: var(--bs-navbar-toggler-border-radius); + transition: var(--bs-navbar-toggler-transition); +} +@media (prefers-reduced-motion: reduce) { + .navbar-toggler { + transition: none; + } +} +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.navbar-nav-scroll { + max-height: var(--bs-scroll-height, 75vh); + overflow-y: auto; +} + +@media (min-width: 576px) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; + } + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-sm .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; + } + .navbar-expand-md .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-md .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; + } + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-lg .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; + } + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1400px) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xxl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xxl .navbar-toggler { + display: none; + } + .navbar-expand-xxl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +.navbar-expand { + flex-wrap: nowrap; + justify-content: flex-start; +} +.navbar-expand .navbar-nav { + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); +} +.navbar-expand .navbar-nav-scroll { + overflow: visible; +} +.navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-expand .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + box-shadow: none; + transition: none; +} +.navbar-expand .offcanvas .offcanvas-header { + display: none; +} +.navbar-expand .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; +} + +.navbar-dark, +.navbar[data-bs-theme=dark] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +[data-bs-theme=dark] .navbar-toggler-icon { + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +.card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1.5rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-title-color: ; + --bs-card-subtitle-color: ; + --bs-card-border-width: var(--bs-border-width); + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: var(--bs-border-radius); + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); + --bs-card-cap-padding-y: 1rem; + --bs-card-cap-padding-x: 1.5rem; + --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: var(--bs-body-bg); + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + height: var(--bs-card-height); + color: var(--bs-body-color); + word-wrap: break-word; + background-color: var(--bs-card-bg); + background-clip: border-box; + border: var(--bs-card-border-width) solid var(--bs-card-border-color); + border-radius: var(--bs-card-border-radius); + box-shadow: var(--bs-card-box-shadow); +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group { + border-top: inherit; + border-bottom: inherit; +} +.card > .list-group:first-child { + border-top-width: 0; + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} +.card > .list-group:last-child { + border-bottom-width: 0; + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} +.card > .card-header + .list-group, +.card > .list-group + .card-footer { + border-top: 0; +} + +.card-body { + flex: 1 1 auto; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color); +} + +.card-title { + margin-bottom: var(--bs-card-title-spacer-y); + color: var(--bs-card-title-color); +} + +.card-subtitle { + margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); + margin-bottom: 0; + color: var(--bs-card-subtitle-color); +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link + .card-link { + margin-left: var(--bs-card-spacer-x); +} + +.card-header { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + margin-bottom: 0; + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-header:first-child { + border-radius: var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius) 0 0; +} + +.card-footer { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-footer:last-child { + border-radius: 0 0 var(--bs-card-inner-border-radius) var(--bs-card-inner-border-radius); +} + +.card-header-tabs { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); + border-bottom: 0; +} +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg); +} + +.card-header-pills { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--bs-card-img-overlay-padding); + border-radius: var(--bs-card-inner-border-radius); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; +} + +.card-img, +.card-img-top { + border-top-left-radius: var(--bs-card-inner-border-radius); + border-top-right-radius: var(--bs-card-inner-border-radius); +} + +.card-img, +.card-img-bottom { + border-bottom-right-radius: var(--bs-card-inner-border-radius); + border-bottom-left-radius: var(--bs-card-inner-border-radius); +} + +.card-group > .card { + margin-bottom: var(--bs-card-group-margin); +} +@media (min-width: 576px) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; + } +} + +.accordion { + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-bg: var(--bs-body-bg); + --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: var(--bs-border-width); + --bs-accordion-border-radius: var(--bs-border-radius); + --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); + --bs-accordion-btn-padding-x: 1.25rem; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23495057'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 1.25rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2315245c'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-border-color: #9aacf3; + --bs-accordion-btn-focus-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + --bs-accordion-body-padding-x: 1.25rem; + --bs-accordion-body-padding-y: 1rem; + --bs-accordion-active-color: var(--bs-primary-text-emphasis); + --bs-accordion-active-bg: var(--bs-primary-bg-subtle); +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); + font-size: 1rem; + color: var(--bs-accordion-btn-color); + text-align: left; + background-color: var(--bs-accordion-btn-bg); + border: 0; + border-radius: 0; + overflow-anchor: none; + transition: var(--bs-accordion-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button { + transition: none; + } +} +.accordion-button:not(.collapsed) { + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color); +} +.accordion-button:not(.collapsed)::after { + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform); +} +.accordion-button::after { + flex-shrink: 0; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--bs-accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--bs-accordion-btn-icon-width); + transition: var(--bs-accordion-btn-icon-transition); +} +@media (prefers-reduced-motion: reduce) { + .accordion-button::after { + transition: none; + } +} +.accordion-button:hover { + z-index: 2; +} +.accordion-button:focus { + z-index: 3; + border-color: var(--bs-accordion-btn-focus-border-color); + outline: 0; + box-shadow: var(--bs-accordion-btn-focus-box-shadow); +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); +} +.accordion-item:first-of-type { + border-top-left-radius: var(--bs-accordion-border-radius); + border-top-right-radius: var(--bs-accordion-border-radius); +} +.accordion-item:first-of-type .accordion-button { + border-top-left-radius: var(--bs-accordion-inner-border-radius); + border-top-right-radius: var(--bs-accordion-inner-border-radius); +} +.accordion-item:not(:first-of-type) { + border-top: 0; +} +.accordion-item:last-of-type { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} +.accordion-item:last-of-type .accordion-button.collapsed { + border-bottom-right-radius: var(--bs-accordion-inner-border-radius); + border-bottom-left-radius: var(--bs-accordion-inner-border-radius); +} +.accordion-item:last-of-type .accordion-collapse { + border-bottom-right-radius: var(--bs-accordion-border-radius); + border-bottom-left-radius: var(--bs-accordion-border-radius); +} + +.accordion-body { + padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x); +} + +.accordion-flush .accordion-collapse { + border-width: 0; +} +.accordion-flush .accordion-item { + border-right: 0; + border-left: 0; + border-radius: 0; +} +.accordion-flush .accordion-item:first-child { + border-top: 0; +} +.accordion-flush .accordion-item:last-child { + border-bottom: 0; +} +.accordion-flush .accordion-item .accordion-button, .accordion-flush .accordion-item .accordion-button.collapsed { + border-radius: 0; +} + +[data-bs-theme=dark] .accordion-button::after { + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23859bf0'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23859bf0'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} + +.breadcrumb { + --bs-breadcrumb-padding-x: 1rem; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: var(--bs-secondary-color); + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); + list-style: none; + background-color: var(--bs-breadcrumb-bg); + border-radius: var(--bs-breadcrumb-border-radius); +} + +.breadcrumb-item + .breadcrumb-item { + padding-left: var(--bs-breadcrumb-item-padding-x); +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); + content: var(--bs-breadcrumb-divider, ">") /* rtl: var(--bs-breadcrumb-divider, ">") */; +} +.breadcrumb-item.active { + color: var(--bs-breadcrumb-item-active-color); +} + +.pagination { + --bs-pagination-padding-x: 1rem; + --bs-pagination-padding-y: 0.5rem; + --bs-pagination-font-size: 1rem; + --bs-pagination-color: var(--bs-primary-bg); + --bs-pagination-bg: var(--bs-body-bg); + --bs-pagination-border-width: var(--bs-border-width); + --bs-pagination-border-color: var(--bs-border-color); + --bs-pagination-border-radius: var(--bs-border-radius); + --bs-pagination-hover-color: var(--bs-primary-bg); + --bs-pagination-hover-bg: var(--bs-secondary-bg); + --bs-pagination-hover-border-color: var(--bs-border-color); + --bs-pagination-focus-color: var(--bs-primary-bg); + --bs-pagination-focus-bg: var(--bs-secondary-bg); + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(52, 89, 230, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #3459e6; + --bs-pagination-active-border-color: #3459e6; + --bs-pagination-disabled-color: var(--bs-tertiary-color); + --bs-pagination-disabled-bg: var(--bs-tertiary-bg); + --bs-pagination-disabled-border-color: var(--bs-border-color); + display: flex; + padding-left: 0; + list-style: none; +} + +.page-link { + position: relative; + display: block; + padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); + text-decoration: none; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color); + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .page-link { + transition: none; + } +} +.page-link:hover { + z-index: 2; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color); +} +.page-link:focus { + z-index: 3; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); + outline: 0; + box-shadow: var(--bs-pagination-focus-box-shadow); +} +.page-link.active, .active > .page-link { + z-index: 3; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color); +} +.page-link.disabled, .disabled > .page-link { + color: var(--bs-pagination-disabled-color); + pointer-events: none; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color); +} + +.page-item:not(:first-child) .page-link { + margin-left: calc(var(--bs-border-width) * -1); +} +.page-item:first-child .page-link { + border-top-left-radius: var(--bs-pagination-border-radius); + border-bottom-left-radius: var(--bs-pagination-border-radius); +} +.page-item:last-child .page-link { + border-top-right-radius: var(--bs-pagination-border-radius); + border-bottom-right-radius: var(--bs-pagination-border-radius); +} + +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 1.25rem; + --bs-pagination-border-radius: var(--bs-border-radius-lg); +} + +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: var(--bs-border-radius-sm); +} + +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: var(--bs-badge-border-radius); +} +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color); + --bs-alert-border-radius: var(--bs-border-radius); + --bs-alert-link-color: inherit; + position: relative; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); + border-radius: var(--bs-alert-border-radius); +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; + color: var(--bs-alert-link-color); +} + +.alert-dismissible { + padding-right: 3rem; +} +.alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 1.25rem 1rem; +} + +.alert-primary { + --bs-alert-color: var(--bs-primary-text-emphasis); + --bs-alert-bg: var(--bs-primary-bg-subtle); + --bs-alert-border-color: var(--bs-primary-border-subtle); + --bs-alert-link-color: var(--bs-primary-text-emphasis); +} + +.alert-secondary { + --bs-alert-color: var(--bs-secondary-text-emphasis); + --bs-alert-bg: var(--bs-secondary-bg-subtle); + --bs-alert-border-color: var(--bs-secondary-border-subtle); + --bs-alert-link-color: var(--bs-secondary-text-emphasis); +} + +.alert-success { + --bs-alert-color: var(--bs-success-text-emphasis); + --bs-alert-bg: var(--bs-success-bg-subtle); + --bs-alert-border-color: var(--bs-success-border-subtle); + --bs-alert-link-color: var(--bs-success-text-emphasis); +} + +.alert-info { + --bs-alert-color: var(--bs-info-text-emphasis); + --bs-alert-bg: var(--bs-info-bg-subtle); + --bs-alert-border-color: var(--bs-info-border-subtle); + --bs-alert-link-color: var(--bs-info-text-emphasis); +} + +.alert-warning { + --bs-alert-color: var(--bs-warning-text-emphasis); + --bs-alert-bg: var(--bs-warning-bg-subtle); + --bs-alert-border-color: var(--bs-warning-border-subtle); + --bs-alert-link-color: var(--bs-warning-text-emphasis); +} + +.alert-danger { + --bs-alert-color: var(--bs-danger-text-emphasis); + --bs-alert-bg: var(--bs-danger-bg-subtle); + --bs-alert-border-color: var(--bs-danger-border-subtle); + --bs-alert-link-color: var(--bs-danger-text-emphasis); +} + +.alert-light { + --bs-alert-color: var(--bs-light-text-emphasis); + --bs-alert-bg: var(--bs-light-bg-subtle); + --bs-alert-border-color: var(--bs-light-border-subtle); + --bs-alert-link-color: var(--bs-light-text-emphasis); +} + +.alert-dark { + --bs-alert-color: var(--bs-dark-text-emphasis); + --bs-alert-bg: var(--bs-dark-bg-subtle); + --bs-alert-border-color: var(--bs-dark-border-subtle); + --bs-alert-link-color: var(--bs-dark-text-emphasis); +} + +@keyframes progress-bar-stripes { + 0% { + background-position-x: 1rem; + } +} +.progress, +.progress-stacked { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.75rem; + --bs-progress-bg: var(--bs-secondary-bg); + --bs-progress-border-radius: var(--bs-border-radius); + --bs-progress-box-shadow: var(--bs-box-shadow-inset); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #3459e6; + --bs-progress-bar-transition: width 0.6s ease; + display: flex; + height: var(--bs-progress-height); + overflow: hidden; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); + border-radius: var(--bs-progress-border-radius); + box-shadow: var(--bs-progress-box-shadow); +} + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--bs-progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-progress-bar-bg); + transition: var(--bs-progress-bar-transition); +} +@media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; + } +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: var(--bs-progress-height) var(--bs-progress-height); +} + +.progress-stacked > .progress { + overflow: visible; +} + +.progress-stacked > .progress > .progress-bar { + width: 100%; +} + +.progress-bar-animated { + animation: 1s linear infinite progress-bar-stripes; +} +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + animation: none; + } +} + +.list-group { + --bs-list-group-color: var(--bs-body-color); + --bs-list-group-bg: var(--bs-body-bg); + --bs-list-group-border-color: var(--bs-border-color); + --bs-list-group-border-width: var(--bs-border-width); + --bs-list-group-border-radius: var(--bs-border-radius); + --bs-list-group-item-padding-x: 1.5rem; + --bs-list-group-item-padding-y: 1rem; + --bs-list-group-action-color: var(--bs-secondary-color); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-tertiary-bg); + --bs-list-group-action-active-color: var(--bs-body-color); + --bs-list-group-action-active-bg: var(--bs-secondary-bg); + --bs-list-group-disabled-color: var(--bs-secondary-color); + --bs-list-group-disabled-bg: var(--bs-body-bg); + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #3459e6; + --bs-list-group-active-border-color: #3459e6; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + border-radius: var(--bs-list-group-border-radius); +} + +.list-group-numbered { + list-style-type: none; + counter-reset: section; +} +.list-group-numbered > .list-group-item::before { + content: counters(section, ".") ". "; + counter-increment: section; +} + +.list-group-item-action { + width: 100%; + color: var(--bs-list-group-action-color); + text-align: inherit; +} +.list-group-item-action:hover, .list-group-item-action:focus { + z-index: 1; + color: var(--bs-list-group-action-hover-color); + text-decoration: none; + background-color: var(--bs-list-group-action-hover-bg); +} +.list-group-item-action:active { + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg); +} + +.list-group-item { + position: relative; + display: block; + padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); + text-decoration: none; + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color); +} +.list-group-item:first-child { + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} +.list-group-item:last-child { + border-bottom-right-radius: inherit; + border-bottom-left-radius: inherit; +} +.list-group-item.disabled, .list-group-item:disabled { + color: var(--bs-list-group-disabled-color); + pointer-events: none; + background-color: var(--bs-list-group-disabled-bg); +} +.list-group-item.active { + z-index: 2; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color); +} +.list-group-item + .list-group-item { + border-top-width: 0; +} +.list-group-item + .list-group-item.active { + margin-top: calc(-1 * var(--bs-list-group-border-width)); + border-top-width: var(--bs-list-group-border-width); +} + +.list-group-horizontal { + flex-direction: row; +} +.list-group-horizontal > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; +} +.list-group-horizontal > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; +} +.list-group-horizontal > .list-group-item.active { + margin-top: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); +} + +@media (min-width: 576px) { + .list-group-horizontal-sm { + flex-direction: row; + } + .list-group-horizontal-sm > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-sm > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 768px) { + .list-group-horizontal-md { + flex-direction: row; + } + .list-group-horizontal-md > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-md > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 992px) { + .list-group-horizontal-lg { + flex-direction: row; + } + .list-group-horizontal-lg > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-lg > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1200px) { + .list-group-horizontal-xl { + flex-direction: row; + } + .list-group-horizontal-xl > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-xl > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1400px) { + .list-group-horizontal-xxl { + flex-direction: row; + } + .list-group-horizontal-xxl > .list-group-item:first-child:not(:last-child) { + border-bottom-left-radius: var(--bs-list-group-border-radius); + border-top-right-radius: 0; + } + .list-group-horizontal-xxl > .list-group-item:last-child:not(:first-child) { + border-top-right-radius: var(--bs-list-group-border-radius); + border-bottom-left-radius: 0; + } + .list-group-horizontal-xxl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +.list-group-flush { + border-radius: 0; +} +.list-group-flush > .list-group-item { + border-width: 0 0 var(--bs-list-group-border-width); +} +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; +} + +.list-group-item-primary { + --bs-list-group-color: var(--bs-primary-text-emphasis); + --bs-list-group-bg: var(--bs-primary-bg-subtle); + --bs-list-group-border-color: var(--bs-primary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-primary-border-subtle); + --bs-list-group-active-color: var(--bs-primary-bg-subtle); + --bs-list-group-active-bg: var(--bs-primary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-primary-text-emphasis); +} + +.list-group-item-secondary { + --bs-list-group-color: var(--bs-secondary-text-emphasis); + --bs-list-group-bg: var(--bs-secondary-bg-subtle); + --bs-list-group-border-color: var(--bs-secondary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle); + --bs-list-group-active-color: var(--bs-secondary-bg-subtle); + --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis); +} + +.list-group-item-success { + --bs-list-group-color: var(--bs-success-text-emphasis); + --bs-list-group-bg: var(--bs-success-bg-subtle); + --bs-list-group-border-color: var(--bs-success-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-success-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-success-border-subtle); + --bs-list-group-active-color: var(--bs-success-bg-subtle); + --bs-list-group-active-bg: var(--bs-success-text-emphasis); + --bs-list-group-active-border-color: var(--bs-success-text-emphasis); +} + +.list-group-item-info { + --bs-list-group-color: var(--bs-info-text-emphasis); + --bs-list-group-bg: var(--bs-info-bg-subtle); + --bs-list-group-border-color: var(--bs-info-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-info-border-subtle); + --bs-list-group-active-color: var(--bs-info-bg-subtle); + --bs-list-group-active-bg: var(--bs-info-text-emphasis); + --bs-list-group-active-border-color: var(--bs-info-text-emphasis); +} + +.list-group-item-warning { + --bs-list-group-color: var(--bs-warning-text-emphasis); + --bs-list-group-bg: var(--bs-warning-bg-subtle); + --bs-list-group-border-color: var(--bs-warning-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-warning-border-subtle); + --bs-list-group-active-color: var(--bs-warning-bg-subtle); + --bs-list-group-active-bg: var(--bs-warning-text-emphasis); + --bs-list-group-active-border-color: var(--bs-warning-text-emphasis); +} + +.list-group-item-danger { + --bs-list-group-color: var(--bs-danger-text-emphasis); + --bs-list-group-bg: var(--bs-danger-bg-subtle); + --bs-list-group-border-color: var(--bs-danger-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-danger-border-subtle); + --bs-list-group-active-color: var(--bs-danger-bg-subtle); + --bs-list-group-active-bg: var(--bs-danger-text-emphasis); + --bs-list-group-active-border-color: var(--bs-danger-text-emphasis); +} + +.list-group-item-light { + --bs-list-group-color: var(--bs-light-text-emphasis); + --bs-list-group-bg: var(--bs-light-bg-subtle); + --bs-list-group-border-color: var(--bs-light-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-light-border-subtle); + --bs-list-group-active-color: var(--bs-light-bg-subtle); + --bs-list-group-active-bg: var(--bs-light-text-emphasis); + --bs-list-group-active-border-color: var(--bs-light-text-emphasis); +} + +.list-group-item-dark { + --bs-list-group-color: var(--bs-dark-text-emphasis); + --bs-list-group-bg: var(--bs-dark-bg-subtle); + --bs-list-group-border-color: var(--bs-dark-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); + --bs-list-group-active-color: var(--bs-dark-bg-subtle); + --bs-list-group-active-bg: var(--bs-dark-text-emphasis); + --bs-list-group-active-border-color: var(--bs-dark-text-emphasis); +} + +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(52, 89, 230, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%); + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; + border: 0; + border-radius: 0.375rem; + opacity: var(--bs-btn-close-opacity); +} +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity); +} +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity); +} +.btn-close:disabled, .btn-close.disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: var(--bs-btn-close-disabled-opacity); +} + +.btn-close-white { + filter: var(--bs-btn-close-white-filter); +} + +[data-bs-theme=dark] .btn-close { + filter: var(--bs-btn-close-white-filter); +} + +.toast { + --bs-toast-zindex: 1090; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-border-width: var(--bs-border-width); + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: var(--bs-border-radius); + --bs-toast-box-shadow: var(--bs-box-shadow); + --bs-toast-header-color: var(--bs-primary-color); + --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-border-color: var(--bs-border-color-translucent); + width: var(--bs-toast-max-width); + max-width: 100%; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); + pointer-events: auto; + background-color: var(--bs-toast-bg); + background-clip: padding-box; + border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); + border-radius: var(--bs-toast-border-radius); +} +.toast.showing { + opacity: 0; +} +.toast:not(.show) { + display: none; +} + +.toast-container { + --bs-toast-zindex: 1090; + position: absolute; + z-index: var(--bs-toast-zindex); + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none; +} +.toast-container > :not(:last-child) { + margin-bottom: var(--bs-toast-spacing); +} + +.toast-header { + display: flex; + align-items: center; + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); + background-clip: padding-box; + border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color); + border-top-left-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); + border-top-right-radius: calc(var(--bs-toast-border-radius) - var(--bs-toast-border-width)); +} +.toast-header .btn-close { + margin-right: calc(-0.5 * var(--bs-toast-padding-x)); + margin-left: var(--bs-toast-padding-x); +} + +.toast-body { + padding: var(--bs-toast-padding-x); + word-wrap: break-word; +} + +.modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: ; + --bs-modal-bg: var(--bs-body-bg); + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: var(--bs-border-width); + --bs-modal-border-radius: var(--bs-border-radius-lg); + --bs-modal-box-shadow: var(--bs-box-shadow-sm); + --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width))); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: 0; + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: 0; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; +} + +.modal-dialog { + position: relative; + width: auto; + margin: var(--bs-modal-margin); + pointer-events: none; +} +.modal.fade .modal-dialog { + transition: transform 0.3s ease-out; + transform: translate(0, -50px); +} +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; + } +} +.modal.show .modal-dialog { + transform: none; +} +.modal.modal-static .modal-dialog { + transform: scale(1.02); +} + +.modal-dialog-scrollable { + height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden; +} +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--bs-modal-margin) * 2); +} + +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + color: var(--bs-modal-color); + pointer-events: auto; + background-color: var(--bs-modal-bg); + background-clip: padding-box; + border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); + border-radius: var(--bs-modal-border-radius); + box-shadow: var(--bs-modal-box-shadow); + outline: 0; +} + +.modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-backdrop-zindex); + width: 100vw; + height: 100vh; + background-color: var(--bs-backdrop-bg); +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: var(--bs-backdrop-opacity); +} + +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + justify-content: space-between; + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color); + border-top-left-radius: var(--bs-modal-inner-border-radius); + border-top-right-radius: var(--bs-modal-inner-border-radius); +} +.modal-header .btn-close { + padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); + margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto; +} + +.modal-title { + margin-bottom: 0; + line-height: var(--bs-modal-title-line-height); +} + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: var(--bs-modal-padding); +} + +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color); + border-bottom-right-radius: var(--bs-modal-inner-border-radius); + border-bottom-left-radius: var(--bs-modal-inner-border-radius); +} +.modal-footer > * { + margin: calc(var(--bs-modal-footer-gap) * 0.5); +} + +@media (min-width: 576px) { + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: var(--bs-box-shadow); + } + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto; + } + .modal-sm { + --bs-modal-width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + --bs-modal-width: 800px; + } +} +@media (min-width: 1200px) { + .modal-xl { + --bs-modal-width: 1140px; + } +} +.modal-fullscreen { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; +} +.modal-fullscreen .modal-content { + height: 100%; + border: 0; + border-radius: 0; +} +.modal-fullscreen .modal-header, +.modal-fullscreen .modal-footer { + border-radius: 0; +} +.modal-fullscreen .modal-body { + overflow-y: auto; +} + +@media (max-width: 575.98px) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-header, + .modal-fullscreen-sm-down .modal-footer { + border-radius: 0; + } + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 767.98px) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-md-down .modal-header, + .modal-fullscreen-md-down .modal-footer { + border-radius: 0; + } + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 991.98px) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-header, + .modal-fullscreen-lg-down .modal-footer { + border-radius: 0; + } + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1199.98px) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-header, + .modal-fullscreen-xl-down .modal-footer { + border-radius: 0; + } + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1399.98px) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-header, + .modal-fullscreen-xxl-down .modal-footer { + border-radius: 0; + } + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; + } +} +.tooltip { + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.875rem; + --bs-tooltip-color: var(--bs-body-bg); + --bs-tooltip-bg: var(--bs-emphasis-color); + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); + display: block; + margin: var(--bs-tooltip-margin); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-tooltip-font-size); + word-wrap: break-word; + opacity: 0; +} +.tooltip.show { + opacity: var(--bs-tooltip-opacity); +} +.tooltip .tooltip-arrow { + display: block; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height); +} +.tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { + bottom: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { + top: -1px; + border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-top-color: var(--bs-tooltip-bg); +} + +/* rtl:begin:ignore */ +.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { + left: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { + right: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-right-color: var(--bs-tooltip-bg); +} + +/* rtl:end:ignore */ +.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { + top: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { + bottom: -1px; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg); +} + +/* rtl:begin:ignore */ +.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { + right: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { + left: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg); +} + +/* rtl:end:ignore */ +.tooltip-inner { + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); + text-align: center; + background-color: var(--bs-tooltip-bg); + border-radius: var(--bs-tooltip-border-radius); +} + +.popover { + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.875rem; + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-border-width: var(--bs-border-width); + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: var(--bs-border-radius-lg); + --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width)); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 1rem; + --bs-popover-header-color: var(--bs-primary-color); + --bs-popover-header-bg: var(--bs-secondary-bg); + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); + display: block; + max-width: var(--bs-popover-max-width); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-popover-font-size); + word-wrap: break-word; + background-color: var(--bs-popover-bg); + background-clip: padding-box; + border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-radius: var(--bs-popover-border-radius); + box-shadow: var(--bs-popover-box-shadow); +} +.popover .popover-arrow { + display: block; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height); +} +.popover .popover-arrow::before, .popover .popover-arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; +} + +.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow { + bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); +} +.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { + border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before { + bottom: 0; + border-top-color: var(--bs-popover-arrow-border); +} +.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg); +} + +/* rtl:begin:ignore */ +.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow { + left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before { + left: 0; + border-right-color: var(--bs-popover-arrow-border); +} +.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg); +} + +/* rtl:end:ignore */ +.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { + top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); +} +.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { + border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); +} +.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before { + top: 0; + border-bottom-color: var(--bs-popover-arrow-border); +} +.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg); +} +.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--bs-popover-arrow-width); + margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); + content: ""; + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg); +} + +/* rtl:begin:ignore */ +.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow { + right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); +} +.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before { + right: 0; + border-left-color: var(--bs-popover-arrow-border); +} +.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg); +} + +/* rtl:end:ignore */ +.popover-header { + padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); + margin-bottom: 0; + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color); + border-top-left-radius: var(--bs-popover-inner-border-radius); + border-top-right-radius: var(--bs-popover-inner-border-radius); +} +.popover-header:empty { + display: none; +} + +.popover-body { + padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color); +} + +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner::after { + display: block; + clear: both; + content: ""; +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: transform 0.6s ease-in-out; +} +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; + } +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + transform: translateX(-100%); +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; +} +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; +} +.carousel-fade .active.carousel-item-start, +.carousel-fade .active.carousel-item-end { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-start, + .carousel-fade .active.carousel-item-end { + transition: none; + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: none; + border: 0; + opacity: 0.5; + transition: opacity 0.15s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-control-prev, + .carousel-control-next { + transition: none; + } +} +.carousel-control-prev:hover, .carousel-control-prev:focus, +.carousel-control-next:hover, +.carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} + +/* rtl:options: { + "autoRename": true, + "stringMap":[ { + "name" : "prev-next", + "search" : "prev", + "replace" : "next" + } ] +} */ +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15%; +} +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; + transition: opacity 0.6s ease; +} +@media (prefers-reduced-motion: reduce) { + .carousel-indicators [data-bs-target] { + transition: none; + } +} +.carousel-indicators .active { + opacity: 1; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 1.25rem; + left: 15%; + padding-top: 1.25rem; + padding-bottom: 1.25rem; + color: #fff; + text-align: center; +} + +.carousel-dark .carousel-control-prev-icon, +.carousel-dark .carousel-control-next-icon { + filter: invert(1) grayscale(100); +} +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; +} +.carousel-dark .carousel-caption { + color: #000; +} + +[data-bs-theme=dark] .carousel .carousel-control-prev-icon, +[data-bs-theme=dark] .carousel .carousel-control-next-icon, [data-bs-theme=dark].carousel .carousel-control-prev-icon, +[data-bs-theme=dark].carousel .carousel-control-next-icon { + filter: invert(1) grayscale(100); +} +[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target], [data-bs-theme=dark].carousel .carousel-indicators [data-bs-target] { + background-color: #000; +} +[data-bs-theme=dark] .carousel .carousel-caption, [data-bs-theme=dark].carousel .carousel-caption { + color: #000; +} + +.spinner-grow, +.spinner-border { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); +} + +@keyframes spinner-border { + to { + transform: rotate(360deg) /* rtl:ignore */; + } +} +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent; +} + +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em; +} + +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0; +} + +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; +} + +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + --bs-spinner-animation-speed: 1.5s; + } +} +.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm { + --bs-offcanvas-zindex: 1045; + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: var(--bs-body-color); + --bs-offcanvas-bg: var(--bs-body-bg); + --bs-offcanvas-border-width: var(--bs-border-width); + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm); + --bs-offcanvas-transition: transform 0.3s ease-in-out; + --bs-offcanvas-title-line-height: 1.5; +} + +@media (max-width: 575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 575.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-sm { + transition: none; + } +} +@media (max-width: 575.98px) { + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) { + transform: none; + } + .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show { + visibility: visible; + } +} +@media (min-width: 576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-sm .offcanvas-header { + display: none; + } + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 767.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-md { + transition: none; + } +} +@media (max-width: 767.98px) { + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) { + transform: none; + } + .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show { + visibility: visible; + } +} +@media (min-width: 768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-md .offcanvas-header { + display: none; + } + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 991.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-lg { + transition: none; + } +} +@media (max-width: 991.98px) { + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) { + transform: none; + } + .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show { + visibility: visible; + } +} +@media (min-width: 992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-lg .offcanvas-header { + display: none; + } + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1199.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xl { + transition: none; + } +} +@media (max-width: 1199.98px) { + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) { + transform: none; + } + .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show { + visibility: visible; + } +} +@media (min-width: 1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xl .offcanvas-header { + display: none; + } + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); + } +} +@media (max-width: 1399.98px) and (prefers-reduced-motion: reduce) { + .offcanvas-xxl { + transition: none; + } +} +@media (max-width: 1399.98px) { + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) { + transform: none; + } + .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show { + visibility: visible; + } +} +@media (min-width: 1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xxl .offcanvas-header { + display: none; + } + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +.offcanvas { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + box-shadow: var(--bs-offcanvas-box-shadow); + transition: var(--bs-offcanvas-transition); +} +@media (prefers-reduced-motion: reduce) { + .offcanvas { + transition: none; + } +} +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); +} +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); +} +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); +} +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); +} +.offcanvas.showing, .offcanvas.show:not(.hiding) { + transform: none; +} +.offcanvas.showing, .offcanvas.hiding, .offcanvas.show { + visibility: visible; +} + +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} +.offcanvas-backdrop.fade { + opacity: 0; +} +.offcanvas-backdrop.show { + opacity: 0.5; +} + +.offcanvas-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); +} +.offcanvas-header .btn-close { + padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); + margin-top: calc(-0.5 * var(--bs-offcanvas-padding-y)); + margin-right: calc(-0.5 * var(--bs-offcanvas-padding-x)); + margin-bottom: calc(-0.5 * var(--bs-offcanvas-padding-y)); +} + +.offcanvas-title { + margin-bottom: 0; + line-height: var(--bs-offcanvas-title-line-height); +} + +.offcanvas-body { + flex-grow: 1; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); + overflow-y: auto; +} + +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: 0.5; +} +.placeholder.btn::before { + display: inline-block; + content: ""; +} + +.placeholder-xs { + min-height: 0.6em; +} + +.placeholder-sm { + min-height: 0.8em; +} + +.placeholder-lg { + min-height: 1.2em; +} + +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite; +} + +@keyframes placeholder-glow { + 50% { + opacity: 0.2; + } +} +.placeholder-wave { + -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + -webkit-mask-size: 200% 100%; + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite; +} + +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; + } +} +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.text-bg-primary { + color: #fff !important; + background-color: RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-secondary { + color: #000 !important; + background-color: RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-success { + color: #fff !important; + background-color: RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-info { + color: #fff !important; + background-color: RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-warning { + color: #fff !important; + background-color: RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-danger { + color: #fff !important; + background-color: RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-light { + color: #000 !important; + background-color: RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-dark { + color: #fff !important; + background-color: RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.link-primary { + color: RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-primary:hover, .link-primary:focus { + color: RGBA(42, 71, 184, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(42, 71, 184, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(42, 71, 184, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-secondary { + color: RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-secondary:hover, .link-secondary:focus { + color: RGBA(255, 255, 255, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(255, 255, 255, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(255, 255, 255, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-success { + color: RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-success:hover, .link-success:focus { + color: RGBA(38, 143, 102, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(38, 143, 102, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(38, 143, 102, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-info { + color: RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-info:hover, .link-info:focus { + color: RGBA(32, 98, 145, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(32, 98, 145, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(32, 98, 145, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-warning { + color: RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-warning:hover, .link-warning:focus { + color: RGBA(195, 151, 78, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(195, 151, 78, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(195, 151, 78, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-danger { + color: RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-danger:hover, .link-danger:focus { + color: RGBA(174, 33, 37, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(174, 33, 37, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(174, 33, 37, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-light { + color: RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-light:hover, .link-light:focus { + color: RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-dark { + color: RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-dark:hover, .link-dark:focus { + color: RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-body-emphasis { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-body-emphasis:hover, .link-body-emphasis:focus { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important; + -webkit-text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; +} + +.focus-ring:focus { + outline: 0; + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color); +} + +.icon-link { + display: inline-flex; + gap: 0.375rem; + align-items: center; + -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); + text-underline-offset: 0.25em; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; +} +.icon-link > .bi { + flex-shrink: 0; + width: 1em; + height: 1em; + fill: currentcolor; + transition: 0.2s ease-in-out transform; +} +@media (prefers-reduced-motion: reduce) { + .icon-link > .bi { + transition: none; + } +} + +.icon-link-hover:hover > .bi, .icon-link-hover:focus-visible > .bi { + transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); +} + +.ratio { + position: relative; + width: 100%; +} +.ratio::before { + display: block; + padding-top: var(--bs-aspect-ratio); + content: ""; +} +.ratio > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.ratio-1x1 { + --bs-aspect-ratio: 100%; +} + +.ratio-4x3 { + --bs-aspect-ratio: 75%; +} + +.ratio-16x9 { + --bs-aspect-ratio: 56.25%; +} + +.ratio-21x9 { + --bs-aspect-ratio: 42.8571428571%; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +.sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; +} + +.sticky-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; +} + +@media (min-width: 576px) { + .sticky-sm-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-sm-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 768px) { + .sticky-md-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-md-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 992px) { + .sticky-lg-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-lg-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1200px) { + .sticky-xl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1400px) { + .sticky-xxl-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xxl-bottom { + position: -webkit-sticky; + position: sticky; + bottom: 0; + z-index: 1020; + } +} +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; +} + +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch; +} + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} +.visually-hidden:not(caption), +.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) { + position: absolute !important; +} + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + content: ""; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.vr { + display: inline-block; + align-self: stretch; + width: var(--bs-border-width); + min-height: 1em; + background-color: currentcolor; + opacity: 0.25; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.float-start { + float: left !important; +} + +.float-end { + float: right !important; +} + +.float-none { + float: none !important; +} + +.object-fit-contain { + -o-object-fit: contain !important; + object-fit: contain !important; +} + +.object-fit-cover { + -o-object-fit: cover !important; + object-fit: cover !important; +} + +.object-fit-fill { + -o-object-fit: fill !important; + object-fit: fill !important; +} + +.object-fit-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; +} + +.object-fit-none { + -o-object-fit: none !important; + object-fit: none !important; +} + +.opacity-0 { + opacity: 0 !important; +} + +.opacity-25 { + opacity: 0.25 !important; +} + +.opacity-50 { + opacity: 0.5 !important; +} + +.opacity-75 { + opacity: 0.75 !important; +} + +.opacity-100 { + opacity: 1 !important; +} + +.overflow-auto { + overflow: auto !important; +} + +.overflow-hidden { + overflow: hidden !important; +} + +.overflow-visible { + overflow: visible !important; +} + +.overflow-scroll { + overflow: scroll !important; +} + +.overflow-x-auto { + overflow-x: auto !important; +} + +.overflow-x-hidden { + overflow-x: hidden !important; +} + +.overflow-x-visible { + overflow-x: visible !important; +} + +.overflow-x-scroll { + overflow-x: scroll !important; +} + +.overflow-y-auto { + overflow-y: auto !important; +} + +.overflow-y-hidden { + overflow-y: hidden !important; +} + +.overflow-y-visible { + overflow-y: visible !important; +} + +.overflow-y-scroll { + overflow-y: scroll !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-grid { + display: grid !important; +} + +.d-inline-grid { + display: inline-grid !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: flex !important; +} + +.d-inline-flex { + display: inline-flex !important; +} + +.d-none { + display: none !important; +} + +.shadow { + box-shadow: var(--bs-box-shadow) !important; +} + +.shadow-sm { + box-shadow: var(--bs-box-shadow-sm) !important; +} + +.shadow-lg { + box-shadow: var(--bs-box-shadow-lg) !important; +} + +.shadow-none { + box-shadow: none !important; +} + +.focus-ring-primary { + --bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-secondary { + --bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-success { + --bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-info { + --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-warning { + --bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-danger { + --bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-light { + --bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-dark { + --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity)); +} + +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} + +.top-0 { + top: 0 !important; +} + +.top-50 { + top: 50% !important; +} + +.top-100 { + top: 100% !important; +} + +.bottom-0 { + bottom: 0 !important; +} + +.bottom-50 { + bottom: 50% !important; +} + +.bottom-100 { + bottom: 100% !important; +} + +.start-0 { + left: 0 !important; +} + +.start-50 { + left: 50% !important; +} + +.start-100 { + left: 100% !important; +} + +.end-0 { + right: 0 !important; +} + +.end-50 { + right: 50% !important; +} + +.end-100 { + right: 100% !important; +} + +.translate-middle { + transform: translate(-50%, -50%) !important; +} + +.translate-middle-x { + transform: translateX(-50%) !important; +} + +.translate-middle-y { + transform: translateY(-50%) !important; +} + +.border { + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top { + border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-end { + border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-end-0 { + border-right: 0 !important; +} + +.border-bottom { + border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-start { + border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-start-0 { + border-left: 0 !important; +} + +.border-primary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important; +} + +.border-secondary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important; +} + +.border-success { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important; +} + +.border-info { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important; +} + +.border-warning { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important; +} + +.border-danger { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; +} + +.border-light { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important; +} + +.border-dark { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important; +} + +.border-black { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important; +} + +.border-white { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important; +} + +.border-primary-subtle { + border-color: var(--bs-primary-border-subtle) !important; +} + +.border-secondary-subtle { + border-color: var(--bs-secondary-border-subtle) !important; +} + +.border-success-subtle { + border-color: var(--bs-success-border-subtle) !important; +} + +.border-info-subtle { + border-color: var(--bs-info-border-subtle) !important; +} + +.border-warning-subtle { + border-color: var(--bs-warning-border-subtle) !important; +} + +.border-danger-subtle { + border-color: var(--bs-danger-border-subtle) !important; +} + +.border-light-subtle { + border-color: var(--bs-light-border-subtle) !important; +} + +.border-dark-subtle { + border-color: var(--bs-dark-border-subtle) !important; +} + +.border-1 { + border-width: 1px !important; +} + +.border-2 { + border-width: 2px !important; +} + +.border-3 { + border-width: 3px !important; +} + +.border-4 { + border-width: 4px !important; +} + +.border-5 { + border-width: 5px !important; +} + +.border-opacity-10 { + --bs-border-opacity: 0.1; +} + +.border-opacity-25 { + --bs-border-opacity: 0.25; +} + +.border-opacity-50 { + --bs-border-opacity: 0.5; +} + +.border-opacity-75 { + --bs-border-opacity: 0.75; +} + +.border-opacity-100 { + --bs-border-opacity: 1; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.w-auto { + width: auto !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.vw-100 { + width: 100vw !important; +} + +.min-vw-100 { + min-width: 100vw !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.h-auto { + height: auto !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.vh-100 { + height: 100vh !important; +} + +.min-vh-100 { + min-height: 100vh !important; +} + +.flex-fill { + flex: 1 1 auto !important; +} + +.flex-row { + flex-direction: row !important; +} + +.flex-column { + flex-direction: column !important; +} + +.flex-row-reverse { + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + flex-direction: column-reverse !important; +} + +.flex-grow-0 { + flex-grow: 0 !important; +} + +.flex-grow-1 { + flex-grow: 1 !important; +} + +.flex-shrink-0 { + flex-shrink: 0 !important; +} + +.flex-shrink-1 { + flex-shrink: 1 !important; +} + +.flex-wrap { + flex-wrap: wrap !important; +} + +.flex-nowrap { + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + justify-content: flex-start !important; +} + +.justify-content-end { + justify-content: flex-end !important; +} + +.justify-content-center { + justify-content: center !important; +} + +.justify-content-between { + justify-content: space-between !important; +} + +.justify-content-around { + justify-content: space-around !important; +} + +.justify-content-evenly { + justify-content: space-evenly !important; +} + +.align-items-start { + align-items: flex-start !important; +} + +.align-items-end { + align-items: flex-end !important; +} + +.align-items-center { + align-items: center !important; +} + +.align-items-baseline { + align-items: baseline !important; +} + +.align-items-stretch { + align-items: stretch !important; +} + +.align-content-start { + align-content: flex-start !important; +} + +.align-content-end { + align-content: flex-end !important; +} + +.align-content-center { + align-content: center !important; +} + +.align-content-between { + align-content: space-between !important; +} + +.align-content-around { + align-content: space-around !important; +} + +.align-content-stretch { + align-content: stretch !important; +} + +.align-self-auto { + align-self: auto !important; +} + +.align-self-start { + align-self: flex-start !important; +} + +.align-self-end { + align-self: flex-end !important; +} + +.align-self-center { + align-self: center !important; +} + +.align-self-baseline { + align-self: baseline !important; +} + +.align-self-stretch { + align-self: stretch !important; +} + +.order-first { + order: -1 !important; +} + +.order-0 { + order: 0 !important; +} + +.order-1 { + order: 1 !important; +} + +.order-2 { + order: 2 !important; +} + +.order-3 { + order: 3 !important; +} + +.order-4 { + order: 4 !important; +} + +.order-5 { + order: 5 !important; +} + +.order-last { + order: 6 !important; +} + +.m-0 { + margin: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; +} + +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; +} + +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; +} + +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; +} + +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; +} + +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} + +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} + +.mt-0 { + margin-top: 0 !important; +} + +.mt-1 { + margin-top: 0.25rem !important; +} + +.mt-2 { + margin-top: 0.5rem !important; +} + +.mt-3 { + margin-top: 1rem !important; +} + +.mt-4 { + margin-top: 1.5rem !important; +} + +.mt-5 { + margin-top: 3rem !important; +} + +.mt-auto { + margin-top: auto !important; +} + +.me-0 { + margin-right: 0 !important; +} + +.me-1 { + margin-right: 0.25rem !important; +} + +.me-2 { + margin-right: 0.5rem !important; +} + +.me-3 { + margin-right: 1rem !important; +} + +.me-4 { + margin-right: 1.5rem !important; +} + +.me-5 { + margin-right: 3rem !important; +} + +.me-auto { + margin-right: auto !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.mb-1 { + margin-bottom: 0.25rem !important; +} + +.mb-2 { + margin-bottom: 0.5rem !important; +} + +.mb-3 { + margin-bottom: 1rem !important; +} + +.mb-4 { + margin-bottom: 1.5rem !important; +} + +.mb-5 { + margin-bottom: 3rem !important; +} + +.mb-auto { + margin-bottom: auto !important; +} + +.ms-0 { + margin-left: 0 !important; +} + +.ms-1 { + margin-left: 0.25rem !important; +} + +.ms-2 { + margin-left: 0.5rem !important; +} + +.ms-3 { + margin-left: 1rem !important; +} + +.ms-4 { + margin-left: 1.5rem !important; +} + +.ms-5 { + margin-left: 3rem !important; +} + +.ms-auto { + margin-left: auto !important; +} + +.p-0 { + padding: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} + +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; +} + +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; +} + +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; +} + +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; +} + +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} + +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} + +.pt-0 { + padding-top: 0 !important; +} + +.pt-1 { + padding-top: 0.25rem !important; +} + +.pt-2 { + padding-top: 0.5rem !important; +} + +.pt-3 { + padding-top: 1rem !important; +} + +.pt-4 { + padding-top: 1.5rem !important; +} + +.pt-5 { + padding-top: 3rem !important; +} + +.pe-0 { + padding-right: 0 !important; +} + +.pe-1 { + padding-right: 0.25rem !important; +} + +.pe-2 { + padding-right: 0.5rem !important; +} + +.pe-3 { + padding-right: 1rem !important; +} + +.pe-4 { + padding-right: 1.5rem !important; +} + +.pe-5 { + padding-right: 3rem !important; +} + +.pb-0 { + padding-bottom: 0 !important; +} + +.pb-1 { + padding-bottom: 0.25rem !important; +} + +.pb-2 { + padding-bottom: 0.5rem !important; +} + +.pb-3 { + padding-bottom: 1rem !important; +} + +.pb-4 { + padding-bottom: 1.5rem !important; +} + +.pb-5 { + padding-bottom: 3rem !important; +} + +.ps-0 { + padding-left: 0 !important; +} + +.ps-1 { + padding-left: 0.25rem !important; +} + +.ps-2 { + padding-left: 0.5rem !important; +} + +.ps-3 { + padding-left: 1rem !important; +} + +.ps-4 { + padding-left: 1.5rem !important; +} + +.ps-5 { + padding-left: 3rem !important; +} + +.gap-0 { + gap: 0 !important; +} + +.gap-1 { + gap: 0.25rem !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +.gap-3 { + gap: 1rem !important; +} + +.gap-4 { + gap: 1.5rem !important; +} + +.gap-5 { + gap: 3rem !important; +} + +.row-gap-0 { + row-gap: 0 !important; +} + +.row-gap-1 { + row-gap: 0.25rem !important; +} + +.row-gap-2 { + row-gap: 0.5rem !important; +} + +.row-gap-3 { + row-gap: 1rem !important; +} + +.row-gap-4 { + row-gap: 1.5rem !important; +} + +.row-gap-5 { + row-gap: 3rem !important; +} + +.column-gap-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; +} + +.column-gap-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; +} + +.column-gap-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; +} + +.column-gap-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; +} + +.column-gap-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; +} + +.column-gap-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; +} + +.font-monospace { + font-family: var(--bs-font-monospace) !important; +} + +.fs-1 { + font-size: calc(1.375rem + 1.5vw) !important; +} + +.fs-2 { + font-size: calc(1.325rem + 0.9vw) !important; +} + +.fs-3 { + font-size: calc(1.3rem + 0.6vw) !important; +} + +.fs-4 { + font-size: calc(1.275rem + 0.3vw) !important; +} + +.fs-5 { + font-size: 1.25rem !important; +} + +.fs-6 { + font-size: 1rem !important; +} + +.fst-italic { + font-style: italic !important; +} + +.fst-normal { + font-style: normal !important; +} + +.fw-lighter { + font-weight: lighter !important; +} + +.fw-light { + font-weight: 300 !important; +} + +.fw-normal { + font-weight: 400 !important; +} + +.fw-medium { + font-weight: 500 !important; +} + +.fw-semibold { + font-weight: 600 !important; +} + +.fw-bold { + font-weight: 700 !important; +} + +.fw-bolder { + font-weight: bolder !important; +} + +.lh-1 { + line-height: 1 !important; +} + +.lh-sm { + line-height: 1.25 !important; +} + +.lh-base { + line-height: 1.5 !important; +} + +.lh-lg { + line-height: 2 !important; +} + +.text-start { + text-align: left !important; +} + +.text-end { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +.text-decoration-none { + text-decoration: none !important; +} + +.text-decoration-underline { + text-decoration: underline !important; +} + +.text-decoration-line-through { + text-decoration: line-through !important; +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.text-wrap { + white-space: normal !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +/* rtl:begin:remove */ +.text-break { + word-wrap: break-word !important; + word-break: break-word !important; +} + +/* rtl:end:remove */ +.text-primary { + --bs-text-opacity: 1; + color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important; +} + +.text-secondary { + --bs-text-opacity: 1; + color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important; +} + +.text-success { + --bs-text-opacity: 1; + color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important; +} + +.text-info { + --bs-text-opacity: 1; + color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important; +} + +.text-warning { + --bs-text-opacity: 1; + color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important; +} + +.text-danger { + --bs-text-opacity: 1; + color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important; +} + +.text-light { + --bs-text-opacity: 1; + color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important; +} + +.text-dark { + --bs-text-opacity: 1; + color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important; +} + +.text-black { + --bs-text-opacity: 1; + color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important; +} + +.text-white { + --bs-text-opacity: 1; + color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important; +} + +.text-body { + --bs-text-opacity: 1; + color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important; +} + +.text-muted { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} + +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, 0.5) !important; +} + +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, 0.5) !important; +} + +.text-body-secondary { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} + +.text-body-tertiary { + --bs-text-opacity: 1; + color: var(--bs-tertiary-color) !important; +} + +.text-body-emphasis { + --bs-text-opacity: 1; + color: var(--bs-emphasis-color) !important; +} + +.text-reset { + --bs-text-opacity: 1; + color: inherit !important; +} + +.text-opacity-25 { + --bs-text-opacity: 0.25; +} + +.text-opacity-50 { + --bs-text-opacity: 0.5; +} + +.text-opacity-75 { + --bs-text-opacity: 0.75; +} + +.text-opacity-100 { + --bs-text-opacity: 1; +} + +.text-primary-emphasis { + color: var(--bs-primary-text-emphasis) !important; +} + +.text-secondary-emphasis { + color: var(--bs-secondary-text-emphasis) !important; +} + +.text-success-emphasis { + color: var(--bs-success-text-emphasis) !important; +} + +.text-info-emphasis { + color: var(--bs-info-text-emphasis) !important; +} + +.text-warning-emphasis { + color: var(--bs-warning-text-emphasis) !important; +} + +.text-danger-emphasis { + color: var(--bs-danger-text-emphasis) !important; +} + +.text-light-emphasis { + color: var(--bs-light-text-emphasis) !important; +} + +.text-dark-emphasis { + color: var(--bs-dark-text-emphasis) !important; +} + +.link-opacity-10 { + --bs-link-opacity: 0.1; +} + +.link-opacity-10-hover:hover { + --bs-link-opacity: 0.1; +} + +.link-opacity-25 { + --bs-link-opacity: 0.25; +} + +.link-opacity-25-hover:hover { + --bs-link-opacity: 0.25; +} + +.link-opacity-50 { + --bs-link-opacity: 0.5; +} + +.link-opacity-50-hover:hover { + --bs-link-opacity: 0.5; +} + +.link-opacity-75 { + --bs-link-opacity: 0.75; +} + +.link-opacity-75-hover:hover { + --bs-link-opacity: 0.75; +} + +.link-opacity-100 { + --bs-link-opacity: 1; +} + +.link-opacity-100-hover:hover { + --bs-link-opacity: 1; +} + +.link-offset-1 { + text-underline-offset: 0.125em !important; +} + +.link-offset-1-hover:hover { + text-underline-offset: 0.125em !important; +} + +.link-offset-2 { + text-underline-offset: 0.25em !important; +} + +.link-offset-2-hover:hover { + text-underline-offset: 0.25em !important; +} + +.link-offset-3 { + text-underline-offset: 0.375em !important; +} + +.link-offset-3-hover:hover { + text-underline-offset: 0.375em !important; +} + +.link-underline-primary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-secondary { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-success { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-info { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-warning { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-danger { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-light { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-dark { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; + text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline { + --bs-link-underline-opacity: 1; + -webkit-text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; +} + +.link-underline-opacity-0 { + --bs-link-underline-opacity: 0; +} + +.link-underline-opacity-0-hover:hover { + --bs-link-underline-opacity: 0; +} + +.link-underline-opacity-10 { + --bs-link-underline-opacity: 0.1; +} + +.link-underline-opacity-10-hover:hover { + --bs-link-underline-opacity: 0.1; +} + +.link-underline-opacity-25 { + --bs-link-underline-opacity: 0.25; +} + +.link-underline-opacity-25-hover:hover { + --bs-link-underline-opacity: 0.25; +} + +.link-underline-opacity-50 { + --bs-link-underline-opacity: 0.5; +} + +.link-underline-opacity-50-hover:hover { + --bs-link-underline-opacity: 0.5; +} + +.link-underline-opacity-75 { + --bs-link-underline-opacity: 0.75; +} + +.link-underline-opacity-75-hover:hover { + --bs-link-underline-opacity: 0.75; +} + +.link-underline-opacity-100 { + --bs-link-underline-opacity: 1; +} + +.link-underline-opacity-100-hover:hover { + --bs-link-underline-opacity: 1; +} + +.bg-primary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-info { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-warning { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-danger { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-light { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-dark { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-black { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-white { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-body { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-transparent { + --bs-bg-opacity: 1; + background-color: transparent !important; +} + +.bg-body-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-body-tertiary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-opacity-10 { + --bs-bg-opacity: 0.1; +} + +.bg-opacity-25 { + --bs-bg-opacity: 0.25; +} + +.bg-opacity-50 { + --bs-bg-opacity: 0.5; +} + +.bg-opacity-75 { + --bs-bg-opacity: 0.75; +} + +.bg-opacity-100 { + --bs-bg-opacity: 1; +} + +.bg-primary-subtle { + background-color: var(--bs-primary-bg-subtle) !important; +} + +.bg-secondary-subtle { + background-color: var(--bs-secondary-bg-subtle) !important; +} + +.bg-success-subtle { + background-color: var(--bs-success-bg-subtle) !important; +} + +.bg-info-subtle { + background-color: var(--bs-info-bg-subtle) !important; +} + +.bg-warning-subtle { + background-color: var(--bs-warning-bg-subtle) !important; +} + +.bg-danger-subtle { + background-color: var(--bs-danger-bg-subtle) !important; +} + +.bg-light-subtle { + background-color: var(--bs-light-bg-subtle) !important; +} + +.bg-dark-subtle { + background-color: var(--bs-dark-bg-subtle) !important; +} + +.bg-gradient { + background-image: var(--bs-gradient) !important; +} + +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important; +} + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + user-select: auto !important; +} + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; +} + +.pe-none { + pointer-events: none !important; +} + +.pe-auto { + pointer-events: auto !important; +} + +.rounded { + border-radius: var(--bs-border-radius) !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.rounded-1 { + border-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-2 { + border-radius: var(--bs-border-radius) !important; +} + +.rounded-3 { + border-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-5 { + border-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-pill { + border-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-top { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} + +.rounded-top-0 { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} + +.rounded-top-1 { + border-top-left-radius: var(--bs-border-radius-sm) !important; + border-top-right-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-top-2 { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} + +.rounded-top-3 { + border-top-left-radius: var(--bs-border-radius-lg) !important; + border-top-right-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-top-4 { + border-top-left-radius: var(--bs-border-radius-xl) !important; + border-top-right-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-top-5 { + border-top-left-radius: var(--bs-border-radius-xxl) !important; + border-top-right-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-top-circle { + border-top-left-radius: 50% !important; + border-top-right-radius: 50% !important; +} + +.rounded-top-pill { + border-top-left-radius: var(--bs-border-radius-pill) !important; + border-top-right-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-end { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} + +.rounded-end-0 { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.rounded-end-1 { + border-top-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-right-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-end-2 { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} + +.rounded-end-3 { + border-top-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-right-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-end-4 { + border-top-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-right-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-end-5 { + border-top-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-end-circle { + border-top-right-radius: 50% !important; + border-bottom-right-radius: 50% !important; +} + +.rounded-end-pill { + border-top-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-right-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-bottom { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} + +.rounded-bottom-0 { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +.rounded-bottom-1 { + border-bottom-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-left-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-bottom-2 { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} + +.rounded-bottom-3 { + border-bottom-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-left-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-bottom-4 { + border-bottom-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-left-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-bottom-5 { + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-bottom-circle { + border-bottom-right-radius: 50% !important; + border-bottom-left-radius: 50% !important; +} + +.rounded-bottom-pill { + border-bottom-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-left-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-start { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} + +.rounded-start-0 { + border-bottom-left-radius: 0 !important; + border-top-left-radius: 0 !important; +} + +.rounded-start-1 { + border-bottom-left-radius: var(--bs-border-radius-sm) !important; + border-top-left-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-start-2 { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} + +.rounded-start-3 { + border-bottom-left-radius: var(--bs-border-radius-lg) !important; + border-top-left-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-start-4 { + border-bottom-left-radius: var(--bs-border-radius-xl) !important; + border-top-left-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-start-5 { + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; + border-top-left-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-start-circle { + border-bottom-left-radius: 50% !important; + border-top-left-radius: 50% !important; +} + +.rounded-start-pill { + border-bottom-left-radius: var(--bs-border-radius-pill) !important; + border-top-left-radius: var(--bs-border-radius-pill) !important; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +.z-n1 { + z-index: -1 !important; +} + +.z-0 { + z-index: 0 !important; +} + +.z-1 { + z-index: 1 !important; +} + +.z-2 { + z-index: 2 !important; +} + +.z-3 { + z-index: 3 !important; +} + +@media (min-width: 576px) { + .float-sm-start { + float: left !important; + } + .float-sm-end { + float: right !important; + } + .float-sm-none { + float: none !important; + } + .object-fit-sm-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-sm-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-sm-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-sm-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-sm-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-grid { + display: grid !important; + } + .d-sm-inline-grid { + display: inline-grid !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: flex !important; + } + .d-sm-inline-flex { + display: inline-flex !important; + } + .d-sm-none { + display: none !important; + } + .flex-sm-fill { + flex: 1 1 auto !important; + } + .flex-sm-row { + flex-direction: row !important; + } + .flex-sm-column { + flex-direction: column !important; + } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; + } + .flex-sm-grow-0 { + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + flex-shrink: 1 !important; + } + .flex-sm-wrap { + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + justify-content: flex-start !important; + } + .justify-content-sm-end { + justify-content: flex-end !important; + } + .justify-content-sm-center { + justify-content: center !important; + } + .justify-content-sm-between { + justify-content: space-between !important; + } + .justify-content-sm-around { + justify-content: space-around !important; + } + .justify-content-sm-evenly { + justify-content: space-evenly !important; + } + .align-items-sm-start { + align-items: flex-start !important; + } + .align-items-sm-end { + align-items: flex-end !important; + } + .align-items-sm-center { + align-items: center !important; + } + .align-items-sm-baseline { + align-items: baseline !important; + } + .align-items-sm-stretch { + align-items: stretch !important; + } + .align-content-sm-start { + align-content: flex-start !important; + } + .align-content-sm-end { + align-content: flex-end !important; + } + .align-content-sm-center { + align-content: center !important; + } + .align-content-sm-between { + align-content: space-between !important; + } + .align-content-sm-around { + align-content: space-around !important; + } + .align-content-sm-stretch { + align-content: stretch !important; + } + .align-self-sm-auto { + align-self: auto !important; + } + .align-self-sm-start { + align-self: flex-start !important; + } + .align-self-sm-end { + align-self: flex-end !important; + } + .align-self-sm-center { + align-self: center !important; + } + .align-self-sm-baseline { + align-self: baseline !important; + } + .align-self-sm-stretch { + align-self: stretch !important; + } + .order-sm-first { + order: -1 !important; + } + .order-sm-0 { + order: 0 !important; + } + .order-sm-1 { + order: 1 !important; + } + .order-sm-2 { + order: 2 !important; + } + .order-sm-3 { + order: 3 !important; + } + .order-sm-4 { + order: 4 !important; + } + .order-sm-5 { + order: 5 !important; + } + .order-sm-last { + order: 6 !important; + } + .m-sm-0 { + margin: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-sm-0 { + margin-top: 0 !important; + } + .mt-sm-1 { + margin-top: 0.25rem !important; + } + .mt-sm-2 { + margin-top: 0.5rem !important; + } + .mt-sm-3 { + margin-top: 1rem !important; + } + .mt-sm-4 { + margin-top: 1.5rem !important; + } + .mt-sm-5 { + margin-top: 3rem !important; + } + .mt-sm-auto { + margin-top: auto !important; + } + .me-sm-0 { + margin-right: 0 !important; + } + .me-sm-1 { + margin-right: 0.25rem !important; + } + .me-sm-2 { + margin-right: 0.5rem !important; + } + .me-sm-3 { + margin-right: 1rem !important; + } + .me-sm-4 { + margin-right: 1.5rem !important; + } + .me-sm-5 { + margin-right: 3rem !important; + } + .me-sm-auto { + margin-right: auto !important; + } + .mb-sm-0 { + margin-bottom: 0 !important; + } + .mb-sm-1 { + margin-bottom: 0.25rem !important; + } + .mb-sm-2 { + margin-bottom: 0.5rem !important; + } + .mb-sm-3 { + margin-bottom: 1rem !important; + } + .mb-sm-4 { + margin-bottom: 1.5rem !important; + } + .mb-sm-5 { + margin-bottom: 3rem !important; + } + .mb-sm-auto { + margin-bottom: auto !important; + } + .ms-sm-0 { + margin-left: 0 !important; + } + .ms-sm-1 { + margin-left: 0.25rem !important; + } + .ms-sm-2 { + margin-left: 0.5rem !important; + } + .ms-sm-3 { + margin-left: 1rem !important; + } + .ms-sm-4 { + margin-left: 1.5rem !important; + } + .ms-sm-5 { + margin-left: 3rem !important; + } + .ms-sm-auto { + margin-left: auto !important; + } + .p-sm-0 { + padding: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-sm-0 { + padding-top: 0 !important; + } + .pt-sm-1 { + padding-top: 0.25rem !important; + } + .pt-sm-2 { + padding-top: 0.5rem !important; + } + .pt-sm-3 { + padding-top: 1rem !important; + } + .pt-sm-4 { + padding-top: 1.5rem !important; + } + .pt-sm-5 { + padding-top: 3rem !important; + } + .pe-sm-0 { + padding-right: 0 !important; + } + .pe-sm-1 { + padding-right: 0.25rem !important; + } + .pe-sm-2 { + padding-right: 0.5rem !important; + } + .pe-sm-3 { + padding-right: 1rem !important; + } + .pe-sm-4 { + padding-right: 1.5rem !important; + } + .pe-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-0 { + padding-bottom: 0 !important; + } + .pb-sm-1 { + padding-bottom: 0.25rem !important; + } + .pb-sm-2 { + padding-bottom: 0.5rem !important; + } + .pb-sm-3 { + padding-bottom: 1rem !important; + } + .pb-sm-4 { + padding-bottom: 1.5rem !important; + } + .pb-sm-5 { + padding-bottom: 3rem !important; + } + .ps-sm-0 { + padding-left: 0 !important; + } + .ps-sm-1 { + padding-left: 0.25rem !important; + } + .ps-sm-2 { + padding-left: 0.5rem !important; + } + .ps-sm-3 { + padding-left: 1rem !important; + } + .ps-sm-4 { + padding-left: 1.5rem !important; + } + .ps-sm-5 { + padding-left: 3rem !important; + } + .gap-sm-0 { + gap: 0 !important; + } + .gap-sm-1 { + gap: 0.25rem !important; + } + .gap-sm-2 { + gap: 0.5rem !important; + } + .gap-sm-3 { + gap: 1rem !important; + } + .gap-sm-4 { + gap: 1.5rem !important; + } + .gap-sm-5 { + gap: 3rem !important; + } + .row-gap-sm-0 { + row-gap: 0 !important; + } + .row-gap-sm-1 { + row-gap: 0.25rem !important; + } + .row-gap-sm-2 { + row-gap: 0.5rem !important; + } + .row-gap-sm-3 { + row-gap: 1rem !important; + } + .row-gap-sm-4 { + row-gap: 1.5rem !important; + } + .row-gap-sm-5 { + row-gap: 3rem !important; + } + .column-gap-sm-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-sm-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-sm-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-sm-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-sm-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-sm-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-sm-start { + text-align: left !important; + } + .text-sm-end { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .float-md-start { + float: left !important; + } + .float-md-end { + float: right !important; + } + .float-md-none { + float: none !important; + } + .object-fit-md-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-md-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-md-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-md-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-md-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-grid { + display: grid !important; + } + .d-md-inline-grid { + display: inline-grid !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: flex !important; + } + .d-md-inline-flex { + display: inline-flex !important; + } + .d-md-none { + display: none !important; + } + .flex-md-fill { + flex: 1 1 auto !important; + } + .flex-md-row { + flex-direction: row !important; + } + .flex-md-column { + flex-direction: column !important; + } + .flex-md-row-reverse { + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + flex-direction: column-reverse !important; + } + .flex-md-grow-0 { + flex-grow: 0 !important; + } + .flex-md-grow-1 { + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + flex-shrink: 1 !important; + } + .flex-md-wrap { + flex-wrap: wrap !important; + } + .flex-md-nowrap { + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + justify-content: flex-start !important; + } + .justify-content-md-end { + justify-content: flex-end !important; + } + .justify-content-md-center { + justify-content: center !important; + } + .justify-content-md-between { + justify-content: space-between !important; + } + .justify-content-md-around { + justify-content: space-around !important; + } + .justify-content-md-evenly { + justify-content: space-evenly !important; + } + .align-items-md-start { + align-items: flex-start !important; + } + .align-items-md-end { + align-items: flex-end !important; + } + .align-items-md-center { + align-items: center !important; + } + .align-items-md-baseline { + align-items: baseline !important; + } + .align-items-md-stretch { + align-items: stretch !important; + } + .align-content-md-start { + align-content: flex-start !important; + } + .align-content-md-end { + align-content: flex-end !important; + } + .align-content-md-center { + align-content: center !important; + } + .align-content-md-between { + align-content: space-between !important; + } + .align-content-md-around { + align-content: space-around !important; + } + .align-content-md-stretch { + align-content: stretch !important; + } + .align-self-md-auto { + align-self: auto !important; + } + .align-self-md-start { + align-self: flex-start !important; + } + .align-self-md-end { + align-self: flex-end !important; + } + .align-self-md-center { + align-self: center !important; + } + .align-self-md-baseline { + align-self: baseline !important; + } + .align-self-md-stretch { + align-self: stretch !important; + } + .order-md-first { + order: -1 !important; + } + .order-md-0 { + order: 0 !important; + } + .order-md-1 { + order: 1 !important; + } + .order-md-2 { + order: 2 !important; + } + .order-md-3 { + order: 3 !important; + } + .order-md-4 { + order: 4 !important; + } + .order-md-5 { + order: 5 !important; + } + .order-md-last { + order: 6 !important; + } + .m-md-0 { + margin: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-md-0 { + margin-top: 0 !important; + } + .mt-md-1 { + margin-top: 0.25rem !important; + } + .mt-md-2 { + margin-top: 0.5rem !important; + } + .mt-md-3 { + margin-top: 1rem !important; + } + .mt-md-4 { + margin-top: 1.5rem !important; + } + .mt-md-5 { + margin-top: 3rem !important; + } + .mt-md-auto { + margin-top: auto !important; + } + .me-md-0 { + margin-right: 0 !important; + } + .me-md-1 { + margin-right: 0.25rem !important; + } + .me-md-2 { + margin-right: 0.5rem !important; + } + .me-md-3 { + margin-right: 1rem !important; + } + .me-md-4 { + margin-right: 1.5rem !important; + } + .me-md-5 { + margin-right: 3rem !important; + } + .me-md-auto { + margin-right: auto !important; + } + .mb-md-0 { + margin-bottom: 0 !important; + } + .mb-md-1 { + margin-bottom: 0.25rem !important; + } + .mb-md-2 { + margin-bottom: 0.5rem !important; + } + .mb-md-3 { + margin-bottom: 1rem !important; + } + .mb-md-4 { + margin-bottom: 1.5rem !important; + } + .mb-md-5 { + margin-bottom: 3rem !important; + } + .mb-md-auto { + margin-bottom: auto !important; + } + .ms-md-0 { + margin-left: 0 !important; + } + .ms-md-1 { + margin-left: 0.25rem !important; + } + .ms-md-2 { + margin-left: 0.5rem !important; + } + .ms-md-3 { + margin-left: 1rem !important; + } + .ms-md-4 { + margin-left: 1.5rem !important; + } + .ms-md-5 { + margin-left: 3rem !important; + } + .ms-md-auto { + margin-left: auto !important; + } + .p-md-0 { + padding: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-md-0 { + padding-top: 0 !important; + } + .pt-md-1 { + padding-top: 0.25rem !important; + } + .pt-md-2 { + padding-top: 0.5rem !important; + } + .pt-md-3 { + padding-top: 1rem !important; + } + .pt-md-4 { + padding-top: 1.5rem !important; + } + .pt-md-5 { + padding-top: 3rem !important; + } + .pe-md-0 { + padding-right: 0 !important; + } + .pe-md-1 { + padding-right: 0.25rem !important; + } + .pe-md-2 { + padding-right: 0.5rem !important; + } + .pe-md-3 { + padding-right: 1rem !important; + } + .pe-md-4 { + padding-right: 1.5rem !important; + } + .pe-md-5 { + padding-right: 3rem !important; + } + .pb-md-0 { + padding-bottom: 0 !important; + } + .pb-md-1 { + padding-bottom: 0.25rem !important; + } + .pb-md-2 { + padding-bottom: 0.5rem !important; + } + .pb-md-3 { + padding-bottom: 1rem !important; + } + .pb-md-4 { + padding-bottom: 1.5rem !important; + } + .pb-md-5 { + padding-bottom: 3rem !important; + } + .ps-md-0 { + padding-left: 0 !important; + } + .ps-md-1 { + padding-left: 0.25rem !important; + } + .ps-md-2 { + padding-left: 0.5rem !important; + } + .ps-md-3 { + padding-left: 1rem !important; + } + .ps-md-4 { + padding-left: 1.5rem !important; + } + .ps-md-5 { + padding-left: 3rem !important; + } + .gap-md-0 { + gap: 0 !important; + } + .gap-md-1 { + gap: 0.25rem !important; + } + .gap-md-2 { + gap: 0.5rem !important; + } + .gap-md-3 { + gap: 1rem !important; + } + .gap-md-4 { + gap: 1.5rem !important; + } + .gap-md-5 { + gap: 3rem !important; + } + .row-gap-md-0 { + row-gap: 0 !important; + } + .row-gap-md-1 { + row-gap: 0.25rem !important; + } + .row-gap-md-2 { + row-gap: 0.5rem !important; + } + .row-gap-md-3 { + row-gap: 1rem !important; + } + .row-gap-md-4 { + row-gap: 1.5rem !important; + } + .row-gap-md-5 { + row-gap: 3rem !important; + } + .column-gap-md-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-md-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-md-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-md-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-md-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-md-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-md-start { + text-align: left !important; + } + .text-md-end { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .float-lg-start { + float: left !important; + } + .float-lg-end { + float: right !important; + } + .float-lg-none { + float: none !important; + } + .object-fit-lg-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-lg-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-lg-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-lg-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-lg-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-grid { + display: grid !important; + } + .d-lg-inline-grid { + display: inline-grid !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: flex !important; + } + .d-lg-inline-flex { + display: inline-flex !important; + } + .d-lg-none { + display: none !important; + } + .flex-lg-fill { + flex: 1 1 auto !important; + } + .flex-lg-row { + flex-direction: row !important; + } + .flex-lg-column { + flex-direction: column !important; + } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; + } + .flex-lg-grow-0 { + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + flex-shrink: 1 !important; + } + .flex-lg-wrap { + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + justify-content: flex-start !important; + } + .justify-content-lg-end { + justify-content: flex-end !important; + } + .justify-content-lg-center { + justify-content: center !important; + } + .justify-content-lg-between { + justify-content: space-between !important; + } + .justify-content-lg-around { + justify-content: space-around !important; + } + .justify-content-lg-evenly { + justify-content: space-evenly !important; + } + .align-items-lg-start { + align-items: flex-start !important; + } + .align-items-lg-end { + align-items: flex-end !important; + } + .align-items-lg-center { + align-items: center !important; + } + .align-items-lg-baseline { + align-items: baseline !important; + } + .align-items-lg-stretch { + align-items: stretch !important; + } + .align-content-lg-start { + align-content: flex-start !important; + } + .align-content-lg-end { + align-content: flex-end !important; + } + .align-content-lg-center { + align-content: center !important; + } + .align-content-lg-between { + align-content: space-between !important; + } + .align-content-lg-around { + align-content: space-around !important; + } + .align-content-lg-stretch { + align-content: stretch !important; + } + .align-self-lg-auto { + align-self: auto !important; + } + .align-self-lg-start { + align-self: flex-start !important; + } + .align-self-lg-end { + align-self: flex-end !important; + } + .align-self-lg-center { + align-self: center !important; + } + .align-self-lg-baseline { + align-self: baseline !important; + } + .align-self-lg-stretch { + align-self: stretch !important; + } + .order-lg-first { + order: -1 !important; + } + .order-lg-0 { + order: 0 !important; + } + .order-lg-1 { + order: 1 !important; + } + .order-lg-2 { + order: 2 !important; + } + .order-lg-3 { + order: 3 !important; + } + .order-lg-4 { + order: 4 !important; + } + .order-lg-5 { + order: 5 !important; + } + .order-lg-last { + order: 6 !important; + } + .m-lg-0 { + margin: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-lg-0 { + margin-top: 0 !important; + } + .mt-lg-1 { + margin-top: 0.25rem !important; + } + .mt-lg-2 { + margin-top: 0.5rem !important; + } + .mt-lg-3 { + margin-top: 1rem !important; + } + .mt-lg-4 { + margin-top: 1.5rem !important; + } + .mt-lg-5 { + margin-top: 3rem !important; + } + .mt-lg-auto { + margin-top: auto !important; + } + .me-lg-0 { + margin-right: 0 !important; + } + .me-lg-1 { + margin-right: 0.25rem !important; + } + .me-lg-2 { + margin-right: 0.5rem !important; + } + .me-lg-3 { + margin-right: 1rem !important; + } + .me-lg-4 { + margin-right: 1.5rem !important; + } + .me-lg-5 { + margin-right: 3rem !important; + } + .me-lg-auto { + margin-right: auto !important; + } + .mb-lg-0 { + margin-bottom: 0 !important; + } + .mb-lg-1 { + margin-bottom: 0.25rem !important; + } + .mb-lg-2 { + margin-bottom: 0.5rem !important; + } + .mb-lg-3 { + margin-bottom: 1rem !important; + } + .mb-lg-4 { + margin-bottom: 1.5rem !important; + } + .mb-lg-5 { + margin-bottom: 3rem !important; + } + .mb-lg-auto { + margin-bottom: auto !important; + } + .ms-lg-0 { + margin-left: 0 !important; + } + .ms-lg-1 { + margin-left: 0.25rem !important; + } + .ms-lg-2 { + margin-left: 0.5rem !important; + } + .ms-lg-3 { + margin-left: 1rem !important; + } + .ms-lg-4 { + margin-left: 1.5rem !important; + } + .ms-lg-5 { + margin-left: 3rem !important; + } + .ms-lg-auto { + margin-left: auto !important; + } + .p-lg-0 { + padding: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-lg-0 { + padding-top: 0 !important; + } + .pt-lg-1 { + padding-top: 0.25rem !important; + } + .pt-lg-2 { + padding-top: 0.5rem !important; + } + .pt-lg-3 { + padding-top: 1rem !important; + } + .pt-lg-4 { + padding-top: 1.5rem !important; + } + .pt-lg-5 { + padding-top: 3rem !important; + } + .pe-lg-0 { + padding-right: 0 !important; + } + .pe-lg-1 { + padding-right: 0.25rem !important; + } + .pe-lg-2 { + padding-right: 0.5rem !important; + } + .pe-lg-3 { + padding-right: 1rem !important; + } + .pe-lg-4 { + padding-right: 1.5rem !important; + } + .pe-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-0 { + padding-bottom: 0 !important; + } + .pb-lg-1 { + padding-bottom: 0.25rem !important; + } + .pb-lg-2 { + padding-bottom: 0.5rem !important; + } + .pb-lg-3 { + padding-bottom: 1rem !important; + } + .pb-lg-4 { + padding-bottom: 1.5rem !important; + } + .pb-lg-5 { + padding-bottom: 3rem !important; + } + .ps-lg-0 { + padding-left: 0 !important; + } + .ps-lg-1 { + padding-left: 0.25rem !important; + } + .ps-lg-2 { + padding-left: 0.5rem !important; + } + .ps-lg-3 { + padding-left: 1rem !important; + } + .ps-lg-4 { + padding-left: 1.5rem !important; + } + .ps-lg-5 { + padding-left: 3rem !important; + } + .gap-lg-0 { + gap: 0 !important; + } + .gap-lg-1 { + gap: 0.25rem !important; + } + .gap-lg-2 { + gap: 0.5rem !important; + } + .gap-lg-3 { + gap: 1rem !important; + } + .gap-lg-4 { + gap: 1.5rem !important; + } + .gap-lg-5 { + gap: 3rem !important; + } + .row-gap-lg-0 { + row-gap: 0 !important; + } + .row-gap-lg-1 { + row-gap: 0.25rem !important; + } + .row-gap-lg-2 { + row-gap: 0.5rem !important; + } + .row-gap-lg-3 { + row-gap: 1rem !important; + } + .row-gap-lg-4 { + row-gap: 1.5rem !important; + } + .row-gap-lg-5 { + row-gap: 3rem !important; + } + .column-gap-lg-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-lg-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-lg-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-lg-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-lg-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-lg-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-lg-start { + text-align: left !important; + } + .text-lg-end { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .float-xl-start { + float: left !important; + } + .float-xl-end { + float: right !important; + } + .float-xl-none { + float: none !important; + } + .object-fit-xl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-grid { + display: grid !important; + } + .d-xl-inline-grid { + display: inline-grid !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: flex !important; + } + .d-xl-inline-flex { + display: inline-flex !important; + } + .d-xl-none { + display: none !important; + } + .flex-xl-fill { + flex: 1 1 auto !important; + } + .flex-xl-row { + flex-direction: row !important; + } + .flex-xl-column { + flex-direction: column !important; + } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xl-grow-0 { + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xl-wrap { + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + justify-content: flex-start !important; + } + .justify-content-xl-end { + justify-content: flex-end !important; + } + .justify-content-xl-center { + justify-content: center !important; + } + .justify-content-xl-between { + justify-content: space-between !important; + } + .justify-content-xl-around { + justify-content: space-around !important; + } + .justify-content-xl-evenly { + justify-content: space-evenly !important; + } + .align-items-xl-start { + align-items: flex-start !important; + } + .align-items-xl-end { + align-items: flex-end !important; + } + .align-items-xl-center { + align-items: center !important; + } + .align-items-xl-baseline { + align-items: baseline !important; + } + .align-items-xl-stretch { + align-items: stretch !important; + } + .align-content-xl-start { + align-content: flex-start !important; + } + .align-content-xl-end { + align-content: flex-end !important; + } + .align-content-xl-center { + align-content: center !important; + } + .align-content-xl-between { + align-content: space-between !important; + } + .align-content-xl-around { + align-content: space-around !important; + } + .align-content-xl-stretch { + align-content: stretch !important; + } + .align-self-xl-auto { + align-self: auto !important; + } + .align-self-xl-start { + align-self: flex-start !important; + } + .align-self-xl-end { + align-self: flex-end !important; + } + .align-self-xl-center { + align-self: center !important; + } + .align-self-xl-baseline { + align-self: baseline !important; + } + .align-self-xl-stretch { + align-self: stretch !important; + } + .order-xl-first { + order: -1 !important; + } + .order-xl-0 { + order: 0 !important; + } + .order-xl-1 { + order: 1 !important; + } + .order-xl-2 { + order: 2 !important; + } + .order-xl-3 { + order: 3 !important; + } + .order-xl-4 { + order: 4 !important; + } + .order-xl-5 { + order: 5 !important; + } + .order-xl-last { + order: 6 !important; + } + .m-xl-0 { + margin: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xl-0 { + margin-top: 0 !important; + } + .mt-xl-1 { + margin-top: 0.25rem !important; + } + .mt-xl-2 { + margin-top: 0.5rem !important; + } + .mt-xl-3 { + margin-top: 1rem !important; + } + .mt-xl-4 { + margin-top: 1.5rem !important; + } + .mt-xl-5 { + margin-top: 3rem !important; + } + .mt-xl-auto { + margin-top: auto !important; + } + .me-xl-0 { + margin-right: 0 !important; + } + .me-xl-1 { + margin-right: 0.25rem !important; + } + .me-xl-2 { + margin-right: 0.5rem !important; + } + .me-xl-3 { + margin-right: 1rem !important; + } + .me-xl-4 { + margin-right: 1.5rem !important; + } + .me-xl-5 { + margin-right: 3rem !important; + } + .me-xl-auto { + margin-right: auto !important; + } + .mb-xl-0 { + margin-bottom: 0 !important; + } + .mb-xl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xl-3 { + margin-bottom: 1rem !important; + } + .mb-xl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xl-5 { + margin-bottom: 3rem !important; + } + .mb-xl-auto { + margin-bottom: auto !important; + } + .ms-xl-0 { + margin-left: 0 !important; + } + .ms-xl-1 { + margin-left: 0.25rem !important; + } + .ms-xl-2 { + margin-left: 0.5rem !important; + } + .ms-xl-3 { + margin-left: 1rem !important; + } + .ms-xl-4 { + margin-left: 1.5rem !important; + } + .ms-xl-5 { + margin-left: 3rem !important; + } + .ms-xl-auto { + margin-left: auto !important; + } + .p-xl-0 { + padding: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xl-0 { + padding-top: 0 !important; + } + .pt-xl-1 { + padding-top: 0.25rem !important; + } + .pt-xl-2 { + padding-top: 0.5rem !important; + } + .pt-xl-3 { + padding-top: 1rem !important; + } + .pt-xl-4 { + padding-top: 1.5rem !important; + } + .pt-xl-5 { + padding-top: 3rem !important; + } + .pe-xl-0 { + padding-right: 0 !important; + } + .pe-xl-1 { + padding-right: 0.25rem !important; + } + .pe-xl-2 { + padding-right: 0.5rem !important; + } + .pe-xl-3 { + padding-right: 1rem !important; + } + .pe-xl-4 { + padding-right: 1.5rem !important; + } + .pe-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-0 { + padding-bottom: 0 !important; + } + .pb-xl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xl-3 { + padding-bottom: 1rem !important; + } + .pb-xl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xl-5 { + padding-bottom: 3rem !important; + } + .ps-xl-0 { + padding-left: 0 !important; + } + .ps-xl-1 { + padding-left: 0.25rem !important; + } + .ps-xl-2 { + padding-left: 0.5rem !important; + } + .ps-xl-3 { + padding-left: 1rem !important; + } + .ps-xl-4 { + padding-left: 1.5rem !important; + } + .ps-xl-5 { + padding-left: 3rem !important; + } + .gap-xl-0 { + gap: 0 !important; + } + .gap-xl-1 { + gap: 0.25rem !important; + } + .gap-xl-2 { + gap: 0.5rem !important; + } + .gap-xl-3 { + gap: 1rem !important; + } + .gap-xl-4 { + gap: 1.5rem !important; + } + .gap-xl-5 { + gap: 3rem !important; + } + .row-gap-xl-0 { + row-gap: 0 !important; + } + .row-gap-xl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xl-3 { + row-gap: 1rem !important; + } + .row-gap-xl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xl-5 { + row-gap: 3rem !important; + } + .column-gap-xl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xl-start { + text-align: left !important; + } + .text-xl-end { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} +@media (min-width: 1400px) { + .float-xxl-start { + float: left !important; + } + .float-xxl-end { + float: right !important; + } + .float-xxl-none { + float: none !important; + } + .object-fit-xxl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xxl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xxl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xxl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xxl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xxl-inline { + display: inline !important; + } + .d-xxl-inline-block { + display: inline-block !important; + } + .d-xxl-block { + display: block !important; + } + .d-xxl-grid { + display: grid !important; + } + .d-xxl-inline-grid { + display: inline-grid !important; + } + .d-xxl-table { + display: table !important; + } + .d-xxl-table-row { + display: table-row !important; + } + .d-xxl-table-cell { + display: table-cell !important; + } + .d-xxl-flex { + display: flex !important; + } + .d-xxl-inline-flex { + display: inline-flex !important; + } + .d-xxl-none { + display: none !important; + } + .flex-xxl-fill { + flex: 1 1 auto !important; + } + .flex-xxl-row { + flex-direction: row !important; + } + .flex-xxl-column { + flex-direction: column !important; + } + .flex-xxl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xxl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xxl-grow-0 { + flex-grow: 0 !important; + } + .flex-xxl-grow-1 { + flex-grow: 1 !important; + } + .flex-xxl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xxl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xxl-wrap { + flex-wrap: wrap !important; + } + .flex-xxl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xxl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xxl-start { + justify-content: flex-start !important; + } + .justify-content-xxl-end { + justify-content: flex-end !important; + } + .justify-content-xxl-center { + justify-content: center !important; + } + .justify-content-xxl-between { + justify-content: space-between !important; + } + .justify-content-xxl-around { + justify-content: space-around !important; + } + .justify-content-xxl-evenly { + justify-content: space-evenly !important; + } + .align-items-xxl-start { + align-items: flex-start !important; + } + .align-items-xxl-end { + align-items: flex-end !important; + } + .align-items-xxl-center { + align-items: center !important; + } + .align-items-xxl-baseline { + align-items: baseline !important; + } + .align-items-xxl-stretch { + align-items: stretch !important; + } + .align-content-xxl-start { + align-content: flex-start !important; + } + .align-content-xxl-end { + align-content: flex-end !important; + } + .align-content-xxl-center { + align-content: center !important; + } + .align-content-xxl-between { + align-content: space-between !important; + } + .align-content-xxl-around { + align-content: space-around !important; + } + .align-content-xxl-stretch { + align-content: stretch !important; + } + .align-self-xxl-auto { + align-self: auto !important; + } + .align-self-xxl-start { + align-self: flex-start !important; + } + .align-self-xxl-end { + align-self: flex-end !important; + } + .align-self-xxl-center { + align-self: center !important; + } + .align-self-xxl-baseline { + align-self: baseline !important; + } + .align-self-xxl-stretch { + align-self: stretch !important; + } + .order-xxl-first { + order: -1 !important; + } + .order-xxl-0 { + order: 0 !important; + } + .order-xxl-1 { + order: 1 !important; + } + .order-xxl-2 { + order: 2 !important; + } + .order-xxl-3 { + order: 3 !important; + } + .order-xxl-4 { + order: 4 !important; + } + .order-xxl-5 { + order: 5 !important; + } + .order-xxl-last { + order: 6 !important; + } + .m-xxl-0 { + margin: 0 !important; + } + .m-xxl-1 { + margin: 0.25rem !important; + } + .m-xxl-2 { + margin: 0.5rem !important; + } + .m-xxl-3 { + margin: 1rem !important; + } + .m-xxl-4 { + margin: 1.5rem !important; + } + .m-xxl-5 { + margin: 3rem !important; + } + .m-xxl-auto { + margin: auto !important; + } + .mx-xxl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xxl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xxl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xxl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xxl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xxl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xxl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xxl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xxl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xxl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xxl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xxl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xxl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xxl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xxl-0 { + margin-top: 0 !important; + } + .mt-xxl-1 { + margin-top: 0.25rem !important; + } + .mt-xxl-2 { + margin-top: 0.5rem !important; + } + .mt-xxl-3 { + margin-top: 1rem !important; + } + .mt-xxl-4 { + margin-top: 1.5rem !important; + } + .mt-xxl-5 { + margin-top: 3rem !important; + } + .mt-xxl-auto { + margin-top: auto !important; + } + .me-xxl-0 { + margin-right: 0 !important; + } + .me-xxl-1 { + margin-right: 0.25rem !important; + } + .me-xxl-2 { + margin-right: 0.5rem !important; + } + .me-xxl-3 { + margin-right: 1rem !important; + } + .me-xxl-4 { + margin-right: 1.5rem !important; + } + .me-xxl-5 { + margin-right: 3rem !important; + } + .me-xxl-auto { + margin-right: auto !important; + } + .mb-xxl-0 { + margin-bottom: 0 !important; + } + .mb-xxl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xxl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xxl-3 { + margin-bottom: 1rem !important; + } + .mb-xxl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xxl-5 { + margin-bottom: 3rem !important; + } + .mb-xxl-auto { + margin-bottom: auto !important; + } + .ms-xxl-0 { + margin-left: 0 !important; + } + .ms-xxl-1 { + margin-left: 0.25rem !important; + } + .ms-xxl-2 { + margin-left: 0.5rem !important; + } + .ms-xxl-3 { + margin-left: 1rem !important; + } + .ms-xxl-4 { + margin-left: 1.5rem !important; + } + .ms-xxl-5 { + margin-left: 3rem !important; + } + .ms-xxl-auto { + margin-left: auto !important; + } + .p-xxl-0 { + padding: 0 !important; + } + .p-xxl-1 { + padding: 0.25rem !important; + } + .p-xxl-2 { + padding: 0.5rem !important; + } + .p-xxl-3 { + padding: 1rem !important; + } + .p-xxl-4 { + padding: 1.5rem !important; + } + .p-xxl-5 { + padding: 3rem !important; + } + .px-xxl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xxl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xxl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xxl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xxl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xxl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xxl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xxl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xxl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xxl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xxl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xxl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xxl-0 { + padding-top: 0 !important; + } + .pt-xxl-1 { + padding-top: 0.25rem !important; + } + .pt-xxl-2 { + padding-top: 0.5rem !important; + } + .pt-xxl-3 { + padding-top: 1rem !important; + } + .pt-xxl-4 { + padding-top: 1.5rem !important; + } + .pt-xxl-5 { + padding-top: 3rem !important; + } + .pe-xxl-0 { + padding-right: 0 !important; + } + .pe-xxl-1 { + padding-right: 0.25rem !important; + } + .pe-xxl-2 { + padding-right: 0.5rem !important; + } + .pe-xxl-3 { + padding-right: 1rem !important; + } + .pe-xxl-4 { + padding-right: 1.5rem !important; + } + .pe-xxl-5 { + padding-right: 3rem !important; + } + .pb-xxl-0 { + padding-bottom: 0 !important; + } + .pb-xxl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xxl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xxl-3 { + padding-bottom: 1rem !important; + } + .pb-xxl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xxl-5 { + padding-bottom: 3rem !important; + } + .ps-xxl-0 { + padding-left: 0 !important; + } + .ps-xxl-1 { + padding-left: 0.25rem !important; + } + .ps-xxl-2 { + padding-left: 0.5rem !important; + } + .ps-xxl-3 { + padding-left: 1rem !important; + } + .ps-xxl-4 { + padding-left: 1.5rem !important; + } + .ps-xxl-5 { + padding-left: 3rem !important; + } + .gap-xxl-0 { + gap: 0 !important; + } + .gap-xxl-1 { + gap: 0.25rem !important; + } + .gap-xxl-2 { + gap: 0.5rem !important; + } + .gap-xxl-3 { + gap: 1rem !important; + } + .gap-xxl-4 { + gap: 1.5rem !important; + } + .gap-xxl-5 { + gap: 3rem !important; + } + .row-gap-xxl-0 { + row-gap: 0 !important; + } + .row-gap-xxl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xxl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xxl-3 { + row-gap: 1rem !important; + } + .row-gap-xxl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xxl-5 { + row-gap: 3rem !important; + } + .column-gap-xxl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xxl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xxl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xxl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xxl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xxl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xxl-start { + text-align: left !important; + } + .text-xxl-end { + text-align: right !important; + } + .text-xxl-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .fs-1 { + font-size: 2.5rem !important; + } + .fs-2 { + font-size: 2rem !important; + } + .fs-3 { + font-size: 1.75rem !important; + } + .fs-4 { + font-size: 1.5rem !important; + } +} +@media print { + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-grid { + display: grid !important; + } + .d-print-inline-grid { + display: inline-grid !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: flex !important; + } + .d-print-inline-flex { + display: inline-flex !important; + } + .d-print-none { + display: none !important; + } +} +.navbar { + font-size: 0.875rem; + font-weight: 500; +} +.navbar .nav-item { + margin-right: 0.5rem; + margin-left: 0.5rem; +} +.navbar .navbar-nav .nav-link { + border-radius: 0.375rem; +} + +.navbar-dark .navbar-nav .nav-link:hover { + background-color: rgba(255, 255, 255, 0.1); +} +.navbar-dark .navbar-nav .nav-link.active { + background-color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:hover { + background-color: rgba(0, 0, 0, 0.03); +} +.navbar-light .navbar-nav .nav-link.active { + background-color: rgba(0, 0, 0, 0.05); +} + +.navbar-nav { + --bs-nav-link-padding-x: .5rem; +} + +.btn-secondary, +.btn-light, +.btn-outline-secondary, +.btn-outline-light { + color: #212529; +} +.btn-secondary:disabled, .btn-secondary.disabled, +.btn-light:disabled, +.btn-light.disabled, +.btn-outline-secondary:disabled, +.btn-outline-secondary.disabled, +.btn-outline-light:disabled, +.btn-outline-light.disabled { + border: 1px solid #e6e6e6; +} + +.btn-secondary, +.btn-outline-secondary { + border-color: #e6e6e6; +} +.btn-secondary:hover, .btn-secondary:active, +.btn-outline-secondary:hover, +.btn-outline-secondary:active { + background-color: #e6e6e6; + border-color: #e6e6e6; +} + +.btn-light, +.btn-outline-light { + border-color: #dfe0e1; +} +.btn-light:hover, .btn-light:active, +.btn-outline-light:hover, +.btn-outline-light:active { + background-color: #dfe0e1; + border-color: #dfe0e1; +} + +.table { + font-size: 0.875rem; + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +thead th { + font-size: 0.875rem; + text-transform: uppercase; +} + +.input-group-text { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.nav-tabs { + font-weight: 500; +} +.nav-tabs .nav-link { + padding-top: 1rem; + padding-bottom: 1rem; + border-width: 0 0 1px; +} +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + box-shadow: inset 0 -2px 0 #3459e6; +} + +.nav-pills { + font-weight: 500; +} + +.pagination { + font-size: 0.875rem; + font-weight: 500; +} +.pagination .page-link { + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} + +.breadcrumb { + font-size: 0.875rem; + font-weight: 500; + border: 1px solid var(--bs-secondary-bg); + border-radius: 0.375rem; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); +} +.breadcrumb-item { + padding: 1rem 0.5rem 1rem 0; +} + +.breadcrumb-item + .breadcrumb-item::before { + padding-right: 1rem; +} + +.alert .btn-close { + color: inherit; +} + +.badge.bg-secondary, .badge.bg-light { + color: #212529; +} + +.list-group-item h1, +.list-group-item h2, +.list-group-item h3, +.list-group-item h4, +.list-group-item h5, +.list-group-item h6, +.list-group-item .h1, +.list-group-item .h2, +.list-group-item .h3, +.list-group-item .h4, +.list-group-item .h5, +.list-group-item .h6, +.card h1, +.card h2, +.card h3, +.card h4, +.card h5, +.card h6, +.card .h1, +.card .h2, +.card .h3, +.card .h4, +.card .h5, +.card .h6 { + color: inherit; +} + +.list-group { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.card { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.modal-footer { + background-color: var(--bs-tertiary-bg); +} + +.modal-content { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} diff --git a/VisualPinball.Unity/Documentation~/template/vpe/styles/fonts.css b/VisualPinball.Unity/Documentation~/template/vpe/styles/fonts.css deleted file mode 100644 index 2ee636e79..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/styles/fonts.css +++ /dev/null @@ -1,208 +0,0 @@ - - - -/* -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 100; - font-display: swap; - src: url("Inter-Thin.woff2?v=3.15") format("woff2"), - url("Inter-Thin.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 100; - font-display: swap; - src: url("Inter-ThinItalic.woff2?v=3.15") format("woff2"), - url("Inter-ThinItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLight.woff2?v=3.15") format("woff2"), - url("Inter-ExtraLight.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 200; - font-display: swap; - src: url("Inter-ExtraLightItalic.woff2?v=3.15") format("woff2"), - url("Inter-ExtraLightItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 300; - font-display: swap; - src: url("Inter-Light.woff2?v=3.15") format("woff2"), - url("Inter-Light.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 300; - font-display: swap; - src: url("Inter-LightItalic.woff2?v=3.15") format("woff2"), - url("Inter-LightItalic.woff?v=3.15") format("woff"); -} -*/ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 400; - font-display: swap; - src: url("/fonts/Inter-Regular.woff2?v=3.15") format("woff2"), - url("/fonts/Inter-Regular.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 400; - font-display: swap; - src: url("/fonts/Inter-Italic.woff2?v=3.15") format("woff2"), - url("/fonts/Inter-Italic.woff?v=3.15") format("woff"); -} -/* -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 500; - font-display: swap; - src: url("Inter-Medium.woff2?v=3.15") format("woff2"), - url("Inter-Medium.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 500; - font-display: swap; - src: url("Inter-MediumItalic.woff2?v=3.15") format("woff2"), - url("Inter-MediumItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBold.woff2?v=3.15") format("woff2"), - url("Inter-SemiBold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 600; - font-display: swap; - src: url("Inter-SemiBoldItalic.woff2?v=3.15") format("woff2"), - url("Inter-SemiBoldItalic.woff?v=3.15") format("woff"); -} -*/ -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 700; - font-display: swap; - src: url("/fonts/Inter-Bold.woff2?v=3.15") format("woff2"), - url("/fonts/Inter-Bold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 700; - font-display: swap; - src: url("/fonts/Inter-BoldItalic.woff2?v=3.15") format("woff2"), - url("/fonts/Inter-BoldItalic.woff?v=3.15") format("woff"); -} -/* -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBold.woff2?v=3.15") format("woff2"), - url("Inter-ExtraBold.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 800; - font-display: swap; - src: url("Inter-ExtraBoldItalic.woff2?v=3.15") format("woff2"), - url("Inter-ExtraBoldItalic.woff?v=3.15") format("woff"); -} - -@font-face { - font-family: 'Inter'; - font-style: normal; - font-weight: 900; - font-display: swap; - src: url("Inter-Black.woff2?v=3.15") format("woff2"), - url("Inter-Black.woff?v=3.15") format("woff"); -} -@font-face { - font-family: 'Inter'; - font-style: italic; - font-weight: 900; - font-display: swap; - src: url("Inter-BlackItalic.woff2?v=3.15") format("woff2"), - url("Inter-BlackItalic.woff?v=3.15") format("woff"); -} -*/ -/* ------------------------------------------------------- -Variable font. -Usage: - - html { font-family: 'Inter', sans-serif; } - @supports (font-variation-settings: normal) { - html { font-family: 'Inter var', sans-serif; } - } -*/ - -/* -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: normal; - font-named-instance: 'Regular'; - src: url("Inter-roman.var.woff2?v=3.15") format("woff2"); -} -@font-face { - font-family: 'Inter var'; - font-weight: 100 900; - font-display: swap; - font-style: italic; - font-named-instance: 'Italic'; - src: url("Inter-italic.var.woff2?v=3.15") format("woff2"); -} -*/ - -/* -------------------------------------------------------------------------- -[EXPERIMENTAL] Multi-axis, single variable font. - -Slant axis is not yet widely supported (as of February 2019) and thus this -multi-axis single variable font is opt-in rather than the default. - -When using this, you will probably need to set font-variation-settings -explicitly, e.g. - - * { font-variation-settings: "slnt" 0deg } - .italic { font-variation-settings: "slnt" 10deg } - -*/ -/* -@font-face { - font-family: 'Inter var experimental'; - font-weight: 100 900; - font-display: swap; - font-style: oblique 0deg 10deg; - src: url("Inter.var.woff2?v=3.15") format("woff2"); -} -*/ \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/styles/main.css b/VisualPinball.Unity/Documentation~/template/vpe/styles/main.css deleted file mode 100644 index cc0a74e32..000000000 --- a/VisualPinball.Unity/Documentation~/template/vpe/styles/main.css +++ /dev/null @@ -1,56 +0,0 @@ -html, -body { - font-family: Inter, 'Segoe UI', Tahoma, Helvetica, sans-serif; - font-size: 100%; - -} - -.btn.btn-default.active, .btn.btn-default:active { - outline: 0; - -webkit-box-shadow: none; - box-shadow: none; - background-color: rgba(1,1,1,0.08); -} - -.navbar-brand > svg { - margin-top: 6px; -} - -.navbar-github > svg { - margin-top: 8px; - fill: #ccc; -} - -.navbar-default { - background-color: rgba(1,1,1,0.05); - border-color: rgba(1,1,1,0.1); -} - -#sidetoc .sidefilter { - top: 94px; -} - -#sidetoc .sidetoc.shiftup { - bottom: 0; -} - -img[alt="Visual Pinball Engine"] { - margin-top: 15px; -} - -h1, h2 { - font-weight: bold; -} - -.table-bordered, -.table-bordered>tbody>tr>td, -.table-bordered>tbody>tr>th, -.table-bordered>tfoot>tr>td, -.table-bordered>tfoot>tr>th, -.table-bordered>thead>tr>td, -.table-bordered>thead>tr>th { - border-color: rgba(1,1,1,0.15); -} -.table-striped > tbody > tr:nth-of-type(odd) { - background-color: rgba(1,1,1,0.03) -} \ No newline at end of file diff --git a/VisualPinball.Unity/Documentation~/template/vpe/token.json b/VisualPinball.Unity/Documentation~/template/vpe/token.json new file mode 100644 index 000000000..203df6dd8 --- /dev/null +++ b/VisualPinball.Unity/Documentation~/template/vpe/token.json @@ -0,0 +1,4 @@ +{ + "improveThisDoc": "Improve this Page", + "inThisArticle": "Table of Contents" +} diff --git a/VisualPinball.Unity/Documentation~/toc.yml b/VisualPinball.Unity/Documentation~/toc.yml index bdab2402d..20d6500f4 100644 --- a/VisualPinball.Unity/Documentation~/toc.yml +++ b/VisualPinball.Unity/Documentation~/toc.yml @@ -2,8 +2,5 @@ href: creators-guide/ - name: Plugins href: plugins/ -- name: API Documentation - href: api/ - homepage: api/index.md - name: Changelog href: /CHANGELOG.html diff --git a/VisualPinball.Unity/Documentation~/vpe.svg b/VisualPinball.Unity/Documentation~/vpe.svg deleted file mode 100644 index c47ac3a0a..000000000 --- a/VisualPinball.Unity/Documentation~/vpe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Common/TableSelectorHook.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Common/TableSelectorHook.cs index f04d0e46b..f921e7fd9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Common/TableSelectorHook.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Common/TableSelectorHook.cs @@ -50,7 +50,7 @@ private static void SetTableFromSelection() // find parent in hierarchy var selectedTable = Selection.activeGameObject.GetComponentInParent(); - if (selectedTable != null) { + if (selectedTable != null && Selection.activeGameObject.activeInHierarchy) { TableSelector.Instance.SelectedTable = selectedTable; } } @@ -67,7 +67,7 @@ private static TableComponent FindTableInHierarchy() // try root objects first foreach (var go in rootObjects) { var ta = go.GetComponent(); - if (ta != null) { + if (ta != null && ta.isActiveAndEnabled) { return ta; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs index 30b1f7007..7292c8c88 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs @@ -20,6 +20,8 @@ using System.IO; using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine.SceneManagement; namespace VisualPinball.Unity.Editor { @@ -28,6 +30,11 @@ public static class VpxMenuImporter [MenuItem("Visual Pinball/Import VPX", false, 2)] public static void ImportVpxIntoScene(MenuCommand menuCommand) { + // if it's an untitled scene, save first. + if (!EnsureUntitledSceneHasBeenSaved("Before importing, you need to make your current scene an asset by saving it.")) { + return; + } + // open file dialog var vpxPath = EditorUtility.OpenFilePanelWithFilters("Import .VPX File", null, new[] { "Visual Pinball Table Files", "vpx" }); if (vpxPath.Length == 0) { @@ -36,5 +43,25 @@ public static void ImportVpxIntoScene(MenuCommand menuCommand) VpxImportEngine.ImportIntoScene(vpxPath, tableName: Path.GetFileNameWithoutExtension(vpxPath)); } + + private static bool EnsureUntitledSceneHasBeenSaved(string message) + { + if (string.IsNullOrEmpty(SceneManager.GetActiveScene().path)) { + + if (!EditorUtility.DisplayDialog("Info", "Before importing, you need to make your current scene an asset by saving it.\nSave your current scene?", "Yes", "No")) { + return false; + } + + // Ask the user to save + EditorSceneManager.SaveOpenScenes(); + + // Check that the scene was saved + if (!string.IsNullOrEmpty(SceneManager.GetActiveScene().path)) { + return true; + } + return false; + } + return true; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs index e3f20caf8..6d3730a0a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxPrefab.cs @@ -45,9 +45,9 @@ public VpxPrefab(Object prefab, TItem item) GameObject = PrefabUtility.InstantiatePrefab(prefab) as GameObject; GameObject!.name = item.Name; _mainComponent = GameObject.GetComponent(); - if (_mainComponent && _mainComponent.HasProceduralMesh) { - PrefabUtility.UnpackPrefabInstance(GameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); - } + // if (_mainComponent && _mainComponent.HasProceduralMesh) { + // PrefabUtility.UnpackPrefabInstance(GameObject, PrefabUnpackMode.OutermostRoot, InteractionMode.AutomatedAction); + // } } public void SetData() diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index b55130cc9..fe26430bf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -66,6 +66,7 @@ public class VpxSceneConverter : ITextureProvider, IMaterialProvider, IMeshProvi private readonly Table _sourceTable; private readonly ConvertOptions _options; + private Scene _tableScene; private GameObject _tableGo; private TableComponent _tableComponent; private PlayfieldComponent _playfieldComponent; @@ -167,9 +168,8 @@ public GameObject Convert(bool applyPatch = true, string tableName = null) _patcher?.PostPatch(_tableGo); } - return _tableGo; + return MakeSubScene(); } - private void SaveData() { foreach (var key in _sourceContainer.TableInfo.Keys) { @@ -573,6 +573,7 @@ private void CreateRootHierarchy(string tableName = null) .Replace("%INFONAME%", _sourceContainer.InfoName); } + // 1. create game object hierarchy _tableGo = new GameObject(tableName); _playfieldGo = new GameObject("Playfield"); var backglassGo = new GameObject("Backglass"); @@ -585,15 +586,48 @@ private void CreateRootHierarchy(string tableName = null) backglassGo.transform.SetParent(_tableGo.transform, false); cabinetGo.transform.SetParent(_tableGo.transform, false); + // 2. add components + _playfieldGo.AddComponent(); _playfieldComponent = _playfieldGo.AddComponent(); _playfieldGo.AddComponent(); _playfieldGo.AddComponent(); _playfieldGo.AddComponent(); - //_playfieldGo.transform.localRotation = PlayfieldComponent.GlobalRotation; - //_playfieldGo.transform.localPosition = new Vector3(-_sourceTable.Width / 2 * PlayfieldComponent.GlobalScale, 0f, _sourceTable.Height / 2 * PlayfieldComponent.GlobalScale); - //_playfieldGo.transform.localScale = new Vector3(PlayfieldComponent.GlobalScale, PlayfieldComponent.GlobalScale, PlayfieldComponent.GlobalScale); _playfieldComponent.SetData(_sourceTable.Data); } + + private GameObject MakeSubScene() + { + // var sceneName = _tableScene.name; + // var scenePath = GetScenePath(sceneName); + // EditorSceneManager.SaveScene(_tableScene, scenePath); + // EditorSceneManager.CloseScene(_tableScene, true); + // + // // link table scene as sub scene + // var subSceneGo = new GameObject(sceneName); + // var subSceneMb = subSceneGo.AddComponent(); + // var subSceneAsset = AssetDatabase.LoadAssetAtPath(scenePath); + // subSceneMb.SceneAsset = subSceneAsset; + // + // return subSceneGo; + + return _tableGo; + } + + private static string GetScenePath(string tableName) + { + const string sceneRoot = "Assets/Tables/"; + var i = -1; + do { + var suffix = i >= 0 ? "." + i.ToString("D3") : ""; + var scenePath = $"{sceneRoot}{tableName}{suffix}.unity"; + if (!File.Exists(scenePath)) { + return scenePath; + } + i++; + } while (i < 999); + + return null; + } private GameObject GetGroupParent(IItem item) => GetGroupParent(item.ItemGroupName); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs new file mode 100644 index 000000000..e6ed34144 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs @@ -0,0 +1,43 @@ +// 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 UnityEditor; +using UnityEngine; + +namespace VisualPinball.Unity.Editor +{ + [CustomEditor(typeof(PhysicsEngine))] + public class PhysicsEngineInspector : UnityEditor.Editor + { + private SerializedProperty _gravityProperty; + + private void OnEnable() + { + _gravityProperty = serializedObject.FindProperty(nameof(PhysicsEngine.GravityStrength)); + } + + public override void OnInspectorGUI() + { + EditorGUI.BeginChangeCheck(); + + EditorGUILayout.PropertyField(_gravityProperty, new GUIContent("Gravity Constant")); + + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs.meta new file mode 100644 index 000000000..d5cec9090 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PhysicsEngineInspector.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c082aa8d96384de191a3ac3700f3b06b +timeCreated: 1697578139 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PlayerInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PlayerInspector.cs index ae7b0fd4b..9c3db3957 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PlayerInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/PlayerInspector.cs @@ -14,10 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Linq; +// ReSharper disable AssignmentInConditionalExpression + using UnityEditor; using UnityEngine; -using VisualPinball.Engine.Common; namespace VisualPinball.Unity.Editor { @@ -25,23 +25,13 @@ namespace VisualPinball.Unity.Editor [CanEditMultipleObjects] public class PlayerInspector : UnityEditor.Editor { - private IPhysicsEngine[] _physicsEngines; - private string[] _physicsEngineNames; - private int _physicsEngineIndex; - - private IDebugUI[] _debugUIs; - private string[] _debugUINames; - private int _debugUIIndex; - private bool _toggleDebug = true; private SerializedProperty _updateDuringGameplayProperty; private void OnEnable() { - var player = (Player)target; - - _updateDuringGameplayProperty = serializedObject.FindProperty(nameof(player.UpdateDuringGamplay)); + _updateDuringGameplayProperty = serializedObject.FindProperty(nameof(Player.UpdateDuringGamplay)); } public override void OnInspectorGUI() @@ -50,8 +40,6 @@ public override void OnInspectorGUI() if (player == null) { return; } - DrawEngineSelector("Physics Engine", ref player.physicsEngineId, ref _physicsEngines, ref _physicsEngineNames, ref _physicsEngineIndex); - DrawEngineSelector("Debug UI", ref player.debugUiId, ref _debugUIs, ref _debugUINames, ref _debugUIIndex); if (_toggleDebug = EditorGUILayout.BeginFoldoutHeaderGroup(_toggleDebug, "Debug")) { @@ -71,35 +59,5 @@ public override void OnInspectorGUI() EditorGUILayout.EndFoldoutHeaderGroup(); } - - private void DrawEngineSelector(string engineName, ref string engineId, ref T[] instances, ref string[] names, ref int index) where T : IEngine - { - if (instances == null) { - // get all instances - instances = EngineProvider.GetAll().ToArray(); - names = instances.Select(x => x.Name).ToArray(); - - // set the current index based on the table's ID - index = -1; - for (var i = 0; i < instances.Length; i++) { - if (EngineProvider.GetId(instances[i]) == engineId) { - index = i; - break; - } - } - if (instances.Length > 0 && index < 0) { - index = 0; - engineId = EngineProvider.GetId(instances[index]); - } - } - if (names.Length == 0) { - return; - } - var newIndex = EditorGUILayout.Popup(engineName, index, names); - if (index != newIndex) { - index = newIndex; - engineId = EngineProvider.GetId(instances[newIndex]); - } - } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs index cb13b132f..9677f45c7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Inspectors/TroughInspector.cs @@ -47,6 +47,7 @@ public class TroughInspector : MainInspector private SerializedProperty _playfieldEntrySwitchProperty; private SerializedProperty _playfieldExitKickerProperty; private SerializedProperty _ballCountProperty; + private SerializedProperty _ballProperty; private SerializedProperty _switchCountProperty; private SerializedProperty _jamSwitchProperty; private SerializedProperty _rollTimeProperty; @@ -60,6 +61,7 @@ protected override void OnEnable() _typeProperty = serializedObject.FindProperty(nameof(TroughComponent.Type)); _playfieldEntrySwitchProperty = serializedObject.FindProperty(nameof(TroughComponent._playfieldEntrySwitch)); _playfieldExitKickerProperty = serializedObject.FindProperty(nameof(TroughComponent.PlayfieldExitKicker)); + _ballProperty = serializedObject.FindProperty(nameof(TroughComponent.Ball)); _ballCountProperty = serializedObject.FindProperty(nameof(TroughComponent.BallCount)); _switchCountProperty = serializedObject.FindProperty(nameof(TroughComponent.SwitchCount)); _jamSwitchProperty = serializedObject.FindProperty(nameof(TroughComponent.JamSwitch)); @@ -78,6 +80,11 @@ public override void OnInspectorGUI() DropDownProperty("Type", _typeProperty, TypeLabels, TypeValues); + PropertyField(_ballProperty, "Ball Prefab"); + if (MainComponent.Ball && !MainComponent.Ball.GetComponent()) { + EditorGUILayout.HelpBox("Ball prefab must contain a ball component.", MessageType.Error); + } + if (MainComponent.Type != TroughType.ClassicSingleBall) { PropertyField(_ballCountProperty); } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs index 29cb31b4b..c5e1a0297 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Managers/Lamp/LampListViewItemRenderer.cs @@ -101,7 +101,9 @@ protected override void OnIconClick(LampListData data, bool pressedDown) var lamp = player.Lamp(data.Device); lamp?.OnChange(pressedDown); if (player.LampStatuses.ContainsKey(data.Id)) { - player.LampStatuses[data.Id].IsOn = pressedDown; + var status = player.LampStatuses[data.Id]; + status.IsOn = pressedDown; + player.LampStatuses[data.Id] = status; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs index d47362d73..1a9abde26 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Utils/Icons.cs @@ -62,6 +62,7 @@ public IconVariant(string name, IconSize size, IconColor color) } private const string AssetLibraryName = "asset_library"; + private const string BallName = "ball"; private const string BallRollerName = "ball_roller"; private const string BoltName = "bolt"; private const string BumperName = "bumper"; @@ -85,6 +86,7 @@ public IconVariant(string name, IconSize size, IconColor color) private const string PlugName = "plug"; private const string PlungerName = "plunger"; private const string PrimitiveName = "primitive"; + private const string PhysicsName = "physics"; private const string RampName = "ramp"; private const string RotatorName = "rotator"; private const string RubberName = "rubber"; @@ -114,9 +116,9 @@ public IconVariant(string name, IconSize size, IconColor color) private const string DisplayEventName = "display_event"; private static readonly string[] Names = { - AssetLibraryName, BallRollerName, BoltName, BumperName, CalendarName, CannonName, CoilName, DropTargetBankName, DropTargetName, FlasherName, + AssetLibraryName, BallRollerName, BallName, BoltName, BumperName, CalendarName, CannonName, CoilName, DropTargetBankName, DropTargetName, FlasherName, FlipperName, GateName, GateLifterName, HitTargetName, KeyName, KickerName, LightGroupName, LightName, MechName, MechPinMameName, PlayfieldName, PlugName, - PlungerName, PrimitiveName, RampName, RotatorName, RubberName, ScoreReelName, ScoreReelSingleName, SlingshotName, SpinnerName, SurfaceName, + PhysicsName, PlungerName, PrimitiveName, RampName, RotatorName, RubberName, ScoreReelName, ScoreReelSingleName, SlingshotName, SpinnerName, SurfaceName, SwitchNcName, SwitchNoName, TableName, TeleporterName, TriggerName, TroughName, CoilEventName, SwitchEventName, LampEventName, LampSeqName, MetalWireGuideName, PlayerVariableName, PlayerVariableEventName, TableVariableName, TableVariableEventName, UpdateDisplayName, DisplayEventName @@ -163,6 +165,7 @@ private static IIconLookup[] GetLookups() { } public static Texture2D AssetLibrary(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(AssetLibraryName, size, color); + public static Texture2D Ball(IconSize size = IconSize.Small, IconColor color = IconColor.Gray) => Instance.GetItem(BallName, size, color); public static Texture2D BallRoller(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(BallRollerName, size, color); public static Texture2D Bolt(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(BoltName, size, color); public static Texture2D Bumper(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(BumperName, size, color); @@ -183,6 +186,7 @@ private static IIconLookup[] GetLookups() { public static Texture2D Mech(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MechName, size, color); public static Texture2D MechPinMame(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MechPinMameName, size, color); public static Texture2D MetalWireGuide(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(MetalWireGuideName, size, color); + public static Texture2D Physics(IconSize size = IconSize.Small, IconColor color = IconColor.Gray) => Instance.GetItem(PhysicsName, size, color); public static Texture2D Playfield(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlayfieldName, size, color); public static Texture2D Plug(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlugName, size, color); public static Texture2D Plunger(IconSize size = IconSize.Large, IconColor color = IconColor.Gray) => Instance.GetItem(PlungerName, size, color); @@ -273,6 +277,7 @@ internal class IconLookup : IIconLookup public Texture2D Lookup(T mb, IconSize size = IconSize.Large, IconColor color = IconColor.Gray) where T : class { switch (mb) { + case BallComponent _: return Icons.Ball(size, color); case BallRollerComponent _: return Icons.BallRoller(size, color); case BumperComponent _: return Icons.Bumper(size, color); case CannonRotatorComponent _: return Icons.Cannon(size, color); @@ -285,6 +290,7 @@ public Texture2D Lookup(T mb, IconSize size = IconSize.Large, IconColor color case KickerComponent _: return Icons.Kicker(size, color); case LightComponent _: return Icons.Light(size, color); case LightGroupComponent _: return Icons.LightGroup(size, color); + case PhysicsEngine _: return Icons.Physics(size, color); case PlungerComponent _: return Icons.Plunger(size, color); case PlayfieldComponent _: return Icons.Playfield(size, color); case PrimitiveComponent _: return Icons.Primitive(size, color); @@ -309,6 +315,7 @@ public Texture2D Lookup(T mb, IconSize size = IconSize.Large, IconColor color public void DisableGizmoIcons() { + Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); Icons.DisableGizmo(); @@ -337,6 +344,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.Editor/VPT/ColliderInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs index 5ce721c78..fb6248fdc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/ColliderInspector.cs @@ -80,6 +80,10 @@ public override void OnInspectorGUI() var showColliders = EditorGUILayout.Toggle("Show Colliders", ColliderComponent.ShowColliderMesh); refresh = refresh || showColliders != ColliderComponent.ShowColliderMesh; ColliderComponent.ShowColliderMesh = showColliders; + + var showOctree = EditorGUILayout.Toggle("Show Octree", ColliderComponent.ShowColliderOctree); + refresh = refresh || showOctree != ColliderComponent.ShowColliderOctree; + ColliderComponent.ShowColliderOctree = showOctree; } EditorGUILayout.EndFoldoutHeaderGroup(); diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs index baa2cb860..7ebc8219a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VPT/Primitive/PrimitiveInspector.cs @@ -17,7 +17,6 @@ // ReSharper disable AssignmentInConditionalExpression using System.Linq; -using Unity.Entities; using UnityEditor; using UnityEngine; using VisualPinball.Engine.VPT.Primitive; @@ -90,8 +89,8 @@ public static void MakePrimitive() var cc = go.AddComponent(); cc.enabled = true; - var cte = go.AddComponent(); - cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; + // var cte = go.AddComponent(); + // cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; } } @@ -117,8 +116,8 @@ public static void MakeCollider() var cc = go.AddComponent(); cc.enabled = true; - var cte = go.AddComponent(); - cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; + // var cte = go.AddComponent(); + // cte.ConversionMode = ConvertToEntity.Mode.ConvertAndInjectGameObject; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef b/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef index 1cbf50624..f67a5a51e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/VisualPinball.Unity.Editor.asmdef @@ -2,12 +2,14 @@ "name": "VisualPinball.Unity.Editor", "rootNamespace": "", "references": [ + "Unity.Collections", "Unity.Entities", "Unity.Entities.Hybrid", "Unity.Formats.Fbx.Editor", "Unity.Mathematics", "Unity.Transforms", "Unity.InputSystem", + "Unity.Scenes", "VisualPinball.Engine", "VisualPinball.Unity", "VisualPinball.Unity.Patcher" diff --git a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Matcher/TablePatcher.cs b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Matcher/TablePatcher.cs index 56d6183a4..c3f00d545 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Patcher/Matcher/TablePatcher.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Patcher/Matcher/TablePatcher.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; @@ -26,6 +25,7 @@ using VisualPinball.Unity.Editor; using Light = UnityEngine.Light; using Object = UnityEngine.Object; +using VpeLight = VisualPinball.Engine.VPT.Light.Light; namespace VisualPinball.Unity.Patcher { @@ -373,7 +373,7 @@ protected static void LightPos(GameObject go, float x, float y, float z) /// protected static LightComponent CreateLight(string name, float x, float y, GameObject parentGo) { - var light = Engine.VPT.Light.Light.GetDefault(name, x, y); + var light = VpeLight.GetDefault(name, x, y); light.Data.ShowBulbMesh = false; var prefab = RenderPipeline.Current.PrefabProvider.CreateLight(); @@ -401,7 +401,7 @@ protected GameObject ConvertToInsertLight(LightComponent lo) var name = lo.name; var parent = lo.transform.parent.gameObject; Object.DestroyImmediate(lo.gameObject); - return CreateInsertLight(TableContainer.Get(name).Data, parent); + return CreateInsertLight(TableContainer.Get(name).Data, parent); } /// diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json index f86ab9853..1dc52f55f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/manifest.json @@ -1,7 +1,9 @@ { "dependencies": { - "com.unity.test-framework": "1.1.31", - "com.unity.testtools.codecoverage": "1.1.0", + "com.unity.ai.navigation": "1.1.5", + "com.unity.test-framework": "1.1.33", + "com.unity.testtools.codecoverage": "1.2.4", + "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.6", "com.unity.ugui": "1.0.0", "org.visualpinball.engine.unity": "file:../../../..", "com.unity.modules.ai": "1.0.0", @@ -36,5 +38,13 @@ "com.unity.modules.wind": "1.0.0", "com.unity.modules.xr": "1.0.0" }, - "scopedRegistries": [] + "scopedRegistries": [ + { + "name": "VPE Registry", + "url": "https://registry.visualpinball.org", + "scopes": [ + "com.bartofzo" + ] + } + ] } 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 54df0a3d5..b8e220bc6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/Packages/packages-lock.json @@ -1,47 +1,67 @@ { "dependencies": { "com.autodesk.fbx": { - "version": "4.1.1", + "version": "4.2.1", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.burst": { - "version": "1.6.5", + "com.bartofzo.nativetrees": { + "version": "0.1.6", "depth": 1, "source": "registry", + "dependencies": { + "com.unity.burst": "1.8.9", + "com.unity.collections": "2.1.4" + }, + "url": "https://registry.visualpinball.org" + }, + "com.unity.ai.navigation": { + "version": "1.1.5", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" + }, + "com.unity.burst": { + "version": "1.8.9", + "depth": 2, + "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1" }, "url": "https://packages.unity.com" }, "com.unity.collections": { - "version": "0.15.0-preview.21", + "version": "2.1.4", "depth": 2, "source": "registry", "dependencies": { - "com.unity.test-framework.performance": "2.3.1-preview", - "com.unity.burst": "1.4.1" + "com.unity.burst": "1.8.4", + "com.unity.modules.unityanalytics": "1.0.0", + "com.unity.nuget.mono-cecil": "1.11.4" }, "url": "https://packages.unity.com" }, "com.unity.entities": { - "version": "0.17.0-preview.42", + "version": "1.0.16", "depth": 1, "source": "registry", "dependencies": { - "com.unity.burst": "1.4.1", - "com.unity.properties": "1.5.0-preview", - "com.unity.serialization": "1.5.0-preview", - "com.unity.collections": "0.15.0-preview.21", - "com.unity.mathematics": "1.2.1", + "com.unity.burst": "1.8.8", + "com.unity.serialization": "3.1.1", + "com.unity.collections": "2.1.4", + "com.unity.mathematics": "1.2.6", "com.unity.modules.assetbundle": "1.0.0", - "com.unity.test-framework.performance": "2.3.1-preview", - "com.unity.nuget.mono-cecil": "0.1.6-preview.2", - "com.unity.jobs": "0.8.0-preview.23", - "com.unity.scriptablebuildpipeline": "1.9.0", - "com.unity.platforms": "0.10.0-preview.10" + "com.unity.modules.audio": "1.0.0", + "com.unity.modules.unitywebrequest": "1.0.0", + "com.unity.test-framework.performance": "3.0.2", + "com.unity.nuget.mono-cecil": "1.11.4", + "com.unity.scriptablebuildpipeline": "1.20.2", + "com.unity.profiling.core": "1.0.2" }, "url": "https://packages.unity.com" }, @@ -53,17 +73,17 @@ "url": "https://packages.unity.com" }, "com.unity.formats.fbx": { - "version": "4.1.2", + "version": "4.2.1", "depth": 1, "source": "registry", "dependencies": { - "com.unity.timeline": "1.5.2", - "com.autodesk.fbx": "4.1.1" + "com.unity.timeline": "1.7.1", + "com.autodesk.fbx": "4.2.1" }, "url": "https://packages.unity.com" }, "com.unity.inputsystem": { - "version": "1.3.0", + "version": "1.7.0", "depth": 1, "source": "registry", "dependencies": { @@ -71,99 +91,69 @@ }, "url": "https://packages.unity.com" }, - "com.unity.jobs": { - "version": "0.8.0-preview.23", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "0.15.0-preview.21", - "com.unity.mathematics": "1.2.1" - }, - "url": "https://packages.unity.com" - }, "com.unity.mathematics": { - "version": "1.2.5", - "depth": 1, + "version": "1.2.6", + "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.mono-cecil": { - "version": "1.10.1", + "version": "1.11.4", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.nuget.newtonsoft-json": { - "version": "2.0.0", - "depth": 3, + "com.unity.profiling.core": { + "version": "1.0.2", + "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.platforms": { - "version": "0.10.0-preview.10", + "com.unity.scriptablebuildpipeline": { + "version": "1.21.20", "depth": 2, "source": "registry", - "dependencies": { - "com.unity.properties": "1.6.0-preview", - "com.unity.properties.ui": "1.6.2-preview.1", - "com.unity.scriptablebuildpipeline": "1.6.4-preview", - "com.unity.serialization": "1.6.2-preview" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.properties": { - "version": "1.6.0-preview", - "depth": 3, + "com.unity.serialization": { + "version": "3.1.1", + "depth": 2, "source": "registry", "dependencies": { - "com.unity.nuget.mono-cecil": "0.1.6-preview.2", - "com.unity.test-framework.performance": "2.3.1-preview" + "com.unity.collections": "2.1.4", + "com.unity.burst": "1.7.2" }, "url": "https://packages.unity.com" }, - "com.unity.properties.ui": { - "version": "1.6.2-preview.1", - "depth": 3, + "com.unity.settings-manager": { + "version": "2.0.1", + "depth": 1, "source": "registry", - "dependencies": { - "com.unity.properties": "1.6.0-preview", - "com.unity.serialization": "1.6.1-preview", - "com.unity.modules.uielements": "1.0.0" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.scriptablebuildpipeline": { - "version": "1.19.6", - "depth": 2, + "com.unity.sysroot": { + "version": "2.0.7", + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, - "com.unity.serialization": { - "version": "1.6.2-preview", - "depth": 3, + "com.unity.sysroot.linux-x86_64": { + "version": "2.0.6", + "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "0.12.0-preview.13", - "com.unity.burst": "1.3.5", - "com.unity.jobs": "0.5.0-preview.14", - "com.unity.properties": "1.6.0-preview", - "com.unity.test-framework.performance": "2.3.1-preview" + "com.unity.sysroot": "2.0.7" }, "url": "https://packages.unity.com" }, - "com.unity.settings-manager": { - "version": "1.0.3", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, "com.unity.test-framework": { - "version": "1.1.31", + "version": "1.1.33", "depth": 0, "source": "registry", "dependencies": { @@ -174,17 +164,17 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { - "version": "2.3.1-preview", + "version": "3.0.2", "depth": 2, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.0", - "com.unity.nuget.newtonsoft-json": "2.0.0-preview" + "com.unity.test-framework": "1.1.31", + "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.testtools.codecoverage": { - "version": "1.1.0", + "version": "1.2.4", "depth": 0, "source": "registry", "dependencies": { @@ -194,7 +184,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.6.4", + "version": "1.7.5", "depth": 2, "source": "registry", "dependencies": { @@ -205,6 +195,16 @@ }, "url": "https://packages.unity.com" }, + "com.unity.toolchain.win-x86_64-linux-x86_64": { + "version": "2.0.6", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.sysroot": "2.0.7", + "com.unity.sysroot.linux-x86_64": "2.0.6" + }, + "url": "https://packages.unity.com" + }, "com.unity.ugui": { "version": "1.0.0", "depth": 0, @@ -219,11 +219,9 @@ "depth": 0, "source": "local", "dependencies": { - "com.unity.burst": "1.6.5", - "com.unity.entities": "0.17.0-preview.42", + "com.unity.entities": "1.0.16", + "com.bartofzo.nativetrees": "0.1.6", "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.3.0", "com.unity.ugui": "1.0.0", "com.unity.test-framework": "1.1.31" @@ -361,17 +359,6 @@ "version": "1.0.0", "depth": 0, "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.uielementsnative": "1.0.0" - } - }, - "com.unity.modules.uielementsnative": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/EntitiesClientSettings.asset b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/EntitiesClientSettings.asset new file mode 100644 index 000000000..3f1b7c49f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/EntitiesClientSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &1 +MonoBehaviour: + m_ObjectHideFlags: 53 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e2ea235c1fcfe29488ed97c467a0da53, type: 3} + m_Name: + m_EditorClassIdentifier: + FilterSettings: + ExcludedBakingSystemAssemblies: [] diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/MemorySettings.asset b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/MemorySettings.asset new file mode 100644 index 000000000..5b5faceca --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/MemorySettings.asset @@ -0,0 +1,35 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!387306366 &1 +MemorySettings: + m_ObjectHideFlags: 0 + m_EditorMemorySettings: + m_MainAllocatorBlockSize: -1 + m_ThreadAllocatorBlockSize: -1 + m_MainGfxBlockSize: -1 + m_ThreadGfxBlockSize: -1 + m_CacheBlockSize: -1 + m_TypetreeBlockSize: -1 + m_ProfilerBlockSize: -1 + m_ProfilerEditorBlockSize: -1 + m_BucketAllocatorGranularity: -1 + m_BucketAllocatorBucketsCount: -1 + m_BucketAllocatorBlockSize: -1 + m_BucketAllocatorBlockCount: -1 + m_ProfilerBucketAllocatorGranularity: -1 + m_ProfilerBucketAllocatorBucketsCount: -1 + m_ProfilerBucketAllocatorBlockSize: -1 + m_ProfilerBucketAllocatorBlockCount: -1 + m_TempAllocatorSizeMain: -1 + m_JobTempAllocatorBlockSize: -1 + m_BackgroundJobTempAllocatorBlockSize: -1 + m_JobTempAllocatorReducedBlockSize: -1 + m_TempAllocatorSizeGIBakingWorker: -1 + m_TempAllocatorSizeNavMeshWorker: -1 + m_TempAllocatorSizeAudioWorker: -1 + m_TempAllocatorSizeCloudWorker: -1 + m_TempAllocatorSizeGfx: -1 + m_TempAllocatorSizeJobWorker: -1 + m_TempAllocatorSizeBackgroundWorker: -1 + m_TempAllocatorSizePreloadManager: -1 + m_PlatformMemorySettings: {} diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/PackageManagerSettings.asset b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/PackageManagerSettings.asset index be4a7974e..08d580056 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/PackageManagerSettings.asset +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/PackageManagerSettings.asset @@ -12,10 +12,11 @@ MonoBehaviour: m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: - m_EnablePreviewPackages: 0 - m_EnablePackageDependencies: 0 + m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 0 m_Registries: - m_Id: main @@ -24,20 +25,20 @@ MonoBehaviour: m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 - m_UserSelectedRegistryName: + m_ConfigSource: 0 + - m_Id: scoped:project:VPE Registry + m_Name: VPE Registry + m_Url: https://registry.visualpinball.org + m_Scopes: + - com.bartofzo + m_IsDefault: 0 + m_Capabilities: 0 + m_ConfigSource: 4 + m_UserSelectedRegistryName: VPE Registry m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: - m_ErrorMessage: - m_Original: - m_Id: - m_Name: - m_Url: - m_Scopes: [] - m_IsDefault: 0 - m_Capabilities: 0 m_Modified: 0 - m_Name: - m_Url: - m_Scopes: - - - m_SelectedScopeIndex: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -854 + m_OriginalInstanceId: -856 + m_LoadAssets: 0 diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt b/VisualPinball.Unity/VisualPinball.Unity.Test/TestProject~/ProjectSettings/ProjectVersion.txt index 6b2c79b80..37f39b7bb 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.3.0f1 -m_EditorVersionWithRevision: 2021.3.0f1 (6eacc8284459) +m_EditorVersion: 2022.3.11f1 +m_EditorVersionWithRevision: 2022.3.11f1 (d00248457e15) diff --git a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs index 9406aa9a6..f80dabd3a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Test/VPT/PrimitiveTests.cs @@ -21,7 +21,6 @@ using VisualPinball.Engine.Test.VPT.Primitive; using VisualPinball.Engine.VPT.Table; using VisualPinball.Unity.Editor; -using Assert = Unity.Assertions.Assert; namespace VisualPinball.Unity.Test { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections.meta new file mode 100644 index 000000000..40b7a7b10 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a8226ee24ab34f1ea5940bfb5736c9d5 +timeCreated: 1696369879 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs b/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs new file mode 100644 index 000000000..f1df0a3fe --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("VisualPinball.Unity.Collections")] diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs.meta new file mode 100644 index 000000000..bae708483 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/AssemblyInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b49ec6e82c524d6f920855969a015f8f +timeCreated: 1696368718 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs new file mode 100644 index 000000000..4070c8752 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs @@ -0,0 +1,100 @@ +// MIT License +// +// Copyright (c) 2022 Timothy Raines +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; + +namespace VisualPinball.Unity.Collections +{ + /// + /// Part of Tertle's excellent Core extensions. + /// + /// See https://gitlab.com/tertle/com.bovinelabs.core + /// + public static unsafe class CollectionExtensions + { + /// + /// Returns a reference to the value associated with the specified key. Does not copy the value. + /// + /// The hashmap to retrieve the reference to the value from + /// The key of the hashmap pointing to the value + /// Type of the hashmap's key + /// Type of the hashmap's value + /// Reference to the value + /// Source + public static ref TValue GetValueByRef(this NativeParallelHashMap map, TKey key) + where TKey : unmanaged, IEquatable + where TValue : unmanaged + { + return ref map.m_HashMapData.m_Buffer->GetValueByRef(key); + } + + /// Source + private static ref TValue GetValueByRef(this ref UnsafeParallelHashMapData data, TKey key) + where TKey : struct, IEquatable + where TValue : struct + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (data.allocatedIndexLength <= 0) + { + throw new KeyNotFoundException(); + } +#endif + + // First find the slot based on the hash + var buckets = (int*)data.buckets; + var bucket = key.GetHashCode() & data.bucketCapacityMask; + var entryIdx = buckets[bucket]; + + var nextPtrs = (int*)data.next; + while (!UnsafeUtility.ReadArrayElement(data.keys, entryIdx).Equals(key)) + { + entryIdx = nextPtrs[entryIdx]; +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if ((entryIdx < 0) || (entryIdx >= data.keyCapacity)) + { + throw new KeyNotFoundException("Cannot find key " + key); + } +#endif + } + + // Read the value + return ref UnsafeUtility.ArrayElementAsRef(data.values, entryIdx); + } + + #region Own stuff + + public static ref T GetElementAsRef(this NativeArray array, int index) where T : unmanaged + { + return ref UnsafeUtility.ArrayElementAsRef(array.GetUnsafePtr(), index); + } + + public static ref T GetElementAsRef(this NativeList list, int index) where T : unmanaged + { + return ref UnsafeUtility.ArrayElementAsRef(list.GetUnsafePtr(), index); + } + + #endregion + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs.meta new file mode 100644 index 000000000..8d7b91bbf --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2d9514da4f354444b3350df327f761d8 +timeCreated: 1696368479 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref new file mode 100644 index 000000000..369c8b2c6 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref @@ -0,0 +1,3 @@ +{ + "reference": "GUID:e0cd26848372d4e5c891c569017e11f1" +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref.meta new file mode 100644 index 000000000..4c2539af2 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/CollectionReference.asmref.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a8ab32aaa18d4966b7a5974294f069c2 +timeCreated: 1696369605 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs b/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs new file mode 100644 index 000000000..2779d7d1d --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs @@ -0,0 +1,124 @@ +using System; +using System.Runtime.CompilerServices; +using Unity.Collections; +using UnityEngine; +using static Unity.Mathematics.math; + +namespace VisualPinball.Unity.Collections +{ + /// + /// Burst friendly curve implementation used to efficiently work with animation curves in the job system. + /// + public struct NativeCurve : IDisposable + { + /// + /// Informs if the native data structure has an allocated memory buffer. + /// + public bool isCreated => m_Values.IsCreated; + + NativeArray m_Values; + WrapMode m_PreWrapMode; + WrapMode m_PostWrapMode; + + void InitializeValues(int count, Allocator allocator = Allocator.Persistent) + { + if (m_Values.IsCreated) + m_Values.Dispose(); + + m_Values = new NativeArray(count, allocator, NativeArrayOptions.UninitializedMemory); + } + + /// + /// Re-initialize native curve data with new Animation curve. + /// + /// Curve ground truth to initialize from. + /// Number of samples to use when converting from animation curve to native curve. + public void Update(AnimationCurve curve, int resolution) + { + if (curve == null) + return; + + m_PreWrapMode = curve.preWrapMode; + m_PostWrapMode = curve.postWrapMode; + + if (!m_Values.IsCreated || m_Values.Length != resolution) + InitializeValues(resolution); + + for (int i = 0; i < resolution; i++) + m_Values[i] = curve.Evaluate((float)i / (float)resolution); + } + + /// + /// Evaluate value along the underlying native curve. + /// + /// Location along curve to evaluate. + /// Value along curve at given location t. + public float Evaluate(float t) + { + var count = m_Values.Length; + + if (count == 1) + return m_Values[0]; + + if (t < 0f) + { + switch (m_PreWrapMode) + { + default: + return m_Values[0]; + case WrapMode.Loop: + t = 1f - (abs(t) % 1f); + break; + case WrapMode.PingPong: + t = PingPong(t, 1f); + break; + } + } + else if (t > 1f) + { + switch (m_PostWrapMode) + { + default: + return m_Values[count - 1]; + case WrapMode.Loop: + t %= 1f; + break; + case WrapMode.PingPong: + t = PingPong(t, 1f); + break; + } + } + + var it = t * (count - 1); + + var lower = (int)it; + var upper = lower + 1; + if (upper >= count) + upper = count - 1; + + return lerp(m_Values[lower], m_Values[upper], it - lower); + } + + /// + /// Dispose native collection. + /// + public void Dispose() + { + if (m_Values.IsCreated) + m_Values.Dispose(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + float Repeat(float t, float length) + { + return clamp(t - floor(t / length) * length, 0, length); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + float PingPong(float t, float length) + { + t = Repeat(t, length * 2f); + return length - abs(t - length); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs.meta new file mode 100644 index 000000000..20876e515 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/NativeCurve.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3b29d38c75fe47968f0c82207d784ba6 +timeCreated: 1698489496 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs b/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs new file mode 100644 index 000000000..8fac3f300 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs @@ -0,0 +1,66 @@ +// MIT License +// +// Copyright (c) 2022 Timothy Raines +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using Unity.Collections.LowLevel.Unsafe; + +namespace VisualPinball.Unity +{ + public readonly unsafe struct UnmanagedArray where T : unmanaged + { + private readonly void* _buffer; + public readonly int Length; + + public UnmanagedArray(void* buffer, int length) + { + _buffer = buffer; + Length = length; + } + + public T this[int index] + { + get { + if (index < 0 || index >= Length) { + throw new IndexOutOfRangeException(); + } + return UnsafeUtility.ReadArrayElement(_buffer, index); + } + } + + public ref T GetAsRef(int index) + { + if (index < 0 || index >= Length) { + throw new IndexOutOfRangeException(); + } + return ref UnsafeUtility.ArrayElementAsRef(_buffer, index); + } + + public T[] ToArray() + { + var array = new T[Length]; + for (var i = 0; i < Length; i++) { + array[i] = UnsafeUtility.ReadArrayElement(_buffer, i); + } + return array; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs.meta new file mode 100644 index 000000000..d95738c95 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Collections/UnmanagedArray.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b19e3cbc43a1490a80a425f1ba15d1ec +timeCreated: 1698533074 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs b/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs new file mode 100644 index 000000000..8428d9583 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs @@ -0,0 +1,22 @@ +using System; +using System.IO; + +namespace VisualPinball.Unity +{ + public static class DebugLogger + { + private const string FileName = @"C:\Temp\physdebug\vpe-2022.log"; + + public static void ClearLog() + { + if (File.Exists(FileName)) { + File.Delete(FileName); + } + } + + public static void Log(string msg) + { + File.AppendAllText(FileName, msg + Environment.NewLine); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs.meta new file mode 100644 index 000000000..3630278de --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/DebugLogger.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 2187d14cf7304e5b8124cf377c7b24e0 +timeCreated: 1695983818 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Common/EdgeSet.cs b/VisualPinball.Unity/VisualPinball.Unity/Common/EdgeSet.cs index 206def19b..eaab41fbc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Common/EdgeSet.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Common/EdgeSet.cs @@ -14,22 +14,24 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using System.Collections.Generic; +using System; +using Unity.Collections; +using Unity.Mathematics; namespace VisualPinball.Unity { - internal class EdgeSet + internal struct EdgeSet : IDisposable { - private readonly HashSet _edges; + private NativeParallelHashSet _edges; - internal static EdgeSet Get() + internal static EdgeSet Get(Allocator allocator, int capacity = 1024) { - return new EdgeSet(); + return new EdgeSet(allocator, capacity); } - private EdgeSet() + private EdgeSet(Allocator allocator, int capacity) { - _edges = new HashSet(); + _edges = new NativeParallelHashSet(capacity, allocator); } private void Add(int i, int j) { @@ -48,9 +50,11 @@ internal bool ShouldAddHitEdge(int i, int j) { return false; } - private static long GetKey(int i, int j) + private static long GetKey(int i, int j) => ((long) math.min(i, j) << 32) + math.max(i, j); + + public void Dispose() { - return ((long) System.Math.Min(i, j) << 32) + System.Math.Max(i, j); + _edges.Dispose(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MeshExtensions.cs b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MeshExtensions.cs index 458063a31..4b2881552 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Extensions/MeshExtensions.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Extensions/MeshExtensions.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System; +using Unity.Collections; using UnityEngine; using UnityEngine.Rendering; using VisualPinball.Engine.Math; @@ -28,19 +29,21 @@ public static class MeshExtensions public static Mesh ToVpMesh(this UnityEngine.Mesh unityMesh) { + using var meshDataArray = UnityEngine.Mesh.AcquireReadOnlyMeshData(unityMesh); + var meshData = meshDataArray[0]; var vpMesh = new Mesh(unityMesh.name) { - Vertices = new Vertex3DNoTex2[unityMesh.vertexCount] + Vertices = new Vertex3DNoTex2[meshData.vertexCount] }; - var unityVertices = unityMesh.vertices; - var unityNormals = unityMesh.normals; + var unityVertices = new NativeArray(meshData.vertexCount, Allocator.TempJob); + var unityNormals = new NativeArray(meshData.vertexCount, Allocator.TempJob); + meshData.GetVertices(unityVertices); + meshData.GetNormals(unityNormals); for (var i = 0; i < vpMesh.Vertices.Length; i++) { - var unityVertex = unityVertices[i]; - var unityNormal = unityNormals[i]; var unityUv = unityMesh.uv[i]; vpMesh.Vertices[i] = new Vertex3DNoTex2( - unityVertex.x, unityVertex.y, unityVertex.z, - unityNormal.x, unityNormal.y, unityNormal.z, + unityVertices[i].x, unityVertices[i].y, unityVertices[i].z, + unityNormals[i].x, unityNormals[i].y, unityNormals[i].z, unityUv.x, -unityUv.y ); } vpMesh.Indices = unityMesh.triangles; @@ -62,17 +65,18 @@ public static Mesh ToVpMesh(this UnityEngine.Mesh unityMesh) var frameData = new Mesh.VertData[unityMesh.vertexCount]; for (var j = 0; j < unityMesh.vertexCount; j++) { - var vertex = deltaVertices[j] + unityVertices[j]; - var normal = deltaNormals[j] + unityNormals[j]; frameData[j] = new Mesh.VertData( - vertex.x, vertex.y, vertex.z, - normal.x, normal.y, normal.z); + deltaVertices[j].x + unityVertices[j].x, deltaVertices[j].y + unityVertices[j].y, deltaVertices[j].z + unityVertices[j].z, + deltaNormals[j].x + unityNormals[j].x, deltaNormals[j].y + unityNormals[j].y, deltaNormals[j].z + unityNormals[j].z); } vpMesh.AnimationFrames.Add(frameData); } } + unityVertices.Dispose(); + unityNormals.Dispose(); + return vpMesh; } @@ -152,4 +156,3 @@ public static Vector3 ToUnityNormalVector3(this Mesh.VertData vpVert) } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs index 436b94824..2b450be6a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/BallRollerComponent.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 Unity.Collections; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using UnityEngine.InputSystem; @@ -24,15 +22,14 @@ namespace VisualPinball.Unity { public class BallRollerComponent : MonoBehaviour { + private PhysicsEngine _physicsEngine; private PlayfieldComponent _playfield; private Matrix4x4 _ltw; private Matrix4x4 _wtl; private Plane _playfieldPlane; - private EntityManager _entityManager; - private Entity _ballEntity = Entity.Null; - private EntityQuery _ballEntityQuery; + private int _ballId = 0; private void Awake() { @@ -46,9 +43,7 @@ private void Awake() var p2 = _ltw.MultiplyPoint(new Vector3(100f, 100f, z)); var p3 = _ltw.MultiplyPoint(new Vector3(100f, -100f, z)); _playfieldPlane.Set3Points(p1, p2, p3); - - _entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - _ballEntityQuery = _entityManager.CreateEntityQuery(typeof(BallData)); + _physicsEngine = GetComponentInChildren(); } private void Update() @@ -60,21 +55,24 @@ private void Update() // find nearest ball if (Mouse.current.middleButton.wasPressedThisFrame) { if (GetCursorPositionOnPlayfield(out var mousePosition)) { - var ballEntities = _ballEntityQuery.ToEntityArray(Allocator.Temp); var nearestDistance = float.PositiveInfinity; - BallData nearestBall = default; + BallState nearestBall = default; var ballFound = false; - foreach (var ballEntity in ballEntities) { - var ballData = _entityManager.GetComponentData(ballEntity); - if (ballData.IsFrozen) { - continue; - } - var distance = math.distance(mousePosition, ballData.Position.xy); - if (distance < nearestDistance) { - nearestDistance = distance; - nearestBall = ballData; - ballFound = true; - _ballEntity = ballEntity; + + using (var enumerator = _physicsEngine.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + var ball = enumerator.Current.Value; + + if (ball.IsFrozen) { + continue; + } + var distance = math.distance(mousePosition, ball.Position.xy); + if (distance < nearestDistance) { + nearestDistance = distance; + nearestBall = ball; + ballFound = true; + _ballId = ball.Id; + } } } @@ -83,26 +81,24 @@ private void Update() } } - } else if (Mouse.current.middleButton.isPressed && _ballEntity != Entity.Null) { + } else if (Mouse.current.middleButton.isPressed && _ballId != 0) { if (GetCursorPositionOnPlayfield(out var mousePosition)) { - var ballData = _entityManager.GetComponentData(_ballEntity); - UpdateBall(ref ballData, mousePosition); + ref var ball = ref _physicsEngine.BallState(_ballId); + UpdateBall(ref ball, mousePosition); } } - if (Mouse.current.middleButton.wasReleasedThisFrame && _ballEntity != Entity.Null) { - var ballData = _entityManager.GetComponentData(_ballEntity); + if (Mouse.current.middleButton.wasReleasedThisFrame && _ballId != 0) { + ref var ballData = ref _physicsEngine.BallState(_ballId); ballData.ManualControl = false; - _entityManager.SetComponentData(_ballEntity, ballData); - _ballEntity = Entity.Null; + _ballId = 0; } } - private void UpdateBall(ref BallData ballData, float2 position) + private void UpdateBall(ref BallState ballState, float2 position) { - ballData.ManualControl = true; - ballData.ManualPosition = position; - _entityManager.SetComponentData(_ballEntity, ballData); + ballState.ManualControl = true; + ballState.ManualPosition = position; } private bool GetCursorPositionOnPlayfield(out float2 position) diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/CameraTranslateAndOrbit.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/CameraTranslateAndOrbit.cs index f59d8b3c3..0da2e2d91 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/CameraTranslateAndOrbit.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/CameraTranslateAndOrbit.cs @@ -51,12 +51,11 @@ private void Start() { var playfield = FindObjectOfType(); - var pfr = playfield.GetComponent(); - if (pfr) { + var pfr = playfield == null ? null : playfield.GetComponent(); + if (pfr != null) { positionOffset = pfr.bounds.center; } - _radius = Vector3.Distance(Vector3.zero, transform.position); transformCache = transform; _focusPoint = transformCache.forward * -1f * _radius; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs index 75fa703f9..d0b5c4316 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/CoilPlayer.cs @@ -77,7 +77,7 @@ public void OnStart() // check if device exists if (!_coilDevices.ContainsKey(coilMapping.Device)) { - Logger.Error($"Unknown coil device \"{coilMapping.Device.name}\"."); + Logger.Warn($"Unknown coil device \"{coilMapping.Device.name}\"."); break; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs index 1c4b945b6..31e4e14e7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/DeviceSwitch.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using System; -using Unity.Entities; namespace VisualPinball.Unity { @@ -52,11 +51,11 @@ public class DeviceSwitch : IApiSwitch private readonly SwitchDefault _switchDefault; private readonly SwitchHandler _switchHandler; - public DeviceSwitch(string name, bool isPulseSwitch, SwitchDefault switchDefault, Player player) + public DeviceSwitch(string name, bool isPulseSwitch, SwitchDefault switchDefault, Player player, PhysicsEngine physicsEngine) { _isPulseSwitch = isPulseSwitch; _switchDefault = switchDefault; - _switchHandler = new SwitchHandler(name, player); + _switchHandler = new SwitchHandler(name, player, physicsEngine); } IApiSwitchStatus IApiSwitch.AddSwitchDest(SwitchConfig switchConfig, IApiSwitchStatus switchStatus) => @@ -71,7 +70,7 @@ IApiSwitchStatus IApiSwitch.AddSwitchDest(SwitchConfig switchConfig, IApiSwitchS public void SetSwitch(bool enabled) { _switchHandler.OnSwitch(enabled); - Switch?.Invoke(this, new SwitchEventArgs(enabled, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(enabled)); } /// @@ -85,7 +84,7 @@ public void ScheduleSwitch(bool enabled, int delay) SetSwitch(enabled); } else { _switchHandler.ScheduleSwitch(enabled, delay, isEnabled => { - Switch?.Invoke(this, new SwitchEventArgs(isEnabled, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(isEnabled)); }); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs index 6d237596e..7f6c21b5b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Engine/DefaultGamelogicEngine.cs @@ -74,7 +74,7 @@ public class DefaultGamelogicEngine : MonoBehaviour, IGamelogicEngine private const string SwMotorEnd = "s_motor_end"; public GamelogicEngineSwitch[] RequestedSwitches => _availableSwitches.ToArray(); - private readonly List _availableSwitches = new List { + private readonly List _availableSwitches = new() { new GamelogicEngineSwitch(SwLeftFlipper) { Description = "Left Flipper (Button)", InputActionHint = InputConstants.ActionLeftFlipper }, new GamelogicEngineSwitch(SwRightFlipper) { Description = "Right Flipper (Button)", InputActionHint = InputConstants.ActionRightFlipper }, new GamelogicEngineSwitch(SwTroughDrain) { Description = "Trough Drain", DeviceHint = "^Trough\\s*\\d?", DeviceItemHint = TroughComponent.EntrySwitchId }, @@ -102,7 +102,8 @@ public class DefaultGamelogicEngine : MonoBehaviour, IGamelogicEngine public DisplayConfig[] RequiredDisplays => new[] { new DisplayConfig(DisplayDmd, DmdWidth, DmdHeight) }; public GamelogicEngineCoil[] RequestedCoils => _availableCoils.ToArray(); - private readonly List _availableCoils = new List { + private readonly List _availableCoils = new() + { new GamelogicEngineCoil(CoilLeftFlipperMain) { Description = "Left Flipper (Main)", DeviceHint = "^(LeftFlipper|LFlipper|FlipperLeft|FlipperL)$", DeviceItemHint = FlipperComponent.MainCoilItem }, new GamelogicEngineCoil(CoilLeftFlipperHold) { Description = "Left Flipper (Hold)", DeviceHint = "^(LeftFlipper|LFlipper|FlipperLeft|FlipperL)$", DeviceItemHint = FlipperComponent.HoldCoilItem }, new GamelogicEngineCoil(CoilRightFlipperMain) { Description = "Right Flipper (Main)", DeviceHint = "^(RightFlipper|RFlipper|FlipperRight|FlipperR)$", DeviceItemHint = FlipperComponent.MainCoilItem }, @@ -113,8 +114,8 @@ public class DefaultGamelogicEngine : MonoBehaviour, IGamelogicEngine }; public GamelogicEngineWire[] AvailableWires { get; } = { - new GamelogicEngineWire(SwLeftFlipper, CoilLeftFlipperMain, DestinationType.Coil, "Left Flipper"), - new GamelogicEngineWire(SwRightFlipper, CoilRightFlipperMain, DestinationType.Coil, "Right Flipper"), + new(SwLeftFlipper, CoilLeftFlipperMain, DestinationType.Coil, "Left Flipper"), + new(SwRightFlipper, CoilRightFlipperMain, DestinationType.Coil, "Right Flipper"), }; private const string GiSlingshotRightLower = "gi_1"; @@ -137,32 +138,32 @@ public class DefaultGamelogicEngine : MonoBehaviour, IGamelogicEngine private const string LampRedBumper = "l_bumper"; public GamelogicEngineLamp[] RequestedLamps { get; } = { - new GamelogicEngineLamp(GiSlingshotRightLower) { Description = "Right Slingshot (lower)", DeviceHint = "gi1$" }, - new GamelogicEngineLamp(GiSlingshotRightUpper) { Description = "Right Slingshot (upper)", DeviceHint = "gi2$" }, - new GamelogicEngineLamp(GiSlingshotLeftLower) { Description = "Left Slingshot (lower)", DeviceHint = "gi3$" }, - new GamelogicEngineLamp(GiSlingshotLeftUpper) { Description = "Left Slingshot (upper)", DeviceHint = "gi4$" }, - new GamelogicEngineLamp(GiDropTargetsRightLower) { Description = "Right Drop Targets (lower)", DeviceHint = "gi5$" }, - new GamelogicEngineLamp(GiDropTargetsRightUpper) { Description = "Right Drop Targets (upper)", DeviceHint = "gi8$" }, - new GamelogicEngineLamp(GiDropTargetsLeftLower) { Description = "Left Drop Targets (lower)", DeviceHint = "gi6$" }, - new GamelogicEngineLamp(GiDropTargetsLeftUpper) { Description = "Left Drop Targets (upper)", DeviceHint = "gi7$" }, - new GamelogicEngineLamp(GiTop1) { Description = "Top 1 (left)", DeviceHint = "gi13$" }, - new GamelogicEngineLamp(GiTop2) { Description = "Top 2", DeviceHint = "gi10$" }, - new GamelogicEngineLamp(GiTop3) { Description = "Top 3", DeviceHint = "gi9$" }, - new GamelogicEngineLamp(GiTop4) { Description = "Top 4", DeviceHint = "gi11$" }, - new GamelogicEngineLamp(GiTop5) { Description = "Top 5 (right)", DeviceHint = "gi12$" }, - new GamelogicEngineLamp(GiLowerRamp) { Description = "Ramp (lower)", DeviceHint = "gi14$" }, - new GamelogicEngineLamp(GiUpperRamp) { Description = "Ramp (upper)", DeviceHint = "gi15$" }, - new GamelogicEngineLamp(GiTopLeftPlastic) { Description = "Top Left Plastics", DeviceHint = "gi16$" }, - new GamelogicEngineLamp(LampRedBumper) { Description = "Red Bumper", DeviceHint = "^b1l2$" } + new(GiSlingshotRightLower) { Description = "Right Slingshot (lower)", DeviceHint = "gi1$" }, + new(GiSlingshotRightUpper) { Description = "Right Slingshot (upper)", DeviceHint = "gi2$" }, + new(GiSlingshotLeftLower) { Description = "Left Slingshot (lower)", DeviceHint = "gi3$" }, + new(GiSlingshotLeftUpper) { Description = "Left Slingshot (upper)", DeviceHint = "gi4$" }, + new(GiDropTargetsRightLower) { Description = "Right Drop Targets (lower)", DeviceHint = "gi5$" }, + new(GiDropTargetsRightUpper) { Description = "Right Drop Targets (upper)", DeviceHint = "gi8$" }, + new(GiDropTargetsLeftLower) { Description = "Left Drop Targets (lower)", DeviceHint = "gi6$" }, + new(GiDropTargetsLeftUpper) { Description = "Left Drop Targets (upper)", DeviceHint = "gi7$" }, + new(GiTop1) { Description = "Top 1 (left)", DeviceHint = "gi13$" }, + new(GiTop2) { Description = "Top 2", DeviceHint = "gi10$" }, + new(GiTop3) { Description = "Top 3", DeviceHint = "gi9$" }, + new(GiTop4) { Description = "Top 4", DeviceHint = "gi11$" }, + new(GiTop5) { Description = "Top 5 (right)", DeviceHint = "gi12$" }, + new(GiLowerRamp) { Description = "Ramp (lower)", DeviceHint = "gi14$" }, + new(GiUpperRamp) { Description = "Ramp (upper)", DeviceHint = "gi15$" }, + new(GiTopLeftPlastic) { Description = "Top Left Plastics", DeviceHint = "gi16$" }, + new(LampRedBumper) { Description = "Red Bumper", DeviceHint = "^b1l2$" } }; private Player _player; private BallManager _ballManager; private bool _frameSent; private PlayfieldComponent _playfieldComponent; - private const float FlipperLag = 0.5f; + private const float FlipperLag = 0f; - private readonly Dictionary _switchTime = new Dictionary(); + private readonly Dictionary _switchTime = new(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs new file mode 100644 index 000000000..9ec27e078 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs @@ -0,0 +1,93 @@ +using System; +using Unity.Collections; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + internal struct InsideOfs : IDisposable + { + private NativeParallelHashMap _bitLookup; + private NativeParallelHashMap _insideOfs; + + public InsideOfs(Allocator allocator) + { + _bitLookup = new NativeParallelHashMap(64, allocator); + _insideOfs = new NativeParallelHashMap(64, allocator); + } + + internal void SetInsideOf(int itemId, int ballId) + { + if (!_insideOfs.ContainsKey(itemId)) { + _insideOfs.Add(itemId, new BitField64()); + } + + ref var bits = ref _insideOfs.GetValueByRef(itemId); + bits.SetBits(GetBitIndex(ballId), true); + } + + internal void SetOutsideOf(int itemId, int ballId) + { + if (!_insideOfs.ContainsKey(itemId)) { + return; + } + + ref var bits = ref _insideOfs.GetValueByRef(itemId); + bits.SetBits(GetBitIndex(ballId), false); + ClearBitIndex(ballId); + ClearItems(itemId); + } + + internal bool IsInsideOf(int itemId, int ballId) + { + return _insideOfs.ContainsKey(itemId) && _insideOfs[itemId].IsSet(GetBitIndex(ballId)); + } + + internal bool IsOutsideOf(int itemId, int ballId) => !IsInsideOf(itemId, ballId); + + private void ClearItems(int itemId) + { + if (_insideOfs[itemId].GetBits(0, 64) == 0L) { + _insideOfs.Remove(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; + } + maps.Dispose(); + return; + } + _bitLookup.Remove(ballId); + } + + private int GetBitIndex(int ballId) + { + if (_bitLookup.ContainsKey(ballId)) { + return _bitLookup[ballId]; + } + + var indices = _bitLookup.GetValueArray(Allocator.Temp); + for (var i = 0; i < 64; i++) { + if (indices.Contains(i)) { + continue; + } + _bitLookup[ballId] = i; + indices.Dispose(); + return i; + } + throw new IndexOutOfRangeException(); + } + + + public void Dispose() + { + _bitLookup.Dispose(); + _insideOfs.Dispose(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs.meta new file mode 100644 index 000000000..95c048567 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/InsideOfs.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 39439753f2f64ab3ace90deff7a74ae7 +timeCreated: 1681344266 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs index 0d909bef6..34fa7aca5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/LampPlayer.cs @@ -98,38 +98,57 @@ public void OnStart() private void HandleLampsEvent(object sender, LampsEventArgs lampsEvent) { + LampAction action = default; foreach (var lampEvent in lampsEvent.LampsChanged) { - Apply(lampEvent.Id, lampEvent.Source, lampEvent.IsCoil, (state, lamp, mapping) => ApplyValue(lampEvent.Id, lampEvent.Value, state, lamp, mapping)); + if (Apply(lampEvent.Id, lampEvent.Source, lampEvent.IsCoil, ref action)) { + ApplyValue(lampEvent.Id, lampEvent.Value, action.State, action.Lamp, action.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)); + LampAction action = default; + if (Apply(lampEvent.Id, lampEvent.Source, lampEvent.IsCoil, ref action)) { + ApplyValue(lampEvent.Id, lampEvent.Value, action.State, action.Lamp, action.Mapping); + } } public void HandleLampEvent(string id, float value) { - Apply(id, LampSource.Lamp, false, (state, lamp, mapping) => ApplyValue(id, value, state, lamp, mapping)); + LampAction action = default; + if (Apply(id, LampSource.Lamp, false, ref action)) { + ApplyValue(id, value, action.State, action.Lamp, action.Mapping); + } } public void HandleLampEvent(string id, LampStatus status) { - Apply(id, LampSource.Lamp, false, (state, lamp, _) => ApplyStatus(id, status, state, lamp)); + LampAction action = default; + if (Apply(id, LampSource.Lamp, false, ref action)) { + ApplyStatus(id, status, action.State, action.Lamp); + } } public void HandleLampEvent(string id, Color color) { - Apply(id, LampSource.Lamp, false, (state, lamp, _) => ApplyColor(id, color, state, lamp)); + LampAction action = default; + if (Apply(id, LampSource.Lamp, false, ref action)) { + ApplyColor(id, color, action.State, action.Lamp); + } } public void HandleCoilEvent(string id, bool isEnabled) { - Apply(id, LampSource.Lamp, true, (state, lamp, _) => ApplyStatus(id, isEnabled ? LampStatus.On : LampStatus.Off, state, lamp)); + LampAction action = default; + if (Apply(id, LampSource.Lamp, true, ref action)) { + ApplyStatus(id, isEnabled ? LampStatus.On : LampStatus.Off, action.State, action.Lamp); + } } - private void Apply(string id, LampSource lampSource, bool isCoil, Action action) + private bool Apply(string id, LampSource lampSource, bool isCoil, ref LampAction action) { + var hasChanged = false; if (_lampAssignments.ContainsKey(id)) { foreach (var component in _lampAssignments[id]) { var mapping = _lampMappings[id][component]; @@ -141,20 +160,21 @@ private void Apply(string id, LampSource lampSource, bool isCoil, Action. + +using System; +using NativeTrees; +using Unity.Collections; +using Unity.Profiling; +using VisualPinball.Engine.Common; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + public struct PhysicsCycle : IDisposable + { + private NativeList _contacts; + + private static readonly ProfilerMarker PerfMarker = new("PhysicsCycle"); + private static readonly ProfilerMarker PerfMarkerDisplacement = new("Displacement"); + private static readonly ProfilerMarker PerfMarkerCollision = new("Collision"); + private static readonly ProfilerMarker PerfMarkerContacts = new("Contacts"); + + public PhysicsCycle(Allocator a) + { + _contacts = new NativeList(a); + } + + internal void Simulate(ref PhysicsState state, in AABB playfieldBounds, ref NativeParallelHashSet overlappingColliders, float dTime) + { + PerfMarker.Begin(); + var staticCounts = PhysicsConstants.StaticCnts; + + while (dTime > 0) { + + var hitTime = dTime; // begin time search from now ... until delta ends + + ApplyFlipperTime(ref hitTime, ref state); + + // clear contacts + _contacts.Clear(); + + // create octree of ball-to-ball collision + var ballOctree = PhysicsDynamicBroadPhase.CreateOctree(ref state.Balls, in playfieldBounds); + + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var ball = ref enumerator.Current.Value; + + if (ball.IsFrozen) { + continue; + } + + // hit testing + PhysicsStaticBroadPhase.FindOverlaps(in state.Octree, in ball, ref overlappingColliders); + PhysicsStaticNarrowPhase.FindNextCollision(hitTime, ref ball, ref overlappingColliders, ref _contacts, ref state); + PhysicsDynamicBroadPhase.FindOverlaps(in ballOctree, in ball, ref overlappingColliders, ref state.Balls); + PhysicsDynamicNarrowPhase.FindNextCollision(ref ball, ref overlappingColliders, ref _contacts, ref state); + + // apply static time + ApplyStaticTime(ref hitTime, ref staticCounts, in ball); + } + } + + #region Displacement + PerfMarkerDisplacement.Begin(); + + // balls + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + BallDisplacementPhysics.UpdateDisplacements(ref enumerator.Current.Value, hitTime); // use static method instead of member + } + } + // flippers + using (var enumerator = state.FlipperStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var flipperState = ref enumerator.Current.Value; + FlipperDisplacementPhysics.UpdateDisplacement(flipperState.ItemId, ref flipperState.Movement, + ref flipperState.Tricks, in flipperState.Static, hitTime, ref state.EventQueue); + } + } + // gates + using (var enumerator = state.GateStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var gateState = ref enumerator.Current.Value; + GateDisplacementPhysics.UpdateDisplacement(gateState.ItemId, ref gateState.Movement, in gateState.Static, + hitTime, ref state.EventQueue); + } + } + // plunger + using (var enumerator = state.PlungerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var plungerState = ref enumerator.Current.Value; + PlungerDisplacementPhysics.UpdateDisplacement(plungerState.ItemId, ref plungerState.Movement, ref plungerState.Collider, + in plungerState.Static, hitTime, ref state.EventQueue); + } + } + // spinners + using (var enumerator = state.SpinnerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var spinnerState = ref enumerator.Current.Value; + SpinnerDisplacementPhysics.UpdateDisplacement(spinnerState.ItemId, ref spinnerState.Movement, in spinnerState.Static, + hitTime, ref state.EventQueue); + } + } + + PerfMarkerDisplacement.End(); + #endregion + + // collision + PerfMarkerCollision.Begin(); + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var ball = ref enumerator.Current.Value; + + // dynamic collision + PhysicsDynamicCollision.Collide(hitTime, ref ball, ref state); + + // static collision + PhysicsStaticCollision.Collide(hitTime, ref ball, ref state); + } + } + PerfMarkerCollision.End(); + + // handle contacts + PerfMarkerContacts.Begin(); + 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); + } + PerfMarkerContacts.End(); + + // clear contacts + _contacts.Clear(); + + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var ball = ref enumerator.Current.Value; + BallSpinHackPhysics.Update(ref ball); + } + } + + dTime -= hitTime; + + state.SwapBallCollisionHandling = !state.SwapBallCollisionHandling; + } + PerfMarker.End(); + } + + private static void ApplyStaticTime(ref float hitTime, ref float staticCounts, in BallState ball) + { + // for each collision event + var collEvent = ball.CollisionEvent; + if (collEvent.HasCollider() && collEvent.HitTime <= hitTime) { // smaller hit time?? + hitTime = collEvent.HitTime; // record actual event time + if (hitTime < PhysicsConstants.StaticTime) { // less than static time interval + if (--staticCounts < 0) { + staticCounts = 0; // keep from wrapping + hitTime = PhysicsConstants.StaticTime; + } + } + } + } + + private void ApplyFlipperTime(ref float hitTime, ref PhysicsState state) + { + // for each flipper + using (var enumerator = state.FlipperStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var flipperState = ref enumerator.Current.Value; + var flipperHitTime = flipperState.Movement.GetHitTime(flipperState.Static.AngleStart, flipperState.Tricks.AngleEnd); + + // if flipper comes to a rest before the end of the cycle, advance to that time + if (flipperHitTime > 0 && flipperHitTime < hitTime) { //!! >= 0.f causes infinite loop + hitTime = flipperHitTime; + } + } + } + } + + public void Dispose() + { + _contacts.Dispose(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs.meta new file mode 100644 index 000000000..7e9bb7946 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsCycle.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 21428d2788334ad59c3394263748202c +timeCreated: 1678661168 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs new file mode 100644 index 000000000..ffa298ccb --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs @@ -0,0 +1,58 @@ +// 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 NativeTrees; +using Unity.Collections; +using Unity.Profiling; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + public static class PhysicsDynamicBroadPhase + { + private static readonly ProfilerMarker PerfMarkerBallOctree = new("CreateBallOctree"); + private static readonly ProfilerMarker PerfMarkerDynamicBroadPhase = new("DynamicBroadPhase"); + + internal static NativeOctree CreateOctree(ref NativeParallelHashMap balls, in AABB playfieldBounds) + { + PerfMarkerBallOctree.Begin(); + var octree = new NativeOctree(playfieldBounds, 16, 10, Allocator.TempJob); + using var enumerator = balls.GetEnumerator(); + while (enumerator.MoveNext()) { + ref var ball = ref enumerator.Current.Value; + octree.Insert(ball.Id, ball.Aabb); + } + PerfMarkerBallOctree.End(); + return octree; + } + + internal static void FindOverlaps(in NativeOctree octree, in BallState ball, ref NativeParallelHashSet overlappingBalls, ref NativeParallelHashMap balls) + { + PerfMarkerDynamicBroadPhase.Begin(); + overlappingBalls.Clear(); + octree.RangeAABBUnique(ball.Aabb, overlappingBalls); + using var ob = overlappingBalls.ToNativeArray(Allocator.Temp); + for (var i = 0; i < ob.Length; i ++) { + var overlappingBallId = ob[i]; + ref var overlappingBall = ref balls.GetValueByRef(overlappingBallId); + if (overlappingBallId == ball.Id || overlappingBall.IsFrozen) { + overlappingBalls.Remove(overlappingBallId); + } + } + PerfMarkerDynamicBroadPhase.End(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs.meta new file mode 100644 index 000000000..fe393901e --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicBroadPhase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 39f41526d2d74c068970e5b36dbfaca8 +timeCreated: 1697322346 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs new file mode 100644 index 000000000..553e4fab6 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs @@ -0,0 +1,54 @@ +// 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.Profiling; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + internal static class PhysicsDynamicCollision + { + private static readonly ProfilerMarker PerfMarker = new("DynamicCollision"); + + internal static void Collide(float hitTime, ref BallState ball, ref PhysicsState state) + { + // pick "other" ball + ref var collEvent = ref ball.CollisionEvent; + ref var otherId = ref collEvent.BallId; + + // find balls with hit objects and minimum time + if (otherId != 0 && collEvent.HitTime <= hitTime) { + + PerfMarker.Begin(); + + ref var otherBall = ref state.Balls.GetValueByRef(otherId); + ref var otherCollEvent = ref otherBall.CollisionEvent; + + // now collision, contact and script reactions on active ball (object)+++++++++ + + //this.activeBall = ball; // For script that wants the ball doing the collision + + BallCollider.Collide(ref otherBall, ref ball, in otherCollEvent, in collEvent, + state.SwapBallCollisionHandling); + + // remove trial hit object pointer + collEvent.ClearCollider(); + + PerfMarker.End(); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs.meta new file mode 100644 index 000000000..2f3439dde --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicCollision.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a8a75d40ec354691b6de0c39c1950933 +timeCreated: 1697366994 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs new file mode 100644 index 000000000..05a25fb3f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs @@ -0,0 +1,60 @@ +// 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.Collections; +using Unity.Profiling; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + internal static class PhysicsDynamicNarrowPhase + { + private static readonly ProfilerMarker PerfMarkerDynamicNarrowPhase = new("DynamicNarrowPhase"); + + internal static void FindNextCollision(ref BallState ball, ref NativeParallelHashSet collidingBalls, + ref NativeList contacts, ref PhysicsState state) + { + // don't play with frozen balls + if (ball.IsFrozen) { + return; + } + PerfMarkerDynamicNarrowPhase.Begin(); + + ref var collEvent = ref ball.CollisionEvent; + using var enumerator = collidingBalls.GetEnumerator(); + while (enumerator.MoveNext()) { + var collidingBallId = enumerator.Current; + ref var collBall = ref state.Balls.GetValueByRef(collidingBallId); + + var newCollEvent = new CollisionEventData(); + var newTime = BallCollider.HitTest(ref newCollEvent, ref ball, in collBall, collEvent.HitTime); + var validHit = newTime >= 0 && !Math.Sign(newTime) && newTime <= collEvent.HitTime; + + if (newCollEvent.IsContact || validHit) { + newCollEvent.SetBallItem(collidingBallId); + newCollEvent.HitTime = newTime; + if (newCollEvent.IsContact) { + contacts.Add(new ContactBufferElement(ball.Id, newCollEvent)); + + } else { // if (validhit) + collEvent = newCollEvent; + } + } + } + PerfMarkerDynamicNarrowPhase.End(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs.meta new file mode 100644 index 000000000..cc0db2515 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsDynamicNarrowPhase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8ac8708494e49e28d99ed310112d4c9 +timeCreated: 1697365085 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs new file mode 100644 index 000000000..b6fb6007c --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs @@ -0,0 +1,421 @@ +// 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 System; +using System.Collections.Generic; +using System.Diagnostics; +using NativeTrees; +using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEngine; +using VisualPinball.Engine.Common; +using VisualPinball.Unity.Collections; +using AABB = NativeTrees.AABB; +using Debug = UnityEngine.Debug; + +namespace VisualPinball.Unity +{ + public class PhysicsEngine : MonoBehaviour + { + #region Configuration + + [Tooltip("Gravity constant, in VPX units.")] + public float GravityStrength = PhysicsConstants.GravityConst * PhysicsConstants.DefaultTableGravity; + + #endregion + + #region States + + [NonSerialized] private AABB _playfieldBounds; + [NonSerialized] private InsideOfs _insideOfs; + [NonSerialized] private NativeOctree _octree; + [NonSerialized] private NativeColliders _colliders; + [NonSerialized] private NativeArray _physicsEnv = new(1, Allocator.Persistent); + [NonSerialized] private NativeQueue _eventQueue = new(Allocator.Persistent); + + [NonSerialized] private NativeParallelHashMap _ballStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _bumperStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _flipperStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _gateStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _dropTargetStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _hitTargetStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _kickerStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _plungerStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _spinnerStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _surfaceStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashMap _triggerStates = new(0, Allocator.Persistent); + [NonSerialized] private NativeParallelHashSet _disabledCollisionItems = new(0, Allocator.Persistent); + [NonSerialized] private bool _swapBallCollisionHandling; + + #endregion + + #region Transforms + + [NonSerialized] private readonly Dictionary _transforms = new(); + [NonSerialized] private readonly Dictionary _skinnedMeshRenderers = new(); + + #endregion + + [NonSerialized] private readonly Queue _inputActions = new(); + [NonSerialized] private readonly List _scheduledActions = new(); + + [NonSerialized] private Player _player; + + private static ulong NowUsec => (ulong)(Time.timeAsDouble * 1000000); + + #region API + + public void ScheduleAction(int timeoutMs, Action action) => ScheduleAction((uint)timeoutMs, action); + public void ScheduleAction(uint timeoutMs, Action action) + { + lock (_scheduledActions) { + _scheduledActions.Add(new ScheduledAction(_physicsEnv[0].CurPhysicsFrameTime + (ulong)timeoutMs * 1000, action)); + } + } + + internal delegate void InputAction(ref PhysicsState state); + + internal ref NativeParallelHashMap Balls => ref _ballStates; + internal ref InsideOfs InsideOfs => ref _insideOfs; + internal NativeQueue.ParallelWriter EventQueue => _eventQueue.AsParallelWriter(); + + internal void Schedule(InputAction action) => _inputActions.Enqueue(action); + internal ref BallState BallState(int ballId) => ref _ballStates.GetValueByRef(ballId); + internal ref BumperState BumperState(int itemId) => ref _bumperStates.GetValueByRef(itemId); + internal ref FlipperState FlipperState(int itemId) => ref _flipperStates.GetValueByRef(itemId); + internal ref GateState GateState(int itemId) => ref _gateStates.GetValueByRef(itemId); + internal ref DropTargetState DropTargetState(int itemId) => ref _dropTargetStates.GetValueByRef(itemId); + internal ref HitTargetState HitTargetState(int itemId) => ref _hitTargetStates.GetValueByRef(itemId); + internal ref KickerState KickerState(int itemId) => ref _kickerStates.GetValueByRef(itemId); + internal ref PlungerState PlungerState(int itemId) => ref _plungerStates.GetValueByRef(itemId); + internal ref SpinnerState SpinnerState(int itemId) => ref _spinnerStates.GetValueByRef(itemId); + internal ref SurfaceState SurfaceState(int itemId) => ref _surfaceStates.GetValueByRef(itemId); + internal ref TriggerState TriggerState(int itemId) => ref _triggerStates.GetValueByRef(itemId); + internal void SetBallInsideOf(int ballId, int itemId) => _insideOfs.SetInsideOf(itemId, ballId); + internal uint TimeMsec => _physicsEnv[0].TimeMsec; + internal void Register(T item) where T : MonoBehaviour + { + var go = item.gameObject; + var itemId = go.GetInstanceID(); + _transforms.TryAdd(itemId, go.transform); + + switch (item) { + case BallComponent c: + if (!_ballStates.ContainsKey(itemId)) { + _ballStates[itemId] = c.CreateState(); + } + break; + case BumperComponent c: _bumperStates[itemId] = c.CreateState(); break; + case FlipperComponent c: _flipperStates[itemId] = c.CreateState(); break; + case GateComponent c: _gateStates[itemId] = c.CreateState(); break; + case DropTargetComponent c: _dropTargetStates[itemId] = c.CreateState(); break; + case HitTargetComponent c: _hitTargetStates[itemId] = c.CreateState(); break; + case KickerComponent c: _kickerStates[itemId] = c.CreateState(); break; + case PlungerComponent c: + _plungerStates[itemId] = c.CreateState(); + _skinnedMeshRenderers[itemId] = c.GetComponentsInChildren(); + break; + case SpinnerComponent c: _spinnerStates[itemId] = c.CreateState(); break; + case SurfaceComponent c: _surfaceStates[itemId] = c.CreateState(); break; + case TriggerComponent c: _triggerStates[itemId] = c.CreateState(); break; + } + } + + internal Transform UnregisterBall(int ballId) + { + var transform = _transforms[ballId]; + _transforms.Remove(ballId); + _ballStates.Remove(ballId); + return transform; + } + + internal void EnableCollider(int itemId) + { + if (_disabledCollisionItems.Contains(itemId)) { + _disabledCollisionItems.Remove(itemId); + } + } + internal void DisableCollider(int itemId) + { + if (!_disabledCollisionItems.Contains(itemId)) { + _disabledCollisionItems.Add(itemId); + } + } + + #endregion + + #region Event Functions + + private void Awake() + { + _player = GetComponentInParent(); + _insideOfs = new InsideOfs(Allocator.Persistent); + _physicsEnv[0] = new PhysicsEnv(NowUsec, GetComponentInChildren(), GravityStrength); + } + + private void Start() + { + // create static octree + var sw = Stopwatch.StartNew(); + var colliderItems = GetComponentsInChildren(); + Debug.Log($"Found {colliderItems.Length} collidable items."); + var colliders = new ColliderReference(Allocator.TempJob); + foreach (var colliderItem in colliderItems) { + if (!colliderItem.IsCollidable) { + _disabledCollisionItems.Add(colliderItem.ItemId); + } + colliderItem.GetColliders(_player, ref colliders, 0); + } + + // allocate colliders + _colliders = new NativeColliders(ref colliders, Allocator.Persistent); + + // create octree + var elapsedMs = sw.Elapsed.TotalMilliseconds; + var playfieldBounds = GetComponentInChildren().Bounds; + _playfieldBounds = GetComponentInChildren().Bounds; + _octree = new NativeOctree(playfieldBounds, 1024, 10, Allocator.Persistent); + + sw.Restart(); + var populateJob = new PhysicsPopulateJob { + Colliders = _colliders, + Octree = _octree, + }; + populateJob.Run(); + _octree = populateJob.Octree; + Debug.Log($"Octree of {_colliders.Length} constructed (colliders: {elapsedMs}ms, tree: {sw.Elapsed.TotalMilliseconds}ms)."); + + // get balls + var balls = GetComponentsInChildren(); + foreach (var ball in balls) { + Register(ball); + } + } + + private void Update() + { + // prepare job + var events = _eventQueue.AsParallelWriter(); + var updatePhysics = new PhysicsUpdateJob { + InitialTimeUsec = NowUsec, + DeltaTimeMs = Time.deltaTime * 1000, + PhysicsEnv = _physicsEnv, + Octree = _octree, + Colliders = _colliders, + InsideOfs = _insideOfs, + Events = events, + Balls = _ballStates, + BumperStates = _bumperStates, + DropTargetStates = _dropTargetStates, + FlipperStates = _flipperStates, + GateStates = _gateStates, + HitTargetStates = _hitTargetStates, + KickerStates = _kickerStates, + PlungerStates = _plungerStates, + SpinnerStates = _spinnerStates, + SurfaceStates = _surfaceStates, + TriggerStates = _triggerStates, + DisabledCollisionItems = _disabledCollisionItems, + PlayfieldBounds = _playfieldBounds, + OverlappingColliders = new NativeParallelHashSet(0, Allocator.TempJob) + }; + + var env = _physicsEnv[0]; + var state = new PhysicsState(ref env, ref _octree, ref _colliders, ref events, ref _insideOfs, ref _ballStates, + ref _bumperStates, ref _dropTargetStates, ref _flipperStates, ref _gateStates, + ref _hitTargetStates, ref _kickerStates, ref _plungerStates, ref _spinnerStates, + ref _surfaceStates, ref _triggerStates, ref _disabledCollisionItems, ref _swapBallCollisionHandling); + + // process input + while (_inputActions.Count > 0) { + var action = _inputActions.Dequeue(); + action(ref state); + } + + // run physics loop + updatePhysics.Run(); + + // dequeue events + while (_eventQueue.TryDequeue(out var eventData)) { + _player.OnEvent(in eventData); + } + + // process scheduled events from managed land + lock (_scheduledActions) { + for (var i = _scheduledActions.Count - 1; i >= 0; i--) { + if (_physicsEnv[0].CurPhysicsFrameTime > _scheduledActions[i].ScheduleAt) { + _scheduledActions[i].Action(); + _scheduledActions.RemoveAt(i); + } + } + } + + // retrieve updated data + _ballStates = updatePhysics.Balls; + _physicsEnv = updatePhysics.PhysicsEnv; + _flipperStates = updatePhysics.FlipperStates; + + #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.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.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.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.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.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var gateState = ref enumerator.Current.Value; + var gateTransform = _transforms[gateState.WireItemId]; + gateTransform.localRotation = quaternion.RotateX(-gateState.Movement.Angle); + } + } + + // plungers + 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); + } + } + } + + // spinners + using (var enumerator = _spinnerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var spinnerState = ref enumerator.Current.Value; + var spinnerTransform = _transforms[spinnerState.AnimationItemId]; + spinnerTransform.localRotation = quaternion.RotateX(-spinnerState.Movement.Angle); + } + } + + // triggers + 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); + } + } + + #endregion + } + + private void OnDestroy() + { + _physicsEnv.Dispose(); + _eventQueue.Dispose(); + _ballStates.Dispose(); + _colliders.Dispose(); + _insideOfs.Dispose(); + _octree.Dispose(); + _bumperStates.Dispose(); + _dropTargetStates.Dispose(); + _flipperStates.Dispose(); + _gateStates.Dispose(); + _hitTargetStates.Dispose(); + using (var enumerator = _kickerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + enumerator.Current.Value.Dispose(); + } + } + _kickerStates.Dispose(); + _plungerStates.Dispose(); + _spinnerStates.Dispose(); + _surfaceStates.Dispose(); + using (var enumerator = _triggerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + enumerator.Current.Value.Dispose(); + } + } + _triggerStates.Dispose(); + _disabledCollisionItems.Dispose(); + } + + #endregion + + private class ScheduledAction + { + public readonly ulong ScheduleAt; + public readonly Action Action; + + public ScheduledAction(ulong scheduleAt, Action action) + { + ScheduleAt = scheduleAt; + Action = action; + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs.meta similarity index 61% rename from VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs.meta index c6bcf44b8..eda51e10e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEngine.cs.meta @@ -1,11 +1,11 @@ fileFormatVersion: 2 -guid: e9b4ba602429a4aadaa2d69d71d900a5 +guid: b80d5ee1d3ab4a109515ddcf56026d94 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {instanceID: 0} + icon: {fileID: 2800000, guid: b8980903958b24841adb47db27c3344e, type: 3} userData: assetBundleName: assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs new file mode 100644 index 000000000..0d6d446bb --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs @@ -0,0 +1,47 @@ +// 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; +using Unity.Mathematics; +using VisualPinball.Engine.Common; +using Random = Unity.Mathematics.Random; + +namespace VisualPinball.Unity +{ + public struct PhysicsEnv : IDisposable + { + public readonly float3 Gravity; + public readonly ulong StartTimeUsec; + public ulong CurPhysicsFrameTime; + public ulong NextPhysicsFrameTime; + public uint TimeMsec; + + public Random Random; + + public PhysicsEnv(ulong startTimeUsec, PlayfieldComponent playfield, float gravityStrength) : this() + { + StartTimeUsec = startTimeUsec; + CurPhysicsFrameTime = StartTimeUsec; + NextPhysicsFrameTime = StartTimeUsec + PhysicsConstants.PhysicsStepTime; + Random = new Random((uint)UnityEngine.Random.Range(1, 100000)); + Gravity = playfield.PlayfieldGravity(gravityStrength); + } + + public void Dispose() + { + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs.meta new file mode 100644 index 000000000..565fdfea9 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsEnv.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b915f496c6ce491ba0ed3143007a1fe8 +timeCreated: 1678661883 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs new file mode 100644 index 000000000..eaa43a57e --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs @@ -0,0 +1,22 @@ +using NativeTrees; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; + +namespace VisualPinball.Unity +{ + [BurstCompile(CompileSynchronously = true)] + internal struct PhysicsPopulateJob : IJob + { + [ReadOnly] + public NativeColliders Colliders; + public NativeOctree Octree; + + public void Execute() + { + for (var i = 0; i < Colliders.Length; i++) { + Octree.Insert(i, Colliders.GetAabb(i)); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs.meta new file mode 100644 index 000000000..97f223edf --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsPopulateJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0d5b69cfa7a94e9885f48041fcd5af9a +timeCreated: 1697223597 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs new file mode 100644 index 000000000..996d8072f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs @@ -0,0 +1,187 @@ +// 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 NativeTrees; +using Unity.Collections; +using VisualPinball.Engine.VPT; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + internal struct PhysicsState + { + internal PhysicsEnv Env; + internal NativeOctree Octree; + internal NativeColliders Colliders; + internal NativeQueue.ParallelWriter EventQueue; + internal InsideOfs InsideOfs; + internal NativeParallelHashMap Balls; + internal NativeParallelHashMap BumperStates; + internal NativeParallelHashMap DropTargetStates; + internal NativeParallelHashMap FlipperStates; + internal NativeParallelHashMap GateStates; + internal NativeParallelHashMap HitTargetStates; + internal NativeParallelHashMap KickerStates; + internal NativeParallelHashMap PlungerStates; + internal NativeParallelHashMap SpinnerStates; + internal NativeParallelHashMap SurfaceStates; + internal NativeParallelHashMap TriggerStates; + internal NativeParallelHashSet DisabledCollisionItems; + internal bool SwapBallCollisionHandling; + + public PhysicsState(ref PhysicsEnv env, ref NativeOctree octree, ref NativeColliders colliders, + ref NativeQueue.ParallelWriter eventQueue, ref InsideOfs insideOfs, ref NativeParallelHashMap balls, + ref NativeParallelHashMap bumperStates, ref NativeParallelHashMap dropTargetStates, + ref NativeParallelHashMap flipperStates, ref NativeParallelHashMap gateStates, + ref NativeParallelHashMap hitTargetStates, ref NativeParallelHashMap kickerStates, + ref NativeParallelHashMap plungerStates, ref NativeParallelHashMap spinnerStates, + ref NativeParallelHashMap surfaceStates, ref NativeParallelHashMap triggerStates, + ref NativeParallelHashSet disabledCollisionItems, ref bool swapBallCollisionHandling) + { + Env = env; + Octree = octree; + Colliders = colliders; + EventQueue = eventQueue; + InsideOfs = insideOfs; + Balls = balls; + BumperStates = bumperStates; + DropTargetStates = dropTargetStates; + FlipperStates = flipperStates; + GateStates = gateStates; + HitTargetStates = hitTargetStates; + KickerStates = kickerStates; + PlungerStates = plungerStates; + SpinnerStates = spinnerStates; + SurfaceStates = surfaceStates; + TriggerStates = triggerStates; + DisabledCollisionItems = disabledCollisionItems; + SwapBallCollisionHandling = swapBallCollisionHandling; + } + + internal ref ColliderHeader GetColliderHeader(int colliderId) => ref Colliders.GetHeader(colliderId); + internal ColliderType GetColliderType(int colliderId) => Colliders.GetHeader(colliderId).Type; + + internal bool IsColliderActive(int colliderId) => !DisabledCollisionItems.Contains(Colliders.GetItemId(colliderId)); + + #region States + + internal ref FlipperState GetFlipperState(int colliderId) => ref FlipperStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + 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 TriggerState GetTriggerState(int colliderId) => ref TriggerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + internal ref KickerState GetKickerState(int colliderId) => ref KickerStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + + internal bool HasDropTargetState(int colliderId) => DropTargetStates.ContainsKey(Colliders.GetItemId(colliderId)); + + internal bool HasHitTargetState(int colliderId) => HitTargetStates.ContainsKey(Colliders.GetItemId(colliderId)); + + internal ref DropTargetState GetDropTargetState(int colliderId) => ref DropTargetStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + internal ref HitTargetState GetHitTargetState(int colliderId) => ref HitTargetStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + internal ref BumperState GetBumperState(int colliderId) => ref BumperStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + internal ref GateState GetGateState(int colliderId) => ref GateStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + internal ref SurfaceState GetSurfaceState(int colliderId) => ref SurfaceStates.GetValueByRef(Colliders.GetItemId(colliderId)); + + #endregion + + #region Hit Test + + internal float HitTest(int colliderId, ref BallState ball, ref CollisionEventData collEvent, ref NativeList contacts, ref PhysicsState state) + { + if (IsInactiveDropTarget(colliderId)) { + return -1f; + } + switch (GetColliderType(colliderId)) { + case ColliderType.Bumper: + case ColliderType.Circle: + return Colliders.Circle(colliderId).HitTest(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime); + + case ColliderType.Gate: + return Colliders.Gate(colliderId).HitTest(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime); + + case ColliderType.Line: + return Colliders.Line(colliderId).HitTest(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime); + + case ColliderType.LineZ: + return Colliders.LineZ(colliderId).HitTest(ref collEvent, in ball, ball.CollisionEvent.HitTime); + + case ColliderType.Line3D: + return Colliders.Line3D(colliderId).HitTest(ref collEvent, in ball, ball.CollisionEvent.HitTime); + + case ColliderType.LineSlingShot: + return Colliders.LineSlingShot(colliderId).HitTest(ref collEvent, ref state.InsideOfs, in ball, ball.CollisionEvent.HitTime); + + case ColliderType.Point: + return Colliders.Point(colliderId).HitTest(ref collEvent, in ball, ball.CollisionEvent.HitTime); + + case ColliderType.Plane: + return Colliders.Plane(colliderId).HitTest(ref collEvent, in ball, ball.CollisionEvent.HitTime); + + case ColliderType.Spinner: + return Colliders.Spinner(colliderId).HitTest(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime); + + case ColliderType.Triangle: + return Colliders.Triangle(colliderId).HitTest(ref collEvent, in state.InsideOfs, in ball, + ball.CollisionEvent.HitTime); + + case ColliderType.KickerCircle: + case ColliderType.TriggerCircle: + return Colliders.Circle(colliderId).HitTestBasicRadius(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime, false, false, false); + + case ColliderType.TriggerLine: + return Colliders.Line(colliderId).HitTestBasic(ref collEvent, ref state.InsideOfs, in ball, + ball.CollisionEvent.HitTime, false, false, false); + + case ColliderType.Flipper: + ref var flipperState = ref state.GetFlipperState(colliderId); + return Colliders.Flipper(colliderId).HitTest(ref collEvent, ref state.InsideOfs, ref flipperState.Hit, + in flipperState.Movement, in flipperState.Tricks, in flipperState.Static, in ball, collEvent.HitTime); + + case ColliderType.Plunger: + ref var plungerState = ref state.GetPlungerState(colliderId); + return Colliders.Plunger(colliderId).HitTest(ref collEvent, ref state.InsideOfs, ref plungerState.Movement, + in plungerState.Collider, in plungerState.Static, in ball, collEvent.HitTime); + } + return -1f; + } + + private bool IsInactiveDropTarget(int colliderId) + { + if (Colliders.GetItemType(colliderId) == ItemType.HitTarget && HasDropTargetState(colliderId)) { + ref var dropTargetState = ref GetDropTargetState(colliderId); + if (dropTargetState.Animation.IsDropped || dropTargetState.Animation.MoveAnimation) { // QUICKFIX so that DT is not triggered twice + return true; + } + } + return false; + } + + #endregion + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs.meta new file mode 100644 index 000000000..075d404dc --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5f6509a9a6e74f81a4c439f5c2f4a2d2 +timeCreated: 1696276234 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs new file mode 100644 index 000000000..2e7d7dcdf --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs @@ -0,0 +1,35 @@ +// 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 NativeTrees; +using Unity.Collections; +using Unity.Profiling; + +namespace VisualPinball.Unity +{ + public static class PhysicsStaticBroadPhase + { + private static readonly ProfilerMarker PerfMarkerBroadPhase = new("BroadPhase"); + + internal static void FindOverlaps(in NativeOctree octree, in BallState ball, ref NativeParallelHashSet overlappingColliders) + { + PerfMarkerBroadPhase.Begin(); + overlappingColliders.Clear(); + octree.RangeAABBUnique(ball.Aabb, overlappingColliders); + PerfMarkerBroadPhase.End(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs.meta new file mode 100644 index 000000000..7b50c62e2 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticBroadPhase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0cf814a76e55460fb3a3232176ead728 +timeCreated: 1678825180 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs new file mode 100644 index 000000000..1fa4ae3e7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs @@ -0,0 +1,178 @@ +// 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 ConvertIfStatementToSwitchStatement + +using VisualPinball.Engine.VPT; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + internal static class PhysicsStaticCollision + { + internal static void Collide(float hitTime, ref BallState ball, ref PhysicsState state) + { + + // find balls with hit objects and minimum time + if (ball.CollisionEvent.ColliderId < 0 || ball.CollisionEvent.HitTime > hitTime) { + return; + } + + Collide(ref ball, ref state); + + // remove trial hit object pointer + ball.CollisionEvent.ClearCollider(); + } + + private static void Collide(ref BallState ball, ref PhysicsState state) + { + var colliderId = ball.CollisionEvent.ColliderId; + var collHeader = state.GetColliderHeader(colliderId); + if (CollidesWithItem(ref collHeader, ref ball, ref state)) { + return; + } + ref var cols = ref state.Colliders; + switch (state.GetColliderType(colliderId)) { + + case ColliderType.Circle: + ref var circleCollider = ref cols.Circle(colliderId); + circleCollider.Collide(ref ball, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Plane: + ref var planeCollider = ref cols.Plane(colliderId); + planeCollider.Collide(ref ball, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Line: + ref var lineCollider = ref cols.Line(colliderId); + lineCollider.Collide(ref ball, ref state.EventQueue, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Triangle: + ref var triangleCollider = ref cols.Triangle(colliderId); + triangleCollider.Collide(ref ball, ref state.EventQueue, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Line3D: + ref var line3DCollider = ref cols.Line3D(colliderId); + line3DCollider.Collide(ref ball, ref state.EventQueue, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Point: + ref var pointCollider = ref cols.Point(colliderId); + pointCollider.Collide(ref ball, ref state.EventQueue, in ball.CollisionEvent, ref state.Env.Random); + break; + + case ColliderType.Bumper: + ref var bumperState = ref state.GetBumperState(colliderId); + BumperCollider.Collide(ref ball, ref state.EventQueue, ref ball.CollisionEvent, ref bumperState.RingAnimation, ref bumperState.SkirtAnimation, + in collHeader, in bumperState.Static, ref state.Env.Random); + break; + + case ColliderType.Flipper: + ref var flipperState = ref state.GetFlipperState(colliderId); + ref var flipperCollider = ref cols.Flipper(colliderId); + 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 + ); + break; + + case ColliderType.Gate: + ref var gateState = ref state.GetGateState(colliderId); + GateCollider.Collide(ref ball, ref ball.CollisionEvent, ref gateState.Movement, ref state.EventQueue, + in collHeader, in gateState.Static); + break; + + case ColliderType.LineSlingShot: + ref var surfaceState = ref state.GetSurfaceState(colliderId); + ref var surfaceCollider = ref cols.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); + PlungerCollider.Collide(ref ball, ref ball.CollisionEvent, ref plungerState.Movement, in plungerState.Static, ref state.Env.Random); + break; + + case ColliderType.Spinner: + ref var spinnerState = ref state.GetSpinnerState(colliderId); + SpinnerCollider.Collide(in ball, ref ball.CollisionEvent, ref spinnerState.Movement, in spinnerState.Static); + break; + + case ColliderType.TriggerCircle: + case ColliderType.TriggerLine: + TriggerCollide(ref ball, ref state, in collHeader); + break; + + case ColliderType.KickerCircle: + ref var kickerState = ref state.GetKickerState(colliderId); + 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; + } + } + + private static bool CollidesWithItem(ref ColliderHeader collHeader, ref BallState ball, ref PhysicsState state) + { + ref var cols = ref state.Colliders; + + // hit target + var colliderId = ball.CollisionEvent.ColliderId; + if (collHeader.ItemType == ItemType.HitTarget) { + + var normal = collHeader.Type == ColliderType.Triangle + ? cols.Triangle(colliderId).Normal() + : ball.CollisionEvent.HitNormal; + + if (state.HasDropTargetState(colliderId)) { + ref var dropTargetState = ref state.GetDropTargetState(colliderId); + 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); + 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); + return true; + } + + return false; + } + + private static void TriggerCollide(ref BallState ball, ref PhysicsState state, in ColliderHeader collHeader) + { + ref var triggerState = ref state.GetTriggerState(collHeader.Id); + TriggerCollider.Collide(ref ball, ref state.EventQueue, ref ball.CollisionEvent, ref state.InsideOfs, ref triggerState.Animation, in collHeader); + + if (triggerState.FlipperCorrection.IsEnabled) { + 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); + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs.meta new file mode 100644 index 000000000..de996f8c7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticCollision.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: af1a4f63b59246f2be6138582acfa192 +timeCreated: 1679007108 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs new file mode 100644 index 000000000..246dd1f8b --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs @@ -0,0 +1,77 @@ +// 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 ForCanBeConvertedToForeach + +using Unity.Collections; +using Unity.Profiling; + +namespace VisualPinball.Unity +{ + public static class PhysicsStaticNarrowPhase + { + private static readonly ProfilerMarker PerfMarkerNarrowPhase = new("NarrowPhase"); + + internal static void FindNextCollision( + float hitTime, + ref BallState ball, + ref NativeParallelHashSet overlappingColliders, + ref NativeList contacts, + ref PhysicsState state + ) + { + PerfMarkerNarrowPhase.Begin(); + + // init contacts and event + ball.CollisionEvent.ClearCollider(hitTime); // search upto current hit time + + using (var enumerator = overlappingColliders.GetEnumerator()) { + while (enumerator.MoveNext()) { + var overlappingColliderId = enumerator.Current; + if (!state.IsColliderActive(overlappingColliderId)) { + continue; + } + var newCollEvent = new CollisionEventData(); + var newTime = state.HitTest(overlappingColliderId, ref ball, ref newCollEvent, ref contacts, ref state); + SaveCollisions(ref ball, ref newCollEvent, ref contacts, overlappingColliderId, newTime); + } + } + + // no negative time allowed + if (ball.CollisionEvent.HitTime < 0) { + ball.CollisionEvent.ClearCollider(); + } + PerfMarkerNarrowPhase.End(); + } + + private static void SaveCollisions(ref BallState ball, ref CollisionEventData newCollEvent, + ref NativeList contacts, int colliderId, float newTime) + { + 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 + newCollEvent.SetCollider(colliderId); + newCollEvent.HitTime = newTime; + if (newCollEvent.IsContact) { // remember all contacts? + contacts.Add(new ContactBufferElement(ball.Id, newCollEvent)); + + } else { // if (validhit) + ball.CollisionEvent = newCollEvent; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs.meta new file mode 100644 index 000000000..7d1b87346 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsStaticNarrowPhase.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a04a4f7580b94ee6af69ff781069ce5f +timeCreated: 1678831337 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs new file mode 100644 index 000000000..f247a812c --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs @@ -0,0 +1,175 @@ +// 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 NativeTrees; +using Unity.Burst; +using Unity.Collections; +using Unity.Jobs; +using VisualPinball.Engine.Common; + +namespace VisualPinball.Unity +{ + [BurstCompile(CompileSynchronously = true)] + internal struct PhysicsUpdateJob : IJob + { + [ReadOnly] + public ulong InitialTimeUsec; + + public float DeltaTimeMs; + + [NativeDisableParallelForRestriction] + public NativeParallelHashSet OverlappingColliders; + public NativeArray PhysicsEnv; + public NativeOctree Octree; + public NativeColliders Colliders; + public InsideOfs InsideOfs; + public NativeQueue.ParallelWriter Events; + public AABB PlayfieldBounds; + + public NativeParallelHashMap Balls; + public NativeParallelHashMap BumperStates; + public NativeParallelHashMap DropTargetStates; + public NativeParallelHashMap FlipperStates; + public NativeParallelHashMap GateStates; + public NativeParallelHashMap HitTargetStates; + public NativeParallelHashMap KickerStates; + public NativeParallelHashMap PlungerStates; + public NativeParallelHashMap SpinnerStates; + public NativeParallelHashMap SurfaceStates; + public NativeParallelHashMap TriggerStates; + public NativeParallelHashSet DisabledCollisionItems; + public bool SwapBallCollisionHandling; + + public void Execute() + { + var env = PhysicsEnv[0]; + var state = new PhysicsState(ref env, ref Octree, ref Colliders, 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); + var cycle = new PhysicsCycle(Allocator.Temp); + + while (env.CurPhysicsFrameTime < InitialTimeUsec) // loop here until current (real) time matches the physics (simulated) time + { + env.TimeMsec = (uint)((env.CurPhysicsFrameTime - env.StartTimeUsec) / 1000); + var physicsDiffTime = (float)((env.NextPhysicsFrameTime - env.CurPhysicsFrameTime) * (1.0 / PhysicsConstants.DefaultStepTime)); + + // update velocities - always on integral physics frame boundary (spinner, gate, flipper, plunger, ball) + #region Update Velocities + + // balls + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + BallVelocityPhysics.UpdateVelocities(ref enumerator.Current.Value, env.Gravity); + } + } + // flippers + using (var enumerator = FlipperStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + FlipperVelocityPhysics.UpdateVelocities(ref enumerator.Current.Value); + } + } + // gates + using (var enumerator = GateStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var gateState = ref enumerator.Current.Value; + GateVelocityPhysics.UpdateVelocities(ref gateState.Movement, in gateState.Static); + } + } + // plungers + using (var enumerator = PlungerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var plungerState = ref enumerator.Current.Value; + PlungerVelocityPhysics.UpdateVelocities(ref plungerState.Movement, ref plungerState.Velocity, in plungerState.Static); + } + } + // spinners + using (var enumerator = SpinnerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var spinnerState = ref enumerator.Current.Value; + SpinnerVelocityPhysics.UpdateVelocities(ref spinnerState.Movement, in spinnerState.Static); + } + } + + #endregion + + // primary physics loop + cycle.Simulate(ref state, in PlayfieldBounds, ref OverlappingColliders, physicsDiffTime); + + // ball trail, keep old pos of balls + using (var enumerator = state.Balls.GetEnumerator()) { + while (enumerator.MoveNext()) { + BallRingCounterPhysics.Update(ref enumerator.Current.Value); + } + } + + #region Animation + + // bumper + using (var enumerator = BumperStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var bumperState = ref enumerator.Current.Value; + if (bumperState.RingItemId != 0) { + BumperRingAnimation.Update(ref bumperState.RingAnimation, DeltaTimeMs); + } + if (bumperState.SkirtItemId != 0) { + BumperSkirtAnimation.Update(ref bumperState.SkirtAnimation, DeltaTimeMs); + } + } + } + + // drop target + using (var enumerator = DropTargetStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var dropTargetState = ref enumerator.Current.Value; + DropTargetAnimation.Update(enumerator.Current.Key, ref dropTargetState.Animation, in dropTargetState.Static, ref state); + } + } + + // hit target + using (var enumerator = HitTargetStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var hitTargetState = ref enumerator.Current.Value; + HitTargetAnimation.Update(ref hitTargetState.Animation, in hitTargetState.Static, env.TimeMsec); + } + } + + // plunger + using (var enumerator = PlungerStates.GetEnumerator()) { + while (enumerator.MoveNext()) { + ref var plungerState = ref enumerator.Current.Value; + PlungerAnimation.Update(ref plungerState.Animation, in plungerState.Movement, in plungerState.Static); + } + } + + // trigger + 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); + } + } + + #endregion + + env.CurPhysicsFrameTime = env.NextPhysicsFrameTime; + env.NextPhysicsFrameTime += PhysicsConstants.PhysicsStepTime; + } + + PhysicsEnv[0] = env; + cycle.Dispose(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs.meta new file mode 100644 index 000000000..2817b063a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/PhysicsUpdateJob.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0b10767332f9444e84e5c9293196e58a +timeCreated: 1697223526 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs index 47d088618..d197481f3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/Player.cs @@ -18,14 +18,13 @@ using System.Collections.Generic; using System.Linq; using NLog; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; using VisualPinball.Engine.Game.Engines; -using VisualPinball.Engine.VPT.Trigger; +using Color = VisualPinball.Engine.Math.Color; using Logger = NLog.Logger; namespace VisualPinball.Unity @@ -36,7 +35,7 @@ public class Player : MonoBehaviour public PlayfieldApi PlayfieldApi { get; private set; } // shortcuts - public GameObject Playfield => _playfieldComponent.gameObject; + public GameObject Playfield => PlayfieldComponent.gameObject; [NonSerialized] public IGamelogicEngine GamelogicEngine; @@ -44,7 +43,7 @@ public class Player : MonoBehaviour [NonSerialized] public BallManager BallManager; - public event EventHandler OnPlayerStarted; + public event EventHandler OnPlayeStarted; public List SwitchMapping => _tableComponent.MappingConfig.Switches; public List CoilMapping => _tableComponent.MappingConfig.Coils; @@ -56,45 +55,32 @@ public class Player : MonoBehaviour public event EventHandler OnBallDestroyed; [HideInInspector] [SerializeField] public string debugUiId; - [HideInInspector] [SerializeField] public string physicsEngineId; [Tooltip("When enabled, update the switch, coil, lamp and wire manager windows in the editor (slower performance)")] public bool UpdateDuringGamplay = true; // table related - private readonly List _apis = new List(); - private readonly List _colliderGenerators = new List(); - private readonly Dictionary _hittables = new Dictionary(); - private readonly Dictionary _rotatables = new Dictionary(); - private readonly Dictionary _collidables = new Dictionary(); - private readonly Dictionary _spinnables = new Dictionary(); - private readonly Dictionary _slingshots = new Dictionary(); - private readonly Dictionary _droppables = new Dictionary(); - - internal readonly Dictionary FlipperTransforms = new Dictionary(); - internal readonly Dictionary BumperSkirtTransforms = new Dictionary(); - internal readonly Dictionary BumperRingTransforms = new Dictionary(); - internal readonly Dictionary GateWireTransforms = new Dictionary(); - internal readonly Dictionary HitTargetTransforms = new Dictionary(); - internal readonly Dictionary DropTargetTransforms = new Dictionary(); - internal readonly Dictionary SpinnerPlateTransforms = new Dictionary(); - internal readonly Dictionary TriggerTransforms = new Dictionary(); - internal readonly Dictionary PlungerSkinnedMeshRenderers = new Dictionary(); - internal readonly Dictionary Balls = new Dictionary(); + private readonly List _apis = new(); + private readonly List _colliderGenerators = new(); + private readonly Dictionary _hittables = new(); + private readonly Dictionary _rotatables = new(); + private readonly Dictionary _collidables = new(); + private readonly Dictionary _spinnables = new(); + private readonly Dictionary _slingshots = new(); + private readonly Dictionary _droppables = new(); internal IEnumerable ColliderGenerators => _colliderGenerators; // input related [NonSerialized] private InputManager _inputManager; - [NonSerialized] private VisualPinballSimulationSystemGroup _simulationSystemGroup; - [NonSerialized] private readonly List<(InputAction, Action)> _actions = new List<(InputAction, Action)>(); + [NonSerialized] private readonly List<(InputAction, Action)> _actions = new(); // players - [NonSerialized] private readonly LampPlayer _lampPlayer = new LampPlayer(); - [NonSerialized] private readonly CoilPlayer _coilPlayer = new CoilPlayer(); - [NonSerialized] private readonly SwitchPlayer _switchPlayer = new SwitchPlayer(); - [NonSerialized] private readonly WirePlayer _wirePlayer = new WirePlayer(); - [NonSerialized] private readonly DisplayPlayer _displayPlayer = new DisplayPlayer(); + [NonSerialized] private readonly LampPlayer _lampPlayer = new(); + [NonSerialized] private readonly CoilPlayer _coilPlayer = new(); + [NonSerialized] private readonly SwitchPlayer _switchPlayer = new(); + [NonSerialized] private readonly WirePlayer _wirePlayer = new(); + [NonSerialized] private readonly DisplayPlayer _displayPlayer = new(); private const float SlowMotionMax = 0.1f; private const float TimeLapseMax = 2.5f; @@ -102,8 +88,25 @@ public class Player : MonoBehaviour private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private TableComponent _tableComponent; private PlayfieldComponent _playfieldComponent; + private PhysicsEngine _physicsEngine; - internal static readonly Entity PlayfieldEntity = new Entity {Index = -3, Version = 0}; // a fake entity we just use for reference + private PlayfieldComponent PlayfieldComponent { + get { + if (_playfieldComponent == null) { + _playfieldComponent = GetComponentInChildren(); + } + return _playfieldComponent; + } + } + + private PhysicsEngine PhysicsEngine { + get { + if (_physicsEngine == null) { + _physicsEngine = GetComponentInChildren(); + } + return _physicsEngine; + } + } #region Access @@ -117,11 +120,13 @@ public class Player : MonoBehaviour public Dictionary CoilStatuses => _coilPlayer.CoilStatuses; public Dictionary LampStatuses => _lampPlayer.LampStates; public Dictionary WireStatuses => _wirePlayer.WireStatuses; - public float3 Gravity => _playfieldComponent.Gravity; + + public int NextBallId => ++_currentBallId; + private int _currentBallId; 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, Color color) => _lampPlayer.HandleLampEvent(lampId, color); #endregion @@ -135,12 +140,11 @@ public Player() private void Awake() { _tableComponent = GetComponent(); - _playfieldComponent = GetComponentInChildren(); var engineComponent = GetComponent(); _apis.Add(TableApi); - BallManager = new BallManager(this); + BallManager = new BallManager(GetComponentInChildren(), this, Playfield.transform); _inputManager = new InputManager(); _inputManager.Enable(HandleInput); @@ -149,16 +153,9 @@ private void Awake() _lampPlayer.Awake(this, _tableComponent, GamelogicEngine); _coilPlayer.Awake(this, _tableComponent, GamelogicEngine, _lampPlayer, _wirePlayer); _switchPlayer.Awake(_tableComponent, GamelogicEngine, _inputManager); - _wirePlayer.Awake(_tableComponent, _inputManager, _switchPlayer, this); + _wirePlayer.Awake(_tableComponent, _inputManager, _switchPlayer, this, PhysicsEngine); _displayPlayer.Awake(GamelogicEngine); } - - EngineProvider.Set(physicsEngineId); - EngineProvider.Get().Init(_tableComponent, BallManager); - if (!string.IsNullOrEmpty(debugUiId)) { - EngineProvider.Set(debugUiId); - } - _simulationSystemGroup = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); } private void Start() @@ -178,10 +175,6 @@ private void Start() _wirePlayer.OnStart(); GamelogicEngine?.OnInit(this, TableApi, BallManager); - - if (EngineProvider.Exists) { - EngineProvider.Get().Init(_tableComponent); - } } private void Update() @@ -207,178 +200,42 @@ private void OnDestroy() } } -#endregion + #endregion #region Registrations - public void RegisterBumper(BumperComponent component, Entity entity) - { - Register(new BumperApi(component.gameObject, entity, this), component, entity); - RegisterTransform(BumperRingTransforms, component, entity); - RegisterTransform(BumperSkirtTransforms, component, entity); - } - - public void RegisterFlipper(FlipperComponent component, Entity entity) - { - Register(new FlipperApi(component.gameObject, entity, this), component, entity); - FlipperTransforms[entity] = component.gameObject.transform; - - if (EngineProvider.Exists) { - EngineProvider.Get().OnRegisterFlipper(entity, component.gameObject.name); - } - } - - public void RegisterDropTarget(DropTargetComponent component, Entity entity) - { - Register(new DropTargetApi(component.gameObject, entity, this), component, entity); - RegisterTransform(DropTargetTransforms, component, entity); - } - - public void RegisterGate(GateComponent component, Entity entity) - { - Register(new GateApi(component.gameObject, entity, this), component, entity); - RegisterTransform(GateWireTransforms, component, entity); - } - - public void RegisterGateLifter(GateLifterComponent component) - { - Register(new GateLifterApi(component.gameObject, this), component); - } - - public void RegisterHitTarget(HitTargetComponent component, Entity entity) - { - Register(new HitTargetApi(component.gameObject, entity, this), component, entity); - RegisterTransform(HitTargetTransforms, component, entity); - } - - public void RegisterKicker(KickerComponent component, Entity entity) - { - Register(new KickerApi(component.gameObject, entity, this), component, entity); - } - - public void RegisterLamp(LightComponent component) - { - Register(component.GetApi(this), component); - } - - public void RegisterLampGroup(LightGroupComponent component) - { - Register(component.GetApi(this), component); - } - - public void RegisterStepRotator(StepRotatorMechComponent component) - { - Register(new StepRotatorMechApi(component.gameObject, this), component); - } - - public void RegisterScoreMotorComponent(ScoreMotorComponent component) - { - Register(new ScoreMotorApi(component.gameObject, this), component); - } - - public void RegisterDropTargetBankComponent(DropTargetBankComponent component) - { - Register(new DropTargetBankApi(component.gameObject, this), component); - } - - public void RegisterCollisionSwitchComponent(CollisionSwitchComponent component) + public void Register(PlungerApi plungerApi, PlungerComponent component, InputActionReference actionRef) { - Register(new CollisionSwitchApi(component.gameObject, this), component); - } - - public void RegisterSlingshotComponent(SlingshotComponent component) - { - Register(new SlingshotApi(component.gameObject, this), component); - } - - public void RegisterPlunger(PlungerComponent component, Entity entity, InputActionReference actionRef) - { - var plungerApi = new PlungerApi(component.gameObject, entity, this); - Register(plungerApi, component, entity); - + Register(plungerApi, component); if (actionRef != null) { actionRef.action.performed += plungerApi.OnAnalogPlunge; _actions.Add((actionRef.action, plungerApi.OnAnalogPlunge)); } - - PlungerSkinnedMeshRenderers[entity] = component.gameObject.GetComponentsInChildren(); - } - - public void RegisterPlayfield(GameObject go) - { - PlayfieldApi = new PlayfieldApi(go, this); - _colliderGenerators.Add(PlayfieldApi); - } - - public void RegisterPrimitive(PrimitiveComponent component, Entity entity) - { - Register(new PrimitiveApi(component.gameObject, entity, this), component, entity); - } - - public void RegisterRamp(RampComponent component, Entity entity) - { - Register(new RampApi(component.gameObject, entity, this), component, entity); - } - - public void RegisterRubber(RubberComponent component, Entity entity) - { - Register(new RubberApi(component.gameObject, entity, this), component, entity); - } - - public void RegisterSpinner(SpinnerComponent component, Entity entity) - { - Register(new SpinnerApi(component.gameObject, entity, this), component, entity); - RegisterTransform(SpinnerPlateTransforms, component, entity); } - public void RegisterSurface(SurfaceComponent component, Entity entity) + public void Register(PlayfieldApi playfieldApi) { - Register(new SurfaceApi(component.gameObject, entity, this), component, entity); + PlayfieldApi = playfieldApi; + _colliderGenerators.Add(playfieldApi); } - public void RegisterTeleporter(TeleporterComponent component) - { - Register(new TeleporterApi(component.gameObject, this), component); - } - - public void RegisterTrigger(TriggerComponent component, Entity entity) - { - Register(new TriggerApi(component.gameObject, entity, this), component, entity); - TriggerTransforms[entity] = component.gameObject.transform; - } - - public void RegisterTrigger(TriggerData data, Entity entity, GameObject go) - { - var component = go.AddComponent(); - component.SetData(data); - Register(new TriggerApi(go, entity, this), component, entity); - } - - public void RegisterTrough(TroughComponent component) - { - Register(new TroughApi(component.gameObject, this), component); - } - - public void RegisterMetalWireGuide(MetalWireGuideComponent component, Entity entity) - { - Register(new MetalWireGuideApi(component.gameObject, entity, this), component, entity); - } - - private void Register(TApi api, MonoBehaviour component, Entity entity = default) where TApi : IApi + public void Register(TApi api, MonoBehaviour component) where TApi : IApi { TableApi.Register(component, api); _apis.Add(api); + var itemId = component.gameObject.GetInstanceID(); + Debug.Log($"Registering {component.GetType()} {component.name} with ID {itemId}."); if (api is IApiRotatable rotatable) { - _rotatables[entity] = rotatable; + _rotatables[itemId] = rotatable; } if (api is IApiSlingshot slingshot) { - _slingshots[entity] = slingshot; + _slingshots[itemId] = slingshot; } if (api is IApiDroppable droppable) { - _droppables[entity] = droppable; + _droppables[itemId] = droppable; } if (api is IApiSpinnable spinnable) { - _spinnables[entity] = spinnable; + _spinnables[itemId] = spinnable; } if (api is IApiSwitchDevice switchDevice) { if (component is ISwitchDeviceComponent switchDeviceComponent) { @@ -411,30 +268,22 @@ private void Register(TApi api, MonoBehaviour component, Entity entity = d } if (api is IApiColliderGenerator colliderGenerator) { - RegisterCollider(entity, colliderGenerator); - } - } - - private void RegisterTransform(Dictionary transforms, MonoBehaviour component, Entity entity) where T : MonoBehaviour - { - var comp = component.gameObject.GetComponentInChildren(); - if (comp) { - transforms[entity] = comp.gameObject.transform; + RegisterCollider(itemId, colliderGenerator); } } - private void RegisterCollider(Entity entity, IApiColliderGenerator apiColl) + private void RegisterCollider(int itemId, IApiColliderGenerator apiColl) { if (!apiColl.IsColliderAvailable) { return; } _colliderGenerators.Add(apiColl); if (apiColl is IApiHittable apiHittable) { - _hittables[entity] = apiHittable; + _hittables[itemId] = apiHittable; } if (apiColl is IApiCollidable apiCollidable) { - _collidables[entity] = apiCollidable; + _collidables[itemId] = apiCollidable; } } @@ -442,65 +291,60 @@ private void RegisterCollider(Entity entity, IApiColliderGenerator apiColl) #region Events - public void Queue(Action action) => _simulationSystemGroup.QueueBeforeBallCreation(action); - public void ScheduleAction(int timeMs, Action action) => _simulationSystemGroup.ScheduleAction(timeMs, action); - public void ScheduleAction(uint timeMs, Action action) => _simulationSystemGroup.ScheduleAction(timeMs, action); + public void ScheduleAction(int timeMs, Action action) => PhysicsEngine.ScheduleAction(timeMs, action); + public void ScheduleAction(uint timeMs, Action action) => PhysicsEngine.ScheduleAction(timeMs, action); public void OnEvent(in EventData eventData) { - switch (eventData.eventId) { + Debug.Log(eventData); + switch (eventData.EventId) { case EventId.HitEventsHit: - if (!_hittables.ContainsKey(eventData.ItemEntity)) { - Debug.LogError($"Cannot find entity {eventData.ItemEntity} in hittables."); + if (!_hittables.ContainsKey(eventData.ItemId)) { + Debug.LogError($"Cannot find {eventData.ItemId} in hittables."); } - _hittables[eventData.ItemEntity].OnHit(eventData.BallEntity); + _hittables[eventData.ItemId].OnHit(eventData.BallId); break; case EventId.HitEventsUnhit: - _hittables[eventData.ItemEntity].OnHit(eventData.BallEntity, true); + _hittables[eventData.ItemId].OnHit(eventData.BallId, true); break; case EventId.LimitEventsBos: - _rotatables[eventData.ItemEntity].OnRotate(eventData.FloatParam, false); + _rotatables[eventData.ItemId].OnRotate(eventData.FloatParam, false); break; case EventId.LimitEventsEos: - _rotatables[eventData.ItemEntity].OnRotate(eventData.FloatParam, true); + _rotatables[eventData.ItemId].OnRotate(eventData.FloatParam, true); break; case EventId.SpinnerEventsSpin: - _spinnables[eventData.ItemEntity].OnSpin(); + _spinnables[eventData.ItemId].OnSpin(); break; case EventId.FlipperEventsCollide: - _collidables[eventData.ItemEntity].OnCollide(eventData.BallEntity, eventData.FloatParam); + _collidables[eventData.ItemId].OnCollide(eventData.BallId, eventData.FloatParam); break; case EventId.SurfaceEventsSlingshot: - _slingshots[eventData.ItemEntity].OnSlingshot(eventData.BallEntity); + _slingshots[eventData.ItemId].OnSlingshot(eventData.BallId); break; case EventId.TargetEventsDropped: - _droppables[eventData.ItemEntity].OnDropStatusChanged(true, eventData.BallEntity); + _droppables[eventData.ItemId].OnDropStatusChanged(true, eventData.BallId); break; case EventId.TargetEventsRaised: - _droppables[eventData.ItemEntity].OnDropStatusChanged(false, eventData.BallEntity); + _droppables[eventData.ItemId].OnDropStatusChanged(false, eventData.BallId); break; default: - throw new InvalidOperationException($"Unknown event {eventData.eventId} for entity {eventData.ItemEntity}"); + throw new InvalidOperationException($"Unknown event {eventData.EventId} for entity {eventData.ItemId}"); } } - internal void BallCreated(Entity ballEntity, GameObject ball) - { - OnBallCreated?.Invoke(this, new BallEvent(ballEntity, ball)); - } - internal void BallDestroyed(Entity ballEntity, GameObject ball) - { - OnBallDestroyed?.Invoke(this, new BallEvent(ballEntity, ball)); - } + internal void BallCreated(int ballId, GameObject ball) => OnBallCreated?.Invoke(this, new BallEvent(ballId, ball)); + + internal void BallDestroyed(int ballId, GameObject ball) => OnBallDestroyed?.Invoke(this, new BallEvent(ballId, ball)); #endregion @@ -599,12 +443,12 @@ private static void HandleInput(object obj, InputActionChange change) public readonly struct BallEvent { - public readonly Entity BallEntity; + public readonly int BallId; public readonly GameObject Ball; - public BallEvent(Entity ballEntity, GameObject ball) + public BallEvent(int ballId, GameObject ball) { - BallEntity = ballEntity; + BallId = ballId; Ball = ball; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs index 10808496c..b68b6d66b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/SwitchHandler.cs @@ -17,10 +17,8 @@ using System; using System.Collections.Generic; using NLog; -using Unity.Entities; using UnityEngine; using Logger = NLog.Logger; - #if UNITY_EDITOR using UnityEditor; #endif @@ -44,6 +42,8 @@ public class SwitchHandler public readonly string Name; private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; + private IGamelogicEngine Engine => _player.GamelogicEngine; /// @@ -58,13 +58,13 @@ public class SwitchHandler private readonly Dictionary _switchStatuses = new Dictionary(); - private static VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - public SwitchHandler(string name, Player player, bool isEnabled = false) + public SwitchHandler(string name, Player player, PhysicsEngine physicsEngine, bool isEnabled = false) { Name = name; _player = player; + _physicsEngine = physicsEngine; IsEnabled = isEnabled; } @@ -129,7 +129,7 @@ internal void OnSwitch(bool enabled) // if it's pulse, schedule to re-open if (enabled && switchConfig.IsPulseSwitch) { - SimulationSystemGroup.ScheduleAction(switchConfig.PulseDelay, + _physicsEngine.ScheduleAction(switchConfig.PulseDelay, () => { _switchStatuses[switchConfig.SwitchId].IsSwitchEnabled = false; Engine.Switch(switchConfig.SwitchId, switchConfig.IsNormallyClosed); @@ -162,7 +162,7 @@ internal void ScheduleSwitch(bool enabled, int delay, Action onSwitched) // handle switch -> gamelogic engine if (Engine != null && _switches != null) { foreach (var switchConfig in _switches) { - SimulationSystemGroup.ScheduleAction(delay, + _physicsEngine.ScheduleAction(delay, () => Engine.Switch(switchConfig.SwitchId, switchConfig.IsNormallyClosed ? !enabled : enabled)); } } else { @@ -175,7 +175,7 @@ internal void ScheduleSwitch(bool enabled, int delay, Action onSwitched) var device = _player.WireDevice(wireConfig.Device); if (device != null) { var dest = device.Wire(wireConfig.DeviceItem); - SimulationSystemGroup.ScheduleAction(delay, () => dest.OnChange(enabled)); + _physicsEngine.ScheduleAction(delay, () => dest.OnChange(enabled)); } else { Logger.Warn($"Cannot find wire device \"{wireConfig.Device}\"."); @@ -184,7 +184,7 @@ internal void ScheduleSwitch(bool enabled, int delay, Action onSwitched) } // handle own status - SimulationSystemGroup.ScheduleAction(delay, () => { + _physicsEngine.ScheduleAction(delay, () => { Debug.Log($"Setting scheduled switch {Name} to {enabled}."); IsEnabled = enabled; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs deleted file mode 100644 index 46fe9dcb8..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballAutomaticWorldBootstrap.cs +++ /dev/null @@ -1,38 +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 . - -// https://forum.unity.com/threads/got-nullreferenceexception-from-automaticworldbootstrap-initialize.1075933/#post-7132262 -// https://github.com/freezy/VisualPinball.Engine/issues/371 - -#if UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP - -using Unity.Entities; -using UnityEngine; - -namespace VisualPinball.Unity -{ - static class VisualPinballAutomaticWorldBootstrap - { - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] - static void Initialize() - { - DefaultWorldInitialization.Initialize("Default World", false); - //GameObjectSceneUtility.AddGameObjectSceneReferences(); - } - } -} - -#endif diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs deleted file mode 100644 index ce713a370..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs +++ /dev/null @@ -1,211 +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; -using System.Collections.Generic; -using System.Diagnostics; -using Unity.Entities; -using Unity.Transforms; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - /// - /// Main physics simulation system, executed once per frame. - /// - [UpdateBefore(typeof(TransformSystemGroup))] - internal class VisualPinballSimulationSystemGroup : ComponentSystemGroup - { - public double PhysicsDiffTime; - public double CurrentPhysicsTime => _currentPhysicsTime * (1.0 / PhysicsConstants.DefaultStepTime); - public uint TimeMsec; - - public override IReadOnlyList Systems => _systemsToUpdate; - - private readonly Stopwatch _time = new Stopwatch(); - private ulong _currentPhysicsTime; - private ulong _currentPhysicsFrameTime; - private ulong _nextPhysicsFrameTime; - - private readonly List _systemsToUpdate = new List(); - private CreateBallEntityCommandBufferSystem _createBallEntityCommandBufferSystem; - private UpdateVelocitiesSystemGroup _velocitiesSystemGroup; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private BallRingCounterSystem _ballRingCounterSystem; - private UpdateAnimationsSystemGroup _updateAnimationsSystemGroup; - private TransformMeshesSystemGroup _transformMeshesSystemGroup; - - private readonly Queue _afterBallCreationQueue = new Queue(); - private readonly Queue _beforeBallCreationQueue = new Queue(); - private readonly List _scheduledActions = new List(); - - private const TimingMode Timing = TimingMode.UnityTime; - - protected override void OnCreate() - { - // let IPhysicsEngine enable it - Enabled = false; - - _time.Start(); - - _createBallEntityCommandBufferSystem = World.GetOrCreateSystem(); - _velocitiesSystemGroup = World.GetOrCreateSystem(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _ballRingCounterSystem = World.GetOrCreateSystem(); - _updateAnimationsSystemGroup = World.GetOrCreateSystem(); - _transformMeshesSystemGroup = World.GetOrCreateSystem(); - - _systemsToUpdate.Add(_createBallEntityCommandBufferSystem); - _systemsToUpdate.Add(_velocitiesSystemGroup); - _systemsToUpdate.Add(_simulateCycleSystemGroup); - _systemsToUpdate.Add(_ballRingCounterSystem); - _systemsToUpdate.Add(_updateAnimationsSystemGroup); - _systemsToUpdate.Add(_transformMeshesSystemGroup); - base.OnCreate(); - } - - protected override void OnStartRunning() - { - _currentPhysicsTime = GetTargetTime(); - _nextPhysicsFrameTime = _currentPhysicsTime + PhysicsConstants.PhysicsStepTime; - } - - protected override void OnUpdate() - { - lock (_beforeBallCreationQueue) { - while (_beforeBallCreationQueue.Count > 0) { - _beforeBallCreationQueue.Dequeue().Invoke(); - } - } - _createBallEntityCommandBufferSystem.Update(); - lock (_afterBallCreationQueue) { - while (_afterBallCreationQueue.Count > 0) { - _afterBallCreationQueue.Dequeue().Invoke(); - } - } - - //const int startTimeUsec = 0; - var initialTimeUsec = GetTargetTime(); - - while (_currentPhysicsFrameTime < initialTimeUsec) { - - TimeMsec = (uint) (Time.ElapsedTime * 1000); - PhysicsDiffTime = (_nextPhysicsFrameTime - _currentPhysicsFrameTime) * (1.0 / PhysicsConstants.DefaultStepTime); - - // update velocities - _velocitiesSystemGroup.Update(); - - // simulate cycle - _simulateCycleSystemGroup.Update(); - - // new cycle, on physics frame boundary - _currentPhysicsFrameTime = _nextPhysicsFrameTime; - - // advance physics position - _nextPhysicsFrameTime += PhysicsConstants.PhysicsStepTime; - - // run scheduled actions - lock (_scheduledActions) { - for (var i = _scheduledActions.Count - 1; i >= 0; i--) { - if (_currentPhysicsFrameTime > _scheduledActions[i].ScheduleAt) { - _scheduledActions[i].Action(); - _scheduledActions.RemoveAt(i); - } - } - } - } - - _ballRingCounterSystem.Update(); - - _currentPhysicsTime = _currentPhysicsFrameTime; - - // update animations - _updateAnimationsSystemGroup.Update(); - - // transform all meshes - _transformMeshesSystemGroup.Update(); - } - - private ulong GetTargetTime() - { - const long dt60fps = 1000000 / 60; - - switch (Timing) { - case TimingMode.Atleast60: - var dt = (ulong)(Time.DeltaTime * 1000000); - if (_currentPhysicsTime > 0 && dt > dt60fps) { - dt = dt60fps; - } - return _currentPhysicsTime + dt; - - case TimingMode.Locked60: - return _currentPhysicsTime + dt60fps; - - case TimingMode.UnityTime: - return (ulong)(Time.ElapsedTime * 1000000); - - case TimingMode.SystemTime: - return (ulong) (_time.Elapsed.TotalMilliseconds * 1000); - - default: - throw new ArgumentOutOfRangeException(); - } - } - - public void QueueBeforeBallCreation(Action action) - { - lock (_beforeBallCreationQueue) { - _beforeBallCreationQueue.Enqueue(action); - } - } - - public void QueueAfterBallCreation(Action action) - { - lock (_afterBallCreationQueue) { - _afterBallCreationQueue.Enqueue(action); - } - } - - public void ScheduleAction(int timeoutMs, Action action) => ScheduleAction((uint)timeoutMs, action); - public void ScheduleAction(uint timeoutMs, Action action) - { - lock (_scheduledActions) { - _scheduledActions.Add(new ScheduledAction(_currentPhysicsFrameTime + (ulong)timeoutMs * 1000, action)); - } - } - - private enum TimingMode - { - UnityTime, - SystemTime, - Atleast60, - Locked60 - } - - private class ScheduledAction - { - public readonly ulong ScheduleAt; - public readonly Action Action; - - public ScheduledAction(ulong scheduleAt, Action action) - { - ScheduleAt = scheduleAt; - Action = action; - } - } - } - -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs index c34794d87..4610cc571 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Game/WirePlayer.cs @@ -17,12 +17,10 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; +using NLog; using UnityEngine; using UnityEngine.InputSystem; -using NLog; using Logger = NLog.Logger; - #if UNITY_EDITOR using UnityEditor; #endif @@ -41,11 +39,11 @@ public class WirePlayer private readonly Dictionary>> _gleSignals = new Dictionary>>(); private Player _player; + private PhysicsEngine _physicsEngine; private TableComponent _tableComponent; private InputManager _inputManager; private SwitchPlayer _switchPlayer; - private static VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); internal Dictionary WireStatuses { get; } = new Dictionary(); @@ -54,12 +52,13 @@ public class WirePlayer #region Lifecycle - public void Awake(TableComponent tableComponent, InputManager inputManager, SwitchPlayer switchPlayer, Player player) + public void Awake(TableComponent tableComponent, InputManager inputManager, SwitchPlayer switchPlayer, Player player, PhysicsEngine physicsEngine) { _tableComponent = tableComponent; _inputManager = inputManager; _switchPlayer = switchPlayer; _player = player; + _physicsEngine = physicsEngine; } public void OnStart() @@ -311,7 +310,7 @@ public void HandleSwitchChange(WireDestConfig wireConfig, bool isEnabled) // if it's pulse, schedule to re-open if (isEnabled && wireConfig.IsPulseSource) { - SimulationSystemGroup.ScheduleAction(wireConfig.PulseDelay, () => { + _physicsEngine.ScheduleAction(wireConfig.PulseDelay, () => { wire.OnChange(false); WireStatuses[wireConfig.Id] = (false, 0); #if UNITY_EDITOR @@ -329,7 +328,7 @@ public void HandleSwitchChange(WireDestConfig wireConfig, bool isEnabled) // if it's pulse, schedule to re-open if (isEnabled && wireConfig.IsPulseSource) { - SimulationSystemGroup.ScheduleAction(wireConfig.PulseDelay, () => { + _physicsEngine.ScheduleAction(wireConfig.PulseDelay, () => { wire.OnChange(false); WireStatuses[wireConfig.Id] = (false, -2); #if UNITY_EDITOR @@ -516,7 +515,7 @@ public class WireDestConfig internal bool IsActive; /// - /// Status flag for dynamic wires. Timestamp () + /// Status flag for dynamic wires. Timestamp () /// when the wire became active. /// internal float ActiveSince; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs index ac4a7f963..bd18a8a48 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/CircleCollider.cs @@ -14,20 +14,21 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT; -using Random = Unity.Mathematics.Random; namespace VisualPinball.Unity { internal struct CircleCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly float2 Center; public readonly float Radius; @@ -35,7 +36,7 @@ internal struct CircleCollider : ICollider private readonly float _zHigh; private readonly float _zLow; - public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb( + public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( Center.x - Radius, Center.x + Radius, Center.y - Radius, @@ -46,34 +47,22 @@ internal struct CircleCollider : ICollider public CircleCollider(float2 center, float radius, float zLow, float zHigh, ColliderInfo info, ColliderType type = ColliderType.Circle) : this() { - _header.Init(info, type); + Header.Init(info, type); Center = center; Radius = radius; _zHigh = zHigh; _zLow = zLow; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[colliderId]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(CircleCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { // normal face, lateral, rigid return HitTestBasicRadius(ref collEvent, ref insideOfs, ball, dTime, true, true, true); } - public float HitTestBasicRadius(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime, bool direction, bool lateral, bool rigid) + public float HitTestBasicRadius(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime, bool direction, bool lateral, bool rigid) { // todo IsEnabled if (/*!IsEnabled || */ball.IsFrozen) { @@ -85,8 +74,8 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref DynamicBuf var dv = ball.Velocity; var capsule3D = !lateral && ball.Position.z > _zHigh; - var isKicker = _header.ItemType == ItemType.Kicker; - var isKickerOrTrigger = _header.ItemType == ItemType.Trigger || _header.ItemType == ItemType.Kicker; + var isKicker = Header.ItemType == ItemType.Kicker; + var isKickerOrTrigger = Header.ItemType == ItemType.Trigger || Header.ItemType == ItemType.Kicker; float targetRadius; if (capsule3D) { @@ -129,7 +118,7 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref DynamicBuf // Kicker is special.. handle ball stalled on kicker, commonly hit while receding, knocking back into kicker pocket if (isKicker && bnd <= 0 && bnd >= -Radius && a < PhysicsConstants.ContactVel * PhysicsConstants.ContactVel/* && ball.Hit.IsRealBall()*/) { - BallData.SetOutsideOf(ref insideOfs, _header.Entity); + insideOfs.SetOutsideOf(Header.ItemId, ball.Id); } // contact positive possible in future ... objects Negative in contact now @@ -149,13 +138,13 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref DynamicBuf hitTime = math.max(0.0f, (float) (-bnd / bnv)); } - } else if (isKickerOrTrigger /*&& ball.Hit.IsRealBall()*/ && bnd < 0 == BallData.IsOutsideOf(in insideOfs, in _header.Entity)) { + } else if (isKickerOrTrigger /*&& ball.Hit.IsRealBall()*/ && bnd < 0 == insideOfs.IsOutsideOf(Header.ItemId, ball.Id)) { // triggers & kickers // here if ... ball inside and no hit set .... or ... ball outside and hit set if (math.abs(bnd - Radius) < 0.05) { // if ball appears in center of trigger, then assumed it was gen"ed there - BallData.SetInsideOf(ref insideOfs, _header.Entity); // special case for trigger overlaying a kicker + insideOfs.SetInsideOf(Header.ItemId, ball.Id); // special case for trigger overlaying a kicker } else { // this will add the ball to the trigger space without a Hit @@ -228,9 +217,9 @@ public float HitTestBasicRadius(ref CollisionEventData collEvent, ref DynamicBuf #endregion - public void Collide(ref BallData ball, in CollisionEventData collEvent, ref Random random) + 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); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs index 5b062154f..7cc531caa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Collider.cs @@ -16,11 +16,9 @@ using System; using Unity.Collections; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Game; using VisualPinball.Engine.VPT; -using Random = Unity.Mathematics.Random; namespace VisualPinball.Unity { @@ -28,12 +26,12 @@ namespace VisualPinball.Unity /// Base struct common to all colliders. /// Dispatches the interface methods to appropriate implementations for the collider type. /// - public struct Collider : IComponentData + public struct Collider { public ColliderHeader Header; public int Id => Header.Id; - public Entity Entity => Header.Entity; + public int ItemId => Header.ItemId; public ColliderType Type => Header.Type; public PhysicsMaterialData Material => Header.Material; public float Threshold => Header.Threshold; @@ -67,6 +65,8 @@ public unsafe ColliderBounds Bounds() { return ((LineZCollider*) collider)->Bounds; case ColliderType.Point: return ((PointCollider*) collider)->Bounds; + case ColliderType.Plane: + return ((PlaneCollider*) collider)->Bounds; case ColliderType.Plunger: return ((PlungerCollider*) collider)->Bounds; case ColliderType.Spinner: @@ -74,97 +74,12 @@ public unsafe ColliderBounds Bounds() { case ColliderType.Triangle: return ((TriangleCollider*) collider)->Bounds; default: - throw new InvalidOperationException("Cannot compute AABBs for collider " + Type); - } - } - } - - internal static unsafe float HitTest(ref Collider coll, ref CollisionEventData collEvent, - ref DynamicBuffer insideOf, in BallData ball, float dTime) - { - fixed (Collider* collider = &coll) - { - switch (collider->Type) - { - case ColliderType.Bumper: - case ColliderType.Circle: - return ((CircleCollider*) collider)->HitTest(ref collEvent, ref insideOf, in ball, dTime); - case ColliderType.Gate: - return ((GateCollider*) collider)->HitTest(ref collEvent, ref insideOf, in ball, dTime); - case ColliderType.Line: - return ((LineCollider*) collider)->HitTest(ref collEvent, ref insideOf, in ball, dTime); - case ColliderType.LineZ: - return ((LineZCollider*) collider)->HitTest(ref collEvent, in ball, dTime); - case ColliderType.Line3D: - return ((Line3DCollider*) collider)->HitTest(ref collEvent, in ball, dTime); - case ColliderType.Point: - return ((PointCollider*) collider)->HitTest(ref collEvent, in ball, dTime); - case ColliderType.Plane: - return ((PlaneCollider*) collider)->HitTest(ref collEvent, in ball, dTime); - case ColliderType.Spinner: - return ((SpinnerCollider*) collider)->HitTest(ref collEvent, ref insideOf, in ball, dTime); - case ColliderType.Triangle: - return ((TriangleCollider*) collider)->HitTest(ref collEvent, in insideOf, in ball, dTime); - case ColliderType.KickerCircle: - case ColliderType.TriggerCircle: - return ((CircleCollider*) collider)->HitTestBasicRadius(ref collEvent, ref insideOf, in ball, dTime, false, false, false); - case ColliderType.TriggerLine: - return ((LineCollider*) collider)->HitTestBasic(ref collEvent, ref insideOf, in ball, dTime, false, false, false); - - case ColliderType.Plunger: - throw new InvalidOperationException("ColliderType.Plunger must be hit-tested separately!"); - case ColliderType.Flipper: - throw new InvalidOperationException("ColliderType.Flipper must be hit-tested separately!"); - case ColliderType.LineSlingShot: - throw new InvalidOperationException("ColliderType.LineSlingShot must be hit-tested separately!"); - - default: - return -1; - } - } - } - - /// - /// Most colliders use the standard Collide3DWall routine, only overrides - /// are cast and dispatched to their respective implementation. - /// - internal static unsafe void Collide(ref Collider coll, ref BallData ballData, - ref NativeQueue.ParallelWriter events, in Entity ballEntity, - in CollisionEventData collEvent, ref Random random) - { - fixed (Collider* collider = &coll) - { - switch (collider->Type) - { - case ColliderType.Circle: - ((CircleCollider*) collider)->Collide(ref ballData, in collEvent, ref random); - break; - case ColliderType.Line: - ((LineCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random); - break; - case ColliderType.Line3D: - ((Line3DCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random); - break; - case ColliderType.LineZ: - ((LineZCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random); - break; - case ColliderType.Plane: - ((PlaneCollider*) collider)->Collide(ref ballData, in collEvent, ref random); - break; - case ColliderType.Point: - ((PointCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random); - break; - case ColliderType.Triangle: - ((TriangleCollider*) collider)->Collide(ref ballData, ref events, in ballEntity, in collEvent, ref random); - break; - - default: - break; + throw new InvalidOperationException(); } } } - internal static void FireHitEvent(ref BallData ball, ref NativeQueue.ParallelWriter events, in Entity ballEntity, in ColliderHeader collHeader) + internal static void FireHitEvent(ref BallState ball, ref NativeQueue.ParallelWriter events, in ColliderHeader collHeader) { if (collHeader.FireEvents/* && collHeader.IsEnabled*/) { // todo enabled @@ -180,14 +95,14 @@ internal static void FireHitEvent(ref BallData ball, ref NativeQueue. // must be a new place if only by a little if (distLs > normalDist) { - events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.ItemId, ball.Id, true)); } } } - internal static void Contact(ref Collider coll, ref BallData ball, in CollisionEventData collEvent, double hitTime, in float3 gravity) + internal static void Contact(in ColliderHeader collHeader, ref BallState ball, in CollisionEventData collEvent, double hitTime, in float3 gravity) { - BallCollider.HandleStaticContact(ref ball, in collEvent, coll.Header.Material.Friction, (float)hitTime, gravity); + BallCollider.HandleStaticContact(ref ball, in collEvent, collHeader.Material.Friction, (float)hitTime, gravity); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderInfo.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderInfo.cs index 2ce7013cf..336d86ffc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderInfo.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderInfo.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 Unity.Entities; using VisualPinball.Engine.VPT; namespace VisualPinball.Unity @@ -29,11 +28,10 @@ namespace VisualPinball.Unity public struct ColliderInfo { public int Id; + public int ItemId; public ItemType ItemType; - public Entity Entity; public PhysicsMaterialData Material; public float HitThreshold; public bool FireEvents; - public bool IsEnabled; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs new file mode 100644 index 000000000..29e1bc729 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs @@ -0,0 +1,248 @@ +// 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; +using Unity.Collections; +using VisualPinball.Unity.Collections; + +namespace VisualPinball.Unity +{ + public struct ColliderReference : IDisposable + { + internal NativeList CircleColliders; + internal NativeList FlipperColliders; + internal NativeList GateColliders; + internal NativeList Line3DColliders; + internal NativeList LineSlingshotColliders; + internal NativeList LineColliders; + internal NativeList LineZColliders; + internal NativeList PlungerColliders; + internal NativeList PointColliders; + internal NativeList SpinnerColliders; + internal NativeList TriangleColliders; + internal NativeList PlaneColliders; + + public NativeList Lookup; + + public ColliderReference(Allocator allocator) + { + CircleColliders = new NativeList(allocator); + FlipperColliders = new NativeList(allocator); + GateColliders = new NativeList(allocator); + Line3DColliders = new NativeList(allocator); + LineSlingshotColliders = new NativeList(allocator); + LineColliders = new NativeList(allocator); + LineZColliders = new NativeList(allocator); + PlungerColliders = new NativeList(allocator); + PointColliders = new NativeList(allocator); + SpinnerColliders = new NativeList(allocator); + TriangleColliders = new NativeList(allocator); + PlaneColliders = new NativeList(allocator); + Lookup = new NativeList(allocator); + } + + public void Dispose() + { + CircleColliders.Dispose(); + FlipperColliders.Dispose(); + GateColliders.Dispose(); + Line3DColliders.Dispose(); + LineSlingshotColliders.Dispose(); + LineColliders.Dispose(); + LineZColliders.Dispose(); + PlungerColliders.Dispose(); + PointColliders.Dispose(); + SpinnerColliders.Dispose(); + TriangleColliders.Dispose(); + PlaneColliders.Dispose(); + } + + public int Count => Lookup.Length; + + public ICollider this[int i] => LookupCollider(i); + + private ICollider LookupCollider(int i) + { + if (i < 0 || i >= Lookup.Length) { + throw new IndexOutOfRangeException($"Invalid index {i} when looking up collider."); + } + + ref var lookup = ref Lookup.GetElementAsRef(i); + switch (lookup.Type) { + case ColliderType.Circle: return CircleColliders.GetElementAsRef(lookup.Index); + case ColliderType.Flipper: return FlipperColliders.GetElementAsRef(lookup.Index); + case ColliderType.Gate: return GateColliders.GetElementAsRef(lookup.Index); + case ColliderType.Line3D: return Line3DColliders.GetElementAsRef(lookup.Index); + case ColliderType.LineSlingShot: return LineSlingshotColliders.GetElementAsRef(lookup.Index); + case ColliderType.Line: return LineColliders.GetElementAsRef(lookup.Index); + case ColliderType.LineZ: return LineZColliders.GetElementAsRef(lookup.Index); + case ColliderType.Plunger: return PlungerColliders.GetElementAsRef(lookup.Index); + case ColliderType.Point: return PointColliders.GetElementAsRef(lookup.Index); + case ColliderType.Spinner: return SpinnerColliders.GetElementAsRef(lookup.Index); + case ColliderType.Triangle: return TriangleColliders.GetElementAsRef(lookup.Index); + case ColliderType.Plane: return PlaneColliders.GetElementAsRef(lookup.Index); + } + throw new ArgumentException($"Unknown lookup type."); + } + + #region Add + + internal int Add(CircleCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Circle, CircleColliders.Length)); + CircleColliders.Add(collider); + return collider.Id; + } + + internal int Add(FlipperCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Flipper, FlipperColliders.Length)); + FlipperColliders.Add(collider); + return collider.Id; + } + + internal int Add(GateCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Gate, GateColliders.Length)); + GateColliders.Add(collider); + return collider.Id; + } + + internal int Add(Line3DCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Line3D, Line3DColliders.Length)); + Line3DColliders.Add(collider); + return collider.Id; + } + + internal int Add(LineSlingshotCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.LineSlingShot, LineSlingshotColliders.Length)); + LineSlingshotColliders.Add(collider); + return collider.Id; + } + + internal int Add(LineCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Line, LineColliders.Length)); + LineColliders.Add(collider); + return collider.Id; + } + + internal int Add(LineZCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.LineZ, LineZColliders.Length)); + LineZColliders.Add(collider); + return collider.Id; + } + + internal int Add(PlungerCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Plunger, PlungerColliders.Length)); + PlungerColliders.Add(collider); + return collider.Id; + } + + internal int Add(PointCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Point, PointColliders.Length)); + PointColliders.Add(collider); + return collider.Id; + } + + internal int Add(SpinnerCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Spinner, SpinnerColliders.Length)); + SpinnerColliders.Add(collider); + return collider.Id; + } + + internal int Add(TriangleCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Triangle, TriangleColliders.Length)); + TriangleColliders.Add(collider); + return collider.Id; + } + + internal int Add(PlaneCollider collider) + { + collider.Id = Lookup.Length; + Lookup.Add(new ColliderLookup(ColliderType.Plane, PlaneColliders.Length)); + PlaneColliders.Add(collider); + return collider.Id; + } + + #endregion + + public ICollider[] ToArray() + { + var array = new ICollider[Lookup.Length]; + for (var i = 0; i < Lookup.Length; i++) { + var lookup = Lookup[i]; + switch (lookup.Type) { + case ColliderType.Circle: + array[i] = CircleColliders[lookup.Index]; + break; + case ColliderType.Flipper: + array[i] = FlipperColliders[lookup.Index]; + break; + case ColliderType.Gate: + array[i] = GateColliders[lookup.Index]; + break; + case ColliderType.Line3D: + array[i] = Line3DColliders[lookup.Index]; + break; + case ColliderType.LineSlingShot: + array[i] = LineSlingshotColliders[lookup.Index]; + break; + case ColliderType.Line: + array[i] = LineColliders[lookup.Index]; + break; + case ColliderType.LineZ: + array[i] = LineZColliders[lookup.Index]; + break; + case ColliderType.Plunger: + array[i] = PlungerColliders[lookup.Index]; + break; + case ColliderType.Point: + array[i] = PointColliders[lookup.Index]; + break; + case ColliderType.Spinner: + array[i] = SpinnerColliders[lookup.Index]; + break; + case ColliderType.Triangle: + array[i] = TriangleColliders[lookup.Index]; + break; + case ColliderType.Plane: + array[i] = PlaneColliders[lookup.Index]; + break; + } + } + return array; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs.meta new file mode 100644 index 000000000..62c4aa785 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderReference.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9444b66c84164b8c9db3a671b943ea03 +timeCreated: 1696717952 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs index 80ca435be..b452e50d2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ColliderUtils.cs @@ -14,16 +14,21 @@ // 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 Unity.Profiling; +using UnityEngine; using VisualPinball.Engine.Math; -using VisualPinball.Engine.VPT; +using Mesh = VisualPinball.Engine.VPT.Mesh; namespace VisualPinball.Unity { public static class ColliderUtils { - public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, ICollection colliders) + 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) { var inputVerts = new float2[rgv.Length]; @@ -41,13 +46,13 @@ public static void Generate3DPolyColliders(in float3[] rgv, ColliderInfo info, I } var mesh = new Mesh(triangulatedVerts, outputIndices); - GenerateCollidersFromMesh(mesh, info, colliders, true); + GenerateCollidersFromMesh(mesh, info, ref colliders, true); } - public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ICollection colliders, bool onlyTriangles = false) + public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, ref ColliderReference colliders, bool onlyTriangles = false) { - var addedEdges = EdgeSet.Get(); - var collCount = colliders.Count; + PerfMarker1.Begin(); + var addedEdges = EdgeSet.Get(Allocator.TempJob); // add collision triangles and edges for (var i = 0; i < mesh.Indices.Length; i += 3) { @@ -76,6 +81,7 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, IColl } } } + addedEdges.Dispose(); // add collision vertices if (!onlyTriangles) { @@ -83,6 +89,50 @@ public static void GenerateCollidersFromMesh(Mesh mesh, ColliderInfo info, IColl colliders.Add(new PointCollider(vertex.ToUnityFloat3(), info)); } } + PerfMarker1.End(); + } + + public static void GenerateCollidersFromMesh(in NativeArray vertices, in NativeArray indices, ref Matrix4x4 matrix, ColliderInfo info, ref ColliderReference colliders, bool onlyTriangles = false) + { + PerfMarker2.Begin(); + var addedEdges = EdgeSet.Get(Allocator.TempJob, vertices.Length); + + // add collision triangles and edges + for (var i = 0; i < indices.Length; i += 3) { + var i0 = indices[i]; + var i1 = indices[i + 1]; + 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]); + + colliders.Add(new TriangleCollider(rgv0, rgv2, rgv1, info)); + + if (!onlyTriangles) { + + if (addedEdges.ShouldAddHitEdge(i0, i1)) { + colliders.Add(new Line3DCollider(rgv0, rgv2, info)); + } + if (addedEdges.ShouldAddHitEdge(i1, i2)) { + colliders.Add(new Line3DCollider(rgv2, rgv1, info)); + } + if (addedEdges.ShouldAddHitEdge(i2, i0)) { + colliders.Add(new Line3DCollider(rgv1, rgv0, info)); + } + } + } + + // add collision vertices + if (!onlyTriangles) { + foreach (var vertex in vertices) { + colliders.Add(new PointCollider(matrix.MultiplyPoint(vertex), info)); + } + } + + addedEdges.Dispose(); + PerfMarker2.End(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs index a17605b2e..2c480eb8e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/ICollider.cs @@ -14,14 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { public interface ICollider { int Id { get; } - void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int index); ColliderBounds Bounds { get; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs index 0c8f7a24a..ebbf1ec4a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/Line3DCollider.cs @@ -16,16 +16,24 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { internal struct Line3DCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set { + Header.Id = value; + var bounds = Bounds; + bounds.ColliderId = value; + Bounds = bounds; + } + } - private ColliderHeader _header; + public ColliderHeader Header; // these are all used when casting this to LineZCollider, // so the order is important too. @@ -37,11 +45,11 @@ internal struct Line3DCollider : ICollider private readonly float _zHigh; private readonly float3x3 _matrix; - public ColliderBounds Bounds { get; set; } + public ColliderBounds Bounds { get; private set; } public Line3DCollider(float3 v1, float3 v2, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Line3D); + Header.Init(info, ColliderType.Line3D); var vLine = math.normalize(v2 - v1); @@ -73,7 +81,7 @@ public Line3DCollider(float3 v1, float3 v2, ColliderInfo info) : this() _zLow = math.min(trans1.z, trans2Z); _zHigh = math.max(trans1.z, trans2Z); - Bounds = new ColliderBounds(_header.Entity, _header.Id, new Aabb( + 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), @@ -83,35 +91,15 @@ public Line3DCollider(float3 v1, float3 v2, ColliderInfo info) : this() )); } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - var bounds = Bounds; - bounds.ColliderId = colliderId; - Bounds = bounds; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(Line3DCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, in BallState ball, float dTime) { return HitTest(ref collEvent, ref this, in ball, dTime); } - private static float HitTest(ref CollisionEventData collEvent, ref Line3DCollider coll, in BallData ball, float dTime) + private static float HitTest(ref CollisionEventData collEvent, ref Line3DCollider coll, in BallState ball, float dTime) { - // todo - // if (!IsEnabled) { - // return -1.0f; - // } - var hitTestBall = ball; // transform ball to cylinder coordinate system @@ -131,16 +119,18 @@ private static float HitTest(ref CollisionEventData collEvent, ref Line3DCollide #endregion - public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - in Entity ballEntity, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + in CollisionEventData collEvent, ref Random random) { var dot = math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); - if (_header.FireEvents && dot >= _header.Threshold && _header.IsPrimitive) { + if (Header.FireEvents && dot >= Header.Threshold && Header.IsPrimitive) { // todo m_obj->m_currentHitThreshold = dot; - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header); + Collider.FireHitEvent(ref ball, ref hitEvents, in Header); } } + + public override string ToString() => $"Line3DCollider[{Header.ItemId}] ({_xy.x}/{_xy.y} | {_zLow} -> {_zHigh})"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs index 3469616d9..9ace858cb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineCollider.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT; @@ -25,9 +23,13 @@ namespace VisualPinball.Unity { internal struct LineCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public float2 V1; public float2 V2; @@ -37,13 +39,15 @@ internal struct LineCollider : ICollider public readonly float ZHigh; private float _length; - private ItemType ItemType => _header.ItemType; - private Entity Entity => _header.Entity; + 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 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.Entity, _header.Id, new Aabb( + 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), @@ -54,7 +58,7 @@ internal struct LineCollider : ICollider public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info, ColliderType type = ColliderType.Line) : this() { - _header.Init(info, type); + Header.Init(info, type); V1 = v1; V2 = v2; ZLow = zLow; @@ -62,18 +66,6 @@ public LineCollider(float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo CalcNormal(); } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(LineCollider) - ); - } - public void CalcNormal() { var vT = new float2(V1.x - V2.x, V1.y - V2.y); @@ -88,29 +80,25 @@ public void CalcNormal() #region Narrowphase public static float HitTest(ref CollisionEventData collEvent, - ref DynamicBuffer insideOfs, in LineCollider coll, in BallData ball, float dTime) + ref InsideOfs insideOfs, in LineCollider coll, in BallState ball, float dTime) { - return HitTestBasic(ref collEvent, ref insideOfs, in coll, ball, dTime, true, true, true); // normal face, lateral, rigid + return HitTestBasic(ref collEvent, ref insideOfs, in coll, in ball, dTime, true, true, true); // normal face, lateral, rigid } public float HitTest(ref CollisionEventData collEvent, - ref DynamicBuffer insideOfs, in BallData ball, float dTime) + ref InsideOfs insideOfs, in BallState ball, float dTime) { - return HitTestBasic(ref collEvent, ref insideOfs, in this, ball, dTime, true, true, true); // normal face, lateral, rigid + return HitTestBasic(ref collEvent, ref insideOfs, in this, in ball, dTime, true, true, true); // normal face, lateral, rigid } - public float HitTestBasic(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime, + public float HitTestBasic(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime, bool direction, bool lateral, bool rigid) { - return HitTestBasic(ref collEvent, ref insideOfs, in this, ball, dTime, direction, lateral, rigid); + return HitTestBasic(ref collEvent, ref insideOfs, in this, in ball, dTime, direction, lateral, rigid); } - public static float HitTestBasic(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in LineCollider coll, in BallData ball, float dTime, bool direction, bool lateral, bool rigid) + public static float HitTestBasic(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in LineCollider coll, in BallState ball, float dTime, bool direction, bool lateral, bool rigid) { - // if (!IsEnabled || ball.State.IsFrozen) { - // return -1.0f; - // } - // ball velocity var ballVx = ball.Velocity.x; var ballVy = ball.Velocity.y; @@ -171,7 +159,7 @@ public static float HitTestBasic(ref CollisionEventData collEvent, ref DynamicBu /*todo || !ball.m_vpVolObjs*/ // it's a trigger, so test: || math.abs(bnd) >= ball.Radius * 0.5f // not too close ... nor too far away - || inside == BallData.IsInsideOf(in insideOfs, coll.Entity)) // ...ball outside and hit set or ball inside and no hit set + || inside == insideOfs.IsInsideOf(coll.ItemId, ball.Id)) // ...ball outside and hit set or ball inside and no hit set { return -1.0f; } @@ -229,14 +217,14 @@ public static float HitTestBasic(ref CollisionEventData collEvent, ref DynamicBu #region Collision - public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - in Entity ballEntity, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + in CollisionEventData collEvent, ref Random random) { var dot = math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); - if (dot <= -_header.Threshold) { - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header); + if (dot <= -Header.Threshold) { + Collider.FireHitEvent(ref ball, ref hitEvents, in Header); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs index f03b7593f..57886a1a5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineSlingshotCollider.cs @@ -16,7 +16,6 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Game; @@ -24,9 +23,13 @@ namespace VisualPinball.Unity { internal struct LineSlingshotCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly float2 V1; public readonly float2 V2; @@ -38,7 +41,7 @@ internal struct LineSlingshotCollider : ICollider private readonly float _force; - public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb( + 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), @@ -49,7 +52,7 @@ internal struct LineSlingshotCollider : ICollider public LineSlingshotCollider(float force, float2 v1, float2 v2, float zLow, float zHigh, ColliderInfo info) : this() { - _header.Init(info, ColliderType.LineSlingShot); + Header.Init(info, ColliderType.LineSlingShot); _force = force; V1 = v1; V2 = v2; @@ -58,18 +61,6 @@ public LineSlingshotCollider(float force, float2 v1, float2 v2, float zLow, floa CalcNormal(); } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(LineSlingshotCollider) - ); - } - private void CalcNormal() { var vT = new float2(V1.x - V2.x, V1.y - V2.y); @@ -83,12 +74,12 @@ private void CalcNormal() #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { return HitTest(ref collEvent, ref this, ref insideOfs, in ball, dTime); } - private static float HitTest(ref CollisionEventData collEvent, ref LineSlingshotCollider coll, ref DynamicBuffer insideOfs, in BallData ball, float dTime) + private static float HitTest(ref CollisionEventData collEvent, ref LineSlingshotCollider coll, ref InsideOfs insideOfs, in BallState ball, float dTime) { ref var lineColl = ref UnsafeUtility.As(ref coll); return LineCollider.HitTestBasic(ref collEvent, ref insideOfs, in lineColl, in ball, dTime, true, true, true); @@ -98,7 +89,7 @@ private static float HitTest(ref CollisionEventData collEvent, ref LineSlingshot #region Collision - public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, in Entity ballEntity, in LineSlingshotData slingshotData, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter events, in LineSlingshotState slingshotState, in CollisionEventData collEvent, ref Random random) { var hitNormal = collEvent.HitNormal; @@ -106,9 +97,9 @@ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter var dot = math.dot(collEvent.HitNormal, ball.Velocity); // normal greater than threshold? - var threshold = dot <= -slingshotData.Threshold; + var threshold = dot <= -slingshotState.Threshold; - if (!slingshotData.IsDisabled && threshold) { // enabled and if velocity greater than threshold level + if (!slingshotState.IsDisabled && threshold) { // enabled and if velocity greater than threshold level // length of segment, Unit TAN points from V1 to V2 var len = (V2.x - V1.x) * hitNormal.y - (V2.y - V1.y) * hitNormal.x; @@ -133,9 +124,9 @@ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter ball.Velocity -= hitNormal * force; } - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in hitNormal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in hitNormal, ref random); - if (/*m_obj &&*/ _header.FireEvents /*&& !m_psurface->m_disabled*/ && threshold) { // todo enabled + if (/*m_obj &&*/ Header.FireEvents /*&& !m_psurface->m_disabled*/ && threshold) { // todo enabled // is this the same place as last event? if same then ignore it var distLs = math.lengthsq(ball.EventPosition - ball.Position); @@ -143,7 +134,7 @@ public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter // !! magic distance, must be a new place if only by a little if (distLs > 0.25f) { - events.Enqueue(new EventData(EventId.SurfaceEventsSlingshot, _header.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.SurfaceEventsSlingshot, Header.ItemId, ball.Id, true)); // todo slingshot animation // m_slingshotanim.m_TimeReset = g_pplayer->m_time_msec + 100; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs index 20eb1afcf..c8c38e877 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/LineZCollider.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; @@ -24,9 +22,13 @@ namespace VisualPinball.Unity { internal struct LineZCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public float2 XY; private readonly float _zLow; @@ -34,7 +36,7 @@ internal struct LineZCollider : ICollider public float XyY { set => XY.y = value; } - public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb ( + public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb ( XY.x, XY.x, XY.y, @@ -43,21 +45,9 @@ internal struct LineZCollider : ICollider _zHigh )); - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(LineZCollider) - ); - } - public LineZCollider(float2 xy, float zLow, float zHigh, ColliderInfo info) : this() { - _header.Init(info, ColliderType.LineZ); + Header.Init(info, ColliderType.LineZ); XY = xy; _zLow = zLow; _zHigh = zHigh; @@ -65,18 +55,13 @@ public LineZCollider(float2 xy, float zLow, float zHigh, ColliderInfo info) : th #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, in BallState ball, float dTime) { return HitTest(ref collEvent, in this, in ball, dTime); } - public static float HitTest(ref CollisionEventData collEvent, in LineZCollider coll, in BallData ball, float dTime) + public static float HitTest(ref CollisionEventData collEvent, in LineZCollider coll, in BallState ball, float dTime) { - // todo - // if (!IsEnabled) { - // return -1.0f; - // } - var bp2d = new float2(ball.Position.x, ball.Position.y); var dist = bp2d - coll.XY; // relative ball position var dv = new float2(ball.Velocity.x, ball.Velocity.y); @@ -164,14 +149,14 @@ public static float HitTest(ref CollisionEventData collEvent, in LineZCollider c #region Collision - public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - in Entity ballEntity, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + in int ballId, in CollisionEventData collEvent, ref Random random) { var dot = math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); - if (dot <= -_header.Threshold) { - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header); + if (dot <= -Header.Threshold) { + Collider.FireHitEvent(ref ball, ref hitEvents, in Header); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs index 260f0098b..7ff1a87ee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PlaneCollider.cs @@ -14,51 +14,40 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; +using UnityEngine; using VisualPinball.Engine.Common; +using Random = Unity.Mathematics.Random; namespace VisualPinball.Unity { internal struct PlaneCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; private readonly float3 _normal; private readonly float _distance; - public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, default); + public ColliderBounds Bounds => new(Header.ItemId, Header.Id, new Aabb(float.MinValue, float.MaxValue, float.MinValue, float.MaxValue, float.MinValue, float.MaxValue)); public PlaneCollider(float3 normal, float distance, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Plane); + Header.Init(info, ColliderType.Plane); _normal = normal; _distance = distance; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(PlaneCollider) - ); - } - - public override string ToString() - { - return $"PlaneCollider[{_header.Entity}] {_distance} at ({_normal.x}/{_normal.y}/{_normal.z})"; - } + public override string ToString() => $"PlaneCollider[{Header.ItemId}] {_distance} at ({_normal.x}/{_normal.y}/{_normal.z})"; #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, in BallState ball, float dTime) { // speed in normal direction var bnv = math.dot(_normal, ball.Velocity); @@ -83,6 +72,10 @@ public float HitTest(ref CollisionEventData collEvent, in BallData ball, float d collEvent.HitNormal = _normal; collEvent.HitOrgNormalVelocity = bnv; // remember original normal velocity collEvent.HitDistance = bnd; + + if (collEvent.HitNormal is { x: 0, y: 0, z: 0 }) { + Debug.Log("Hit normal set to zero by plane collider."); + } // hit time is ignored for contacts return 0.0f; @@ -114,9 +107,9 @@ public float HitTest(ref CollisionEventData collEvent, in BallData ball, float d #region Collision - public void Collide(ref BallData ball, in CollisionEventData collEvent, ref Random random) + 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); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); // distance from plane to ball surface var bnd = math.dot(_normal, ball.Position) - ball.Radius - _distance; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs index 94ea677fc..94f6b3956 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/PointCollider.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; @@ -24,13 +22,17 @@ namespace VisualPinball.Unity { internal struct PointCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly float3 P; - public ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb( + public ColliderBounds Bounds => new ColliderBounds(Header.ItemId, Header.Id, new Aabb( P.x, P.x, P.y, @@ -41,31 +43,14 @@ internal struct PointCollider : ICollider public PointCollider(float3 p, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Point); + Header.Init(info, ColliderType.Point); P = p; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(PointCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, in BallState ball, float dTime) { - // todo - // if (!IsEnabled) { - // return -1.0f; - // } - // relative ball position var dist = ball.Position - P; @@ -140,17 +125,19 @@ public float HitTest(ref CollisionEventData collEvent, in BallData ball, float d #region Collision - public void Collide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - in Entity ballEntity, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + in CollisionEventData collEvent, ref Random random) { var dot = math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in collEvent.HitNormal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in collEvent.HitNormal, ref random); - if (dot <= -_header.Threshold) { - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header); + if (dot <= -Header.Threshold) { + Collider.FireHitEvent(ref ball, ref hitEvents, in Header); } } #endregion + + public override string ToString() => $"PointCollider[{Header.ItemId}] ({P.x}/{P.y}/{P.z})"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs index ab63b4a37..0959028ea 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collider/TriangleCollider.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT; @@ -25,9 +23,13 @@ namespace VisualPinball.Unity { internal struct TriangleCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set => Header.Id = value; + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly float3 Rgv0; public readonly float3 Rgv1; @@ -35,8 +37,10 @@ internal struct TriangleCollider : ICollider private readonly float3 _normal; public float3 Normal() => _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 ColliderBounds Bounds => new ColliderBounds(_header.Entity, _header.Id, new Aabb( + 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)), @@ -47,7 +51,7 @@ internal struct TriangleCollider : ICollider public TriangleCollider(float3 rgv0, float3 rgv1, float3 rgv2, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Triangle); + Header.Init(info, ColliderType.Triangle); Rgv0 = rgv0; Rgv1 = rgv1; Rgv2 = rgv2; @@ -57,18 +61,6 @@ public TriangleCollider(float3 rgv0, float3 rgv1, float3 rgv2, ColliderInfo info _normal = math.normalizesafe(math.cross(e0, e1)); } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(TriangleCollider) - ); - } - public static bool IsDegenerate(float3 rg0, float3 rg1, float3 rg2) { var e0 = rg2 - rg0; @@ -80,7 +72,7 @@ public static bool IsDegenerate(float3 rg0, float3 rg1, float3 rg2) #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, in DynamicBuffer insideOfs, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, in InsideOfs insideOfs, in BallState ball, float dTime) { // if (!this.isEnabled) { // return -1.0; @@ -155,7 +147,7 @@ public float HitTest(ref CollisionEventData collEvent, in DynamicBuffer 0; } @@ -177,15 +169,15 @@ public float HitTest(ref CollisionEventData collEvent, in DynamicBuffer.ParallelWriter hitEvents, - in Entity ballEntity, in CollisionEventData collEvent, ref Random random) + public void Collide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + in CollisionEventData collEvent, ref Random random) { var dot = -math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in _header.Material, in collEvent, in _normal, ref random); + BallCollider.Collide3DWall(ref ball, in Header.Material, in collEvent, in _normal, ref random); - if (_header.FireEvents && dot >= _header.Threshold && _header.IsPrimitive) { + if (Header.FireEvents && dot >= Header.Threshold && Header.IsPrimitive) { // todo m_obj->m_currentHitThreshold = dot; - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in _header); + Collider.FireHitEvent(ref ball, ref hitEvents, in Header); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs index 232ae0c34..b856983d6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/Aabb.cs @@ -32,6 +32,9 @@ public struct Aabb public float Height => math.abs(Top - Bottom); public float Depth => math.abs(ZLow - ZHigh); + public Vector3 Min => new Vector3(Left, Top, ZLow); + public Vector3 Max => new Vector3(Right, Bottom, ZHigh); + public Vector3 Center => new Vector3( (Right + Left) / 2f, (Bottom + Top) / 2f, @@ -93,6 +96,31 @@ public bool IntersectRect(Aabb rc) && ZLow <= rc.ZHigh // 0 <= -8.79384 && ZHigh >= rc.ZLow; // 90 >= 58.79945 } + + public static implicit operator NativeTrees.AABB(Aabb aabb) + { + return new NativeTrees.AABB(aabb.Min, aabb.Max); + } + + public static implicit operator NativeTrees.AABB2D(Aabb aabb) + { + return new NativeTrees.AABB2D(new float2(aabb.Min.x, aabb.Min.y), new float2(aabb.Min.x, aabb.Max.y)); + } + + public static bool operator == (Aabb a, Aabb b) => a.Equals(b); + + public static bool operator != (Aabb a, Aabb b) => !a.Equals(b); + + private bool Equals(Aabb a) + { + return + a.Right == Left && + a.Left == Left && + a.Bottom == Bottom && + a.Top == Top && + a.ZLow == ZLow && + a.ZHigh == ZHigh; + } public override string ToString() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs.meta deleted file mode 100644 index 5d42d56d1..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7d7fc699a55b785449dae32e78168bcb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs deleted file mode 100644 index 6c5429515..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs +++ /dev/null @@ -1,160 +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; -using System.Collections.Generic; -using Unity.Burst; -using Unity.Collections; -using Unity.Entities; -using Unity.Jobs; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [BurstCompile] - internal struct ColliderAllocationJob : IJob, IDisposable - { - [ReadOnly] private NativeList _circleColliders; - [ReadOnly] private NativeList _flipperColliders; - [ReadOnly] private NativeList _gateColliders; - [ReadOnly] private NativeList _line3DColliders; - [ReadOnly] private NativeList _lineSlingshotColliders; - [ReadOnly] private NativeList _lineColliders; - [ReadOnly] private NativeList _lineZColliders; - [ReadOnly] private NativeList _plungerColliders; - [ReadOnly] private NativeList _pointColliders; - [ReadOnly] private NativeList _spinnerColliders; - [ReadOnly] private NativeList _triangleColliders; - [ReadOnly] private NativeArray _planeColliders; - - public NativeArray> BlobAsset; - - public ColliderAllocationJob(IEnumerable colliderList, PlaneCollider playfieldCollider, PlaneCollider glassCollider) : this() - { - var perfMarker = new ProfilerMarker("ColliderAllocationJob.ctr"); - perfMarker.Begin(); - - _circleColliders = new NativeList(Allocator.TempJob); - _flipperColliders = new NativeList(Allocator.TempJob); - _gateColliders = new NativeList(Allocator.TempJob); - _line3DColliders = new NativeList(Allocator.TempJob); - _lineSlingshotColliders = new NativeList(Allocator.TempJob); - _lineColliders = new NativeList(Allocator.TempJob); - _lineZColliders = new NativeList(Allocator.TempJob); - _plungerColliders = new NativeList(Allocator.TempJob); - _pointColliders = new NativeList(Allocator.TempJob); - _spinnerColliders = new NativeList(Allocator.TempJob); - _triangleColliders = new NativeList(Allocator.TempJob); - _planeColliders = new NativeArray(2, Allocator.TempJob) { - [0] = playfieldCollider, - [1] = glassCollider - }; - - BlobAsset = new NativeArray>(1, Allocator.TempJob); - - // separate created colliders per type - foreach (var collider in colliderList) { - switch (collider) { - case CircleCollider circleCollider: _circleColliders.Add(circleCollider); break; - case FlipperCollider flipperCollider: _flipperColliders.Add(flipperCollider); break; - case GateCollider gateCollider: _gateColliders.Add(gateCollider); break; - case LineCollider lineCollider: _lineColliders.Add(lineCollider); break; - case Line3DCollider line3DCollider: _line3DColliders.Add(line3DCollider); break; - case LineSlingshotCollider lineSlingshotCollider: _lineSlingshotColliders.Add(lineSlingshotCollider); break; - case LineZCollider lineZCollider: _lineZColliders.Add(lineZCollider); break; - case PlungerCollider plungerCollider: _plungerColliders.Add(plungerCollider); break; - case PointCollider pointCollider: _pointColliders.Add(pointCollider); break; - case SpinnerCollider spinnerCollider: _spinnerColliders.Add(spinnerCollider); break; - case TriangleCollider triangleCollider: _triangleColliders.Add(triangleCollider); break; - } - } - - perfMarker.End(); - } - public void Execute() - { - var builder = new BlobBuilder(Allocator.Temp); - var colliderId = 0; - ref var root = ref builder.ConstructRoot(); - var count = _circleColliders.Length + _flipperColliders.Length + _gateColliders.Length + _line3DColliders.Length - + _lineSlingshotColliders.Length + _lineColliders.Length + _lineZColliders.Length + _plungerColliders.Length - + _pointColliders.Length + _spinnerColliders.Length + _triangleColliders.Length + _planeColliders.Length; - - var colliders = builder.Allocate(ref root.Colliders, count); - - _planeColliders[0].Allocate(builder, ref colliders, colliderId++); - _planeColliders[1].Allocate(builder, ref colliders, colliderId++); - - root.PlayfieldColliderId = _planeColliders[0].Id; - root.GlassColliderId = _planeColliders[1].Id; - - // copy generated colliders into blob array - for (var i = 0; i < _circleColliders.Length; i++) { - _circleColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _flipperColliders.Length; i++) { - _flipperColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _gateColliders.Length; i++) { - _gateColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _line3DColliders.Length; i++) { - _line3DColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _lineSlingshotColliders.Length; i++) { - _lineSlingshotColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _lineColliders.Length; i++) { - _lineColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _lineZColliders.Length; i++) { - _lineZColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _plungerColliders.Length; i++) { - _plungerColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _pointColliders.Length; i++) { - _pointColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _spinnerColliders.Length; i++) { - _spinnerColliders[i].Allocate(builder, ref colliders, colliderId++); - } - for (var i = 0; i < _triangleColliders.Length; i++) { - _triangleColliders[i].Allocate(builder, ref colliders, colliderId++); - } - - BlobAsset[0] = builder.CreateBlobAssetReference(Allocator.Persistent); - builder.Dispose(); - } - - public void Dispose() - { - _circleColliders.Dispose(); - _flipperColliders.Dispose(); - _gateColliders.Dispose(); - _line3DColliders.Dispose(); - _lineSlingshotColliders.Dispose(); - _lineColliders.Dispose(); - _lineZColliders.Dispose(); - _plungerColliders.Dispose(); - _pointColliders.Dispose(); - _spinnerColliders.Dispose(); - _triangleColliders.Dispose(); - _planeColliders.Dispose(); - BlobAsset.Dispose(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs.meta deleted file mode 100644 index b3cf36e79..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderAllocationJob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e1b39a65578966b45abdf218d03e27c8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs deleted file mode 100644 index 66a0e3efd..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs +++ /dev/null @@ -1,27 +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.Entities; - -namespace VisualPinball.Unity -{ - internal struct ColliderBlob : IComponentData - { - public BlobArray> Colliders; - public int PlayfieldColliderId; - public int GlassColliderId; - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs.meta deleted file mode 100644 index 17630be91..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBlob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: dce2e781114bd3a48b96e49445bf7a72 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBounds.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBounds.cs index 0228d60e0..58460f446 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBounds.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderBounds.cs @@ -15,30 +15,32 @@ // along with this program. If not, see . using System; -using Unity.Entities; +using NativeTrees; namespace VisualPinball.Unity { public struct ColliderBounds { - public Entity ColliderEntity; + public int ItemId; public int ColliderId; public Aabb Aabb; - public ColliderBounds(Entity colliderEntity, int colliderId, Aabb aabb) + public ColliderBounds(int itemId, int colliderId, Aabb aabb) { - if (colliderEntity == Entity.Null) { - throw new ArgumentException("Entity must not be null."); + if (itemId == 0) { + throw new ArgumentException("Item ID must not be null."); } - ColliderEntity = colliderEntity; + ItemId = itemId; ColliderId = colliderId; Aabb = aabb; } + public static implicit operator AABB(ColliderBounds b) => b.Aabb; + public override string ToString() { - return $"{Aabb.ToString()} ({ColliderId}:{ColliderEntity})"; + return $"{Aabb.ToString()} ({ItemId}:{ColliderId})"; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs.meta deleted file mode 100644 index 7a281dde0..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c502479a16d8f54418483b12c2b5f9a0 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs index 49a127b5e..291e26a84 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderHeader.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using System; -using Unity.Entities; using VisualPinball.Engine.VPT; namespace VisualPinball.Unity @@ -30,12 +29,11 @@ public struct ColliderHeader public ColliderType Type; public ItemType ItemType; public int Id; - public Entity Entity; + public int ItemId; public PhysicsMaterialData Material; public float Threshold; public bool FireEvents; - public bool IsEnabled; /// /// Some colliders only collide with "primitives", which aren't only @@ -54,17 +52,31 @@ public struct ColliderHeader public void Init(ColliderInfo info, ColliderType colliderType) { - if (info.Entity == Entity.Null) { + if (info.ItemId == 0) { throw new InvalidOperationException("Entity of " + info.ItemType + " " + colliderType + " not set!"); } Type = colliderType; ItemType = info.ItemType; Id = info.Id; - Entity = info.Entity; + ItemId = info.ItemId; Material = info.Material; Threshold = info.HitThreshold; FireEvents = info.FireEvents; - IsEnabled = info.IsEnabled; + } + + public static bool operator == (ColliderHeader a, ColliderHeader b) => a.Equals(b); + public static bool operator != (ColliderHeader a, ColliderHeader b) => !a.Equals(b); + + public bool Equals(ColliderHeader other) + { + return + Type == other.Type && + ItemType == other.ItemType && + Id == other.Id && + ItemId == other.ItemId && + Material == other.Material && + Threshold == other.Threshold && + FireEvents == other.FireEvents; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs index a72e06369..83ed4b55a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/CollisionEventData.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct CollisionEventData : IComponentData + internal struct CollisionEventData { public float HitTime; public float3 HitNormal; @@ -30,37 +29,36 @@ internal struct CollisionEventData : IComponentData public bool IsContact; public int ColliderId; - public Entity ColliderEntity; - + public int BallId; + public void SetCollider(int colliderId) { ColliderId = colliderId; - ColliderEntity = Entity.Null; + BallId = 0; } - public void SetCollider(Entity colliderEntity) + public void SetBallItem(int ballId) { ColliderId = -1; - ColliderEntity = colliderEntity; + BallId = ballId; } public void ClearCollider(float hitTime) { HitTime = hitTime; - ColliderId = -1; - ColliderEntity = Entity.Null; + ClearCollider(); } public void ClearCollider() { ColliderId = -1; - ColliderEntity = Entity.Null; + BallId = 0; } public bool HasCollider() { - return ColliderId > -1 || ColliderEntity != Entity.Null; + return ColliderId > -1 || BallId != 0; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactBufferElement.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactBufferElement.cs index e1e1992ee..dafe9c893 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactBufferElement.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactBufferElement.cs @@ -14,19 +14,16 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - [InternalBufferCapacity(8)] - internal struct ContactBufferElement : IBufferElementData + internal struct ContactBufferElement { public CollisionEventData CollEvent; - public Entity BallEntity; + public int BallId; - public ContactBufferElement(Entity ballEntity, CollisionEventData collEvent) + public ContactBufferElement(int ballId, CollisionEventData collEvent) { - BallEntity = ballEntity; + BallId = ballId; CollEvent = collEvent; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs new file mode 100644 index 000000000..f1523ccac --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs @@ -0,0 +1,40 @@ +// 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 +{ + internal static class ContactPhysics + { + internal static void Update(ref ContactBufferElement contact, ref BallState ball, ref PhysicsState state, float hitTime) + { + ref var collEvent = ref contact.CollEvent; + if (collEvent.ColliderId > -1) { // collide with static collider + var collHeader = state.GetColliderHeader(collEvent.ColliderId); + if (collHeader.Type == ColliderType.Flipper) { + ref var flipperCollider = ref state.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); + } else { + Collider.Contact(in collHeader, ref ball, in collEvent, hitTime, in state.Env.Gravity); + } + } else if (collEvent.BallId != 0) { // collide with ball + ref var collHeader = ref state.GetColliderHeader(contact.CollEvent.ColliderId); + BallCollider.HandleStaticContact(ref ball, in collEvent, collHeader.Material.Friction, hitTime, state.Env.Gravity); + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactSystem.cs deleted file mode 100644 index 7d417000f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ContactSystem.cs +++ /dev/null @@ -1,111 +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 ClassNeverInstantiated.Global - -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class ContactSystem : SystemBase - { - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - - private EntityQuery _collDataEntityQuery; - private float3 _gravity; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("ContactSystem"); - - protected override void OnCreate() - { - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _collDataEntityQuery = EntityManager.CreateEntityQuery(typeof(ColliderData)); - } - - protected override void OnStartRunning() - { - _gravity = Object.FindObjectOfType().Gravity; - } - - protected override void OnUpdate() - { - var hitTime = _simulateCycleSystemGroup.HitTime; - var gravity = _gravity; - - // retrieve reference to static collider data - var collEntity = _collDataEntityQuery.GetSingletonEntity(); - var collData = EntityManager.GetComponentData(collEntity); - var contacts = _simulateCycleSystemGroup.Contacts; - var ballsLookup = GetComponentDataFromEntity(); - - var marker = PerfMarker; - - Job - .WithName("ContactJob") - .WithCode(() => - { - - marker.Begin(); - - ref var colliders = ref collData.Value.Value.Colliders; - - //if (rnd.NextBool()) { // swap order of contact handling randomly - for (var i = 0; i < contacts.Length; i++) { - - var contact = contacts[i]; - ref var collEvent = ref contact.CollEvent; - var ball = ballsLookup[contact.BallEntity]; - - if (collEvent.ColliderId > -1) { // collide with static collider - ref var coll = ref colliders[collEvent.ColliderId].Value; - unsafe { - fixed (Collider* collider = &coll) { - - // flipper contact updates movement data - if (coll.Type == ColliderType.Flipper) { - - var flipperMovementData = GetComponent(coll.Entity); - var flipperMaterialData = GetComponent(coll.Entity); - var flipperVelocityData = GetComponent(coll.Entity); - ((FlipperCollider*) collider)->Contact( - ref ball, ref flipperMovementData, in collEvent, - in flipperMaterialData, in flipperVelocityData, hitTime, in gravity); - SetComponent(coll.Entity, flipperMovementData); - - } else { - Collider.Contact(ref coll, ref ball, in collEvent, hitTime, in gravity); - } - } - } - - } else if (collEvent.ColliderEntity != Entity.Null) { // collide with ball - // todo move ball friction into some data component - BallCollider.HandleStaticContact(ref ball, in collEvent, 0.3f, hitTime, in gravity); - } - - ballsLookup[contact.BallEntity] = ball; - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs deleted file mode 100644 index 394ecdc65..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs +++ /dev/null @@ -1,74 +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.Collections; -using Unity.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class DynamicBroadPhaseSystem : SystemBase - { - private EntityQuery _ballQuery; - private static readonly ProfilerMarker PerfMarker1 = new ProfilerMarker("DynamicBroadPhaseSystem.CreateKdTree"); - private static readonly ProfilerMarker PerfMarker2 = new ProfilerMarker("DynamicBroadPhaseSystem.GetAabbOverlaps"); - - protected override void OnCreate() { - _ballQuery = GetEntityQuery(ComponentType.ReadOnly()); - } - - protected override void OnUpdate() - { - // create kdtree - PerfMarker1.Begin(); - - var ballBounds = new NativeArray(_ballQuery.CalculateEntityCount(), Allocator.TempJob); - Entities.ForEach((Entity ballEntity, int entityInQueryIndex, in BallData ballData) => { - ballBounds[entityInQueryIndex] = ballData.Bounds(ballEntity); - }).Run(); - - var kdRoot = new KdRoot(); - Job.WithCode(() => kdRoot.Init(ballBounds, Allocator.TempJob)).Run(); - - PerfMarker1.End(); - - var overlappingEntities = GetBufferFromEntity(); - var marker = PerfMarker2; - - Entities - .WithName("DynamicBroadPhaseJob") - .WithDisposeOnCompletion(kdRoot) - .WithNativeDisableParallelForRestriction(overlappingEntities) - .ForEach((Entity entity, in BallData ball) => { - - // don't play with frozen balls - if (ball.IsFrozen) { - return; - } - - marker.Begin(); - - var colliderEntities = overlappingEntities[entity]; - colliderEntities.Clear(); - kdRoot.GetAabbOverlaps(in entity, in ball, ref colliderEntities); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs.meta deleted file mode 100644 index d35922185..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicBroadPhaseSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 7de2d86b279848e458b629bbb2918e6d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs deleted file mode 100644 index 167f4a897..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs +++ /dev/null @@ -1,74 +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.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class DynamicCollisionSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("DynamicCollisionSystem"); - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - - protected override void OnCreate() - { - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - var hitTime = _simulateCycleSystemGroup.HitTime; - var swapBallCollisionHandling = _simulateCycleSystemGroup.SwapBallCollisionHandling; - var balls = GetComponentDataFromEntity(); - var collEvents = GetComponentDataFromEntity(true); - - Entities - .WithName("DynamicCollisionJob") - .WithNativeDisableParallelForRestriction(balls) - .WithReadOnly(collEvents) - .ForEach((ref BallData ball, ref CollisionEventData collEvent) => { - - // pick "other" ball - ref var otherEntity = ref collEvent.ColliderEntity; - - // find balls with hit objects and minimum time - if (otherEntity != Entity.Null && collEvent.HitTime <= hitTime) { - - marker.Begin(); - - var otherBall = balls[otherEntity]; - var otherCollEvent = collEvents[otherEntity]; - - // now collision, contact and script reactions on active ball (object)+++++++++ - - //this.activeBall = ball; // For script that wants the ball doing the collision - - if (BallCollider.Collide(ref otherBall, ref ball,in otherCollEvent, in collEvent, swapBallCollisionHandling)) { - balls[otherEntity] = otherBall; - } - - // remove trial hit object pointer - collEvent.ClearCollider(); - - marker.End(); - } - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs.meta deleted file mode 100644 index 1aa341fe7..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicCollisionSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cf4c222b51824c44f989b0574936ed75 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs deleted file mode 100644 index aede3be70..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs +++ /dev/null @@ -1,84 +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.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class DynamicNarrowPhaseSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("DynamicNarrowPhaseSystem"); - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - - protected override void OnCreate() - { - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - } - - protected override void OnUpdate() - { - var ballsLookup = GetComponentDataFromEntity(); - var contacts = _simulateCycleSystemGroup.Contacts; - - var marker = PerfMarker; - - Entities - .WithName("DynamicNarrowPhaseJob") - .WithReadOnly(contacts) - .WithNativeDisableParallelForRestriction(ballsLookup) - .ForEach((Entity ballEntity, ref BallData ball, ref CollisionEventData collEvent, - in DynamicBuffer overlappingEntities) => - { - // don't play with frozen balls - if (ball.IsFrozen) { - return; - } - - marker.Begin(); - - //var contacts = contactsLookup[collDataEntity]; - for (var k = 0; k < overlappingEntities.Length; k++) { - var collBallEntity = overlappingEntities[k].Value; - var collBall = ballsLookup[collBallEntity]; - - var newCollEvent = new CollisionEventData(); - var newTime = BallCollider.HitTest(ref newCollEvent, ref ball, in collBall, collEvent.HitTime); - var validHit = newTime >= 0 && !Math.Sign(newTime) && newTime <= collEvent.HitTime; - - if (newCollEvent.IsContact || validHit) { - newCollEvent.SetCollider(collBallEntity); - newCollEvent.HitTime = newTime; - if (newCollEvent.IsContact) { - contacts.Add(new ContactBufferElement(ballEntity, newCollEvent)); - - } else { // if (validhit) - collEvent = newCollEvent; - } - } - - // write back - ballsLookup[collBallEntity] = collBall; - } - - marker.End(); - - } - ).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs.meta deleted file mode 100644 index 17d2a5bb8..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/DynamicNarrowPhaseSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 78cc21e0c4cdaac48870177ec4b4fd99 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs deleted file mode 100644 index 8d97efcb4..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs +++ /dev/null @@ -1,335 +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.Entities; -using Unity.Mathematics; - -namespace VisualPinball.Unity -{ - internal struct KdNode - { - public Aabb Bounds; // m_rectbounds - public int Start; // m_start - - /// - /// Contains the 2 bits for axis (bits 30/31) - /// - public int Items; // m_items - - /// - /// If NULL, is a leaf; otherwise keeps the 2 children - /// - private int _childA; // m_children - private int _childB; - - private bool HasChildren => _childA > -1 && _childB > -1; - - public KdNode(Aabb bounds) - { - Bounds = bounds; - Start = 0; - Items = 2; - _childA = -1; - _childB = -1; - } - - public void Reset() - { - _childA = -1; - _childB = -1; - Start = 0; - Items = 0; - } - - - public void CreateNextLevel(int level, int levelEmpty, KdRoot hitOct) { - var orgItems = Items & 0x3FFFFFFF; - - // !! magic - if (orgItems <= 4 || level >= 128 / 2) { - return; - } - - var vDiag = new float3( - Bounds.Right - Bounds.Left, - Bounds.Bottom - Bounds.Top, - Bounds.ZHigh - Bounds.ZLow - ); - - int axis; - if (vDiag.x > vDiag.y && vDiag.x > vDiag.z) { - if (vDiag.x < 0.0001) { - return; - } - axis = 0; - - } else if (vDiag.y > vDiag.z) { - if (vDiag.y < 0.0001) { - return; - } - axis = 1; - - } else { - if (vDiag.z < 0.0001) { - return; - } - axis = 2; - } - - //!! weight this with ratio of elements going to middle vs left&right! (avoids volume split that goes directly through object) - - // create children - if (!hitOct.HasNodesAvailable()) { - // ran out of nodes - abort - return; - } - - var childA = new KdNode(Bounds); - var childB = new KdNode(Bounds); - - var vCenter = new float3( - (Bounds.Left + Bounds.Right) * 0.5f, - (Bounds.Top + Bounds.Bottom) * 0.5f, - (Bounds.ZLow + Bounds.ZHigh) * 0.5f - ); - switch (axis) { - case 0: - childA.Bounds.Right = vCenter.x; - childB.Bounds.Left = vCenter.x; - break; - case 1: - childA.Bounds.Bottom = vCenter.y; - childB.Bounds.Top = vCenter.y; - break; - default: - childA.Bounds.ZHigh = vCenter.z; - childB.Bounds.ZLow = vCenter.z; - break; - } - - childA.Reset(); - childB.Reset(); - - // determine amount of items that cross splitplane, or are passed on to the children - if (axis == 0) { - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.Right < vCenter.x) { - childA.Items++; - - } else if (bounds.Aabb.Left > vCenter.x) { - childB.Items++; - } - } - - } else if (axis == 1) { - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.Bottom < vCenter.y) { - childA.Items++; - - } else if (bounds.Aabb.Top > vCenter.y) { - childB.Items++; - } - } - - } else { - // axis == 2 - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.ZHigh < vCenter.z) { - childA.Items++; - - } else if (bounds.Aabb.ZLow > vCenter.z) { - childB.Items++; - } - } - } - - // check if at least two nodes feature objects, otherwise don"t bother subdividing further - var countEmpty = 0; - if (childA.Items == 0) { - countEmpty = 1; - } - - if (childB.Items == 0) { - ++countEmpty; - } - - if (orgItems - childA.Items - childB.Items == 0) { - ++countEmpty; - } - - if (countEmpty >= 2) { - ++levelEmpty; - - } else { - levelEmpty = 0; - } - - if (levelEmpty > 8) { - // If 8 levels were all just subdividing the same objects without luck, exit & Free the nodes again (but at least empty space was cut off) - // no need to update NumNodes, since we didn't increment them yet. - _childA = -1; - _childB = -1; - return; - } - - childA.Start = Start + orgItems - childA.Items - childB.Items; - childB.Start = childA.Start + childA.Items; - - var items = 0; - childA.Items = 0; - childB.Items = 0; - - switch (axis) { - - // sort items that cross splitplane in-place, the others are sorted into a temporary - case 0: { - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.Right < vCenter.x) { - hitOct.Indices[childA.Start + childA.Items++] = hitOct.OrgIdx[i]; - - } else if (bounds.Aabb.Left > vCenter.x) { - hitOct.Indices[childB.Start + childB.Items++] = hitOct.OrgIdx[i]; - - } else { - hitOct.OrgIdx[Start + items++] = hitOct.OrgIdx[i]; - } - } - break; - } - - case 1: { - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.Bottom < vCenter.y) { - hitOct.Indices[childA.Start + childA.Items++] = hitOct.OrgIdx[i]; - - } else if (bounds.Aabb.Top > vCenter.y) { - hitOct.Indices[childB.Start + childB.Items++] = hitOct.OrgIdx[i]; - - } else { - hitOct.OrgIdx[Start + items++] = hitOct.OrgIdx[i]; - } - } - break; - } - - default: { // axis == 2 - - for (var i = Start; i < Start + orgItems; ++i) { - var bounds = hitOct.GetItemAt(i); - - if (bounds.Aabb.ZHigh < vCenter.z) { - hitOct.Indices[childA.Start + childA.Items++] = hitOct.OrgIdx[i]; - - } else if (bounds.Aabb.ZLow > vCenter.z) { - hitOct.Indices[childB.Start + childB.Items++] = hitOct.OrgIdx[i]; - - } else { - hitOct.OrgIdx[Start + items++] = hitOct.OrgIdx[i]; - } - } - break; - } - } - - // The following assertions hold after this step: - //assert( this.Start + items == this.Children[0].This.Start ); - //assert( this.Children[0].This.Start + this.Children[0].This.Items == this.Children[1].This.Start ); - //assert( this.Children[1].This.Start + this.Children[1].This.Items == this.Start + org_items ); - //assert( this.Start + org_items <= this.HitOct->tmp.Size() ); - - Items = items | (axis << 30); - - // copy temporary back //!! could omit this by doing everything inplace - for (var i = 0; i < childA.Items; i++) { - hitOct.OrgIdx[childA.Start + i] = hitOct.Indices[childA.Start + i]; - } - for (var i = 0; i < childB.Items; i++) { - hitOct.OrgIdx[childB.Start + i] = hitOct.Indices[childB.Start + i]; - } - - hitOct.AddNodes(childA, childB, out _childA, out _childB); - - childA.CreateNextLevel(level + 1, levelEmpty, hitOct); - childB.CreateNextLevel(level + 1, levelEmpty, hitOct); - } - - public void GetAabbOverlaps(ref KdRoot hitOct, in Entity entity, in BallData ball, ref DynamicBuffer overlappingEntities) { - - var orgItems = Items & 0x3FFFFFFF; - var axis = Items >> 30; - var bounds = ball.Aabb; - var collisionRadiusSqr = ball.CollisionRadiusSqr; - - for (var i = Start; i < Start + orgItems; i++) { - var b = hitOct.GetItemAt(i); - if (entity != b.BallEntity && b.Aabb.IntersectSphere(ball.Position, collisionRadiusSqr)) { - overlappingEntities.Add(b.BallEntity); - } - } - - if (HasChildren) { - switch (axis) { - // not a leaf - case 0: { - var vCenter = (Bounds.Left + Bounds.Right) * 0.5f; - if (bounds.Left <= vCenter) { - hitOct.GetNodeAt(_childA).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - - if (bounds.Right >= vCenter) { - hitOct.GetNodeAt(_childB).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - break; - } - - case 1: { - var vCenter = (Bounds.Top + Bounds.Bottom) * 0.5f; - if (bounds.Top <= vCenter) { - hitOct.GetNodeAt(_childA).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - - if (bounds.Bottom >= vCenter) { - hitOct.GetNodeAt(_childB).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - break; - } - - default: { - var vCenter = (Bounds.ZLow + Bounds.ZHigh) * 0.5f; - if (bounds.ZLow <= vCenter) { - hitOct.GetNodeAt(_childA).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - - if (bounds.ZHigh >= vCenter) { - hitOct.GetNodeAt(_childB).GetAabbOverlaps(ref hitOct, in entity, in ball, ref overlappingEntities); - } - break; - } - } - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs.meta deleted file mode 100644 index d04b321ac..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdNode.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8751c1d1dcb375a4792baee9353d0f7d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs deleted file mode 100644 index 059b20b79..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs +++ /dev/null @@ -1,103 +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; -using Unity.Collections; -using Unity.Entities; - -namespace VisualPinball.Unity -{ - internal struct KdRoot : IDisposable - { - public int NumNodes; // m_num_nodes - public NativeArray OrgIdx; // m_org_idx - public NativeArray Indices; // tmp - - private KdNode _rootNode; // m_rootNode - private NativeArray _bounds; // m_org_vho - private NativeArray _nodes; // m_nodes - - private int _numItems; // m_num_items - - public void Init(NativeArray bounds, Allocator allocator) - { - _bounds = bounds; - _numItems = bounds.Length; - - OrgIdx = new NativeArray(_numItems, allocator); - Indices = new NativeArray(_numItems, allocator); - _nodes = new NativeArray(_numItems * 2 + 1, allocator); - - NumNodes = 0; - _rootNode = new KdNode(); - _rootNode.Reset(); - - FillFromVector(bounds); - } - - private void FillFromVector(NativeArray bounds) - { - _rootNode.Bounds.Clear(); - _rootNode.Start = 0; - _rootNode.Items = _numItems; - - for (var i = 0; i < _numItems; ++i) { - _rootNode.Bounds.Extend(bounds[i].Aabb); - OrgIdx[i] = i; - } - - _rootNode.CreateNextLevel(0, 0, this); - } - - public void GetAabbOverlaps(in Entity entity, in BallData ball, ref DynamicBuffer overlappingEntities) - { - _rootNode.GetAabbOverlaps(ref this, in entity, in ball, ref overlappingEntities); - } - - public BallColliderBounds GetItemAt(int i) - { - return _bounds[OrgIdx[i]]; - } - - public KdNode GetNodeAt(int i) - { - return _nodes[i]; - } - - public bool HasNodesAvailable() - { - // space for two more nodes? - return NumNodes + 1 < _nodes.Length; - } - - public void AddNodes(KdNode nodeA, KdNode nodeB, out int nodeIndexA, out int nodeIndexB) - { - NumNodes += 2; - nodeIndexA = NumNodes - 2; - nodeIndexB = NumNodes - 1; - _nodes[nodeIndexA] = nodeA; - _nodes[nodeIndexB] = nodeB; - } - - public void Dispose() - { - _bounds.Dispose(); - _nodes.Dispose(); - OrgIdx.Dispose(); - Indices.Dispose(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs.meta deleted file mode 100644 index fa823cc6e..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/KdRoot.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 389ba5fd12b0d6142a5bfeaa323b8050 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotData.cs rename to VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotState.cs index 280313729..bdc3a7084 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct LineSlingshotData : IComponentData + internal struct LineSlingshotState { public bool IsDisabled; public float Threshold; diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/LineSlingshotState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs.meta deleted file mode 100644 index f5a7c9d48..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: eab61292d11b9324f95de2b811f19b34 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs.meta deleted file mode 100644 index cf31261b1..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b34a0c3ee39824845996a8f36ae9e9ee -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/PhysicsMaterialData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/PhysicsMaterialData.cs index 11b50b176..77c3e561d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/PhysicsMaterialData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/PhysicsMaterialData.cs @@ -15,20 +15,32 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Entities; namespace VisualPinball.Unity { - public struct PhysicsMaterialData : IComponentData + public struct PhysicsMaterialData { public float Elasticity; public float ElasticityFalloff; public float Friction; public float ScatterAngleRad; - public FixedListFloat512 ElasticityOverVelocityLUT; + public FixedList512Bytes ElasticityOverVelocityLUT; public bool UseElasticityOverVelocity; - public FixedListFloat512 FrictionOverVelocityLUT; + public FixedList512Bytes FrictionOverVelocityLUT; public bool UseFrictionOverVelocity; + public static bool operator == (PhysicsMaterialData a, PhysicsMaterialData b) => a.Equals(b); + public static bool operator != (PhysicsMaterialData a, PhysicsMaterialData b) => !a.Equals(b); + + public bool Equals(PhysicsMaterialData other) + { + return + Elasticity == other.Elasticity && + ElasticityFalloff == other.ElasticityFalloff && + Friction == other.Friction && + ScatterAngleRad == other.ScatterAngleRad && + UseElasticityOverVelocity == other.UseElasticityOverVelocity && + UseFrictionOverVelocity == other.UseFrictionOverVelocity; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs deleted file mode 100644 index 8e2c40fec..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs +++ /dev/null @@ -1,255 +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; -using System.Collections.Generic; -using System.Linq; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; - -namespace VisualPinball.Unity -{ - internal struct QuadTree - { - private BlobArray> _children; - private BlobArray> _bounds; - private float3 _center; - private bool _isLeaf; - - public static void Create(BlobBuilder builder, ref BlobArray> colliders, ref QuadTree dest, Aabb rootBounds) - { - var children = builder.Allocate(ref dest._children, 4); - var aabbs = new List(); - var cs = new List(); - for (var i = 0; i < colliders.Length; i++) { - if (colliders[i].Value.Type != ColliderType.Plane) { - var c = colliders[i].Value; - var bounds = colliders[i].Value.Bounds(); - //Debug.Log("Adding aabb " + aabb + " (" + colliders[i].Value.Type + ")"); - if (bounds.ColliderEntity == Entity.Null) { - throw new InvalidOperationException($"Entity of {bounds} must be set ({colliders[i].Value.ItemType})."); - } - if (bounds.ColliderId < 0) { - throw new InvalidOperationException($"ColliderId of {bounds} must be set ({colliders[i].Value.ItemType})."); - } - - aabbs.Add(bounds); - cs.Add(c); - } - } - - dest.CreateNextLevel(builder, rootBounds, 0, 0, aabbs, ref children); - } - - private void CreateNextLevel(BlobBuilder builder, Aabb bounds, int level, int levelEmpty, - IReadOnlyCollection remainingBounds, ref BlobBuilderArray> children) - { - _center.x = (bounds.Left + bounds.Right) * 0.5f; - _center.y = (bounds.Top + bounds.Bottom) * 0.5f; - _center.z = (bounds.ZLow + bounds.ZHigh) * 0.5f; - - if (remainingBounds.Count <= 4) { - CopyBounds(builder, remainingBounds.ToArray(), ref _bounds); - _isLeaf = true; - return; - } - - _isLeaf = false; - - ref var child0 = ref builder.Allocate(ref children[0]); - ref var child1 = ref builder.Allocate(ref children[1]); - ref var child2 = ref builder.Allocate(ref children[2]); - ref var child3 = ref builder.Allocate(ref children[3]); - - var childBounds0 = new List(); - var childBounds1 = new List(); - var childBounds2 = new List(); - var childBounds3 = new List(); - - var vRemain = new List(); // hit objects which did not go to a quadrant - - //_unique = HitObjects[0].E ? HitObjects[0].Item as Primitive : null; - - // sort items into appropriate child nodes - foreach (var b in remainingBounds) { - int oct; - - // if ((hitObject.E ? hitObject.Item : null) != _unique) { - // // are all objects in current node unique/belong to the same primitive? - // _unique = null; - // } - - if (b.Aabb.Right < _center.x) { - oct = 0; - - } else if (b.Aabb.Left > _center.x) { - oct = 1; - - } else { - oct = 128; - } - - if (b.Aabb.Bottom < _center.y) { - oct |= 0; - - } else if (b.Aabb.Top > _center.y) { - oct |= 2; - - } else { - oct |= 128; - } - - if ((oct & 128) == 0) { - switch (oct) { - case 0: childBounds0.Add(b); break; - case 1: childBounds1.Add(b); break; - case 2: childBounds2.Add(b); break; - case 3: childBounds3.Add(b); break; - } - - } else { - vRemain.Add(b); - } - } - - // copy remaining AABBs to blob - CopyBounds(builder, vRemain, ref _bounds); - - // check if at least two nodes feature objects, otherwise don't bother subdividing further - var countEmpty = vRemain.Count == 0 ? 1 : 0; - if (childBounds0.Count == 0) ++countEmpty; - if (childBounds1.Count == 0) ++countEmpty; - if (childBounds2.Count == 0) ++countEmpty; - if (childBounds3.Count == 0) ++countEmpty; - - if (countEmpty >= 4) { - ++levelEmpty; - - } else { - levelEmpty = 0; - } - - if (_center.x - bounds.Left > 0.0001 //!! magic - && levelEmpty <= 8 // If 8 levels were all just subdividing the same objects without luck, exit & Free the nodes again (but at least empty space was cut off) - && level + 1 < 128 / 3) - { - CreateNextLevel(builder, ref child0, ref childBounds0, GetBounds(0, in _center, in bounds), level, levelEmpty); - CreateNextLevel(builder, ref child1, ref childBounds1, GetBounds(1, in _center, in bounds), level, levelEmpty); - CreateNextLevel(builder, ref child2, ref childBounds2, GetBounds(2, in _center, in bounds), level, levelEmpty); - CreateNextLevel(builder, ref child3, ref childBounds3, GetBounds(3, in _center, in bounds), level, levelEmpty); - - } else { - child0._isLeaf = true; - child1._isLeaf = true; - child2._isLeaf = true; - child3._isLeaf = true; - } - } - - private static void CreateNextLevel(BlobBuilder builder, ref QuadTree child, ref List childBounds, - in Aabb bounds, int level, int levelEmpty) - { - var children = builder.Allocate(ref child._children, 4); - child.CreateNextLevel(builder, bounds, level + 1, levelEmpty, childBounds, ref children); - - } - - private static void CopyBounds(BlobBuilder builder, IReadOnlyList src, ref BlobArray> dest) - { - var boundsBlob = builder.Allocate(ref dest, src.Count); - for (var i = 0; i < src.Count; i++) { - ref var bounds = ref builder.Allocate(ref boundsBlob[i]); - bounds.Aabb = src[i].Aabb; - bounds.ColliderEntity = src[i].ColliderEntity; - bounds.ColliderId = src[i].ColliderId; - } - } - - private static Aabb GetBounds(int i, in float3 center, in Aabb bounds) - { - return new Aabb { - Left = (i & 1) != 0 ? center.x : bounds.Left, - Top = (i & 2) != 0 ? center.y : bounds.Top, - ZLow = bounds.ZLow, - Right = (i & 1) != 0 ? bounds.Right : center.x, - Bottom = (i & 2) != 0 ? bounds.Bottom : center.y, - ZHigh = bounds.ZHigh - }; - } - - public void GetAabbOverlaps(in BallData ball, in NativeHashMap itemsColliding, ref DynamicBuffer matchedColliderIds) - { - var ballAabb = ball.Aabb; - var collisionRadiusSqr = ball.CollisionRadiusSqr; - - for (var i = 0; i < _bounds.Length; i++) { - ref var bounds = ref _bounds[i].Value; - if (itemsColliding[bounds.ColliderEntity] && bounds.Aabb.IntersectRect(ballAabb) && bounds.Aabb.IntersectSphere(ball.Position, collisionRadiusSqr)) { - matchedColliderIds.Add(new OverlappingStaticColliderBufferElement { Value = bounds.ColliderId }); - } - } - - if (!_isLeaf) { - var isLeft = ballAabb.Left <= _center.x; - var isRight = ballAabb.Right >= _center.x; - - if (ballAabb.Top <= _center.y) { - // Top - if (isLeft) { - _children[0].Value.GetAabbOverlaps(in ball, in itemsColliding, ref matchedColliderIds); - } - - if (isRight) { - _children[1].Value.GetAabbOverlaps(in ball, in itemsColliding, ref matchedColliderIds); - } - } - - if (ballAabb.Bottom >= _center.y) { - // Bottom - if (isLeft) { - _children[2].Value.GetAabbOverlaps(in ball, in itemsColliding, ref matchedColliderIds); - } - - if (isRight) { - _children[3].Value.GetAabbOverlaps(in ball, in itemsColliding, ref matchedColliderIds); - } - } - } - } - - public override string ToString() - { - var bounds = new ColliderBounds[_bounds.Length]; - for (var i = 0; i < _bounds.Length; i++) { - bounds[i] = _bounds[i].Value; - } - return $"[({bounds.Length}){string.Join(" - ", bounds)}]"; - } - - public string ToString(int level) - { - var ident = new string(' ', level * 3); - var str = ident + ToString() + "\n"; - if (!_isLeaf) { - for (var i = 0; i < _children.Length; i++) { - str += _children[i].Value.ToString(level + 1); - } - } - return str; - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs.meta deleted file mode 100644 index 473ad6d34..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTree.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2579132f809b4cb4c9dd7cc096e0b430 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.cs deleted file mode 100644 index 8f6523132..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.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 -{ - internal struct QuadTreeBlob - { - public QuadTree QuadTree; - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.cs.meta deleted file mode 100644 index 68201732c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeBlob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a51d8de4406f6ab42bfb26abb969b085 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs deleted file mode 100644 index 649ddfe6f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs +++ /dev/null @@ -1,97 +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 System.Linq; -using NLog; -using Unity.Collections; -using Unity.Entities; -using Unity.Jobs; -using Unity.Profiling; -using UnityEngine; -using Logger = NLog.Logger; - -namespace VisualPinball.Unity -{ - internal static class QuadTreeCreator - { - private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); - private static readonly ProfilerMarker PerfMarkerTotal = new ProfilerMarker("QuadTreeCreator"); - private static readonly ProfilerMarker PerfMarkerGenerateColliders = new ProfilerMarker("QuadTreeCreator (1 - generate colliders)"); - private static readonly ProfilerMarker PerfMarkerCreateColliders = new ProfilerMarker("IColliderGenerator.CreateColliders"); - private static readonly ProfilerMarker PerfMarkerCreateBlobAsset = new ProfilerMarker("QuadTreeCreator (2 - allocate blob asset)"); - private static readonly ProfilerMarker PerfMarkerCreateQuadTree = new ProfilerMarker("QuadTreeCreator (3 - create quad tree)"); - private static readonly ProfilerMarker PerfMarkerSaveToEntity = new ProfilerMarker("QuadTreeCreator (4 - save to entity)"); - - public static void Create(EntityManager entityManager, out NativeHashMap itemsColliding) - { - PerfMarkerTotal.Begin(); - - var player = Object.FindObjectOfType(); - var playfieldComponent = player.GetComponentInChildren(); - var itemApis = player.ColliderGenerators.ToArray(); - - // 1. generate colliders - PerfMarkerGenerateColliders.Begin(); - var colliderList = new List(); - var (playfieldCollider, glassCollider) = player.PlayfieldApi.CreateColliders(); - itemsColliding = new NativeHashMap(itemApis.Length, Allocator.Persistent); - foreach (var itemApi in itemApis) { - PerfMarkerCreateColliders.Begin(); - if (itemApi.ColliderEntity != Entity.Null) { - itemsColliding.Add(itemApi.ColliderEntity, itemApi.IsColliderEnabled); - } - itemApi.CreateColliders(colliderList, 0); - PerfMarkerCreateColliders.End(); - } - PerfMarkerGenerateColliders.End(); - - // 2. allocate created colliders - PerfMarkerCreateBlobAsset.Begin(); - var allocateColliderJob = new ColliderAllocationJob(colliderList, playfieldCollider, glassCollider); - allocateColliderJob.Run(); - - // retrieve result and dispose - var colliderBlobAssetRef = allocateColliderJob.BlobAsset[0]; - allocateColliderJob.Dispose(); - PerfMarkerCreateBlobAsset.End(); - - // 3. Create quadtree blob (BlobAssetReference) from AABBs - PerfMarkerCreateQuadTree.Begin(); - BlobAssetReference quadTreeBlobAssetRef; - using (var builder = new BlobBuilder(Allocator.Temp)) { - ref var rootQuadTree = ref builder.ConstructRoot(); - QuadTree.Create(builder, ref colliderBlobAssetRef.Value.Colliders, ref rootQuadTree.QuadTree, - playfieldComponent.BoundingBox.ToAabb()); - - quadTreeBlobAssetRef = builder.CreateBlobAssetReference(Allocator.Persistent); - } - PerfMarkerCreateQuadTree.End(); - - // save it to entity - PerfMarkerSaveToEntity.Begin(); - //Debug.Log(quadTreeBlobAssetRef.Value.QuadTree.ToString(0)); - var collEntity = entityManager.CreateEntity(ComponentType.ReadOnly(), ComponentType.ReadOnly()); - entityManager.SetComponentData(collEntity, new QuadTreeData { Value = quadTreeBlobAssetRef }); - entityManager.SetComponentData(collEntity, new ColliderData { Value = colliderBlobAssetRef }); - PerfMarkerSaveToEntity.End(); - - Logger.Info("Static QuadTree initialized."); - - PerfMarkerTotal.End(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs.meta deleted file mode 100644 index 98de850db..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeCreator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0c7f237e205800f41842b62b2105c45c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs.meta deleted file mode 100644 index 94bc9db5e..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d0fc1537caa35b945967fa9073314ff6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs deleted file mode 100644 index fa47a9c36..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs +++ /dev/null @@ -1,65 +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.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class StaticBroadPhaseSystem : SystemBase - { - private EntityQuery _quadTreeEntityQuery; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("StaticBroadPhaseSystem"); - - protected override void OnCreate() - { - _quadTreeEntityQuery = EntityManager.CreateEntityQuery(typeof(QuadTreeData)); - _simulateCycleSystemGroup = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); - } - - protected override void OnUpdate() - { - // retrieve reference to static quad tree data - var collEntity = _quadTreeEntityQuery.GetSingletonEntity(); - var collData = EntityManager.GetComponentData(collEntity); - var itemsColliding = _simulateCycleSystemGroup.ItemsColliding; - var marker = PerfMarker; - - Entities - .WithName("StaticBroadPhaseJob") - .WithReadOnly(itemsColliding) - .ForEach((ref DynamicBuffer colliderIds, in BallData ballData) => { - - // don't play with frozen balls - if (ballData.IsFrozen) { - return; - } - - marker.Begin(); - - ref var quadTree = ref collData.Value.Value.QuadTree; - colliderIds.Clear(); - quadTree.GetAabbOverlaps(in ballData, in itemsColliding, ref colliderIds); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs.meta deleted file mode 100644 index ba72cdb22..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticBroadPhaseSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 08e7bd65c999ea745a459ca73b26e86b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs deleted file mode 100644 index a546130c9..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs +++ /dev/null @@ -1,308 +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 ConvertIfStatementToSwitchStatement - -using System; -using Unity.Collections; -using Unity.Entities; -using Unity.Profiling; -using VisualPinball.Engine.VPT; -using Object = UnityEngine.Object; -using Random = UnityEngine.Random; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class StaticCollisionSystem : SystemBase - { - private Player _player; - private VisualPinballSimulationSystemGroup _visualPinballSimulationSystemGroup; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private EntityQuery _collDataEntityQuery; - private NativeQueue _eventQueue; - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("StaticCollisionSystem"); - - protected override void OnCreate() - { - _visualPinballSimulationSystemGroup = World.GetOrCreateSystem(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _collDataEntityQuery = EntityManager.CreateEntityQuery(typeof(ColliderData)); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnStartRunning() - { - _player = Object.FindObjectOfType(); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - // retrieve reference to static collider data - var collEntity = _collDataEntityQuery.GetSingletonEntity(); - var collData = EntityManager.GetComponentData(collEntity); - var random = new global::Unity.Mathematics.Random((uint)Random.Range(1, 100000)); - - var events = _eventQueue.AsParallelWriter(); - - var hitTime = _simulateCycleSystemGroup.HitTime; - var timeMsec = _visualPinballSimulationSystemGroup.TimeMsec; - var marker = PerfMarker; - - Entities - .WithName("StaticCollisionJob") - .ForEach((Entity ballEntity, ref BallData ballData, ref CollisionEventData collEvent, - ref DynamicBuffer insideOfs) => { - - // find balls with hit objects and minimum time - if (collEvent.ColliderId < 0 || collEvent.HitTime > hitTime) { - return; - } - - marker.Begin(); - - // retrieve static data - ref var colliders = ref collData.Value.Value.Colliders; - - // pick collider that matched during narrowphase - ref var coll = ref colliders[collEvent.ColliderId].Value; // object that ball hit in trials - - // now collision, contact and script reactions on active ball (object)+++++++++ - - //this.activeBall = ball; // For script that wants the ball doing the collision - - unsafe { - fixed (Collider* collider = &coll) { - - switch (coll.Type) { - case ColliderType.Bumper: { - var bumperStaticData = GetComponent(coll.Entity); - var animateRing = HasComponent(coll.Entity); - var animateSkirt = HasComponent(coll.Entity); - var ringData = animateRing ? GetComponent(coll.Entity) : default; - var skirtData = animateSkirt ? GetComponent(coll.Entity): default; - BumperCollider.Collide(ref ballData, ref events, ref collEvent, ref ringData, ref skirtData, - in ballEntity, in coll, bumperStaticData, ref random); - if (animateRing) { - SetComponent(coll.Entity, ringData); - } - if (animateSkirt) { - SetComponent(coll.Entity, skirtData); - } - break; - } - - case ColliderType.Flipper: { - var flipperVelocityData = GetComponent(coll.Entity); - var flipperMovementData = GetComponent(coll.Entity); - var flipperMaterialData = GetComponent(coll.Entity); - var flipperHitData = GetComponent(coll.Entity); - var flipperTricksData = GetComponent(coll.Entity); - // do liveCatch - check before collision - FlipperCollider.LiveCatch( - ref ballData, ref collEvent, ref flipperTricksData, in flipperMaterialData, timeMsec - ); - ((FlipperCollider*)collider)->Collide( - ref ballData, ref collEvent, ref flipperMovementData, ref events, - in ballEntity, in flipperTricksData,in flipperMaterialData, in flipperVelocityData, in flipperHitData, timeMsec - ); - SetComponent(coll.Entity, flipperMovementData); - break; - } - - case ColliderType.Gate: { - var gateStaticData = GetComponent(coll.Entity); - var gateMovementData = GetComponent(coll.Entity); - GateCollider.Collide( - ref ballData, ref collEvent, ref gateMovementData, ref events, - in ballEntity, in coll, in gateStaticData - ); - SetComponent(coll.Entity, gateMovementData); - break; - } - - case ColliderType.LineSlingShot: { - var slingshotData = GetComponent(coll.Entity); - ((LineSlingshotCollider*)collider)->Collide( - ref ballData, ref events, - in ballEntity, in slingshotData, in collEvent, ref random); - break; - } - - case ColliderType.Plunger: { - var plungerMovementData = GetComponent(coll.Entity); - var plungerStaticData = GetComponent(coll.Entity); - PlungerCollider.Collide( - ref ballData, ref collEvent, ref plungerMovementData, - in plungerStaticData, ref random); - SetComponent(coll.Entity, plungerMovementData); - break; - } - - case ColliderType.Spinner: { - var spinnerStaticData = GetComponent(coll.Entity); - var spinnerMovementData = GetComponent(coll.Entity); - SpinnerCollider.Collide( - in ballData, ref collEvent, ref spinnerMovementData, - in spinnerStaticData - ); - SetComponent(coll.Entity, spinnerMovementData); - break; - } - - case ColliderType.TriggerCircle: - case ColliderType.TriggerLine: { - - var triggerAnimationData = HasComponent(coll.Entity) - ? GetComponent(coll.Entity) - : new TriggerAnimationData(); - - TriggerCollider.Collide( - ref ballData, ref events, ref collEvent, ref insideOfs, ref triggerAnimationData, - in ballEntity, in coll - ); - - if (HasComponent(coll.Entity)) { - if (triggerAnimationData.UnHitEvent) { - var flipperCorrectionData = GetComponent(coll.Entity); - ref var flipperCorrectionBlob = ref flipperCorrectionData.Value.Value; - var flipperMovementData = GetComponent(flipperCorrectionBlob.FlipperEntity); - var flipperStaticData = GetComponent(flipperCorrectionBlob.FlipperEntity); - var flipperTricksData = GetComponent(flipperCorrectionBlob.FlipperEntity); - FlipperCorrection.OnBallLeaveFlipper( - ref ballData, ref flipperCorrectionBlob, in flipperMovementData, in flipperTricksData, in flipperStaticData, timeMsec - ); - } - - } else { - SetComponent(coll.Entity, triggerAnimationData); - } - break; - } - - case ColliderType.KickerCircle: { - var kickerCollisionData = GetComponent(coll.Entity); - var kickerStaticData = GetComponent(coll.Entity); - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - var legacyMode = KickerCollider.ForceLegacyMode || kickerStaticData.LegacyMode; - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - var kickerMeshData = !legacyMode ? GetComponent(coll.Entity) : default; - KickerCollider.Collide(ref ballData, ref events, ref insideOfs, ref kickerCollisionData, - in kickerStaticData, in kickerMeshData, in collEvent, coll.Entity, in ballEntity - ); - SetComponent(coll.Entity, kickerCollisionData); - break; - } - - case ColliderType.Line: - case ColliderType.Line3D: - case ColliderType.Circle: - case ColliderType.LineZ: - case ColliderType.Plane: - case ColliderType.Point: - case ColliderType.Triangle: - - // hit target - if (coll.Header.ItemType == ItemType.HitTarget) { - - var normal = coll.Type == ColliderType.Triangle - ? ((TriangleCollider*) collider)->Normal() - : collEvent.HitNormal; - - if (HasComponent(coll.Entity)) { - var dropTargetAnimationData = GetComponent(coll.Entity); - TargetCollider.DropTargetCollide(ref ballData, ref events, ref dropTargetAnimationData, - in normal, in ballEntity, in collEvent, in coll, ref random); - SetComponent(coll.Entity, dropTargetAnimationData); - } - - if (HasComponent(coll.Entity)) { - var hitTargetAnimationData = GetComponent(coll.Entity); - TargetCollider.HitTargetCollide(ref ballData, ref events, ref hitTargetAnimationData, - in normal, in ballEntity, in collEvent, in coll, ref random); - SetComponent(coll.Entity, hitTargetAnimationData); - } - - // trigger - } else if (coll.Header.ItemType == ItemType.Trigger) { - - var triggerAnimationData = HasComponent(coll.Entity) - ? GetComponent(coll.Entity) - : new TriggerAnimationData(); - - TriggerCollider.Collide( - ref ballData, ref events, ref collEvent, ref insideOfs, ref triggerAnimationData, - in ballEntity, in coll - ); - - if (HasComponent(coll.Entity)) { - if (triggerAnimationData.UnHitEvent) { - var flipperCorrectionData = GetComponent(coll.Entity); - ref var flipperCorrectionBlob = ref flipperCorrectionData.Value.Value; - var flipperMovementData = GetComponent(flipperCorrectionBlob.FlipperEntity); - var flipperStaticData = GetComponent(flipperCorrectionBlob.FlipperEntity); - var flipperTricksData = GetComponent(flipperCorrectionBlob.FlipperEntity); - FlipperCorrection.OnBallLeaveFlipper( - ref ballData, ref flipperCorrectionBlob, in flipperMovementData, in flipperTricksData, in flipperStaticData, timeMsec - ); - } - - } else { - SetComponent(coll.Entity, triggerAnimationData); - } - - } else { - Collider.Collide(ref coll, ref ballData, ref events, in ballEntity, in collEvent, ref random); - } - break; - - case ColliderType.None: - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - // remove trial hit object pointer - collEvent.ClearCollider(); - - // todo fix below (probably just delete) - // Collide may have changed the velocity of the ball, - // and therefore the bounding box for the next hit cycle - // if (this.balls[i] !== ball) { // Ball still exists? may have been deleted from list - // - // // collision script deleted the ball, back up one count - // --i; - // - // } else { - // ball.hit.calcHitBBox(); // do new boundings - // } - - marker.End(); - - }).Run(); - - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(eventData); - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs.meta deleted file mode 100644 index a78e2df8f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticCollisionSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 12d011021080560428d30ceae7d677cc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs deleted file mode 100644 index 8c4cb5068..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs +++ /dev/null @@ -1,211 +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.Collections; -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using VisualPinball.Engine.VPT; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class StaticNarrowPhaseSystem : SystemBase - { - public bool CollideAgainstPlayfieldPlane; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private EntityQuery _collDataEntityQuery; - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("StaticNarrowPhaseSystem"); - - protected override void OnCreate() - { - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _collDataEntityQuery = EntityManager.CreateEntityQuery(typeof(ColliderData)); - } - - protected override void OnUpdate() - { - // retrieve reference to static collider data - var collideAgainstPlayfieldPlane = CollideAgainstPlayfieldPlane; - var collEntity = _collDataEntityQuery.GetSingletonEntity(); - var collData = EntityManager.GetComponentData(collEntity); - var contacts = _simulateCycleSystemGroup.Contacts; - var hitTime = _simulateCycleSystemGroup.HitTime; - var random = new Random((uint)UnityEngine.Random.Range(1, 100000)); - var marker = PerfMarker; - - Entities - .WithName("StaticNarrowPhaseJob") - .ForEach((Entity ballEntity, ref CollisionEventData collEvent, - ref DynamicBuffer insideOfs, - in DynamicBuffer colliderIds, in BallData ballData) => - { - - // don't play with frozen balls - if (ballData.IsFrozen) { - return; - } - - marker.Begin(); - - // retrieve static data - ref var colliders = ref collData.Value.Value.Colliders; - - // init contacts and event - collEvent.ClearCollider(hitTime); // search upto current hit time - - // check playfield and glass first - if (collideAgainstPlayfieldPlane) { - ref var playfieldCollider = ref colliders[collData.Value.Value.PlayfieldColliderId].Value; - HitTest(ref playfieldCollider, ref collEvent, ref contacts, ref insideOfs, in ballEntity, in ballData); - } - ref var glassCollider = ref colliders[collData.Value.Value.GlassColliderId].Value; - HitTest(ref glassCollider, ref collEvent, ref contacts, ref insideOfs, in ballEntity, in ballData); - - var traversalOrder = false; //random.NextBool(); - var start = traversalOrder ? 0 : colliderIds.Length - 1; - var end = traversalOrder ? colliderIds.Length : -1; - var dt = traversalOrder ? 1 : -1; - - - for (var i = start; i != end; i += dt) { - ref var coll = ref colliders[colliderIds[i].Value].Value; - var saveCollision = true; - - var newCollEvent = new CollisionEventData(); - float newTime = 0; - unsafe { - fixed (Collider* collider = &coll) { - switch (coll.Type) { - - case ColliderType.LineSlingShot: - newTime = ((LineSlingshotCollider*) collider)->HitTest(ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - break; - - case ColliderType.Flipper: - if (HasComponent(coll.Entity) && - HasComponent(coll.Entity) && - HasComponent(coll.Entity)) - { - var flipperHitData = GetComponent(coll.Entity); - var flipperMovementData = GetComponent(coll.Entity); - var flipperMaterialData = GetComponent(coll.Entity); - var flipperTricksData = GetComponent(coll.Entity); - newTime = ((FlipperCollider*)collider)->HitTest( - ref newCollEvent, ref insideOfs, ref flipperHitData, - in flipperMovementData, in flipperTricksData, in flipperMaterialData, in ballData, collEvent.HitTime - ); - - SetComponent(coll.Entity, flipperHitData); - } - break; - - case ColliderType.Plunger: - if (HasComponent(coll.Entity) && - HasComponent(coll.Entity) && - HasComponent(coll.Entity)) - { - var plungerColliderData = GetComponent(coll.Entity); - var plungerStaticData = GetComponent(coll.Entity); - var plungerMovementData = GetComponent(coll.Entity); - newTime = ((PlungerCollider*)collider)->HitTest( - ref newCollEvent, ref insideOfs, ref plungerMovementData, - in plungerColliderData, in plungerStaticData, in ballData, collEvent.HitTime - ); - - SetComponent(coll.Entity, plungerMovementData); - } - break; - case ColliderType.Line: - case ColliderType.Line3D: - case ColliderType.Circle: - case ColliderType.LineZ: - case ColliderType.Plane: - case ColliderType.Point: - case ColliderType.Triangle: - // hit target - if (coll.Header.ItemType == ItemType.HitTarget) { - if (HasComponent(coll.Entity)) { - var dropTargetAnimationData = GetComponent(coll.Entity); - if (dropTargetAnimationData.IsDropped || dropTargetAnimationData.MoveAnimation) { // QUICKFIX so that DT is not triggered twice - saveCollision = false; - } - else { - newTime = Collider.HitTest(ref coll, ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - } - } - if (HasComponent(coll.Entity)) { - newTime = Collider.HitTest(ref coll, ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - } - } - else - newTime = Collider.HitTest(ref coll, ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - break; - - default: - newTime = Collider.HitTest(ref coll, ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - break; - } - } - } - if (saveCollision) { - SaveCollisions(ref collEvent, ref newCollEvent, ref contacts, in ballEntity, in coll, newTime); - } - } - - // no negative time allowed - if (collEvent.HitTime < 0) { - collEvent.ClearCollider(); - } - - marker.End(); - - }).Run(); - } - - private static void HitTest(ref Collider coll, ref CollisionEventData collEvent, - ref NativeList contacts, ref DynamicBuffer insideOfs, - in Entity ballEntity, in BallData ballData) { - - // todo - // if (collider.obj && collider.obj.abortHitTest && collider.obj.abortHitTest()) { - // return; - // } - - var newCollEvent = new CollisionEventData(); - var newTime = Collider.HitTest(ref coll, ref newCollEvent, ref insideOfs, in ballData, collEvent.HitTime); - - SaveCollisions(ref collEvent, ref newCollEvent, ref contacts, in ballEntity, in coll, newTime); - } - - private static void SaveCollisions(ref CollisionEventData collEvent, ref CollisionEventData newCollEvent, - ref NativeList contacts, in Entity ballEntity, in Collider coll, float newTime) - { - var validHit = newTime >= 0f && !Math.Sign(newTime) && newTime <= collEvent.HitTime; - - if (newCollEvent.IsContact || validHit) { - newCollEvent.SetCollider(coll.Id); - newCollEvent.HitTime = newTime; - if (newCollEvent.IsContact) { - contacts.Add(new ContactBufferElement(ballEntity, newCollEvent)); - - } else { // if (validhit) - collEvent = newCollEvent; - } - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs.meta deleted file mode 100644 index 349e12fb5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/StaticNarrowPhaseSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 61f4dfd573c4cb3408beb5297105d11b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI.meta deleted file mode 100644 index 44ca312b6..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8c8201e573a5b824fa8463da684c07b3 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs deleted file mode 100644 index 6500ba6c7..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs +++ /dev/null @@ -1,46 +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 ClassNeverInstantiated.Global -// ReSharper disable MemberCanBePrivate.Global - -namespace VisualPinball.Unity -{ - public class DebugFlipperSlider - { - public readonly string Label; - public readonly DebugFlipperSliderParam Param; - public readonly float MinValue; - public readonly float MaxValue; - - public DebugFlipperSlider(string label, DebugFlipperSliderParam param, float minValue, float maxValue) - { - Label = label; - Param = param; - MinValue = minValue; - MaxValue = maxValue; - } - } - - public enum DebugFlipperSliderParam - { - Acc = 1, - OffScale = 2, - OnNearEndScale = 3, - NumOfDegreeNearEnd = 4, - Mass = 5, - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs.meta deleted file mode 100644 index 3f16f6823..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperSlider.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b27b24d96c69de14b8ed68eb4efabfd3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs.meta deleted file mode 100644 index dd4997f45..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 26c795fe292cd8346a874c5b8947ca26 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs deleted file mode 100644 index 339434703..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs +++ /dev/null @@ -1,92 +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.Entities; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - public interface IDebugUI : IEngine - { - /// - /// Initializes the debug UI. This is called in the table's Start() method. - /// - /// Table component - void Init(TableComponent tableComponent); - - /// - /// Called when a physics cycle has completed. - /// - /// Physics simulation clock time in miliseconds - /// Number of completed ticks - /// Number of milliseconds of cpu time used for physics simulation - void OnPhysicsUpdate(double physicClockMilliseconds, int numSteps, float processingTimeMilliseconds); - - /// - /// Called when a flipper has been converted to an entity. - /// - /// Flipper entity - /// Name of the flipper - void OnRegisterFlipper(Entity entity, string name); - - /// - /// Called when a new ball has been created. - /// - /// Ball entity - void OnCreateBall(Entity entity); - - /// - /// Add new property to debug window. - /// If type T is not recognized it is treated as beginning of new group. - /// - /// Data type, like Vector3, float, int, ... - /// index to parent. If =-1 it means root. - /// Label or name of group. - /// Initial value. - /// Message to display as tooltip. - /// Index of property. Use it as parentIdx or propIdx. - int AddProperty(int parentIdx, string name, T value, string tip = null); - - /// - /// Get property value from DebugUI. - /// - /// Data type, like Vector3, float, int, ... - /// - /// Output where new value will be writen. - /// true if value is changed. - bool GetProperty(int propIdx, ref T value); - - - /// - /// Set property value in DebugUI. - /// - /// Data type, like Vector3, float, int, ... - /// Index of property. - /// New value for property. - void SetProperty(int propIdx, T value); - - /// - /// One line to add property (to Quick group) and sync value. - /// Property is recognized base on name & type. So, you can use same name for different types - /// - /// Data type, like Vector3, float, int, ... - /// Label for property. - /// Current value as input. Can be updated. - /// Message to display as tooltip. - /// true if value is changed. - bool QuickPropertySync(string name, ref T value, string tip = null); - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs.meta deleted file mode 100644 index d6bd084f1..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/IDebugUI.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 178b946ad7cc63e4cbfe4f9866549ee8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs deleted file mode 100644 index 7b9cf1554..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs +++ /dev/null @@ -1,179 +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; -using Unity.Assertions; -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; -using UnityEngine; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - public class DefaultPhysicsEngine : IPhysicsEngine - { - public string Name => "Default VPX"; - - public BallManager _ballManager; - - private EntityManager _entityManager; - private EntityQuery _flipperDataQuery; - private EntityQuery _ballDataQuery; - - private Matrix4x4 _worldToLocal; - private DebugFlipperState[] _flipperStates = new DebugFlipperState[0]; - private readonly DebugFlipperSlider[] _flipperSliders = new DebugFlipperSlider[0]; - private int _nextBallIdToNotifyDebugUI; - - private VisualPinballSimulationSystemGroup _visualPinballSimulationSystemGroup; - - public void Init(TableComponent tableComponent, BallManager ballManager) - { - _ballManager = ballManager; - _entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - _flipperDataQuery = _entityManager.CreateEntityQuery( - ComponentType.ReadOnly(), - ComponentType.ReadOnly(), - ComponentType.ReadOnly() - ); - - _ballDataQuery = _entityManager.CreateEntityQuery(ComponentType.ReadOnly()); - - _visualPinballSimulationSystemGroup = _entityManager.World.GetOrCreateSystem(); - var simulateCycleSystemGroup = _entityManager.World.GetOrCreateSystem(); - - _visualPinballSimulationSystemGroup.Enabled = true; - simulateCycleSystemGroup.PhysicsEngine = this; // needed for flipper status update we don't do in all engines - - var transform = tableComponent.gameObject.transform; - _worldToLocal = transform.worldToLocalMatrix; - } - - public void BallCreate(GameObject ballGo, int id, in float3 worldPos, in float3 localPos, - in float3 localVel, in float scale, in float mass, in float radius, in Entity kickerRef) - { - _ballManager.CreateEntity(ballGo, id, in worldPos, in localPos, in localVel,scale * radius * 2, - in mass, in radius, in kickerRef); - } - - public void BallManualRoll(in Entity entity, in float3 targetWorldPosition) - { - // fail safe, if we get invalid entity - if (entity == Entity.Null && entity.Index != -1) { - return; - } - - float3 target = _worldToLocal.MultiplyPoint(targetWorldPosition); - var ballData = _entityManager.GetComponentData(entity); - ballData.Velocity = float3.zero; - ballData.AngularMomentum = float3.zero; - ballData.IsFrozen = false; - - var dir = (target - ballData.Position); - var dist = math.length(dir); - if (dist > 50) { - dist = 50; - } - if (dist > 0.1f) { - dist += 1.0f; - ballData.Velocity = dir * dist * 0.001f; - _entityManager.SetComponentData(entity, ballData); - } - } - - public void FlipperRotateToEnd(in Entity entity) - { - var mData = _entityManager.GetComponentData(entity); - mData.EnableRotateEvent = 1; - mData.StartRotateToEndTime = _visualPinballSimulationSystemGroup.TimeMsec; - mData.AngleAtRotateToEnd = mData.Angle; - _entityManager.SetComponentData(entity, mData); - _entityManager.SetComponentData(entity, new SolenoidStateData { Value = true }); - } - - public void FlipperRotateToStart(in Entity entity) - { - var mData = _entityManager.GetComponentData(entity); - mData.EnableRotateEvent = -1; - _entityManager.SetComponentData(entity, mData); - _entityManager.SetComponentData(entity, new SolenoidStateData { Value = false }); - } - - public DebugFlipperState[] FlipperGetDebugStates() - { - return _flipperStates; - } - - public DebugFlipperSlider[] FlipperGetDebugSliders() - { - return _flipperSliders; - } - - public void SetFlipperDebugValue(DebugFlipperSliderParam param, float v) - { - throw new InvalidOperationException("No debug values for default engine."); - } - - public float GetFlipperDebugValue(DebugFlipperSliderParam param) - { - throw new InvalidOperationException("No debug values for default engine."); - } - - public void UpdateDebugFlipperStates() - { - // for each flipper - var entities = _flipperDataQuery.ToEntityArray(Allocator.TempJob); - if (_flipperStates.Length == 0) { - _flipperStates = new DebugFlipperState[entities.Length]; - } - for (var i = 0; i < entities.Length; i++) { - var entity = entities[i]; - var movementData = _entityManager.GetComponentData(entity); - var staticData = _entityManager.GetComponentData(entity); - var solenoidData = _entityManager.GetComponentData(entity); - _flipperStates[i] = new DebugFlipperState( - entity, - math.degrees(math.abs(movementData.Angle - staticData.AngleStart)), - solenoidData.Value - ); - } - entities.Dispose(); - } - - public void PushPendingCreateBallNotifications() - { - if (_nextBallIdToNotifyDebugUI == _ballManager.NumBallsCreated) - return; // nothing to report - - var entities = _ballDataQuery.ToEntityArray(Allocator.TempJob); - int numBallsToReport = _ballManager.NumBallsCreated - _nextBallIdToNotifyDebugUI; - foreach (var entity in entities) - { - var ballData = _entityManager.GetComponentData(entity); - if (ballData.Id >= _nextBallIdToNotifyDebugUI) - { - EngineProvider.Get().OnCreateBall(entity); - --numBallsToReport; - } - } - - // error checking - Assert.AreEqual(0, numBallsToReport); - _nextBallIdToNotifyDebugUI = _ballManager.NumBallsCreated; - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs.meta deleted file mode 100644 index 8dc5cf98d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/DefaultPhysicsEngine.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a5cceab61b91d3841981ea53976c8b42 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs deleted file mode 100644 index ae074efd3..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs +++ /dev/null @@ -1,100 +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.Entities; -using Unity.Mathematics; -using UnityEngine; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - /// - /// A swappable engine that implements VPE's rigid body physics. - /// - public interface IPhysicsEngine : IEngine - { - /// - /// Initialize and enable the engine. - /// - /// - /// All engines are instantiated, but they should only activate - /// themselves when this method is called. - /// - /// - /// - void Init(TableComponent tableComponent, BallManager ballManager); - - /// - /// Create a new ball and returns its entity. - /// - /// Created game object of the ball - /// Unique ID of the ball - /// Position in world space - /// Position in local space - /// Velocity in local space - /// Scale relative to ball mesh - /// Physics mass - /// Radius in local space - /// If created within a kicker, this is the kicker entity - void BallCreate(GameObject ballGo, int id, in float3 worldPos, in float3 localPos, in float3 localVel, - in float scale, in float mass, in float radius, in Entity kickerRef); - - /// - /// Rolls the ball manually to a position on the playfield. - /// - /// Ball entity - /// New position in world space - void BallManualRoll(in Entity ballEntity, in float3 targetWorldPosition); - - /// - /// Rotate the flipper "up" (button pressed) - /// - /// - void FlipperRotateToEnd(in Entity entity); - - /// - /// Rotate the flipper "down" (button released) - /// - /// - void FlipperRotateToStart(in Entity entity); - - /// - /// Returns a simplified version of all flipper states for the - /// debug UI to deal with. - /// - /// All flipper states - DebugFlipperState[] FlipperGetDebugStates(); - - /// - /// Returns which sliders for flippers the debug UI should display. - /// - DebugFlipperSlider[] FlipperGetDebugSliders(); - - /// - /// Updates a flipper property during gameplay. - /// - /// Which property to update - /// New value - void SetFlipperDebugValue(DebugFlipperSliderParam param, float v); - - /// - /// Retrieves a flipper property during gameplay. - /// - /// Which property to retrieve - /// Value of the property - float GetFlipperDebugValue(DebugFlipperSliderParam param); - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs.meta deleted file mode 100644 index e7de09b88..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Engine/IPhysicsEngine.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6f7bfff55a6e64543bf6c269ad5e0d49 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs index 5da918989..47e2c5b71 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/Event/EventData.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 Unity.Entities; using VisualPinball.Engine.Game; namespace VisualPinball.Unity @@ -25,45 +24,47 @@ namespace VisualPinball.Unity /// public readonly struct EventData { - public readonly EventId eventId; - public readonly Entity ItemEntity; - public readonly Entity BallEntity; + public readonly EventId EventId; + public readonly int ItemId; + public readonly int BallId; public readonly float FloatParam; public readonly bool GroupEvent; - public EventData(EventId eventId, Entity itemEntity, Entity ballEntity, bool groupEvent = false) : this() + public EventData(EventId eventId, int itemId, int ballId, bool groupEvent = false) : this() { - this.eventId = eventId; - ItemEntity = itemEntity; - BallEntity = ballEntity; + EventId = eventId; + ItemId = itemId; + BallId = ballId; GroupEvent = groupEvent; } - public EventData(EventId eventId, Entity itemEntity, Entity ballEntity, float floatParam, bool groupEvent = false) : this() + public EventData(EventId eventId, int itemId, int ballId, float floatParam, bool groupEvent = false) : this() { - this.eventId = eventId; - ItemEntity = itemEntity; - BallEntity = ballEntity; + EventId = eventId; + ItemId = itemId; + BallId = ballId; FloatParam = floatParam; GroupEvent = groupEvent; } - public EventData(EventId eventId, Entity itemEntity, bool groupEvent = false) : this() + public EventData(EventId eventId, int itemId, bool groupEvent = false) : this() { - this.eventId = eventId; - ItemEntity = itemEntity; - BallEntity = Entity.Null; + this.EventId = eventId; + ItemId = itemId; + BallId = 0; GroupEvent = groupEvent; } - public EventData(EventId eventId, Entity itemEntity, float floatParam, bool groupEvent = false) : this() + public EventData(EventId eventId, int itemId, float floatParam, bool groupEvent = false) : this() { - this.eventId = eventId; - ItemEntity = itemEntity; - BallEntity = Entity.Null; + this.EventId = eventId; + ItemId = itemId; + BallId = 0; FloatParam = floatParam; GroupEvent = groupEvent; } + + public override string ToString() => $"Event {EventId} for item {ItemId} by ball {BallId} ({FloatParam})"; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs new file mode 100644 index 000000000..73e628c51 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs @@ -0,0 +1,581 @@ +// 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 System.Diagnostics; +using System; +using Unity.Burst; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Collections; +using Unity.Profiling; +using VisualPinball.Engine.VPT; + +namespace VisualPinball.Unity +{ + [NativeContainer] + [NativeContainerSupportsMinMaxWriteRestriction] + [DebuggerDisplay("Length = {Length}")] + [DebuggerTypeProxy(typeof(NativeCollidersDebugView))] + public unsafe struct NativeColliders : IDisposable + { + #region Members + + private static readonly ProfilerMarker PerfMarker = new("NativeColliders Allocation"); + + public int Length => m_Length; + + /// + /// 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; + [NativeDisableUnsafePtrRestriction] private void* m_Line3DColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_LineSlingshotColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_LineColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_LineZColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_PlungerColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_PointColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_SpinnerColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_TriangleColliderBuffer; + [NativeDisableUnsafePtrRestriction] private void* m_PlaneColliderBuffer; + + private readonly Allocator m_AllocatorLabel; + + private int m_Length; // must be here, and called like that. + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + internal int m_MinIndex; + internal int m_MaxIndex; + internal AtomicSafetyHandle m_Safety; + internal static readonly SharedStatic s_staticSafetyId = SharedStatic.GetOrCreate(); +#endif + + #endregion + + #region Constructor / Allocation + + public NativeColliders(ref ColliderReference colRef, Allocator allocator) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // Native allocation is only valid for Temp, Job and Persistent + if (allocator <= Allocator.None) { + throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", nameof(allocator)); + } +#endif + PerfMarker.Begin(); + + m_Length = colRef.Lookup.Length; + + long size = UnsafeUtility.SizeOf() * colRef.Lookup.Length; + m_LookupBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_LookupBuffer, colRef.Lookup.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.FlipperColliders.Length; + m_FlipperColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_FlipperColliderBuffer, colRef.FlipperColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.CircleColliders.Length; + m_CircleColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_CircleColliderBuffer, colRef.CircleColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.GateColliders.Length; + m_GateColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_GateColliderBuffer, colRef.GateColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.Line3DColliders.Length; + m_Line3DColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_Line3DColliderBuffer, colRef.Line3DColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.LineSlingshotColliders.Length; + m_LineSlingshotColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_LineSlingshotColliderBuffer, colRef.LineSlingshotColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.LineColliders.Length; + m_LineColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_LineColliderBuffer, colRef.LineColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.LineZColliders.Length; + m_LineZColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_LineZColliderBuffer, colRef.LineZColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.PlungerColliders.Length; + m_PlungerColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_PlungerColliderBuffer, colRef.PlungerColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.PointColliders.Length; + m_PointColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_PointColliderBuffer, colRef.PointColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.SpinnerColliders.Length; + m_SpinnerColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_SpinnerColliderBuffer, colRef.SpinnerColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.TriangleColliders.Length; + m_TriangleColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_TriangleColliderBuffer, colRef.TriangleColliders.GetUnsafePtr(), size); + + size = UnsafeUtility.SizeOf() * colRef.PlaneColliders.Length; + m_PlaneColliderBuffer = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(m_PlaneColliderBuffer, colRef.PlaneColliders.GetUnsafePtr(), size); + + m_AllocatorLabel = allocator; + + PerfMarker.End(); + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + m_MinIndex = 0; + m_MaxIndex = m_Length - 1; + m_Safety = CollectionHelper.CreateSafetyHandle(allocator); + CollectionHelper.SetStaticSafetyId(ref m_Safety, ref s_staticSafetyId.Data); +#endif + } + + #endregion + + #region Collider Access + + internal ref CircleCollider Circle(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Circle) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up circle collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_CircleColliderBuffer, lookup.Index); + } + + internal ref FlipperCollider Flipper(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Flipper) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up flipper collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_FlipperColliderBuffer, lookup.Index); + } + + internal ref GateCollider Gate(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Gate) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up gate collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_GateColliderBuffer, lookup.Index); + } + + internal ref Line3DCollider Line3D(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Line3D) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up line3d collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_Line3DColliderBuffer, lookup.Index); + } + + internal ref LineCollider Line(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Line) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up line collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_LineColliderBuffer, lookup.Index); + } + + internal ref LineSlingshotCollider LineSlingShot(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.LineSlingShot) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up line slingshot collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_LineSlingshotColliderBuffer, lookup.Index); + } + + internal ref LineZCollider LineZ(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.LineZ) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up line-z collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_LineZColliderBuffer, lookup.Index); + } + + internal ref PlaneCollider Plane(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Plane) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up plane collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_PlaneColliderBuffer, lookup.Index); + } + + internal ref PlungerCollider Plunger(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Plunger) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up plunger collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_PlungerColliderBuffer, lookup.Index); + } + + internal ref PointCollider Point(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Point) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up point collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_PointColliderBuffer, lookup.Index); + } + + internal ref SpinnerCollider Spinner(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Spinner) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up spinner collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_SpinnerColliderBuffer, lookup.Index); + } + + internal ref TriangleCollider Triangle(int colliderId) + { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, colliderId); +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (lookup.Type != ColliderType.Triangle) { + throw new ArgumentException($"Invalid collider type {lookup.Type} when looking up triangle collider."); + } +#endif + return ref UnsafeUtility.ArrayElementAsRef(m_TriangleColliderBuffer, lookup.Index); + } + + #endregion + + #region Collection Access + + public ICollider this[int index] + { + get + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // If the container is currently not allowed to read from the buffer + // then this will throw an exception. + // This handles all cases, from already disposed containers + // to safe multithreaded access. + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); + + // Perform out of range checks based on + // the NativeContainerSupportsMinMaxWriteRestriction policy + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); +#endif + 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: return UnsafeUtility.ReadArrayElement(m_CircleColliderBuffer, lookup.Index); + case ColliderType.Flipper: return UnsafeUtility.ReadArrayElement(m_FlipperColliderBuffer, lookup.Index); + case ColliderType.Gate: return UnsafeUtility.ReadArrayElement(m_GateColliderBuffer, lookup.Index); + case ColliderType.Line3D: return UnsafeUtility.ReadArrayElement(m_Line3DColliderBuffer, lookup.Index); + case ColliderType.LineSlingShot: return UnsafeUtility.ReadArrayElement(m_LineSlingshotColliderBuffer, lookup.Index); + case ColliderType.Line: return UnsafeUtility.ReadArrayElement(m_LineColliderBuffer, lookup.Index); + case ColliderType.LineZ: return UnsafeUtility.ReadArrayElement(m_LineZColliderBuffer, lookup.Index); + case ColliderType.Plunger: return UnsafeUtility.ReadArrayElement(m_PlungerColliderBuffer, lookup.Index); + case ColliderType.Point: return UnsafeUtility.ReadArrayElement(m_PointColliderBuffer, lookup.Index); + case ColliderType.Spinner: return UnsafeUtility.ReadArrayElement(m_SpinnerColliderBuffer, lookup.Index); + case ColliderType.Triangle: return UnsafeUtility.ReadArrayElement(m_TriangleColliderBuffer, lookup.Index); + case ColliderType.Plane: return UnsafeUtility.ReadArrayElement(m_PlaneColliderBuffer, lookup.Index); + } + throw new ArgumentException($"Unknown lookup type."); + } + + set + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + // If the container is currently not allowed to write to the buffer + // then this will throw an exception. + // This handles all cases, from already disposed containers + // to safe multithreaded access. + AtomicSafetyHandle.CheckWriteAndThrow(m_Safety); + + // Perform out of range checks based on + // the NativeContainerSupportsMinMaxWriteRestriction policy + if (index < m_MinIndex || index > m_MaxIndex) + FailOutOfRangeError(index); +#endif + + 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: + UnsafeUtility.WriteArrayElement(m_CircleColliderBuffer, lookup.Index, (CircleCollider)value); + break; + case ColliderType.Flipper: + UnsafeUtility.WriteArrayElement(m_FlipperColliderBuffer, lookup.Index, (FlipperCollider)value); + break; + case ColliderType.Gate: + UnsafeUtility.WriteArrayElement(m_GateColliderBuffer, lookup.Index, (GateCollider)value); + break; + case ColliderType.Line: + UnsafeUtility.WriteArrayElement(m_LineColliderBuffer, lookup.Index, (LineCollider)value); + break; + case ColliderType.Line3D: + UnsafeUtility.WriteArrayElement(m_Line3DColliderBuffer, lookup.Index, (Line3DCollider)value); + break; + case ColliderType.LineSlingShot: + UnsafeUtility.WriteArrayElement(m_LineSlingshotColliderBuffer, lookup.Index, (LineSlingshotCollider)value); + break; + case ColliderType.LineZ: + UnsafeUtility.WriteArrayElement(m_LineZColliderBuffer, lookup.Index, (LineZCollider)value); + break; + case ColliderType.Plane: + UnsafeUtility.WriteArrayElement(m_PlaneColliderBuffer, lookup.Index, (PlaneCollider)value); + break; + case ColliderType.Plunger: + UnsafeUtility.WriteArrayElement(m_PlungerColliderBuffer, lookup.Index, (PlungerCollider)value); + break; + case ColliderType.Point: + UnsafeUtility.WriteArrayElement(m_PointColliderBuffer, lookup.Index, (PointCollider)value); + break; + case ColliderType.Spinner: + UnsafeUtility.WriteArrayElement(m_SpinnerColliderBuffer, lookup.Index, (SpinnerCollider)value); + break; + case ColliderType.Triangle: + UnsafeUtility.WriteArrayElement(m_TriangleColliderBuffer, lookup.Index, (TriangleCollider)value); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + #endregion + + #region Disposition + + public void Dispose() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + CollectionHelper.DisposeSafetyHandle(ref m_Safety); +#endif + + UnsafeUtility.Free(m_LookupBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_CircleColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_FlipperColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_GateColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_Line3DColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_LineSlingshotColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_LineColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_LineZColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_PlungerColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_PointColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_SpinnerColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_TriangleColliderBuffer, m_AllocatorLabel); + UnsafeUtility.Free(m_PlaneColliderBuffer, m_AllocatorLabel); + + m_LookupBuffer = null; + m_CircleColliderBuffer = null; + m_FlipperColliderBuffer = null; + m_GateColliderBuffer = null; + m_Line3DColliderBuffer = null; + m_LineSlingshotColliderBuffer = null; + m_LineColliderBuffer = null; + m_LineZColliderBuffer = null; + m_PlungerColliderBuffer = null; + m_PointColliderBuffer = null; + m_SpinnerColliderBuffer = null; + m_TriangleColliderBuffer = null; + m_PlaneColliderBuffer = null; + m_Length = 0; + } + +#if ENABLE_UNITY_COLLECTIONS_CHECKS + private void FailOutOfRangeError(int index) + { + if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1)) + throw new IndexOutOfRangeException(string.Format( + "Index {0} is out of restricted IJobParallelFor range [{1}...{2}] in ReadWriteBuffer.\n" + + "ReadWriteBuffers are restricted to only read & write the element at the job index. " + + "You can use double buffering strategies to avoid race conditions due to " + + "reading & writing in parallel to the same elements from a job.", + index, m_MinIndex, m_MaxIndex)); + + throw new IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length."); + } +#endif + + #endregion + + #region Collider Data + + public Aabb GetAabb(int index) + { + 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: return UnsafeUtility.ArrayElementAsRef(m_CircleColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Flipper: return UnsafeUtility.ArrayElementAsRef(m_FlipperColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Gate: return UnsafeUtility.ArrayElementAsRef(m_GateColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Line3D: return UnsafeUtility.ArrayElementAsRef(m_Line3DColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.LineSlingShot: return UnsafeUtility.ArrayElementAsRef(m_LineSlingshotColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Line: return UnsafeUtility.ArrayElementAsRef(m_LineColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.LineZ: return UnsafeUtility.ArrayElementAsRef(m_LineZColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Plunger: return UnsafeUtility.ArrayElementAsRef(m_PlungerColliderBuffer, lookup.Index).Bounds.Aabb; + case ColliderType.Point: return UnsafeUtility.ArrayElementAsRef(m_PointColliderBuffer, lookup.Index).Bounds.Aabb; + 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; + } + throw new ArgumentException($"Unknown lookup type."); + } + + public int GetItemId(int index) => GetHeader(index).ItemId; + + public ItemType GetItemType(int index) => GetHeader(index).ItemType; + + public ref ColliderHeader GetHeader(int index) + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + if (index < 0 || index >= m_Length) { + throw new IndexOutOfRangeException($"Invalid index {index} when looking up collider."); + } +#endif + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, index); + switch (lookup.Type) { + case ColliderType.Circle: return ref UnsafeUtility.ArrayElementAsRef(m_CircleColliderBuffer, lookup.Index).Header; + case ColliderType.Flipper: return ref UnsafeUtility.ArrayElementAsRef(m_FlipperColliderBuffer, lookup.Index).Header; + case ColliderType.Gate: return ref UnsafeUtility.ArrayElementAsRef(m_GateColliderBuffer, lookup.Index).Header; + case ColliderType.Line3D: return ref UnsafeUtility.ArrayElementAsRef(m_Line3DColliderBuffer, lookup.Index).Header; + case ColliderType.LineSlingShot: return ref UnsafeUtility.ArrayElementAsRef(m_LineSlingshotColliderBuffer, lookup.Index).Header; + case ColliderType.Line: return ref UnsafeUtility.ArrayElementAsRef(m_LineColliderBuffer, lookup.Index).Header; + case ColliderType.LineZ: return ref UnsafeUtility.ArrayElementAsRef(m_LineZColliderBuffer, lookup.Index).Header; + case ColliderType.Plunger: return ref UnsafeUtility.ArrayElementAsRef(m_PlungerColliderBuffer, lookup.Index).Header; + case ColliderType.Point: return ref UnsafeUtility.ArrayElementAsRef(m_PointColliderBuffer, lookup.Index).Header; + case ColliderType.Spinner: return ref UnsafeUtility.ArrayElementAsRef(m_SpinnerColliderBuffer, lookup.Index).Header; + 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."); + } + + #endregion + + #region Debug + + public ICollider[] ToArray() + { +#if ENABLE_UNITY_COLLECTIONS_CHECKS + AtomicSafetyHandle.CheckReadAndThrow(m_Safety); +#endif + var array = new ICollider[Length]; + for (var i = 0; i < Length; i++) { + ref var lookup = ref UnsafeUtility.ArrayElementAsRef(m_LookupBuffer, i); + switch (lookup.Type) { + case ColliderType.Circle: + array[i] = UnsafeUtility.ReadArrayElement(m_CircleColliderBuffer, lookup.Index); + break; + case ColliderType.Flipper: + array[i] = UnsafeUtility.ReadArrayElement(m_FlipperColliderBuffer, lookup.Index); + break; + case ColliderType.Gate: + array[i] = UnsafeUtility.ReadArrayElement(m_GateColliderBuffer, lookup.Index); + break; + case ColliderType.Line3D: + array[i] = UnsafeUtility.ReadArrayElement(m_Line3DColliderBuffer, lookup.Index); + break; + case ColliderType.LineSlingShot: + array[i] = UnsafeUtility.ReadArrayElement(m_LineSlingshotColliderBuffer, lookup.Index); + break; + case ColliderType.Line: + array[i] = UnsafeUtility.ReadArrayElement(m_LineColliderBuffer, lookup.Index); + break; + case ColliderType.LineZ: + array[i] = UnsafeUtility.ReadArrayElement(m_LineZColliderBuffer, lookup.Index); + break; + case ColliderType.Plunger: + array[i] = UnsafeUtility.ReadArrayElement(m_PlungerColliderBuffer, lookup.Index); + break; + case ColliderType.Point: + array[i] = UnsafeUtility.ReadArrayElement(m_PointColliderBuffer, lookup.Index); + break; + case ColliderType.Spinner: + array[i] = UnsafeUtility.ReadArrayElement(m_SpinnerColliderBuffer, lookup.Index); + break; + case ColliderType.Triangle: + array[i] = UnsafeUtility.ReadArrayElement(m_TriangleColliderBuffer, lookup.Index); + break; + case ColliderType.Plane: + array[i] = UnsafeUtility.ReadArrayElement(m_PlaneColliderBuffer, lookup.Index); + break; + } + } + return array; + } + + #endregion + } + + public readonly struct ColliderLookup + { + public readonly ColliderType Type; + public readonly int Index; + + public ColliderLookup(ColliderType type, int index) + { + Type = type; + Index = index; + } + } + + // Visualizes the colliders in the C# debugger + internal sealed class NativeCollidersDebugView + { + private NativeColliders _nativeColliders; + + public NativeCollidersDebugView(NativeColliders nativeColliders) + { + _nativeColliders = nativeColliders; + } + public ICollider[] Colliders => _nativeColliders.ToArray(); + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs.meta new file mode 100644 index 000000000..e941cb236 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/Physics/NativeCollider.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 264d4febde7b4f19955044bba6cbd6d6 +timeCreated: 1698263847 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup.meta deleted file mode 100644 index 2d962cda5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 81bce842dfe81a848a288463be2334ed -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs deleted file mode 100644 index bf5d7595e..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs +++ /dev/null @@ -1,26 +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.Entities; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class CreateBallEntityCommandBufferSystem : EntityCommandBufferSystem - { - - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs.meta deleted file mode 100644 index ea29d107f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/CreateBallEntityCommandBufferSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6966183f5f8a87a4dae27aebfc2f120e -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs deleted file mode 100644 index a31837c2d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Visual Pinball Engine -// Copyright (C) 2021 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.Entities; -using Unity.Mathematics; -using Unity.Transforms; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateBefore(typeof(TransformMeshesSystemGroup))] - internal class SimulateBuildSystem : SystemBase - { - private float4x4 _baseTransform; - - protected override void OnStartRunning() - { - var root = Object.FindObjectOfType(); - var ltw = root.gameObject.transform.localToWorldMatrix; - _baseTransform = new float4x4( - ltw.m00, ltw.m01, ltw.m02, ltw.m03, - ltw.m10, ltw.m11, ltw.m12, ltw.m13, - ltw.m20, ltw.m21, ltw.m22, ltw.m23, - ltw.m30, ltw.m31, ltw.m32, ltw.m33 - ); - } - - protected override void OnUpdate() - { - if (Application.isEditor) { - return; - } - var ltw = _baseTransform; - Entities.WithName("SimulateBuildJob").ForEach((ref Translation translation, ref BallData ball) => { - ball.Position = math.transform(math.inverse(ltw), math.transform(ltw, float3.zero)); - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs.meta deleted file mode 100644 index dcceef4eb..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateBuildSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 35e8e832242528f40b7d4319a1e0db98 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs deleted file mode 100644 index b88acb386..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs +++ /dev/null @@ -1,206 +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 System.Diagnostics; -using Unity.Collections; -using Unity.Entities; -using UnityEngine; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - /// - /// The main simulation loop - /// - [DisableAutoCreation] - internal class SimulateCycleSystemGroup : ComponentSystemGroup - { - /// - /// Time of the next collision; other systems can update this. - /// - public float HitTime; - - /// - /// Ball-ball collision resolution order is swapped each time - /// - public bool SwapBallCollisionHandling; - - public NativeHashMap ItemsColliding; - - public DefaultPhysicsEngine PhysicsEngine; - - public override IReadOnlyList Systems => _systemsToUpdate; - public NativeList Contacts; - - private readonly List _systemsToUpdate = new List(); - - private StaticBroadPhaseSystem _staticBroadPhaseSystem; - private DynamicBroadPhaseSystem _dynamicBroadPhaseSystem; - private StaticNarrowPhaseSystem _staticNarrowPhaseSystem; - private DynamicNarrowPhaseSystem _dynamicNarrowPhaseSystem; - private UpdateDisplacementSystemGroup _displacementSystemGroup; - private StaticCollisionSystem _staticCollisionSystem; - private DynamicCollisionSystem _dynamicCollisionSystem; - private ContactSystem _contactSystem; - private BallSpinHackSystem _ballSpinHackSystem; - - private float _staticCounts; - private EntityQuery _flipperDataQuery; - private EntityQuery _collisionEventDataQuery; - - private readonly Stopwatch _simulationTime = new Stopwatch(); - private VisualPinballSimulationSystemGroup _simulationSystemGroup; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - QuadTreeCreator.Create(EntityManager, out ItemsColliding); - } - - protected override void OnCreate() - { - _flipperDataQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly(), ComponentType.ReadOnly()); - _collisionEventDataQuery = EntityManager.CreateEntityQuery(ComponentType.ReadOnly()); - - _simulationSystemGroup = World.GetExistingSystem(); - _staticBroadPhaseSystem = World.GetOrCreateSystem(); - _dynamicBroadPhaseSystem = World.GetOrCreateSystem(); - _staticNarrowPhaseSystem = World.GetOrCreateSystem(); - _dynamicNarrowPhaseSystem = World.GetOrCreateSystem(); - _displacementSystemGroup = World.GetOrCreateSystem(); - _staticCollisionSystem = World.GetOrCreateSystem(); - _dynamicCollisionSystem = World.GetOrCreateSystem(); - _contactSystem = World.GetOrCreateSystem(); - _ballSpinHackSystem = World.GetOrCreateSystem(); - _systemsToUpdate.Add(_staticBroadPhaseSystem); - _systemsToUpdate.Add(_dynamicBroadPhaseSystem); - _systemsToUpdate.Add(_staticNarrowPhaseSystem); - _systemsToUpdate.Add(_dynamicNarrowPhaseSystem); - _systemsToUpdate.Add(_displacementSystemGroup); - _systemsToUpdate.Add(_staticCollisionSystem); - _systemsToUpdate.Add(_dynamicCollisionSystem); - _systemsToUpdate.Add(_contactSystem); - _systemsToUpdate.Add(_ballSpinHackSystem); - - Contacts = new NativeList(Allocator.Persistent); - } - - protected override void OnDestroy() - { - Contacts.Dispose(); - ItemsColliding.Dispose(); - } - - protected override void OnUpdate() - { - if (Application.isEditor) { - UpdateCycle(); - - } else { - #if DEV_MODE_ENABLED - UpdateCycle(); - #endif - } - } - - private void UpdateCycle() - { - _simulationTime.Restart(); - - _staticCounts = PhysicsConstants.StaticCnts; - var dTime = _simulationSystemGroup.PhysicsDiffTime; - var numSteps = 0; - while (dTime > 0) { - - HitTime = (float)dTime; - - ApplyFlipperTime(); - ClearContacts(); - - _dynamicBroadPhaseSystem.Update(); - _staticBroadPhaseSystem.Update(); - _staticNarrowPhaseSystem.Update(); - _dynamicNarrowPhaseSystem.Update(); - - ApplyStaticTime(); - - _displacementSystemGroup.Update(); - _dynamicCollisionSystem.Update(); - _staticCollisionSystem.Update(); - _contactSystem.Update(); - - ClearContacts(); - - _ballSpinHackSystem.Update(); - - dTime -= HitTime; - - SwapBallCollisionHandling = !SwapBallCollisionHandling; - ++numSteps; - } - - // debug ui update - if (EngineProvider.Exists) { - PhysicsEngine.UpdateDebugFlipperStates(); - PhysicsEngine.PushPendingCreateBallNotifications(); - EngineProvider.Get().OnPhysicsUpdate(_simulationSystemGroup.CurrentPhysicsTime, numSteps, (float)_simulationTime.Elapsed.TotalMilliseconds); - } - } - - private void ClearContacts() - { - Contacts.Clear(); - } - - private void ApplyFlipperTime() - { - // for each flipper - var entities = _flipperDataQuery.ToEntityArray(Allocator.TempJob); - foreach (var entity in entities) { - var movementData = EntityManager.GetComponentData(entity); - var staticData = EntityManager.GetComponentData(entity); - var tricksData = EntityManager.GetComponentData(entity); - var flipperHitTime = movementData.GetHitTime(staticData.AngleStart, tricksData.AngleEnd); - - // if flipper comes to a rest before the end of the cycle, advance to that time - if (flipperHitTime > 0 && flipperHitTime < HitTime) { //!! >= 0.f causes infinite loop - HitTime = flipperHitTime; - } - } - entities.Dispose(); - } - - private void ApplyStaticTime() - { - // for each collision event - var entities = _collisionEventDataQuery.ToEntityArray(Allocator.TempJob); - foreach (var entity in entities) { - var collEvent = EntityManager.GetComponentData(entity); - if (collEvent.HasCollider() && collEvent.HitTime <= HitTime) { // smaller hit time?? - HitTime = collEvent.HitTime; // record actual event time - if (collEvent.HitTime < PhysicsConstants.StaticTime) { // less than static time interval - if (--_staticCounts < 0) { - _staticCounts = 0; // keep from wrapping - HitTime = PhysicsConstants.StaticTime; - } - } - } - } - entities.Dispose(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs.meta deleted file mode 100644 index ba3666d5e..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/SimulateCycleSystemGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3a0c324e97594e34caf2813cdf7b7eb5 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.cs deleted file mode 100644 index a59943903..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.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 . - -using Unity.Entities; - -namespace VisualPinball.Unity -{ - internal class TransformMeshesSystemGroup : ComponentSystemGroup - { - - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.cs.meta deleted file mode 100644 index 48997e962..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/TransformMeshesSystemGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b3cd4fc2284ab694aba839a05a7a2bc3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs deleted file mode 100644 index e80649d50..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs +++ /dev/null @@ -1,26 +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.Entities; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class UpdateAnimationsSystemGroup : ComponentSystemGroup - { - - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs.meta deleted file mode 100644 index 9a88f51f7..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateAnimationsSystemGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: d6d9ca61b61c9d04b857eb9dc132882a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.cs deleted file mode 100644 index 971fcf294..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.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 . - -using Unity.Entities; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class UpdateDisplacementSystemGroup : ComponentSystemGroup - { - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.cs.meta deleted file mode 100644 index bbf56bf28..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateDisplacementSystemGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 228103f732034244ca23ad99495adf93 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.cs b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.cs deleted file mode 100644 index 41d7c672a..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.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 . - -using Unity.Entities; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class UpdateVelocitiesSystemGroup : ComponentSystemGroup - { - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.cs.meta deleted file mode 100644 index c28698439..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/SystemGroup/UpdateVelocitiesSystemGroup.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0a1634dd6651e6346be17d8fc511f452 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs index ea1b45623..a5700fba1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/AnimationComponent.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 Unity.Entities; using VisualPinball.Engine.VPT; namespace VisualPinball.Unity @@ -27,14 +25,9 @@ public abstract class AnimationComponent : SubComponent MainComponent.UpdateTransforms(); - private Entity MainEntity { - get { - var ma = MainComponent; - if (ma == null) { - throw new InvalidOperationException("Cannot find main component of " + name + "."); - } - return ma.Entity; - } + private void Awake() + { + RegisterPhysics(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs.meta deleted file mode 100644 index ab0463d92..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 451d480ce82baf34d899d5205034ed7c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallCollider.cs index 68a6cfffe..e3b4fd091 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallCollider.cs @@ -24,7 +24,7 @@ internal static class BallCollider { private const float HardScatter = 0.0f; - public static void Collide3DWall(ref BallData ball, in PhysicsMaterialData material, in CollisionEventData collEvent, in float3 hitNormal, ref Random random) + public static void Collide3DWall(ref BallState ball, in PhysicsMaterialData material, in CollisionEventData collEvent, in float3 hitNormal, ref Random random) { // speed normal to wall var dot = math.dot(ball.Velocity, hitNormal); @@ -78,7 +78,7 @@ public static void Collide3DWall(ref BallData ball, in PhysicsMaterialData mater // compute friction impulse var surfP = -ball.Radius * hitNormal; // surface contact point relative to center of mass - var surfVel = BallData.SurfaceVelocity(in ball, in surfP); // velocity at impact point + var surfVel = BallState.SurfaceVelocity(in ball, in surfP); // velocity at impact point var tangent = surfVel - hitNormal * math.dot(surfVel, hitNormal); // calc the tangential velocity var tangentSpSq = math.lengthsq(tangent); @@ -139,7 +139,7 @@ public static void Collide3DWall(ref BallData ball, in PhysicsMaterialData mater } } - public static void HandleStaticContact(ref BallData ball, in CollisionEventData collEvent, float friction, float dTime, in float3 gravity) + public static void HandleStaticContact(ref BallState ball, in CollisionEventData collEvent, float friction, float dTime, in float3 gravity) { // this should be zero, but only up to +/- PhysicsConstants.ContactVel var normVel = math.dot(ball.Velocity, collEvent.HitNormal); @@ -161,11 +161,11 @@ public static void HandleStaticContact(ref BallData ball, in CollisionEventData } } - private static void ApplyFriction(ref BallData ball, in float3 hitNormal, float dTime, float frictionCoeff, in float3 gravity) + private static void ApplyFriction(ref BallState ball, in float3 hitNormal, float dTime, float frictionCoeff, in float3 gravity) { // surface contact point relative to center of mass var surfP = -ball.Radius * hitNormal; - var surfVel = BallData.SurfaceVelocity(in ball, in surfP); + var surfVel = BallState.SurfaceVelocity(in ball, in surfP); // calc the tangential slip velocity var slip = surfVel - hitNormal * math.dot(surfVel, hitNormal); @@ -181,7 +181,7 @@ private static void ApplyFriction(ref BallData ball, in float3 hitNormal, float // check for <=0.025 originated from ball<->rubber collisions pushing the ball upwards, but this is still not enough, some could even use <=0.2 // slip speed zero - static friction case - var surfAcc = BallData.SurfaceAcceleration(in ball, in surfP, in gravity); + var surfAcc = BallState.SurfaceAcceleration(in ball, in surfP, in gravity); // calc the tangential slip acceleration var slipAcc = surfAcc - hitNormal * math.dot(surfAcc, hitNormal); @@ -208,7 +208,7 @@ private static void ApplyFriction(ref BallData ball, in float3 hitNormal, float } } - public static float HitTest(ref CollisionEventData collEvent, ref BallData otherBall, in BallData ball, float dTime) + public static float HitTest(ref CollisionEventData collEvent, ref BallState otherBall, in BallState ball, float dTime) { var d = ball.Position - otherBall.Position; // delta position var dv = ball.Velocity - otherBall.Velocity; // delta velocity @@ -305,7 +305,7 @@ public static float HitTest(ref CollisionEventData collEvent, ref BallData other return hitTime; } - public static bool Collide(ref BallData ball, ref BallData otherBall, + public static bool Collide(ref BallState ball, ref BallState otherBall, in CollisionEventData ballCollEvent, in CollisionEventData otherCollEvent, bool swapBallCollisionHandling) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs similarity index 51% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs index 19683d84e..6a91da5ca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs @@ -14,38 +14,38 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; +// ReSharper disable InconsistentNaming + using Unity.Mathematics; -using Unity.Profiling; using UnityEngine; namespace VisualPinball.Unity { - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class GateMovementSystem : SystemBase + public class BallComponent : MonoBehaviour { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("GateMovementSystem"); - - private Player _player; + public int Id => gameObject.GetInstanceID(); + public float Radius = 25; + public float Mass = 1; + public float3 Velocity; + public bool IsFrozen; - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - protected override void OnUpdate() + internal BallState CreateState() { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("GateMovementJob").ForEach((Entity entity, in GateMovementData movementData) => { - - marker.Begin(); - - _player.GateWireTransforms[entity].localRotation = quaternion.RotateX(-movementData.Angle); - - marker.End(); - - }).Run(); + var pos = transform.localPosition.TranslateToVpx(); + return new BallState + { + Id = Id, + IsFrozen = IsFrozen, + Position = new float3(pos.x, pos.y, math.round(pos.z*100000) / 100000), + Radius = Radius, + Mass = Mass, + Velocity = Velocity, + BallOrientation = float3x3.identity, + BallOrientationForUnity = float3x3.identity, + RingCounterOldPos = 0, + AngularMomentum = float3.zero + }; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs.meta similarity index 61% rename from VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs.meta index 0a7ad6668..dd724599c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Game/VisualPinballSimulationSystemGroup.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallComponent.cs.meta @@ -1,11 +1,11 @@ fileFormatVersion: 2 -guid: 86917f35ffc397f40852201b8deb1f07 +guid: a04fca20ce2246b9abf456441b587efd MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 - icon: {instanceID: 0} + icon: {fileID: 2800000, guid: 7e22e740622cf2243bfe316adbf63442, type: 3} userData: assetBundleName: assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs new file mode 100644 index 000000000..bf50db4f2 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs @@ -0,0 +1,83 @@ +// 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; +using UnityEngine; + +namespace VisualPinball.Unity +{ + internal static class BallDisplacementPhysics + { + internal static void UpdateDisplacements(ref BallState ball, float dTime) + { + if (ball.IsFrozen) { + return; + } + + ball.Position += ball.Velocity * dTime; + + var inertia = ball.Inertia; + var mat3 = CreateSkewSymmetric(ball.AngularMomentum / inertia); + var addedOrientation = math.mul(ball.BallOrientation, mat3); + addedOrientation *= dTime; + + ball.BallOrientation += addedOrientation; + + // do the same for Unity's ball Orientation (where z (and z only rotation) has to be flipped), + // which (maybe??) can't be done after skew matrix operations (or we don't know how)) + // If we flip an exis in the matrix, we always flip two rotations. + var AngMomFlippedZ = new float3(ball.AngularMomentum.x, ball.AngularMomentum.y, -ball.AngularMomentum.z); + mat3 = CreateSkewSymmetric(AngMomFlippedZ / inertia); + addedOrientation = math.mul(ball.BallOrientationForUnity, mat3); + addedOrientation *= dTime; + + ball.BallOrientationForUnity += addedOrientation; + + VPOrthonormalize(ref ball.BallOrientation); + VPOrthonormalize(ref ball.BallOrientationForUnity); + } + + private static void VPOrthonormalize(ref float3x3 orientation) + { + Vector3 vX = new Vector3(orientation.c0.x, orientation.c1.x, orientation.c2.x); + Vector3 vY = new Vector3(orientation.c0.y, orientation.c1.y, orientation.c2.y); + Vector3 vZ = Vector3.Cross(vX, vY); + vX = Vector3.Normalize(vX); + vZ = Vector3.Normalize(vZ); + vY = Vector3.Cross(vZ, vX); + + orientation.c0.x = vX.x; + orientation.c0.y = vY.x; + orientation.c0.z = vZ.x; + orientation.c1.x = vX.y; + orientation.c1.y = vY.y; + orientation.c1.z = vZ.y; + orientation.c2.x = vX.z; + orientation.c2.y = vY.z; + orientation.c2.z = vZ.z; + + } + + private static float3x3 CreateSkewSymmetric(in float3 pv3D) + { + return new float3x3( + 0, -pv3D.z, pv3D.y, + pv3D.z, 0, -pv3D.x, + -pv3D.y, pv3D.x, 0 + ); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs.meta new file mode 100644 index 000000000..2e095d05a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementPhysics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3f76a7dfe56c4f1981dce51d4dd4a160 +timeCreated: 1679006624 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs deleted file mode 100644 index 6eb140d6c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs +++ /dev/null @@ -1,109 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateDisplacementSystemGroup))] - internal class BallDisplacementSystem : SystemBase - { - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BallDisplacementSystem"); - - protected override void OnCreate() - { - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - } - - protected override void OnUpdate() - { - var dTime = _simulateCycleSystemGroup.HitTime; - var marker = PerfMarker; - - Entities.WithName("BallDisplacementJob").ForEach((ref BallData ball) => { - - if (ball.IsFrozen) { - return; - } - - marker.Begin(); - - ball.Position += ball.Velocity * dTime; - - - //Logger.Debug($"Ball {ball.Id} Position = {ball.Position}"); - - var inertia = ball.Inertia; - var mat3 = CreateSkewSymmetric(ball.AngularMomentum / inertia); - var addedOrientation = math.mul(ball.BallOrientation, mat3); - addedOrientation *= dTime; - - ball.BallOrientation += addedOrientation; - - // do the same for Unity's ball Orientation (where z (and z only rotation) has to be flipped), - // which (maybe??) can't be done after skew matrix operations (or we don't know how)) - // If we flip an exis in the matrix, we always flip two rotations. - var AngMomFlippedZ = new float3(ball.AngularMomentum.x, ball.AngularMomentum.y, -ball.AngularMomentum.z); - mat3 = CreateSkewSymmetric(AngMomFlippedZ / inertia); - addedOrientation = math.mul(ball.BallOrientationForUnity, mat3); - addedOrientation *= dTime; - - ball.BallOrientationForUnity += addedOrientation; - - VPOrthonormalize(ref ball.BallOrientation); - VPOrthonormalize(ref ball.BallOrientationForUnity); - - marker.End(); - - }).Run(); - } - - private static void VPOrthonormalize(ref float3x3 orientation) - { - Vector3 vX = new Vector3(orientation.c0.x, orientation.c1.x, orientation.c2.x); - Vector3 vY = new Vector3(orientation.c0.y, orientation.c1.y, orientation.c2.y); - Vector3 vZ = Vector3.Cross(vX, vY); - vX = Vector3.Normalize(vX); - vZ = Vector3.Normalize(vZ); - vY = Vector3.Cross(vZ, vX); - - orientation.c0.x = vX.x; - orientation.c0.y = vY.x; - orientation.c0.z = vZ.x; - orientation.c1.x = vX.y; - orientation.c1.y = vY.y; - orientation.c1.z = vZ.y; - orientation.c2.x = vX.z; - orientation.c2.y = vY.z; - orientation.c2.z = vZ.z; - - } - - private static float3x3 CreateSkewSymmetric(in float3 pv3D) - { - return new float3x3( - 0, -pv3D.z, pv3D.y, - pv3D.z, 0, -pv3D.x, - -pv3D.y, pv3D.x, 0 - ); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs.meta deleted file mode 100644 index 7c392f41d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallDisplacementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 22ede533e7e963a478cac75e3f0d3404 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs deleted file mode 100644 index f7bf14f30..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs +++ /dev/null @@ -1,30 +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.Entities; - -namespace VisualPinball.Unity -{ - /// - /// List of triggers and kickers the ball is now inside - /// - /// It's what VPX calls `m_vpVolObjs` - [InternalBufferCapacity(0)] - internal struct BallInsideOfBufferElement : IBufferElementData - { - public Entity Value; - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs.meta deleted file mode 100644 index 718ce9c27..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallInsideOfBufferElement.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 54b4dcc117bbc004897a4adadc89fddb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs.meta deleted file mode 100644 index a1f183f56..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b2421e157d48a764b8adcb466202489b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs index 700cb6146..9d25dd573 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallManager.cs @@ -15,10 +15,7 @@ // along with this program. If not, see . using System; -using Unity.Entities; -using Unity.Mathematics; using UnityEngine; -using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; using Object = UnityEngine.Object; @@ -31,164 +28,50 @@ namespace VisualPinball.Unity public class BallManager { public int NumBallsCreated { get; private set; } - public int NumBalls { get; private set; } - private readonly GameObject _playfield; + private readonly PhysicsEngine _physicsEngine; private readonly Player _player; + private readonly Transform _parent; - private static EntityManager EntityManager => World.DefaultGameObjectInjectionWorld.EntityManager; - - private static Mesh _unitySphereMesh; // used to cache ball mesh from GameObject - - public BallManager(Player player) + public BallManager(PhysicsEngine physicsEngine, Player player, Transform parent) { + _physicsEngine = physicsEngine; _player = player; - _playfield = player.Playfield; - } - - public void CreateBall(IBallCreationPosition ballCreator, float radius = 25f, float mass = 1f) - { - CreateBall(ballCreator, radius, mass, Entity.Null); + _parent = parent; } - public void CreateBall(IBallCreationPosition ballCreator, float radius, float mass, in Entity kickerRef) + public int CreateBall(IBallCreationPosition ballCreator, float radius = 25f, float mass = 1f, GameObject ballPrefab = null) { var localPos = ballCreator.GetBallCreationPosition().ToUnityFloat3(); - var localVel = ballCreator.GetBallCreationVelocity().ToUnityFloat3(); localPos.z += radius; - var ltw = _playfield.transform.localToWorldMatrix; - var worldPos = ltw.MultiplyPoint(localPos); - var scale3 = new Vector3( - ltw.GetColumn(0).magnitude, - ltw.GetColumn(1).magnitude, - ltw.GetColumn(2).magnitude - ); - var scale = (scale3.x + scale3.y + scale3.z) / 3.0f; // scale is only scale (without radiusfloat now, not vector. - - var ballId = NumBallsCreated++; - var ballPrefab = RenderPipeline.Current.BallConverter.CreateDefaultBall(); - var ballGo = Object.Instantiate(ballPrefab, _playfield.transform); - ballGo.name = $"Ball{ballId}"; - ballGo.transform.localScale = Physics.ScaleToWorld(new Vector3(radius, radius, radius) * 2f); - ballGo.transform.localPosition = localPos; - - // create ball entity - EngineProvider - .Get() - .BallCreate(ballGo, ballId, worldPos, localPos, localVel, scale, mass, radius, in kickerRef); - } - - public void CreateEntity(GameObject ballGo, int id, in float3 worldPos, in float3 localPos, in float3 localVel, in float scale, - in float mass, in float radius, in Entity kickerEntity) - { - // Efficiently instantiate a bunch of entities from the already converted entity prefab - var entity = EntityManager.CreateEntity( - typeof(OverlappingStaticColliderBufferElement), - typeof(OverlappingDynamicBufferElement), - typeof(BallInsideOfBufferElement), - typeof(BallLastPositionsBufferElement), - typeof(BallData), - typeof(CollisionEventData) - ); - -#if UNITY_EDITOR - EntityManager.SetName(entity, $"Ball{id}"); -#endif - - _player.Balls[entity] = ballGo; - - var world = World.DefaultGameObjectInjectionWorld; - var ecbs = world.GetOrCreateSystem(); - var ecb = ecbs.CreateCommandBuffer(); - - ecb.AddBuffer(entity); - ecb.AddBuffer(entity); - ecb.AddBuffer(entity); - - ecb.AddComponent(entity, new BallData { - Id = id, - IsFrozen = kickerEntity != Entity.Null, - Position = localPos, - Radius = radius, - Mass = mass, - Velocity = localVel, - BallOrientation = float3x3.identity, - BallOrientationForUnity = float3x3.identity, - RingCounterOldPos = 0, - AngularMomentum = float3.zero - }); - - ecb.AddComponent(entity, new CollisionEventData { - HitTime = -1, - HitDistance = 0, - HitFlag = false, - IsContact = false, - HitNormal = new float3(0, 0, 0), - }); - - var lastBallPostBuffer = ecb.AddBuffer(entity); - for (var i = 0; i < BallRingCounterSystem.MaxBallTrailPos; i++) { - lastBallPostBuffer.Add(new BallLastPositionsBufferElement - { Value = new float3(float.MaxValue, float.MaxValue, float.MaxValue) } - ); - } - - // handle inside-kicker creation - if (kickerEntity != Entity.Null) { - var kickerData = EntityManager.GetComponentData(kickerEntity); - if (!kickerData.FallThrough) { - var kickerCollData = EntityManager.GetComponentData(kickerEntity); - var inside = ecb.AddBuffer(entity); - BallData.SetInsideOf(ref inside, kickerEntity); - kickerCollData.BallEntity = entity; - kickerCollData.LastCapturedBallEntity = entity; - ecb.SetComponent(kickerEntity, kickerCollData); - } + if (!ballPrefab) { + ballPrefab = RenderPipeline.Current.BallConverter.CreateDefaultBall(); } + var ballGo = Object.Instantiate(ballPrefab, _parent); + var ballComp = ballGo.GetComponent(); + ballGo.name = $"Ball {NumBallsCreated++}"; + ballGo.transform.localScale = Physics.ScaleToWorld(new Vector3(radius, radius, radius) * 2f); + ballGo.transform.localPosition = localPos.TranslateToWorld(); + ballComp.Radius = radius; + ballComp.Mass = mass; + ballComp.Velocity = ballCreator.GetBallCreationVelocity().ToUnityFloat3(); + ballComp.IsFrozen = false; - NumBalls++; + // register ball + _physicsEngine.Register(ballComp); + _player.BallCreated(ballGo.GetInstanceID(), ballGo); - _player.BallCreated(entity, ballGo); + return ballComp.Id; } - public void DestroyEntity(Entity ballEntity) + public void DestroyBall(int ballId) { - var ballGo = _player.Balls[ballEntity]; - _player.BallDestroyed(ballEntity, ballGo); + var ballTransform = _physicsEngine.UnregisterBall(ballId); + _player.BallDestroyed(ballId, ballTransform.gameObject); // destroy game object - Object.DestroyImmediate(ballGo); - _player.Balls.Remove(ballEntity); - - // destroy entity - World.DefaultGameObjectInjectionWorld - .GetOrCreateSystem() - .CreateCommandBuffer() - .DestroyEntity(ballEntity); - - NumBalls--; + Object.DestroyImmediate(ballTransform.gameObject); } - - /// - /// Dirty way to get SphereMesh from Unity. - /// ToDo: Get Mesh from our resources - /// - /// Sphere Mesh - private static Mesh GetSphereMesh() - { - if (!_unitySphereMesh) - { - var go = GameObject.CreatePrimitive(PrimitiveType.Sphere); - _unitySphereMesh = go.GetComponent().sharedMesh; - Object.Destroy(go); - } - - return _unitySphereMesh; - } - - #region Material - - #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs new file mode 100644 index 000000000..205962853 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs @@ -0,0 +1,106 @@ +// 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 +{ + internal static class BallMovementPhysics + { + + // protected override void OnStartRunning() + // { + // var root = Object.FindObjectOfType(); + // var ltw = root.gameObject.transform.localToWorldMatrix; + // _baseTransform = new float4x4( + // ltw.m00, ltw.m01, ltw.m02, ltw.m03, + // ltw.m10, ltw.m11, ltw.m12, ltw.m13, + // ltw.m20, ltw.m21, ltw.m22, ltw.m23, + // ltw.m30, ltw.m31, ltw.m32, ltw.m33 + // ); + // _player = Object.FindObjectOfType(); + // } + + public static void Move(BallState ball, Transform ballTransform) + { + // calculate/adapt height of ball + var zHeight = !ball.IsFrozen ? ball.Position.z : ball.Position.z - ball.Radius; + ballTransform.localPosition = Physics.TranslateToWorld(ball.Position.x, ball.Position.y, zHeight); + + var or = ball.BallOrientationForUnity; + + var VPX = new Vector3(or.c0.x, or.c1.x, or.c2.x); + var VPY = new Vector3(or.c0.y, or.c1.y, or.c2.y); + var VPZ = new Vector3(or.c0.z, or.c1.z, or.c2.z); + + // 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); + + // flip Z axis + q = FlipZAxis(q); + + ballTransform.localRotation = q.RotateToWorld(); + + static Quaternion FlipZAxis(Quaternion q) + { + // which actually flips x and y axis visually... + return new Quaternion(q.x, q.y, -q.z, -q.w); + } + + /* + * I let these two in here, just in case we need them. + + static float3x3 transpose(float3x3 or) + { + float3x3 or2; + or2.c0.x = or.c0.x; + or2.c0.y = or.c1.x; + or2.c0.z = or.c2.x; + or2.c1.x = or.c0.y; + or2.c1.y = or.c1.y; + or2.c1.z = or.c2.y; + or2.c2.x = or.c0.z; + or2.c2.y = or.c1.z; + or2.c2.z = or.c2.z; + return or2; + } + + static Quaternion QuaternionFromMatrix(Matrix4x4 m) + { + // Adapted from: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + Quaternion q = new Quaternion(); + q.w = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] + m[1, 1] + m[2, 2])) / 2; + q.x = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] - m[1, 1] - m[2, 2])) / 2; + q.y = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] + m[1, 1] - m[2, 2])) / 2; + q.z = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] - m[1, 1] + m[2, 2])) / 2; + q.x *= Mathf.Sign(q.x * (m[2, 1] - m[1, 2])); + q.y *= Mathf.Sign(q.y * (m[0, 2] - m[2, 0])); + q.z *= Mathf.Sign(q.z * (m[1, 0] - m[0, 1])); + return q; + } + + */ + + + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs.meta new file mode 100644 index 000000000..1e8a0dbec --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementPhysics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 141124d679c14e43b023645145178e34 +timeCreated: 1679240848 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs deleted file mode 100644 index 6fe7d797c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs +++ /dev/null @@ -1,132 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [AlwaysSynchronizeSystem] - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class BallMovementSystem : SystemBase - { - private float4x4 _baseTransform; - private Player _player; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BallMovementSystem"); - - protected override void OnStartRunning() - { - var root = Object.FindObjectOfType(); - var ltw = root.gameObject.transform.localToWorldMatrix; - _baseTransform = new float4x4( - ltw.m00, ltw.m01, ltw.m02, ltw.m03, - ltw.m10, ltw.m11, ltw.m12, ltw.m13, - ltw.m20, ltw.m21, ltw.m22, ltw.m23, - ltw.m30, ltw.m31, ltw.m32, ltw.m33 - ); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var ltw = _baseTransform; - var marker = PerfMarker; - Entities.WithoutBurst().WithName("BallMovementJob").ForEach((Entity entity, in BallData ball) => { - - marker.Begin(); - - if (!_player.Balls.ContainsKey(entity)) { - marker.End(); - return; - } - - // calculate/adapt height of ball - var zHeight = !ball.IsFrozen ? ball.Position.z : ball.Position.z - ball.Radius; - var ballTransform = _player.Balls[entity].transform; - ballTransform.localPosition = Physics.TranslateToWorld(ball.Position.x, ball.Position.y, zHeight); - - var or = ball.BallOrientationForUnity; - - var VPX = new Vector3(or.c0.x, or.c1.x, or.c2.x); - var VPY = new Vector3(or.c0.y, or.c1.y, or.c2.y); - var VPZ = new Vector3(or.c0.z, or.c1.z, or.c2.z); - - // 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); - - // flip Z axis - q = FlipZAxis(q); - - ballTransform.localRotation = q.RotateToWorld(); - - marker.End(); - - }).Run(); - - - static Quaternion FlipZAxis(Quaternion q) - { - // which actually flips x and y axis visually... - return new Quaternion(q.x, q.y, -q.z, -q.w); - } - - /* - * I let these two in here, just in case we need them. - - static float3x3 transpose(float3x3 or) - { - float3x3 or2; - or2.c0.x = or.c0.x; - or2.c0.y = or.c1.x; - or2.c0.z = or.c2.x; - or2.c1.x = or.c0.y; - or2.c1.y = or.c1.y; - or2.c1.z = or.c2.y; - or2.c2.x = or.c0.z; - or2.c2.y = or.c1.z; - or2.c2.z = or.c2.z; - return or2; - } - - static Quaternion QuaternionFromMatrix(Matrix4x4 m) - { - // Adapted from: http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm - Quaternion q = new Quaternion(); - q.w = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] + m[1, 1] + m[2, 2])) / 2; - q.x = Mathf.Sqrt(Mathf.Max(0, 1 + m[0, 0] - m[1, 1] - m[2, 2])) / 2; - q.y = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] + m[1, 1] - m[2, 2])) / 2; - q.z = Mathf.Sqrt(Mathf.Max(0, 1 - m[0, 0] - m[1, 1] + m[2, 2])) / 2; - q.x *= Mathf.Sign(q.x * (m[2, 1] - m[1, 2])); - q.y *= Mathf.Sign(q.y * (m[0, 2] - m[2, 0])); - q.z *= Mathf.Sign(q.z * (m[1, 0] - m[0, 1])); - return q; - } - - */ - - - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs.meta deleted file mode 100644 index 04c2fe918..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallMovementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c37708d9a7a9dfe40a78b6fd1a5286ed -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs new file mode 100644 index 000000000..29efcf518 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs @@ -0,0 +1,86 @@ +// 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; +using Unity.Mathematics; + +namespace VisualPinball.Unity +{ + /// + /// We can't have lists of lists in jobs, so... + /// + public struct BallPositions + { + public static int Count => 10; + + private float3 _pos00; + private float3 _pos01; + private float3 _pos02; + private float3 _pos03; + private float3 _pos04; + private float3 _pos05; + private float3 _pos06; + private float3 _pos07; + private float3 _pos08; + private float3 _pos09; + + public BallPositions(float3 initialPositions) + { + _pos00 = initialPositions; + _pos01 = initialPositions; + _pos02 = initialPositions; + _pos03 = initialPositions; + _pos04 = initialPositions; + _pos05 = initialPositions; + _pos06 = initialPositions; + _pos07 = initialPositions; + _pos08 = initialPositions; + _pos09 = initialPositions; + } + + public float3 this[int index] { + get { + return index switch { + 0 => _pos00, + 1 => _pos01, + 2 => _pos02, + 3 => _pos03, + 4 => _pos04, + 5 => _pos05, + 6 => _pos06, + 7 => _pos07, + 8 => _pos08, + 9 => _pos09, + _ => throw new ArgumentOutOfRangeException("Only " + Count + " positions available.") + }; + } + set { + switch (index) { + case 0: _pos00 = value; break; + case 1: _pos01 = value; break; + case 2: _pos02 = value; break; + case 3: _pos03 = value; break; + case 4: _pos04 = value; break; + case 5: _pos05 = value; break; + case 6: _pos06 = value; break; + case 7: _pos07 = value; break; + case 8: _pos08 = value; break; + case 9: _pos09 = value; break; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs.meta new file mode 100644 index 000000000..fb3544d8e --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallPositions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 01112e733aac4619b2c5d14fc90a2064 +timeCreated: 1696882763 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterPhysics.cs new file mode 100644 index 000000000..00c84dc16 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterPhysics.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 VisualPinball.Engine.Common; + +namespace VisualPinball.Unity +{ + internal static class BallRingCounterPhysics + { + internal static void Update(ref BallState ball) + { + var idx = ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime); + ball.LastPositions[idx] = ball.Position; + ball.RingCounterOldPos++; + if (ball.RingCounterOldPos == BallPositions.Count * (10000 / PhysicsConstants.PhysicsStepTime)) { + ball.RingCounterOldPos = 0; + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterSystem.cs deleted file mode 100644 index 755a9b6aa..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallRingCounterSystem.cs +++ /dev/null @@ -1,48 +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.Entities; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class BallRingCounterSystem : SystemBase - { - public const int MaxBallTrailPos = 10; - - protected override void OnUpdate() - { - var lastPositionBuffer = GetBufferFromEntity(); - Entities - .WithNativeDisableParallelForRestriction(lastPositionBuffer) - .ForEach((Entity entity, ref BallData ball) => - { - var posIdx = ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime); - var lastPositions = lastPositionBuffer[entity]; - var lastPosition = lastPositions[posIdx]; - lastPosition.Value = ball.Position; - lastPositions[posIdx] = lastPosition; - - ball.RingCounterOldPos++; - if (ball.RingCounterOldPos == MaxBallTrailPos * (10000 / PhysicsConstants.PhysicsStepTime)) { - ball.RingCounterOldPos = 0; - } - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackPhysics.cs new file mode 100644 index 000000000..7075970e8 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackPhysics.cs @@ -0,0 +1,46 @@ +// 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; +using VisualPinball.Engine.Common; + +// ReSharper disable CompareOfFloatsByEqualityOperator + +namespace VisualPinball.Unity +{ + internal static class BallSpinHackPhysics + { + internal static void Update(ref BallState ball) + { + var p0 = (ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime) + 1) % BallPositions.Count; + var p1 = (ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime) + 2) % BallPositions.Count; + + // only if already initialized + if (ball.CollisionEvent.HitDistance < PhysicsConstants.PhysTouch && ball.LastPositions[p0].x != Constants.FloatMax && ball.LastPositions[p1].x != float.MaxValue) { + var diffPos = ball.LastPositions[p0] - ball.Position; + var mag = diffPos.x*diffPos.x + diffPos.y*diffPos.y; + var diffPos2 = ball.LastPositions[p1] - ball.Position; + var mag2 = diffPos2.x*diffPos2.x + diffPos2.y*diffPos2.y; + var threshold = (ball.AngularMomentum.x*ball.AngularMomentum.x + ball.AngularMomentum.y*ball.AngularMomentum.y) / math.max(mag, mag2); + + if (!float.IsNaN(threshold) && !float.IsInfinity(threshold) && threshold > 666) { + var damp = math.clamp(1.0f - (threshold - 666) / 10000, 0.23f, 1); // do not kill spin completely, otherwise stuck balls will happen during regular gameplay + ball.AngularMomentum *= damp; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackSystem.cs deleted file mode 100644 index 96b8ab4a1..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallSpinHackSystem.cs +++ /dev/null @@ -1,55 +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.Entities; -using Unity.Mathematics; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - [DisableAutoCreation] - internal class BallSpinHackSystem : SystemBase - { - protected override void OnUpdate() - { - var lastPositionBuffer = GetBufferFromEntity(true); - Entities - .WithReadOnly(lastPositionBuffer) - .ForEach((Entity entity, ref BallData ball, in CollisionEventData collEvent) => - { - - var lastPos = lastPositionBuffer[entity]; - var p0 = (ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime) + 1) % BallRingCounterSystem.MaxBallTrailPos; - var p1 = (ball.RingCounterOldPos / (10000 / PhysicsConstants.PhysicsStepTime) + 2) % BallRingCounterSystem.MaxBallTrailPos; - - // only if already initialized - if (collEvent.HitDistance < PhysicsConstants.PhysTouch && lastPos[p0].Value.x != Constants.FloatMax && lastPos[p1].Value.x != float.MaxValue) { - var diffPos = lastPos[p0].Value - ball.Position; - var mag = diffPos.x*diffPos.x + diffPos.y*diffPos.y; - var diffPos2 = lastPos[p1].Value - ball.Position; - var mag2 = diffPos2.x*diffPos2.x + diffPos2.y*diffPos2.y; - var threshold = (ball.AngularMomentum.x*ball.AngularMomentum.x + ball.AngularMomentum.y*ball.AngularMomentum.y) / math.max(mag, mag2); - - if (!float.IsNaN(threshold) && !float.IsInfinity(threshold) && threshold > 666) { - var damp = math.clamp(1.0f - (threshold - 666) / 10000, 0.23f, 1); // do not kill spin completely, otherwise stuck balls will happen during regular gameplay - ball.AngularMomentum *= damp; - } - } - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs similarity index 75% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs index 6afbe306e..efef6c68b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs @@ -14,13 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - // todo split this into at least 2 components - internal struct BallData : IComponentData + internal struct BallState { public int Id; public float3 Position; @@ -60,6 +58,10 @@ internal struct BallData : IComponentData public float3 OldVelocity; + public CollisionEventData CollisionEvent; + + public BallPositions LastPositions; + public Aabb Aabb { get { var vl = math.length(Velocity) + Radius + 0.05f; // 0.05f = paranoia @@ -74,19 +76,6 @@ public Aabb Aabb { } } - public BallColliderBounds Bounds(Entity entity) - { - var vl = math.length(Velocity) + Radius + 0.05f; // 0.05f = paranoia - return new BallColliderBounds(entity, new Aabb( - Position.x - vl, - Position.x + vl, - Position.y - vl, - Position.y + vl, - Position.z - vl, - Position.z + vl - )); - } - public float CollisionRadiusSqr { get { var v1 = math.length(Velocity) + Radius + 0.05f; @@ -108,7 +97,7 @@ public void ApplySurfaceImpulse(in float3 rotI, in float3 impulse) AngularMomentum += rotI; } - public static float3 SurfaceVelocity(in BallData ball, in float3 surfP) + public static float3 SurfaceVelocity(in BallState ball, in float3 surfP) { // linear velocity plus tangential velocity due to rotation return ball.Velocity + math.cross(ball.AngularMomentum / ball.Inertia, surfP); @@ -126,7 +115,7 @@ Original code from VPX */ } - public static float3 SurfaceAcceleration(in BallData ball, in float3 surfP, in float3 gravity) + public static float3 SurfaceAcceleration(in BallState ball, in float3 surfP, in float3 gravity) { var currentAngularVelocity = ball.AngularMomentum / ball.Inertia; @@ -147,35 +136,7 @@ the angular velocity used here is not the angular velocity that the ball has (wh */ } - public static void SetOutsideOf(ref DynamicBuffer insideOfs, in Entity entity) - { - for (var i = 0; i < insideOfs.Length; i++) { - if (insideOfs[i].Value == entity) { - insideOfs.RemoveAt(i); - return; - } - } - } - - public static void SetInsideOf(ref DynamicBuffer insideOfs, Entity entity) - { - insideOfs.Add(new BallInsideOfBufferElement { Value = entity }); - } - - public static bool IsOutsideOf(in DynamicBuffer insideOfs, in Entity entity) - { - return !IsInsideOf(in insideOfs, in entity); - } - - public static bool IsInsideOf(in DynamicBuffer insideOfs, in Entity entity) - { - for (var i = 0; i < insideOfs.Length; i++) { - if (insideOfs[i].Value == entity) { - return true; - } - } - return false; - } + public void UpdateVelocities(float3 gravity) => BallVelocityPhysics.UpdateVelocities(ref this, gravity); public override string ToString() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs.meta similarity index 95% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs.meta index fc73b3430..e4990103a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallData.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 40e389a41e9b61d419678ddd9a131c51 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 40e389a41e9b61d419678ddd9a131c51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs similarity index 51% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocitySystem.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs index 4986b510d..287f9e134 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocitySystem.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs @@ -14,34 +14,31 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; +// ReSharper disable CompareOfFloatsByEqualityOperator + using Unity.Mathematics; -using Unity.Profiling; using VisualPinball.Engine.Common; namespace VisualPinball.Unity { - [UpdateInGroup(typeof(UpdateVelocitiesSystemGroup))] - internal class SpinnerVelocitySystem : SystemBase + internal static class BallVelocityPhysics { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("SpinnerVelocitySystem"); - - protected override void OnUpdate() + public static void UpdateVelocities(ref BallState ball, float3 gravity) { - var marker = PerfMarker; - Entities - .WithName("SpinnerVelocityJob") - .ForEach((ref SpinnerMovementData movementData, in SpinnerStaticData data) => { - - marker.Begin(); - - // Center of gravity towards bottom of object, makes it stop vertical - movementData.AngleSpeed -= math.sin(movementData.Angle) * (float)(0.0025 * PhysicsConstants.PhysFactor); - movementData.AngleSpeed *= data.Damping; - - marker.End(); + if (ball.IsFrozen) { + return; + } - }).Run(); + if (ball.ManualControl) { + ball.Velocity *= 0.5f; // Null out most of the X/Y velocity, want a little bit so the ball can sort of find its way out of obstacles. + ball.Velocity += new float3( + math.max(-10.0f, math.min(10.0f, (ball.ManualPosition.x - ball.Position.x) * (float)(1.0/10.0))), + math.max(-10.0f, math.min(10.0f, (ball.ManualPosition.y - ball.Position.y) * (float)(1.0/10.0))), + -2.0f + ); + } else { + ball.Velocity += (float)PhysicsConstants.PhysFactor * gravity; + } } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs.meta new file mode 100644 index 000000000..a5c236f6f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocityPhysics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d897220282d842e987275304b9cacad9 +timeCreated: 1679264641 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs deleted file mode 100644 index 6667ee1c9..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs +++ /dev/null @@ -1,66 +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 CompareOfFloatsByEqualityOperator - -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateVelocitiesSystemGroup))] - internal class BallVelocitySystem : SystemBase - { - private float3 _gravity; - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BallVelocitySystem"); - - protected override void OnStartRunning() - { - _gravity = Object.FindObjectOfType().Gravity; - } - - protected override void OnUpdate() - { - var gravity = _gravity; - var marker = PerfMarker; - Entities.WithName("BallVelocityJob").ForEach((ref BallData ball) => { - - if (ball.IsFrozen) { - return; - } - - marker.Begin(); - - if (ball.ManualControl) { - ball.Velocity *= 0.5f; // Null out most of the X/Y velocity, want a little bit so the ball can sort of find its way out of obstacles. - ball.Velocity += new float3( - math.max(-10.0f, math.min(10.0f, (ball.ManualPosition.x - ball.Position.x) * (float)(1.0/10.0))), - math.max(-10.0f, math.min(10.0f, (ball.ManualPosition.y - ball.Position.y) * (float)(1.0/10.0))), - -2.0f - ); - } else { - ball.Velocity += gravity * (float)PhysicsConstants.PhysFactor; - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs.meta deleted file mode 100644 index 527312329..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallVelocitySystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: aa2bbeac528b2614e96ff611feea8491 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs index 496e4441c..1a3dc9149 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Bumper; @@ -40,8 +39,7 @@ public class BumperApi : CollidableApi public event EventHandler Switch; - public BumperApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + public BumperApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -58,11 +56,11 @@ public BumperApi(GameObject go, Entity entity, Player player) void IApiSwitch.RemoveWireDest(string destId) => RemoveWireDest(destId); void IApiCoil.OnCoil(bool enabled) { - if (enabled) { - var ringAnimation = EntityManager.GetComponentData(Entity); - ringAnimation.IsHit = true; - EntityManager.SetComponentData(Entity, ringAnimation); + if (!enabled) { + return; } + ref var bumperState = ref PhysicsEngine.BumperState(ItemId); + bumperState.RingAnimation.IsHit = true; } void IApiWireDest.OnChange(bool enabled) => (this as IApiCoil).OnCoil(enabled); @@ -74,7 +72,7 @@ void IApiCoil.OnCoil(bool enabled) protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var height = MainComponent.PositionZ; colliders.Add(new CircleCollider(MainComponent.Position, MainComponent.Radius, height, @@ -95,10 +93,10 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool isUnHit) + void IApiHittable.OnHit(int ballId, bool isUnHit) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(!isUnHit, ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(!isUnHit, ballId)); OnSwitch(true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs index bf24d7687..25847bf6e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperCollider.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Game; @@ -23,26 +22,26 @@ namespace VisualPinball.Unity { internal static class BumperCollider { - public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, - ref CollisionEventData collEvent, ref BumperRingAnimationData ringData, ref BumperSkirtAnimationData skirtData, - in Entity ballEntity, in Collider collider, in BumperStaticData data, ref Random random) + public static void Collide(ref BallState ball, ref NativeQueue.ParallelWriter events, + ref CollisionEventData collEvent, ref BumperRingAnimationState ringState, ref BumperSkirtAnimationState skirtState, + in ColliderHeader collHeader, in BumperStaticState state, ref Random random) { // todo // if (!m_enabled) return; var dot = math.dot(collEvent.HitNormal, ball.Velocity); // needs to be computed before Collide3DWall()! - var material = collider.Material; + var material = collHeader.Material; BallCollider.Collide3DWall(ref ball, in material, in collEvent, in collEvent.HitNormal, ref random); // reflect ball from wall - if (data.HitEvent && dot <= -data.Threshold) { // if velocity greater than threshold level + if (state.HitEvent && dot <= -state.Threshold) { // if velocity greater than threshold level - ball.Velocity += collEvent.HitNormal * data.Force; // add a chunk of velocity to drive ball away + ball.Velocity += collEvent.HitNormal * state.Force; // add a chunk of velocity to drive ball away - ringData.IsHit = true; - skirtData.HitEvent = true; - skirtData.BallPosition = ball.Position; + ringState.IsHit = true; + skirtState.HitEvent = true; + skirtState.BallPosition = ball.Position; - events.Enqueue(new EventData(EventId.HitEventsHit, collider.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.ItemId, ball.Id, true)); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs index 89c39eee6..a3f19df37 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Bumper; @@ -45,8 +43,8 @@ public class BumperColliderComponent : ColliderComponent new BumperApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.BumperApi ?? new BumperApi(gameObject, player, physicsEngine); public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(scatterAngleDeg: Scatter); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs index 326c34b6f..c71ece42a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperComponent.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Game.Engines; @@ -35,7 +34,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Bumper")] public class BumperComponent : MainRenderableComponent, - ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent { #region Data @@ -84,6 +83,24 @@ public class BumperComponent : MainRenderableComponent, #endregion + #region Runtime + + public BumperApi BumperApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + BumperApi = new BumperApi(gameObject, player, physicsEngine); + + player.Register(BumperApi, this); + if (GetComponentInChildren()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + #region Wiring public IEnumerable AvailableSwitches => new[] { @@ -133,55 +150,6 @@ public override void UpdateTransforms() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // physics collision data - var collComponent = GetComponentInChildren(); - if (collComponent) { - dstManager.AddComponentData(entity, new BumperStaticData { - Force = collComponent.Force, - HitEvent = collComponent.HitEvent, - Threshold = collComponent.Threshold - }); - } - - // skirt animation data - if (GetComponentInChildren()) { - dstManager.AddComponentData(entity, new BumperSkirtAnimationData { - BallPosition = default, - AnimationCounter = 0f, - DoAnimate = false, - DoUpdate = false, - EnableAnimation = true, - Rotation = new float2(0, 0), - Center = Position - }); - } - - // ring animation data - var ringAnimComponent = GetComponentInChildren(); - if (ringAnimComponent) { - dstManager.AddComponentData(entity, new BumperRingAnimationData { - - // dynamic - IsHit = false, - Offset = 0, - AnimateDown = false, - DoAnimate = false, - - // static - DropOffset = ringAnimComponent.RingDropOffset, - HeightScale = HeightScale, - Speed = ringAnimComponent.RingSpeed, - }); - } - - // register at player - GetComponentInParent().RegisterBumper(this, entity); - } - public override IEnumerable SetData(BumperData data) { var updatedComponents = new List { this }; @@ -336,6 +304,61 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal BumperState CreateState() + { + // physics collision data + var collComponent = GetComponentInChildren(); + var staticData = collComponent + ? new BumperStaticState { + Force = collComponent.Force, + HitEvent = collComponent.HitEvent, + Threshold = collComponent.Threshold + } : default; + + // skirt animation data + var skirtAnimComponent = GetComponentInChildren(); + var skirtAnimation = skirtAnimComponent + ? new BumperSkirtAnimationState { + BallPosition = default, + AnimationCounter = 0f, + DoAnimate = false, + DoUpdate = false, + EnableAnimation = true, + Rotation = new float2(0, 0), + Center = Position + } : default; + + // ring animation data + var ringAnimComponent = GetComponentInChildren(); + var ringAnimation = ringAnimComponent + ? new BumperRingAnimationState { + + // dynamic + IsHit = false, + Offset = 0, + AnimateDown = false, + DoAnimate = false, + + // static + DropOffset = ringAnimComponent.RingDropOffset, + HeightScale = HeightScale, + Speed = ringAnimComponent.RingSpeed, + } : default; + + return new BumperState( + collComponent ? gameObject.GetInstanceID() : 0, + skirtAnimComponent ? skirtAnimComponent.gameObject.GetInstanceID() : 0, + ringAnimComponent ? ringAnimComponent.gameObject.GetInstanceID() : 0, + staticData, + ringAnimation, + skirtAnimation + ); + } + + #endregion + #region Editor Tooling public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs new file mode 100644 index 000000000..0dc629b8b --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs @@ -0,0 +1,51 @@ +// 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 +{ + internal static class BumperRingAnimation + { + internal static void Update(ref BumperRingAnimationState state, float dTime) + { + // todo visibility - skip if invisible + + var limit = state.DropOffset + state.HeightScale * 0.5f; + if (state.IsHit) { + state.DoAnimate = true; + state.AnimateDown = true; + state.IsHit = false; + } + if (state.DoAnimate) { + var step = state.Speed; + if (state.AnimateDown) { + step = -step; + } + state.Offset += step * dTime; + if (state.AnimateDown) { + if (state.Offset <= -limit) { + state.Offset = -limit; + state.AnimateDown = false; + } + } else { + if (state.Offset >= 0.0f) { + state.Offset = 0.0f; + state.DoAnimate = false; + } + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs index dbbffb1fe..af9f11efc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct BumperRingAnimationData : IComponentData + internal struct BumperRingAnimationState { public bool IsHit; public float DropOffset; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs.meta similarity index 95% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs.meta index 94d637600..1e1d856e5 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationData.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: f247ceaae40d7224281f631a0fb7901c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: f247ceaae40d7224281f631a0fb7901c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationSystem.cs deleted file mode 100644 index 80bc689b5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingAnimationSystem.cs +++ /dev/null @@ -1,70 +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.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class BumperRingAnimationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BumperRingAnimationSystem"); - - protected override void OnUpdate() - { - var dTime = Time.DeltaTime * 1000; - var marker = PerfMarker; - - Entities - .WithName("BumperRingAnimationJob") - .ForEach((ref BumperRingAnimationData data) => { - - // todo visibility - skip if invisible - - marker.Begin(); - - var limit = data.DropOffset + data.HeightScale * 0.5f; - if (data.IsHit) { - data.DoAnimate = true; - data.AnimateDown = true; - data.IsHit = false; - } - if (data.DoAnimate) { - var step = data.Speed; - if (data.AnimateDown) { - step = -step; - } - data.Offset += step * dTime; - if (data.AnimateDown) { - if (data.Offset <= -limit) { - data.Offset = -limit; - data.AnimateDown = false; - } - } else { - if (data.Offset >= 0.0f) { - data.Offset = 0.0f; - data.DoAnimate = false; - } - } - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingMovementSystem.cs deleted file mode 100644 index 5771ff2e2..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingMovementSystem.cs +++ /dev/null @@ -1,64 +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 Unity.Entities; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class BumperRingMovementSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BumperRingMovementSystem"); - - private Player _player; - - private readonly Dictionary _initialOffset = new(); - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("BumperRingMovementJob").ForEach((Entity entity, in BumperRingAnimationData data) => { - - marker.Begin(); - - var transform = _player.BumperRingTransforms[entity]; - if (!_initialOffset.ContainsKey(entity)) { - _initialOffset[entity] = transform.position.y; - } - - var limit = data.DropOffset + data.HeightScale * 0.5f; - var localLimit = _initialOffset[entity] + limit; - var localOffset = localLimit / limit * data.Offset; - - var worldPos = transform.position; - worldPos.y = _initialOffset[entity] + Physics.ScaleToWorld(localOffset); - transform.position = worldPos; - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs new file mode 100644 index 000000000..3e331db4a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs @@ -0,0 +1,78 @@ +// 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 +{ + internal static class BumperSkirtAnimation + { + internal static void Update(ref BumperSkirtAnimationState state, float dTime) + { + // todo visibility - skip if invisible + + var isHit = state.HitEvent; + state.HitEvent = false; + + if (state.EnableAnimation) { + if (isHit) { + state.DoAnimate = true; + UpdateSkirt(ref state); + state.AnimationCounter = 0.0f; + } + if (state.DoAnimate) { + state.AnimationCounter += dTime; + if (state.AnimationCounter > 160.0f) { + state.DoAnimate = false; + ResetSkirt(ref state); + } + } + } else if (state.DoUpdate) { // do a single update if the animation was turned off via script + state.DoUpdate = false; + ResetSkirt(ref state); + } + } + + private static void UpdateSkirt(ref BumperSkirtAnimationState state) + { + const float skirtTilt = 5.0f; + + var hitX = state.BallPosition.x; + var hitY = state.BallPosition.y; + var dy = math.abs(hitY - state.Center.y); + if (dy == 0.0f) { + dy = 0.000001f; + } + + var dx = math.abs(hitX - state.Center.x); + var skirtA = math.atan(dx / dy); + state.Rotation.x = math.cos(skirtA) * skirtTilt; + state.Rotation.y = math.sin(skirtA) * skirtTilt; + if (state.Center.y < hitY) { + state.Rotation.x = -state.Rotation.x; + } + + if (state.Center.x > hitX) { + state.Rotation.y = -state.Rotation.y; + } + } + + private static void ResetSkirt(ref BumperSkirtAnimationState state) + { + state.Rotation = new float2(0, 0); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs index 0ea2c9e34..f61274231 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct BumperSkirtAnimationData : IComponentData + internal struct BumperSkirtAnimationState { // dynamic public bool HitEvent; @@ -32,6 +31,5 @@ internal struct BumperSkirtAnimationData : IComponentData // static public float2 Center; - } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs.meta similarity index 95% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs.meta index 4d8f62fd4..56fcb823b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationData.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 763cf045db0d0444b9ba0b9c074711e3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 763cf045db0d0444b9ba0b9c074711e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationSystem.cs deleted file mode 100644 index bd0bbad1c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtAnimationSystem.cs +++ /dev/null @@ -1,96 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class BumperSkirtAnimationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BumperSkirtAnimationSystem"); - - protected override void OnUpdate() - { - var dTime = Time.DeltaTime * 1000; - var marker = PerfMarker; - - Entities - .WithName("BumperSkirtAnimationJob") - .ForEach((ref BumperSkirtAnimationData data) => { - - // todo visibility - skip if invisible - - marker.Begin(); - - var isHit = data.HitEvent; - data.HitEvent = false; - - if (data.EnableAnimation) { - if (isHit) { - data.DoAnimate = true; - UpdateSkirt(ref data); - data.AnimationCounter = 0.0f; - } - if (data.DoAnimate) { - data.AnimationCounter += dTime; - if (data.AnimationCounter > 160.0f) { - data.DoAnimate = false; - ResetSkirt(ref data); - } - } - } else if (data.DoUpdate) { // do a single update if the animation was turned off via script - data.DoUpdate = false; - ResetSkirt(ref data); - } - - marker.End(); - - }).Run(); - } - - private static void UpdateSkirt(ref BumperSkirtAnimationData data) - { - const float skirtTilt = 5.0f; - - var hitX = data.BallPosition.x; - var hitY = data.BallPosition.y; - var dy = math.abs(hitY - data.Center.y); - if (dy == 0.0f) { - dy = 0.000001f; - } - - var dx = math.abs(hitX - data.Center.x); - var skirtA = math.atan(dx / dy); - data.Rotation.x = math.cos(skirtA) * skirtTilt; - data.Rotation.y = math.sin(skirtA) * skirtTilt; - if (data.Center.y < hitY) { - data.Rotation.x = -data.Rotation.x; - } - - if (data.Center.x > hitX) { - data.Rotation.y = -data.Rotation.y; - } - } - - private static void ResetSkirt(ref BumperSkirtAnimationData data) - { - data.Rotation = new float2(0, 0); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs deleted file mode 100644 index fead826ca..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs +++ /dev/null @@ -1,53 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class BumperSkirtMovementSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("BumperSkirtMovementSystem"); - - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("BumperSkirtMovementJob").ForEach((Entity entity, in BumperSkirtAnimationData data) => { - - marker.Begin(); - - var transform = _player.BumperSkirtTransforms[entity]; - var parentRotation = transform.parent.rotation; - transform.rotation = Quaternion.Euler(data.Rotation.x, 0, -data.Rotation.y) * parentRotation; - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs.meta deleted file mode 100644 index 386e8a21e..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperSkirtMovementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3890dfd771cce314a8949d8cc9bc5cf7 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs new file mode 100644 index 000000000..1e2de8b9f --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs @@ -0,0 +1,39 @@ +// 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 +{ + internal struct BumperState + { + internal readonly int ItemId; + internal readonly int SkirtItemId; + internal int RingItemId; + internal BumperStaticState Static; + internal BumperRingAnimationState RingAnimation; + internal BumperSkirtAnimationState SkirtAnimation; + + public BumperState(int itemId, int skirtItemId, int ringItemId, BumperStaticState @static, + BumperRingAnimationState ringAnimation, BumperSkirtAnimationState skirtAnimation) + { + ItemId = itemId; + SkirtItemId = skirtItemId; + RingItemId = ringItemId; + Static = @static; + RingAnimation = ringAnimation; + SkirtAnimation = skirtAnimation; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs.meta new file mode 100644 index 000000000..8cad9513c --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 84922bfe360d4d3598dbf85749eb2173 +timeCreated: 1696418720 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs index b80e2430d..e992caa22 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct BumperStaticData : IComponentData + internal struct BumperStaticState { public float Force; public float Threshold; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs.meta similarity index 95% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs.meta index 677faf6d7..aecfefd22 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticData.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperStaticState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 7e0c88d47ab1257499977c31f15fb37b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 7e0c88d47ab1257499977c31f15fb37b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs new file mode 100644 index 000000000..62f84f244 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.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 . + +using System.Collections.Generic; +using UnityEngine; + +namespace VisualPinball.Unity +{ + /// + /// Applies the state to the scene, aka the transform of the game objects. + /// + internal static class BumperTransform + { + private static readonly Dictionary InitialOffset = new(); + + internal static void UpdateRing(int itemId, in BumperRingAnimationState state, Transform transform) + { + var worldPos = transform.position; + InitialOffset.TryAdd(itemId, worldPos.y); + + var limit = state.DropOffset + state.HeightScale * 0.5f; + var localLimit = InitialOffset[itemId] + limit; + var localOffset = localLimit / limit * state.Offset; + + worldPos.y = InitialOffset[itemId] + Physics.ScaleToWorld(localOffset); + transform.position = worldPos; + } + + internal static void UpdateSkirt(in BumperSkirtAnimationState state, Transform transform) + { + var parentRotation = transform.parent.rotation; + transform.rotation = Quaternion.Euler(state.Rotation.x, 0, -state.Rotation.y) * parentRotation; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperRingMovementSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Bumper/BumperTransform.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs index e752663ab..6f6ec848c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollidableApi.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.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT; @@ -27,49 +25,30 @@ public abstract class CollidableApi : I where TCollidableComponent : ColliderComponent where TData : ItemData { - public bool IsCollidable { - get => _simulateCycleSystemGroup != null && _simulateCycleSystemGroup.ItemsColliding[Entity]; - set { - if (_simulateCycleSystemGroup != null) { - _simulateCycleSystemGroup.ItemsColliding[Entity] = value; - } - } - } - - protected readonly Entity Entity; + protected readonly int ItemId; protected readonly TCollidableComponent ColliderComponent; - private protected EntityManager EntityManager; - private readonly SimulateCycleSystemGroup _simulateCycleSystemGroup; - - protected CollidableApi(GameObject go, Entity entity, Player player) : base(go, player) + protected CollidableApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { - if (World.DefaultGameObjectInjectionWorld != null) { - _simulateCycleSystemGroup = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); - } - EntityManager = World.DefaultGameObjectInjectionWorld != null ? World.DefaultGameObjectInjectionWorld.EntityManager : default; - Entity = entity; - + ItemId = MainComponent.gameObject.GetInstanceID(); ColliderComponent = go.GetComponent(); } #region Collider bool IApiColliderGenerator.IsColliderAvailable => ColliderComponent; - bool IApiColliderGenerator.IsColliderEnabled => ColliderComponent && ColliderComponent.isActiveAndEnabled; - Entity IApiColliderGenerator.ColliderEntity => Entity; protected virtual bool FireHitEvents => false; protected virtual float HitThreshold => 0; - protected abstract void CreateColliders(List colliders, float margin); + protected abstract void CreateColliders(ref ColliderReference colliders, float margin); - void IApiColliderGenerator.CreateColliders(List colliders, float margin) + void IApiColliderGenerator.CreateColliders(ref ColliderReference colliders, float margin) { if (!ColliderComponent) { return; } - CreateColliders(colliders, margin); + CreateColliders(ref colliders, margin); } ColliderInfo IApiColliderGenerator.GetColliderInfo() => GetColliderInfo(MainComponent.ItemType); @@ -79,11 +58,10 @@ void IApiColliderGenerator.CreateColliders(List colliders, float marg public ColliderInfo GetColliderInfo(ItemType itemType) { return new ColliderInfo { - Id = -1, + Id = -1, // is set during allocation + ItemId = ItemId, ItemType = itemType, - Entity = Entity, FireEvents = FireHitEvents, - IsEnabled = ColliderComponent && ColliderComponent.isActiveAndEnabled, Material = ColliderComponent.PhysicsMaterialData, HitThreshold = HitThreshold, }; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs index b656ead52..e03a448f0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ColliderComponent.cs @@ -19,9 +19,11 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; -using Unity.Mathematics; +using NativeTrees; using Unity.Collections; +using Unity.Jobs; +using Unity.Mathematics; +using UnityEditor; using UnityEngine; using UnityEngine.Profiling; using VisualPinball.Engine.VPT; @@ -32,7 +34,7 @@ namespace VisualPinball.Unity { [DisallowMultipleComponent] public abstract class ColliderComponent : SubComponent, - IColliderComponent + IColliderComponent, ICollidableComponent where TData : ItemData where TMainComponent : MainComponent { @@ -45,6 +47,9 @@ public abstract class ColliderComponent : SubComponent
: SubComponent _collidersDirty = value; } - private readonly Entity _colliderEntity = Player.PlayfieldEntity; - [NonSerialized] private Mesh _colliderMesh; [NonSerialized] private readonly List _nonMeshColliders = new List(); [NonSerialized] private bool _collidersDirty; - protected abstract IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity); + protected abstract IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine); public abstract PhysicsMaterialData PhysicsMaterialData { get; } @@ -70,6 +73,16 @@ private void Start() // make enable checkbox visible } + private void OnEnable() + { + GetComponentInParent()?.EnableCollider(MainComponent.gameObject.GetInstanceID()); + } + + private void OnDisable() + { + GetComponentInParent()?.DisableCollider(MainComponent.gameObject.GetInstanceID()); + } + protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, float elasticityFalloff = 1f, float friction = 0f, float scatterAngleDeg = 0f, bool overwrite = true) { @@ -95,7 +108,9 @@ protected PhysicsMaterialData GetPhysicsMaterialData(float elasticity = 1f, floa }; } - #if UNITY_EDITOR + #region Collider Gizmos + +#if UNITY_EDITOR private void OnDrawGizmos() { @@ -105,7 +120,7 @@ private void OnDrawGizmos() var overrideColliderMesh = playfieldColliderComponent && playfieldColliderComponent.ShowAllColliderMeshes; var showColliders = ShowColliderMesh || overrideColliderMesh; - if (!(ShowGizmos || overrideColliderMesh) || !ShowAabbs && !showColliders) { + if (!(ShowGizmos || overrideColliderMesh) || !ShowAabbs && !showColliders && !ShowColliderOctree) { Profiler.EndSample(); return; } @@ -117,16 +132,16 @@ private void OnDrawGizmos() } var ltw = GetComponentInParent().transform.localToWorldMatrix; Gizmos.matrix = ltw * (Matrix4x4)Physics.VpxToWorld; - UnityEditor.Handles.matrix = Gizmos.matrix; + Handles.matrix = Gizmos.matrix; var generateColliders = ShowAabbs || showColliders && !HasCachedColliders; if (generateColliders) { - var api = InstantiateColliderApi(player, _colliderEntity); - var colliders = new List(); - api.CreateColliders(colliders, 0.1f); + var api = InstantiateColliderApi(player, null); + var colliders = new ColliderReference(Allocator.TempJob); + api.CreateColliders(ref colliders, 0.1f); if (showColliders) { - _colliderMesh = GenerateColliderMesh(colliders); + _colliderMesh = GenerateColliderMesh(ref colliders); _collidersDirty = false; } @@ -136,12 +151,31 @@ private void OnDrawGizmos() DrawAabb(col.Bounds.Aabb, i == SelectedCollider); } } + colliders.Dispose(); + } + if (ShowColliderOctree) { + + var api = InstantiateColliderApi(player, null); + var colliders = new ColliderReference(Allocator.TempJob); + api.CreateColliders(ref colliders, 0.1f); + + var playfieldBounds = GetComponentInChildren().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(); + colliders.Dispose(); } if (showColliders) { var color = Color.green; - UnityEditor.Handles.color = color; + Handles.color = color; color.a = 0.3f; Gizmos.color = color; Gizmos.DrawMesh(_colliderMesh); @@ -153,86 +187,54 @@ private void OnDrawGizmos() } Gizmos.matrix = Matrix4x4.identity; - UnityEditor.Handles.matrix = Matrix4x4.identity; + Handles.matrix = Matrix4x4.identity; Profiler.EndSample(); } - #region Collider Gizmos - - private Mesh GenerateColliderMesh(List colliders) + private Mesh GenerateColliderMesh(ref ColliderReference colliders) { var color = Color.green; - UnityEditor.Handles.color = color; + Handles.color = color; color.a = 0.3f; Gizmos.color = color; var vertices = new List(); var normals = new List(); var indices = new List(); _nonMeshColliders.Clear(); - foreach (var col in colliders) { - switch (col) { - - case CircleCollider circleCol: { - AddCollider(circleCol, vertices, normals, indices); - break; - } - - case FlipperCollider _: { - AddFlipperCollider(vertices, normals, indices); - break; - } - - case GateCollider gateCol: { - AddCollider(gateCol.LineSeg0, vertices, normals, indices); - AddCollider(gateCol.LineSeg1, vertices, normals, indices); - break; - } - - case Line3DCollider line3DCol: { - // todo - break; - } - - case LineCollider lineCol: { - AddCollider(lineCol, vertices, normals, indices); - break; - } - - case LineSlingshotCollider lineSlingshotCol: { - AddCollider(lineSlingshotCol, vertices, normals, indices); - break; - } - - case LineZCollider lineZCol: { - _nonMeshColliders.Add(lineZCol); - break; - } - - case PlungerCollider plungerCol: { - AddCollider(plungerCol.LineSegBase, vertices, normals, indices); - AddCollider(plungerCol.JointBase0, vertices, normals, indices); - AddCollider(plungerCol.JointBase1, vertices, normals, indices); - break; - } - - case PointCollider pointCol: { - // ignoring points for now - break; - } - - case SpinnerCollider spinnerCol: { - AddCollider(spinnerCol.LineSeg0, vertices, normals, indices); - AddCollider(spinnerCol.LineSeg1, vertices, normals, indices); - break; - } - - case TriangleCollider triangleCol: { - AddCollider(triangleCol, vertices, normals, indices); - break; - } - } + foreach (var col in colliders.CircleColliders) { + AddCollider(col, vertices, normals, indices); + } + foreach (var _ in colliders.FlipperColliders) { + AddFlipperCollider(vertices, normals, indices); + } + foreach (var col in colliders.GateColliders) { + AddCollider(col.LineSeg0, vertices, normals, indices); + AddCollider(col.LineSeg1, vertices, normals, indices); + } + foreach (var col in colliders.LineColliders) { + AddCollider(col, vertices, normals, indices); + } + foreach (var col in colliders.LineSlingshotColliders) { + AddCollider(col, vertices, normals, indices); + } + foreach (var col in colliders.LineZColliders) { + _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); + } + foreach (var col in colliders.SpinnerColliders) { + AddCollider(col.LineSeg0, vertices, normals, indices); + AddCollider(col.LineSeg1, vertices, normals, indices); + } + foreach (var col in colliders.TriangleColliders) { + AddCollider(col, vertices, normals, indices); + } + + // todo Line3DCollider return new Mesh { name = $"{name} (debug collider)", @@ -313,7 +315,7 @@ private static void AddCollider(CircleCollider circleCol, IList vertice private static void DrawLine(Vector3 p1, Vector3 p2) { const int thickness = 10; - UnityEditor.Handles.DrawAAPolyLine(thickness, p1, p2); + Handles.DrawAAPolyLine(thickness, p1, p2); } private static void AddCollider(LineZCollider lineZCol, ICollection vertices, ICollection normals, ICollection indices) @@ -428,9 +430,17 @@ private static void DrawAabb(Aabb aabb, bool isSelected) Gizmos.DrawWireCube(aabb.Center, aabb.Size); } +#endif + #endregion - #endif + void ICollidableComponent.GetColliders(Player player, ref ColliderReference colliders, float margin) + { + InstantiateColliderApi(player, null).CreateColliders(ref colliders, margin); + } + int ICollidableComponent.ItemId => MainComponent.gameObject.GetInstanceID(); + bool ICollidableComponent.IsCollidable => isActiveAndEnabled; + } internal static class ColliderColor diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchApi.cs index 070322300..5ef223522 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchApi.cs @@ -43,12 +43,12 @@ public class CollisionSwitchApi : IApi, IApiSwitch, IApiSwitchDevice public bool IsHittable => _hittable != null; - internal CollisionSwitchApi(GameObject go, Player player) + internal CollisionSwitchApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _collisionSwitchComponent = go.GetComponentInChildren(); _player = player; - _switchHandler = new SwitchHandler(go.name, player); + _switchHandler = new SwitchHandler(go.name, player, physicsEngine); } void IApi.OnInit(BallManager ballManager) @@ -67,7 +67,7 @@ void IApi.OnInit(BallManager ballManager) private void OnHit(object sender, HitEventArgs e) { - Switch?.Invoke(this, new SwitchEventArgs(true, e.BallEntity)); + Switch?.Invoke(this, new SwitchEventArgs(true, e.BallId)); OnSwitch(true); } @@ -81,4 +81,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchComponent.cs index 882c99989..0eebc8fcc 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/CollisionSwitch/CollisionSwitchComponent.cs @@ -37,9 +37,14 @@ public class CollisionSwitchComponent : MonoBehaviour, ISwitchDeviceComponent #region Runtime + public CollisionSwitchApi CollisionSwitchApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterCollisionSwitchComponent(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + CollisionSwitchApi = new CollisionSwitchApi(gameObject, player, physicsEngine); + player.Register(CollisionSwitchApi, this); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankApi.cs index e01a7c1ee..498b264db 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankApi.cs @@ -28,6 +28,7 @@ public class DropTargetBankApi : IApi, IApiCoilDevice, IApiSwitchDevice private readonly DropTargetBankComponent _dropTargetBankComponent; private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; private readonly List _dropTargetApis = new List(); public DeviceSwitch SequenceCompletedSwitch; @@ -56,15 +57,16 @@ private IApiCoil Coil(string deviceItem) }; } - internal DropTargetBankApi(GameObject go, Player player) + internal DropTargetBankApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _dropTargetBankComponent = go.GetComponentInChildren(); _player = player; + _physicsEngine = physicsEngine; } void IApi.OnInit(BallManager ballManager) { - SequenceCompletedSwitch = new DeviceSwitch(DropTargetBankComponent.SequenceCompletedSwitchItem, false, SwitchDefault.NormallyOpen, _player); + SequenceCompletedSwitch = new DeviceSwitch(DropTargetBankComponent.SequenceCompletedSwitchItem, false, SwitchDefault.NormallyOpen, _player, _physicsEngine); ResetCoil = new DeviceCoil(_player, OnResetCoilEnabled); for (var index = 0; index < _dropTargetBankComponent.BankSize; index++) { @@ -112,4 +114,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankComponent.cs index 73f7e40fc..b030c0a8e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/DropTargetBank/DropTargetBankComponent.cs @@ -60,9 +60,14 @@ public class DropTargetBankComponent : MonoBehaviour, ICoilDeviceComponent, ISwi #region Runtime + public DropTargetBankApi DropTargetBankApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterDropTargetBankComponent(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + DropTargetBankApi = new DropTargetBankApi(gameObject, player, physicsEngine); + player.Register(DropTargetBankApi, this); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs index 274f76534..41c14ea55 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/EventArgs.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 Unity.Entities; - namespace VisualPinball.Unity { @@ -33,23 +31,23 @@ public struct RotationEventArgs public struct HitEventArgs { - public Entity BallEntity; + public int BallId; - public HitEventArgs(Entity ballEntity) + public HitEventArgs(int ballId) { - BallEntity = ballEntity; + BallId = ballId; } } public readonly struct SwitchEventArgs { public readonly bool IsEnabled; - public readonly Entity BallEntity; + public readonly int BallId; - public SwitchEventArgs(bool isEnabled, Entity ballEntity) + public SwitchEventArgs(bool isEnabled, int ballId = 0) { IsEnabled = isEnabled; - BallEntity = ballEntity; + BallId = ballId; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs index fe8fc21e5..27c95e65a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperApi.cs @@ -18,11 +18,8 @@ #pragma warning disable 67 using System; -using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; -using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT.Flipper; namespace VisualPinball.Unity @@ -71,8 +68,7 @@ public class FlipperApi : CollidableApi public void RotateToEnd() { - EngineProvider.Get().FlipperRotateToEnd(Entity); + ref var state = ref PhysicsEngine.FlipperState(ItemId); + state.Movement.EnableRotateEvent = 1; + state.Movement.StartRotateToEndTime = PhysicsEngine.TimeMsec; + state.Movement.AngleAtRotateToEnd = state.Movement.Angle; + state.Solenoid.Value = true; } /// @@ -104,15 +104,18 @@ public void RotateToEnd() /// public void RotateToStart() { - EngineProvider.Get().FlipperRotateToStart(Entity); + ref var state = ref PhysicsEngine.FlipperState(ItemId); + state.Movement.EnableRotateEvent = -1; + state.Solenoid.Value = false; } + internal ref FlipperState State => ref PhysicsEngine.FlipperState(ItemId); + internal float StartAngle { set { - var staticData = EntityManager.GetComponentData(Entity); - staticData.AngleStart = value; - EntityManager.SetComponentData(Entity, staticData); + ref var flipperState = ref PhysicsEngine.FlipperState(ItemId); + flipperState.Static.AngleStart = value; } } @@ -165,7 +168,7 @@ private void OnDualWoundCoil(bool enabled, bool isHoldCoil) } else { if (_isEos && isHoldCoil) { _isEos = false; - Switch?.Invoke(this, new SwitchEventArgs(false, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(false)); OnSwitch(false); RotateToStart(); } @@ -191,9 +194,9 @@ private void OnDualWoundCoil(bool enabled, bool isHoldCoil) #region Events - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } void IApiRotatable.OnRotate(float speed, bool direction) @@ -201,7 +204,7 @@ void IApiRotatable.OnRotate(float speed, bool direction) if (direction) { _isEos = true; LimitEos?.Invoke(this, new RotationEventArgs { AngleSpeed = speed }); - Switch?.Invoke(this, new SwitchEventArgs(true, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(true)); OnSwitch(true); } else { @@ -209,7 +212,7 @@ void IApiRotatable.OnRotate(float speed, bool direction) } } - void IApiCollidable.OnCollide(Entity ballEntity, float hit) + void IApiCollidable.OnCollide(int ballId, float hit) { Collide?.Invoke(this, new CollideEventArgs { FlipperHit = hit }); } @@ -218,7 +221,7 @@ void IApiCollidable.OnCollide(Entity ballEntity, float hit) #region Collider Generation - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var height = MainComponent.PositionZ; var baseRadius = math.max(MainComponent.BaseRadius, 0.01f); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs index 8e61ecb25..2ad84f096 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCollider.cs @@ -16,8 +16,6 @@ using NLog; using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; @@ -26,15 +24,24 @@ namespace VisualPinball.Unity { internal struct FlipperCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set { + Header.Id = value; + var bounds = Bounds; + bounds.ColliderId = value; + Bounds = bounds; + } + } - private ColliderHeader _header; + public ColliderHeader Header; private readonly CircleCollider _hitCircleBase; private readonly float _zLow; private readonly float _zHigh; - public ColliderBounds Bounds { get; private set; } + public ColliderBounds Bounds { get; set; } public static readonly Logger Logger = LogManager.GetCurrentClassLogger(); @@ -43,7 +50,7 @@ internal struct FlipperCollider : ICollider public FlipperCollider(CircleCollider hitCircleBase, float flipperRadius, float startRadius, float endRadius, float startAngle, float endAngle, ColliderInfo info) : this() { var bounds = hitCircleBase.Bounds; - _header.Init(info, ColliderType.Flipper); + Header.Init(info, ColliderType.Flipper); _hitCircleBase = hitCircleBase; _zLow = bounds.Aabb.ZLow; _zHigh = bounds.Aabb.ZHigh; @@ -69,7 +76,7 @@ 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.Entity, _header.Id, aabb); + 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) @@ -133,28 +140,13 @@ private static float ClampDegrees(float angle) return deg > 180 ? deg - 360 : deg; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - var bounds = Bounds; - bounds.ColliderId = colliderId; - Bounds = bounds; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(FlipperCollider) - ); - } - #endregion #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, - ref FlipperHitData hitData, - in FlipperMovementData movementData, in FlipperTricksData tricks, in FlipperStaticData matData, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, ref FlipperHitData hitData, + in FlipperMovementState movementState, in FlipperTricksData tricks, in FlipperStaticData matData, in BallState ball, + float dTime) { // todo // if (!_data.IsEnabled) { @@ -170,20 +162,20 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer= 0) { return hitTime; } // second face - hitTime = HitTestFlipperFace(ref collEvent, ref hitData, movementData, tricks, matData, ball, dTime, !lastFace); + hitTime = HitTestFlipperFace(ref collEvent, ref hitData, movementState, tricks, matData, ball, dTime, !lastFace); if (hitTime >= 0) { hitData.LastHitFace = !lastFace; // change this face to check first // HACK return hitTime; } // end radius - hitTime = HitTestFlipperEnd(ref collEvent, ref hitData, movementData, tricks, matData, ball, dTime); + hitTime = HitTestFlipperEnd(ref collEvent, ref hitData, movementState, tricks, matData, ball, dTime); if (hitTime >= 0) { return hitTime; } @@ -200,11 +192,11 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer.ParallelWriter events, in Entity ballEntity, in FlipperTricksData tricks, in FlipperStaticData matData, + public void Collide(ref BallState ball, ref CollisionEventData collEvent, ref FlipperMovementState movementState, + ref NativeQueue.ParallelWriter events, in int ballId, in FlipperTricksData tricks, in FlipperStaticData matData, in FlipperVelocityData velData, in FlipperHitData hitData, uint timeMsec) { var normal = collEvent.HitNormal; - GetRelativeVelocity(normal, ball, movementData, out var vRel, out var rB, out var rF); + GetRelativeVelocity(normal, ball, movementState, out var vRel, out var rB, out var rF); var bnv = math.dot(normal, vRel); // relative normal velocity @@ -842,7 +834,7 @@ public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref Fli * We use a heuristic model which decreases the COR according to a falloff parameter: * 0 = no falloff, 1 = half the COR at 1 m/s (18.53 speed units) */ - var epsilon = Math.ElasticityWithFalloff(_header.Material.Elasticity, _header.Material.ElasticityFalloff, bnv); + var epsilon = Math.ElasticityWithFalloff(Header.Material.Elasticity, Header.Material.ElasticityFalloff, bnv); if (tricks.UseFlipperTricksPhysics) epsilon *= tricks.ElasticityMultiplier; @@ -879,7 +871,7 @@ public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref Fli } ball.Velocity += impulse * ball.InvMass * normal; // new velocity for ball after impact - movementData.ApplyImpulse(rotI, matData.Inertia); + movementState.ApplyImpulse(rotI, matData.Inertia); // apply friction var tangent = vRel - math.dot(vRel, normal) * normal; // calc the tangential velocity @@ -899,30 +891,30 @@ public void Collide(ref BallData ball, ref CollisionEventData collEvent, ref Fli kt += math.dot(tangent, math.cross(pv13, rF)); // flipper only has angular response // friction impulse can't be greater than coefficient of friction times collision impulse (Coulomb friction cone) - var maxFriction = _header.Material.Friction * impulse; + var maxFriction = Header.Material.Friction * impulse; var jt = math.clamp(-vt / kt, -maxFriction, maxFriction); ball.ApplySurfaceImpulse( jt * crossB, jt * tangent ); - movementData.ApplyImpulse(-jt * crossF, matData.Inertia); + movementState.ApplyImpulse(-jt * crossF, matData.Inertia); } // event - if (bnv < -0.25f && timeMsec - movementData.LastHitTime > 250) { + if (bnv < -0.25f && timeMsec - movementState.LastHitTime > 250) { // limit rate to 250 milliseconds per event var flipperHit = hitData.HitMomentBit ? -1.0f : -bnv; // move event processing to end of collision handler... if (flipperHit < 0f) { // simple hit event - events.Enqueue(new EventData(EventId.HitEventsHit, _header.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.HitEventsHit, Header.ItemId, ballId, true)); } else { // collision velocity (normal to face) - events.Enqueue(new EventData(EventId.FlipperEventsCollide, _header.Entity, ballEntity, flipperHit)); + events.Enqueue(new EventData(EventId.FlipperEventsCollide, Header.ItemId, ballId, flipperHit)); } } - movementData.LastHitTime = timeMsec; // keep resetting until idle for 250 milliseconds + movementState.LastHitTime = timeMsec; // keep resetting until idle for 250 milliseconds } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs index 155bf5426..1a23f397d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperColliderComponent.cs @@ -16,7 +16,6 @@ // ReSharper disable InconsistentNaming -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Flipper; @@ -123,7 +122,7 @@ public class FlipperColliderComponent : ColliderComponent /// If set, apply Live Catch (nFozzy/RothBauerW) /// - #endregion + [Tooltip("The nFozzy's LiveCatch Physics")] public bool useFlipperLiveCatch = false; @@ -157,8 +156,9 @@ public class FlipperColliderComponent : ColliderComponent new FlipperApi(gameObject, entity, player); + #endregion + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.FlipperApi ?? new FlipperApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs index e03fc4839..8c34e45ed 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperComponent.cs @@ -23,16 +23,14 @@ using System; using System.Collections.Generic; -using Unity.Collections; -using Unity.Entities; using Unity.Mathematics; +using UnityEditor; using UnityEngine; using VisualPinball.Engine.Game.Engines; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Flipper; using VisualPinball.Engine.VPT.Table; -using VisualPinball.Engine.VPT.Trigger; using Color = UnityEngine.Color; namespace VisualPinball.Unity @@ -41,7 +39,7 @@ namespace VisualPinball.Unity [HelpURL("https://docs.visualpinball.org/creators-guide/manual/mechanisms/flippers.html")] public class FlipperComponent : MainRenderableComponent, IFlipperData, ISwitchDeviceComponent, ICoilDeviceComponent, IOnSurfaceComponent, - IRotatableComponent, IConvertGameObjectToEntity + IRotatableComponent { #region Data @@ -201,35 +199,6 @@ public float2 RotatedPosition { #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - var player = transform.GetComponentInParent(); - var colliderComponent = gameObject.GetComponent(); - - // collision - if (colliderComponent) { - - // vpx physics - var d = GetMaterialData(colliderComponent); - dstManager.AddComponentData(entity, d); - dstManager.AddComponentData(entity, GetMovementData(d)); - dstManager.AddComponentData(entity, GetVelocityData(d)); - dstManager.AddComponentData(entity, GetHitData()); - dstManager.AddComponentData(entity, GetFlipperTricksData(colliderComponent, d)); - dstManager.AddComponentData(entity, new SolenoidStateData { Value = false }); - - // flipper correction (nFozzy) - if (colliderComponent.FlipperCorrection) { - SetupFlipperCorrection(entity, dstManager, player, colliderComponent); - } - } - - // register - player.RegisterFlipper(this, entity); - } - public override IEnumerable SetData(FlipperData data) { var updatedComponents = new List { this }; @@ -390,7 +359,7 @@ protected void OnDrawGizmosSelected() } Gizmos.matrix = Matrix4x4.identity; - UnityEditor.Handles.matrix = Matrix4x4.identity; + Handles.matrix = Matrix4x4.identity; // Draw enclosing polygon Gizmos.color = Color.cyan; @@ -435,74 +404,6 @@ protected void OnDrawGizmosSelected() private bool IsLeft => EndAngle < _startAngle; - private void SetupFlipperCorrection(Entity entity, EntityManager dstManager, Player player, FlipperColliderComponent colliderComponent) - { - var fc = colliderComponent.FlipperCorrection; - - // create trigger - var triggerData = CreateCorrectionTriggerData(); - var triggerEntity = dstManager.CreateEntity(typeof(TriggerStaticData)); - dstManager.AddComponentData(triggerEntity, new TriggerStaticData()); - player.RegisterTrigger(triggerData, triggerEntity, gameObject); - - using (var builder = new BlobBuilder(Allocator.Temp)) { - - ref var root = ref builder.ConstructRoot(); - root.FlipperEntity = entity; - root.TimeDelayMs = fc.TimeThresholdMs; - - // Discretize the curves - var polarities = builder.Allocate(ref root.Polarities, fc.PolaritiesCurveSlicingCount + 1); - if (fc.Polarities != null) - { - var curve = fc.Polarities; - float stepP = (curve[curve.length - 1].time - curve[0].time) / fc.PolaritiesCurveSlicingCount; - int i = 0; - for (var t = curve[0].time; t <= curve[curve.length - 1].time; t += stepP) - { - polarities[i].x = t; - polarities[i++].y = curve.Evaluate(t); - } - } - else - { - for (int i = 0; i < fc.PolaritiesCurveSlicingCount + 1; i++) - { - polarities[i].x = i / (float)fc.PolaritiesCurveSlicingCount; - polarities[i].y = 0F; - } - } - - var velocities = builder.Allocate(ref root.Velocities, fc.VelocitiesCurveSlicingCount + 1); - if (fc.Velocities != null) - { - var curve = fc.Velocities; - float stepP = (curve[curve.length - 1].time - curve[0].time) / fc.VelocitiesCurveSlicingCount; - int i = 0; - for (var t = curve[0].time; t <= curve[curve.length - 1].time; t += stepP) - { - velocities[i].x = t; - velocities[i++].y = curve.Evaluate(t); - } - } - else - { - for (int i = 0; i < fc.VelocitiesCurveSlicingCount + 1; i++) - { - velocities[i].x = i / (float)fc.PolaritiesCurveSlicingCount; - velocities[i].y = 1F; - } - } - - var blobAssetRef = builder.CreateBlobAssetReference(Allocator.Persistent); - - // add correction data - dstManager.AddComponentData(triggerEntity, new FlipperCorrectionData { - Value = blobAssetRef - }); - } - } - //! 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) { @@ -571,36 +472,21 @@ public List GetEnclosingPolygon(float margin = 0.0F, float stepSize = 5 return ret; } - public TriggerData CreateCorrectionTriggerData() - { - // Get table reference - var ta = GetComponentInParent(); - if (ta != null) { - - var localPos = transform.localPosition; - var data = new TriggerData(name + " (Correction Trigger)", localPos.x, localPos.y); - var poly = GetEnclosingPolygon(23, 12); - data.DragPoints = new DragPointData[poly.Count]; - data.IsLocked = true; - data.HitHeight = 150F; // nFozzy's recommendation, but I think 50 should be ok - - for (var i = 0; i < poly.Count; i++) { - // Poly points are expressed in flipper's frame: transpose to Table's frame as this is the basis uses for drag points - var p = ta.transform.InverseTransformPoint(transform.TransformPoint(poly[i])); - data.DragPoints[poly.Count - i - 1] = new DragPointData(p.x, p.y); - } - return data; - } - throw new InvalidOperationException("Cannot create correction trigger for flipper outside of the table hierarchy."); - } - #endregion #region Runtime + public FlipperApi FlipperApi { get; private set; } + private void Awake() { _originalRotateZ = _startAngle; + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + FlipperApi = new FlipperApi(gameObject, player, physicsEngine); + + player.Register(FlipperApi, this); + RegisterPhysics(physicsEngine); } private void Start() @@ -610,9 +496,36 @@ private void Start() #endregion - #region DOTS Data + #region State + + internal FlipperState CreateState() + { + var colliderComponent = gameObject.GetComponent(); + if (colliderComponent) { + + // vpx physics + var d = GetMaterialData(colliderComponent); + var state = new FlipperState( + gameObject.GetInstanceID(), + d, + GetMovementData(d), + GetVelocityData(d), + GetHitData(), + GetFlipperTricksData(colliderComponent, d), + new SolenoidState { Value = false } + ); + + // flipper correction (nFozzy) + if (colliderComponent.FlipperCorrection) { + SetupFlipperCorrection(colliderComponent); + } + + return state; + } + return default; + } - private FlipperTricksData GetFlipperTricksData(FlipperColliderComponent colliderComponent, FlipperStaticData staticData) + internal FlipperTricksData GetFlipperTricksData(FlipperColliderComponent colliderComponent, FlipperStaticData staticData) { return new FlipperTricksData { @@ -646,7 +559,7 @@ private FlipperTricksData GetFlipperTricksData(FlipperColliderComponent collider }; } - private FlipperStaticData GetMaterialData(FlipperColliderComponent colliderComponent) + internal FlipperStaticData GetMaterialData(FlipperColliderComponent colliderComponent) { float flipperRadius; if (FlipperRadiusMin > 0 && FlipperRadiusMax > FlipperRadiusMin) { @@ -687,14 +600,14 @@ private FlipperStaticData GetMaterialData(FlipperColliderComponent colliderCompo }; } - private FlipperMovementData GetMovementData(FlipperStaticData d) + internal FlipperMovementState GetMovementData(FlipperStaticData d) { // store flipper base rotation without starting angle var baseRotation = math.normalize(math.mul( math.normalize(transform.rotation), quaternion.EulerXYZ(0, 0, -d.AngleStart) )); - return new FlipperMovementData { + return new FlipperMovementState { Angle = d.AngleStart, AngleSpeed = 0f, AngularMomentum = 0f, @@ -702,7 +615,7 @@ private FlipperMovementData GetMovementData(FlipperStaticData d) }; } - private static FlipperVelocityData GetVelocityData(FlipperStaticData d) + internal static FlipperVelocityData GetVelocityData(FlipperStaticData d) { return new FlipperVelocityData { AngularAcceleration = 0f, @@ -713,7 +626,7 @@ private static FlipperVelocityData GetVelocityData(FlipperStaticData d) }; } - private FlipperHitData GetHitData() + internal FlipperHitData GetHitData() { var ratio = (math.max(_baseRadius, 0.01f) - math.max(_endRadius, 0.01f)) / math.max(FlipperRadiusMax, 0.01f); var zeroAngNorm = new float2( @@ -729,6 +642,93 @@ private FlipperHitData GetHitData() }; } + private void SetupFlipperCorrection(FlipperColliderComponent colliderComponent) + { + var fc = colliderComponent.FlipperCorrection; + var ta = GetComponentInParent(); + + if (!ta) { + throw new InvalidOperationException("Cannot create correction trigger for flipper outside of the table hierarchy."); + } + + // create new gameobject with the correct components, the gameobject will handle itself after that. + var go = new GameObject { + name = $"{name} (Flipper Correction)", + }; + go.transform.SetParent(gameObject.transform.parent, false); + var triggerComponent = go.AddComponent(); + var triggerCollider = go.AddComponent(); + + triggerCollider.ForFlipper = this; + triggerCollider.TimeThresholdMs = fc.TimeThresholdMs; + + var localPos = transform.localPosition.TranslateToVpx(); + triggerComponent.Position = new Vector2(localPos.x, localPos.y); + + 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: + // 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 + // to localPos was close, but not close enough. + var flipperToPlayfield = new Matrix4x4( + new Vector4(-0.50754f, 0.86163f, 0, 0), + new Vector4(-0.86163f, -0.50754f, 0, 0), + new Vector4(0, 0, 1f, 0), + new Vector4(278.21380f, 1803.27200f, 0, 1f) + ); + + for (var i = 0; i < poly.Count; i++) { + // Poly points are expressed in flipper's frame: rotate to get it to the correct position. + var p = flipperToPlayfield.MultiplyPoint3x4(poly[i]); + triggerComponent.DragPoints[poly.Count - i - 1] = new DragPointData(p.x, p.y); + } + + // create polarities and velocities curves + var polarities = new float2[fc.PolaritiesCurveSlicingCount + 1]; + if (fc.Polarities != null) { + Discretize(fc.Polarities, polarities); + + } else { + for (var i = 0; i < fc.PolaritiesCurveSlicingCount + 1; i++) { + polarities[i++] = new float2(i / (float)fc.PolaritiesCurveSlicingCount, 0); + } + } + triggerCollider.FlipperPolarities = polarities; + + var velocities = new float2[fc.VelocitiesCurveSlicingCount + 1]; + if (fc.Velocities != null) { + Discretize(fc.Velocities, velocities); + + } else { + for (var i = 0; i < fc.VelocitiesCurveSlicingCount + 1; i++) { + velocities[i++] = new float2(i / (float)fc.PolaritiesCurveSlicingCount, 1f); + } + } + triggerCollider.FlipperVelocities = velocities; + + // need to explicitly register, since awake was called before the components were added. + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + triggerComponent.TriggerApi = new TriggerApi(triggerComponent.gameObject, player, physicsEngine); + player.Register(triggerComponent.TriggerApi, triggerComponent); + physicsEngine.Register(triggerComponent); + } + + private static void Discretize(AnimationCurve curve, IList outArray) + { + var stepP = (curve[curve.length - 1].time - curve[0].time) / (outArray.Count - 1); + var i = 0; + for (var t = curve[0].time; t <= curve[curve.length - 1].time; t += stepP) { + outArray[i++] = new float2(t, curve.Evaluate(t)); + } + } + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs index dc3edaaa7..9f9d475b1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrection.cs @@ -15,35 +15,32 @@ // along with this program. If not, see . using Unity.Mathematics; -using Unity.Entities; namespace VisualPinball.Unity { internal static class FlipperCorrection { - public static void OnBallLeaveFlipper(ref BallData ballData, ref FlipperCorrectionBlob flipperCorrectionBlob, - in FlipperMovementData flipperMovementData, in FlipperTricksData tricks, in FlipperStaticData flipperStaticData, uint timeMs) + public static void OnBallLeaveFlipper(ref BallState ballState, ref FlipperCorrectionState flipperCorrectionState, + in FlipperMovementState flipperMovementState, in FlipperTricksData tricks, in FlipperStaticData flipperStaticData, uint timeMs) { - var timeSinceFlipperStartedRotatingToEndMs = timeMs - flipperMovementData.StartRotateToEndTime; + var timeSinceFlipperStartedRotatingToEndMs = timeMs - flipperMovementState.StartRotateToEndTime; // Time delay overrun test - if (timeSinceFlipperStartedRotatingToEndMs > flipperCorrectionBlob.TimeDelayMs) + if (timeSinceFlipperStartedRotatingToEndMs > flipperCorrectionState.TimeDelayMs) return; - ref var velocities = ref flipperCorrectionBlob.Velocities; - ref var polarities = ref flipperCorrectionBlob.Polarities; - var angleCur = flipperMovementData.Angle; + var angleCur = flipperMovementState.Angle; - var ballPosition = ballData.Position; - var ballVelocity = ballData.Velocity; + var ballPosition = ballState.Position; + var ballVelocity = ballState.Velocity; var uncorrectedVel = ballVelocity; if (ballVelocity.y > -8F) // ball going down { return; } - var angleAtFire = flipperMovementData.AngleAtRotateToEnd; + var angleAtFire = flipperMovementState.AngleAtRotateToEnd; var angleStart = flipperStaticData.AngleStart; var angleEnd = tricks.AngleEnd; @@ -65,7 +62,7 @@ public static void OnBallLeaveFlipper(ref BallData ballData, ref FlipperCorrecti partialFlipCoef = math.abs(partialFlipCoef - 1F); // Velocity correction - var velCoef = LinearEnvelopeEven(ballPos, ref velocities); + var velCoef = LinearEnvelopeEven(ballPos, flipperCorrectionState.Velocities); var velCoefInit = velCoef; if (partialFlipCoef < 1) { @@ -76,7 +73,7 @@ public static void OnBallLeaveFlipper(ref BallData ballData, ref FlipperCorrecti // Polarity Correction bool isLeft = angleEnd < angleStart; // TODO: better if not classic flippers (trigonometry problems) - float AddX = LinearEnvelopeEven(ballPos, ref polarities, 0F); + float AddX = LinearEnvelopeEven(ballPos, flipperCorrectionState.Polarities, 0F); if(!isLeft) { AddX = -AddX; } @@ -85,7 +82,7 @@ public static void OnBallLeaveFlipper(ref BallData ballData, ref FlipperCorrecti // Apply all corrections - ballData.Velocity = ballVelocity; + ballState.Velocity = ballVelocity; } // awful linear interpolations : TODO: replace by AnimationCurve equivalent... @@ -96,7 +93,7 @@ private static float PSlope(float x, float x1, float y1, float x2, float y2) // return m * x + b; } - private static float LinearEnvelope(float xInput, ref BlobArray curve, float defaultValue = 1F) + private static float LinearEnvelope(float xInput, ref UnmanagedArray curve, float defaultValue = 1F) { if (curve.Length <= 0) return defaultValue; @@ -126,7 +123,7 @@ private static float LinearEnvelope(float xInput, ref BlobArray curve, f } // if segments are even on x axis, no need to iterate: faster - private static float LinearEnvelopeEven(float xInput, ref BlobArray curve, float defaultValue = 1F) + private static float LinearEnvelopeEven(float xInput, UnmanagedArray curve, float defaultValue = 1F) { if (curve.Length <= 0) return defaultValue; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs deleted file mode 100644 index 5801067b4..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs +++ /dev/null @@ -1,29 +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.Entities; -using Unity.Mathematics; - -namespace VisualPinball.Unity -{ - public struct FlipperCorrectionBlob - { - public Entity FlipperEntity; - public BlobArray Polarities; - public BlobArray Velocities; - public uint TimeDelayMs; - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs.meta deleted file mode 100644 index 692af5c11..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionBlob.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a385a32de1a35664899367786c93a093 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionData.cs deleted file mode 100644 index aae7c2520..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionData.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 . - -using Unity.Entities; - -namespace VisualPinball.Unity -{ - public struct FlipperCorrectionData : IComponentData - { - public BlobAssetReference Value; - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs new file mode 100644 index 000000000..7c2b939a6 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs @@ -0,0 +1,73 @@ +// 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; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; +using Unity.Mathematics; + +namespace VisualPinball.Unity +{ + public unsafe struct FlipperCorrectionState : IDisposable + { + public readonly bool IsEnabled; + public readonly int FlipperItemId; + public readonly uint TimeDelayMs; + + [NativeDisableUnsafePtrRestriction] private void* _polarities; + [NativeDisableUnsafePtrRestriction] private void* _velocities; + + 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) + { + IsEnabled = isEnabled; + FlipperItemId = flipperItemId; + TimeDelayMs = timeDelayMs; + _allocator = allocator; + _polarities = Allocate(polarities, _allocator); + _velocities = Allocate(velocities, _allocator); + _numPolarities = polarities.Length; + _numVelocities = velocities.Length; + } + + public UnmanagedArray Velocities => new(_velocities, _numVelocities); + public UnmanagedArray Polarities => new(_polarities, _numPolarities); + + private static void* Allocate(float2[] src, Allocator allocator) + { + var na = new NativeArray(src, Allocator.Temp); + var size = UnsafeUtility.SizeOf() * src.Length; + var dest = UnsafeUtility.Malloc(size, UnsafeUtility.AlignOf(), allocator); + UnsafeUtility.MemCpy(dest, na.GetUnsafeReadOnlyPtr(), size); + na.Dispose(); + + return dest; + } + + public void Dispose() + { + UnsafeUtility.Free(_velocities, Allocator.None); + UnsafeUtility.Free(_polarities, Allocator.None); + + _polarities = null; + _velocities = null; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperCorrectionState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs new file mode 100644 index 000000000..83d5ad1bc --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs @@ -0,0 +1,88 @@ +// 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.Collections; +using Unity.Mathematics; +using VisualPinball.Engine.Game; + +namespace VisualPinball.Unity +{ + internal static class FlipperDisplacementPhysics + { + internal static void UpdateDisplacement(int itemId, ref FlipperMovementState movementState, ref FlipperTricksData tricks, in FlipperStaticData staticState, + float dTime, ref NativeQueue.ParallelWriter events) + { + //var dTime = _simulateCycleSystemGroup.HitTime; + var currentTime = 0;// todo SystemAPI.Time.ElapsedTime; + + movementState.Angle += movementState.AngleSpeed * dTime; // move flipper angle + + var angleMin = math.min(staticState.AngleStart, tricks.AngleEnd); + var angleMax = math.max(staticState.AngleStart, tricks.AngleEnd); + + if (movementState.Angle > angleMax) { + movementState.Angle = angleMax; + } + + if (movementState.Angle < angleMin) { + movementState.Angle = angleMin; + } + + if (math.abs(movementState.AngleSpeed) < 0.0005f) { + // avoids "jumping balls" when two or more balls held on flipper (and more other balls are in play) //!! make dependent on physics update rate + return; + } + + var handleEvent = false; + + // ReSharper disable once CompareOfFloatsByEqualityOperator + if (movementState.Angle == tricks.AngleEnd) { + tricks.FlipperAngleEndTime = currentTime; + } + + if (movementState.Angle >= angleMax) { + // hit stop? + if (movementState.AngleSpeed > 0) { + handleEvent = true; + } + + } else if (movementState.Angle <= angleMin) { + if (movementState.AngleSpeed < 0) { + handleEvent = true; + } + } + + if (handleEvent) { + var angleSpeed = math.abs(math.degrees(movementState.AngleSpeed)); + movementState.AngularMomentum *= -0.3f; // make configurable? + movementState.AngleSpeed = movementState.AngularMomentum / staticState.Inertia; + + if (movementState.EnableRotateEvent > 0) { + + // send EOS event + events.Enqueue(new EventData(EventId.LimitEventsEos, itemId, angleSpeed)); + + } else if (movementState.EnableRotateEvent < 0) { + + // send BOS event + events.Enqueue(new EventData(EventId.LimitEventsBos, itemId, angleSpeed)); + } + + movementState.EnableRotateEvent = 0; + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs.meta new file mode 100644 index 000000000..6896bb020 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementPhysics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 971a7144094d47e39f1182c608bff07a +timeCreated: 1696163536 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs deleted file mode 100644 index 5d0e33d4d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs +++ /dev/null @@ -1,125 +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.Collections; -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Game; - -namespace VisualPinball.Unity -{ - [AlwaysSynchronizeSystem] - [UpdateInGroup(typeof(UpdateDisplacementSystemGroup))] - internal class FlipperDisplacementSystem : SystemBase - { - private Player _player; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private NativeQueue _eventQueue; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("FlipperDisplacementSystem"); - - protected override void OnCreate() - { - _player = Object.FindObjectOfType(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - var dTime = _simulateCycleSystemGroup.HitTime; - var events = _eventQueue.AsParallelWriter(); - var marker = PerfMarker; - var currentTime = Time.ElapsedTime; - - Entities.WithName("FlipperDisplacementJob").ForEach((Entity entity, ref FlipperMovementData state, ref FlipperTricksData tricks, in FlipperStaticData data) => { - - marker.Begin(); - - state.Angle += state.AngleSpeed * dTime; // move flipper angle - - var angleMin = math.min(data.AngleStart, tricks.AngleEnd); - var angleMax = math.max(data.AngleStart, tricks.AngleEnd); - - if (state.Angle > angleMax) { - state.Angle = angleMax; - } - - if (state.Angle < angleMin) { - state.Angle = angleMin; - } - - if (math.abs(state.AngleSpeed) < 0.0005f) { - // avoids "jumping balls" when two or more balls held on flipper (and more other balls are in play) //!! make dependent on physics update rate - marker.End(); - return; - } - - var handleEvent = false; - - if (state.Angle == tricks.AngleEnd) { - tricks.FlipperAngleEndTime = currentTime; - } - - if (state.Angle >= angleMax) { - // hit stop? - if (state.AngleSpeed > 0) { - handleEvent = true; - } - - } else if (state.Angle <= angleMin) { - if (state.AngleSpeed < 0) { - handleEvent = true; - } - } - - if (handleEvent) { - var angleSpeed = math.abs(math.degrees(state.AngleSpeed)); - state.AngularMomentum *= -0.3f; // make configurable? - state.AngleSpeed = state.AngularMomentum / data.Inertia; - - if (state.EnableRotateEvent > 0) { - - // send EOS event - events.Enqueue(new EventData(EventId.LimitEventsEos, entity, angleSpeed)); - - } else if (state.EnableRotateEvent < 0) { - - // send BOS event - events.Enqueue(new EventData(EventId.LimitEventsBos, entity, angleSpeed)); - } - - state.EnableRotateEvent = 0; - } - - marker.End(); - - }).Run(); - - // dequeue events - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(in eventData); - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs.meta deleted file mode 100644 index f0206729d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperDisplacementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: bc4aedced3dcace46a35c7d3ef279aec -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperHitData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperHitData.cs index 8182069d9..a14d485c1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperHitData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperHitData.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct FlipperHitData : IComponentData + internal struct FlipperHitData { public bool LastHitFace; public float2 HitVelocity; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs similarity index 82% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs index 801a02deb..79fe66e12 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct FlipperMovementData : IComponentData + internal struct FlipperMovementState { public float Angle; public float AngleSpeed; @@ -40,21 +39,21 @@ public void ApplyImpulse(in float3 rotI, float inertia) AngleSpeed = AngularMomentum / inertia; // figure TODO out moment of inertia } - public static float3 SurfaceAcceleration(in FlipperMovementData data, in FlipperVelocityData velData, in float3 surfP) + public static float3 SurfaceAcceleration(in FlipperMovementState movement, in FlipperVelocityData velData, in float3 surfP) { // tangential acceleration = (0, 0, omega) x surfP var tangAcc = Math.CrossZ(velData.AngularAcceleration, surfP); // centripetal acceleration = (0,0,omega) x ( (0,0,omega) x surfP ) - var av2 = data.AngleSpeed * data.AngleSpeed; + var av2 = movement.AngleSpeed * movement.AngleSpeed; var centrAcc = new float3(-av2 * surfP.x, -av2 * surfP.y, 0); return tangAcc + centrAcc; } - public static float3 SurfaceVelocity(in FlipperMovementData data, in float3 surfP) + public static float3 SurfaceVelocity(in FlipperMovementState movementState, in float3 surfP) { - return Math.CrossZ(data.AngleSpeed, in surfP); + return Math.CrossZ(movementState.AngleSpeed, in surfP); } public float GetHitTime(float angleStart, float angleEnd) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs.meta similarity index 95% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs.meta index b7ff39aee..0542e4811 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementData.cs.meta +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperMovementState.cs.meta @@ -1,11 +1,11 @@ -fileFormatVersion: 2 -guid: 1eaedb51d635c764f85bfd3fa8bbc223 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 1eaedb51d635c764f85bfd3fa8bbc223 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs deleted file mode 100644 index 45d2de024..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs +++ /dev/null @@ -1,52 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [AlwaysSynchronizeSystem] - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class FlipperRotateSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("FlipperRotateSystem"); - - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("FlipperRotateJob").ForEach((Entity entity, in FlipperMovementData movement) => { - - marker.Begin(); - - _player.FlipperTransforms[entity].localRotation = quaternion.Euler(0, movement.Angle, 0); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs.meta deleted file mode 100644 index 4d0846eb6..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperRotateSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 430201287c4577648af97349897ff952 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs new file mode 100644 index 000000000..eab2654d0 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs @@ -0,0 +1,42 @@ +// 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 +{ + internal struct FlipperState + { + internal readonly int ItemId; + internal FlipperStaticData Static; + internal FlipperMovementState Movement; + internal FlipperVelocityData Velocity; + internal FlipperHitData Hit; + internal FlipperTricksData Tricks; + internal SolenoidState Solenoid; + + public FlipperState(int itemId, FlipperStaticData @static, FlipperMovementState movement, + FlipperVelocityData velocity, FlipperHitData hit, FlipperTricksData tricks, + SolenoidState solenoid) + { + ItemId = itemId; + Static = @static; + Movement = movement; + Velocity = velocity; + Hit = hit; + Tricks = tricks; + Solenoid = solenoid; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs.meta new file mode 100644 index 000000000..9fcaf194a --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 761c2c59a9e948a7b674703f9bc04d79 +timeCreated: 1696161233 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs index 95d61f84b..478bb1290 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperStaticData.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct FlipperStaticData : IComponentData + internal struct FlipperStaticData { public float3 Position; public float Inertia; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs index 65196a82b..69cb1285b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperTricksData.cs @@ -14,12 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; -using Unity.Mathematics; - namespace VisualPinball.Unity { - internal struct FlipperTricksData : IComponentData + internal struct FlipperTricksData { // used in flippertricks // internals diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityData.cs index 058ec9253..2c646d23f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityData.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct FlipperVelocityData : IComponentData + internal struct FlipperVelocityData { public bool Direction; public float CurrentTorque; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs new file mode 100644 index 000000000..08cb16ab7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs @@ -0,0 +1,179 @@ +// 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 CompareOfFloatsByEqualityOperator + +using Unity.Mathematics; +using VisualPinball.Engine.Common; + +namespace VisualPinball.Unity +{ + internal static class FlipperVelocityPhysics + { + internal static void UpdateVelocities(ref FlipperState state) + { + ref var mState = ref state.Movement; + ref var vState = ref state.Velocity; + ref var tricks = ref state.Tricks; + ref var solenoid = ref state.Solenoid; + ref var data = ref state.Static; + + var angleMin = math.min(data.AngleStart, tricks.AngleEnd); + var angleMax = math.max(data.AngleStart, tricks.AngleEnd); + var minIsStart = angleMin == data.AngleStart; // Usually true for the right Flipper + + var desiredTorque = data.Strength; + if (!solenoid.Value) + { + // True solState = button pressed, false = released + desiredTorque *= -data.ReturnRatio; + } + + if (tricks.UseFlipperTricksPhysics) { + // check if solenoid was just activated or deactivated + if (solenoid.Value != tricks.lastSolState) + { + // check if solenoid was just activated or deactivated for Flippertricks + // Flippertricks case 1 and 2 are always before case 3, 4 and 5. + if (solenoid.Value) + { + // Flippertricks, case 2 (OnButtonActivate) + tricks.TorqueDamping = tricks.OriginalTorqueDamping; + tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; + tricks.ElasticityMultiplier = 1f; + } + else + { + // Flippertricks, case 1 (OnButtonDeactivate) + tricks.TorqueDamping = tricks.OriginalTorqueDamping * tricks.EOSReturn / data.ReturnRatio; + tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; + } + } + } + + // hold coil is weaker + float eosAngle = math.radians(tricks.TorqueDampingAngle); + if (math.abs(mState.Angle - tricks.AngleEnd) < eosAngle) { + // fade in/out damping, depending on angle to end + var lerp = math.pow(math.abs(mState.Angle - tricks.AngleEnd) / eosAngle, 4); + if (tricks.UseFlipperTricksPhysics) + desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); + else + desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); + } + + if (!vState.Direction) { + desiredTorque = -desiredTorque; + } + + var torqueRampUpSpeed = tricks.RampUpSpeed; + if (torqueRampUpSpeed <= 0) { + // set very high for instant coil response + torqueRampUpSpeed = 1e6f; + + } else { + torqueRampUpSpeed = math.min(data.Strength / torqueRampUpSpeed, 1e6f); + } + + // update current torque linearly towards desired torque + // (simple model for coil hysteresis) + if (desiredTorque >= vState.CurrentTorque) { + vState.CurrentTorque = math.min(vState.CurrentTorque + torqueRampUpSpeed * (float)PhysicsConstants.PhysFactor, desiredTorque); + + } else { + vState.CurrentTorque = math.max(vState.CurrentTorque - torqueRampUpSpeed * (float)PhysicsConstants.PhysFactor, desiredTorque); + } + + // resolve contacts with stoppers + var torque = vState.CurrentTorque; + vState.IsInContact = false; + if (math.abs(mState.AngleSpeed) <= 1e-2) { + + if (mState.Angle >= angleMax - 1e-2 && torque > 0) { + mState.Angle = angleMax; + vState.IsInContact = true; + vState.ContactTorque = torque; + mState.AngularMomentum = 0f; + torque = 0f; + + } else if (mState.Angle <= angleMin + 1e-2 && torque < 0) { + mState.Angle = angleMin; + vState.IsInContact = true; + vState.ContactTorque = torque; + mState.AngularMomentum = 0f; + torque = 0f; + } + } + + mState.AngularMomentum += (float)PhysicsConstants.PhysFactor * torque; + mState.AngleSpeed = mState.AngularMomentum / data.Inertia; + vState.AngularAcceleration = torque / data.Inertia; + + if (tricks.UseFlipperTricksPhysics) + { + // Flippertricks, case 3 (OnFlipperDown) and 4 (OnFlipperUpResting) + if (!tricks.WasInContact && vState.IsInContact) + { + // the flipper stopped due to being at max or min angle. + // so check if at start angle + if (((mState.Angle == angleMin) && (minIsStart)) || + ((mState.Angle == angleMax) && (!minIsStart))) + { + // is at start angle + // FlipperTricks case 3: OnFlipperDown + if (minIsStart) + tricks.AngleEnd = tricks.OriginalAngleEnd + tricks.Overshoot; + else + tricks.AngleEnd = tricks.OriginalAngleEnd - tricks.Overshoot; + + tricks.RampUpSpeed = tricks.SOSRampUp; + tricks.ElasticityMultiplier = tricks.SOSEM; + + //data.ft = 3f; + } + else + { + // is at end angle + // FlipperTricks case 4: OnFlipperUpResting + tricks.AngleEnd = tricks.OriginalAngleEnd; // This causes the flipper to instantly flip back to normal end angle (like in the original Flippertricks implementation) + tricks.RampUpSpeed = tricks.EOSRampup; + tricks.TorqueDamping = tricks.EOSTNew; + tricks.TorqueDampingAngle = tricks.EOSANew; + + //data.ft = 4f; + } + } + + // Flippertricks, case 5 (OnEnterinbetween) (and pressed) + if ((tricks.WasInContact) && (!vState.IsInContact) && solenoid.Value) + { + // Flippertricks Case 5 + tricks.RampUpSpeed = tricks.OriginalRampUpSpeed; + tricks.TorqueDamping = tricks.OriginalTorqueDamping; + tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; + tricks.ElasticityMultiplier = 1f; + + //data.ft = 5f; + } + + tricks.WasInContact = vState.IsInContact; + + // check if solenoid was just activated or deactivated + tricks.lastSolState = solenoid.Value; + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs.meta new file mode 100644 index 000000000..7ea3399a3 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocityPhysics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d014647421cc4e3aacc873b43e34b68b +timeCreated: 1696189650 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs deleted file mode 100644 index e3f631c01..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs +++ /dev/null @@ -1,188 +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 CompareOfFloatsByEqualityOperator - -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - [AlwaysSynchronizeSystem] - [UpdateInGroup(typeof(UpdateVelocitiesSystemGroup))] - internal class FlipperVelocitySystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("FlipperVelocitySystem"); - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithName("FlipperVelocityJob").ForEach((ref FlipperMovementData mState, ref FlipperVelocityData vState, ref FlipperTricksData tricks, in SolenoidStateData solenoid, in FlipperStaticData data) => - { - - marker.Begin(); - - var angleMin = math.min(data.AngleStart, tricks.AngleEnd); - var angleMax = math.max(data.AngleStart, tricks.AngleEnd); - var minIsStart = angleMin == data.AngleStart; // Usually true for the right Flipper - - var desiredTorque = data.Strength; - if (!solenoid.Value) - { - // True solState = button pressed, false = released - desiredTorque *= -data.ReturnRatio; - } - - if (tricks.UseFlipperTricksPhysics) { - // check if solenoid was just activated or deactivated - if (solenoid.Value != tricks.lastSolState) - { - // check if solenoid was just activated or deactivated for Flippertricks - // Flippertricks case 1 and 2 are always before case 3, 4 and 5. - if (solenoid.Value) - { - // Flippertricks, case 2 (OnButtonActivate) - tricks.TorqueDamping = tricks.OriginalTorqueDamping; - tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; - tricks.ElasticityMultiplier = 1f; - } - else - { - // Flippertricks, case 1 (OnButtonDeactivate) - tricks.TorqueDamping = tricks.OriginalTorqueDamping * tricks.EOSReturn / data.ReturnRatio; - tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; - } - } - } - - // hold coil is weaker - float eosAngle = math.radians(tricks.TorqueDampingAngle); - if (math.abs(mState.Angle - tricks.AngleEnd) < eosAngle) { - // fade in/out damping, depending on angle to end - var lerp = math.pow(math.abs(mState.Angle - tricks.AngleEnd) / eosAngle, 4); - if (tricks.UseFlipperTricksPhysics) - desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); - else - desiredTorque *= lerp + tricks.TorqueDamping * (1 - lerp); - } - - if (!vState.Direction) { - desiredTorque = -desiredTorque; - } - - var torqueRampUpSpeed = tricks.RampUpSpeed; - if (torqueRampUpSpeed <= 0) { - // set very high for instant coil response - torqueRampUpSpeed = 1e6f; - - } else { - torqueRampUpSpeed = math.min(data.Strength / torqueRampUpSpeed, 1e6f); - } - - // update current torque linearly towards desired torque - // (simple model for coil hysteresis) - if (desiredTorque >= vState.CurrentTorque) { - vState.CurrentTorque = math.min(vState.CurrentTorque + torqueRampUpSpeed * (float)PhysicsConstants.PhysFactor, desiredTorque); - - } else { - vState.CurrentTorque = math.max(vState.CurrentTorque - torqueRampUpSpeed * (float)PhysicsConstants.PhysFactor, desiredTorque); - } - - // resolve contacts with stoppers - var torque = vState.CurrentTorque; - vState.IsInContact = false; - if (math.abs(mState.AngleSpeed) <= 1e-2) { - - if (mState.Angle >= angleMax - 1e-2 && torque > 0) { - mState.Angle = angleMax; - vState.IsInContact = true; - vState.ContactTorque = torque; - mState.AngularMomentum = 0f; - torque = 0f; - - } else if (mState.Angle <= angleMin + 1e-2 && torque < 0) { - mState.Angle = angleMin; - vState.IsInContact = true; - vState.ContactTorque = torque; - mState.AngularMomentum = 0f; - torque = 0f; - } - } - - mState.AngularMomentum += (float)PhysicsConstants.PhysFactor * torque; - mState.AngleSpeed = mState.AngularMomentum / data.Inertia; - vState.AngularAcceleration = torque / data.Inertia; - - if (tricks.UseFlipperTricksPhysics) - { - // Flippertricks, case 3 (OnFlipperDown) and 4 (OnFlipperUpResting) - if (!tricks.WasInContact && vState.IsInContact) - { - // the flipper stopped due to being at max or min angle. - // so check if at start angle - if (((mState.Angle == angleMin) && (minIsStart)) || - ((mState.Angle == angleMax) && (!minIsStart))) - { - // is at start angle - // FlipperTricks case 3: OnFlipperDown - if (minIsStart) - tricks.AngleEnd = tricks.OriginalAngleEnd + tricks.Overshoot; - else - tricks.AngleEnd = tricks.OriginalAngleEnd - tricks.Overshoot; - - tricks.RampUpSpeed = tricks.SOSRampUp; - tricks.ElasticityMultiplier = tricks.SOSEM; - - //data.ft = 3f; - } - else - { - // is at end angle - // FlipperTricks case 4: OnFlipperUpResting - tricks.AngleEnd = tricks.OriginalAngleEnd; // This causes the flipper to instantly flip back to normal end angle (like in the original Flippertricks implementation) - tricks.RampUpSpeed = tricks.EOSRampup; - tricks.TorqueDamping = tricks.EOSTNew; - tricks.TorqueDampingAngle = tricks.EOSANew; - - //data.ft = 4f; - } - } - - // Flippertricks, case 5 (OnEnterinbetween) (and pressed) - if ((tricks.WasInContact) && (!vState.IsInContact) && solenoid.Value) - { - // Flippertricks Case 5 - tricks.RampUpSpeed = tricks.OriginalRampUpSpeed; - tricks.TorqueDamping = tricks.OriginalTorqueDamping; - tricks.TorqueDampingAngle = tricks.OriginalTorqueDampingAngle; - tricks.ElasticityMultiplier = 1f; - - //data.ft = 5f; - } - - tricks.WasInContact = vState.IsInContact; - - // check if solenoid was just activated or deactivated - tricks.lastSolState = solenoid.Value; - } - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs.meta deleted file mode 100644 index 7e1ef90d3..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/FlipperVelocitySystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5d22edc5cea271f4b9b58aa9af85efb8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidStateData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidStateData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidState.cs index 8b4a92cb6..bfdebed5d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidStateData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct SolenoidStateData : IComponentData + internal struct SolenoidState { public bool Value; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidStateData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidStateData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Flipper/SolenoidState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs index 08847e285..a4f28e686 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateApi.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Gate; @@ -74,18 +72,16 @@ public class GateApi : CollidableApi(Entity); - data.IsLifting = true; - data.LiftSpeed = speed; - data.LiftAngle = math.radians(angleDeg); - EntityManager.SetComponentData(Entity, data); + ref var gateState = ref PhysicsEngine.GateState(ItemId); + gateState.Movement.IsLifting = true; + gateState.Movement.LiftSpeed = speed; + gateState.Movement.LiftAngle = math.radians(angleDeg); } #region Wiring @@ -101,10 +97,10 @@ public void Lift(float speed, float angleDeg) #region Collider Generation - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new GateColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PositionZ, colliders); + colliderGenerator.GenerateColliders(MainComponent.PositionZ, ref colliders); } #endregion @@ -121,10 +117,10 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); OnSwitch(true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs index ce6b4a1dc..28bda1dcd 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateCollider.cs @@ -15,17 +15,24 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { internal struct GateCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set { + Header.Id = value; + var bounds = Bounds; + bounds.ColliderId = value; + Bounds = bounds; + } + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly LineCollider LineSeg0; public readonly LineCollider LineSeg1; @@ -34,31 +41,16 @@ internal struct GateCollider : ICollider public GateCollider(in LineCollider lineSeg0, in LineCollider lineSeg1, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Gate); + Header.Init(info, ColliderType.Gate); LineSeg0 = lineSeg0; LineSeg1 = lineSeg1; Bounds = LineSeg0.Bounds; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - var bounds = Bounds; - bounds.ColliderId = colliderId; - Bounds = bounds; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(GateCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { // todo // if (!this.isEnabled) { @@ -85,11 +77,11 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer.ParallelWriter events, in Entity ballEntity, in Collider coll, in GateStaticData data) + public static void Collide(ref BallState ball, ref CollisionEventData collEvent, ref GateMovementState movementState, + ref NativeQueue.ParallelWriter events, in ColliderHeader collHeader, in GateStaticState state) { var dot = math.dot(collEvent.HitNormal, ball.Velocity); - var h = data.Height * 0.5f; + var h = state.Height * 0.5f; // linear speed = ball speed // angular speed = linear/radius (height of hit) @@ -99,22 +91,22 @@ public static void Collide(ref BallData ball, ref CollisionEventData collEvent, speed /= h; } - movementData.AngleSpeed = speed; - if (!collEvent.HitFlag && !data.TwoWay) { - movementData.HitDirection = dot > 0; - movementData.AngleSpeed *= (float)(1.0 / 8.0); // Give a little bounce-back. + movementState.AngleSpeed = speed; + if (!collEvent.HitFlag && !state.TwoWay) { + movementState.HitDirection = dot > 0; + movementState.AngleSpeed *= (float)(1.0 / 8.0); // Give a little bounce-back. return; // hit from back doesn't count if not two-way } - movementData.HitDirection = false; + movementState.HitDirection = false; // We encoded which side of the spinner the ball hit - if (collEvent.HitFlag && data.TwoWay) { + if (collEvent.HitFlag && state.TwoWay) { - movementData.AngleSpeed = -movementData.AngleSpeed; + movementState.AngleSpeed = -movementState.AngleSpeed; } - Collider.FireHitEvent(ref ball, ref events, in ballEntity, in coll.Header); + Collider.FireHitEvent(ref ball, ref events, in collHeader); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs index 1508a6c7c..ef8bd0d53 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderComponent.cs @@ -16,9 +16,7 @@ // ReSharper disable InconsistentNaming -using System; using System.ComponentModel; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Gate; @@ -68,7 +66,7 @@ public class GateColliderComponent : ColliderComponent, public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new GateApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.GateApi ?? new GateApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs index 0f01a0a90..8e00197f2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateColliderGenerator.cs @@ -35,7 +35,7 @@ internal GateColliderGenerator(GateApi gateApi, IGateData data, IGateColliderDat _collData = collData; } - internal void GenerateColliders(float height, List colliders) // var height = table.GetSurfaceHeight(_data.Surface, _data.Center.X, _data.Center.Y); + internal void GenerateColliders(float height, 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); @@ -47,14 +47,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(colliders, height, radAngle); - GenerateLineCollider(colliders, height, tangent); + GenerateGateCollider(ref colliders, height, radAngle); + GenerateLineCollider(ref colliders, height, tangent); if (_data.ShowBracket) { - GenerateBracketColliders(colliders, height, tangent); + GenerateBracketColliders(ref colliders, height, tangent); } } - private void GenerateGateCollider(ICollection colliders, float height, float radAngle) + private void GenerateGateCollider(ref ColliderReference colliders, float height, float radAngle) { var halfLength = _data.Length * 0.5f; var sn = math.sin(radAngle); @@ -74,7 +74,7 @@ private void GenerateGateCollider(ICollection colliders, float height colliders.Add(new GateCollider(in lineSeg0, in lineSeg1, _api.GetColliderInfo())); } - private void GenerateLineCollider(ICollection colliders, float height, float2 tangent) + private void GenerateLineCollider(ref ColliderReference colliders, float height, float2 tangent) { if (_collData.TwoWay) { return; @@ -90,7 +90,7 @@ private void GenerateLineCollider(ICollection colliders, float height colliders.Add(new LineCollider(rgv0, rgv1, height, height + 2.0f * PhysicsConstants.PhysSkin, info)); //!! = ball diameter } - private void GenerateBracketColliders(ICollection colliders, float height, float2 tangent) + private void GenerateBracketColliders(ref ColliderReference colliders, float height, float2 tangent) { var center = new float2(_data.PosX, _data.PosY); var halfLength = _data.Length * 0.5f; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs index 9c8eb969d..53d9e21b7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateComponent.cs @@ -23,20 +23,23 @@ using System; using System.Collections.Generic; -using Unity.Entities; +using System.IO; using Unity.Mathematics; +using UnityEditor; using UnityEngine; +using UnityEngine.UIElements; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game.Engines; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Gate; +using VisualPinball.Engine.VPT.Surface; using VisualPinball.Engine.VPT.Table; namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Gate")] public class GateComponent : MainRenderableComponent, - IGateData, ISwitchDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + IGateData, ISwitchDeviceComponent, IOnSurfaceComponent { #region Data @@ -102,6 +105,24 @@ public bool ShowBracket { get { #endregion + #region Runtime + + public GateApi GateApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + GateApi = new GateApi(gameObject, player, physicsEngine); + + player.Register(GateApi, this); + if (GetComponent()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + #region Wiring public IEnumerable AvailableSwitches => new[] { @@ -141,39 +162,6 @@ public override void UpdateTransforms() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // collision - var colliderComponent = gameObject.GetComponent(); - if (colliderComponent) { - - dstManager.AddComponentData(entity, new GateStaticData { - AngleMin = math.radians(colliderComponent._angleMin), - AngleMax = math.radians(colliderComponent._angleMax), - Height = Position.z, - Damping = math.pow(colliderComponent.Damping, (float)PhysicsConstants.PhysFactor), - GravityFactor = colliderComponent.GravityFactor, - TwoWay = colliderComponent.TwoWay, - }); - - // movement data - if (GetComponentInChildren()) { - dstManager.AddComponentData(entity, new GateMovementData { - Angle = math.radians(colliderComponent._angleMin), - AngleSpeed = 0, - ForcedMove = false, - IsOpen = false, - HitDirection = false - }); - } - } - - // register - transform.GetComponentInParent().RegisterGate(this, entity); - } - public override IEnumerable SetData(GateData data) { var updatedComponents = new List { this }; @@ -219,7 +207,7 @@ public override IEnumerable SetReferencedData(GateData data, Tabl break; case WireObjectName: #if UNITY_EDITOR - _meshName = System.IO.Path.GetFileNameWithoutExtension(UnityEditor.AssetDatabase.GetAssetPath(mf.sharedMesh)); + _meshName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(mf.sharedMesh)); #endif mf.gameObject.SetActive(data.IsVisible); break; @@ -296,6 +284,43 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal GateState CreateState() + { + // collision + var collComponent = GetComponent(); + var staticData = collComponent + ? new GateStaticState { + AngleMin = math.radians(collComponent._angleMin), + AngleMax = math.radians(collComponent._angleMax), + Height = Position.z, + Damping = math.pow(math.clamp(collComponent.Damping, 0, 1), (float)PhysicsConstants.PhysFactor), + GravityFactor = collComponent.GravityFactor, + TwoWay = collComponent.TwoWay, + } : default; + Debug.Log($"Damping = {staticData.Damping}"); + + var wireComponent = GetComponentInChildren(); + var movementData = collComponent && wireComponent + ? new GateMovementState { + Angle = math.radians(collComponent._angleMin), + AngleSpeed = 0, + ForcedMove = false, + IsOpen = false, + HitDirection = false + } : default; + + return new GateState( + collComponent ? gameObject.GetInstanceID() : 0, + wireComponent ? wireComponent.gameObject.GetInstanceID() : 0, + staticData, + movementData + ); + } + + #endregion + #region Editor Tooling public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementPhysics.cs new file mode 100644 index 000000000..ca417bfd8 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementPhysics.cs @@ -0,0 +1,102 @@ +// 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.Collections; +using Unity.Mathematics; +using VisualPinball.Engine.Game; + +namespace VisualPinball.Unity +{ + internal static class GateDisplacementPhysics + { + internal static void UpdateDisplacement(int itemId, ref GateMovementState movementState, in GateStaticState state, + float dTime, ref NativeQueue.ParallelWriter events) + { + if (state.TwoWay) { + if (math.abs(movementState.Angle) > state.AngleMax) { + if (movementState.Angle < 0.0) { + movementState.Angle = -state.AngleMax; + } else { + movementState.Angle = state.AngleMax; + } + + // send EOS event + events.Enqueue(new EventData(EventId.LimitEventsEos, itemId, movementState.AngleSpeed)); + + if (!movementState.ForcedMove) { + movementState.AngleSpeed = -movementState.AngleSpeed; + movementState.AngleSpeed *= state.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster + } else if (movementState.AngleSpeed > 0.0) { + movementState.AngleSpeed = 0.0f; + } + } + if (math.abs(movementState.Angle) < state.AngleMin) { + if (movementState.Angle < 0.0) { + movementState.Angle = -state.AngleMin; + } else { + movementState.Angle = state.AngleMin; + } + if (!movementState.ForcedMove) { + movementState.AngleSpeed = -movementState.AngleSpeed; + movementState.AngleSpeed *= state.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster + } else if (movementState.AngleSpeed < 0.0) { + movementState.AngleSpeed = 0.0f; + } + } + } else { + var direction = movementState.HitDirection ? -1f : 1f; + if (direction * movementState.Angle > state.AngleMax) { + movementState.Angle = direction * state.AngleMax; + + // send EOS event + events.Enqueue(new EventData(EventId.LimitEventsEos, itemId, movementState.AngleSpeed)); + + if (!movementState.ForcedMove) { + movementState.AngleSpeed = -movementState.AngleSpeed; + movementState.AngleSpeed *= state.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster + } else if (movementState.AngleSpeed > 0.0) { + movementState.AngleSpeed = 0.0f; + } + } + if (direction * movementState.Angle < state.AngleMin) { + movementState.Angle = direction * state.AngleMin; + + // send Park event + events.Enqueue(new EventData(EventId.LimitEventsBos, itemId, movementState.AngleSpeed)); + + if (!movementState.ForcedMove) { + movementState.AngleSpeed = -movementState.AngleSpeed; + movementState.AngleSpeed *= state.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster + } else if (movementState.AngleSpeed < 0.0) { + movementState.AngleSpeed = 0.0f; + } + } + } + + movementState.Angle += movementState.AngleSpeed * dTime; + + if (movementState.IsLifting) { + if (math.abs(movementState.Angle - movementState.LiftAngle) > 0.000001f) { + var direction = movementState.Angle < movementState.LiftAngle ? 1f : -1f; + movementState.Angle += direction * (movementState.LiftSpeed * dTime); + + } else { + movementState.IsLifting = false; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementSystem.cs deleted file mode 100644 index d82478f53..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateDisplacementSystem.cs +++ /dev/null @@ -1,141 +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.Collections; -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Game; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateDisplacementSystemGroup))] - internal class GateDisplacementSystem : SystemBase - { - private Player _player; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private NativeQueue _eventQueue; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("GateDisplacementSystem"); - - protected override void OnCreate() - { - _player = Object.FindObjectOfType(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - var dTime = _simulateCycleSystemGroup.HitTime; - var marker = PerfMarker; - var events = _eventQueue.AsParallelWriter(); - - Entities - .WithName("GateDisplacementJob") - .ForEach((Entity entity, ref GateMovementData movementData, in GateStaticData data) => { - - marker.Begin(); - - if (data.TwoWay) { - if (math.abs(movementData.Angle) > data.AngleMax) { - if (movementData.Angle < 0.0) { - movementData.Angle = -data.AngleMax; - } else { - movementData.Angle = data.AngleMax; - } - - // send EOS event - events.Enqueue(new EventData(EventId.LimitEventsEos, entity, movementData.AngleSpeed)); - - if (!movementData.ForcedMove) { - movementData.AngleSpeed = -movementData.AngleSpeed; - movementData.AngleSpeed *= data.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster - } else if (movementData.AngleSpeed > 0.0) { - movementData.AngleSpeed = 0.0f; - } - } - if (math.abs(movementData.Angle) < data.AngleMin) { - if (movementData.Angle < 0.0) { - movementData.Angle = -data.AngleMin; - } else { - movementData.Angle = data.AngleMin; - } - if (!movementData.ForcedMove) { - movementData.AngleSpeed = -movementData.AngleSpeed; - movementData.AngleSpeed *= data.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster - } else if (movementData.AngleSpeed < 0.0) { - movementData.AngleSpeed = 0.0f; - } - } - } else { - var direction = movementData.HitDirection ? -1f : 1f; - if (direction * movementData.Angle > data.AngleMax) { - movementData.Angle = direction * data.AngleMax; - - // send EOS event - events.Enqueue(new EventData(EventId.LimitEventsEos, entity, movementData.AngleSpeed)); - - if (!movementData.ForcedMove) { - movementData.AngleSpeed = -movementData.AngleSpeed; - movementData.AngleSpeed *= data.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster - } else if (movementData.AngleSpeed > 0.0) { - movementData.AngleSpeed = 0.0f; - } - } - if (direction * movementData.Angle < data.AngleMin) { - movementData.Angle = direction * data.AngleMin; - - // send Park event - events.Enqueue(new EventData(EventId.LimitEventsBos, entity, movementData.AngleSpeed)); - - if (!movementData.ForcedMove) { - movementData.AngleSpeed = -movementData.AngleSpeed; - movementData.AngleSpeed *= data.Damping * 0.8f; // just some extra damping to reduce the angleSpeed a bit faster - } else if (movementData.AngleSpeed < 0.0) { - movementData.AngleSpeed = 0.0f; - } - } - } - movementData.Angle += movementData.AngleSpeed * dTime; - - if (movementData.IsLifting) { - if (math.abs(movementData.Angle - movementData.LiftAngle) > 0.000001f) { - var direction = movementData.Angle < movementData.LiftAngle ? 1f : -1f; - movementData.Angle += direction * (movementData.LiftSpeed * dTime); - - } else { - movementData.IsLifting = false; - } - } - - marker.End(); - - }).Run(); - - // dequeue events - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(in eventData); - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterApi.cs index eaf5fbd6f..3a25993eb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterApi.cs @@ -28,6 +28,7 @@ public class GateLifterApi : IApi, IApiCoilDevice, IApiWireDeviceDest public event EventHandler Init; private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; private readonly GateComponent _gateComponent; private readonly GateLifterComponent _gateLifterComponent; private readonly GateColliderComponent _gateColliderComponent; @@ -35,12 +36,13 @@ public class GateLifterApi : IApi, IApiCoilDevice, IApiWireDeviceDest private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); private GateApi _gateApi; - internal GateLifterApi(GameObject go, Player player) + internal GateLifterApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _gateComponent = go.GetComponent(); _gateColliderComponent = go.GetComponent(); _gateLifterComponent = go.GetComponent(); _player = player; + _physicsEngine = physicsEngine; } void IApi.OnInit(BallManager ballManager) @@ -72,8 +74,7 @@ private void OnLifterCoilEnabled() Logger.Warn("Lifter coil enabled, but gate collider not found."); return; } - - _gateApi.IsCollidable = false; + _physicsEngine.DisableCollider(((ICollidableComponent)_gateColliderComponent).ItemId); _gateApi.Lift(_gateLifterComponent.AnimationSpeed, _gateLifterComponent.LiftedAngleDeg); } @@ -84,7 +85,7 @@ private void OnLifterCoilDisabled() return; } - _gateApi.IsCollidable = true; + _physicsEngine.EnableCollider(((ICollidableComponent)_gateColliderComponent).ItemId); _gateApi.Lift(_gateLifterComponent.AnimationSpeed, 0f); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterComponent.cs index bb7d970f0..53991eb86 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateLifterComponent.cs @@ -49,9 +49,15 @@ public class GateLifterComponent : MonoBehaviour, ICoilDeviceComponent #endregion + public GateLifterApi GateLifterApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterGateLifter(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + GateLifterApi = new GateLifterApi(gameObject, player, physicsEngine); + + player.Register(GateLifterApi, this); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementState.cs index c40586b53..7af0d54bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct GateMovementData : IComponentData + internal struct GateMovementState { public float Angle; public float AngleSpeed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs.meta deleted file mode 100644 index 6f112c9dc..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateMovementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5fecec9bc1c0ce844853a4ab162a7dfd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs similarity index 67% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs index 1e0635ab5..4e017f528 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/DebugUI/DebugFlipperState.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs @@ -1,34 +1,34 @@ -// 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.Entities; - -namespace VisualPinball.Unity -{ - public struct DebugFlipperState - { - public Entity Entity; - public float Angle; - public bool Solenoid; - - public DebugFlipperState(Entity entity, float angle, bool solenoid) - { - Entity = entity; - Angle = angle; - Solenoid = solenoid; - } - } -} +// 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 +{ + internal struct GateState + { + internal readonly int ItemId; + internal readonly int WireItemId; + internal GateStaticState Static; + internal GateMovementState Movement; + + public GateState(int itemId, int wireItemId, GateStaticState @static, GateMovementState movement) + { + ItemId = itemId; + WireItemId = wireItemId; + Static = @static; + Movement = movement; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs.meta new file mode 100644 index 000000000..08e2bb8a7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: baf88e2f669140a6ae69edc71cb69619 +timeCreated: 1696494786 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticState.cs index 4eb9a0194..5f9f02b28 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct GateStaticData : IComponentData + internal struct GateStaticState { public float AngleMin; public float AngleMax; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs new file mode 100644 index 000000000..5765b77f0 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs @@ -0,0 +1,41 @@ +// 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 CompareOfFloatsByEqualityOperator + +using Unity.Mathematics; +using VisualPinball.Engine.Common; + +namespace VisualPinball.Unity +{ + internal static class GateVelocityPhysics + { + internal static void UpdateVelocities(ref GateMovementState movementState, in GateStaticState state) + { + if (!movementState.IsOpen) { + if (math.abs(movementState.Angle) < state.AngleMin + 0.01f && math.abs(movementState.AngleSpeed) < 0.01f) { + // stop a bit earlier to prevent a nearly endless animation (especially for slow balls) + movementState.Angle = state.AngleMin; + movementState.AngleSpeed = 0.0f; + } + if (math.abs(movementState.AngleSpeed) != 0.0f && movementState.Angle != state.AngleMin) { + movementState.AngleSpeed -= math.sin(movementState.Angle) * state.GravityFactor * (float)(PhysicsConstants.PhysFactor / 100.0); // Center of gravity towards bottom of object, makes it stop vertical + movementState.AngleSpeed *= state.Damping; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocitySystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocitySystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocityPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocitySystem.cs deleted file mode 100644 index c26d0a073..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Gate/GateVelocitySystem.cs +++ /dev/null @@ -1,57 +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 CompareOfFloatsByEqualityOperator - -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using VisualPinball.Engine.Common; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateVelocitiesSystemGroup))] - internal class GateVelocitySystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("GateVelocitySystem"); - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities - .WithName("GateVelocityJob") - .ForEach((ref GateMovementData movementData, in GateStaticData data) => { - - marker.Begin(); - - if (!movementData.IsOpen) { - if (math.abs(movementData.Angle) < data.AngleMin + 0.01f && math.abs(movementData.AngleSpeed) < 0.01f) { - // stop a bit earlier to prevent a nearly endless animation (especially for slow balls) - movementData.Angle = data.AngleMin; - movementData.AngleSpeed = 0.0f; - } - if (math.abs(movementData.AngleSpeed) != 0.0f && movementData.Angle != data.AngleMin) { - movementData.AngleSpeed -= math.sin(movementData.Angle) * data.GravityFactor * (float)(PhysicsConstants.PhysFactor / 100.0); // Center of gravity towards bottom of object, makes it stop vertical - movementData.AngleSpeed *= data.Damping; - } - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimation.cs new file mode 100644 index 000000000..5e95675c7 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimation.cs @@ -0,0 +1,75 @@ +// 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.Game; + +namespace VisualPinball.Unity +{ + internal static class DropTargetAnimation + { + internal static void Update(int itemId, ref DropTargetAnimationState animation, in DropTargetStaticState staticState, + ref PhysicsState state) + { + var oldTimeMsec = animation.TimeMsec < state.Env.TimeMsec ? animation.TimeMsec : state.Env.TimeMsec; + animation.TimeMsec = state.Env.TimeMsec; + var diffTimeMsec = (float)(state.Env.TimeMsec - oldTimeMsec); + + if (animation.HitEvent) { + if (!animation.IsDropped) { + animation.MoveDown = true; + } + + animation.MoveAnimation = true; + animation.HitEvent = false; + } + + if (animation.MoveAnimation) { + var step = staticState.Speed; + + if (animation.MoveDown) { + step = -step; + + } else if (animation.TimeMsec - animation.TimeStamp < (uint) staticState.RaiseDelay) { + step = 0.0f; + } + + animation.ZOffset += step * diffTimeMsec; + if (animation.MoveDown) { + if (animation.ZOffset <= -DropTargetAnimationState.DropTargetLimit) { + animation.ZOffset = -DropTargetAnimationState.DropTargetLimit; + animation.MoveDown = false; + animation.IsDropped = true; + animation.MoveAnimation = false; + animation.TimeStamp = 0; + if (staticState.UseHitEvent) { + state.EventQueue.Enqueue(new EventData(EventId.TargetEventsDropped, itemId)); + } + } + + } else { + if (animation.ZOffset >= 0.0f) { + animation.ZOffset = 0.0f; + animation.MoveAnimation = false; + animation.IsDropped = false; + if (staticState.UseHitEvent) { + state.EventQueue.Enqueue(new EventData(EventId.TargetEventsRaised, itemId)); + } + } + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationState.cs index 71e320e64..2cc6d2c50 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct DropTargetAnimationData : IComponentData + internal struct DropTargetAnimationState { public const float DropTargetLimit = 52.0f; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationSystem.cs deleted file mode 100644 index f05ae26a5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetAnimationSystem.cs +++ /dev/null @@ -1,116 +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.Collections; -using Unity.Entities; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Game; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class DropTargetAnimationSystem : SystemBase - { - private Player _player; - private VisualPinballSimulationSystemGroup _visualPinballSimulationSystemGroup; - private NativeQueue _eventQueue; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("HitTargetAnimationSystem"); - - protected override void OnCreate() - { - _player = Object.FindObjectOfType(); - _visualPinballSimulationSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - var timeMsec = _visualPinballSimulationSystemGroup.TimeMsec; - var events = _eventQueue.AsParallelWriter(); - var marker = PerfMarker; - - Entities - .WithName("HitTargetAnimationJob") - .ForEach((Entity entity, ref DropTargetAnimationData data, in DropTargetStaticData staticData) => - { - marker.Begin(); - - var oldTimeMsec = data.TimeMsec < timeMsec ? data.TimeMsec : timeMsec; - data.TimeMsec = timeMsec; - var diffTimeMsec = (float)(timeMsec - oldTimeMsec); - - if (data.HitEvent) { - if (!data.IsDropped) { - data.MoveDown = true; - } - - data.MoveAnimation = true; - data.HitEvent = false; - } - - if (data.MoveAnimation) { - var step = staticData.Speed; - - if (data.MoveDown) { - step = -step; - - } else if (data.TimeMsec - data.TimeStamp < (uint) staticData.RaiseDelay) { - step = 0.0f; - } - - data.ZOffset += step * diffTimeMsec; - if (data.MoveDown) { - if (data.ZOffset <= -DropTargetAnimationData.DropTargetLimit) { - data.ZOffset = -DropTargetAnimationData.DropTargetLimit; - data.MoveDown = false; - data.IsDropped = true; - data.MoveAnimation = false; - data.TimeStamp = 0; - if (staticData.UseHitEvent) { - events.Enqueue(new EventData(EventId.TargetEventsDropped, entity)); - } - } - - } else { - if (data.ZOffset >= 0.0f) { - data.ZOffset = 0.0f; - data.MoveAnimation = false; - data.IsDropped = false; - if (staticData.UseHitEvent) { - events.Enqueue(new EventData(EventId.TargetEventsRaised, entity)); - } - } - } - } - - marker.End(); - - }).Run(); - - // dequeue events - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(in eventData); - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs index 959d5566d..66bc87543 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -51,19 +50,18 @@ public class DropTargetApi : CollidableApiThrown if target is not a drop target (but a hit target, which can't be dropped) public bool IsDropped { - get => EntityManager.GetComponentData(Entity).IsDropped; + get => PhysicsEngine.DropTargetState(ItemId).Animation.IsDropped; set => SetIsDropped(value); } - internal DropTargetApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + internal DropTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } - public void OnDropStatusChanged(bool isDropped, Entity ballEntity) + public void OnDropStatusChanged(bool isDropped, int ballId) { OnSwitch(isDropped); - Switch?.Invoke(this, new SwitchEventArgs(isDropped, ballEntity)); + Switch?.Invoke(this, new SwitchEventArgs(isDropped, ballId)); } /// @@ -73,21 +71,19 @@ public void OnDropStatusChanged(bool isDropped, Entity ballEntity) /// private void SetIsDropped(bool isDropped) { - var data = EntityManager.GetComponentData(Entity); - if (data.IsDropped != isDropped) { - data.MoveAnimation = true; + ref var state = ref PhysicsEngine.DropTargetState(ItemId); + if (state.Animation.IsDropped != isDropped) { + state.Animation.MoveAnimation = true; if (isDropped) { - data.MoveDown = true; + state.Animation.MoveDown = true; } else { - data.MoveDown = false; - data.TimeStamp = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem().TimeMsec; + state.Animation.MoveDown = false; + state.Animation.TimeStamp = PhysicsEngine.TimeMsec; } } else { - data.IsDropped = isDropped; + state.Animation.IsDropped = isDropped; } - - EntityManager.SetComponentData(Entity, data); } #region Wiring @@ -106,10 +102,10 @@ private void SetIsDropped(bool isDropped) protected override bool FireHitEvents => true; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new DropTargetColliderGenerator(this, MainComponent, MainComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, colliders); + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders); } #endregion @@ -126,9 +122,9 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs index dc262808b..bda149905 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -62,7 +60,7 @@ public class DropTargetColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new DropTargetApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => (MainComponent as DropTargetComponent)?.DropTargetApi ?? new DropTargetApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs index 97c2cf3ce..bf07c0cee 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetColliderGenerator.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System.Collections.Generic; +using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -28,7 +29,7 @@ public DropTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, { } - internal void GenerateColliders(float playfieldHeight, ICollection colliders) + internal void GenerateColliders(float playfieldHeight, ref ColliderReference colliders) { var localToPlayfield = MeshGenerator.GetTransformationMatrix(); @@ -43,7 +44,7 @@ internal void GenerateColliders(float playfieldHeight, ICollection co GenerateCollidables(hitMesh, addedEdges, Data.IsLegacy, colliders); */ - var addedEdges = EdgeSet.Get(); + var addedEdges = EdgeSet.Get(Allocator.TempJob); var tempMatrix = new Matrix3D().RotateZMatrix(MathF.DegToRad(Data.RotZ)); var fullMatrix = new Matrix3D().Multiply(tempMatrix); @@ -108,6 +109,8 @@ internal void GenerateColliders(float playfieldHeight, ICollection co colliders.Add(new PointCollider(rgv3D[i].ToUnityFloat3(), GetColliderInfo(true))); } } + + addedEdges.Dispose(); } private static readonly float3[] DropTargetHitPlaneVertices = { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs index 2535133de..03ac170e3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetComponent.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; using VisualPinball.Engine.VPT.Table; @@ -27,7 +26,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Drop Target")] - public class DropTargetComponent : TargetComponent, IConvertGameObjectToEntity + public class DropTargetComponent : TargetComponent { public override bool IsLegacy { get { @@ -39,37 +38,12 @@ public override bool IsLegacy { protected override float ZOffset { get { var animationComponent = GetComponentInChildren(); - return animationComponent && animationComponent.IsDropped ? -DropTargetAnimationData.DropTargetLimit : 0f; + return animationComponent && animationComponent.IsDropped ? -DropTargetAnimationState.DropTargetLimit : 0f; } } #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - var colliderComponent = GetComponent(); - var animationComponent = GetComponentInChildren(); - if (colliderComponent && animationComponent) { - - dstManager.AddComponentData(entity, new DropTargetStaticData { - Speed = animationComponent.Speed, - RaiseDelay = animationComponent.RaiseDelay, - UseHitEvent = colliderComponent.UseHitEvent, - }); - - dstManager.AddComponentData(entity, new DropTargetAnimationData { - IsDropped = animationComponent.IsDropped, - MoveDown = !animationComponent.IsDropped, - ZOffset = animationComponent.IsDropped ? -DropTargetAnimationData.DropTargetLimit : 0f - }); - } - - // register - transform.GetComponentInParent().RegisterDropTarget(this, entity); - } - public override IEnumerable SetData(HitTargetData data) { var updatedComponents = base.SetData(data).ToList(); @@ -153,5 +127,54 @@ public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNa } #endregion + + #region Runtime + + public DropTargetApi DropTargetApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + DropTargetApi = new DropTargetApi(gameObject, player, physicsEngine); + + player.Register(DropTargetApi, this); + if (GetComponent() && GetComponentInChildren()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + + #region State + + internal DropTargetState CreateState() + { + var colliderComponent = GetComponent(); + var animationComponent = GetComponentInChildren(); + + var staticData = colliderComponent && animationComponent + ? new DropTargetStaticState { + Speed = animationComponent.Speed, + RaiseDelay = animationComponent.RaiseDelay, + UseHitEvent = colliderComponent.UseHitEvent, + } : default; + + var animationData = colliderComponent && animationComponent + ? new DropTargetAnimationState { + IsDropped = animationComponent.IsDropped, + MoveDown = !animationComponent.IsDropped, + ZOffset = animationComponent.IsDropped ? -DropTargetAnimationState.DropTargetLimit : 0f + } : default; + + return new DropTargetState( + colliderComponent && animationComponent ? gameObject.GetInstanceID() : 0, + animationComponent ? animationComponent.gameObject.GetInstanceID() : 0, + staticData, + animationData + ); + } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs similarity index 64% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs index 4f4dae0e7..b5434468e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingDynamicBufferElement.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs @@ -1,36 +1,34 @@ -// 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.Entities; - -namespace VisualPinball.Unity -{ - [InternalBufferCapacity(0)] - internal struct OverlappingDynamicBufferElement : IBufferElementData - { - public Entity Value; - - public static implicit operator Entity(OverlappingDynamicBufferElement e) - { - return e.Value; - } - - public static implicit operator OverlappingDynamicBufferElement(Entity e) - { - return new OverlappingDynamicBufferElement { Value = e }; - } - } -} +// 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 +{ + internal struct DropTargetState + { + internal readonly int ItemId; + internal readonly int AnimatedItemId; + internal DropTargetStaticState Static; + internal DropTargetAnimationState Animation; + + public DropTargetState(int itemId, int animatedItemId, DropTargetStaticState @static, DropTargetAnimationState animation) + { + ItemId = itemId; + AnimatedItemId = animatedItemId; + Static = @static; + Animation = animation; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs.meta new file mode 100644 index 000000000..0a6db4fdd --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e34f6f6716214c678cba4e902a46b810 +timeCreated: 1696495888 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticState.cs index 931feac11..dceafc631 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct DropTargetStaticData : IComponentData + internal struct DropTargetStaticState { public float Speed; public float RaiseDelay; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs deleted file mode 100644 index 31d4f1f9f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs +++ /dev/null @@ -1,54 +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.Entities; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class DropTargetTransformationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker(nameof(DropTargetTransformationSystem)); - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("DropTargetTransformationJob").ForEach((Entity entity, in DropTargetAnimationData data) => - { - marker.Begin(); - - var localPos = _player.DropTargetTransforms[entity].localPosition; - _player.DropTargetTransforms[entity].localPosition = new Vector3( - localPos.x, - Physics.ScaleToWorld(data.ZOffset), - localPos.z - ); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs.meta deleted file mode 100644 index 54b09480c..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/DropTargetTransformationSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4a74e339b9e173d468ec0e0bd1d7401c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimation.cs new file mode 100644 index 000000000..b3a9029b6 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimation.cs @@ -0,0 +1,56 @@ +// 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 +{ + internal static class HitTargetAnimation + { + internal static void Update(ref HitTargetAnimationData data, in HitTargetStaticData staticData, + uint timeMsec) + { + var oldTimeMsec = data.TimeMsec < timeMsec ? data.TimeMsec : timeMsec; + data.TimeMsec = timeMsec; + var diffTimeMsec = (float)(timeMsec - oldTimeMsec); + + if (data.HitEvent) { + data.MoveAnimation = true; + data.HitEvent = false; + } + + if (data.MoveAnimation) { + var step = staticData.Speed; + if (!data.MoveDirection) { + step = -step; + } + + data.XRotation += step * diffTimeMsec; + if (data.MoveDirection) { + if (data.XRotation >= staticData.MaxAngle) { + data.XRotation = staticData.MaxAngle; + data.MoveDirection = false; + } + + } else { + if (data.XRotation <= 0.0f) { + data.XRotation = 0.0f; + data.MoveAnimation = false; + data.MoveDirection = true; + } + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs index adf29abba..2f7e46ca9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationData.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct HitTargetAnimationData : IComponentData + internal struct HitTargetAnimationData { public float XRotation; public bool HitEvent; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationSystem.cs deleted file mode 100644 index 63191eba2..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetAnimationSystem.cs +++ /dev/null @@ -1,89 +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.Collections; -using Unity.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class HitTargetAnimationSystem : SystemBase - { - private VisualPinballSimulationSystemGroup _visualPinballSimulationSystemGroup; - private NativeQueue _eventQueue; - - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker(nameof(HitTargetAnimationSystem)); - - protected override void OnCreate() - { - _visualPinballSimulationSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - var timeMsec = _visualPinballSimulationSystemGroup.TimeMsec; - var marker = PerfMarker; - - Entities - .WithName("HitTargetAnimationJob") - .ForEach((Entity entity, ref HitTargetAnimationData data, in HitTargetStaticData staticData) => - { - marker.Begin(); - - var oldTimeMsec = data.TimeMsec < timeMsec ? data.TimeMsec : timeMsec; - data.TimeMsec = timeMsec; - var diffTimeMsec = (float)(timeMsec - oldTimeMsec); - - if (data.HitEvent) { - data.MoveAnimation = true; - data.HitEvent = false; - } - - if (data.MoveAnimation) { - var step = staticData.Speed; - if (!data.MoveDirection) { - step = -step; - } - - data.XRotation += step * diffTimeMsec; - if (data.MoveDirection) { - if (data.XRotation >= staticData.MaxAngle) { - data.XRotation = staticData.MaxAngle; - data.MoveDirection = false; - } - - } else { - if (data.XRotation <= 0.0f) { - data.XRotation = 0.0f; - data.MoveAnimation = false; - data.MoveDirection = true; - } - } - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs index 9ed73a566..ae3818139 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -40,8 +39,7 @@ public class HitTargetApi : CollidableApi public event EventHandler Switch; - internal HitTargetApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + internal HitTargetApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -61,10 +59,10 @@ internal HitTargetApi(GameObject go, Entity entity, Player player) protected override bool FireHitEvents => true; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new HitTargetColliderGenerator(this, MainComponent, MainComponent); - colliderGenerator.GenerateColliders(colliders); + colliderGenerator.GenerateColliders(ref colliders); } #endregion @@ -81,10 +79,10 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); OnSwitch(true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs index a222e4969..75d9bcf5f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; @@ -56,7 +54,7 @@ public class HitTargetColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new HitTargetApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => (MainComponent as HitTargetComponent)?.HitTargetApi ?? new HitTargetApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs index eeaa082df..d7eb3b77c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetColliderGenerator.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System.Collections.Generic; +using Unity.Collections; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.HitTarget; @@ -26,15 +27,16 @@ public HitTargetColliderGenerator(IApiColliderGenerator api, ITargetData data, I { } - internal void GenerateColliders(List colliders) + 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(); - GenerateCollidables(hitMesh, addedEdges, true, colliders); + var addedEdges = EdgeSet.Get(Allocator.TempJob); + GenerateCollidables(hitMesh, ref addedEdges, true, ref colliders); + addedEdges.Dispose(); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs index 576a51bdc..2034b65a4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetComponent.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.HitTarget; using VisualPinball.Engine.VPT.Table; @@ -25,7 +24,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Hit Target")] - public class HitTargetComponent : TargetComponent, IConvertGameObjectToEntity + public class HitTargetComponent : TargetComponent { protected override float ZOffset => 0; @@ -33,28 +32,6 @@ public class HitTargetComponent : TargetComponent, IConvertGameObjectToEntity #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - var hitTargetColliderComponent = GetComponent(); - var hitTargetAnimationComponent = GetComponentInChildren(); - if (hitTargetColliderComponent && hitTargetAnimationComponent) { - - dstManager.AddComponentData(entity, new HitTargetStaticData { - Speed = hitTargetAnimationComponent.Speed, - MaxAngle = hitTargetAnimationComponent.MaxAngle, - }); - - dstManager.AddComponentData(entity, new HitTargetAnimationData { - MoveDirection = true, - }); - } - - // register - transform.GetComponentInParent().RegisterHitTarget(this, entity); - } - public override IEnumerable SetData(HitTargetData data) { var updatedComponents = base.SetData(data).ToList(); @@ -130,5 +107,50 @@ public override HitTargetData CopyDataTo(HitTargetData data, string[] materialNa } #endregion + + #region Runtime + + public HitTargetApi HitTargetApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + HitTargetApi = new HitTargetApi(gameObject, player, physicsEngine); + + player.Register(HitTargetApi, this); + if (GetComponent() && GetComponentInChildren()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + + #region State + + internal HitTargetState CreateState() + { + var hitTargetColliderComponent = GetComponent(); + var hitTargetAnimationComponent = GetComponentInChildren(); + var staticData = hitTargetColliderComponent && hitTargetAnimationComponent + ? new HitTargetStaticData { + Speed = hitTargetAnimationComponent.Speed, + MaxAngle = hitTargetAnimationComponent.MaxAngle, + } : default; + + var animationData = hitTargetColliderComponent && hitTargetAnimationComponent + ? new HitTargetAnimationData { + MoveDirection = true, + } : default; + + return new HitTargetState( + hitTargetColliderComponent && hitTargetAnimationComponent ? gameObject.GetInstanceID() : 0, + hitTargetAnimationComponent ? hitTargetAnimationComponent.gameObject.GetInstanceID() : 0, + staticData, + animationData + ); + } + + #endregion } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs similarity index 65% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs index eee4535e6..5938e876f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/BallColliderBounds.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs @@ -1,37 +1,34 @@ -// 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.Entities; - -namespace VisualPinball.Unity -{ - internal struct BallColliderBounds - { - public Entity BallEntity; - public Aabb Aabb; - - public BallColliderBounds(Entity ballEntity, Aabb aabb) - { - BallEntity = ballEntity; - Aabb = aabb; - } - - public override string ToString() - { - return $"{Aabb.ToString()} ({BallEntity})"; - } - } -} +// 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 +{ + internal struct HitTargetState + { + internal readonly int ItemId; + internal readonly int AnimatedItemId; + internal HitTargetStaticData Static; + internal HitTargetAnimationData Animation; + + public HitTargetState(int itemId, int animatedItemId, HitTargetStaticData @static, HitTargetAnimationData animation) + { + ItemId = itemId; + AnimatedItemId = animatedItemId; + Static = @static; + Animation = animation; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs.meta new file mode 100644 index 000000000..3d592f254 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a3893ee528fa4c5c8f3b0d8007999290 +timeCreated: 1696497154 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs index 51553bf11..4e3e02271 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetStaticData.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct HitTargetStaticData : IComponentData + internal struct HitTargetStaticData { public float Speed; public float MaxAngle; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs deleted file mode 100644 index 6f2925a3f..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs +++ /dev/null @@ -1,55 +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.Entities; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class HitTargetTransformationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker(nameof(HitTargetTransformationSystem)); - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("HitTargetTransformationJob").ForEach((Entity entity, - in HitTargetAnimationData data, in HitTargetStaticData staticData) => - { - marker.Begin(); - - var localRot = _player.HitTargetTransforms[entity].transform.localEulerAngles; - _player.HitTargetTransforms[entity].transform.localEulerAngles = new Vector3( - data.XRotation, - localRot.y, - localRot.z - ); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs.meta deleted file mode 100644 index eeec43b97..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/HitTargetTransformationSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4c4dddeb8a2b43243a7b6af2941accdd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetCollider.cs index 086f07dcb..4b92c7c00 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetCollider.cs @@ -15,42 +15,41 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { internal static class TargetCollider { - public static void DropTargetCollide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - ref DropTargetAnimationData animationData, in float3 normal, in Entity ballEntity, in CollisionEventData collEvent, - in Collider coll, ref Random random) + public static void DropTargetCollide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + ref DropTargetAnimationState animation, in float3 normal, in CollisionEventData collEvent, + in ColliderHeader collHeader, ref Random random) { - if (animationData.IsDropped) { + if (animation.IsDropped) { return; } var dot = -math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in coll.Header.Material, in collEvent, in normal, ref random); + BallCollider.Collide3DWall(ref ball, in collHeader.Material, in collEvent, in normal, ref random); - if (coll.FireEvents && dot >= coll.Threshold && !animationData.IsDropped) { - animationData.HitEvent = true; + if (collHeader.FireEvents && dot >= collHeader.Threshold && !animation.IsDropped) { + animation.HitEvent = true; //todo m_obj->m_currentHitThreshold = dot; - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in coll.Header); + Collider.FireHitEvent(ref ball, ref hitEvents, in collHeader); } } - public static void HitTargetCollide(ref BallData ball, ref NativeQueue.ParallelWriter hitEvents, - ref HitTargetAnimationData animationData, in float3 normal, in Entity ballEntity, in CollisionEventData collEvent, - in Collider coll, ref Random random) + public static void HitTargetCollide(ref BallState ball, ref NativeQueue.ParallelWriter hitEvents, + ref HitTargetAnimationData animationData, in float3 normal, in CollisionEventData collEvent, + in ColliderHeader collHeader, ref Random random) { var dot = -math.dot(collEvent.HitNormal, ball.Velocity); - BallCollider.Collide3DWall(ref ball, in coll.Header.Material, in collEvent, in normal, ref random); + BallCollider.Collide3DWall(ref ball, in collHeader.Material, in collEvent, in normal, ref random); - if (coll.FireEvents && dot >= coll.Threshold) { + if (collHeader.FireEvents && dot >= collHeader.Threshold) { animationData.HitEvent = true; //todo m_obj->m_currentHitThreshold = dot; - Collider.FireHitEvent(ref ball, ref hitEvents, in ballEntity, in coll.Header); + Collider.FireHitEvent(ref ball, ref hitEvents, in collHeader); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs index 91eefacc4..c1120b01a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/HitTarget/TargetColliderGenerator.cs @@ -33,7 +33,7 @@ protected TargetColliderGenerator(IApiColliderGenerator api, ITargetData data, I MeshGenerator = meshGenerator; } - private protected void GenerateCollidables(Mesh hitMesh, EdgeSet addedEdges, bool setHitObject, ICollection colliders) { + 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) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs index 0f6fb7f95..74f3816ad 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IApi.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.Game.Engines; @@ -44,7 +42,7 @@ public interface IApiColliderGenerator /// /// List to add colliders to. /// - void CreateColliders(List colliders, float margin); + void CreateColliders(ref ColliderReference colliders, float margin); /// /// Computes collider info based on the component data. @@ -52,16 +50,6 @@ public interface IApiColliderGenerator /// ColliderInfo GetColliderInfo(); - /// - /// The entity of the game item - /// - Entity ColliderEntity { get; } - - /// - /// If false, this will be included in the quad tree but marked as inactive. - /// - bool IsColliderEnabled { get; } - /// /// If false, this won't be included in the quad tree. /// @@ -78,7 +66,7 @@ public interface IApiHittable /// /// Which ball /// Whether it exited the hittable area - void OnHit(Entity ballEntity, bool isUnHit = false); + void OnHit(int ballId, bool isUnHit = false); /// /// Public event to subscribe to for hits. @@ -104,7 +92,7 @@ internal interface IApiRotatable /// internal interface IApiCollidable { - void OnCollide(Entity ballEntity, float hit); + void OnCollide(int ballId, float hit); } /// @@ -120,7 +108,7 @@ internal interface IApiSpinnable /// internal interface IApiSlingshot { - void OnSlingshot(Entity ballEntity); + void OnSlingshot(int ballId); } /// @@ -128,7 +116,7 @@ internal interface IApiSlingshot /// internal interface IApiDroppable { - void OnDropStatusChanged(bool isDropped, Entity ballEntity); + void OnDropStatusChanged(bool isDropped, int ballId); } /// diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs similarity index 79% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs index 05d4b43fd..3367c18ea 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/OverlappingStaticBufferElement.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs @@ -1,26 +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 Unity.Entities; - -namespace VisualPinball.Unity -{ - [InternalBufferCapacity(0)] - internal struct OverlappingStaticColliderBufferElement : IBufferElementData - { - public int Value; - } -} +// 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 ICollidableComponent + { + internal void GetColliders(Player player, ref ColliderReference colliders, float margin); + internal int ItemId { get; } + internal bool IsCollidable { get; } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs.meta new file mode 100644 index 000000000..96fb59c5d --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ICollidableComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d18da5a17a8b436db01731cafce44a07 +timeCreated: 1678721087 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs index 75c526700..7eb40db1f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/IMainRenderableComponent.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 Unity.Entities; using UnityEngine; namespace VisualPinball.Unity @@ -55,7 +54,6 @@ public interface IMainRenderableComponent : IMainComponent void SetEditorRotation(Vector3 pos); ItemDataTransformType EditorScaleType { get; } - Entity Entity { get; set; } Vector3 GetEditorScale(); void SetEditorScale(Vector3 pos); void EditorStartScaling(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs index 5360f176a..83569970f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemApi.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 Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT; @@ -41,19 +40,19 @@ public abstract class ItemApi private protected TableApi TableApi => Player.TableApi; - internal VisualPinballSimulationSystemGroup SimulationSystemGroup => World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); - private protected readonly Player Player; + private protected readonly PhysicsEngine PhysicsEngine; private protected readonly SwitchHandler SwitchHandler; private protected BallManager BallManager; private protected TableComponent TableComponent; - protected ItemApi(GameObject go, Player player) + protected ItemApi(GameObject go, Player player, PhysicsEngine physicsEngine) { GameObject = go; MainComponent = go.GetComponent(); Player = player; - SwitchHandler = new SwitchHandler(Name, player); + SwitchHandler = new SwitchHandler(Name, player, physicsEngine); + PhysicsEngine = physicsEngine; } protected void OnInit(BallManager ballManager) @@ -64,7 +63,7 @@ protected void OnInit(BallManager ballManager) #region IApiSwitchable - private protected DeviceSwitch CreateSwitch(string name, bool isPulseSwitch, SwitchDefault switchDefault = SwitchDefault.Configurable) => new DeviceSwitch(name, isPulseSwitch, switchDefault, Player); + private protected DeviceSwitch CreateSwitch(string name, bool isPulseSwitch, SwitchDefault switchDefault = SwitchDefault.Configurable) => new DeviceSwitch(name, isPulseSwitch, switchDefault, Player, PhysicsEngine); private protected IApiSwitchStatus AddSwitchDest(SwitchConfig switchConfig,IApiSwitchStatus switchStatus) => SwitchHandler.AddSwitchDest(switchConfig, switchStatus); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs index 557f09a90..961150f40 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs @@ -16,7 +16,9 @@ // ReSharper disable InconsistentNaming +using NLog; using UnityEngine; +using Logger = NLog.Logger; namespace VisualPinball.Unity { @@ -27,6 +29,19 @@ public abstract class ItemComponent : MonoBehaviour { public abstract string ItemName { get; } + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + + protected void RegisterPhysics(PhysicsEngine physicsEngine) + { + if (physicsEngine) { + physicsEngine.Register(this); + } else { + Logger.Error("No physics engine found in parent hierarchy."); + } + } + + protected void RegisterPhysics() => RegisterPhysics(GetComponentInParent()); + protected static void DrawArrow(Vector3 pos, Vector3 direction, float arrowHeadLength = 0.025f, float arrowHeadAngle = 20.0f) { Debug.DrawRay(pos, direction); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs index 2d387eacd..94b41e856 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerApi.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Kicker; @@ -54,8 +53,7 @@ public class KickerApi : CollidableApi _coils = new Dictionary(); - public KickerApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + public KickerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { foreach (var coil in MainComponent.Coils) { _coils[coil.Id] = new KickerDeviceCoil(player, coil, this); @@ -72,24 +70,27 @@ void IApi.OnDestroy() { } - public void CreateBall() + public void CreateBall(GameObject ballPrefab = null, float radius = 25f, float mass = 1f) { - BallManager.CreateBall(MainComponent, 25f, 1f, Entity); - } + var ballId = BallManager.CreateBall(MainComponent, radius, mass, ballPrefab); - public void CreateSizedBallWithMass(float radius, float mass) - { - BallManager.CreateBall(MainComponent, radius, mass, Entity); + ref var ball = ref PhysicsEngine.BallState(ballId); + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + 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, + in kickerState.Static, in kickerState.CollisionMesh, in ball.CollisionEvent, ItemId, true); } - public void CreateSizedBall(float radius) + public void CreateSizedBallWithMass(float radius, float mass) { - BallManager.CreateBall(MainComponent, radius, 1f, Entity); + BallManager.CreateBall(MainComponent, radius, mass); } public void Kick(float angle, float speed, float inclination = 0) { - SimulationSystemGroup.QueueAfterBallCreation(() => KickXYZ(Entity, angle, speed, inclination, 0, 0, 0)); + KickXYZ(angle, speed, inclination, 0, 0, 0); } /// @@ -101,12 +102,10 @@ public void Kick(float angle, float speed, float inclination = 0) /// public void DestroyBall() { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(Entity); - var ballEntity = kickerCollisionData.BallEntity; - if (ballEntity != Entity.Null) { - BallManager.DestroyEntity(ballEntity); - SimulationSystemGroup.QueueAfterBallCreation(OnBallDestroyed); + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + if (kickerState.Collision.HasBall) { + BallManager.DestroyBall(kickerState.Collision.BallId); + OnBallDestroyed(); } } @@ -116,26 +115,20 @@ public void DestroyBall() /// True if there is a ball in the kicker, false otherwise. public bool HasBall() { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(Entity); - return kickerCollisionData.HasBall; + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + return kickerState.Collision.HasBall; } - internal BallData GetBallData() + internal ref BallState GetBallData() { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(Entity); - return kickerCollisionData.HasBall - ? entityManager.GetComponentData(kickerCollisionData.BallEntity) - : default; + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + return ref PhysicsEngine.BallState(kickerState.Collision.BallId); } - internal Entity BallEntity - { + internal int BallId { get { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(Entity); - return kickerCollisionData.BallEntity; + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + return kickerState.Collision.BallId; } } @@ -161,26 +154,19 @@ private IApiCoil Coil(string deviceItem) private void OnBallDestroyed() { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(Entity); - var ballEntity = kickerCollisionData.BallEntity; - if (ballEntity != Entity.Null) { - - // update kicker status - kickerCollisionData.BallEntity = Entity.Null; - entityManager.SetComponentData(Entity, kickerCollisionData); + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + if (kickerState.Collision.HasBall) { + kickerState.Collision.BallId = 0; } } #endregion - private void KickXYZ(Entity kickerEntity, float angle, float speed, float inclination, float x, float y, float z) + private void KickXYZ(float angle, float speed, float inclination, float x, float y, float z) { - var entityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - var kickerCollisionData = entityManager.GetComponentData(kickerEntity); - var kickerStaticData = entityManager.GetComponentData(kickerEntity); - var ballEntity = kickerCollisionData.BallEntity; - if (ballEntity != Entity.Null) { + ref var kickerState = ref PhysicsEngine.KickerState(ItemId); + var ballId = kickerState.Collision.BallId; + if (ballId != 0) { var angleRad = math.radians(angle); // yaw angle, zero is along -Y axis if (math.abs(inclination) > (float) (System.Math.PI / 2.0)) { @@ -189,7 +175,7 @@ private void KickXYZ(Entity kickerEntity, float angle, float speed, float inclin } // if < 0 use global value - var scatterAngle = kickerStaticData.Scatter < 0.0f ? 0.0f : math.radians(kickerStaticData.Scatter); + var scatterAngle = kickerState.Static.Scatter < 0.0f ? 0.0f : math.radians(kickerState.Static.Scatter); scatterAngle *= TableComponent.GlobalDifficulty; // apply difficulty weighting if (scatterAngle > 1.0e-5f) { // ignore near zero angles @@ -204,7 +190,7 @@ private void KickXYZ(Entity kickerEntity, float angle, float speed, float inclin } // update ball data - var ballData = entityManager.GetComponentData(ballEntity); + ref var ballData = ref PhysicsEngine.BallState(ballId); ballData.Position = new float3( ballData.Position.x + x, ballData.Position.y + y, @@ -217,21 +203,18 @@ private void KickXYZ(Entity kickerEntity, float angle, float speed, float inclin ); ballData.IsFrozen = false; ballData.AngularMomentum = float3.zero; - entityManager.SetComponentData(ballEntity, ballData); // update collision event - var collEvent = entityManager.GetComponentData(ballEntity); + ref var collEvent = ref ballData.CollisionEvent; collEvent.HitDistance = 0.0f; collEvent.HitTime = -1.0f; collEvent.HitNormal = float3.zero; collEvent.HitVelocity = float2.zero; collEvent.HitFlag = false; collEvent.IsContact = false; - entityManager.SetComponentData(ballEntity, collEvent); // update kicker status - kickerCollisionData.BallEntity = Entity.Null; - entityManager.SetComponentData(kickerEntity, kickerCollisionData); + kickerState.Collision.BallId = 0; } } @@ -241,7 +224,7 @@ private void KickXYZ(Entity kickerEntity, float angle, float speed, float inclin #region Collider Generation - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var height = MainComponent.PositionZ; @@ -256,16 +239,16 @@ protected override void CreateColliders(List colliders, float margin) #region Events - void IApiHittable.OnHit(Entity ballEntity, bool isUnHit) + void IApiHittable.OnHit(int ballId, bool isUnHit) { if (isUnHit) { - UnHit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(false, ballEntity)); + UnHit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(false, ballId)); OnSwitch(false); } else { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); OnSwitch(true); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs index c68ea88fc..e9cc0b8ec 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollider.cs @@ -15,7 +15,6 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Entities; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; @@ -29,107 +28,108 @@ internal static class KickerCollider /// resulting in stutter. Disabling this until we find another /// solution. /// - public const bool ForceLegacyMode = true; + public const bool ForceLegacyMode = false; - public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, - ref DynamicBuffer insideOfs, ref KickerCollisionData collData, - in KickerStaticData staticData, in ColliderMeshData meshData, in CollisionEventData collEvent, - in Entity collEntity, in Entity ballEntity) + public static void Collide(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) { // a previous ball already in kicker? - if (collData.HasBall) { + if (collState.HasBall) { return; } // ReSharper disable once ConditionIsAlwaysTrueOrFalse - var legacyMode = ForceLegacyMode || staticData.LegacyMode; + var legacyMode = ForceLegacyMode || staticState.LegacyMode; var hitNormal = collEvent.HitNormal; var hitBit = collEvent.HitFlag; // check if kicker in ball's volume set - var isBallInside = BallData.IsInsideOf(in insideOfs, collEntity); + var isBallInside = insideOfs.IsInsideOf(itemId, ball.Id); - // New or (Hit && !Vol || UnHit && Vol) - if (hitBit == isBallInside) { + // if "New or (Hit && !Vol || UnHit && Vol)", continue. + if (!newBall && hitBit != isBallInside) { + return; + } + if (legacyMode || newBall) { + ball.Position += PhysicsConstants.StaticTime * ball.Velocity; // move ball slightly forward + } - if (legacyMode) { - ball.Position += PhysicsConstants.StaticTime * ball.Velocity; // move ball slightly forward - } + // entering Kickers volume + if (!isBallInside) { + var grabHeight = (staticState.ZLow + ball.Radius) * staticState.HitAccuracy; - // entering Kickers volume - if (!isBallInside) { - var grabHeight = (staticData.ZLow + ball.Radius) * staticData.HitAccuracy; + // early out here if the ball is slow and we are near the kicker center + var hitEvent = ball.Position.z < grabHeight || legacyMode || newBall; - // early out here if the ball is slow and we are near the kicker center - var hitEvent = ball.Position.z < grabHeight || legacyMode; + if (!hitEvent) { - if (!hitEvent) { + DoChangeBallVelocity(ref ball, in hitNormal, in meshData); - DoChangeBallVelocity(ref ball, in hitNormal, in meshData); + // this is an ugly hack to prevent the ball stopping rapidly at the kicker bevel + // something with the friction calculation is wrong in the physics engine + // so we monitor the ball velocity if it drop under a length value of 0.2 + // if so we take the last "good" velocity to help the ball moving over the critical spot at the kicker bevel + // this hack seems to work only if the kicker is on the playfield, a kicker attached to a wall has still problems + // because the friction calculation for a wall is also different + if (math.lengthsq(ball.Velocity) < (float) (0.2 * 0.2)) { + ball.Velocity = ball.OldVelocity; + } - // this is an ugly hack to prevent the ball stopping rapidly at the kicker bevel - // something with the friction calculation is wrong in the physics engine - // so we monitor the ball velocity if it drop under a length value of 0.2 - // if so we take the last "good" velocity to help the ball moving over the critical spot at the kicker bevel - // this hack seems to work only if the kicker is on the playfield, a kicker attached to a wall has still problems - // because the friction calculation for a wall is also different - if (math.lengthsq(ball.Velocity) < (float) (0.2 * 0.2)) { - ball.Velocity = ball.OldVelocity; - } + ball.OldVelocity = ball.Velocity; - ball.OldVelocity = ball.Velocity; + } else { - } else { + ball.IsFrozen = !staticState.FallThrough; + if (ball.IsFrozen) { + insideOfs.SetInsideOf(itemId, ball.Id); // add kicker to ball's volume set + collState.BallId = ball.Id; + collState.LastCapturedBallId = ball.Id; + } - ball.IsFrozen = !staticData.FallThrough; - if (ball.IsFrozen) { - BallData.SetInsideOf(ref insideOfs, collEntity); // add kicker to ball's volume set - collData.BallEntity = ballEntity; - collData.LastCapturedBallEntity = ballEntity; - } - - // Fire the event before changing ball attributes, so scripters can get a useful ball state - events.Enqueue(new EventData(EventId.HitEventsHit, collEntity, ballEntity, true)); - - if (ball.IsFrozen || staticData.FallThrough) { // script may have unfrozen the ball - - // if ball falls through hole, we fake the collision algo by changing the ball height - // in HitTestBasicRadius() the z-position of the ball is checked if it is >= to the hit cylinder - // if we don't change the height of the ball we get a lot of hit events while the ball is falling!! - - // Only mess with variables if ball was not kicked during event - ball.Velocity = float3.zero; - ball.AngularMomentum = float3.zero; - var posZ = !staticData.FallIn - ? staticData.ZLow + ball.Radius * 2 - : staticData.FallThrough - ? staticData.ZLow - ball.Radius - 5.0f - : staticData.ZLow + ball.Radius; - ball.Position = new float3(staticData.Center.x, staticData.Center.y, posZ); - - } else { - collData.BallEntity = Entity.Null; // make sure - } + // Fire the event before changing ball attributes, so scripters can get a useful ball state + if (!newBall) { + events.Enqueue(new EventData(EventId.HitEventsHit, itemId, ball.Id, true)); } + if (ball.IsFrozen || staticState.FallThrough) { // script may have unfrozen the ball - } else { // exiting kickers volume - // remove kicker to ball's volume set - BallData.SetOutsideOf(ref insideOfs, collEntity); - events.Enqueue(new EventData(EventId.HitEventsUnhit, collEntity, ballEntity, true)); + // if ball falls through hole, we fake the collision algo by changing the ball height + // in HitTestBasicRadius() the z-position of the ball is checked if it is >= to the hit cylinder + // if we don't change the height of the ball we get a lot of hit events while the ball is falling!! + + // Only mess with variables if ball was not kicked during event + ball.Velocity = float3.zero; + ball.AngularMomentum = float3.zero; + var posZ = !staticState.FallIn + ? staticState.ZLow + 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); + + } else { + collState.BallId = 0; // make sure + } } + + + } else { // exiting kickers volume + // remove kicker to ball's volume set + insideOfs.SetOutsideOf(itemId, ball.Id); + events.Enqueue(new EventData(EventId.HitEventsUnhit, itemId, ball.Id, true)); } } - private static void DoChangeBallVelocity(ref BallData ball, in float3 hitNormal, in ColliderMeshData meshData) + private static void DoChangeBallVelocity(ref BallState ball, in float3 hitNormal, in ColliderMeshData meshData) { var minDistSqr = Constants.FloatMax; var idx = 0u; - ref var hitMesh = ref meshData.Value.Value.Vertices; - ref var hitMeshNormals = ref meshData.Value.Value.Normals; + var hitMesh = meshData.Vertices; + var hitMeshNormals = meshData.Normals; for (var t = 0; t < hitMesh.Length; t++) { // find the right normal by calculating the distance from current ball position to vertex of the kicker mesh - ref var vertex = ref hitMesh[t].Vertex; + ref var vertex = ref hitMesh.GetAsRef(t); var lengthSqr = math.lengthsq(ball.Position - vertex); if (lengthSqr < minDistSqr) { minDistSqr = lengthSqr; @@ -140,12 +140,12 @@ private static void DoChangeBallVelocity(ref BallData ball, in float3 hitNormal, if (idx != ~0u) { // we have the nearest vertex now use the normal and damp it so it doesn't speed up the ball velocity too much - ref var hitNorm = ref hitMeshNormals[(int)idx].Vertex; + ref var hitNorm = ref hitMeshNormals.GetAsRef((int)idx); var dot = -math.dot(ball.Velocity, hitNorm); var reactionImpulse = ball.Mass * math.abs(dot); var surfP = -ball.Radius * hitNormal; // surface contact point relative to center of mass - var surfVel = BallData.SurfaceVelocity(in ball, surfP); // velocity at impact point + var surfVel = BallState.SurfaceVelocity(in ball, surfP); // velocity at impact point var tangent = surfVel - math.dot(surfVel, hitNormal) * hitNorm; // calc the tangential velocity // apply collision impulse (along normal, so no torque) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs index 1c54bfac4..535c77a39 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Kicker; @@ -51,7 +49,7 @@ public class KickerColliderComponent : ColliderComponent GetPhysicsMaterialData(scatterAngleDeg: Scatter); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new KickerApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) => + MainComponent.KickerApi ?? new KickerApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderMeshData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderMeshData.cs index 144673475..01b0169f6 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderMeshData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerColliderMeshData.cs @@ -14,24 +14,57 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; +using System; +using System.Collections.Generic; +using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Mathematics; +using VisualPinball.Engine.Math; namespace VisualPinball.Unity { - internal struct ColliderMeshData : IComponentData - { - public BlobAssetReference Value; - } - public struct KickerMeshVertexBlobAsset + internal unsafe struct ColliderMeshData : IDisposable { - public BlobArray Vertices; - public BlobArray Normals; - } + [NativeDisableUnsafePtrRestriction] private void* _vertices; + [NativeDisableUnsafePtrRestriction] private void* _normals; - public struct KickerMeshVertex - { - public float3 Vertex; + private readonly int _length; + + private readonly Allocator _allocator; + + public ColliderMeshData(IList vertices, float radius, float3 position, Allocator allocator) + { + var rad = radius * 0.8f; + _length = UnsafeUtility.SizeOf() * vertices.Count; + _vertices = UnsafeUtility.Malloc(_length, UnsafeUtility.AlignOf(), allocator); + _normals = UnsafeUtility.Malloc(_length, UnsafeUtility.AlignOf(), allocator); + + for (var i = 0; i < vertices.Count; i++) { + UnsafeUtility.WriteArrayElement(_vertices, i, new float3( + vertices[i].X * rad + position.x, + vertices[i].Y * rad + position.y, + vertices[i].Z * rad + position.z + )); + UnsafeUtility.WriteArrayElement(_normals, i, new float3( + vertices[i].Nx, + vertices[i].Ny, + vertices[i].Nz + )); + } + _allocator = allocator; + } + + public UnmanagedArray Vertices => new(_vertices, _length); + public UnmanagedArray Normals => new(_normals, _length); + + public void Dispose() + { + UnsafeUtility.Free(_vertices, _allocator); + UnsafeUtility.Free(_normals, _allocator); + + _vertices = null; + _normals = null; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionData.cs deleted file mode 100644 index facf1ec24..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionData.cs +++ /dev/null @@ -1,28 +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.Entities; - -namespace VisualPinball.Unity -{ - internal struct KickerCollisionData : IComponentData - { - public Entity BallEntity; - public Entity LastCapturedBallEntity; - public bool HasBall => BallEntity != Entity.Null; - - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionState.cs similarity index 82% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionState.cs index 1d8326a1c..db6398b7d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/QuadTreeData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionState.cs @@ -14,12 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct QuadTreeData : IComponentData + internal struct KickerCollisionState { - public BlobAssetReference Value; + public int BallId; + public int LastCapturedBallId; + public bool HasBall => BallId != 0; + } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerCollisionState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs index de586fab7..8d2cd03b7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerComponent.cs @@ -23,9 +23,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using Unity.Entities; +using Unity.Collections; using Unity.Mathematics; +using UnityEditor; using UnityEngine; using VisualPinball.Engine.Game; using VisualPinball.Engine.Game.Engines; @@ -39,7 +41,7 @@ namespace VisualPinball.Unity [AddComponentMenu("Visual Pinball/Game Item/Kicker")] public class KickerComponent : MainRenderableComponent, ICoilDeviceComponent, ITriggerComponent, IBallCreationPosition, IOnSurfaceComponent, - IRotatableComponent, IConvertGameObjectToEntity, ISerializationCallbackReceiver + IRotatableComponent, ISerializationCallbackReceiver { #region Data @@ -144,12 +146,11 @@ public override void UpdateTransforms() private float _originalRotationZ; private float _originalKickerAngle; - private KickerApi _kickerApi; public float RotateZ { set { Orientation = _originalRotationZ + value; - _kickerApi.KickerCoil.Coil.Angle = _originalKickerAngle + value; + KickerApi.KickerCoil.Coil.Angle = _originalKickerAngle + value; } } @@ -166,50 +167,6 @@ public float2 RotatedPosition { #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // collision - var colliderComponent = gameObject.GetComponent(); - if (colliderComponent) { - dstManager.AddComponentData(entity, new KickerStaticData { - Center = Position, - FallIn = colliderComponent.FallIn, - FallThrough = colliderComponent.FallThrough, - HitAccuracy = colliderComponent.HitAccuracy, - Scatter = colliderComponent.Scatter, - LegacyMode = true, // todo colliderComponent.LegacyMode, - ZLow = Surface?.Height(Position) ?? PlayfieldHeight - }); - - dstManager.AddComponentData(entity, new KickerCollisionData { - BallEntity = Entity.Null, - LastCapturedBallEntity = Entity.Null - }); - - // if (!Data.LegacyMode) { - // // todo currently we don't allow non-legacy mode - // using (var blobBuilder = new BlobBuilder(Allocator.Temp)) { - // ref var blobAsset = ref blobBuilder.ConstructRoot(); - // var vertices = blobBuilder.Allocate(ref blobAsset.Vertices, Item.KickerHit.HitMesh.Length); - // var normals = blobBuilder.Allocate(ref blobAsset.Normals, Item.KickerHit.HitMesh.Length); - // for (var i = 0; i < Item.KickerHit.HitMesh.Length; i++) { - // var v = Item.KickerHit.HitMesh[i]; - // vertices[i] = new KickerMeshVertex { Vertex = v.ToUnityFloat3() }; - // normals[i] = new KickerMeshVertex { Vertex = new float3(KickerHitMesh.Vertices[i].Nx, KickerHitMesh.Vertices[i].Ny, KickerHitMesh.Vertices[i].Nz) }; - // } - // - // var blobAssetReference = blobBuilder.CreateBlobAssetReference(Allocator.Persistent); - // dstManager.AddComponentData(entity, new ColliderMeshData { Value = blobAssetReference }); - // } - // } - } - - // register - transform.GetComponentInParent().RegisterKicker(this, entity); - } - public override IEnumerable SetData(KickerData data) { var updatedComponents = new List { this }; @@ -223,7 +180,7 @@ public override IEnumerable SetData(KickerData data) #if UNITY_EDITOR var mf = GetComponent(); if (mf) { - MeshName = System.IO.Path.GetFileNameWithoutExtension(UnityEditor.AssetDatabase.GetAssetPath(mf.sharedMesh)); + MeshName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(mf.sharedMesh)); } #endif @@ -300,6 +257,38 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal KickerState CreateState() + { + // collision + var colliderComponent = GetComponent(); + var staticData = colliderComponent + ? new KickerStaticState { + Center = Position, + FallIn = colliderComponent.FallIn, + FallThrough = colliderComponent.FallThrough, + HitAccuracy = colliderComponent.HitAccuracy, + Scatter = colliderComponent.Scatter, + LegacyMode = colliderComponent.LegacyMode, + ZLow = Surface?.Height(Position) ?? PlayfieldHeight + } : 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); + + return new KickerState( + colliderComponent ? colliderComponent.gameObject.GetInstanceID() : 0, + staticData, + new KickerCollisionState(), + meshData + ); + } + + #endregion + #region Serialization public void OnBeforeSerialize() @@ -307,7 +296,7 @@ public void OnBeforeSerialize() #if UNITY_EDITOR // don't generate ids for prefabs, otherwise they'll show up in the instances. - if (UnityEditor.PrefabUtility.GetPrefabInstanceStatus(this) != UnityEditor.PrefabInstanceStatus.Connected) { + if (PrefabUtility.GetPrefabInstanceStatus(this) != PrefabInstanceStatus.Connected) { return; } var coilIds = new HashSet(); @@ -328,16 +317,26 @@ public void OnAfterDeserialize() #region Runtime + public KickerApi KickerApi { get; private set; } + private void Awake() { _originalRotationZ = Orientation; + + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + KickerApi = new KickerApi(gameObject, player, physicsEngine); + + player.Register(KickerApi, this); + if (GetComponent()) { + RegisterPhysics(physicsEngine); + } } private void Start() { - _kickerApi = GetComponentInParent().TableApi.Kicker(this); - if (_kickerApi.KickerCoil != null) { - _originalKickerAngle = _kickerApi.KickerCoil.Coil.Angle; + if (KickerApi?.KickerCoil != null) { + _originalKickerAngle = KickerApi.KickerCoil.Coil.Angle; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs new file mode 100644 index 000000000..c18df1806 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs @@ -0,0 +1,41 @@ +// 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; + +namespace VisualPinball.Unity +{ + internal struct KickerState : IDisposable + { + internal readonly int ItemId; + internal KickerStaticState Static; + internal KickerCollisionState Collision; + internal ColliderMeshData CollisionMesh; + + public KickerState(int itemId, KickerStaticState @static, KickerCollisionState collision, ColliderMeshData collisionMesh) + { + ItemId = itemId; + Static = @static; + Collision = collision; + CollisionMesh = collisionMesh; + } + + public void Dispose() + { + CollisionMesh.Dispose(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs.meta new file mode 100644 index 000000000..556aeedca --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 26f155d768f3423c90752762fbb736a4 +timeCreated: 1696497627 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticState.cs similarity index 90% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticState.cs index 95fc7aaa8..445d28bae 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticState.cs @@ -14,12 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; namespace VisualPinball.Unity { - internal struct KickerStaticData : IComponentData + internal struct KickerStaticState { public bool LegacyMode; public bool FallThrough; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Kicker/KickerStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs index aa5d7377b..45eada226 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightApi.cs @@ -91,7 +91,7 @@ public void OnLamp(Color color) IApiWireDest IApiWireDeviceDest.Wire(string deviceItem) => this; - internal LightApi(GameObject go, Player player) : base(go, player) + internal LightApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { _lightComponent = go.GetComponentInChildren(); _status = _lightComponent.State; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs index f5150f67d..52d705507 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightComponent.cs @@ -92,7 +92,7 @@ public class LightComponent : MainRenderableComponent, ILampDeviceCom #region API - public IApiLamp GetApi(Player player) => _api ??= new LightApi(gameObject, player); + public IApiLamp GetApi(Player player) => _api ??= new LightApi(gameObject, player, null); public IEnumerable LightSources => GetComponentsInChildren(); public Color LampColor => _color; @@ -156,7 +156,15 @@ public override void UpdateTransforms() #region Runtime + /// + /// The current light intensity, between 0 and 1. + /// private float _value; + private float _newValue; + private float _oldValue; + private float _newValueAt; + private float _oldValueAt; + private Color _color; private bool _hasLights; private readonly List<(Light, float)> _lights = new(); @@ -165,9 +173,8 @@ public override void UpdateTransforms() public bool Enabled { set { - StopAllCoroutines(); - SetLightIntensity(value ? 1 : 0); - SetMaterialIntensity(value ? 1 : 0); + SetLightIntensity(value ? _value : 0); + SetMaterialIntensity(value ? _value : 0); } } @@ -193,8 +200,8 @@ private void Awake() Logger.Error($"Cannot find player for lamp {name}."); return; } + player.Register(GetApi(player), this); - player.RegisterLamp(this); var lights = GetComponentsInChildren(); _value = 0; _color = lights.FirstOrDefault()?.color ?? Color.white; @@ -224,16 +231,37 @@ private void Awake() _hasLights = _lights.Count > 0 || _materials.Count > 0; } + private void Update() + { + if (_value == _newValue) { + return; + } + var durationSeconds = _newValueAt - _oldValueAt; + var position = durationSeconds == 0 + ? 1 + : (Time.fixedTime - _oldValueAt) / durationSeconds; + _value = position >= 1 // done? + ? _newValue + : math.lerp(_oldValue, _newValue, position); + SetLightIntensity(_value); + SetMaterialIntensity(_value); + } + public void FadeTo(float value) { if (!_hasLights) { return; } if (FadeEnabled) { - StopAllCoroutines(); - StartCoroutine(nameof(Fade), value); + _oldValue = _value; + _oldValueAt = Time.fixedTime; + _newValue = value; + _newValueAt = Time.fixedTime + (_value < value + ? FadeSpeedUp * (1 - _value) + : FadeSpeedDown * _value); } else { + _newValue = value; _value = value; SetLightIntensity(value); SetMaterialIntensity(value); @@ -273,19 +301,19 @@ private IEnumerator Blink(float blinkIntensity) private IEnumerator Fade(float value) { var counter = 0f; - var duration = _value < value + var durationSeconds = _value < value ? FadeSpeedUp * (1 - _value) : FadeSpeedDown * _value; - if (duration == 0) { + if (durationSeconds == 0) { _value = value; SetLightIntensity(value); SetMaterialIntensity(value); } else { - while (counter <= duration) { + while (counter <= durationSeconds) { counter += Time.deltaTime; - var position = counter / duration; + var position = counter / durationSeconds; var newValue = Mathf.Lerp(_value, value, position); yield return SetIntensity(newValue); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightGroupComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightGroupComponent.cs index a64a087b1..1c8b6a1af 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightGroupComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightGroupComponent.cs @@ -71,7 +71,7 @@ private void Awake() return; } - player.RegisterLampGroup(this); + player.Register(GetApi(player), this); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 014bd4175..b13f99eb4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -17,7 +17,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -25,7 +24,7 @@ namespace VisualPinball.Unity { - public abstract class MainRenderableComponent : MainComponent, + public abstract class MainRenderableComponent : MainComponent, IMainRenderableComponent, IOnPlayfieldComponent where TData : ItemData { @@ -44,8 +43,6 @@ public abstract class MainRenderableComponent : MainComponent, protected abstract Type ColliderComponentType { get; } - public Entity Entity { get; set; } - /// /// Returns all child mesh components linked to this data. /// @@ -80,6 +77,15 @@ protected Mesh GetDefaultMesh() return null; } + public UnityEngine.Mesh GetUnityMesh() + { + var mf = GetComponent(); + if (mf && mf.sharedMesh) { + return mf.sharedMesh; + } + return null; + } + public virtual void OnPlayfieldHeightUpdated() => UpdateTransforms(); public virtual void UpdateTransforms() @@ -93,11 +99,6 @@ public virtual void UpdateVisibility() { } - protected void Convert(Entity entity, EntityManager dstManager) - { - Entity = entity; - } - protected float SurfaceHeight(ISurfaceComponent surface, Vector2 position) { return surface?.Height(position) ?? PlayfieldHeight; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs index d895dbe6e..affdc6999 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/RotatorComponent.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; using System.Linq; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; @@ -56,10 +55,9 @@ public IRotatableComponent[] RotateWith { private Player _player; private KickerApi[] _kickers; - private (KickerApi kicker, float distance, float angle, Entity ballEntity)[] _ballEntities; + private (KickerApi kicker, float distance, float angle, int ballId)[] _balls; private Dictionary _rotatingObjectDistances = new(); - private static EntityManager EntityManager => World.DefaultGameObjectInjectionWorld.EntityManager; private void Awake() { @@ -85,11 +83,11 @@ private void Start() public void StartRotating() { var pos = Target.RotatedPosition; - _ballEntities = _kickers.Where(k => k.HasBall()).Select(k => ( + _balls = _kickers.Where(k => k.HasBall()).Select(k => ( k, math.distance(pos, k.Position.xy), math.sign(pos.x - k.Position.x) * Vector2.Angle(k.Position.xy - pos, new float2(0f, -1f)), - k.BallEntity) + ballId: k.BallId) ).ToArray(); } @@ -110,11 +108,11 @@ public void UpdateRotation(float angleDeg) } // rotate ball(s) in kicker(s) - foreach (var (kicker, distance, angle, ballEntity) in _ballEntities) { + foreach (var (kicker, distance, angle, ballId) in _balls) { if (!kicker.HasBall()) { return; } - var ballData = EntityManager.GetComponentData(ballEntity); + ref var ballData = ref GetComponentInParent().BallState(ballId); ballData.Position = new float3( pos.x -distance * math.sin(math.radians(angleDeg + angle)), pos.y -distance * math.cos(math.radians(angleDeg + angle)), @@ -122,8 +120,6 @@ public void UpdateRotation(float angleDeg) ); ballData.Velocity = float3.zero; ballData.AngularMomentum = float3.zero; - - EntityManager.SetComponentData(ballEntity, ballData); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorApi.cs index df699cfa5..dceb394d8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorApi.cs @@ -27,6 +27,7 @@ public class ScoreMotorApi : IApi, IApiSwitchDevice private readonly ScoreMotorComponent _scoreMotorComponent; private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; public event EventHandler Init; @@ -47,18 +48,19 @@ public IApiSwitch Switch(string deviceItem) }; } - internal ScoreMotorApi(GameObject go, Player player) + internal ScoreMotorApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _scoreMotorComponent = go.GetComponentInChildren(); _player = player; + _physicsEngine = physicsEngine; _scoreMotorComponent.OnSwitchChanged += HandleSwitchChanged; } void IApi.OnInit(BallManager ballManager) { - _motorRunningSwitch = new DeviceSwitch(ScoreMotorComponent.MotorRunningSwitchItem, false, SwitchDefault.NormallyOpen, _player); - _motorStepSwitch = new DeviceSwitch(ScoreMotorComponent.MotorStepSwitchItem, true, SwitchDefault.NormallyOpen, _player); + _motorRunningSwitch = new DeviceSwitch(ScoreMotorComponent.MotorRunningSwitchItem, false, SwitchDefault.NormallyOpen, _player, _physicsEngine); + _motorStepSwitch = new DeviceSwitch(ScoreMotorComponent.MotorStepSwitchItem, true, SwitchDefault.NormallyOpen, _player, _physicsEngine); Init?.Invoke(this, EventArgs.Empty); } @@ -76,4 +78,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorComponent.cs index b83956f96..9cf911f60 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/ScoreMotorComponent.cs @@ -95,9 +95,15 @@ public class ScoreMotorComponent : MonoBehaviour, ISwitchDeviceComponent #region Runtime + public ScoreMotorApi ScoreMotorApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterScoreMotorComponent(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + ScoreMotorApi = new ScoreMotorApi(gameObject, player, physicsEngine); + + player.Register(ScoreMotorApi, this); } private void Switch(string id, bool isClosed) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechApi.cs index 2963a8013..473576ca7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechApi.cs @@ -36,6 +36,7 @@ private enum Direction } private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; private readonly StepRotatorMechComponent _component; private DeviceCoil _motorCoil; @@ -46,13 +47,13 @@ private enum Direction private Dictionary _switches; private Dictionary _marks; - internal StepRotatorMechApi(GameObject go, Player player) + internal StepRotatorMechApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _component = go.GetComponentInChildren(); _player = player; + _physicsEngine = physicsEngine; } - void IApi.OnInit(BallManager ballManager) { _enabled = false; @@ -62,7 +63,7 @@ void IApi.OnInit(BallManager ballManager) _motorCoil = new DeviceCoil(_player, OnMotorCoilEnabled, OnMotorCoilDisabled); _marks = _component.Marks.ToDictionary(m => m.SwitchId, m => m); - _switches = _component.Marks.ToDictionary(m => m.SwitchId, m => new DeviceSwitch(m.SwitchId, false, SwitchDefault.NormallyOpen, _player)); + _switches = _component.Marks.ToDictionary(m => m.SwitchId, m => new DeviceSwitch(m.SwitchId, false, SwitchDefault.NormallyOpen, _player, _physicsEngine)); var i = 0; foreach (var sw in _switches.Values) { sw.SetSwitch(i == 0); @@ -161,4 +162,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechComponent.cs index cb392f9d0..faab18409 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Mech/StepRotatorMechComponent.cs @@ -70,6 +70,8 @@ public class StepRotatorMechComponent : MonoBehaviour, ISwitchDeviceComponent, I public event EventHandler OnMechUpdate; + public StepRotatorMechApi StepRotatorMechApi { get; private set; } + private void Awake() { var player = GetComponentInParent(); @@ -78,7 +80,10 @@ private void Awake() return; } - player.RegisterStepRotator(this); + var physicsEngine = GetComponentInParent(); + StepRotatorMechApi = new StepRotatorMechApi(gameObject, player, physicsEngine); + + player.Register(StepRotatorMechApi, this); } public void UpdateRotation(float speed, float position) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs index eabe4b925..2a26c3aba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.MetalWireGuide; @@ -35,7 +34,7 @@ public class MetalWireGuideApi : CollidableApi public event EventHandler Hit; - internal MetalWireGuideApi(GameObject go, Entity entity, Player player) : base(go, entity, player) + internal MetalWireGuideApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -44,10 +43,10 @@ internal MetalWireGuideApi(GameObject go, Entity entity, Player player) : base(g protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => 2.0f; // hard coded threshold for now - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new MetalWireGuideColliderGenerator(this, new MetalWireGuideMeshGenerator(MainComponent)); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, colliders, margin); + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.Bendradius, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } #endregion @@ -64,9 +63,9 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs index f77892b28..9dc0e0b4c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.MetalWireGuide; @@ -56,7 +54,7 @@ public class MetalWireGuideColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new MetalWireGuideApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.MetalWireGuideApi ?? new MetalWireGuideApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs index f974ddbdb..070b35499 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideColliderGenerator.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System.Collections.Generic; +using Unity.Collections; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.MetalWireGuide; @@ -31,10 +32,10 @@ public MetalWireGuideColliderGenerator(MetalWireGuideApi metalWireGuideApi, Meta _meshGenerator = meshGenerator; } - internal void GenerateColliders(float playfieldHeight, float hitHeight, float bendradius, int detailLevel, List colliders, float margin) + 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(); + var addedEdges = EdgeSet.Get(Allocator.TempJob); // add collision triangles and edges for (var i = 0; i < mesh.Indices.Length; i += 3) { @@ -45,19 +46,21 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, float be colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i], mesh.Indices[i + 2], colliders); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], colliders); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i + 1], mesh.Indices[i], colliders); + 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, EdgeSet addedEdges, int i, int j, - ICollection 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(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs index 040aeb698..769b365bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MetalWireGuide/MetalWireGuideComponent.cs @@ -24,8 +24,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; -using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -36,7 +34,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Metal Wire Guide")] public class MetalWireGuideComponent : MainRenderableComponent, - IMetalWireGuideData, IConvertGameObjectToEntity + IMetalWireGuideData { #region Data @@ -90,6 +88,22 @@ public class MetalWireGuideComponent : MainRenderableComponent(); + var physicsEngine = GetComponentInParent(); + MetalWireGuideApi = new MetalWireGuideApi(gameObject, player, physicsEngine); + + player.Register(MetalWireGuideApi, this); + RegisterPhysics(physicsEngine); + } + + #endregion + #region Transformation public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); @@ -98,14 +112,6 @@ public class MetalWireGuideComponent : MainRenderableComponent().RegisterMetalWireGuide(this, entity); - } - public override IEnumerable SetData(MetalWireGuideData data) { var updatedComponents = new List { this }; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/PhysicsMaterialAsset.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/PhysicsMaterialAsset.cs index 9ae40048b..2b28322b1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/PhysicsMaterialAsset.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/PhysicsMaterialAsset.cs @@ -38,11 +38,11 @@ public class PhysicsMaterialAsset : ScriptableObject public float ElasticityFalloff; public AnimationCurve ElasticityOverVelocity; public bool UseElasticictyOverVelocity; - public FixedListFloat512 ElasticityOverVelocityLUT; + public FixedList512Bytes ElasticityOverVelocityLUT; public float Friction; public AnimationCurve FrictionOverVelocity; public bool UseFrictionOverVelocity; - public FixedListFloat512 FrictionOverVelocityLUT; + public FixedList512Bytes FrictionOverVelocityLUT; // public AnimationCurve FrictionOverAngularMomentum; public float ScatterAngle; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs index 0bd65fa64..79121ef15 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldApi.cs @@ -14,11 +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.Entities; using Unity.Mathematics; using UnityEngine; -using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Table; using VisualPinball.Unity.Playfield; @@ -26,23 +23,32 @@ namespace VisualPinball.Unity { public class PlayfieldApi : CollidableApi { - internal PlayfieldApi(GameObject go, Player player) : base(go, Player.PlayfieldEntity, player) + internal PlayfieldApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } #region Collider Generation - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var info = ((IApiColliderGenerator)this).GetColliderInfo(); + // do we have a playfield mesh? var meshComp = GameObject.GetComponent(); if (meshComp && !meshComp.AutoGenerate) { var mf = GameObject.GetComponent(); if (mf && mf.sharedMesh) { - ColliderUtils.GenerateCollidersFromMesh(mf.sharedMesh.ToVpMesh(), info, colliders); + ColliderUtils.GenerateCollidersFromMesh(mf.sharedMesh.ToVpMesh().TransformToVpx(), info, ref 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)); } + } else { + colliders.Add(new PlaneCollider(new float3(0, 0, 1), MainComponent.TableHeight, info)); } + // add playfield glass collider + colliders.Add(new PlaneCollider(new float3(0, 0, -1), MainComponent.GlassHeight, info)); if (ColliderComponent.CollideWithBounds) { @@ -79,37 +85,6 @@ protected override void CreateColliders(List colliders, float margin) info )); } - - // glass: - var rgv3D = new[] { - new float3(MainComponent.Left, MainComponent.Top, MainComponent.GlassHeight), - new float3(MainComponent.Right, MainComponent.Top, MainComponent.GlassHeight), - new float3(MainComponent.Right, MainComponent.Bottom, MainComponent.GlassHeight), - new float3(MainComponent.Left, MainComponent.Bottom, MainComponent.GlassHeight) - }; - ColliderUtils.Generate3DPolyColliders(rgv3D, info, colliders); - } - - internal (PlaneCollider, PlaneCollider) CreateColliders() - { - var info = new ColliderInfo { - ItemType = ItemType.Table, - Entity = Player.PlayfieldEntity, - FireEvents = false, - IsEnabled = true, - Material = new PhysicsMaterialData { - Elasticity = ColliderComponent.Elasticity, - ElasticityFalloff = ColliderComponent.ElasticityFalloff, - Friction = ColliderComponent.Friction, - ScatterAngleRad = ColliderComponent.Scatter - }, - HitThreshold = 0 - }; - - return ( - new PlaneCollider(new float3(0, 0, 1), MainComponent.TableHeight, info), - new PlaneCollider(new float3(0, 0, -1), MainComponent.GlassHeight, info) - ); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs index d804d7298..5176c90ba 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldColliderComponent.cs @@ -17,7 +17,7 @@ // ReSharper disable InconsistentNaming using System; -using Unity.Entities; +using System.Collections.Generic; using UnityEngine; using VisualPinball.Engine.VPT.Table; @@ -53,8 +53,8 @@ public class PlayfieldColliderComponent : ColliderComponent GetPhysicsMaterialData(0, 0); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new PlayfieldApi(gameObject, player); + public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.PlayfieldApi ?? new PlayfieldApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs index 12a7e139a..7c1085bca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Playfield/PlayfieldComponent.cs @@ -18,7 +18,7 @@ using System; using System.Collections.Generic; -using Unity.Entities; +using NativeTrees; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Common; @@ -68,8 +68,6 @@ public class PlayfieldComponent : MainRenderableComponent public int PlayfieldDetailLevel = 10; - public float GravityStrength = 1.762985f; - [SerializeField] private string _playfieldImage; [SerializeField] private string _playfieldMaterial; @@ -91,30 +89,31 @@ public class PlayfieldComponent : MainRenderableComponent protected override Type ColliderComponentType => typeof(PlayfieldColliderComponent); public Rect3D BoundingBox => new Rect3D(Left, Right, Top, Bottom, TableHeight, GlassHeight); - - public float3 Gravity { - get { - var tableComponent = GetComponentInParent(); - var difficulty = tableComponent ? tableComponent.GlobalDifficulty : 0.2f; - var slope = AngleTiltMin + (AngleTiltMax - AngleTiltMin) * difficulty; - var strength = tableComponent.OverridePhysics != 0 ? PhysicsConstants.DefaultTableGravity : GravityStrength; - return new float3(0, math.sin(math.radians(slope)) * strength, -math.cos(math.radians(slope)) * strength); - } + public Aabb Bounds => new Aabb(Left, Right, Top, Bottom, TableHeight, GlassHeight); + public AABB2D Bounds2D => new AABB2D(new float2(Left, Top), new float2(Right, Bottom)); + + public float3 PlayfieldGravity(float strength) { + var tableComponent = GetComponentInParent(); + var difficulty = tableComponent ? tableComponent.GlobalDifficulty : 0.2f; + var slope = AngleTiltMin + (AngleTiltMax - AngleTiltMin) * difficulty; + return new float3(0, math.sin(math.radians(slope)) * strength, -math.cos(math.radians(slope)) * strength); } + public PlayfieldApi PlayfieldApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterPlayfield(gameObject); - var meshComp = GetComponentInChildren(); - if (meshComp) { - World.DefaultGameObjectInjectionWorld.GetOrCreateSystem().CollideAgainstPlayfieldPlane = meshComp.AutoGenerate; - } + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + PlayfieldApi = new PlayfieldApi(gameObject, player, physicsEngine); + player.Register(PlayfieldApi); transform.RotateAround(Vector3.zero, Vector3.right, -RenderSlope); } public override IEnumerable SetData(TableData data) { + var physicsEngine = GetComponentInParent(); var updatedComponents = new List { this }; // position @@ -128,7 +127,9 @@ public override IEnumerable SetData(TableData data) Bottom = data.Bottom; AngleTiltMax = data.AngleTiltMax; AngleTiltMin = data.AngleTiltMin; - GravityStrength = data.Gravity; + if (physicsEngine) { + physicsEngine.GravityStrength = data.Gravity; + } // playfield material _playfieldImage = data.Image; @@ -187,6 +188,8 @@ public IEnumerable SetReferencedData(PrimitiveData primitiveData, public override TableData CopyDataTo(TableData data, string[] materialNames, string[] textureNames, bool forExport) { + var physicsEngine = GetComponentInParent(); + // position data.TableHeight = TableHeight; data.GlassHeight = GlassHeight; @@ -196,7 +199,9 @@ public override TableData CopyDataTo(TableData data, string[] materialNames, str data.Bottom = Bottom; data.AngleTiltMax = AngleTiltMax; data.AngleTiltMin = AngleTiltMin; - data.Gravity = GravityStrength; + if (physicsEngine) { + data.Gravity = physicsEngine.GravityStrength; + } // playfield material data.Image = _playfieldImage; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimation.cs new file mode 100644 index 000000000..ce31f7f61 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimation.cs @@ -0,0 +1,32 @@ +// 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 +{ + internal static class PlungerAnimation + { + internal static void Update(ref PlungerAnimationState animation, in PlungerMovementState movement, + in PlungerStaticState staticState) + { + var frame0 = (int)((movement.Position - staticState.FrameStart) / (staticState.FrameEnd - staticState.FrameStart) * (staticState.NumFrames - 1) + 0.5f); + var frame = frame0 < 0 ? 0 : frame0 >= staticState.NumFrames ? staticState.NumFrames - 1 : frame0; + + animation.Position = math.clamp((float)frame / staticState.NumFrames, 0, 1); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationState.cs index f4a9cd8c2..bdf88f16e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct PlungerAnimationData : IComponentData + internal struct PlungerAnimationState { public float Position; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationSystem.cs deleted file mode 100644 index 0d2943df8..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerAnimationSystem.cs +++ /dev/null @@ -1,49 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class PlungerAnimationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("PlungerAnimationSystem"); - - protected override void OnUpdate() - { - var marker = PerfMarker; - - Entities.WithName("PlungerAnimationJob") - .ForEach((ref PlungerAnimationData animationData, in PlungerMovementData movementData, in PlungerStaticData staticData) => - { - marker.Begin(); - - var frame0 = (int)((movementData.Position - staticData.FrameStart) / (staticData.FrameEnd - staticData.FrameStart) * (staticData.NumFrames - 1) + 0.5f); - var frame = frame0 < 0 ? 0 : frame0 >= staticData.NumFrames ? staticData.NumFrames - 1 : frame0; - - //Debug.Log($"[plunger] frame0 = {frame0} frame = {frame}"); - - animationData.Position = math.clamp((float)frame / staticData.NumFrames, 0, 1); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs index 04f363c28..7ca9c3cd2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerApi.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using UnityEngine.InputSystem; using VisualPinball.Engine.VPT.Plunger; @@ -61,17 +59,15 @@ public class PlungerApi : CollidableApi(); // 0 = resting pos, 1 = pulled back - var movementData = EntityManager.GetComponentData(Entity); - movementData.AnalogPosition = pos; - EntityManager.SetComponentData(Entity, movementData); + ref var plungerState = ref PhysicsEngine.PlungerState(ItemId); + plungerState.Movement.AnalogPosition = pos; } void IApi.OnInit(BallManager ballManager) @@ -93,18 +89,14 @@ public void PullBack() if (!collComponent) { return; } - var movementData = EntityManager.GetComponentData(Entity); - var velocityData = EntityManager.GetComponentData(Entity); + ref var plungerState = ref PhysicsEngine.PlungerState(ItemId); if (DoRetract) { - PlungerCommands.PullBackAndRetract(collComponent.SpeedPull, ref velocityData, ref movementData); + PlungerCommands.PullBackAndRetract(collComponent.SpeedPull, ref plungerState.Velocity, ref plungerState.Movement); } else { - PlungerCommands.PullBack(collComponent.SpeedPull, ref velocityData, ref movementData); + PlungerCommands.PullBack(collComponent.SpeedPull, ref plungerState.Velocity, ref plungerState.Movement); } - - EntityManager.SetComponentData(Entity, movementData); - EntityManager.SetComponentData(Entity, velocityData); } public void Fire() @@ -113,9 +105,7 @@ public void Fire() if (!collComponent) { return; } - var movementData = EntityManager.GetComponentData(Entity); - var velocityData = EntityManager.GetComponentData(Entity); - var staticData = EntityManager.GetComponentData(Entity); + ref var plungerState = ref PhysicsEngine.PlungerState(ItemId); // check for an auto plunger if (collComponent.IsAutoPlunger) { @@ -126,18 +116,15 @@ public void Fire() // is constant (modulo some mechanical randomness). Simulate // this by triggering a release from the maximum retracted // position. - PlungerCommands.Fire(1f, ref velocityData, ref movementData, in staticData); + PlungerCommands.Fire(1f, ref plungerState.Velocity, ref plungerState.Movement, in plungerState.Static); } else { // Regular plunger - trigger a release from the current // position, using the keyboard firing strength. - var pos = (movementData.Position - staticData.FrameEnd) / (staticData.FrameStart - staticData.FrameEnd); - PlungerCommands.Fire(pos, ref velocityData, ref movementData, in staticData); + var pos = (plungerState.Movement.Position - plungerState.Static.FrameEnd) / (plungerState.Static.FrameStart - plungerState.Static.FrameEnd); + PlungerCommands.Fire(pos, ref plungerState.Velocity, ref plungerState.Movement, in plungerState.Static); } - - EntityManager.SetComponentData(Entity, movementData); - EntityManager.SetComponentData(Entity, velocityData); } IApiCoil IApiCoilDevice.Coil(string deviceItem) => Coil(deviceItem); @@ -155,7 +142,7 @@ private IApiCoil Coil(string deviceItem) #region Collider Generation - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { colliders.Add(new PlungerCollider(MainComponent, ColliderComponent, GetColliderInfo())); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs index 9ee87dab3..0300ede24 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCollider.cs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; +using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.Common; using VisualPinball.Engine.VPT.Plunger; @@ -24,9 +23,18 @@ namespace VisualPinball.Unity { internal struct PlungerCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set { + Header.Id = value; + var bounds = Bounds; + bounds.ColliderId = value; + Bounds = bounds; + } + } - private ColliderHeader _header; + public ColliderHeader Header; public LineCollider LineSegBase; public LineZCollider JointBase0; @@ -36,7 +44,7 @@ internal struct PlungerCollider : ICollider public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Plunger); + Header.Init(info, ColliderType.Plunger); var zHeight = comp.PositionZ; var x = comp.Position.x - comp.Width; @@ -49,7 +57,7 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, JointBase1 = new LineZCollider(new float2(x2, y), zHeight, zHeight + Plunger.PlungerHeight, info); var frameEnd = comp.Position.y - collComp.Stroke; - Bounds = new ColliderBounds(_header.Entity, _header.Id, new Aabb( + Bounds = new ColliderBounds(Header.ItemId, Header.Id, new Aabb( x - 0.1f, x2 + 0.1f, frameEnd - 0.1f, @@ -59,25 +67,10 @@ public PlungerCollider(PlungerComponent comp, PlungerColliderComponent collComp, )); } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - var bounds = Bounds; - bounds.ColliderId = colliderId; - Bounds = bounds; - _header.Id = colliderId; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(PlungerCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, - ref PlungerMovementData movementData, in PlungerColliderData colliderData, in PlungerStaticData staticData, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, + ref PlungerMovementState movement, in PlungerColliderState colliderState, in PlungerStaticState staticState, in BallState ball, float dTime) { var hitTime = dTime; //start time var isHit = false; @@ -97,13 +90,13 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer 0.05f ? ball.Mass : 0.05f; - var xferRatio = staticData.MomentumXfer / ballMass; - var deltaY = movementData.Speed * xferRatio; + var xferRatio = staticState.MomentumXfer / ballMass; + var deltaY = movement.Speed * xferRatio; // check the moving bits - newTime = LineCollider.HitTest(ref newCollEvent, ref insideOfs, in colliderData.LineSegEnd, in ballTmp, hitTime); + newTime = LineCollider.HitTest(ref newCollEvent, ref insideOfs, in colliderState.LineSegEnd, in ballTmp, hitTime); UpdateCollision(ref collEvent, ref newCollEvent, ref isHit, ref hitTime, in newTime, deltaY); - newTime = LineZCollider.HitTest(ref newCollEvent, in colliderData.JointEnd0, in ballTmp, hitTime); + newTime = LineZCollider.HitTest(ref newCollEvent, in colliderState.JointEnd0, in ballTmp, hitTime); UpdateCollision(ref collEvent, ref newCollEvent, ref isHit, ref hitTime, in newTime, deltaY); - newTime = LineZCollider.HitTest(ref newCollEvent, in colliderData.JointEnd1, in ballTmp, hitTime); + newTime = LineZCollider.HitTest(ref newCollEvent, in colliderState.JointEnd1, in ballTmp, hitTime); UpdateCollision(ref collEvent, ref newCollEvent, ref isHit, ref hitTime, in newTime, deltaY); // check only if the plunger is not in a controlled retract motion // and check for a hit - if (isHit && !movementData.RetractMotion) { + if (isHit && !movement.RetractMotion) { // We hit the ball. Set a travel limit to freeze the plunger at // its current position for the next displacement update. This // is necessary in case we have a relatively heavy ball with a @@ -191,8 +184,8 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBufferm_ptable->m_globalDifficulty;// apply dificulty weighting // skip if low velocity diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs index 8e03fcac7..cdb441d81 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Plunger; @@ -57,7 +55,7 @@ public class PlungerColliderComponent : ColliderComponent GetPhysicsMaterialData(); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new PlungerApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.PlungerApi ?? new PlungerApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderState.cs index eb2eb929e..526a899e3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct PlungerColliderData : IComponentData + internal struct PlungerColliderState { public LineCollider LineSegSide0; public LineCollider LineSegSide1; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerColliderState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCommands.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCommands.cs index 54a28cd5d..8cf8a1b17 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCommands.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerCommands.cs @@ -19,47 +19,47 @@ namespace VisualPinball.Unity internal static class PlungerCommands { - public static void PullBack(float speed, ref PlungerVelocityData velocityData, ref PlungerMovementData movementData) + public static void PullBack(float speed, ref PlungerVelocityState velocity, ref PlungerMovementState movement) { - movementData.Speed = 0.0f; - velocityData.PullForce = speed; + movement.Speed = 0.0f; + velocity.PullForce = speed; // deactivate the retract code - velocityData.AddRetractMotion = false; + velocity.AddRetractMotion = false; } - public static void PullBackAndRetract(float speedPull, ref PlungerVelocityData velocityData, ref PlungerMovementData movementData) + public static void PullBackAndRetract(float speedPull, ref PlungerVelocityState velocity, ref PlungerMovementState movement) { - movementData.Speed = 0.0f; - velocityData.PullForce = speedPull; + movement.Speed = 0.0f; + velocity.PullForce = speedPull; // deactivate the retract code - velocityData.AddRetractMotion = false; - movementData.RetractMotion = false; - velocityData.InitialSpeed = speedPull; + velocity.AddRetractMotion = false; + movement.RetractMotion = false; + velocity.InitialSpeed = speedPull; } - public static void Fire(float startPos, ref PlungerVelocityData velocityData, ref PlungerMovementData movementData, in PlungerStaticData staticData) + public static void Fire(float startPos, ref PlungerVelocityState velocity, ref PlungerMovementState movement, in PlungerStaticState staticState) { // cancel any pull force - velocityData.PullForce = 0.0f; + velocity.PullForce = 0.0f; // make sure the starting point is behind the park position - if (startPos < staticData.RestPosition) { - startPos = staticData.RestPosition; + if (startPos < staticState.RestPosition) { + startPos = staticState.RestPosition; } // move immediately to the starting position - movementData.Position = staticData.FrameEnd + startPos * staticData.FrameLen; + movement.Position = staticState.FrameEnd + startPos * staticState.FrameLen; // Figure the release speed as a fraction of the // fire speed property, linearly proportional to the // starting distance. Note that the release motion // is upwards, so the speed is negative. - var dx = startPos - staticData.RestPosition; + var dx = startPos - staticState.RestPosition; const float normalize = Engine.VPT.Plunger.Plunger.PlungerNormalize / 13.0f / 100.0f; - movementData.FireSpeed = -staticData.SpeedFire - * dx * staticData.FrameLen / Engine.VPT.Plunger.Plunger.PlungerMass + movement.FireSpeed = -staticState.SpeedFire + * dx * staticState.FrameLen / Engine.VPT.Plunger.Plunger.PlungerMass * normalize; // Figure the target stopping position for the @@ -74,12 +74,12 @@ public static void Fire(float startPos, ref PlungerVelocityData velocityData, re // the initial bounce will be negative, since we're moving upwards, // and we calculated it as a fraction of the forward travel distance // (which is the part between 0 and the rest position) - movementData.FireBounce = -bounceDist * staticData.RestPosition; + movement.FireBounce = -bounceDist * staticState.RestPosition; // enter Fire mode for long enough for the process to complete - movementData.FireTimer = 200; + movement.FireTimer = 200; - movementData.RetractMotion = false; + movement.RetractMotion = false; } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs index fb81a5baf..b7d9867b3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerComponent.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using UnityEngine.InputSystem; @@ -33,7 +32,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Plunger")] public class PlungerComponent : MainRenderableComponent, - ICoilDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ICoilDeviceComponent, IOnSurfaceComponent { #region Data @@ -73,6 +72,24 @@ public class PlungerComponent : MainRenderableComponent, #endregion + #region Runtime + + public PlungerApi PlungerApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + PlungerApi = new PlungerApi(gameObject, player, physicsEngine); + + player.Register(PlungerApi, this, analogPlungerAction); + if (GetComponent()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + #region Wiring public IEnumerable AvailableCoils => new[] { @@ -108,87 +125,6 @@ public override void UpdateTransforms() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - var go = gameObject; - - var collComponent = GetComponent(); - if (!collComponent) { - // without collider, the plunger is only a dead mesh. - return; - } - - var zHeight = PositionZ; - var x = Position.x - Width; - var y = Position.y + Height; - var x2 = Position.x + Width; - - var frameTop = Position.y - collComponent.Stroke; - var frameBottom = Position.y; - var frameLen = frameBottom - frameTop; - var restPos = collComponent.ParkPosition; - var position = frameTop + restPos * frameLen; - - var info = new ColliderInfo { - Entity = entity, - FireEvents = true, - IsEnabled = true, - ItemType = ItemType.Plunger, - }; - - dstManager.AddComponentData(entity, new PlungerStaticData { - MomentumXfer = collComponent.MomentumXfer, - ScatterVelocity = collComponent.ScatterVelocity, - FrameStart = frameBottom, - FrameEnd = frameTop, - FrameLen = frameLen, - RestPosition = restPos, - IsAutoPlunger = collComponent.IsAutoPlunger, - IsMechPlunger = collComponent.IsMechPlunger, - SpeedFire = collComponent.SpeedFire, - NumFrames = (int)(collComponent.Stroke * (float)(PlungerMeshGenerator.PlungerFrameCount / 80.0f)) + 1, // 25 frames per 80 units travel - }); - - dstManager.AddComponentData(entity, new PlungerColliderData { - LineSegSide0 = new LineCollider(new float2(x + 0.0001f, position), new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info), - LineSegSide1 = new LineCollider(new float2(x2, y), new float2(x2 + 0.0001f, position), zHeight, zHeight + Plunger.PlungerHeight, info), - LineSegEnd = new LineCollider(new float2(x2, position), new float2(x, position), zHeight, zHeight + Plunger.PlungerHeight, info), - JointEnd0 = new LineZCollider(new float2(x, position), zHeight, zHeight + Plunger.PlungerHeight, info), - JointEnd1 = new LineZCollider(new float2(x2, position), zHeight, zHeight + Plunger.PlungerHeight, info), - }); - - dstManager.AddComponentData(entity, new PlungerMovementData { - FireBounce = 0f, - Position = position, - RetractMotion = false, - ReverseImpulse = 0f, - Speed = 0f, - TravelLimit = frameTop, - FireSpeed = 0f, - FireTimer = 0 - }); - - dstManager.AddComponentData(entity, new PlungerVelocityData { - Mech0 = 0f, - Mech1 = 0f, - Mech2 = 0f, - PullForce = 0f, - InitialSpeed = 0f, - AutoFireTimer = 0, - AddRetractMotion = false, - RetractWaitLoop = 0, - MechStrength = collComponent.MechStrength - }); - - dstManager.AddComponentData(entity, new PlungerAnimationData { - Position = collComponent.ParkPosition - }); - - // register at player - GetComponentInParent().RegisterPlunger(this, entity, analogPlungerAction); - } - public override IEnumerable SetData(PlungerData data) { var updatedComponents = new List { this }; @@ -339,6 +275,83 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal PlungerState CreateState() + { + var collComponent = GetComponent(); + if (!collComponent) { + // without collider, the plunger is only a dead mesh. + return default; + } + + var zHeight = PositionZ; + var x = Position.x - Width; + var y = Position.y + Height; + var x2 = Position.x + Width; + + var frameTop = Position.y - collComponent.Stroke; + var frameBottom = Position.y; + var frameLen = frameBottom - frameTop; + var restPos = collComponent.ParkPosition; + var position = frameTop + restPos * frameLen; + + var info = new ColliderInfo { + ItemId = GetInstanceID(), + FireEvents = true, + ItemType = ItemType.Plunger, + }; + + return new PlungerState( + gameObject.GetInstanceID(), + new PlungerStaticState { + MomentumXfer = collComponent.MomentumXfer, + ScatterVelocity = collComponent.ScatterVelocity, + FrameStart = frameBottom, + FrameEnd = frameTop, + FrameLen = frameLen, + RestPosition = restPos, + IsAutoPlunger = collComponent.IsAutoPlunger, + IsMechPlunger = collComponent.IsMechPlunger, + SpeedFire = collComponent.SpeedFire, + NumFrames = (int)(collComponent.Stroke * (float)(PlungerMeshGenerator.PlungerFrameCount / 80.0f)) + 1, // 25 frames per 80 units travel + }, + new PlungerColliderState { + LineSegSide0 = new LineCollider(new float2(x + 0.0001f, position), new float2(x, y), zHeight, zHeight + Plunger.PlungerHeight, info), + LineSegSide1 = new LineCollider(new float2(x2, y), new float2(x2 + 0.0001f, position), zHeight, zHeight + Plunger.PlungerHeight, info), + LineSegEnd = new LineCollider(new float2(x2, position), new float2(x, position), zHeight, zHeight + Plunger.PlungerHeight, info), + JointEnd0 = new LineZCollider(new float2(x, position), zHeight, zHeight + Plunger.PlungerHeight, info), + JointEnd1 = new LineZCollider(new float2(x2, position), zHeight, zHeight + Plunger.PlungerHeight, info), + }, + new PlungerMovementState { + FireBounce = 0f, + Position = position, + RetractMotion = false, + ReverseImpulse = 0f, + Speed = 0f, + TravelLimit = frameTop, + FireSpeed = 0f, + FireTimer = 0 + }, + new PlungerVelocityState { + Mech0 = 0f, + Mech1 = 0f, + Mech2 = 0f, + PullForce = 0f, + InitialSpeed = 0f, + AutoFireTimer = 0, + AddRetractMotion = false, + RetractWaitLoop = 0, + MechStrength = collComponent.MechStrength + }, + new PlungerAnimationState { + Position = collComponent.ParkPosition + } + ); + } + + #endregion + public void UpdateParkPosition(float pos) { foreach (var skinnedMeshRenderer in GetComponentsInChildren()) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementPhysics.cs new file mode 100644 index 000000000..8bd332457 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementPhysics.cs @@ -0,0 +1,118 @@ +// 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.Collections; +using Unity.Mathematics; +using VisualPinball.Engine.Game; + +namespace VisualPinball.Unity +{ + internal static class PlungerDisplacementPhysics + { + internal static void UpdateDisplacement(int itemId, ref PlungerMovementState movement, + ref PlungerColliderState colliderState, in PlungerStaticState staticState, float dTime, + ref NativeQueue.ParallelWriter events) + { + // figure the travel distance + var dx = dTime * movement.Speed; + + // figure the position change + movement.Position += dx; + + // apply the travel limit + if (movement.Position < movement.TravelLimit) { + movement.Position = movement.TravelLimit; + } + + // if we're in firing mode and we've crossed the bounce position, reverse course + var relPos = (movement.Position - staticState.FrameEnd) / staticState.FrameLen; + var bouncePos = staticState.RestPosition + movement.FireBounce; + if (movement.FireTimer != 0 && dTime != 0.0f && + (movement.FireSpeed < 0.0f ? relPos <= bouncePos : relPos >= bouncePos)) + { + // stop at the bounce position + movement.Position = staticState.FrameEnd + bouncePos * staticState.FrameLen; + + // reverse course at reduced speed + movement.FireSpeed = -movement.FireSpeed * 0.4f; + + // figure the new bounce as a fraction of the previous bounce + movement.FireBounce *= -0.4f; + } + + // apply the travel limit (again) + if (movement.Position < movement.TravelLimit) { + movement.Position = movement.TravelLimit; + } + + // limit motion to the valid range + if (dTime != 0.0f) { + + if (movement.Position < staticState.FrameEnd) { + movement.Speed = 0.0f; + movement.Position = staticState.FrameEnd; + + } else if (movement.Position > staticState.FrameStart) { + movement.Speed = 0.0f; + movement.Position = staticState.FrameStart; + } + + // apply the travel limit (yet again) + if (movement.Position < movement.TravelLimit) { + movement.Position = movement.TravelLimit; + } + } + + // the travel limit applies to one displacement update only - reset it + movement.TravelLimit = staticState.FrameEnd; + + // fire an Start/End of Stroke events, as appropriate + var strokeEventLimit = staticState.FrameLen / 50.0f; + var strokeEventHysteresis = strokeEventLimit * 2.0f; + if (movement.StrokeEventsArmed && movement.Position + dx > staticState.FrameStart - strokeEventLimit) { + events.Enqueue(new EventData(EventId.LimitEventsBos, itemId, math.abs(movement.Speed))); + movement.StrokeEventsArmed = false; + + } else if (movement.StrokeEventsArmed && movement.Position + dx < staticState.FrameEnd + strokeEventLimit) { + events.Enqueue(new EventData(EventId.LimitEventsEos, itemId, math.abs(movement.Speed))); + movement.StrokeEventsArmed = false; + + } else if (movement.Position > staticState.FrameEnd + strokeEventHysteresis && movement.Position < staticState.FrameStart - strokeEventHysteresis) { + // away from the limits - arm the stroke events + movement.StrokeEventsArmed = true; + } + + // update the display + UpdateCollider(movement.Position, ref colliderState); + } + + private static void UpdateCollider(float len, ref PlungerColliderState colliderState) + { + colliderState.LineSegSide0.V1y = len; + colliderState.LineSegSide1.V2y = len; + + colliderState.LineSegEnd.V2y = len; + colliderState.LineSegEnd.V1y = len; // + 0.0001f; + + colliderState.JointEnd0.XyY = len; + colliderState.JointEnd1.XyY = len; // + 0.0001f; + + colliderState.LineSegSide0.CalcNormal(); + colliderState.LineSegSide1.CalcNormal(); + colliderState.LineSegEnd.CalcNormal(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementSystem.cs deleted file mode 100644 index ab6b15616..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerDisplacementSystem.cs +++ /dev/null @@ -1,154 +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.Collections; -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Game; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateDisplacementSystemGroup))] - internal class PlungerDisplacementSystem : SystemBase - { - private Player _player; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private NativeQueue _eventQueue; - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("PlungerDisplacementSystem"); - - protected override void OnCreate() - { - _player = Object.FindObjectOfType(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - var events = _eventQueue.AsParallelWriter(); - var dTime = _simulateCycleSystemGroup.HitTime; - var marker = PerfMarker; - - Entities.WithName("PlungerDisplacementJob").ForEach((Entity entity, ref PlungerMovementData movementData, - ref PlungerColliderData colliderData, in PlungerStaticData staticData) => - { - marker.Begin(); - - // figure the travel distance - var dx = dTime * movementData.Speed; - - // figure the position change - movementData.Position += dx; - - // apply the travel limit - if (movementData.Position < movementData.TravelLimit) { - movementData.Position = movementData.TravelLimit; - } - - // if we're in firing mode and we've crossed the bounce position, reverse course - var relPos = (movementData.Position - staticData.FrameEnd) / staticData.FrameLen; - var bouncePos = staticData.RestPosition + movementData.FireBounce; - if (movementData.FireTimer != 0 && dTime != 0.0f && - (movementData.FireSpeed < 0.0f ? relPos <= bouncePos : relPos >= bouncePos)) - { - // stop at the bounce position - movementData.Position = staticData.FrameEnd + bouncePos * staticData.FrameLen; - - // reverse course at reduced speed - movementData.FireSpeed = -movementData.FireSpeed * 0.4f; - - // figure the new bounce as a fraction of the previous bounce - movementData.FireBounce *= -0.4f; - } - - // apply the travel limit (again) - if (movementData.Position < movementData.TravelLimit) { - movementData.Position = movementData.TravelLimit; - } - - // limit motion to the valid range - if (dTime != 0.0f) { - - if (movementData.Position < staticData.FrameEnd) { - movementData.Speed = 0.0f; - movementData.Position = staticData.FrameEnd; - - } else if (movementData.Position > staticData.FrameStart) { - movementData.Speed = 0.0f; - movementData.Position = staticData.FrameStart; - } - - // apply the travel limit (yet again) - if (movementData.Position < movementData.TravelLimit) { - movementData.Position = movementData.TravelLimit; - } - } - - // the travel limit applies to one displacement update only - reset it - movementData.TravelLimit = staticData.FrameEnd; - - // fire an Start/End of Stroke events, as appropriate - var strokeEventLimit = staticData.FrameLen / 50.0f; - var strokeEventHysteresis = strokeEventLimit * 2.0f; - if (movementData.StrokeEventsArmed && movementData.Position + dx > staticData.FrameStart - strokeEventLimit) { - events.Enqueue(new EventData(EventId.LimitEventsBos, entity, math.abs(movementData.Speed))); - movementData.StrokeEventsArmed = false; - - } else if (movementData.StrokeEventsArmed && movementData.Position + dx < staticData.FrameEnd + strokeEventLimit) { - events.Enqueue(new EventData(EventId.LimitEventsEos, entity, math.abs(movementData.Speed))); - movementData.StrokeEventsArmed = false; - - } else if (movementData.Position > staticData.FrameEnd + strokeEventHysteresis && movementData.Position < staticData.FrameStart - strokeEventHysteresis) { - // away from the limits - arm the stroke events - movementData.StrokeEventsArmed = true; - } - - // update the display - UpdateCollider(movementData.Position, ref colliderData); - - marker.End(); - }).Run(); - - // dequeue events - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(in eventData); - } - } - - private static void UpdateCollider(float len, ref PlungerColliderData colliderData) - { - colliderData.LineSegSide0.V1y = len; - colliderData.LineSegSide1.V2y = len; - - colliderData.LineSegEnd.V2y = len; - colliderData.LineSegEnd.V1y = len; // + 0.0001f; - - colliderData.JointEnd0.XyY = len; - colliderData.JointEnd1.XyY = len; // + 0.0001f; - - colliderData.LineSegSide0.CalcNormal(); - colliderData.LineSegSide1.CalcNormal(); - colliderData.LineSegEnd.CalcNormal(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementState.cs similarity index 96% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementState.cs index 436cfdd57..6e3684dc4 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct PlungerMovementData : IComponentData + internal struct PlungerMovementState { /// /// Current rod speed, in table distance units per second(?) diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerMovementState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs new file mode 100644 index 000000000..66acf2347 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs @@ -0,0 +1,38 @@ +// 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 +{ + internal struct PlungerState + { + internal readonly int ItemId; + internal PlungerStaticState Static; + internal PlungerColliderState Collider; + internal PlungerMovementState Movement; + internal PlungerVelocityState Velocity; + internal PlungerAnimationState Animation; + + public PlungerState(int itemId, PlungerStaticState @static, PlungerColliderState collider, PlungerMovementState movement, PlungerVelocityState velocity, PlungerAnimationState animation) + { + ItemId = itemId; + Static = @static; + Collider = collider; + Movement = movement; + Velocity = velocity; + Animation = animation; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs.meta new file mode 100644 index 000000000..aeb86b8fa --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: be12823300e24d0e97f4e9beb8608143 +timeCreated: 1696499320 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticState.cs similarity index 90% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticState.cs index e7fc3d150..487268844 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct PlungerStaticData : IComponentData + internal struct PlungerStaticState { // collision public float MomentumXfer; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs deleted file mode 100644 index 9d84108f4..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs +++ /dev/null @@ -1,52 +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.Entities; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class PlungerTransformationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("PlungerTransformationSystem"); - - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - - Entities.WithoutBurst().WithName("PlungerTransformationJob").ForEach((Entity entity, in PlungerAnimationData animationData) => { - - marker.Begin(); - - foreach (var skinnedMeshRenderer in _player.PlungerSkinnedMeshRenderers[entity]) { - skinnedMeshRenderer.SetBlendShapeWeight(0, animationData.Position); - } - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs.meta deleted file mode 100644 index 035df24c5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerTransformationSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e26bea61605afb04681956ccf866fee6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityPhysics.cs new file mode 100644 index 000000000..daf53c805 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityPhysics.cs @@ -0,0 +1,328 @@ +// 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.Plunger; + +namespace VisualPinball.Unity +{ + internal static class PlungerVelocityPhysics + { + internal static void UpdateVelocities(ref PlungerMovementState movement, ref PlungerVelocityState velocity, + in PlungerStaticState staticState) + { + // figure our current position in relative coordinates (0.0-1.0, + // where 0.0 is the maximum forward position and 1.0 is the + // maximum retracted position) + var pos = (movement.Position - staticState.FrameEnd) / staticState.FrameLen; + + var mech = staticState.IsMechPlunger ? movement.AnalogPosition : 0.0f; + + // calculate the delta from the last reading + var dMech = velocity.Mech0 - mech; + + // Frame-to-frame mech movement threshold for detecting a release + // motion. 1.0 is the full range of travel, which corresponds + // to about 3" on a standard pinball plunger. We want to choose + // the value here so that it's faster than the player is likely + // to move the plunger manually, but slower than the plunger + // typically moves under spring power when released. It appears + // from observation that a real plunger moves at something on the + // order of 3 m/s. Figure the fastest USB update interval will + // be 10ms, typical is probably 25ms, and slowest is maybe 40ms; + // and figure the bracket speed range down to about 1 m/s. This + // gives us a distance per USB interval of from 25mm to 100mm. + // 25mm translates to .32 of our distance units (0.0-1.0 scale). + // The lower we make this, the more sensitive we'll be at + // detecting releases, but if we make it too low we might mistake + // manual movements for releases. In practice, it seems safe to + // lower it to about 0.2 - this doesn't seem to cause false + // positives and seems reliable at identifying actual releases. + const float releaseThreshold = 0.2f; + + // note if we're acting as an auto plunger + var autoPlunger = staticState.IsAutoPlunger; + + // check which forces are acting on us + if (movement.FireTimer > 0) { + // Fire mode. In this mode, we're moving freely under the spring + // forces at the speed we calculated when we initiated the release. + // Simply leave the speed unchanged. + // + // Decrement the release mode timer. The mode ends after the + // timeout elapses, even if the mech plunger hasn't actually + // come to rest. This ensures that we don't get stuck in this + // mode, and also allows us to sync up again with the real + // plunger after a respectable pause if the user is just + // moving it around a lot. + movement.Speed = movement.FireSpeed; + --movement.FireTimer; + + } else if (velocity.AutoFireTimer > 0) { + // The Auto Fire timer is running. We start this timer when we + // send a synthetic KeyDown(Return) event to the script to simulate + // a Launch Ball event when the user pulls back and releases the + // mechanical plunger and we're operating as an auto plunger. + // When the timer reaches zero, we'll send the corresponding + // KeyUp event and cancel the timer. + if (--velocity.AutoFireTimer == 0) { + // todo event + // if (g_pplayer != 0) { + // g_pplayer->m_ptable->FireKeyEvent(DISPID_GameEvents_KeyUp, g_pplayer->m_rgKeys[ePlungerKey]); + // } + } + + } else if (autoPlunger && dMech > releaseThreshold) { + // Release motion detected in Auto Plunger mode. + // + // If we're acting as an auto plunger, and the player performs + // a pull-and-release motion on the mechanical plunger, simulate + // a Launch Ball event. + // + // An Auto Plunger simulates a solenoid-driven ball launcher + // on a table like Medieval Madness. On this type of game, + // the original machine doesn't have a spring-loaded plunger. + // for the user to operate manually. The user-operated control + // is instead a button of some kind (the physical form varies + // quite a bit, from big round pushbuttons to gun triggers to + // levers to rotating knobs, but they all amount to momentary + // on/off switches in different guises). But on virtual + // cabinets, the mechanical plunger doesn't just magically + // disappear when you load Medieval Madness! So the idea here + // is that we can use a mech plunger to simulate a button. + // It's pretty simple and natural: you just perform the normal + // action that you're accustomed to doing with a plunger, + // namely pulling it back and letting it go. The software + // observes this gesture, and rather than trying to simulate + // the motion directly on the software plunger, we simply + // turn it into a synthetic Launch Ball keyboard event. This + // amounts to sending a KeyDown(Return) message to the script, + // followed a short time later by a KeyUp(Return). The script + // will then act exactly like it would if the user had actually + // pressed the Return key (or, equivalently on a cabinet, the + // Launch Ball button). + + // Send a KeyDown(Return) to the table script. This + // will allow the script to set ROM switch levels or + // perform any other tasks it normally does when the + // actual Launch Ball button is pressed. + + // todo event + // if (g_pplayer != 0) { + // g_pplayer->m_ptable->FireKeyEvent(DISPID_GameEvents_KeyDown, g_pplayer->m_rgKeys[ePlungerKey]); + // } + + // start the timer to send the corresponding KeyUp in 100ms + velocity.AutoFireTimer = 101; + + } else if (velocity.PullForce != 0.0f) { + // A "pull" force is in effect. This is a *simulated* pull, so + // it overrides the real physical plunger position. + // + // Simply update the model speed by applying the acceleration + // due to the pull force. + // + // Force = mass*acceleration -> a = F/m. Increase the speed + // by the acceleration. Technically we're calculating dv = a dt, + // but we can elide the elapsed time factor because it's + // effectively a constant that's implicitly folded into the + // pull force value. + movement.Speed += velocity.PullForce / Plunger.PlungerMass; + + if (!velocity.AddRetractMotion) { + // this is the normal PullBack branch + + // if we're already at the maximum retracted position, stop + if (movement.Position > staticState.FrameStart) { + movement.Speed = 0.0f; + movement.Position = staticState.FrameStart; + } + + // if we're already at the minimum retracted position, stop + if (movement.Position < staticState.FrameEnd + staticState.RestPosition * staticState.FrameLen) { + movement.Speed = 0.0f; + movement.Position = staticState.FrameEnd + staticState.RestPosition * staticState.FrameLen; + } + + } else { + // this is the PullBackandRetract branch + + // after reaching the max. position the plunger should retract until it reaches the min. position and then start again + // if we're already at the maximum retracted position, reverse + if (movement.Position >= staticState.FrameStart && velocity.PullForce > 0) { + movement.Speed = 0.0f; + movement.Position = staticState.FrameStart; + velocity.RetractWaitLoop++; + if (velocity.RetractWaitLoop > 1000) { // 1 sec, related to PHYSICS_STEPTIME + velocity.PullForce = -velocity.InitialSpeed; + movement.Position = staticState.FrameStart; + movement.RetractMotion = true; + velocity.RetractWaitLoop = 0; + } + } + + // if we're already at the minimum retracted position, start again + if (movement.Position <= staticState.FrameEnd + staticState.RestPosition * staticState.FrameLen && velocity.PullForce <= 0) { + movement.Speed = 0.0f; + velocity.PullForce = velocity.InitialSpeed; + movement.Position = staticState.FrameEnd + staticState.RestPosition * staticState.FrameLen; + } + + // reset retract motion indicator only after the rest position has been left, to avoid ball interactions + // use a linear pullback motion + if (movement.Position > 1.0f + staticState.FrameEnd + staticState.RestPosition * staticState.FrameLen && velocity.PullForce > 0) { + movement.RetractMotion = false; + movement.Speed = 3.0f * velocity.PullForce; // 3 = magic + } + } + + } else if (dMech > releaseThreshold) { + // Normal mode, fast forward motion detected. Consider this + // to be a release event. + // + // The release motion of a physical plunger is much faster + // than our sampling rate can keep up with, so we can't just + // use the joystick readings directly. The problem is that a + // real plunger can shoot all the way forward, bounce all the + // way back, and shoot forward again in the time between two + // consecutive samples. A real plunger moves at around 3-5m/s, + // which translates to 3-5mm/ms, or 30-50mm per 10ms sampling + // period. The whole plunger travel distance is ~65mm. + // So in one reading, we can travel almost the whole range! + // This means that samples are effectively random during a + // release motion. We might happen to get lucky and have + // our sample timing align perfectly with a release, so that + // we get one reading at the retracted position just before + // a release and the very next reading at the full forward + // position. Or we might get unlikely and catch one reading + // halfway down the initial initial lunge and the next reading + // at the very apex of the bounce back - and if we took those + // two readings at face value, we'd be fooled into thinking + // the plunger was stationary at the halfway point! + // + // But there's hope. A real plunger's barrel spring is pretty + // inelastic, so the rebounds after a release damp out quickly. + // Observationally, each bounce bounces back to less than half + // of the previous one. So even with the worst-case aliasing, + // we can be confident that we'll see a declining trend in the + // samples during a release-bounce-bounce-bounce sequence. + // + // Our detection strategy is simply to consider any rapid + // forward motion to be a release. If we see the plunger move + // forward by more than the threshold distance, we'll consider + // it a release. See the comments above for how we chose the + // threshold value. + + // Go back through the recent history to find the apex of the + // release. Our "threshold" calculation is basically attempting + // to measure the instantaneous speed of the plunger as the + // difference in position divided by the time interval. But + // the time interval is extremely imprecise, because joystick + // reports aren't synchronized to our clock. In practice the + // time between USB reports is in the 10-30ms range, which gives + // us a considerable range of error in calculating an instantaneous + // speed. + // + // So instead of relying on the instantaneous speed alone, now + // that we're pretty sure a release motion is under way, go back + // through our recent history to find out where it really + // started. Scan the history for monotonically ascending values, + // and take the highest one we find. That's probably where the + // user actually released the plunger. + var apex = velocity.Mech0; + if (velocity.Mech1 > apex) { + apex = velocity.Mech1; + if (velocity.Mech2 > apex) { + apex = velocity.Mech2; + } + } + + // trigger a release from the apex position + PlungerCommands.Fire(apex, ref velocity, ref movement, in staticState); + + } else { + // Normal mode, and NOT firing the plunger. In this mode, we + // simply want to make the on-screen plunger sync up with the + // position of the physical plunger. + // + // This isn't as simple as just setting the software plunger's + // position to magically match that of the physical plunger. If + // we did that, we'd break the simulation by making the software + // plunger move at infinite speed. This wouldn't rip the fabric + // of space-time or anything that dire, but it *would* prevent + // the collision detection code from working properly. + // + // So instead, sync up the positions by setting the software + // plunger in motion on a course for syncing up with the + // physical plunger, as fast as we can while maintaining a + // realistic speed in the simulation. + + // for a normal plunger, sync to the mech plunger; otherwise + // just go to the rest position + var target = autoPlunger ? staticState.RestPosition : mech; + + // figure the current difference in positions + var error = target - pos; + + // Model the software plunger as though it were connected to the + // mechanical plunger by a spring with spring constant 'mech + // strength'. The force from a stretched spring is -kx (spring + // constant times displacement); in this case, the displacement + // is the distance between the physical and virtual plunger tip + // positions ('error'). The force from an acceleration is ma, + // so the acceleration from the spring force is -kx/m. Apply + // this acceleration to the current plunger speed. While we're + // at it, apply some damping to the current speed to simulate + // friction. + // + // The 'normalize' factor is the table's normalization constant + // divided by 1300, for historical reasons. Old versions applied + // a 1/13 adjustment factor, which appears to have been empirically + // chosen to get the speed in the right range. The m_plungerNormalize + // factor has default value 100 in this version, so we need to + // divide it by 100 to get a multipler value. + // + // The 'dt' factor represents the amount of time that we're applying + // this acceleration. This is in "VP 9 physics frame" units, where + // 1.0 equals the amount of real time in one VP 9 physics frame. + // The other normalization factors were originally chosen for VP 9 + // timing, so we need to adjust for the new VP 10 time base. VP 10 + // runs physics frames at roughly 10x the rate of VP 9, so the time + // per frame is about 1/10 the VP 9 time. + const float plungerFriction = 0.95f; + const float normalize = Plunger.PlungerNormalize / 13.0f / 100.0f; + const float dt = 0.1f; + movement.Speed *= plungerFriction; + movement.Speed += error * staticState.FrameLen * velocity.MechStrength / Plunger.PlungerMass * normalize * dt; + + // add any reverse impulse to the result + movement.Speed += movement.ReverseImpulse; + } + + // cancel any reverse impulse + movement.ReverseImpulse = 0.0f; + + // Shift the current mech reading into the history list, if it's + // different from the last reading. Only keep distinct readings; + // the physics loop tends to run faster than the USB reporting + // rate, so we might see the same USB report several times here. + if (mech != velocity.Mech0) { + velocity.Mech2 = velocity.Mech1; + velocity.Mech1 = velocity.Mech0; + velocity.Mech0 = mech; + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocitySystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocitySystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityState.cs similarity index 94% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityState.cs index e7c1fc5dc..5224dc0c9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct PlungerVelocityData : IComponentData + internal struct PlungerVelocityState { /// /// Recent history of mechanical plunger readings. We keep the diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocityState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocitySystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocitySystem.cs deleted file mode 100644 index 29994ca42..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Plunger/PlungerVelocitySystem.cs +++ /dev/null @@ -1,341 +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.Entities; -using Unity.Profiling; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateVelocitiesSystemGroup))] - internal class PlungerVelocitySystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("PlungerVelocitySystem"); - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.ForEach((ref PlungerMovementData movementData, ref PlungerVelocityData velocityData, - in PlungerStaticData staticData) => - { - marker.Begin(); - - // figure our current position in relative coordinates (0.0-1.0, - // where 0.0 is the maximum forward position and 1.0 is the - // maximum retracted position) - var pos = (movementData.Position - staticData.FrameEnd) / staticData.FrameLen; - - var mech = staticData.IsMechPlunger ? movementData.AnalogPosition : 0.0f; - - // calculate the delta from the last reading - var dMech = velocityData.Mech0 - mech; - - // Frame-to-frame mech movement threshold for detecting a release - // motion. 1.0 is the full range of travel, which corresponds - // to about 3" on a standard pinball plunger. We want to choose - // the value here so that it's faster than the player is likely - // to move the plunger manually, but slower than the plunger - // typically moves under spring power when released. It appears - // from observation that a real plunger moves at something on the - // order of 3 m/s. Figure the fastest USB update interval will - // be 10ms, typical is probably 25ms, and slowest is maybe 40ms; - // and figure the bracket speed range down to about 1 m/s. This - // gives us a distance per USB interval of from 25mm to 100mm. - // 25mm translates to .32 of our distance units (0.0-1.0 scale). - // The lower we make this, the more sensitive we'll be at - // detecting releases, but if we make it too low we might mistake - // manual movements for releases. In practice, it seems safe to - // lower it to about 0.2 - this doesn't seem to cause false - // positives and seems reliable at identifying actual releases. - const float releaseThreshold = 0.2f; - - // note if we're acting as an auto plunger - var autoPlunger = staticData.IsAutoPlunger; - - // check which forces are acting on us - if (movementData.FireTimer > 0) { - // Fire mode. In this mode, we're moving freely under the spring - // forces at the speed we calculated when we initiated the release. - // Simply leave the speed unchanged. - // - // Decrement the release mode timer. The mode ends after the - // timeout elapses, even if the mech plunger hasn't actually - // come to rest. This ensures that we don't get stuck in this - // mode, and also allows us to sync up again with the real - // plunger after a respectable pause if the user is just - // moving it around a lot. - movementData.Speed = movementData.FireSpeed; - --movementData.FireTimer; - - } else if (velocityData.AutoFireTimer > 0) { - // The Auto Fire timer is running. We start this timer when we - // send a synthetic KeyDown(Return) event to the script to simulate - // a Launch Ball event when the user pulls back and releases the - // mechanical plunger and we're operating as an auto plunger. - // When the timer reaches zero, we'll send the corresponding - // KeyUp event and cancel the timer. - if (--velocityData.AutoFireTimer == 0) { - // todo event - // if (g_pplayer != 0) { - // g_pplayer->m_ptable->FireKeyEvent(DISPID_GameEvents_KeyUp, g_pplayer->m_rgKeys[ePlungerKey]); - // } - } - - } else if (autoPlunger && dMech > releaseThreshold) { - // Release motion detected in Auto Plunger mode. - // - // If we're acting as an auto plunger, and the player performs - // a pull-and-release motion on the mechanical plunger, simulate - // a Launch Ball event. - // - // An Auto Plunger simulates a solenoid-driven ball launcher - // on a table like Medieval Madness. On this type of game, - // the original machine doesn't have a spring-loaded plunger. - // for the user to operate manually. The user-operated control - // is instead a button of some kind (the physical form varies - // quite a bit, from big round pushbuttons to gun triggers to - // levers to rotating knobs, but they all amount to momentary - // on/off switches in different guises). But on virtual - // cabinets, the mechanical plunger doesn't just magically - // disappear when you load Medieval Madness! So the idea here - // is that we can use a mech plunger to simulate a button. - // It's pretty simple and natural: you just perform the normal - // action that you're accustomed to doing with a plunger, - // namely pulling it back and letting it go. The software - // observes this gesture, and rather than trying to simulate - // the motion directly on the software plunger, we simply - // turn it into a synthetic Launch Ball keyboard event. This - // amounts to sending a KeyDown(Return) message to the script, - // followed a short time later by a KeyUp(Return). The script - // will then act exactly like it would if the user had actually - // pressed the Return key (or, equivalently on a cabinet, the - // Launch Ball button). - - // Send a KeyDown(Return) to the table script. This - // will allow the script to set ROM switch levels or - // perform any other tasks it normally does when the - // actual Launch Ball button is pressed. - - // todo event - // if (g_pplayer != 0) { - // g_pplayer->m_ptable->FireKeyEvent(DISPID_GameEvents_KeyDown, g_pplayer->m_rgKeys[ePlungerKey]); - // } - - // start the timer to send the corresponding KeyUp in 100ms - velocityData.AutoFireTimer = 101; - - } else if (velocityData.PullForce != 0.0f) { - // A "pull" force is in effect. This is a *simulated* pull, so - // it overrides the real physical plunger position. - // - // Simply update the model speed by applying the acceleration - // due to the pull force. - // - // Force = mass*acceleration -> a = F/m. Increase the speed - // by the acceleration. Technically we're calculating dv = a dt, - // but we can elide the elapsed time factor because it's - // effectively a constant that's implicitly folded into the - // pull force value. - movementData.Speed += velocityData.PullForce / Engine.VPT.Plunger.Plunger.PlungerMass; - - if (!velocityData.AddRetractMotion) { - // this is the normal PullBack branch - - // if we're already at the maximum retracted position, stop - if (movementData.Position > staticData.FrameStart) { - movementData.Speed = 0.0f; - movementData.Position = staticData.FrameStart; - } - - // if we're already at the minimum retracted position, stop - if (movementData.Position < staticData.FrameEnd + staticData.RestPosition * staticData.FrameLen) { - movementData.Speed = 0.0f; - movementData.Position = staticData.FrameEnd + staticData.RestPosition * staticData.FrameLen; - } - - } else { - // this is the PullBackandRetract branch - - // after reaching the max. position the plunger should retract until it reaches the min. position and then start again - // if we're already at the maximum retracted position, reverse - if (movementData.Position >= staticData.FrameStart && velocityData.PullForce > 0) { - movementData.Speed = 0.0f; - movementData.Position = staticData.FrameStart; - velocityData.RetractWaitLoop++; - if (velocityData.RetractWaitLoop > 1000) { // 1 sec, related to PHYSICS_STEPTIME - velocityData.PullForce = -velocityData.InitialSpeed; - movementData.Position = staticData.FrameStart; - movementData.RetractMotion = true; - velocityData.RetractWaitLoop = 0; - } - } - - // if we're already at the minimum retracted position, start again - if (movementData.Position <= staticData.FrameEnd + staticData.RestPosition * staticData.FrameLen && velocityData.PullForce <= 0) { - movementData.Speed = 0.0f; - velocityData.PullForce = velocityData.InitialSpeed; - movementData.Position = staticData.FrameEnd + staticData.RestPosition * staticData.FrameLen; - } - - // reset retract motion indicator only after the rest position has been left, to avoid ball interactions - // use a linear pullback motion - if (movementData.Position > 1.0f + staticData.FrameEnd + staticData.RestPosition * staticData.FrameLen && velocityData.PullForce > 0) { - movementData.RetractMotion = false; - movementData.Speed = 3.0f * velocityData.PullForce; // 3 = magic - } - } - - } else if (dMech > releaseThreshold) { - // Normal mode, fast forward motion detected. Consider this - // to be a release event. - // - // The release motion of a physical plunger is much faster - // than our sampling rate can keep up with, so we can't just - // use the joystick readings directly. The problem is that a - // real plunger can shoot all the way forward, bounce all the - // way back, and shoot forward again in the time between two - // consecutive samples. A real plunger moves at around 3-5m/s, - // which translates to 3-5mm/ms, or 30-50mm per 10ms sampling - // period. The whole plunger travel distance is ~65mm. - // So in one reading, we can travel almost the whole range! - // This means that samples are effectively random during a - // release motion. We might happen to get lucky and have - // our sample timing align perfectly with a release, so that - // we get one reading at the retracted position just before - // a release and the very next reading at the full forward - // position. Or we might get unlikely and catch one reading - // halfway down the initial initial lunge and the next reading - // at the very apex of the bounce back - and if we took those - // two readings at face value, we'd be fooled into thinking - // the plunger was stationary at the halfway point! - // - // But there's hope. A real plunger's barrel spring is pretty - // inelastic, so the rebounds after a release damp out quickly. - // Observationally, each bounce bounces back to less than half - // of the previous one. So even with the worst-case aliasing, - // we can be confident that we'll see a declining trend in the - // samples during a release-bounce-bounce-bounce sequence. - // - // Our detection strategy is simply to consider any rapid - // forward motion to be a release. If we see the plunger move - // forward by more than the threshold distance, we'll consider - // it a release. See the comments above for how we chose the - // threshold value. - - // Go back through the recent history to find the apex of the - // release. Our "threshold" calculation is basically attempting - // to measure the instantaneous speed of the plunger as the - // difference in position divided by the time interval. But - // the time interval is extremely imprecise, because joystick - // reports aren't synchronized to our clock. In practice the - // time between USB reports is in the 10-30ms range, which gives - // us a considerable range of error in calculating an instantaneous - // speed. - // - // So instead of relying on the instantaneous speed alone, now - // that we're pretty sure a release motion is under way, go back - // through our recent history to find out where it really - // started. Scan the history for monotonically ascending values, - // and take the highest one we find. That's probably where the - // user actually released the plunger. - var apex = velocityData.Mech0; - if (velocityData.Mech1 > apex) { - apex = velocityData.Mech1; - if (velocityData.Mech2 > apex) { - apex = velocityData.Mech2; - } - } - - // trigger a release from the apex position - PlungerCommands.Fire(apex, ref velocityData, ref movementData, in staticData); - - } else { - // Normal mode, and NOT firing the plunger. In this mode, we - // simply want to make the on-screen plunger sync up with the - // position of the physical plunger. - // - // This isn't as simple as just setting the software plunger's - // position to magically match that of the physical plunger. If - // we did that, we'd break the simulation by making the software - // plunger move at infinite speed. This wouldn't rip the fabric - // of space-time or anything that dire, but it *would* prevent - // the collision detection code from working properly. - // - // So instead, sync up the positions by setting the software - // plunger in motion on a course for syncing up with the - // physical plunger, as fast as we can while maintaining a - // realistic speed in the simulation. - - // for a normal plunger, sync to the mech plunger; otherwise - // just go to the rest position - var target = autoPlunger ? staticData.RestPosition : mech; - - // figure the current difference in positions - var error = target - pos; - - // Model the software plunger as though it were connected to the - // mechanical plunger by a spring with spring constant 'mech - // strength'. The force from a stretched spring is -kx (spring - // constant times displacement); in this case, the displacement - // is the distance between the physical and virtual plunger tip - // positions ('error'). The force from an acceleration is ma, - // so the acceleration from the spring force is -kx/m. Apply - // this acceleration to the current plunger speed. While we're - // at it, apply some damping to the current speed to simulate - // friction. - // - // The 'normalize' factor is the table's normalization constant - // divided by 1300, for historical reasons. Old versions applied - // a 1/13 adjustment factor, which appears to have been empirically - // chosen to get the speed in the right range. The m_plungerNormalize - // factor has default value 100 in this version, so we need to - // divide it by 100 to get a multipler value. - // - // The 'dt' factor represents the amount of time that we're applying - // this acceleration. This is in "VP 9 physics frame" units, where - // 1.0 equals the amount of real time in one VP 9 physics frame. - // The other normalization factors were originally chosen for VP 9 - // timing, so we need to adjust for the new VP 10 time base. VP 10 - // runs physics frames at roughly 10x the rate of VP 9, so the time - // per frame is about 1/10 the VP 9 time. - const float plungerFriction = 0.95f; - const float normalize = Engine.VPT.Plunger.Plunger.PlungerNormalize / 13.0f / 100.0f; - const float dt = 0.1f; - movementData.Speed *= plungerFriction; - movementData.Speed += error * staticData.FrameLen * velocityData.MechStrength / Engine.VPT.Plunger.Plunger.PlungerMass * normalize * dt; - - // add any reverse impulse to the result - movementData.Speed += movementData.ReverseImpulse; - } - - // cancel any reverse impulse - movementData.ReverseImpulse = 0.0f; - - // Shift the current mech reading into the history list, if it's - // different from the last reading. Only keep distinct readings; - // the physics loop tends to run faster than the USB reporting - // rate, so we might see the same USB report several times here. - if (mech != velocityData.Mech0) { - velocityData.Mech2 = velocityData.Mech1; - velocityData.Mech1 = velocityData.Mech0; - velocityData.Mech0 = mech; - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs index ae92367b7..af5a106f1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Primitive; @@ -35,7 +34,7 @@ public class PrimitiveApi : CollidableApi public event EventHandler Hit; - internal PrimitiveApi(GameObject go, Entity entity, Player player) : base(go, entity, player) + internal PrimitiveApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -44,10 +43,10 @@ internal PrimitiveApi(GameObject go, Entity entity, Player player) : base(go, en protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { - var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent); - colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, colliders); + var colliderGenerator = new PrimitiveColliderGenerator(this, MainComponent, MainComponent); + colliderGenerator.GenerateColliders(ColliderComponent.CollisionReductionFactor, ref colliders); } #endregion @@ -64,9 +63,9 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs index cb77d793d..d24b10d64 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderComponent.cs @@ -16,8 +16,7 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; +using System.Collections.Generic; using UnityEngine; using VisualPinball.Engine.VPT.Primitive; @@ -61,7 +60,7 @@ public class PrimitiveColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new PrimitiveApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + =>MainComponent.PrimitiveApi ?? new PrimitiveApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs index b91b0c8e0..1d11db365 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveColliderGenerator.cs @@ -18,14 +18,18 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using NLog; +using Unity.Collections; using Unity.Mathematics; +using Unity.Profiling; +using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.Math.Mesh; using VisualPinball.Engine.VPT; -using MathF = VisualPinball.Engine.Math.MathF; +using Debug = System.Diagnostics.Debug; +using Logger = NLog.Logger; +using Mesh = VisualPinball.Engine.VPT.Mesh; namespace VisualPinball.Unity { @@ -33,50 +37,82 @@ public class PrimitiveColliderGenerator { private readonly IApiColliderGenerator _api; private readonly IMeshGenerator _meshGenerator; + private readonly PrimitiveComponent _primitiveComponent; 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) + public PrimitiveColliderGenerator(IApiColliderGenerator primitiveApi, IMeshGenerator meshGenerator, PrimitiveComponent primitiveComponent) { _api = primitiveApi; _meshGenerator = meshGenerator; + _primitiveComponent = primitiveComponent; } - internal void GenerateColliders(float collisionReductionFactor, List colliders) + internal void GenerateColliders(float collisionReductionFactor, ref ColliderReference colliders) { - var mesh = _meshGenerator.GetMesh(); - if (mesh == null) { + PerfMarker1.Begin(); + //var mesh = _meshGenerator.GetMesh(); + var unityMesh = _primitiveComponent.GetUnityMesh(); + if (unityMesh == null) { Logger.Warn($"Primitive {_meshGenerator.name} did not return a mesh for collider generation."); return; } - mesh = mesh.Transform(_meshGenerator.GetTransformationMatrix().TransformToVpx()); + + using var meshDataArray = UnityEngine.Mesh.AcquireReadOnlyMeshData(unityMesh); + var meshData = meshDataArray[0]; + var subMesh = meshData.GetSubMesh(0); // todo loop through all sub meshes? + // var vpMesh = new Mesh(unityMesh.name) { + // Vertices = new Vertex3DNoTex2[meshData.vertexCount] + // }; + var unityVertices = new NativeArray(meshData.vertexCount, Allocator.TempJob); + var unityIndices = new NativeArray(subMesh.indexCount, Allocator.TempJob); + meshData.GetVertices(unityVertices); + meshData.GetIndices(unityIndices, 0); var reducedVertices = math.max( - (uint) MathF.Pow(mesh.Vertices.Length, - MathF.Clamp(1f - collisionReductionFactor, 0f, 1f) * 0.25f + 0.75f), + (uint) math.pow(meshData.vertexCount, + math.clamp(1f - collisionReductionFactor, 0f, 1f) * 0.25f + 0.75f), 420u //!! 420 = magic ); - if (reducedVertices < mesh.Vertices.Length) { - mesh = ComputeReducedMesh(mesh, reducedVertices); + PerfMarker2.Begin(); + if (reducedVertices < meshData.vertexCount) { + var mesh = ComputeReducedMesh(in unityVertices, in unityIndices, reducedVertices); + unityIndices.Dispose(); + unityVertices.Dispose(); + + var meshVertices = mesh.Vertices.Select(v => v.ToUnityVector3()).ToArray(); + unityVertices = new NativeArray(meshVertices, Allocator.TempJob); + unityIndices = new NativeArray(mesh.Indices, Allocator.TempJob); } + PerfMarker2.End(); + + PerfMarker3.Begin(); + var worldToVpx = _meshGenerator.GetTransformationMatrix().TransformToVpx().ToUnityMatrix(); + ColliderUtils.GenerateCollidersFromMesh(in unityVertices, in unityIndices, ref worldToVpx, _api.GetColliderInfo(), ref colliders); + PerfMarker3.End(); + PerfMarker1.End(); - ColliderUtils.GenerateCollidersFromMesh(mesh, _api.GetColliderInfo(), colliders); + unityVertices.Dispose(); + unityIndices.Dispose(); } - private static Mesh ComputeReducedMesh(Mesh mesh, uint reducedVertices) + private static Mesh ComputeReducedMesh(in NativeArray vertices, in NativeArray indices, uint reducedVertices) { - var progVertices = mesh.Vertices - .Select(v =>new ProgMeshFloat3(v.X, v.Y, v.Z)) + var progVertices = vertices + .Select(v => new ProgMeshFloat3(v.x, v.y, v.z)) .ToArray(); - var progIndices = new ProgMeshTriData[mesh.Indices.Length / 3]; + var progIndices = new ProgMeshTriData[indices.Length / 3]; var i2 = 0; - for (var i = 0; i < mesh.Indices.Length; i += 3) { + for (var i = 0; i < indices.Length; i += 3) { var t = new ProgMeshTriData( - mesh.Indices[i], - mesh.Indices[i + 1], - mesh.Indices[i + 2] + indices[i], + indices[i + 1], + indices[i + 2] ); if (t.V[0] != t.V[1] && t.V[1] != t.V[2] && t.V[2] != t.V[0]) { progIndices[i2++] = t; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs index 71c6c4739..90a555e0f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Primitive/PrimitiveComponent.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Math; @@ -37,7 +36,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Primitive")] public class PrimitiveComponent : MainRenderableComponent, IMeshGenerator, - IRotatableComponent, IConvertGameObjectToEntity + IRotatableComponent { #region Data @@ -100,15 +99,6 @@ public float2 RotatedPosition { #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // register - transform.GetComponentInParent().RegisterPrimitive(this, entity); - } - - public override IEnumerable SetData(PrimitiveData data) { var updatedComponents = new List { this }; @@ -253,9 +243,18 @@ public override void CopyFromObject(GameObject go) #region Runtime + public PrimitiveApi PrimitiveApi { get; private set; } + private void Awake() { _originalRotateZ = ObjectRotation.z; + + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + PrimitiveApi = new PrimitiveApi(gameObject, player, physicsEngine); + + player.Register(PrimitiveApi, this); + RegisterPhysics(physicsEngine); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs index 93f5098e6..66752d069 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampApi.cs @@ -16,12 +16,12 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; +using VisualPinball.Engine.VPT.Ramp; namespace VisualPinball.Unity { - public class RampApi : CollidableApi, IApi, IApiHittable + public class RampApi : CollidableApi, IApi, IApiHittable { /// /// Event emitted when the table is started. @@ -33,7 +33,7 @@ public class RampApi : CollidableApi public event EventHandler Hit; - internal RampApi(GameObject go, Entity entity, Player player) : base(go, entity, player) + internal RampApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -45,9 +45,9 @@ void IApi.OnInit(BallManager ballManager) Init?.Invoke(this, EventArgs.Empty); } - public void OnHit(Entity ballEntity, bool isUnHit = false) + public void OnHit(int ballId, bool isUnHit = false) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } void IApi.OnDestroy() @@ -61,10 +61,10 @@ void IApi.OnDestroy() protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new RampColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, colliders, margin); + 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 203c1d124..abf566dc1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Ramp; @@ -61,7 +59,7 @@ public class RampColliderComponent : ColliderComponent #endregion public override PhysicsMaterialData PhysicsMaterialData => GetPhysicsMaterialData(Elasticity, friction: Friction, scatterAngleDeg: Scatter, overwrite: OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new RampApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.RampApi ?? new RampApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index f2214b7ef..1c6c5b543 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -38,7 +38,7 @@ public RampColliderGenerator(RampApi rampApi, IRampData data, RampColliderCompon _meshGenerator = new RampMeshGenerator(data); } - internal void GenerateColliders(float tableHeight, List colliders, float margin = 0f) + internal void GenerateColliders(float tableHeight, ref ColliderReference colliders, float margin = 0f) { var rv = _meshGenerator.GetRampVertex(tableHeight, PhysicsConstants.HitShapeDetailLevel, true); var rgvLocal = rv.RgvLocal; @@ -58,9 +58,9 @@ internal void GenerateColliders(float tableHeight, List colliders, fl pv3 = rgvLocal[i + 1].ToUnityFloat2(); GenerateWallLineSeg(pv2, pv3, i > 0,rgHeight1[i], - rgHeight1[i + 1], wallHeightRight, colliders); + rgHeight1[i + 1], wallHeightRight, ref colliders); GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[i], - rgHeight1[i + 1], wallHeightRight, colliders); + rgHeight1[i + 1], wallHeightRight, ref colliders); // add joints at start and end of right wall if (i == 0) { @@ -82,9 +82,9 @@ internal void GenerateColliders(float tableHeight, List colliders, fl pv3 = rgvLocal[vertexCount + i + 1].ToUnityFloat2(); GenerateWallLineSeg(pv2, pv3, i > 0, rgHeight1[vertexCount - i - 2], - rgHeight1[vertexCount - i - 1], wallHeightLeft, colliders); + rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); GenerateWallLineSeg(pv3, pv2, i < vertexCount - 2, rgHeight1[vertexCount - i - 2], - rgHeight1[vertexCount - i - 1], wallHeightLeft, colliders); + rgHeight1[vertexCount - i - 1], wallHeightLeft, ref colliders); // add joints at start and end of left wall if (i == 0) { @@ -135,7 +135,7 @@ internal void GenerateColliders(float tableHeight, List colliders, fl var ph3dPoly = new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()); colliders.Add(ph3dPoly); - CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, colliders); + CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, ref colliders); ph3dPolyOld = ph3dPoly; isOldSet = true; } @@ -152,7 +152,7 @@ internal void GenerateColliders(float tableHeight, List colliders, fl var ph3dPoly = new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo()); colliders.Add(ph3dPoly); - CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, colliders); + CheckJoint(isOldSet, in ph3dPolyOld, in ph3dPoly, ref colliders); ph3dPolyOld = ph3dPoly; isOldSet = true; } @@ -211,14 +211,14 @@ private float2 GetWallHeights() } private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float height1, float height2, float wallHeight, - ICollection colliders) + 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, colliders); + 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, 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, @@ -231,7 +231,7 @@ private void GenerateWallLineSeg(float2 pv1, float2 pv2, bool pv3Exists, float h } private void CheckJoint(bool isOldSet, in TriangleCollider ph3d1, in TriangleCollider ph3d2, - ICollection colliders) + ref ColliderReference colliders) { if (isOldSet) { // may be null in case of degenerate triangles var jointNormal = math.cross(ph3d1.Normal(), ph3d2.Normal()); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 0b6969653..4cc1f25c7 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -25,19 +25,19 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Ramp; 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/Ramp")] public class RampComponent : MainRenderableComponent, - IRampData, ISurfaceComponent, IConvertGameObjectToEntity + IRampData, ISurfaceComponent { #region Data @@ -121,12 +121,28 @@ public class RampComponent : MainRenderableComponent, #endregion + #region Runtime + + public RampApi RampApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + RampApi = new RampApi(gameObject, player, physicsEngine); + + player.Register(RampApi, this); + RegisterPhysics(physicsEngine); + } + + #endregion + #region Transformation public float Height(Vector2 pos) { var vVertex = new RampMeshGenerator(this).GetCentralCurve(); - Engine.VPT.Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x, pos.y), false, out var vOut, out var iSeg); + Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x, pos.y), false, out var vOut, out var iSeg); if (iSeg == -1) { return 0.0f; // Object is not on ramp path @@ -190,14 +206,6 @@ public override void UpdateVisibility() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // register - transform.GetComponentInParent().RegisterRamp(this, entity); - } - public override IEnumerable SetData(RampData data) { var updatedComponents = new List { this }; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs index ea61d2ada..2d92b9260 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Rubber; @@ -35,7 +34,7 @@ public class RubberApi : CollidableApi public event EventHandler Hit; - internal RubberApi(GameObject go, Entity entity, Player player) : base(go, entity, player) + internal RubberApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -44,10 +43,10 @@ internal RubberApi(GameObject go, Entity entity, Player player) : base(go, entit protected override bool FireHitEvents => ColliderComponent.HitEvent; protected override float HitThreshold => 2.0f; // hard coded threshold for now - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new RubberColliderGenerator(this, new RubberMeshGenerator(MainComponent)); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, colliders, margin); + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ColliderComponent.HitHeight, MainComponent.PlayfieldDetailLevel, ref colliders, margin); } #endregion @@ -64,9 +63,9 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs index 90bfd17eb..bcd44bc7f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Rubber; @@ -56,7 +54,7 @@ public class RubberColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new RubberApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.RubberApi ?? new RubberApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs index 8142ae59b..f5786261b 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberColliderGenerator.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using System.Collections.Generic; +using Unity.Collections; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Rubber; @@ -31,10 +32,10 @@ public RubberColliderGenerator(RubberApi rubberApi, RubberMeshGenerator meshGene _meshGenerator = meshGenerator; } - internal void GenerateColliders(float playfieldHeight, float hitHeight, int detailLevel, List colliders, float margin) + internal void GenerateColliders(float playfieldHeight, float hitHeight, int detailLevel, ref ColliderReference colliders, float margin) { var mesh = _meshGenerator.GetTransformedMesh(playfieldHeight, hitHeight, detailLevel, 6, true, margin); //!! adapt hacky code in the function if changing the "6" here - var addedEdges = EdgeSet.Get(); + var addedEdges = EdgeSet.Get(Allocator.TempJob); // add collision triangles and edges for (var i = 0; i < mesh.Indices.Length; i += 3) { @@ -45,19 +46,21 @@ internal void GenerateColliders(float playfieldHeight, float hitHeight, int deta colliders.Add(new TriangleCollider(rg0, rg1, rg2, _api.GetColliderInfo())); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i], mesh.Indices[i + 2], colliders); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i + 2], mesh.Indices[i + 1], colliders); - GenerateHitEdge(mesh, addedEdges, mesh.Indices[i + 1], mesh.Indices[i], colliders); + 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, EdgeSet addedEdges, int i, int j, - ICollection 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(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs index 4a0698957..ecc4f402e 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Rubber/RubberComponent.cs @@ -24,7 +24,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -35,7 +34,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Rubber")] public class RubberComponent : MainRenderableComponent, - IRubberData, IConvertGameObjectToEntity + IRubberData { #region Data @@ -85,6 +84,22 @@ public class RubberComponent : MainRenderableComponent, #endregion + #region Runtime + + public RubberApi RubberApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + RubberApi = new RubberApi(gameObject, player, physicsEngine); + + player.Register(RubberApi, this); + RegisterPhysics(physicsEngine); + } + + #endregion + #region Transformation public override void OnPlayfieldHeightUpdated() => RebuildMeshes(); @@ -93,14 +108,6 @@ public class RubberComponent : MainRenderableComponent, #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // register - transform.GetComponentInParent().RegisterRubber(this, entity); - } - public override IEnumerable SetData(RubberData data) { var updatedComponents = new List { this }; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs index 7c9d57203..ccc5f7940 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerApi.cs @@ -15,8 +15,6 @@ // along with this program. If not, see . using System; -using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Spinner; @@ -65,8 +63,7 @@ public class SpinnerApi : CollidableApi public event EventHandler Switch; - public SpinnerApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + public SpinnerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -85,10 +82,10 @@ public SpinnerApi(GameObject go, Entity entity, Player player) protected override bool FireHitEvents => true; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var colliderGenerator = new SpinnerColliderGenerator(this, MainComponent); - colliderGenerator.GenerateColliders(MainComponent.HeightOnPlayfield, colliders); + colliderGenerator.GenerateColliders(MainComponent.HeightOnPlayfield, ref colliders); } #endregion @@ -108,7 +105,7 @@ void IApi.OnDestroy() void IApiSpinnable.OnSpin() { Spin?.Invoke(this, EventArgs.Empty); - Switch?.Invoke(this, new SwitchEventArgs(true, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(true)); OnSwitch(true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs index bf27cc941..05f57fad2 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerCollider.cs @@ -14,8 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Collections.LowLevel.Unsafe; -using Unity.Entities; +using Unity.Collections; using Unity.Mathematics; using VisualPinball.Engine.Common; @@ -23,9 +22,18 @@ namespace VisualPinball.Unity { internal struct SpinnerCollider : ICollider { - public int Id => _header.Id; + public int Id + { + get => Header.Id; + set { + Header.Id = value; + var bounds = Bounds; + bounds.ColliderId = value; + Bounds = bounds; + } + } - private ColliderHeader _header; + public ColliderHeader Header; public readonly LineCollider LineSeg0; public readonly LineCollider LineSeg1; @@ -34,7 +42,7 @@ internal struct SpinnerCollider : ICollider public SpinnerCollider(SpinnerComponent component, float height, ColliderInfo info) : this() { - _header.Init(info, ColliderType.Spinner); + Header.Init(info, ColliderType.Spinner); var halfLength = component.Length * 0.5f; @@ -57,24 +65,9 @@ public SpinnerCollider(SpinnerComponent component, float height, ColliderInfo in Bounds = LineSeg0.Bounds; } - public unsafe void Allocate(BlobBuilder builder, ref BlobBuilderArray> colliders, int colliderId) - { - _header.Id = colliderId; - var bounds = Bounds; - bounds.ColliderId = colliderId; - Bounds = bounds; - ref var ptr = ref UnsafeUtility.As, BlobPtr>(ref colliders[_header.Id]); - ref var collider = ref builder.Allocate(ref ptr); - UnsafeUtility.MemCpy( - UnsafeUtility.AddressOf(ref collider), - UnsafeUtility.AddressOf(ref this), - sizeof(SpinnerCollider) - ); - } - #region Narrowphase - public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, in BallData ball, float dTime) + public float HitTest(ref CollisionEventData collEvent, ref InsideOfs insideOfs, in BallState ball, float dTime) { // todo // if (!m_enabled) return -1.0f; @@ -100,7 +93,7 @@ public float HitTest(ref CollisionEventData collEvent, ref DynamicBuffer 1.0f) { // avoid divide by zero - movementData.AngleSpeed /= h; + movement.AngleSpeed /= h; } - movementData.AngleSpeed *= data.Damping; + movement.AngleSpeed *= state.Damping; // We encoded which side of the spinner the ball hit if (collEvent.HitFlag) { - movementData.AngleSpeed = -movementData.AngleSpeed; + movement.AngleSpeed = -movement.AngleSpeed; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs index a0a7018e0..bd90929bb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Spinner; @@ -35,7 +33,7 @@ public class SpinnerColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new SpinnerApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.SpinnerApi ?? new SpinnerApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs index 859528890..edd7f5dbb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerColliderGenerator.cs @@ -30,15 +30,15 @@ public SpinnerColliderGenerator(SpinnerApi spinnerApi, SpinnerComponent componen _component = component; } - internal void GenerateColliders(float height, List colliders) + internal void GenerateColliders(float height, ref ColliderReference colliders) { colliders.Add(new SpinnerCollider(_component, height - _component.Height, _api.GetColliderInfo())); if (_component.ShowBracket) { - GenerateBracketColliders(height, colliders); + GenerateBracketColliders(height, ref colliders); } } - private void GenerateBracketColliders(float height, ICollection colliders) + private void GenerateBracketColliders(float height, ref ColliderReference colliders) { const float h = 30.0f; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs index aa6a89339..d7fe56f65 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerComponent.cs @@ -23,7 +23,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.Common; @@ -36,7 +35,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Spinner")] public class SpinnerComponent : MainRenderableComponent, - ISwitchDeviceComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ISwitchDeviceComponent, IOnSurfaceComponent { #region Data @@ -91,6 +90,22 @@ public class SpinnerComponent : MainRenderableComponent, #endregion + #region Runtime + + public SpinnerApi SpinnerApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + SpinnerApi = new SpinnerApi(gameObject, player, physicsEngine); + + player.Register(SpinnerApi, this); + RegisterPhysics(physicsEngine); + } + + #endregion + #region Wiring public IEnumerable AvailableSwitches => new[] { @@ -129,35 +144,6 @@ public override void UpdateTransforms() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // physics collision data - var collComponent = GetComponent(); - if (collComponent) { - - dstManager.AddComponentData(entity, new SpinnerStaticData { - AngleMax = math.radians(AngleMax), - AngleMin = math.radians(AngleMin), - Damping = math.pow(Damping, (float)PhysicsConstants.PhysFactor), - Elasticity = collComponent.Elasticity, - Height = Height - }); - } - - // animation - if (GetComponentInChildren()) { - dstManager.AddComponentData(entity, new SpinnerMovementData { - Angle = math.radians(math.clamp(0.0f, AngleMin, AngleMax)), - AngleSpeed = 0f - }); - } - - // register - transform.GetComponentInParent().RegisterSpinner(this, entity); - } - public override IEnumerable SetData(SpinnerData data) { var updatedComponents = new List { this }; @@ -263,6 +249,39 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal SpinnerState CreateState() + { + // physics collision data + var collComponent = GetComponent(); + var staticData = collComponent + ? new SpinnerStaticState { + AngleMax = math.radians(AngleMax), + AngleMin = math.radians(AngleMin), + Damping = math.pow(Damping, (float)PhysicsConstants.PhysFactor), + Elasticity = collComponent.Elasticity, + Height = Height + } : default; + + // animation + var animComponent = GetComponentInChildren(); + var movementData = animComponent + ? new SpinnerMovementState { + Angle = math.radians(math.clamp(0.0f, AngleMin, AngleMax)), + AngleSpeed = 0f + } : default; + + return new SpinnerState( + collComponent ? gameObject.GetInstanceID() : 0, + animComponent ? animComponent.gameObject.GetInstanceID() : 0, + staticData, + movementData + ); + } + + #endregion + #region Editor Tooling public override ItemDataTransformType EditorPositionType => ItemDataTransformType.ThreeD; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementPhysics.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementPhysics.cs new file mode 100644 index 000000000..12d5c480c --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementPhysics.cs @@ -0,0 +1,92 @@ +// 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 CompareOfFloatsByEqualityOperator + +using Unity.Collections; +using Unity.Mathematics; +using VisualPinball.Engine.Game; + +namespace VisualPinball.Unity +{ + internal static class SpinnerDisplacementPhysics + { + internal static void UpdateDisplacement(int itemId, ref SpinnerMovementState movement, in SpinnerStaticState state, + float dTime, ref NativeQueue.ParallelWriter events) + { + + // those are already converted to radian during authoring. + var angleMin = state.AngleMin; + var angleMax = state.AngleMax; + + // blocked spinner, limited motion spinner + if (state.AngleMin != state.AngleMax) { + + movement.Angle += movement.AngleSpeed * dTime; + + if (movement.Angle > angleMax) { + movement.Angle = angleMax; + + // send EOS event + events.Enqueue(new EventData(EventId.LimitEventsEos, itemId, math.abs(math.degrees(movement.AngleSpeed)))); + + if (movement.AngleSpeed > 0.0f) { + movement.AngleSpeed *= -0.005f - state.Elasticity; + } + } + + if (movement.Angle < angleMin) { + movement.Angle = angleMin; + + // send Park event + events.Enqueue(new EventData(EventId.LimitEventsBos, itemId, math.abs(math.degrees(movement.AngleSpeed)))); + + if (movement.AngleSpeed < 0.0f) { + movement.AngleSpeed *= -0.005f - state.Elasticity; + } + } + + } else { + + var target = movement.AngleSpeed > 0.0f + ? movement.Angle < math.PI ? math.PI : 3.0f * math.PI + : movement.Angle < math.PI ? -math.PI : math.PI; + + movement.Angle += movement.AngleSpeed * dTime; + + if (movement.AngleSpeed > 0.0f) { + + if (movement.Angle > target) { + events.Enqueue(new EventData(EventId.SpinnerEventsSpin, itemId, true)); + } + + } else { + if (movement.Angle < target) { + events.Enqueue(new EventData(EventId.SpinnerEventsSpin, itemId, true)); + } + } + + while (movement.Angle > 2.0f * math.PI) { + movement.Angle -= 2.0f * math.PI; + } + + while (movement.Angle < 0.0f) { + movement.Angle += 2.0f * math.PI; + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementSystem.cs deleted file mode 100644 index a60e16ed8..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerDisplacementSystem.cs +++ /dev/null @@ -1,132 +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 CompareOfFloatsByEqualityOperator - -using Unity.Collections; -using Unity.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; -using VisualPinball.Engine.Game; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateDisplacementSystemGroup))] - internal class SpinnerDisplacementSystem : SystemBase - { - private Player _player; - private SimulateCycleSystemGroup _simulateCycleSystemGroup; - private NativeQueue _eventQueue; - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("SpinnerDisplacementSystem"); - - protected override void OnCreate() - { - _player = Object.FindObjectOfType(); - _simulateCycleSystemGroup = World.GetOrCreateSystem(); - _eventQueue = new NativeQueue(Allocator.Persistent); - } - - protected override void OnDestroy() - { - _eventQueue.Dispose(); - } - - protected override void OnUpdate() - { - - var events = _eventQueue.AsParallelWriter(); - - var dTime = _simulateCycleSystemGroup.HitTime; - var marker = PerfMarker; - - Entities - .WithName("SpinnerDisplacementJob") - .ForEach((Entity entity, ref SpinnerMovementData movementData, in SpinnerStaticData data) => { - - marker.Begin(); - - // those are already converted to radian during authoring. - var angleMin = data.AngleMin; - var angleMax = data.AngleMax; - - // blocked spinner, limited motion spinner - if (data.AngleMin != data.AngleMax) { - - movementData.Angle += movementData.AngleSpeed * dTime; - - if (movementData.Angle > angleMax) { - movementData.Angle = angleMax; - - // send EOS event - events.Enqueue(new EventData(EventId.LimitEventsEos, entity, math.abs(math.degrees(movementData.AngleSpeed)))); - - if (movementData.AngleSpeed > 0.0f) { - movementData.AngleSpeed *= -0.005f - data.Elasticity; - } - } - - if (movementData.Angle < angleMin) { - movementData.Angle = angleMin; - - // send Park event - events.Enqueue(new EventData(EventId.LimitEventsBos, entity, math.abs(math.degrees(movementData.AngleSpeed)))); - - if (movementData.AngleSpeed < 0.0f) { - movementData.AngleSpeed *= -0.005f - data.Elasticity; - } - } - - } else { - - var target = movementData.AngleSpeed > 0.0f - ? movementData.Angle < math.PI ? math.PI : 3.0f * math.PI - : movementData.Angle < math.PI ? -math.PI : math.PI; - - movementData.Angle += movementData.AngleSpeed * dTime; - - if (movementData.AngleSpeed > 0.0f) { - - if (movementData.Angle > target) { - events.Enqueue(new EventData(EventId.SpinnerEventsSpin, entity, true)); - } - - } else { - if (movementData.Angle < target) { - events.Enqueue(new EventData(EventId.SpinnerEventsSpin, entity, true)); - } - } - - while (movementData.Angle > 2.0f * math.PI) { - movementData.Angle -= 2.0f * math.PI; - } - - while (movementData.Angle < 0.0f) { - movementData.Angle += 2.0f * math.PI; - } - } - - marker.End(); - - }).Run(); - - // dequeue events - while (_eventQueue.TryDequeue(out var eventData)) { - _player.OnEvent(in eventData); - } - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementState.cs index 469e48ee3..f759333ff 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct SpinnerMovementData : IComponentData + internal struct SpinnerMovementState { public float Angle; public float AngleSpeed; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs deleted file mode 100644 index 5fa5f0ac3..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs +++ /dev/null @@ -1,51 +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.Entities; -using Unity.Mathematics; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class SpinnerMovementSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("SpinnerMovementSystem"); - - private Player _player; - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - Entities.WithoutBurst().WithName("SpinnerMovementJob").ForEach((Entity entity, in SpinnerMovementData movementData) => { - - marker.Begin(); - - _player.SpinnerPlateTransforms[entity].localRotation = quaternion.RotateX(-movementData.Angle); - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs.meta deleted file mode 100644 index 5e077fb17..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerMovementSystem.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 864f0b7730837de4aa013de0b2aecddb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs similarity index 65% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs index 941748482..4fed8ca9d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs @@ -1,34 +1,34 @@ -// 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.Entities; - -namespace VisualPinball.Unity -{ - public class BallApi - { - private readonly Entity Entity; - private readonly Player Player; - - protected readonly EntityManager EntityManager = World.DefaultGameObjectInjectionWorld.EntityManager; - - public BallApi(Entity entity, Player player) - { - Entity = entity; - Player = player; - } - } -} +// 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 +{ + internal struct SpinnerState + { + internal readonly int ItemId; + internal readonly int AnimationItemId; + internal SpinnerStaticState Static; + internal SpinnerMovementState Movement; + + public SpinnerState(int itemId, int animationItemId, SpinnerStaticState @static, SpinnerMovementState movement) + { + ItemId = itemId; + AnimationItemId = animationItemId; + Static = @static; + Movement = movement; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs.meta new file mode 100644 index 000000000..da6c1b01d --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 50805e48005345e69c19a503e93d1a59 +timeCreated: 1696500242 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticState.cs index 87b7c0b06..c44a0e0c3 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct SpinnerStaticData : IComponentData + internal struct SpinnerStaticState { public float AngleMin; public float AngleMax; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs similarity index 62% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs index 338789778..6bff32dd1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ball/BallLastPositionsBufferElement.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs @@ -1,4 +1,4 @@ -// Visual Pinball Engine +// Visual Pinball Engine // Copyright (C) 2023 freezy and VPE Team // // This program is free software: you can redistribute it and/or modify @@ -14,17 +14,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; using Unity.Mathematics; +using VisualPinball.Engine.Common; namespace VisualPinball.Unity { - /// - /// The ball's last positions - /// - [InternalBufferCapacity(BallRingCounterSystem.MaxBallTrailPos)] - internal struct BallLastPositionsBufferElement : IBufferElementData + internal static class SpinnerVelocityPhysics { - public float3 Value; + internal static void UpdateVelocities(ref SpinnerMovementState movement, in SpinnerStaticState state) + { + // Center of gravity towards bottom of object, makes it stop vertical + movement.AngleSpeed -= math.sin(movement.Angle) * (float)(0.0025 * PhysicsConstants.PhysFactor); + movement.AngleSpeed *= state.Damping; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocitySystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocitySystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Spinner/SpinnerVelocityPhysics.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotApi.cs index aeb7b50af..8e0699218 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotApi.cs @@ -15,10 +15,9 @@ // along with this program. If not, see . using System; -using Logger = NLog.Logger; using NLog; using UnityEngine; -using Unity.Entities; +using Logger = NLog.Logger; namespace VisualPinball.Unity { @@ -43,12 +42,12 @@ public class SlingshotApi : IApi, IApiSwitch, IApiSwitchDevice private void OnSwitch(bool closed) => _switchHandler.OnSwitch(closed); - internal SlingshotApi(GameObject go, Player player) + internal SlingshotApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _slingshotComponent = go.GetComponentInChildren(); _player = player; - _switchHandler = new SwitchHandler(go.name, player); + _switchHandler = new SwitchHandler(go.name, player, physicsEngine); } void IApi.OnInit(BallManager ballManager) @@ -64,7 +63,7 @@ void IApi.OnInit(BallManager ballManager) private void OnSlingshot(object sender, EventArgs e) { - Switch?.Invoke(this, new SwitchEventArgs(true, Entity.Null)); + Switch?.Invoke(this, new SwitchEventArgs(true)); OnSwitch(true); } @@ -78,4 +77,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs index bb711442a..10ea2821d 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SlingshotComponent.cs @@ -19,7 +19,6 @@ using System; using System.Collections; using System.Collections.Generic; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; using UnityEngine.Serialization; @@ -92,12 +91,17 @@ public class SlingshotComponent : MonoBehaviour, IMeshComponent, IMainRenderable IEnumerable IDeviceComponent.AvailableDeviceItems => AvailableSwitches; - #region Runtime + public SlingshotApi SlingshotApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterSlingshotComponent(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + SlingshotApi = new SlingshotApi(gameObject, player, physicsEngine); + + player.Register(SlingshotApi, this); } private void Start() @@ -241,7 +245,6 @@ private Mesh GetMesh() public bool CanBeTransformed => false; public bool OverrideTransform => false; public string ItemName => "Slingshot"; - public Entity Entity { get; set; } public void UpdateTransforms() { } public void UpdateVisibility() { } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs index fef9b2063..25f804dac 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceApi.cs @@ -16,12 +16,12 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; +using VisualPinball.Engine.VPT.Surface; namespace VisualPinball.Unity { - public class SurfaceApi : CollidableApi, + public class SurfaceApi : CollidableApi, IApi, IApiHittable, IApiSlingshot { /// @@ -39,7 +39,7 @@ public class SurfaceApi : CollidableApi public event EventHandler Slingshot; - internal SurfaceApi(GameObject go, Entity entity, Player player) : base(go, entity, player) + internal SurfaceApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -47,13 +47,13 @@ internal SurfaceApi(GameObject go, Entity entity, Player player) : base(go, enti protected override bool FireHitEvents => true; protected override float HitThreshold => ColliderComponent.Threshold; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { if (MainComponent.DragPoints.Length == 0) { return; } var colliderGenerator = new SurfaceColliderGenerator(this, MainComponent, ColliderComponent); - colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, colliders, margin); + colliderGenerator.GenerateColliders(MainComponent.PlayfieldHeight, ref colliders, margin); } #endregion @@ -70,12 +70,12 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool _) + void IApiHittable.OnHit(int ballId, bool _) { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); } - public void OnSlingshot(Entity ballEntity) + public void OnSlingshot(int ballId) { Slingshot?.Invoke(this, EventArgs.Empty); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs index 17fa8338c..26f38178c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderComponent.cs @@ -16,8 +16,6 @@ // ReSharper disable InconsistentNaming -using System; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Surface; @@ -68,7 +66,7 @@ public class SurfaceColliderComponent : ColliderComponent GetPhysicsMaterialData(Elasticity, ElasticityFalloff, Friction, Scatter, OverwritePhysics); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new SurfaceApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.SurfaceApi ?? new SurfaceApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs index 565d05dab..4c1ec83d9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceColliderGenerator.cs @@ -35,7 +35,7 @@ public SurfaceColliderGenerator(SurfaceApi surfaceApi, SurfaceComponent componen _colliderComponent = colliderComponent; } - internal void GenerateColliders(float playfieldHeight, List colliders, float margin = 0f) + internal void GenerateColliders(float playfieldHeight, ref ColliderReference colliders, float margin = 0f) { var vVertex = DragPoint.GetRgVertex(_component.DragPoints); @@ -56,20 +56,20 @@ internal void GenerateColliders(float playfieldHeight, List colliders var pv2 = vVertex[(i + 1) % count]; var pv3 = vVertex[(i + 2) % count]; - GenerateLinePolys(pv2, pv3, playfieldHeight, colliders); + GenerateLinePolys(pv2, pv3, playfieldHeight, ref colliders); } - ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), colliders); + ColliderUtils.Generate3DPolyColliders(in rgv3Dt, _api.GetColliderInfo(), ref colliders); if (rgv3Db != null) { - ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), colliders); + ColliderUtils.Generate3DPolyColliders(in rgv3Db, _api.GetColliderInfo(), ref colliders); } } /// /// Returns the hit line polygons for the surface. /// - private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfieldHeight, ICollection colliders) + private void GenerateLinePolys(RenderVertex2D pv1, Vertex2D pv2, float playfieldHeight, ref ColliderReference colliders) { var bottom = _component.HeightBottom + playfieldHeight; var top = _component.HeightTop + playfieldHeight; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index 7b56dbeea..ee7dde8f3 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.Entities; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; @@ -34,8 +33,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Surface")] - public class SurfaceComponent : MainRenderableComponent, - IConvertGameObjectToEntity, ISurfaceComponent + public class SurfaceComponent : MainRenderableComponent, ISurfaceComponent { #region Data @@ -66,6 +64,24 @@ public class SurfaceComponent : MainRenderableComponent, #endregion + #region Runtime + + public SurfaceApi SurfaceApi { get; private set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + SurfaceApi = new SurfaceApi(gameObject, player, physicsEngine); + + player.Register(SurfaceApi, this); + if (GetComponentInChildren()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + #region Transformation public float Height(Vector2 _) => HeightTop + PlayfieldHeight; @@ -76,22 +92,6 @@ public class SurfaceComponent : MainRenderableComponent, #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - // physics collision data - var collComponent = GetComponentInChildren(); - if (collComponent) { - dstManager.AddComponentData(entity, new LineSlingshotData { - IsDisabled = false, - Threshold = collComponent.SlingshotThreshold, - }); - } - - transform.GetComponentInParent().RegisterSurface(this, entity); - } - public override IEnumerable SetData(SurfaceData data) { var updatedComponents = new List { this }; @@ -209,6 +209,24 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal SurfaceState CreateState() + { + // physics collision data + var collComponent = GetComponentInChildren(); + if (!collComponent) { + return default; + } + + return new SurfaceState(gameObject.GetInstanceID(), new LineSlingshotState { + IsDisabled = false, + Threshold = collComponent.SlingshotThreshold, + }); + } + + #endregion + #region Editor Tooling private Vector3 DragPointCenter { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs similarity index 77% rename from VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs index 13b580640..4213e4550 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Physics/Collision/ColliderData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs @@ -1,25 +1,30 @@ -// 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.Entities; - -namespace VisualPinball.Unity -{ - internal struct ColliderData : IComponentData - { - public BlobAssetReference Value; - } -} +// 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 +{ + internal struct SurfaceState + { + internal readonly int ItemId; + internal LineSlingshotState Slingshot; + + public SurfaceState(int itemId, LineSlingshotState slingshot) + { + ItemId = itemId; + Slingshot = slingshot; + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs.meta new file mode 100644 index 000000000..a93163541 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: faab83ea51c34c6991cfeb4c1540bd2d +timeCreated: 1696503371 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs index aa646d576..2e40a2668 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/SceneTableContainer.cs @@ -104,6 +104,10 @@ 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); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterApi.cs index d39cf223c..288927336 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterApi.cs @@ -17,11 +17,10 @@ // ReSharper disable ConvertIfStatementToSwitchStatement using System; -using Logger = NLog.Logger; using NLog; -using Unity.Entities; using Unity.Mathematics; using UnityEngine; +using Logger = NLog.Logger; namespace VisualPinball.Unity { @@ -31,19 +30,19 @@ public class TeleporterApi : IApi, IApiCoilDevice private readonly TeleporterComponent _component; private readonly Player _player; + private readonly PhysicsEngine _physicsEngine; private DeviceCoil _teleporterCoil; private KickerApi _fromKicker; private KickerApi _toKicker; - private readonly VisualPinballSimulationSystemGroup _simulationSystemGroup; public event EventHandler Init; - internal TeleporterApi(GameObject go, Player player) + internal TeleporterApi(GameObject go, Player player, PhysicsEngine physicsEngine) { _component = go.GetComponentInChildren(); _player = player; - _simulationSystemGroup = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem(); + _physicsEngine = physicsEngine; } void IApi.OnInit(BallManager ballManager) @@ -91,7 +90,7 @@ private void OnTeleport() private void Eject() { if (_component.EjectDelay > 0) { - _simulationSystemGroup.ScheduleAction((int)math.round(_component.EjectDelay * 1000f), TriggerEjectCoil); + _physicsEngine.ScheduleAction((int)math.round(_component.EjectDelay * 1000f), TriggerEjectCoil); } else { TriggerEjectCoil(); @@ -111,4 +110,3 @@ void IApi.OnDestroy() } } } - diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterComponent.cs index 8427c0828..8e83e1883 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Teleporter/TeleporterComponent.cs @@ -71,6 +71,8 @@ public class TeleporterComponent : MonoBehaviour, ICoilDeviceComponent #region Runtime + public TeleporterApi TeleporterApi { get; private set; } + private void Awake() { var player = GetComponentInParent(); @@ -84,7 +86,10 @@ private void Awake() ToKicker.GetComponent().enabled = false; } - player.RegisterTeleporter(this); + var physicsEngine = GetComponentInParent(); + TeleporterApi = new TeleporterApi(gameObject, player, physicsEngine); + + player.Register(TeleporterApi, this); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs new file mode 100644 index 000000000..245822b61 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs @@ -0,0 +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; + } + } + } + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimation.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationState.cs index 86cc7cc23..aa1594e03 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct TriggerAnimationData : IComponentData + internal struct TriggerAnimationState { public bool HitEvent; public bool UnHitEvent; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationSystem.cs deleted file mode 100644 index 4ba2d2fa5..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerAnimationSystem.cs +++ /dev/null @@ -1,99 +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.Entities; -using Unity.Profiling; -using VisualPinball.Engine.VPT; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(UpdateAnimationsSystemGroup))] - internal class TriggerAnimationSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("TriggerAnimationSystem"); - - protected override void OnUpdate() - { - var dTime = Time.DeltaTime * 1000; - var marker = PerfMarker; - - Entities - .WithName("TriggerAnimationJob") - .ForEach((ref TriggerAnimationData data, ref TriggerMovementData movementData, in TriggerStaticData staticData) => { - - marker.Begin(); - - var oldTimeMsec = data.TimeMsec < dTime ? data.TimeMsec : dTime; - data.TimeMsec = dTime; - var diffTimeMsec = dTime - oldTimeMsec; - - var animLimit = staticData.Shape == TriggerShape.TriggerStar ? staticData.Radius * (float)(1.0 / 5.0) : 32.0f; - if (staticData.Shape == TriggerShape.TriggerButton) { - animLimit = staticData.Radius * (float)(1.0 / 10.0); - } - if (staticData.Shape == TriggerShape.TriggerWireC) { - animLimit = 60.0f; - } - if (staticData.Shape == TriggerShape.TriggerWireD) { - animLimit = 25.0f; - } - - var limit = animLimit * staticData.TableScaleZ; - - if (data.HitEvent) { - data.DoAnimation = true; - data.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. - movementData.HeightOffset = 0.0f; - data.MoveDown = true; - } - if (data.UnHitEvent) { - data.DoAnimation = true; - data.UnHitEvent = false; - data.HitEvent = false; - movementData.HeightOffset = limit; - data.MoveDown = false; - } - - if (data.DoAnimation) { - var step = diffTimeMsec * staticData.AnimSpeed * staticData.TableScaleZ; - if (data.MoveDown) { - step = -step; - } - movementData.HeightOffset += step; - - if (data.MoveDown) { - if (movementData.HeightOffset <= -limit) { - movementData.HeightOffset = -limit; - data.DoAnimation = false; - data.MoveDown = false; - } - - } else { - if (movementData.HeightOffset >= 0.0f) { - movementData.HeightOffset = 0.0f; - data.DoAnimation = false; - data.MoveDown = true; - } - } - } - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs index 10370b4aa..4e053be94 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerApi.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using Unity.Entities; using UnityEngine; using VisualPinball.Engine.VPT.Trigger; @@ -45,8 +44,7 @@ public class TriggerApi : CollidableApi public event EventHandler Switch; - internal TriggerApi(GameObject go, Entity entity, Player player) - : base(go, entity, player) + internal TriggerApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -65,13 +63,11 @@ internal TriggerApi(GameObject go, Entity entity, Player player) protected override bool FireHitEvents => true; - protected override void CreateColliders(List colliders, float margin) + protected override void CreateColliders(ref ColliderReference colliders, float margin) { var meshComponent = GameObject.GetComponent(); - if (meshComponent) { - var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent); - colliderGenerator.GenerateColliders(colliders); - } + var colliderGenerator = new TriggerColliderGenerator(this, MainComponent, ColliderComponent, meshComponent); + colliderGenerator.GenerateColliders(ref colliders); } #endregion @@ -88,16 +84,16 @@ void IApi.OnDestroy() { } - void IApiHittable.OnHit(Entity ballEntity, bool isUnHit) + void IApiHittable.OnHit(int ballId, bool isUnHit) { if (isUnHit) { - UnHit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(false, ballEntity)); + UnHit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(false, ballId)); OnSwitch(false); } else { - Hit?.Invoke(this, new HitEventArgs(ballEntity)); - Switch?.Invoke(this, new SwitchEventArgs(true, ballEntity)); + Hit?.Invoke(this, new HitEventArgs(ballId)); + Switch?.Invoke(this, new SwitchEventArgs(true, ballId)); OnSwitch(true); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs index e71e85920..470e14c63 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerCollider.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . using Unity.Collections; -using Unity.Entities; +using UnityEngine; using VisualPinball.Engine.Common; using VisualPinball.Engine.Game; @@ -23,24 +23,24 @@ namespace VisualPinball.Unity { internal static class TriggerCollider { - public static void Collide(ref BallData ball, ref NativeQueue.ParallelWriter events, - ref CollisionEventData collEvent, ref DynamicBuffer insideOfs, - ref TriggerAnimationData animationData, in Entity ballEntity, in Collider coll) + public static void Collide(ref BallState ball, ref NativeQueue.ParallelWriter events, + ref CollisionEventData collEvent, ref InsideOfs insideOfs, + ref TriggerAnimationState animation, in ColliderHeader collHeader) { - var insideOf = BallData.IsInsideOf(in insideOfs, coll.Entity); + var insideOf = insideOfs.IsInsideOf(collHeader.ItemId, ball.Id); if (collEvent.HitFlag == insideOf) { // Hit == NotAlreadyHit ball.Position += PhysicsConstants.StaticTime * ball.Velocity; // move ball slightly forward if (!insideOf) { - BallData.SetInsideOf(ref insideOfs, coll.Entity); - animationData.HitEvent = true; + insideOfs.SetInsideOf(collHeader.ItemId, ball.Id); + animation.HitEvent = true; - events.Enqueue(new EventData(EventId.HitEventsHit, coll.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.HitEventsHit, collHeader.ItemId, ball.Id, true)); } else { - BallData.SetOutsideOf(ref insideOfs, coll.Entity); - animationData.UnHitEvent = true; + insideOfs.SetOutsideOf(collHeader.ItemId, ball.Id); + animation.UnHitEvent = true; - events.Enqueue(new EventData(EventId.HitEventsUnhit, coll.Entity, ballEntity, true)); + events.Enqueue(new EventData(EventId.HitEventsUnhit, collHeader.ItemId, ball.Id, true)); } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs index ab6f283de..1d78689b8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderComponent.cs @@ -17,7 +17,7 @@ // ReSharper disable InconsistentNaming using System; -using Unity.Entities; +using Unity.Mathematics; using UnityEngine; using VisualPinball.Engine.VPT.Trigger; @@ -36,10 +36,22 @@ public class TriggerColliderComponent : ColliderComponent GetPhysicsMaterialData(); - protected override IApiColliderGenerator InstantiateColliderApi(Player player, Entity entity) - => new TriggerApi(gameObject, entity, player); + protected override IApiColliderGenerator InstantiateColliderApi(Player player, PhysicsEngine physicsEngine) + => MainComponent.TriggerApi ?? new TriggerApi(gameObject, player, physicsEngine); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs index 867b1ef80..2bda3eeb8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerColliderGenerator.cs @@ -29,7 +29,7 @@ public class TriggerColliderGenerator private readonly TriggerMeshComponent _meshComponent; private readonly TriggerColliderComponent _colliderComponent; - private bool IsRound => _meshComponent.Shape == TriggerShape.TriggerStar || _meshComponent.Shape == TriggerShape.TriggerButton; + private bool IsRound => _meshComponent && _meshComponent.Shape is TriggerShape.TriggerStar or TriggerShape.TriggerButton; public TriggerColliderGenerator(TriggerApi triggerApi, TriggerComponent component, TriggerColliderComponent colliderComponent, TriggerMeshComponent meshComponent) { @@ -39,24 +39,24 @@ public TriggerColliderGenerator(TriggerApi triggerApi, TriggerComponent componen _colliderComponent = colliderComponent; } - internal void GenerateColliders(List colliders) + internal void GenerateColliders(ref ColliderReference colliders) { if (IsRound) { - GenerateRoundHitObjects(colliders); + GenerateRoundHitObjects(ref colliders); } else { - GenerateCurvedHitObjects(colliders); + GenerateCurvedHitObjects(ref colliders); } } - private void GenerateRoundHitObjects(ICollection colliders) + 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)); } - private void GenerateCurvedHitObjects(ICollection colliders) + private void GenerateCurvedHitObjects(ref ColliderReference colliders) { var height = _component.PositionZ; var vVertex = DragPoint.GetRgVertex(_component.DragPoints); @@ -69,16 +69,16 @@ private void GenerateCurvedHitObjects(ICollection 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(), colliders); + ColliderUtils.Generate3DPolyColliders(rgv3D, _api.GetColliderInfo(), ref 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, colliders); + AddLineSeg(pv2.ToUnityFloat2(), pv3.ToUnityFloat2(), height, ref colliders); } } - private void AddLineSeg(float2 pv1, float2 pv2, float height, ICollection 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)); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs index b5823ab99..315d34357 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerComponent.cs @@ -24,9 +24,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Unity.Entities; +using Unity.Collections; using Unity.Mathematics; -using UnityEditor; using UnityEngine; using VisualPinball.Engine.Game.Engines; using VisualPinball.Engine.Math; @@ -38,7 +37,7 @@ namespace VisualPinball.Unity { [AddComponentMenu("Visual Pinball/Game Item/Trigger")] public class TriggerComponent : MainRenderableComponent, - ITriggerComponent, IOnSurfaceComponent, IConvertGameObjectToEntity + ITriggerComponent, IOnSurfaceComponent { #region Data @@ -81,6 +80,24 @@ public class TriggerComponent : MainRenderableComponent, #endregion + #region Runtime + + public TriggerApi TriggerApi { get; set; } + + private void Awake() + { + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + TriggerApi = new TriggerApi(gameObject, player, physicsEngine); + + player.Register(TriggerApi, this); + if (GetComponentInChildren()) { + RegisterPhysics(physicsEngine); + } + } + + #endregion + #region Wiring public IEnumerable AvailableSwitches => new[] { @@ -119,28 +136,6 @@ public override void UpdateTransforms() #region Conversion - public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem) - { - Convert(entity, dstManager); - - var collComponent = GetComponentInChildren(); - var animComponent = GetComponentInChildren(); - var meshComponent = GetComponentInChildren(); - if (collComponent && animComponent && meshComponent) { - dstManager.AddComponentData(entity, new TriggerAnimationData()); - dstManager.AddComponentData(entity, new TriggerMovementData()); - dstManager.AddComponentData(entity, new TriggerStaticData { - AnimSpeed = animComponent.AnimSpeed, - Radius = collComponent.HitCircleRadius, - Shape = meshComponent.Shape, - TableScaleZ = 1f - }); - } - - // register - transform.GetComponentInParent().RegisterTrigger(this, entity); - } - public override IEnumerable SetData(TriggerData data) { var updatedComponents = new List { this }; @@ -256,6 +251,50 @@ public override void CopyFromObject(GameObject go) #endregion + #region State + + internal TriggerState CreateState() + { + var collComponent = GetComponentInChildren(); + var animComponent = GetComponentInChildren(); + var meshComponent = GetComponentInChildren(); + + if (collComponent.ForFlipper == null) { + return new TriggerState( + gameObject.GetInstanceID(), + animComponent ? animComponent.gameObject.GetInstanceID() : 0, + new TriggerStaticState { + AnimSpeed = animComponent.AnimSpeed, + Radius = collComponent.HitCircleRadius, + Shape = meshComponent.Shape, + TableScaleZ = 1f + }, + new TriggerMovementState(), + new TriggerAnimationState() + ); + } + + return new TriggerState( + gameObject.GetInstanceID(), + new TriggerStaticState { + AnimSpeed = 0, + Radius = collComponent.HitCircleRadius, + Shape = TriggerShape.TriggerNone, + TableScaleZ = 1f + }, + new FlipperCorrectionState( + true, + collComponent.ForFlipper.gameObject.GetInstanceID(), + collComponent.TimeThresholdMs, + collComponent.FlipperPolarities, + collComponent.FlipperVelocities, + Allocator.Persistent + ) + ); + } + + #endregion + #region Editor Tooling public override ItemDataTransformType EditorPositionType => ItemDataTransformType.TwoD; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementState.cs similarity index 88% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementState.cs index 9465563ed..67680787a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct TriggerMovementData : IComponentData + internal struct TriggerMovementState { public float HeightOffset; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementSystem.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementSystem.cs deleted file mode 100644 index 2f5ce005d..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementSystem.cs +++ /dev/null @@ -1,60 +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 Unity.Entities; -using Unity.Profiling; -using UnityEngine; - -namespace VisualPinball.Unity -{ - [UpdateInGroup(typeof(TransformMeshesSystemGroup))] - internal class TriggerMovementSystem : SystemBase - { - private static readonly ProfilerMarker PerfMarker = new ProfilerMarker("TriggerMovementSystem"); - - private Player _player; - private readonly Dictionary _initialOffset = new(); - - protected override void OnStartRunning() - { - base.OnStartRunning(); - _player = Object.FindObjectOfType(); - } - - protected override void OnUpdate() - { - var marker = PerfMarker; - - Entities.WithoutBurst().WithName("TriggerMovementJob").ForEach((Entity entity, in TriggerMovementData data) => { - - marker.Begin(); - - var transform = _player.TriggerTransforms[entity]; - if (!_initialOffset.ContainsKey(entity)) { - _initialOffset[entity] = transform.position.y; - } - - var worldPos = transform.position; - worldPos.y = _initialOffset[entity] + Physics.ScaleToWorld(data.HeightOffset); - transform.position = worldPos; - - marker.End(); - - }).Run(); - } - } -} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs new file mode 100644 index 000000000..ef9acff93 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs @@ -0,0 +1,61 @@ +// 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; + +namespace VisualPinball.Unity +{ + internal struct TriggerState : IDisposable + { + internal readonly int ItemId; + internal readonly int AnimatedItemId; + internal TriggerStaticState Static; + internal TriggerMovementState Movement; + internal TriggerAnimationState Animation; + internal FlipperCorrectionState FlipperCorrection; + + /// + /// Default trigger usage. + /// + public TriggerState(int itemId, int animatedItemId, TriggerStaticState @static, TriggerMovementState movement, TriggerAnimationState animation) + { + ItemId = itemId; + AnimatedItemId = animatedItemId; + Static = @static; + Movement = movement; + Animation = animation; + FlipperCorrection = default; + } + + /// + /// Flipper correction usage. + /// + public TriggerState(int itemId, TriggerStaticState @static, FlipperCorrectionState flipperCorrection) + { + ItemId = itemId; + AnimatedItemId = 0; + Static = @static; + Movement = default; + Animation = default; + FlipperCorrection = flipperCorrection; + } + + public void Dispose() + { + FlipperCorrection.Dispose(); + } + } +} diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs.meta new file mode 100644 index 000000000..e79ca9be8 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerState.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 97a2cfead66f4bda83aa41e7848ba7aa +timeCreated: 1696503613 \ No newline at end of file diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticData.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs similarity index 89% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticData.cs rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs index 21ae12150..ecf610673 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticData.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs @@ -14,11 +14,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -using Unity.Entities; - namespace VisualPinball.Unity { - internal struct TriggerStaticData : IComponentData + internal struct TriggerStaticState { public int Shape; public float Radius; diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticData.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticData.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerStaticState.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs new file mode 100644 index 000000000..62f22ee02 --- /dev/null +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs @@ -0,0 +1,35 @@ +// 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/TriggerMovementSystem.cs.meta b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerMovementSystem.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity/VPT/Trigger/TriggerTransform.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs index ea4ad9d3c..8309bf9bf 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughApi.cs @@ -165,7 +165,7 @@ public class TroughApi : ItemApi, /// public event EventHandler Init; - internal TroughApi(GameObject go, Player player) : base(go, player) + internal TroughApi(GameObject go, Player player, PhysicsEngine physicsEngine) : base(go, player, physicsEngine) { } @@ -291,7 +291,7 @@ private void OnEntry(object sender, SwitchEventArgs args) if (_drainSwitch is KickerApi kickerApi) { kickerApi.DestroyBall(); } else { - BallManager.DestroyEntity(args.BallEntity); + BallManager.DestroyBall(args.BallId); } DrainBall(); @@ -479,11 +479,11 @@ public bool EjectBall() if (_ejectKicker == null) { - Logger.Error("Trough: Cannot spawn ball without an exit kicker."); + Logger.Warn("Trough: Cannot spawn ball without an exit kicker."); return false; } Logger.Info("Trough: Spawning new ball."); - _ejectKicker.CreateBall(); + _ejectKicker.CreateBall(MainComponent.Ball); _ejectCoil.OnCoil(true); // open the switch of the ejected ball immediately diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs index 5df1c3520..8dff2ae11 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Trough/TroughComponent.cs @@ -61,6 +61,9 @@ public ITriggerComponent PlayfieldEntrySwitch public KickerComponent PlayfieldExitKicker; public string PlayfieldExitKickerItem = string.Empty; + [Tooltip("The prefab that will instantiated when ejecting a new ball.")] + public GameObject Ball; + [Range(1, 10)] [Tooltip("How many balls the trough holds when the game starts.")] public int BallCount = 6; @@ -300,9 +303,14 @@ public IEnumerable AvailableCoils { #region Runtime + public TroughApi TroughApi { get; private set; } + private void Awake() { - GetComponentInParent().RegisterTrough(this); + var player = GetComponentInParent(); + var physicsEngine = GetComponentInParent(); + TroughApi = new TroughApi(gameObject, player, physicsEngine); + player.Register(TroughApi, this); } #endregion diff --git a/VisualPinball.Unity/VisualPinball.Unity/VisualPinball.Unity.asmdef b/VisualPinball.Unity/VisualPinball.Unity/VisualPinball.Unity.asmdef index 421081b06..bcbe3a323 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VisualPinball.Unity.asmdef +++ b/VisualPinball.Unity/VisualPinball.Unity/VisualPinball.Unity.asmdef @@ -10,7 +10,8 @@ "Unity.Mathematics.Extensions", "Unity.Transforms", "Unity.InputSystem", - "VisualPinball.Engine" + "VisualPinball.Engine", + "com.bartofzo.nativetrees" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/package.json b/package.json index 4ea332a99..e198c011e 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,8 @@ "unity": "2021.3", "unityRelease": "0f1", "dependencies": { - "com.unity.burst": "1.6.5", - "com.unity.entities": "0.17.0-preview.42", + "com.bartofzo.nativetrees": "0.1.6", "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.3.0", "com.unity.ugui": "1.0.0", "com.unity.test-framework": "1.1.31"