From 8a37ed8fc732b5f74ba9ced8f17c7b451a196cec Mon Sep 17 00:00:00 2001 From: Lwmte <3331699+Lwmte@users.noreply.github.com> Date: Sat, 9 May 2026 19:37:06 +0200 Subject: [PATCH] Initial Commit --- TombEditor/Controls/Panel3D/Panel3D.cs | 3 + TombEditor/Controls/Panel3D/Panel3DDraw.cs | 130 ++++++++++----------- TombEditor/Controls/Panel3D/Panel3DInit.cs | 123 ++++++++++++++++++- 3 files changed, 185 insertions(+), 71 deletions(-) diff --git a/TombEditor/Controls/Panel3D/Panel3D.cs b/TombEditor/Controls/Panel3D/Panel3D.cs index c7fa5ef65e..25a9ea6282 100644 --- a/TombEditor/Controls/Panel3D/Panel3D.cs +++ b/TombEditor/Controls/Panel3D/Panel3D.cs @@ -231,6 +231,9 @@ protected override void Dispose(bool disposing) _rasterizerWireframe?.Dispose(); _objectHeightLineVertexBuffer?.Dispose(); _flybyPathVertexBuffer?.Dispose(); + _flybyPyramidSolidVertexBuffer?.Dispose(); + _flybyPyramidAccentVertexBuffer?.Dispose(); + _flybyPyramidWireVertexBuffer?.Dispose(); _gizmo?.Dispose(); _sphere?.Dispose(); _cone?.Dispose(); diff --git a/TombEditor/Controls/Panel3D/Panel3DDraw.cs b/TombEditor/Controls/Panel3D/Panel3DDraw.cs index e213b5e5f4..75bc6d6b5f 100644 --- a/TombEditor/Controls/Panel3D/Panel3DDraw.cs +++ b/TombEditor/Controls/Panel3D/Panel3DDraw.cs @@ -1349,94 +1349,86 @@ private void DrawPlaceholders(Effect effect, Room[] roomsWhoseObjectsToDraw, Lis if (_editor.CameraPreviewMode != CameraPreviewType.None) return; - // Draw extra flyby cones (hidden during flyby preview) + // Draw extra flyby camera pyramids (hidden during flyby preview). + DrawFlybyCameraPyramids(effect, roomsWhoseObjectsToDraw); + } - _legacyDevice.SetVertexBuffer(_cone.VertexBuffer); - _legacyDevice.SetVertexInputLayout(_cone.InputLayout); - _legacyDevice.SetIndexBuffer(_cone.IndexBuffer, _cone.IsIndex32Bits); + private void DrawFlybyCameraPyramids(Effect effect, Room[] roomsWhoseObjectsToDraw) + { _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone); _legacyDevice.SetBlendState(_legacyDevice.BlendStates.AlphaBlend); - bool wireframe = false; foreach (Room room in roomsWhoseObjectsToDraw) foreach (var instance in room.Objects.OfType()) { var color = MathC.GetRandomColorByIndex(instance.Sequence, 32, 0.7f); - Matrix4x4 model; - if (_highlightedObjects.Contains(instance)) color = _editor.Configuration.UI_ColorScheme.ColorSelection; - for (int pass = 0; pass < 2; pass++) - { - if (_editor.SelectedObject == instance) - { - float coneAngle = (float)Math.Atan2(512, 1024); - float cutoffScaleH = 1; - float cutoffScaleW = instance.Fov * (float)(Math.PI / 360) / coneAngle * cutoffScaleH; + float distance = Vector3.Distance(instance.WorldPosition, Camera.GetPosition()); + if (distance < (_coneRadius * 0.5f)) + color.W *= distance / (_coneRadius * 0.5f); - if (pass == 0) - { - // Ordinary cone - model = Matrix4x4.CreateScale(cutoffScaleW, cutoffScaleW, cutoffScaleH) * instance.ObjectMatrix; - } - else - { - // Roll pointer - var step = 1 / _coneRadius; - var scale = _littleCubeRadius * 2; - var pScale = _littleCubeRadius / 5; - var vOffset = -cutoffScaleW / 2 * _coneRadius - scale; - var hOffset = cutoffScaleH * _coneRadius; - - model = Matrix4x4.CreateScale(step * pScale, step * pScale, step * scale) * - Matrix4x4.CreateTranslation(new Vector3(0, hOffset, vOffset)) * - Matrix4x4.CreateRotationX((float)(Math.PI / 2)) * - instance.ObjectMatrix; - } + if (color.W <= 0.0f) + continue; - if (!wireframe) - { - _legacyDevice.SetRasterizerState(_rasterizerWireframe); - wireframe = true; - } - } - else - { - // Don't do second pass for non-selected flybys - if (pass == 1) - break; + bool selected = _editor.SelectedObject == instance; + float pyramidLength = selected ? _coneRadius * _flybyPyramidSelectedLengthScale : _flybyPyramidInactiveLength; + float pyramidHalfHeight = selected ? GetFlybyPyramidDefaultHalfSize(pyramidLength) : _flybyPyramidInactiveBaseHeight * 0.5f; + float pyramidHalfWidth = GetFlybyPyramidHalfWidth(instance.Fov, pyramidLength, pyramidHalfHeight, selected); + var scaleMatrix = Matrix4x4.CreateScale(pyramidHalfWidth, pyramidHalfHeight, pyramidLength); + var transform = selected ? instance.RotationPositionMatrix : BuildFlybyMarkerMatrix(instance, pyramidLength); + var model = scaleMatrix * transform; + + if (!selected) + DrawFlybyPyramidBuffer(effect, _flybyPyramidSolidVertexBuffer, PrimitiveType.TriangleList, model, color); + + DrawFlybyPyramidBuffer(effect, + selected ? _flybyPyramidWireVertexBuffer : _flybyPyramidAccentVertexBuffer, + PrimitiveType.LineList, + model, + color); + } - // Push unselected cone further away in sprite mode for neatness - model = _editor.Configuration.Rendering3D_UseSpritesForServiceObjects - ? Matrix4x4.CreateTranslation(new Vector3(0, 0, -_coneRadius * 0.5f)) - : Matrix4x4.Identity; + _legacyDevice.SetBlendState(_legacyDevice.BlendStates.Opaque); + _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullBack); + } - model *= Matrix4x4.CreateTranslation(new Vector3(0, 0, -_coneRadius * 1.2f)) * - Matrix4x4.CreateRotationY((float)Math.PI) * - Matrix4x4.CreateScale(1 / _coneRadius * _littleCubeRadius * 2.0f) * - instance.ObjectMatrix; + private void DrawFlybyPyramidBuffer(Effect effect, Buffer buffer, PrimitiveType primitiveType, Matrix4x4 model, Vector4 color) + { + _legacyDevice.SetVertexBuffer(buffer); + _legacyDevice.SetVertexInputLayout(VertexInputLayout.FromBuffer(0, buffer)); + effect.Parameters["ModelViewProjection"].SetValue((model * _viewProjection).ToSharpDX()); + effect.Parameters["Color"].SetValue(color); + effect.CurrentTechnique.Passes[0].Apply(); + _legacyDevice.Draw(primitiveType, buffer.ElementCount); + } - if (wireframe) - { - _legacyDevice.SetRasterizerState(_legacyDevice.RasterizerStates.CullNone); - wireframe = false; - } - } + private Matrix4x4 BuildFlybyMarkerMatrix(FlybyCameraInstance instance, float pyramidLength) + { + var model = _editor.Configuration.Rendering3D_UseSpritesForServiceObjects + ? Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, -pyramidLength * 0.5f)) + : Matrix4x4.Identity; + + return model * + Matrix4x4.CreateTranslation(new Vector3(0.0f, 0.0f, -pyramidLength * 1.2f)) * + Matrix4x4.CreateRotationY((float)Math.PI) * + instance.RotationPositionMatrix; + } - // Apply distance-based fade for nearby flyby cameras. - float distance = Vector3.Distance(instance.WorldPosition, Camera.GetPosition()); - if (distance < (_coneRadius * 0.5f)) - color.W *= distance / (_coneRadius * 0.5f); + private static float GetFlybyPyramidDefaultHalfSize(float pyramidLength) + { + return MathF.Tan(_flybyPyramidReferenceFov * (float)(Math.PI / 360.0f)) * pyramidLength * _flybyPyramidSelectedBaseScale; + } - effect.Parameters["ModelViewProjection"].SetValue((model * _viewProjection).ToSharpDX()); - effect.Parameters["Color"].SetValue(color); - effect.CurrentTechnique.Passes[0].Apply(); - _legacyDevice.DrawIndexed(PrimitiveType.TriangleList, _cone.IndexBuffer.ElementCount); - } - } + private static float GetFlybyPyramidHalfWidth(float fov, float pyramidLength, float pyramidHalfHeight, bool selected) + { + float clampedHalfAngle = Math.Clamp(fov, 1.0f, 179.0f) * (float)(Math.PI / 360.0f); - _legacyDevice.SetBlendState(_legacyDevice.BlendStates.Opaque); + if (selected) + return MathF.Tan(clampedHalfAngle) * pyramidLength * _flybyPyramidSelectedBaseScale; + + return pyramidHalfHeight; } private void DrawOrQueueServiceObject(ISpatial instance, GeometricPrimitive primitive, Vector4 color, Effect effect, List sprites) diff --git a/TombEditor/Controls/Panel3D/Panel3DInit.cs b/TombEditor/Controls/Panel3D/Panel3DInit.cs index a94d59518e..48fb41e76b 100644 --- a/TombEditor/Controls/Panel3D/Panel3DInit.cs +++ b/TombEditor/Controls/Panel3D/Panel3DInit.cs @@ -1,4 +1,5 @@ using SharpDX.Toolkit.Graphics; +using System.Collections.Generic; using System.Numerics; using TombLib.Graphics.Primitives; using TombLib.Graphics; @@ -12,6 +13,26 @@ namespace TombEditor.Controls.Panel3D { public partial class Panel3D { + private Buffer _flybyPyramidSolidVertexBuffer; + private Buffer _flybyPyramidAccentVertexBuffer; + private Buffer _flybyPyramidWireVertexBuffer; + + private const float _flybyPyramidReferenceFov = 80.0f; + private const float _flybyPyramidSelectedLengthScale = 1.5f; + private const float _flybyPyramidSelectedBaseScale = 0.5f; + private const float _flybyPyramidInactiveLength = 300.0f; + private const float _flybyPyramidInactiveBaseHeight = 200.0f; + private const float _flybyPyramidNormalizedHalfWidth = 1.0f; + private const float _flybyPyramidNormalizedHalfHeight = 1.0f; + private const float _flybyPyramidNormalizedLength = 1.0f; + private const int _flybyPyramidPerimeterSegments = 5; + private const int _flybyPyramidStripeLineCount = 8; + private const float _flybyPyramidStripeStart = 0.95f; + private const float _flybyPyramidStripeEnd = 1.0f; + private static readonly SolidVertex[] _flybyPyramidSolidVertices = BuildFlybyPyramidSolidVertices(); + private static readonly SolidVertex[] _flybyPyramidAccentVertices = BuildFlybyPyramidLineVertices(false); + private static readonly SolidVertex[] _flybyPyramidWireVertices = BuildFlybyPyramidLineVertices(true); + public override void InitializeRendering(RenderingDevice device, bool antialias, ObjectRenderingQuality objectQuality) { base.InitializeRendering(device, antialias, objectQuality); @@ -42,8 +63,8 @@ public override void InitializeRendering(RenderingDevice device, bool antialias, int atlasSize = objectQuality switch { ObjectRenderingQuality.High => 4096, - ObjectRenderingQuality.Medium => 1024, - _ => 512 + ObjectRenderingQuality.Medium => 1024, + _ => 512 }; int maxAllocationSize = objectQuality switch @@ -57,6 +78,9 @@ public override void InitializeRendering(RenderingDevice device, bool antialias, // Initialize vertex buffers _ghostBlockVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, 84); _boxVertexBuffer = new BoundingBox(new Vector3(-_littleCubeRadius), new Vector3(_littleCubeRadius)).GetVertexBuffer(_legacyDevice); + _flybyPyramidSolidVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidSolidVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic); + _flybyPyramidAccentVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidAccentVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic); + _flybyPyramidWireVertexBuffer = SharpDX.Toolkit.Graphics.Buffer.Vertex.New(_legacyDevice, _flybyPyramidWireVertices, SharpDX.Direct3D11.ResourceUsage.Dynamic); // Maybe I could use this as bounding box, scaling it properly before drawing _linesCube = GeometricPrimitive.LinesCube.New(_legacyDevice, 128, 128, 128); @@ -130,5 +154,100 @@ RenderingDrawingRoom CacheRoom(Room room) SectorTextureGet = sectorTextures.Get }); } + + private static SolidVertex[] BuildFlybyPyramidSolidVertices() + { + var vertices = new List(); + var apex = Vector3.Zero; + var baseCorners = GetFlybyPyramidBaseCorners(); + + for (int i = 0; i < baseCorners.Length; i++) + AddFlybyPyramidTriangle(vertices, apex, baseCorners[i], baseCorners[(i + 1) % baseCorners.Length]); + + AddFlybyPyramidTriangle(vertices, baseCorners[0], baseCorners[1], baseCorners[2]); + AddFlybyPyramidTriangle(vertices, baseCorners[0], baseCorners[2], baseCorners[3]); + return vertices.ToArray(); + } + + private static SolidVertex[] BuildFlybyPyramidLineVertices(bool includeOutline) + { + var vertices = new List(); + var perimeterPoints = BuildFlybyPyramidPerimeterPoints(); + + foreach (var point in perimeterPoints) + AddFlybyPyramidLine(vertices, Vector3.Zero, point); + + if (includeOutline) + for (int i = 0; i < perimeterPoints.Count; i++) + AddFlybyPyramidLine(vertices, perimeterPoints[i], perimeterPoints[(i + 1) % perimeterPoints.Count]); + + AddFlybyPyramidTopStripe(vertices); + return vertices.ToArray(); + } + + private static Vector3[] GetFlybyPyramidBaseCorners() + { + return new[] + { + CreateFlybyPyramidBasePoint(-1.0f, 1.0f), + CreateFlybyPyramidBasePoint(1.0f, 1.0f), + CreateFlybyPyramidBasePoint(1.0f, -1.0f), + CreateFlybyPyramidBasePoint(-1.0f, -1.0f) + }; + } + + private static List BuildFlybyPyramidPerimeterPoints() + { + var points = new List(_flybyPyramidPerimeterSegments * 4); + + AddFlybyPyramidEdgePoints(points, new Vector2(-1.0f, 1.0f), new Vector2(1.0f, 1.0f)); + AddFlybyPyramidEdgePoints(points, new Vector2(1.0f, 1.0f), new Vector2(1.0f, -1.0f)); + AddFlybyPyramidEdgePoints(points, new Vector2(1.0f, -1.0f), new Vector2(-1.0f, -1.0f)); + AddFlybyPyramidEdgePoints(points, new Vector2(-1.0f, -1.0f), new Vector2(-1.0f, 1.0f)); + + return points; + } + + private static void AddFlybyPyramidEdgePoints(List points, Vector2 start, Vector2 end) + { + for (int i = 0; i < _flybyPyramidPerimeterSegments; i++) + { + float interpolation = i / (float)_flybyPyramidPerimeterSegments; + var point = Vector2.Lerp(start, end, interpolation); + points.Add(CreateFlybyPyramidBasePoint(point.X, point.Y)); + } + } + + private static void AddFlybyPyramidTopStripe(List vertices) + { + int segmentCount = Math.Max(_flybyPyramidStripeLineCount - 1, 1); + + for (int i = 0; i < _flybyPyramidStripeLineCount; i++) + { + float interpolation = i / (float)segmentCount; + float depth = _flybyPyramidStripeStart + ((_flybyPyramidStripeEnd - _flybyPyramidStripeStart) * interpolation); + AddFlybyPyramidLine(vertices, + new Vector3(-_flybyPyramidNormalizedHalfWidth * depth, _flybyPyramidNormalizedHalfHeight * depth, _flybyPyramidNormalizedLength * depth), + new Vector3(_flybyPyramidNormalizedHalfWidth * depth, _flybyPyramidNormalizedHalfHeight * depth, _flybyPyramidNormalizedLength * depth)); + } + } + + private static Vector3 CreateFlybyPyramidBasePoint(float x, float y) + { + return new Vector3(x * _flybyPyramidNormalizedHalfWidth, y * _flybyPyramidNormalizedHalfHeight, _flybyPyramidNormalizedLength); + } + + private static void AddFlybyPyramidTriangle(List vertices, Vector3 p0, Vector3 p1, Vector3 p2) + { + vertices.Add(new SolidVertex(p0)); + vertices.Add(new SolidVertex(p1)); + vertices.Add(new SolidVertex(p2)); + } + + private static void AddFlybyPyramidLine(List vertices, Vector3 start, Vector3 end) + { + vertices.Add(new SolidVertex(start)); + vertices.Add(new SolidVertex(end)); + } } }