diff --git a/TombEditor/Controls/Panel3D/Panel3D.cs b/TombEditor/Controls/Panel3D/Panel3D.cs index c7fa5ef65e..45bd836828 100644 --- a/TombEditor/Controls/Panel3D/Panel3D.cs +++ b/TombEditor/Controls/Panel3D/Panel3D.cs @@ -128,6 +128,7 @@ public bool DisablePickingForHiddenRooms // Legacy rendering state private WadRenderer _wadRenderer; + private ImportedGeometryRenderer _importedGeometryRenderer; private RasterizerState _rasterizerStateDepthBias; private GraphicsDevice _legacyDevice; private RasterizerState _rasterizerWireframe; @@ -243,6 +244,7 @@ protected override void Dispose(bool disposing) _rasterizerStateDepthBias?.Dispose(); _currentContextMenu?.Dispose(); _wadRenderer?.Dispose(); + _importedGeometryRenderer?.Dispose(); } base.Dispose(disposing); } @@ -362,6 +364,10 @@ obj is Editor.HideSelectionEvent || obj is Editor.LevelChangedEvent) _wadRenderer?.GarbageCollect(); + if (obj is Editor.LoadedImportedGeometriesChangedEvent || + obj is Editor.LevelChangedEvent) + _importedGeometryRenderer?.GarbageCollect(); + // Update cursor if (obj is Editor.ActionChangedEvent) { @@ -517,4 +523,4 @@ protected override void OnDraw() DrawScene(); } } -} \ No newline at end of file +} diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index e213b5e5f4..3549b3a24d 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1721,7 +1721,7 @@ private void DrawImportedGeometry(List importedGeometr var groups = importedGeometryToDraw.GroupBy(g => g.Model.UniqueID); foreach (var group in groups) { - var model = group.First().Model.DirectXModel; + var model = _importedGeometryRenderer.GetModel(group.First().Model); if (model == null || model.Meshes == null || model.Meshes.Count == 0) continue; @@ -1787,7 +1787,7 @@ private void DrawImportedGeometry(List importedGeometr if (texture != null && texture is ImportedGeometryTexture) { geometryEffect.Parameters["TextureEnabled"].SetValue(true); - geometryEffect.Parameters["Texture"].SetResource(((ImportedGeometryTexture)texture).DirectXTexture); + geometryEffect.Parameters["Texture"].SetResource(_importedGeometryRenderer.GetTexture((ImportedGeometryTexture)texture)); geometryEffect.Parameters["ReciprocalTextureSize"].SetValue(new Vector2(1.0f / texture.Image.Width, 1.0f / texture.Image.Height)); } else diff --git a/TombEditor/Controls/Panel3D/Panel3DInit.cs b/TombEditor/Controls/Panel3D/Panel3DInit.cs index a94d59518e..60561faa54 100644 --- a/TombEditor/Controls/Panel3D/Panel3DInit.cs +++ b/TombEditor/Controls/Panel3D/Panel3DInit.cs @@ -54,6 +54,7 @@ public override void InitializeRendering(RenderingDevice device, bool antialias, }; _wadRenderer = new WadRenderer(_legacyDevice, true, true, atlasSize, maxAllocationSize, false); + _importedGeometryRenderer = new ImportedGeometryRenderer(_legacyDevice); // Initialize vertex buffers _ghostBlockVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, 84); _boxVertexBuffer = new BoundingBox(new Vector3(-_littleCubeRadius), new Vector3(_littleCubeRadius)).GetVertexBuffer(_legacyDevice); diff --git a/TombLib/TombLib.Forms/Controls/OffscreenItemRenderer.cs b/TombLib/TombLib.Forms/Controls/OffscreenItemRenderer.cs index 126c080f57..e302d93a02 100644 --- a/TombLib/TombLib.Forms/Controls/OffscreenItemRenderer.cs +++ b/TombLib/TombLib.Forms/Controls/OffscreenItemRenderer.cs @@ -21,6 +21,7 @@ public class OffscreenItemRenderer : IDisposable private readonly Dx11RenderingDevice _device; private readonly GraphicsDevice _legacyDevice; private readonly WadRenderer _wadRenderer; + private readonly ImportedGeometryRenderer _importedGeometryRenderer; private Texture2D _renderTarget; private RenderTargetView _renderTargetView; @@ -34,6 +35,7 @@ public OffscreenItemRenderer() _device = (Dx11RenderingDevice)DeviceManager.DefaultDeviceManager.Device; _legacyDevice = DeviceManager.DefaultDeviceManager.___LegacyDevice; _wadRenderer = new WadRenderer(_legacyDevice, true, true, 1024, 512, false); + _importedGeometryRenderer = new ImportedGeometryRenderer(_legacyDevice); } public ImageC RenderThumbnail(IWadObject wadObject, TRVersion.Game version, Vector4 backColor, int size = 128) @@ -66,7 +68,7 @@ public ImageC RenderThumbnail(IWadObject wadObject, TRVersion.Game version, Vect var viewProjection = camera.GetViewProjectionMatrix(size, size); // Render the object using shared helper. - WadObjectRenderHelper.RenderObject(wadObject, _wadRenderer, _legacyDevice, viewProjection, camera.GetPosition(), false); + WadObjectRenderHelper.RenderObject(wadObject, _wadRenderer, _importedGeometryRenderer, _legacyDevice, viewProjection, camera.GetPosition(), false); // Read back pixels. return ReadPixels(size); @@ -188,12 +190,14 @@ private void DisposeRenderTargets() public void GarbageCollect() { _wadRenderer?.GarbageCollect(); + _importedGeometryRenderer?.GarbageCollect(); } public void Dispose() { DisposeRenderTargets(); _wadRenderer?.Dispose(); + _importedGeometryRenderer?.Dispose(); } } } diff --git a/TombLib/TombLib.Forms/Controls/PanelItemPreview.cs b/TombLib/TombLib.Forms/Controls/PanelItemPreview.cs index b48c207253..601cb4efd3 100644 --- a/TombLib/TombLib.Forms/Controls/PanelItemPreview.cs +++ b/TombLib/TombLib.Forms/Controls/PanelItemPreview.cs @@ -77,6 +77,7 @@ public bool AnimatePreview // Legacy rendering state private GraphicsDevice _legacyDevice; private WadRenderer _wadRenderer; + private ImportedGeometryRenderer _importedGeometryRenderer; public PanelItemPreview() { @@ -135,6 +136,7 @@ public override void InitializeRendering(RenderingDevice device, bool antialias // Reset scrollbar _legacyDevice = DeviceManager.DefaultDeviceManager.___LegacyDevice; _wadRenderer = new WadRenderer(DeviceManager.DefaultDeviceManager.___LegacyDevice, true, true, 1024, 512, false); + _importedGeometryRenderer = new ImportedGeometryRenderer(_legacyDevice); ResetCamera(); @@ -167,6 +169,7 @@ public void ResetCamera() public void GarbageCollect() { _wadRenderer?.GarbageCollect(); + _importedGeometryRenderer?.GarbageCollect(); } private bool ValidObject(IWadObject obj) @@ -201,6 +204,7 @@ protected override void Dispose(bool disposing) if (disposing) { _wadRenderer?.Dispose(); + _importedGeometryRenderer?.Dispose(); _textureAllocator?.Dispose(); } base.Dispose(disposing); @@ -245,7 +249,7 @@ protected override void OnDraw() } else { - WadObjectRenderHelper.RenderObject(CurrentObject, _wadRenderer, _legacyDevice, viewProjection, Camera.GetPosition(), DrawTransparency); + WadObjectRenderHelper.RenderObject(CurrentObject, _wadRenderer, _importedGeometryRenderer, _legacyDevice, viewProjection, Camera.GetPosition(), DrawTransparency); } } @@ -328,4 +332,4 @@ protected override void OnMouseMove(MouseEventArgs e) public abstract float NavigationSpeedMouseRotate { get; } public abstract bool ReadOnly { get; } } -} \ No newline at end of file +} diff --git a/TombLib/TombLib.Forms/Controls/WadObjectRenderHelper.cs b/TombLib/TombLib.Forms/Controls/WadObjectRenderHelper.cs index 8c8df16820..b2ce8b889e 100644 --- a/TombLib/TombLib.Forms/Controls/WadObjectRenderHelper.cs +++ b/TombLib/TombLib.Forms/Controls/WadObjectRenderHelper.cs @@ -98,7 +98,7 @@ public static ArcBallCamera CreateCameraForObject(IWadObject wadObject, WadRende -(float)Math.PI / 2, (float)Math.PI / 2, radius * 3, 50, 1000000, fieldOfView * (float)(Math.PI / 180)); } - public static void RenderObject(IWadObject wadObject, WadRenderer wadRenderer, + public static void RenderObject(IWadObject wadObject, WadRenderer wadRenderer, ImportedGeometryRenderer importedGeometryRenderer, GraphicsDevice legacyDevice, Matrix4x4 viewProjection, Vector3 cameraPosition, bool drawTransparency) { if (wadObject is WadMoveable moveable) @@ -106,7 +106,7 @@ public static void RenderObject(IWadObject wadObject, WadRenderer wadRenderer, else if (wadObject is WadStatic staticObj) RenderStatic(staticObj, wadRenderer, legacyDevice, viewProjection, cameraPosition, drawTransparency); else if (wadObject is ImportedGeometry impGeo) - RenderImportedGeometry(impGeo, legacyDevice, viewProjection, cameraPosition, drawTransparency); + RenderImportedGeometry(impGeo, importedGeometryRenderer, legacyDevice, viewProjection, cameraPosition, drawTransparency); } public static void RenderMoveable(WadMoveable moveable, WadRenderer wadRenderer, @@ -206,10 +206,10 @@ public static void RenderStatic(WadStatic staticObj, WadRenderer wadRenderer, } } - public static void RenderImportedGeometry(ImportedGeometry geo, + public static void RenderImportedGeometry(ImportedGeometry geo, ImportedGeometryRenderer importedGeometryRenderer, GraphicsDevice legacyDevice, Matrix4x4 viewProjection, Vector3 cameraPosition, bool drawTransparency) { - var model = geo.DirectXModel; + var model = importedGeometryRenderer.GetModel(geo); if (model == null || model.Meshes == null || model.Meshes.Count == 0) return; @@ -240,7 +240,7 @@ public static void RenderImportedGeometry(ImportedGeometry geo, if (texture != null && texture is ImportedGeometryTexture) { effect.Parameters["TextureEnabled"].SetValue(true); - effect.Parameters["Texture"].SetResource(((ImportedGeometryTexture)texture).DirectXTexture); + effect.Parameters["Texture"].SetResource(importedGeometryRenderer.GetTexture((ImportedGeometryTexture)texture)); effect.Parameters["ReciprocalTextureSize"].SetValue(new Vector2(1.0f / texture.Image.Width, 1.0f / texture.Image.Height)); } else diff --git a/TombLib/TombLib.Rendering/Graphics/DeviceManager.cs b/TombLib/TombLib.Rendering/Graphics/DeviceManager.cs index f18e019007..87c2e3f725 100644 --- a/TombLib/TombLib.Rendering/Graphics/DeviceManager.cs +++ b/TombLib/TombLib.Rendering/Graphics/DeviceManager.cs @@ -25,7 +25,6 @@ public DeviceManager() // Recreate legacy environment { ___LegacyDevice = GraphicsDevice.New(((Rendering.DirectX11.Dx11RenderingDevice)Device).Device); - LevelData.ImportedGeometry.Device = ___LegacyDevice; // Load legacy effects string dir = Path.GetDirectoryName(System.Reflection.Assembly.GetCallingAssembly().Location) + "\\Rendering\\Legacy"; diff --git a/TombLib/TombLib.Test/ImportedGeometryTests.cs b/TombLib/TombLib.Test/ImportedGeometryTests.cs new file mode 100644 index 0000000000..3090d1c79e --- /dev/null +++ b/TombLib/TombLib.Test/ImportedGeometryTests.cs @@ -0,0 +1,83 @@ +using System; +using System.IO; +using System.Numerics; +using TombLib.Graphics; +using TombLib.LevelData; +using TombLib.Utils; + +namespace TombLib.Test; + +[TestClass] +public class ImportedGeometryTests +{ + [TestMethod] + public void UpdateBuffers_UpdatesBoundingBoxesAndVersion() + { + var model = new ImportedGeometry.Model(1.0f); + var material = new Material("TestMaterial"); + model.Materials.Add(material); + + var mesh = new ImportedGeometryMesh("TestMesh"); + mesh.Vertices.Add(CreateVertex(new Vector3(0.0f, 0.0f, 0.0f))); + mesh.Vertices.Add(CreateVertex(new Vector3(1024.0f, 0.0f, 0.0f))); + mesh.Vertices.Add(CreateVertex(new Vector3(0.0f, 1024.0f, 0.0f))); + + var submesh = new Submesh(material); + submesh.Indices.AddRange(new[] { 0, 1, 2 }); + mesh.Submeshes.Add(material, submesh); + model.Meshes.Add(mesh); + + model.UpdateBuffers(); + var firstVersion = model.Version; + + Assert.AreEqual(new Vector3(0.0f, 0.0f, 0.0f), mesh.BoundingBox.Minimum); + Assert.AreEqual(new Vector3(1024.0f, 1024.0f, 0.0f), mesh.BoundingBox.Maximum); + Assert.AreEqual(mesh.BoundingBox.Minimum, model.BoundingBox.Minimum); + Assert.AreEqual(mesh.BoundingBox.Maximum, model.BoundingBox.Maximum); + CollectionAssert.AreEqual(new[] { 0, 1, 2 }, mesh.Indices); + + mesh.Vertices[0] = CreateVertex(new Vector3(-1024.0f, 0.0f, 0.0f)); + model.UpdateBuffers(); + + Assert.IsTrue(model.Version > firstVersion); + Assert.AreEqual(new Vector3(-1024.0f, 0.0f, 0.0f), model.BoundingBox.Minimum); + Assert.AreEqual(new Vector3(1024.0f, 1024.0f, 0.0f), model.BoundingBox.Maximum); + } + + [TestMethod] + public void Assign_UpdatesTextureVersion() + { + var fileName = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".png"); + ImageC.CreateNew(2, 2).SaveToFile(fileName); + + try + { + var original = new ImportedGeometryTexture(fileName); + var reloaded = new ImportedGeometryTexture(fileName); + var originalVersion = original.Version; + + original.Assign(reloaded); + + Assert.IsTrue(original.Version > originalVersion); + Assert.AreEqual(reloaded.AbsolutePath, original.AbsolutePath); + Assert.AreEqual(reloaded.Image.Width, original.Image.Width); + Assert.AreEqual(reloaded.Image.Height, original.Image.Height); + } + finally + { + if (File.Exists(fileName)) + File.Delete(fileName); + } + } + + private static ImportedGeometryVertex CreateVertex(Vector3 position) + { + return new ImportedGeometryVertex + { + Position = position, + UV = Vector2.Zero, + Color = Vector3.One, + Normal = Vector3.UnitY + }; + } +} diff --git a/TombLib/TombLib/Graphics/ImportedGeometryRenderer.cs b/TombLib/TombLib/Graphics/ImportedGeometryRenderer.cs new file mode 100644 index 0000000000..185bab7ff9 --- /dev/null +++ b/TombLib/TombLib/Graphics/ImportedGeometryRenderer.cs @@ -0,0 +1,170 @@ +using NLog; +using SharpDX.Toolkit.Graphics; +using System; +using System.Collections.Generic; +using System.Numerics; +using TombLib.LevelData; +using TombLib.Utils; +using Buffer = SharpDX.Toolkit.Graphics.Buffer; + +namespace TombLib.Graphics +{ + public class ImportedGeometryRenderableMesh : Mesh + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + public bool HasVertexColors { get; set; } + + public ImportedGeometryRenderableMesh(GraphicsDevice device, ImportedGeometryMesh source) + : base(device, source.Name) + { + HasVertexColors = source.HasVertexColors; + BoundingBox = source.BoundingBox; + Vertices.AddRange(source.Vertices); + + foreach (var submesh in source.Submeshes) + { + var newSubmesh = new Submesh(submesh.Key) + { + BaseIndex = submesh.Value.BaseIndex, + MeshBaseIndex = submesh.Value.MeshBaseIndex + }; + newSubmesh.Indices.AddRange(submesh.Value.Indices); + Submeshes.Add(submesh.Key, newSubmesh); + } + } + + public void UpdateBuffers(Vector3? position = null) + { + if (Vertices.Count == 0) + return; + + DepthSort(position); + UpdateBoundingBox(); + + VertexBuffer?.Dispose(); + IndexBuffer?.Dispose(); + + VertexBuffer = Buffer.Vertex.New(GraphicsDevice, Vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Immutable); + InputLayout = VertexInputLayout.FromBuffer(0, VertexBuffer); + IndexBuffer = Buffer.Index.New(GraphicsDevice, Indices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Immutable); + + if (VertexBuffer == null) + logger.Error("Vertex Buffer of Imported Geometry " + Name + " could not be created!"); + if (InputLayout == null) + logger.Error("Input Layout of Imported Geometry " + Name + " could not be created!"); + if (IndexBuffer == null) + logger.Error("Index Buffer of Imported Geometry " + Name + " could not be created!"); + } + } + + public class ImportedGeometryRenderableModel : Model + { + public float Scale { get; } + + public ImportedGeometryRenderableModel(GraphicsDevice device, float scale) + : base(device, ModelType.RoomGeometry) + { + Scale = scale; + } + + public override void UpdateBuffers(Vector3? position = null) + { + foreach (var mesh in Meshes) + mesh.UpdateBuffers(position); + } + } + + public class ImportedGeometryRenderer : IDisposable + { + private sealed class CachedModel + { + public DataVersion Version { get; init; } + public ImportedGeometryRenderableModel Model { get; init; } + } + + private sealed class CachedTexture + { + public DataVersion Version { get; init; } + public Texture2D Texture { get; init; } + } + + public GraphicsDevice GraphicsDevice { get; } + + private Dictionary Models { get; } = new Dictionary(); + private Dictionary Textures { get; } = new Dictionary(); + + public ImportedGeometryRenderer(GraphicsDevice graphicsDevice) + { + GraphicsDevice = graphicsDevice; + } + + public ImportedGeometryRenderableModel GetModel(ImportedGeometry geometry) + { + if (geometry?.DirectXModel == null) + return null; + + if (Models.TryGetValue(geometry, out var cachedModel)) + { + if (cachedModel.Version >= geometry.DirectXModel.Version) + return cachedModel.Model; + + cachedModel.Model.Dispose(); + Models.Remove(geometry); + } + + var model = BuildModel(geometry.DirectXModel); + Models.Add(geometry, new CachedModel { Model = model, Version = geometry.DirectXModel.Version }); + return model; + } + + public Texture2D GetTexture(ImportedGeometryTexture texture) + { + if (texture == null) + return null; + + if (Textures.TryGetValue(texture, out var cachedTexture)) + { + if (cachedTexture.Version >= texture.Version) + return cachedTexture.Texture; + + cachedTexture.Texture.Dispose(); + Textures.Remove(texture); + } + + var directXTexture = TextureLoad.Load(GraphicsDevice, texture.Image); + Textures.Add(texture, new CachedTexture { Texture = directXTexture, Version = texture.Version }); + return directXTexture; + } + + public void GarbageCollect() => Dispose(); + + public void Dispose() + { + foreach (var model in Models.Values) + model.Model.Dispose(); + Models.Clear(); + + foreach (var texture in Textures.Values) + texture.Texture.Dispose(); + Textures.Clear(); + } + + private ImportedGeometryRenderableModel BuildModel(ImportedGeometry.Model source) + { + var model = new ImportedGeometryRenderableModel(GraphicsDevice, source.Scale) + { + BoundingBox = source.BoundingBox, + Version = source.Version + }; + + model.Materials.AddRange(source.Materials); + + foreach (var mesh in source.Meshes) + model.Meshes.Add(new ImportedGeometryRenderableMesh(GraphicsDevice, mesh)); + + model.UpdateBuffers(); + return model; + } + } +} diff --git a/TombLib/TombLib/LevelData/ImportedGeometry.cs b/TombLib/TombLib/LevelData/ImportedGeometry.cs index 2aef2029a5..42f70e4646 100644 --- a/TombLib/TombLib/LevelData/ImportedGeometry.cs +++ b/TombLib/TombLib/LevelData/ImportedGeometry.cs @@ -1,4 +1,4 @@ -using NLog; +using NLog; using SharpDX.Toolkit.Graphics; using System; using System.Collections.Generic; @@ -11,14 +11,13 @@ using TombLib.Graphics; using TombLib.Utils; using TombLib.Wad; -using Buffer = SharpDX.Toolkit.Graphics.Buffer; using Texture = TombLib.Utils.Texture; namespace TombLib.LevelData { public class ImportedGeometryTexture : Texture { - public Texture2D DirectXTexture { get; private set; } + public DataVersion Version { get; private set; } = DataVersion.GetNext(); public ImportedGeometryTexture(string absolutePath) { @@ -27,17 +26,11 @@ public ImportedGeometryTexture(string absolutePath) // Replace magenta with transparent color Image.ReplaceColor(new ColorC(255, 0, 255, 255), new ColorC(0, 0, 0, 0)); - - if (SynchronizationContext.Current == null) - DirectXTexture = TextureLoad.Load(ImportedGeometry.Device, Image); - else - SynchronizationContext.Current.Post(unused => // Synchronize DirectX, we can't 'send' because that may deadlock with the level settings reloader - DirectXTexture = TextureLoad.Load(ImportedGeometry.Device, Image), null); } private ImportedGeometryTexture(ImportedGeometryTexture other) { - DirectXTexture = other.DirectXTexture; + Version = other.Version; AbsolutePath = other.AbsolutePath; Image = other.Image; } @@ -46,7 +39,7 @@ public void Assign(ImportedGeometryTexture other) { AbsolutePath = other.AbsolutePath; Image = other.Image; - DirectXTexture = other.DirectXTexture; + Version = DataVersion.GetNext(); } public override Texture Clone() => new ImportedGeometryTexture(this); @@ -59,7 +52,6 @@ public struct ImportedGeometryVertex : IVertex { [VertexElement("POSITION", 0, SharpDX.DXGI.Format.R32G32B32_Float, 0)] public Vector3 Position; - //private readonly float _unusedPadding; [VertexElement("TEXCOORD", 0, SharpDX.DXGI.Format.R32G32_Float, 12)] public Vector2 UV; [VertexElement("COLOR", 0, SharpDX.DXGI.Format.R32G32B32_Float, 20)] @@ -70,44 +62,67 @@ public struct ImportedGeometryVertex : IVertex Vector3 IVertex.Position => Position; } - public class ImportedGeometryMesh : Mesh + public class ImportedGeometryMesh { - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); - + public string Name { get; } public bool HasVertexColors { get; set; } + public List Vertices { get; } = new List(); + public List Indices { get; } = new List(); + public Dictionary Submeshes { get; } = new Dictionary(); + public BoundingBox BoundingBox { get; set; } + + public ImportedGeometryMesh(string name) + { + Name = name; + } - public ImportedGeometryMesh(GraphicsDevice device, string name) - : base(device, name) - { } + public void UpdateBoundingBox() + { + Vector3 minVertex = new Vector3(float.MaxValue); + Vector3 maxVertex = new Vector3(float.MinValue); + + foreach (var vertex in Vertices) + { + minVertex = Vector3.Min(minVertex, vertex.Position); + maxVertex = Vector3.Max(maxVertex, vertex.Position); + } + + BoundingBox = new BoundingBox(minVertex, maxVertex); + } - public void UpdateBuffers(Vector3? position = null) + public void DepthSort(Vector3? position) { - if (Vertices.Count == 0) - return; - - // FIXME: because imp geo meshes are directly referenced everywhere in TE, - // we can't depth-sort them, otherwise a race condition may occur which will - // cause incorrect rendering or occasional SEHExceptions. For more info, see here: - // https://github.com/MontyTRC89/Tomb-Editor/issues/516 - - DepthSort(null); // null means no depth-sorting occurs - UpdateBoundingBox(); - - if (VertexBuffer != null) - VertexBuffer.Dispose(); - if (IndexBuffer != null) - IndexBuffer.Dispose(); - - VertexBuffer = Buffer.Vertex.New(GraphicsDevice, Vertices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Immutable); - InputLayout = VertexInputLayout.FromBuffer(0, VertexBuffer); - IndexBuffer = Buffer.Index.New(GraphicsDevice, Indices.ToArray(), SharpDX.Direct3D11.ResourceUsage.Immutable); - - if (VertexBuffer == null) - logger.Error("Vertex Buffer of Imported Geometry " + Name + " could not be created!"); - if (InputLayout == null) - logger.Error("Input Layout of Imported Geometry " + Name + " could not be created!"); - if (IndexBuffer == null) - logger.Error("Index Buffer of Imported Geometry " + Name + " could not be created!"); + int lastBaseIndex = 0; + Indices.Clear(); + + foreach (var submesh in Submeshes) + { + submesh.Value.BaseIndex = lastBaseIndex; + if (submesh.Value.NumIndices != 0) + { + var indexList = new List(); + + for (int i = 0; i < submesh.Value.NumIndices; i += 3) + { + var tri = new[] + { + submesh.Value.Indices[i], + submesh.Value.Indices[i + 1], + submesh.Value.Indices[i + 2] + }; + indexList.Add(tri); + } + + if (position != null) + indexList = indexList.OrderByDescending(p => Vector3.Distance(position.Value, + (Vertices[p[0]].Position + Vertices[p[1]].Position + Vertices[p[2]].Position) / 3.0f)).ToList(); + + foreach (var tri in indexList) + Indices.AddRange(tri); + } + + lastBaseIndex += submesh.Value.NumIndices; + } } } @@ -151,41 +166,58 @@ public ImportedGeometryInfo(string path, IOGeometrySettings settings) public class ImportedGeometry : IWadObject, ICloneable, IReloadableResource, IEquatable { - public static GraphicsDevice Device; - private static readonly Logger logger = LogManager.GetCurrentClassLogger(); public class UniqueIDType { } - public class Model : Model + public class Model { - public float Scale { get; private set; } + public BoundingBox BoundingBox { get; set; } + public List Meshes { get; } = new List(); + public List Materials { get; } = new List(); + public float Scale { get; } + public DataVersion Version { get; private set; } = DataVersion.GetNext(); public int TotalTriangles { get { int numTriangles = 0; + foreach (var mesh in Meshes) foreach (var submesh in mesh.Submeshes) numTriangles += submesh.Value.Indices.Count / 3; + return numTriangles; } } - public Model(GraphicsDevice device, float scale) - : base(device, ModelType.RoomGeometry) + public Model(float scale) { Scale = scale; } - public override void UpdateBuffers(Vector3? position = null) + public void UpdateBuffers(Vector3? position = null) { + var boundingBox = new BoundingBox(); + bool hasMesh = false; + foreach (var mesh in Meshes) { + if (mesh.Vertices.Count == 0) + continue; + mesh.UpdateBoundingBox(); - mesh.UpdateBuffers(position); + mesh.DepthSort(position); + + boundingBox = hasMesh ? boundingBox.Union(mesh.BoundingBox) : mesh.BoundingBox; + hasMesh = true; } + + if (hasMesh) + BoundingBox = boundingBox; + + Version = DataVersion.GetNext(); } } @@ -222,7 +254,6 @@ public void Update(LevelSettings settings, Dictionary absoluteP string importedGeometryPath = settings.MakeAbsolute(info.Path); string importedGeometryDirectory = Path.GetDirectoryName(importedGeometryPath); - // Invoke the TombLib geometry import code var settingsIO = new IOGeometrySettings { Scale = info.Scale, @@ -243,14 +274,11 @@ public void Update(LevelSettings settings, Dictionary absoluteP }); var tmpModel = importer.ImportFromFile(importedGeometryPath); - // Integrity checks if (tmpModel.Materials.Count == 0) throw new Exception("No valid materials found"); if (tmpModel.Meshes.Count == 0) throw new Exception("No valid mesh data found"); - // If called from UI thread, synchronize DirectX, we can't 'send' because that may - // deadlock with the level settings reloader. if (SynchronizationContext.Current != null) SynchronizationContext.Current.Post(unused => Update(tmpModel, info), null); else @@ -270,36 +298,31 @@ public void Update(LevelSettings settings, Dictionary absoluteP private bool Update(IOModel tmpModel, ImportedGeometryInfo info) { - if (Device == null) - return false; - - // Create a new static model - DirectXModel = new Model(Device, info.Scale); + DirectXModel = new Model(info.Scale); DirectXModel.BoundingBox = tmpModel.BoundingBox; - // Create materials foreach (var tmpMaterial in tmpModel.Materials) { - var material = new Material(tmpMaterial.Name); - material.Texture = tmpMaterial.Texture; - material.AdditiveBlending = tmpMaterial.AdditiveBlending; - material.DoubleSided = tmpMaterial.DoubleSided; + var material = new Material(tmpMaterial.Name) + { + Texture = tmpMaterial.Texture, + AdditiveBlending = tmpMaterial.AdditiveBlending, + DoubleSided = tmpMaterial.DoubleSided + }; DirectXModel.Materials.Add(material); } - // Loop for each mesh loaded in scene foreach (var mesh in tmpModel.Meshes) { - // Make sure we always have correct normals if (mesh.Normals.Count == 0) mesh.CalculateNormals(); - var modelMesh = new ImportedGeometryMesh(Device, mesh.Name); - - modelMesh.HasVertexColors = (mesh.Colors.Count != 0); + var modelMesh = new ImportedGeometryMesh(mesh.Name) + { + HasVertexColors = mesh.Colors.Count != 0 + }; var currentIndex = 0; - var currPoly = 0; foreach (var tmpSubmesh in mesh.Submeshes) { var material = DirectXModel.Materials[tmpModel.Materials.IndexOf(tmpSubmesh.Value.Material)]; @@ -312,14 +335,7 @@ private bool Update(IOModel tmpModel, ImportedGeometryInfo info) var vertexList = new List(); for (var i = 0; i < 4; i++) - { - var vertex = new ImportedGeometryVertex(); - vertex.Position = mesh.Positions[tmpPoly.Indices[i]]; - vertex.Color = tmpPoly.Indices[i] < mesh.Colors.Count ? mesh.Colors[tmpPoly.Indices[i]].To3() : Vector3.One; - vertex.UV = tmpPoly.Indices[i] < mesh.UV.Count ? mesh.UV[tmpPoly.Indices[i]] : Vector2.Zero; - vertex.Normal = tmpPoly.Indices[i] < mesh.Normals.Count ? mesh.Normals[tmpPoly.Indices[i]] : Vector3.Zero; - vertexList.Add(vertex); - } + vertexList.Add(CreateVertex(mesh, tmpPoly.Indices[i])); // HACK: Triangulate and disjoint quad faces for imported geometry, because otherwise another hack which joints // disjointed vertices together will fail in Rooms.cs @@ -344,18 +360,11 @@ private bool Update(IOModel tmpModel, ImportedGeometryInfo info) { for (var i = 0; i < 3; i++) { - var vertex = new ImportedGeometryVertex(); - vertex.Position = mesh.Positions[tmpPoly.Indices[i]]; - vertex.Color = tmpPoly.Indices[i] < mesh.Colors.Count ? mesh.Colors[tmpPoly.Indices[i]].To3() : Vector3.One; - vertex.UV = tmpPoly.Indices[i] < mesh.UV.Count ? mesh.UV[tmpPoly.Indices[i]] : Vector2.Zero; - vertex.Normal = tmpPoly.Indices[i] < mesh.Normals.Count ? mesh.Normals[tmpPoly.Indices[i]] : Vector3.Zero; - modelMesh.Vertices.Add(vertex); + modelMesh.Vertices.Add(CreateVertex(mesh, tmpPoly.Indices[i])); submesh.Indices.Add(currentIndex); currentIndex++; } } - - currPoly++; } modelMesh.Submeshes.Add(material, submesh); @@ -369,28 +378,34 @@ private bool Update(IOModel tmpModel, ImportedGeometryInfo info) return true; } + private static ImportedGeometryVertex CreateVertex(IOMesh mesh, int index) + { + return new ImportedGeometryVertex + { + Position = mesh.Positions[index], + Color = index < mesh.Colors.Count ? mesh.Colors[index].To3() : Vector3.One, + UV = index < mesh.UV.Count ? mesh.UV[index] : Vector2.Zero, + Normal = index < mesh.Normals.Count ? mesh.Normals[index] : Vector3.Zero + }; + } + private Texture GetOrAddTexture(Dictionary absolutePathTextureLookup, string importedGeometryDirectory, string texturePath) { if (string.IsNullOrEmpty(texturePath)) return null; string absolutePath = Path.GetFullPath(Path.Combine(importedGeometryDirectory, texturePath)); - // Is this texture already loaded? { - Texture texture; - if (absolutePathTextureLookup.TryGetValue(absolutePath, out texture)) + if (absolutePathTextureLookup.TryGetValue(absolutePath, out Texture texture)) { - // Make sure the texture is already listed under this object var importedGeometryTexture = texture as ImportedGeometryTexture; if (importedGeometryTexture != null && !Textures.Contains(importedGeometryTexture)) Textures.Add(importedGeometryTexture); - // Use texture return texture; } } - // Add a new imported geometry texture var newTexture = new ImportedGeometryTexture(absolutePath); Textures.Add(newTexture); absolutePathTextureLookup.Add(absolutePath, newTexture); @@ -418,7 +433,7 @@ public ImportedGeometryComparer(LevelSettings settings) { _settings = settings; } - + public bool Equals(ImportedGeometry x, ImportedGeometry y) { return (x.Info.FlipUV_V == y.Info.FlipUV_V && @@ -447,7 +462,7 @@ public int GetHashCode(ImportedGeometry obj) obj.Info.SwapXY.ToString() + "|" + obj.Info.SwapXZ.ToString() + "|" + obj.Info.SwapYZ.ToString(); - return (info.GetHashCode()); + return info.GetHashCode(); } } -} \ No newline at end of file +}