diff --git a/VisualPinball.Engine/VPT/Ramp/Ramp.cs b/VisualPinball.Engine/VPT/Ramp/Ramp.cs index 1b81aec68..398fa14c4 100644 --- a/VisualPinball.Engine/VPT/Ramp/Ramp.cs +++ b/VisualPinball.Engine/VPT/Ramp/Ramp.cs @@ -28,7 +28,7 @@ public class Ramp : Item, IRenderable public Ramp(RampData data) : base(data) { - MeshGenerator = new RampMeshGenerator(Data); + MeshGenerator = new RampMeshGenerator(Data, Vertex3D.Zero); } public Ramp(BinaryReader reader, string itemName) : this(new RampData(reader, itemName)) diff --git a/VisualPinball.Engine/VPT/Ramp/RampMeshGenerator.cs b/VisualPinball.Engine/VPT/Ramp/RampMeshGenerator.cs index 9ed63b476..7d9291a87 100644 --- a/VisualPinball.Engine/VPT/Ramp/RampMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Ramp/RampMeshGenerator.cs @@ -35,10 +35,12 @@ public class RampMeshGenerator public const string Wires = "Wires"; private readonly IRampData _data; + private readonly Vertex3D _position; - public RampMeshGenerator(IRampData data) + public RampMeshGenerator(IRampData data, Vertex3D position) { _data = data; + _position = position; } public Mesh GetMesh(float tableWidth, float tableHeight, float heightZ, string id) @@ -193,10 +195,10 @@ private Mesh GenerateFlatFloorMesh(float tableWidth, float tableHeight, RampVert //if (_data.Image != null) { if (_data.ImageAlignment == RampImageAlignment.ImageModeWorld) { - rgv3d1.Tu = rgv3d1.X * invTableWidth; - rgv3d1.Tv = rgv3d1.Y * invTableHeight; - rgv3d2.Tu = rgv3d2.X * invTableWidth; - rgv3d2.Tv = rgv3d2.Y * invTableHeight; + rgv3d1.Tu = (_position.X + rgv3d1.X) * invTableWidth; + rgv3d1.Tv = (_position.Y + rgv3d1.Y) * invTableHeight; + rgv3d2.Tu = (_position.X + rgv3d2.X) * invTableWidth; + rgv3d2.Tv = (_position.Y + rgv3d2.Y) * invTableHeight; } else { rgv3d1.Tu = 1.0f; @@ -255,8 +257,8 @@ private Mesh GenerateFlatLeftWall(float tableWidth, float tableHeight, RampVerte rgv3d2.Z = (rv.PointHeights[i] + _data.LeftWallHeightVisible); if (_data.ImageAlignment == RampImageAlignment.ImageModeWorld) { - rgv3d1.Tu = rgv3d1.X * invTableWidth; - rgv3d1.Tv = rgv3d1.Y * invTableHeight; + rgv3d1.Tu = (_position.X + rgv3d1.X) * invTableWidth; + rgv3d1.Tv = (_position.Y + rgv3d1.Y) * invTableHeight; } else { rgv3d1.Tu = 0; @@ -309,8 +311,8 @@ private Mesh GenerateFlatRightWall(float tableWidth, float tableHeight, RampVert rgv3d2.Z = (rv.PointHeights[i] + _data.RightWallHeightVisible); if (_data.ImageAlignment == RampImageAlignment.ImageModeWorld) { - rgv3d1.Tu = rgv3d1.X * invTableWidth; - rgv3d1.Tv = rgv3d1.Y * invTableHeight; + rgv3d1.Tu = (_position.X + rgv3d1.X) * invTableWidth; + rgv3d1.Tv = (_position.Y + rgv3d1.Y) * invTableHeight; } else { rgv3d1.Tu = 0; diff --git a/VisualPinball.Engine/VPT/Surface/Surface.cs b/VisualPinball.Engine/VPT/Surface/Surface.cs index 6b710d27d..736af38e7 100644 --- a/VisualPinball.Engine/VPT/Surface/Surface.cs +++ b/VisualPinball.Engine/VPT/Surface/Surface.cs @@ -28,7 +28,7 @@ public class Surface : Item, IRenderable public Surface(SurfaceData data) : base(data) { - _meshGenerator = new SurfaceMeshGenerator(Data); + _meshGenerator = new SurfaceMeshGenerator(Data, Vertex3D.Zero); } public Surface(BinaryReader reader, string itemName) : this(new SurfaceData(reader, itemName)) diff --git a/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs b/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs index 86ddda0bf..d03619934 100644 --- a/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs +++ b/VisualPinball.Engine/VPT/Surface/SurfaceMeshGenerator.cs @@ -16,7 +16,6 @@ using System; using System.Collections.Generic; -using VisualPinball.Engine.Game; using VisualPinball.Engine.Math; using MathF = VisualPinball.Engine.Math.MathF; @@ -28,10 +27,12 @@ public class SurfaceMeshGenerator public const string Top = "Top"; private readonly ISurfaceData _data; + private readonly Vertex3D _position; - public SurfaceMeshGenerator(ISurfaceData data) + public SurfaceMeshGenerator(ISurfaceData data, Vertex3D position) { _data = data; + _position = position; } public Mesh GetMesh(string id, float tableWidth, float tableHeight, float zHeight, bool asRightHanded) @@ -123,8 +124,8 @@ private Mesh GenerateTopMesh(float tableWidth, float tableHeight, float zHeight) X = pv0.X, Y = pv0.Y, Z = heightNotDropped + zHeight, - Tu = pv0.X * invTableWidth, - Tv = pv0.Y * invTableHeight, + Tu = (_position.X + pv0.X) * invTableWidth, + Tv = (_position.Y + pv0.Y) * invTableHeight, Nx = 0, Ny = 0, Nz = 1.0f @@ -134,8 +135,8 @@ private Mesh GenerateTopMesh(float tableWidth, float tableHeight, float zHeight) X = pv0.X, Y = pv0.Y, Z = (float) heightDropped, - Tu = pv0.X * invTableWidth, - Tv = pv0.Y * invTableHeight, + Tu = (_position.X + pv0.X) * invTableWidth, + Tv = (_position.Y + pv0.Y) * invTableHeight, Nx = 0, Ny = 0, Nz = 1.0f @@ -145,8 +146,8 @@ private Mesh GenerateTopMesh(float tableWidth, float tableHeight, float zHeight) X = pv0.X, Y = pv0.Y, Z = _data.HeightBottom, - Tu = pv0.X * invTableWidth, - Tv = pv0.Y * invTableHeight, + Tu = (_position.X + pv0.X) * invTableWidth, + Tv = (_position.Y + pv0.Y) * invTableHeight, Nx = 0, Ny = 0, Nz = -1.0f diff --git a/VisualPinball.Engine/VPT/Table/TableLoader.cs b/VisualPinball.Engine/VPT/Table/TableLoader.cs index bacf2354a..5fd55964c 100644 --- a/VisualPinball.Engine/VPT/Table/TableLoader.cs +++ b/VisualPinball.Engine/VPT/Table/TableLoader.cs @@ -43,12 +43,12 @@ public static FileTableContainer Load(string filename, bool loadGameItems = true using (var reader = new BinaryReader(stream)) { var tableContainer = new FileTableContainer(reader); - LoadTableInfo(tableContainer, cf.RootStorage, gameStorage); + var tableInfoStorage = LoadTableInfo(tableContainer, cf.RootStorage, gameStorage); if (loadGameItems) { LoadGameItems(tableContainer, gameStorage, tableContainer.NumGameItems, "GameItem"); LoadGameItems(tableContainer, gameStorage, tableContainer.NumVpeGameItems, "VpeGameItem"); } - LoadTextures(tableContainer, gameStorage); + LoadTextures(tableContainer, gameStorage, tableInfoStorage); LoadSounds(tableContainer, gameStorage, fileVersion); LoadCollections(tableContainer, gameStorage); LoadTableMeta(tableContainer, gameStorage); @@ -256,7 +256,7 @@ private static void LoadGameItems(FileTableContainer tableContainer, CFStorage s } } - private static void LoadTextures(FileTableContainer tableContainer, CFStorage storage) + private static void LoadTextures(FileTableContainer tableContainer, CFStorage storage, CFStorage tableInfoStorage) { for (var i = 0; i < tableContainer.NumTextures; i++) { var textureName = $"Image{i}"; @@ -273,7 +273,7 @@ private static void LoadTextures(FileTableContainer tableContainer, CFStorage st using (var stream = new MemoryStream(textureData)) using (var reader = new BinaryReader(stream)) { - var texture = new Texture(reader, textureName); + var texture = new Texture(reader, textureName, tableInfoStorage); tableContainer.AddTexture(texture); } } @@ -313,7 +313,7 @@ private static void LoadSounds(FileTableContainer tableContainer, CFStorage stor } } - private static void LoadTableInfo(FileTableContainer tableContainer, CFStorage rootStorage, CFStorage gameStorage) + private static CFStorage LoadTableInfo(FileTableContainer tableContainer, CFStorage rootStorage, CFStorage gameStorage) { // first, although we can loop through entries, get them from the game storage, so we // know their order, which is important when writing back (because you know, hashing). @@ -329,9 +329,12 @@ private static void LoadTableInfo(FileTableContainer tableContainer, CFStorage r rootStorage.TryGetStorage("TableInfo", out var tableInfoStorage); if (tableInfoStorage == null) { Logger.Info("TableInfo storage not found, skipping."); - return; + return null; } tableInfoStorage.VisitEntries(item => { + if (item.Name == "Screenshot") { // skip those + return; + } if (item.IsStream) { var itemStream = item as CFStream; if (itemStream != null) { @@ -339,6 +342,8 @@ private static void LoadTableInfo(FileTableContainer tableContainer, CFStorage r } } }, false); + + return tableInfoStorage; } private static void LoadTableMeta(FileTableContainer tableContainer, CFStorage gameStorage) diff --git a/VisualPinball.Engine/VPT/Texture.cs b/VisualPinball.Engine/VPT/Texture.cs index 8a245230a..96c2abe2b 100644 --- a/VisualPinball.Engine/VPT/Texture.cs +++ b/VisualPinball.Engine/VPT/Texture.cs @@ -19,6 +19,7 @@ using System.Text; using NetVips; using NLog; +using OpenMcdf; using VisualPinball.Engine.Resources; namespace VisualPinball.Engine.VPT @@ -65,15 +66,20 @@ public string FileExtension { /// contain the header. /// /// - public byte[] Content => ImageData.Bytes; + public byte[] Content => ImageData?.Bytes ?? _screenshot; /// /// Data as it would written to an image file (incl headers). /// - public byte[] FileContent => ImageData.FileContent; + public byte[] FileContent => ImageData?.FileContent ?? _screenshot; private IImageData ImageData => Data.Binary as IImageData ?? Data.Bitmap; + /// + /// VPX can store texture data as screenshot in the TableInfo storage, this is it if it's the case. + /// + private byte[] _screenshot; + public bool HasTransparentFormat => Data.HasBitmap || Data.Path != null && Data.Path.ToLower().EndsWith(".png"); public bool UsageNormalMap; @@ -85,11 +91,25 @@ public Texture(string name) : base(new TextureData(name)) Name = name; } - public Texture(TextureData data) : base(data) { } + public Texture(TextureData data, CFStorage tableInfoStorage) : base(data) + { + if (data.Binary == null && data.Bitmap == null) { + if (data.LinkId == 1) { + if (tableInfoStorage == null) { + Logger.Warn($"Texture {Name} has no binary data and no storage to load from."); + return; + } + // load screenshot + _screenshot = tableInfoStorage.GetStream("Screenshot")?.GetData(); + } else { + Logger.Warn($"Could not load texture {Name} from storage. No binaries and link is {data.LinkId}."); + } + } + } - public Texture(BinaryReader reader, string itemName) : this(new TextureData(reader, itemName)) { } + public Texture(BinaryReader reader, string itemName, CFStorage tableInfoStorage) : this(new TextureData(reader, itemName), tableInfoStorage) { } - private Texture(Resource res) : this(new TextureData(res)) { } + private Texture(Resource res) : this(new TextureData(res), null) { } public void Analyze() { @@ -154,11 +174,11 @@ private TextureStats AnalyzeAlpha() public Image GetImage() { try { - var data = Data.Binary != null ? Data.Binary.Data : Data.Bitmap.Bytes; + var data = Data.Binary != null ? Data.Binary.Data : Data.Bitmap != null ? Data.Bitmap.Bytes : _screenshot; if (data.Length == 0) { throw new InvalidDataException("Image data is empty."); } - return Data.Binary != null + return Data.Binary != null || _screenshot != null ? Image.NewFromBuffer(data) : Image.NewFromMemory(data, Width, Height, 4, Enums.BandFormat.Uchar); diff --git a/VisualPinball.Engine/VPT/TextureData.cs b/VisualPinball.Engine/VPT/TextureData.cs index b1dfcfa1f..f16ce1f20 100644 --- a/VisualPinball.Engine/VPT/TextureData.cs +++ b/VisualPinball.Engine/VPT/TextureData.cs @@ -18,6 +18,7 @@ // ReSharper disable UnassignedField.Global // ReSharper disable StringLiteralTypo // ReSharper disable FieldCanBeMadeReadOnly.Global +// ReSharper disable InconsistentNaming #endregion using System; @@ -61,6 +62,9 @@ public class TextureData : ItemData [BiffBits("BITS", Pos = 6)] public Bitmap Bitmap; // originally "PdsBuffer"; + [BiffInt("LINK", Pos = 6)] // vpx doesn't seem to support anything else than 1, which is "screenshot", found at TableInfo/Screenshot + public int LinkId; + public TextureData(string name) : base(StoragePrefix.Image) { Name = name; diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/MenuImporter.cs similarity index 62% rename from VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs rename to VisualPinball.Unity/VisualPinball.Unity.Editor/Import/MenuImporter.cs index 90bc8e16d..897d9f85f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/MenuImporter.cs @@ -25,26 +25,40 @@ namespace VisualPinball.Unity.Editor { - public static class VpxMenuImporter + public static class MenuImporter { - [MenuItem("Pinball/Import .vpx File", false, 2)] - public static void ImportVpxIntoScene(MenuCommand menuCommand) + [MenuItem("Pinball/Import Table", false, 1)] + public static async void ImportVpeIntoScene(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.")) { + if (!EnsureUntitledSceneHasBeenSaved()) { return; } // open file dialog - var vpxPath = EditorUtility.OpenFilePanelWithFilters("Import .vpx File", null, new[] { "Visual Pinball Table Files", "vpx" }); - if (vpxPath.Length == 0) { + var path = EditorUtility.OpenFilePanelWithFilters("Import", null, new[] { "Pinball Tables", "vpe,vpx" }); + if (path.Length == 0) { return; } - VpxImportEngine.ImportIntoScene(vpxPath, tableName: Path.GetFileNameWithoutExtension(vpxPath)); + switch (Path.GetExtension(path).ToLower()) { + + case ".vpx": { + VpxImportEngine.ImportIntoScene(path, tableName: Path.GetFileNameWithoutExtension(path)); + break; + } + case ".vpe": { + var importer = new PackageReader(path); + await importer.ImportIntoScene(Path.GetFileNameWithoutExtension(path)); + break; + } + default: + EditorUtility.DisplayDialog("Import", "Unsupported file format. Only .vpe and .vpx files are currently supported.", "OK"); + break; + } } - private static bool EnsureUntitledSceneHasBeenSaved(string message) + private static bool EnsureUntitledSceneHasBeenSaved() { if (string.IsNullOrEmpty(SceneManager.GetActiveScene().path)) { diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpeMenuImporter.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/MenuImporter.cs.meta similarity index 100% rename from VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpeMenuImporter.cs.meta rename to VisualPinball.Unity/VisualPinball.Unity.Editor/Import/MenuImporter.cs.meta diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpeMenuImporter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpeMenuImporter.cs deleted file mode 100644 index 26fd3c0fa..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpeMenuImporter.cs +++ /dev/null @@ -1,68 +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 ConvertIfStatementToReturnStatement -// ReSharper disable UnusedType.Global -// ReSharper disable UnusedMember.Global - -using System.IO; -using UnityEditor; -using UnityEditor.SceneManagement; -using UnityEngine.SceneManagement; - -namespace VisualPinball.Unity.Editor -{ - public static class VpeMenuImporter - { - [MenuItem("Pinball/Import .vpe File", false, 1)] - public static async void ImportVpeIntoScene(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 vpePath = EditorUtility.OpenFilePanelWithFilters("Import .vpe File", null, new[] { "VPE Table Files", "vpe" }); - if (vpePath.Length == 0) { - return; - } - - var importer = new PackageReader(vpePath); - await importer.ImportIntoScene(Path.GetFileNameWithoutExtension(vpePath)); - } - - private static bool EnsureUntitledSceneHasBeenSaved(string message) - { - if (string.IsNullOrEmpty(SceneManager.GetActiveScene().path)) { - - if (!EditorUtility.DisplayDialog("Info", "Before importing a VPE file, 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/VpxMenuImporter.cs.meta b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs.meta deleted file mode 100644 index f7cc66390..000000000 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxMenuImporter.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6d0d6da4d00efd3488247cc3283ba0f2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs index 87f41ed4e..5705c4aa1 100644 --- a/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs +++ b/VisualPinball.Unity/VisualPinball.Unity.Editor/Import/VpxSceneConverter.cs @@ -170,7 +170,7 @@ public GameObject Convert(bool applyPatch = true, string tableName = null) _patcher?.PostPatch(_tableGo); } - return MakeSubScene(); + return _tableGo; } private void SaveData() { @@ -601,24 +601,6 @@ private void CreateRootHierarchy(string tableName = null) _playfieldGo.AddComponent(); _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) { diff --git a/VisualPinball.Unity/VisualPinball.Unity/Import/Job/TableLoader.cs b/VisualPinball.Unity/VisualPinball.Unity/Import/Job/TableLoader.cs index 16c102250..477367d01 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/Import/Job/TableLoader.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/Import/Job/TableLoader.cs @@ -68,96 +68,94 @@ public static FileTableContainer LoadTable(string path) // update table with results for (var i = 0; i < tableContainer.Table.Data.NumGameItems; i++) { - if (job.ItemObj[i].ToInt32() > 0) { - var objHandle = (GCHandle) job.ItemObj[i]; - switch ((ItemType)job.ItemType[i]) { - case ItemType.Bumper: { - tableContainer.Add(objHandle.Target as Bumper); - break; - } - case ItemType.Decal: { - tableContainer.Add(objHandle.Target as Decal); - break; - } - case ItemType.DispReel: { - tableContainer.Add(objHandle.Target as DispReel); - break; - } - case ItemType.Flasher: { - tableContainer.Add(objHandle.Target as Flasher); - break; - } - case ItemType.Flipper: { - tableContainer.Add(objHandle.Target as Flipper); - break; - } - case ItemType.Gate: { - tableContainer.Add(objHandle.Target as Gate); - break; - } - case ItemType.HitTarget: { - tableContainer.Add(objHandle.Target as HitTarget); - break; - } - case ItemType.Kicker: { - tableContainer.Add(objHandle.Target as Kicker); - break; - } - case ItemType.Light: { - tableContainer.Add(objHandle.Target as Light); - break; - } - case ItemType.LightSeq: { - tableContainer.Add(objHandle.Target as LightSeq); - break; - } - case ItemType.Plunger: { - tableContainer.Add(objHandle.Target as Plunger); - break; - } - case ItemType.Primitive: { - tableContainer.Add(objHandle.Target as Primitive); - break; - } - case ItemType.Ramp: { - tableContainer.Add(objHandle.Target as Ramp); - break; - } - case ItemType.Rubber: { - tableContainer.Add(objHandle.Target as Rubber); - break; - } - case ItemType.Spinner: { - tableContainer.Add(objHandle.Target as Spinner); - break; - } - case ItemType.Surface: { - tableContainer.Add(objHandle.Target as Surface); - break; - } - case ItemType.TextBox: { - tableContainer.Add(objHandle.Target as TextBox); - break; - } - case ItemType.Timer: { - tableContainer.Add(objHandle.Target as Timer); - break; - } - case ItemType.Trigger: { - tableContainer.Add(objHandle.Target as Trigger); - break; - } - case ItemType.Trough: { - tableContainer.Add(objHandle.Target as Trough); - break; - } - case ItemType.MetalWireGuide:{ - tableContainer.Add(objHandle.Target as MetalWireGuide); - break; - } - default: - throw new ArgumentException("Unknown item type " + (ItemType)job.ItemType[i] + "."); + var objHandle = (GCHandle) job.ItemObj[i]; + switch ((ItemType)job.ItemType[i]) { + case ItemType.Bumper: { + tableContainer.Add(objHandle.Target as Bumper); + break; } + case ItemType.Decal: { + tableContainer.Add(objHandle.Target as Decal); + break; + } + case ItemType.DispReel: { + tableContainer.Add(objHandle.Target as DispReel); + break; + } + case ItemType.Flasher: { + tableContainer.Add(objHandle.Target as Flasher); + break; + } + case ItemType.Flipper: { + tableContainer.Add(objHandle.Target as Flipper); + break; + } + case ItemType.Gate: { + tableContainer.Add(objHandle.Target as Gate); + break; + } + case ItemType.HitTarget: { + tableContainer.Add(objHandle.Target as HitTarget); + break; + } + case ItemType.Kicker: { + tableContainer.Add(objHandle.Target as Kicker); + break; + } + case ItemType.Light: { + tableContainer.Add(objHandle.Target as Light); + break; + } + case ItemType.LightSeq: { + tableContainer.Add(objHandle.Target as LightSeq); + break; + } + case ItemType.Plunger: { + tableContainer.Add(objHandle.Target as Plunger); + break; + } + case ItemType.Primitive: { + tableContainer.Add(objHandle.Target as Primitive); + break; + } + case ItemType.Ramp: { + tableContainer.Add(objHandle.Target as Ramp); + break; + } + case ItemType.Rubber: { + tableContainer.Add(objHandle.Target as Rubber); + break; + } + case ItemType.Spinner: { + tableContainer.Add(objHandle.Target as Spinner); + break; + } + case ItemType.Surface: { + tableContainer.Add(objHandle.Target as Surface); + break; + } + case ItemType.TextBox: { + tableContainer.Add(objHandle.Target as TextBox); + break; + } + case ItemType.Timer: { + tableContainer.Add(objHandle.Target as Timer); + break; + } + case ItemType.Trigger: { + tableContainer.Add(objHandle.Target as Trigger); + break; + } + case ItemType.Trough: { + tableContainer.Add(objHandle.Target as Trough); + break; + } + case ItemType.MetalWireGuide:{ + tableContainer.Add(objHandle.Target as MetalWireGuide); + break; + } + default: + throw new ArgumentException("Unknown item type " + (ItemType)job.ItemType[i] + "."); } } job.Dispose(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs index 961150f40..226a97856 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/ItemComponent.cs @@ -54,14 +54,16 @@ protected static void DrawArrow(Vector3 pos, Vector3 direction, float arrowHeadL protected void SetEnabled(bool value) where T : Object { - var comp = GetComponent(); - switch (comp) { - case Behaviour behaviourComp: - behaviourComp.enabled = value; - break; - case Renderer rendererComp: - rendererComp.enabled = value; - break; + var comp = GetComponentsInChildren(); + foreach (var c in comp) { + switch (c) { + case Behaviour behaviourComp: + behaviourComp.enabled = value; + break; + case Renderer rendererComp: + rendererComp.enabled = value; + break; + } } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs index 9f37a1d68..692d93e78 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Light/LightInsertMeshComponent.cs @@ -43,7 +43,7 @@ public class LightInsertMeshComponent : MeshComponent protected override Mesh GetMesh(LightData data) { var playfieldComponent = GetComponentInParent(); - var meshGen = new SurfaceMeshGenerator(new LightInsertData(_dragPoints, InsertHeight)); + var meshGen = new SurfaceMeshGenerator(new LightInsertData(_dragPoints, InsertHeight), MainComponent.transform.position.TranslateToVpx().ToVertex3D()); var topMesh = meshGen.GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, 0, false); var sideMesh = meshGen.GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, 0, false); return topMesh.Merge(sideMesh).TransformToWorld(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs index 40911efbf..3af86ccf8 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/MainRenderableComponent.cs @@ -17,10 +17,11 @@ using System; using System.Collections.Generic; using System.Linq; -using FluentAssertions.Specialized; +using NLog; using UnityEngine; using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; +using Logger = NLog.Logger; using Mesh = VisualPinball.Engine.VPT.Mesh; namespace VisualPinball.Unity @@ -44,6 +45,8 @@ public abstract class MainRenderableComponent : MainComponent, IMa private PlayfieldComponent _playfield; protected PlayfieldComponent Playfield => _playfield ? _playfield : _playfield = GetComponentInParent(); + private static readonly Logger Logger = LogManager.GetCurrentClassLogger(); + /// /// Returns all child mesh components linked to this data. /// @@ -121,6 +124,10 @@ protected void ParentToSurface(string surfaceName, Vertex2D center, Dictionary(components, surfaceName); + if (surface == null) { + Logger.Error($"Could not find surface {surfaceName} to parent to."); + return; + } transform.SetZPosition(surface.Height(center.ToUnityVector2())); transform.SetParent(surface.transform, true); } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs index b26bc2ef5..8e1aa1cca 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampColliderGenerator.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using Unity.Mathematics; using VisualPinball.Engine.Common; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Ramp; @@ -36,7 +37,7 @@ public RampColliderGenerator(RampApi rampApi, IRampData data, RampColliderCompon _api = rampApi; _data = data; _colliderComponent = colliderComponent; - _meshGenerator = new RampMeshGenerator(data); + _meshGenerator = new RampMeshGenerator(data, Vertex3D.Zero); _matrix = matrix; } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs index 44e8f5229..c9db05c7a 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampComponent.cs @@ -84,6 +84,9 @@ public class RampComponent : MainRenderableComponent, IRampData, ISurf [SerializeField] private DragPointData[] _dragPoints; + [SerializeField] + public Vector3 uvOffset = Vector3.zero; + #endregion #region IRampData @@ -158,7 +161,7 @@ public override void UpdateTransforms() public float Height(Vector2 pos) => Height(pos, transform.localPosition.TranslateToVpx()); public float Height(Vector2 pos, Vector3 diff) { - var vVertex = new RampMeshGenerator(this).GetCentralCurve(); + var vVertex = new RampMeshGenerator(this, uvOffset.ToVertex3D()).GetCentralCurve(); var t = transform.localPosition.TranslateToVpx(); Mesh.ClosestPointOnPolygon(vVertex, new Vertex2D(pos.x - diff.x, pos.y - diff.y), false, out var vOut, out var iSeg); @@ -400,6 +403,10 @@ private void CenterPivot() var centerVpx = DragPoints.Aggregate(Vector3.zero, (current, dragPoint) => current + dragPoint.Center.ToUnityVector3()); centerVpx /= DragPoints.Length; + if (uvOffset == Vector3.zero) { + uvOffset = centerVpx; + } + transform.Translate(centerVpx.TranslateToWorld(transform) - transform.position); foreach (var dragPoint in DragPoints) { dragPoint.Center -= centerVpx.ToVertex3D(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs index 060420ae0..1f6ad58fa 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampFloorMeshComponent.cs @@ -16,6 +16,7 @@ using System; using UnityEngine; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Ramp; using VisualPinball.Engine.VPT.Table; @@ -30,12 +31,12 @@ public class RampFloorMeshComponent : MeshComponent protected override Mesh GetMesh(RampData _) { var playfieldComponent = GetComponentInParent(); - return new RampMeshGenerator(MainComponent) + return new RampMeshGenerator(MainComponent, MainComponent.uvOffset.ToVertex3D()) .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Floor) .TransformToWorld(); } protected override PbrMaterial GetMaterial(RampData data, Table table) - => new RampMeshGenerator(MainComponent).GetMaterial(table, data); + => new RampMeshGenerator(MainComponent, Vertex3D.Zero).GetMaterial(table, data); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampPackable.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampPackable.cs index db4e55de6..21aa8a264 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampPackable.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampPackable.cs @@ -35,6 +35,7 @@ public struct RampPackable public float WireDistanceX; public float WireDistanceY; public IEnumerable DragPoints; + public PackableFloat3 UvOffset; public static byte[] Pack(RampComponent comp) { @@ -50,7 +51,8 @@ public static byte[] Pack(RampComponent comp) WireDiameter = comp.WireDiameter, WireDistanceX = comp.WireDistanceX, WireDistanceY = comp.WireDistanceY, - DragPoints = comp.DragPoints.Select(DragPointPackable.From) + DragPoints = comp.DragPoints.Select(DragPointPackable.From), + UvOffset = comp.uvOffset }); } @@ -69,6 +71,7 @@ public static void Unpack(byte[] bytes, RampComponent comp) comp._wireDistanceX = data.WireDistanceX; comp._wireDistanceY = data.WireDistanceY; comp.DragPoints = data.DragPoints.Select(c => c.ToDragPoint()).ToArray(); + comp.uvOffset = data.UvOffset; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs index 2df14f889..2e8364ddb 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWallMeshComponent.cs @@ -16,6 +16,7 @@ using System; using UnityEngine; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Ramp; using VisualPinball.Engine.VPT.Table; @@ -30,12 +31,12 @@ public class RampWallMeshComponent : MeshComponent protected override Mesh GetMesh(RampData data) { var playfieldComponent = GetComponentInParent(); - return new RampMeshGenerator(MainComponent) + return new RampMeshGenerator(MainComponent, MainComponent.uvOffset.ToVertex3D()) .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Wall) .TransformToWorld(); } protected override PbrMaterial GetMaterial(RampData data, Table table) - => new RampMeshGenerator(MainComponent).GetMaterial(table, data); + => new RampMeshGenerator(MainComponent, Vertex3D.Zero).GetMaterial(table, data); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs index 8198708cd..f7bd57d76 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Ramp/RampWireMeshComponent.cs @@ -16,6 +16,7 @@ using System; using UnityEngine; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Ramp; using VisualPinball.Engine.VPT.Table; @@ -30,12 +31,12 @@ public class RampWireMeshComponent : MeshComponent protected override Mesh GetMesh(RampData data) { var playfieldComponent = GetComponentInParent(); - return new RampMeshGenerator(MainComponent) + return new RampMeshGenerator(MainComponent, MainComponent.uvOffset.ToVertex3D()) .GetMesh(playfieldComponent.Width, playfieldComponent.Height, 0, RampMeshGenerator.Wires) .TransformToWorld(); } protected override PbrMaterial GetMaterial(RampData data, Table table) - => new RampMeshGenerator(MainComponent).GetMaterial(table, data); + => new RampMeshGenerator(MainComponent, Vertex3D.Zero).GetMaterial(table, data); } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs index b9d440fa6..505fbfeb9 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceComponent.cs @@ -49,6 +49,9 @@ public class SurfaceComponent : MainRenderableComponent, ISurfaceCo private DragPointData[] _dragPoints; public DragPointData[] DragPoints { get => _dragPoints; set => _dragPoints = value; } + [SerializeField] + public Vector3 uvOffset = Vector3.zero; + #endregion #region Packaging @@ -235,6 +238,10 @@ private void CenterPivot() var centerVpx = DragPoints.Aggregate(Vector3.zero, (current, dragPoint) => current + dragPoint.Center.ToUnityVector3()); centerVpx /= DragPoints.Length; + if (uvOffset == Vector3.zero) { + uvOffset = centerVpx; + } + transform.Translate(centerVpx.TranslateToWorld(transform) - transform.position); foreach (var dragPoint in DragPoints) { dragPoint.Center -= centerVpx.ToVertex3D(); diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfacePackable.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfacePackable.cs index 6b619c97f..42b140b5c 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfacePackable.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfacePackable.cs @@ -26,13 +26,15 @@ public struct SurfacePackable public float HeightTop; public float HeightBottom; public IEnumerable DragPoints; + public PackableFloat3 UvOffset; public static byte[] Pack(SurfaceComponent comp) { return PackageApi.Packer.Pack(new SurfacePackable { HeightTop = comp.HeightTop, HeightBottom = comp.HeightBottom, - DragPoints = comp.DragPoints.Select(DragPointPackable.From) + DragPoints = comp.DragPoints.Select(DragPointPackable.From), + UvOffset = comp.uvOffset }); } @@ -42,6 +44,7 @@ public static void Unpack(byte[] bytes, SurfaceComponent comp) comp.HeightTop = data.HeightTop; comp.HeightBottom = data.HeightBottom; comp.DragPoints = data.DragPoints.Select(c => c.ToDragPoint()).ToArray(); + comp.uvOffset = data.UvOffset; } } diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs index 409f7941b..ae6dcbb45 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceSideMeshComponent.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using UnityEngine; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Surface; using VisualPinball.Engine.VPT.Table; @@ -30,13 +31,13 @@ public class SurfaceSideMeshComponent : MeshComponent(); - return new SurfaceMeshGenerator(data) + return new SurfaceMeshGenerator(data, Vertex3D.Zero) .GetMesh(SurfaceMeshGenerator.Side, playfieldComponent.Width, playfieldComponent.Height, 0, false) .TransformToWorld(); } protected override PbrMaterial GetMaterial(SurfaceData data, Table table) - => new SurfaceMeshGenerator(data).GetMaterial(SurfaceMeshGenerator.Side, table, data); + => new SurfaceMeshGenerator(data, Vertex3D.Zero).GetMaterial(SurfaceMeshGenerator.Side, table, data); public override void RebuildMeshes() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs index 6c4dbccf2..2deddc08f 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Surface/SurfaceTopMeshComponent.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . using UnityEngine; +using VisualPinball.Engine.Math; using VisualPinball.Engine.VPT; using VisualPinball.Engine.VPT.Surface; using VisualPinball.Engine.VPT.Table; @@ -30,13 +31,13 @@ public class SurfaceTopMeshComponent : MeshComponent(); - return new SurfaceMeshGenerator(data) + return new SurfaceMeshGenerator(data, MainComponent.uvOffset.ToVertex3D()) .GetMesh(SurfaceMeshGenerator.Top, playfieldComponent.Width, playfieldComponent.Height, 0, false) .TransformToWorld(); } protected override PbrMaterial GetMaterial(SurfaceData data, Table table) - => new SurfaceMeshGenerator(data).GetMaterial(SurfaceMeshGenerator.Top, table, data); + => new SurfaceMeshGenerator(data, Vertex3D.Zero).GetMaterial(SurfaceMeshGenerator.Top, table, data); public override void RebuildMeshes() { diff --git a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/LegacyContainer.cs b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/LegacyContainer.cs index 3db015d54..6cde55fa0 100644 --- a/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/LegacyContainer.cs +++ b/VisualPinball.Unity/VisualPinball.Unity/VPT/Table/LegacyContainer.cs @@ -217,7 +217,7 @@ public Engine.VPT.Texture ToTexture() } #endif - return new Engine.VPT.Texture(data); + return new Engine.VPT.Texture(data, null); } } }