mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
qol
This commit is contained in:
@@ -33,7 +33,8 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
}
|
}
|
||||||
|
|
||||||
DrawSelectionStatus();
|
DrawSelectionStatus();
|
||||||
DrawGridToggle();
|
DrawViewportToolbar();
|
||||||
|
DrawDebugControls();
|
||||||
|
|
||||||
var imageSize = ImGui.GetContentRegionAvail();
|
var imageSize = ImGui.GetContentRegionAvail();
|
||||||
if (imageSize.X < 32 || imageSize.Y < 32)
|
if (imageSize.X < 32 || imageSize.Y < 32)
|
||||||
@@ -56,6 +57,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
new Vector2(1, 0));
|
new Vector2(1, 0));
|
||||||
|
|
||||||
var viewportHovered = ImGui.IsItemHovered();
|
var viewportHovered = ImGui.IsItemHovered();
|
||||||
|
var viewportFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
|
||||||
var imageMin = ImGui.GetItemRectMin();
|
var imageMin = ImGui.GetItemRectMin();
|
||||||
|
|
||||||
DrawViewportHelpOverlay();
|
DrawViewportHelpOverlay();
|
||||||
@@ -64,6 +66,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
_scene,
|
_scene,
|
||||||
_camera,
|
_camera,
|
||||||
viewportHovered,
|
viewportHovered,
|
||||||
|
viewportFocused,
|
||||||
imageMin,
|
imageMin,
|
||||||
imageSize);
|
imageSize);
|
||||||
|
|
||||||
@@ -79,17 +82,71 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
ImGui.TextDisabled("Selected: none");
|
ImGui.TextDisabled("Selected: none");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawGridToggle()
|
private void DrawViewportToolbar()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Reset view"))
|
||||||
|
_camera.Reset();
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Frame selected"))
|
||||||
|
{
|
||||||
|
if (_scene.TryGetSelectedPieceWorldBounds(out var selectedBounds))
|
||||||
|
_camera.FrameBounds(selectedBounds.Min, selectedBounds.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
if (ImGui.Button("Frame all"))
|
||||||
|
{
|
||||||
|
if (_scene.TryGetSceneWorldBounds(out var sceneBounds))
|
||||||
|
_camera.FrameBounds(sceneBounds.Min, sceneBounds.Max);
|
||||||
|
else
|
||||||
|
_camera.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDebugControls()
|
||||||
{
|
{
|
||||||
var grid = _scene.Grid;
|
var grid = _scene.Grid;
|
||||||
if (grid == null)
|
if (grid != null)
|
||||||
return;
|
{
|
||||||
|
|
||||||
var isVisible = grid.IsVisible;
|
var isVisible = grid.IsVisible;
|
||||||
if (ImGui.Checkbox("Show grid", ref isVisible))
|
if (ImGui.Checkbox("Grid", ref isVisible))
|
||||||
grid.IsVisible = isVisible;
|
grid.IsVisible = isVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var showOriginAxes = _scene.Debug.ShowOriginAxes;
|
||||||
|
if (ImGui.Checkbox("Origin axes", ref showOriginAxes))
|
||||||
|
_scene.Debug.ShowOriginAxes = showOriginAxes;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var showPieceOrigins = _scene.Debug.ShowPieceOrigins;
|
||||||
|
if (ImGui.Checkbox("Piece axes", ref showPieceOrigins))
|
||||||
|
_scene.Debug.ShowPieceOrigins = showPieceOrigins;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var showSelectedBounds = _scene.Debug.ShowSelectedBounds;
|
||||||
|
if (ImGui.Checkbox("Selected bounds", ref showSelectedBounds))
|
||||||
|
_scene.Debug.ShowSelectedBounds = showSelectedBounds;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var showSceneBounds = _scene.Debug.ShowSceneBounds;
|
||||||
|
if (ImGui.Checkbox("Scene bounds", ref showSceneBounds))
|
||||||
|
_scene.Debug.ShowSceneBounds = showSceneBounds;
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
|
||||||
|
var wireframe = _scene.Debug.Wireframe;
|
||||||
|
if (ImGui.Checkbox("Wireframe", ref wireframe))
|
||||||
|
_scene.Debug.Wireframe = wireframe;
|
||||||
|
}
|
||||||
|
|
||||||
private static void DrawViewportHelpOverlay()
|
private static void DrawViewportHelpOverlay()
|
||||||
{
|
{
|
||||||
var drawList = ImGui.GetWindowDrawList();
|
var drawList = ImGui.GetWindowDrawList();
|
||||||
@@ -97,7 +154,12 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
var imageMin = ImGui.GetItemRectMin();
|
var imageMin = ImGui.GetItemRectMin();
|
||||||
var imageMax = ImGui.GetItemRectMax();
|
var imageMax = ImGui.GetItemRectMax();
|
||||||
|
|
||||||
const string helpText = "LMB click: select / deselect\nMMB drag: rotate\nMouse wheel: zoom";
|
const string helpText =
|
||||||
|
"LMB click: select / deselect\n" +
|
||||||
|
"MMB drag: rotate\n" +
|
||||||
|
"RMB drag or Alt+MMB: pan\n" +
|
||||||
|
"Mouse wheel: zoom\n" +
|
||||||
|
"F: frame selected Home: frame all";
|
||||||
|
|
||||||
var textSize = ImGui.CalcTextSize(helpText);
|
var textSize = ImGui.CalcTextSize(helpText);
|
||||||
var padding = new Vector2(8.0f, 6.0f);
|
var padding = new Vector2(8.0f, 6.0f);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ public sealed class GpuMesh
|
|||||||
PrimitiveType = primitiveType;
|
PrimitiveType = primitiveType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw()
|
public unsafe void Draw()
|
||||||
{
|
{
|
||||||
_gl.BindVertexArray(VertexArrayObject);
|
_gl.BindVertexArray(VertexArrayObject);
|
||||||
_gl.DrawElements(PrimitiveType, IndexCount, DrawElementsType.UnsignedInt, null);
|
_gl.DrawElements(PrimitiveType, IndexCount, DrawElementsType.UnsignedInt, null);
|
||||||
|
|||||||
@@ -132,6 +132,59 @@ public static unsafe class PrimitiveMeshes
|
|||||||
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
return CreateIndexedMesh(gl, vertices.ToArray(), indices.ToArray(), PrimitiveType.Lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static GpuMesh CreateUnitWireBox(GL gl)
|
||||||
|
{
|
||||||
|
float[] vertices =
|
||||||
|
{
|
||||||
|
-1, -1, -1, 1.0f, 0.82f, 0.15f,
|
||||||
|
1, -1, -1, 1.0f, 0.82f, 0.15f,
|
||||||
|
1, 1, -1, 1.0f, 0.82f, 0.15f,
|
||||||
|
-1, 1, -1, 1.0f, 0.82f, 0.15f,
|
||||||
|
-1, -1, 1, 1.0f, 0.82f, 0.15f,
|
||||||
|
1, -1, 1, 1.0f, 0.82f, 0.15f,
|
||||||
|
1, 1, 1, 1.0f, 0.82f, 0.15f,
|
||||||
|
-1, 1, 1, 1.0f, 0.82f, 0.15f,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint[] indices =
|
||||||
|
{
|
||||||
|
0, 1, 1, 2, 2, 3, 3, 0,
|
||||||
|
4, 5, 5, 6, 6, 7, 7, 4,
|
||||||
|
0, 4, 1, 5, 2, 6, 3, 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateIndexedMesh(gl, vertices, indices, PrimitiveType.Lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GpuMesh CreateAxes(GL gl, float length = 1.0f)
|
||||||
|
{
|
||||||
|
if (length <= 0.0f)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(length));
|
||||||
|
|
||||||
|
float[] vertices =
|
||||||
|
{
|
||||||
|
0, 0, 0, 1.0f, 0.25f, 0.25f,
|
||||||
|
length, 0, 0, 1.0f, 0.25f, 0.25f,
|
||||||
|
|
||||||
|
0, 0, 0, 0.25f, 1.0f, 0.25f,
|
||||||
|
0, length, 0, 0.25f, 1.0f, 0.25f,
|
||||||
|
|
||||||
|
0, 0, 0, 0.25f, 0.45f, 1.0f,
|
||||||
|
0, 0, length, 0.25f, 0.45f, 1.0f,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint[] indices =
|
||||||
|
{
|
||||||
|
0, 1,
|
||||||
|
2, 3,
|
||||||
|
4, 5,
|
||||||
|
};
|
||||||
|
|
||||||
|
return CreateIndexedMesh(gl, vertices, indices, PrimitiveType.Lines);
|
||||||
|
}
|
||||||
|
|
||||||
private static GpuMesh CreateIndexedMesh(
|
private static GpuMesh CreateIndexedMesh(
|
||||||
GL gl,
|
GL gl,
|
||||||
float[] vertices,
|
float[] vertices,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public sealed class MsaaFramebuffer
|
|||||||
_samples = samples;
|
_samples = samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnsureSize(int width, int height)
|
public unsafe void EnsureSize(int width, int height)
|
||||||
{
|
{
|
||||||
width = Math.Max(1, width);
|
width = Math.Max(1, width);
|
||||||
height = Math.Max(1, height);
|
height = Math.Max(1, height);
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
|
public readonly struct ViewportBounds
|
||||||
|
{
|
||||||
|
public Vector3 Min { get; }
|
||||||
|
public Vector3 Max { get; }
|
||||||
|
|
||||||
|
public ViewportBounds(Vector3 min, Vector3 max)
|
||||||
|
{
|
||||||
|
Min = min;
|
||||||
|
Max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 Center => (Min + Max) * 0.5f;
|
||||||
|
public Vector3 Size => Max - Min;
|
||||||
|
}
|
||||||
@@ -4,31 +4,104 @@ namespace NResUI.Rendering.Viewport;
|
|||||||
|
|
||||||
public sealed class ViewportCamera
|
public sealed class ViewportCamera
|
||||||
{
|
{
|
||||||
|
public const float FieldOfViewDegrees = 60.0f;
|
||||||
|
|
||||||
public float YawDegrees { get; set; } = 35.0f;
|
public float YawDegrees { get; set; } = 35.0f;
|
||||||
public float PitchDegrees { get; set; } = 25.0f;
|
public float PitchDegrees { get; set; } = 25.0f;
|
||||||
public float Distance { get; set; } = 4.0f;
|
public float Distance { get; set; } = 4.0f;
|
||||||
|
public Vector3 Target { get; set; } = Vector3.Zero;
|
||||||
|
|
||||||
public Matrix4x4 GetSceneRotationMatrix()
|
public Matrix4x4 GetSceneRotationMatrix()
|
||||||
{
|
{
|
||||||
return Matrix4x4.CreateRotationY(ToRadians(YawDegrees)) *
|
// Kept as an explicit hook for future scene-wide transforms.
|
||||||
Matrix4x4.CreateRotationX(ToRadians(PitchDegrees));
|
// Camera orbit is handled by GetViewMatrix().
|
||||||
|
return Matrix4x4.Identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix4x4 GetViewMatrix()
|
public Matrix4x4 GetViewMatrix()
|
||||||
{
|
{
|
||||||
return Matrix4x4.CreateLookAt(
|
return Matrix4x4.CreateLookAt(GetEyePosition(), Target, Vector3.UnitY);
|
||||||
new Vector3(0.0f, 0.0f, Distance),
|
|
||||||
Vector3.Zero,
|
|
||||||
Vector3.UnitY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Matrix4x4 GetProjectionMatrix(float aspectRatio)
|
public Matrix4x4 GetProjectionMatrix(float aspectRatio)
|
||||||
{
|
{
|
||||||
return Matrix4x4.CreatePerspectiveFieldOfView(
|
return Matrix4x4.CreatePerspectiveFieldOfView(
|
||||||
ToRadians(60.0f),
|
ToRadians(FieldOfViewDegrees),
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
0.01f,
|
0.01f,
|
||||||
100.0f);
|
1000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetEyePosition()
|
||||||
|
{
|
||||||
|
return Target + GetOrbitDirection() * Distance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetForwardDirection()
|
||||||
|
{
|
||||||
|
return Vector3.Normalize(Target - GetEyePosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetRightDirection()
|
||||||
|
{
|
||||||
|
var forward = GetForwardDirection();
|
||||||
|
var right = Vector3.Cross(forward, Vector3.UnitY);
|
||||||
|
|
||||||
|
if (right.LengthSquared() < 1e-8f)
|
||||||
|
return Vector3.UnitX;
|
||||||
|
|
||||||
|
return Vector3.Normalize(right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetUpDirection()
|
||||||
|
{
|
||||||
|
var right = GetRightDirection();
|
||||||
|
var forward = GetForwardDirection();
|
||||||
|
return Vector3.Normalize(Vector3.Cross(right, forward));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Pan(Vector2 mouseDelta, Vector2 viewportSize)
|
||||||
|
{
|
||||||
|
if (viewportSize.Y <= 1.0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var worldUnitsPerPixel = 2.0f * Distance * MathF.Tan(ToRadians(FieldOfViewDegrees) * 0.5f) / viewportSize.Y;
|
||||||
|
var right = GetRightDirection();
|
||||||
|
var up = GetUpDirection();
|
||||||
|
|
||||||
|
Target -= right * (mouseDelta.X * worldUnitsPerPixel);
|
||||||
|
Target += up * (mouseDelta.Y * worldUnitsPerPixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
YawDegrees = 35.0f;
|
||||||
|
PitchDegrees = 25.0f;
|
||||||
|
Distance = 4.0f;
|
||||||
|
Target = Vector3.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FrameBounds(Vector3 boundsMin, Vector3 boundsMax)
|
||||||
|
{
|
||||||
|
var center = (boundsMin + boundsMax) * 0.5f;
|
||||||
|
var extents = (boundsMax - boundsMin) * 0.5f;
|
||||||
|
var radius = MathF.Max(extents.Length(), 0.25f);
|
||||||
|
|
||||||
|
Target = center;
|
||||||
|
Distance = Math.Clamp(radius * 2.8f, 1.5f, 200.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetOrbitDirection()
|
||||||
|
{
|
||||||
|
var yaw = ToRadians(YawDegrees);
|
||||||
|
var pitch = ToRadians(PitchDegrees);
|
||||||
|
|
||||||
|
var cosPitch = MathF.Cos(pitch);
|
||||||
|
|
||||||
|
return Vector3.Normalize(new Vector3(
|
||||||
|
MathF.Sin(yaw) * cosPitch,
|
||||||
|
MathF.Sin(pitch),
|
||||||
|
MathF.Cos(yaw) * cosPitch));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static float ToRadians(float degrees)
|
public static float ToRadians(float degrees)
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
namespace NResUI.Rendering.Viewport;
|
||||||
|
|
||||||
|
public sealed class ViewportDebugSettings
|
||||||
|
{
|
||||||
|
public bool ShowOriginAxes { get; set; } = true;
|
||||||
|
public bool ShowPieceOrigins { get; set; } = true;
|
||||||
|
public bool ShowSelectedBounds { get; set; } = true;
|
||||||
|
public bool ShowSceneBounds { get; set; }
|
||||||
|
public bool Wireframe { get; set; }
|
||||||
|
}
|
||||||
@@ -9,33 +9,49 @@ public sealed class ViewportInputController
|
|||||||
private const float MouseZoomSpeed = 0.35f;
|
private const float MouseZoomSpeed = 0.35f;
|
||||||
|
|
||||||
private bool _isRotatingViewport;
|
private bool _isRotatingViewport;
|
||||||
|
private bool _isPanningViewport;
|
||||||
|
|
||||||
public void Handle(
|
public void Handle(
|
||||||
ViewportScene scene,
|
ViewportScene scene,
|
||||||
ViewportCamera camera,
|
ViewportCamera camera,
|
||||||
bool viewportHovered,
|
bool viewportHovered,
|
||||||
|
bool viewportFocused,
|
||||||
Vector2 imageMin,
|
Vector2 imageMin,
|
||||||
Vector2 imageSize)
|
Vector2 imageSize)
|
||||||
{
|
{
|
||||||
var io = ImGui.GetIO();
|
var io = ImGui.GetIO();
|
||||||
|
|
||||||
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
|
var wantsPanWithMiddle = io.KeyAlt && ImGui.IsMouseClicked(ImGuiMouseButton.Middle);
|
||||||
|
var wantsPanWithRight = ImGui.IsMouseClicked(ImGuiMouseButton.Right);
|
||||||
|
|
||||||
|
if (viewportHovered && wantsPanWithMiddle)
|
||||||
|
_isPanningViewport = true;
|
||||||
|
else if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Middle))
|
||||||
_isRotatingViewport = true;
|
_isRotatingViewport = true;
|
||||||
|
|
||||||
|
if (viewportHovered && wantsPanWithRight)
|
||||||
|
_isPanningViewport = true;
|
||||||
|
|
||||||
if (!ImGui.IsMouseDown(ImGuiMouseButton.Middle))
|
if (!ImGui.IsMouseDown(ImGuiMouseButton.Middle))
|
||||||
_isRotatingViewport = false;
|
_isRotatingViewport = false;
|
||||||
|
|
||||||
|
if (!ImGui.IsMouseDown(ImGuiMouseButton.Right) && !(io.KeyAlt && ImGui.IsMouseDown(ImGuiMouseButton.Middle)))
|
||||||
|
_isPanningViewport = false;
|
||||||
|
|
||||||
if (_isRotatingViewport)
|
if (_isRotatingViewport)
|
||||||
{
|
{
|
||||||
camera.YawDegrees += io.MouseDelta.X * MouseRotationSpeed;
|
camera.YawDegrees += -io.MouseDelta.X * MouseRotationSpeed;
|
||||||
camera.PitchDegrees += io.MouseDelta.Y * MouseRotationSpeed;
|
camera.PitchDegrees += io.MouseDelta.Y * MouseRotationSpeed;
|
||||||
camera.PitchDegrees = Math.Clamp(camera.PitchDegrees, -89.0f, 89.0f);
|
camera.PitchDegrees = Math.Clamp(camera.PitchDegrees, -89.0f, 89.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_isPanningViewport)
|
||||||
|
camera.Pan(io.MouseDelta, imageSize);
|
||||||
|
|
||||||
if (viewportHovered && Math.Abs(io.MouseWheel) > float.Epsilon)
|
if (viewportHovered && Math.Abs(io.MouseWheel) > float.Epsilon)
|
||||||
{
|
{
|
||||||
camera.Distance -= io.MouseWheel * MouseZoomSpeed;
|
camera.Distance -= io.MouseWheel * MouseZoomSpeed * MathF.Max(1.0f, camera.Distance * 0.15f);
|
||||||
camera.Distance = Math.Clamp(camera.Distance, 1.5f, 40.0f);
|
camera.Distance = Math.Clamp(camera.Distance, 0.25f, 400.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||||
@@ -49,5 +65,27 @@ public sealed class ViewportInputController
|
|||||||
localMouse,
|
localMouse,
|
||||||
imageSize);
|
imageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (viewportFocused || viewportHovered)
|
||||||
|
HandleKeyboardShortcuts(scene, camera);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleKeyboardShortcuts(ViewportScene scene, ViewportCamera camera)
|
||||||
|
{
|
||||||
|
if (ImGui.IsKeyPressed(ImGuiKey.F))
|
||||||
|
{
|
||||||
|
if (scene.TryGetSelectedPieceWorldBounds(out var selectedBounds))
|
||||||
|
camera.FrameBounds(selectedBounds.Min, selectedBounds.Max);
|
||||||
|
else if (scene.TryGetSceneWorldBounds(out var sceneBounds))
|
||||||
|
camera.FrameBounds(sceneBounds.Min, sceneBounds.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui.IsKeyPressed(ImGuiKey.Home))
|
||||||
|
{
|
||||||
|
if (scene.TryGetSceneWorldBounds(out var sceneBounds))
|
||||||
|
camera.FrameBounds(sceneBounds.Min, sceneBounds.Max);
|
||||||
|
else
|
||||||
|
camera.Reset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using NResUI.Rendering.Viewport.Meshes;
|
||||||
using NResUI.Rendering.Viewport.OpenGL;
|
using NResUI.Rendering.Viewport.OpenGL;
|
||||||
using Silk.NET.OpenGL;
|
using Silk.NET.OpenGL;
|
||||||
using Silk.NET.Windowing;
|
using Silk.NET.Windowing;
|
||||||
@@ -14,6 +15,9 @@ public sealed class ViewportRenderer
|
|||||||
private ShaderProgram? _meshShader;
|
private ShaderProgram? _meshShader;
|
||||||
private ShaderProgram? _outlineShader;
|
private ShaderProgram? _outlineShader;
|
||||||
|
|
||||||
|
private GpuMesh? _unitWireBoxMesh;
|
||||||
|
private GpuMesh? _axesMesh;
|
||||||
|
|
||||||
private int _meshMvpLocation;
|
private int _meshMvpLocation;
|
||||||
private int _outlineMvpLocation;
|
private int _outlineMvpLocation;
|
||||||
private int _outlineColorLocation;
|
private int _outlineColorLocation;
|
||||||
@@ -55,7 +59,9 @@ public sealed class ViewportRenderer
|
|||||||
|
|
||||||
DrawGrid(scene, sceneRotation, view, projection);
|
DrawGrid(scene, sceneRotation, view, projection);
|
||||||
DrawScene(scene, sceneRotation, view, projection);
|
DrawScene(scene, sceneRotation, view, projection);
|
||||||
|
DrawDebugOverlays(scene, sceneRotation, view, projection);
|
||||||
|
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
_framebuffer.Resolve();
|
_framebuffer.Resolve();
|
||||||
|
|
||||||
_gl.Disable(EnableCap.CullFace);
|
_gl.Disable(EnableCap.CullFace);
|
||||||
@@ -71,7 +77,6 @@ public sealed class ViewportRenderer
|
|||||||
return _framebuffer.ColorTexture;
|
return _framebuffer.ColorTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawGrid(
|
private void DrawGrid(
|
||||||
ViewportScene scene,
|
ViewportScene scene,
|
||||||
Matrix4x4 sceneRotation,
|
Matrix4x4 sceneRotation,
|
||||||
@@ -90,6 +95,7 @@ public sealed class ViewportRenderer
|
|||||||
|
|
||||||
_gl.Disable(EnableCap.StencilTest);
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
_gl.Disable(EnableCap.CullFace);
|
_gl.Disable(EnableCap.CullFace);
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
|
|
||||||
_meshShader.Use();
|
_meshShader.Use();
|
||||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||||
@@ -109,6 +115,9 @@ public sealed class ViewportRenderer
|
|||||||
|
|
||||||
_gl.Disable(EnableCap.StencilTest);
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
_meshShader.Use();
|
_meshShader.Use();
|
||||||
|
_gl.PolygonMode(
|
||||||
|
TriangleFace.FrontAndBack,
|
||||||
|
scene.Debug.Wireframe ? PolygonMode.Line : PolygonMode.Fill);
|
||||||
|
|
||||||
foreach (var piece in scene.Pieces)
|
foreach (var piece in scene.Pieces)
|
||||||
{
|
{
|
||||||
@@ -120,7 +129,10 @@ public sealed class ViewportRenderer
|
|||||||
|
|
||||||
var selectedPiece = scene.SelectedPiece;
|
var selectedPiece = scene.SelectedPiece;
|
||||||
if (selectedPiece == null)
|
if (selectedPiece == null)
|
||||||
|
{
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_gl.Enable(EnableCap.StencilTest);
|
_gl.Enable(EnableCap.StencilTest);
|
||||||
_gl.StencilMask(0xFF);
|
_gl.StencilMask(0xFF);
|
||||||
@@ -130,6 +142,7 @@ public sealed class ViewportRenderer
|
|||||||
_meshShader.Use();
|
_meshShader.Use();
|
||||||
DrawPiece(selectedPiece, sceneRotation, view, projection);
|
DrawPiece(selectedPiece, sceneRotation, view, projection);
|
||||||
|
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
_gl.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
|
_gl.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
|
||||||
_gl.StencilMask(0x00);
|
_gl.StencilMask(0x00);
|
||||||
_gl.Disable(EnableCap.DepthTest);
|
_gl.Disable(EnableCap.DepthTest);
|
||||||
@@ -141,21 +154,64 @@ public sealed class ViewportRenderer
|
|||||||
_gl.Disable(EnableCap.StencilTest);
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawDebugOverlays(
|
||||||
|
ViewportScene scene,
|
||||||
|
Matrix4x4 sceneRotation,
|
||||||
|
Matrix4x4 view,
|
||||||
|
Matrix4x4 projection)
|
||||||
|
{
|
||||||
|
if (_meshShader == null || _unitWireBoxMesh == null || _axesMesh == null)
|
||||||
|
throw new InvalidOperationException("Viewport debug resources are not initialized.");
|
||||||
|
|
||||||
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
|
_gl.Disable(EnableCap.CullFace);
|
||||||
|
_gl.Disable(EnableCap.DepthTest);
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
|
_gl.LineWidth(2.0f);
|
||||||
|
|
||||||
|
_meshShader.Use();
|
||||||
|
|
||||||
|
if (scene.Debug.ShowOriginAxes)
|
||||||
|
DrawMesh(_axesMesh, Matrix4x4.CreateScale(1.25f) * sceneRotation, view, projection);
|
||||||
|
|
||||||
|
if (scene.Debug.ShowPieceOrigins)
|
||||||
|
{
|
||||||
|
foreach (var piece in scene.Pieces)
|
||||||
|
{
|
||||||
|
var axesModel = Matrix4x4.CreateScale(0.45f) * piece.LocalTransform * sceneRotation;
|
||||||
|
DrawMesh(_axesMesh, axesModel, view, projection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scene.Debug.ShowSelectedBounds && scene.SelectedPiece != null)
|
||||||
|
{
|
||||||
|
var selectedPiece = scene.SelectedPiece;
|
||||||
|
var boundsModel = BuildLocalBoundsModel(selectedPiece.BoundsMin, selectedPiece.BoundsMax) *
|
||||||
|
selectedPiece.LocalTransform *
|
||||||
|
sceneRotation;
|
||||||
|
|
||||||
|
DrawMesh(_unitWireBoxMesh, boundsModel, view, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scene.Debug.ShowSceneBounds && scene.TryGetSceneWorldBounds(out var sceneBounds))
|
||||||
|
{
|
||||||
|
var sceneBoundsModel = BuildLocalBoundsModel(sceneBounds.Min, sceneBounds.Max) * sceneRotation;
|
||||||
|
DrawMesh(_unitWireBoxMesh, sceneBoundsModel, view, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
_gl.LineWidth(1.0f);
|
||||||
|
_gl.Enable(EnableCap.DepthTest);
|
||||||
|
_gl.Enable(EnableCap.CullFace);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPiece(
|
private void DrawPiece(
|
||||||
ViewportPiece piece,
|
ViewportPiece piece,
|
||||||
Matrix4x4 sceneRotation,
|
Matrix4x4 sceneRotation,
|
||||||
Matrix4x4 view,
|
Matrix4x4 view,
|
||||||
Matrix4x4 projection)
|
Matrix4x4 projection)
|
||||||
{
|
{
|
||||||
if (_meshShader == null)
|
|
||||||
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
|
|
||||||
|
|
||||||
var model = piece.LocalTransform * sceneRotation;
|
var model = piece.LocalTransform * sceneRotation;
|
||||||
var mvp = model * view * projection;
|
DrawMesh(piece.Mesh, model, view, projection);
|
||||||
|
|
||||||
_meshShader.Use();
|
|
||||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
|
||||||
piece.Mesh.Draw();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPieceOutline(
|
private void DrawPieceOutline(
|
||||||
@@ -179,12 +235,38 @@ public sealed class ViewportRenderer
|
|||||||
piece.Mesh.Draw();
|
piece.Mesh.Draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawMesh(
|
||||||
|
GpuMesh mesh,
|
||||||
|
Matrix4x4 model,
|
||||||
|
Matrix4x4 view,
|
||||||
|
Matrix4x4 projection)
|
||||||
|
{
|
||||||
|
if (_meshShader == null)
|
||||||
|
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
|
||||||
|
|
||||||
|
var mvp = model * view * projection;
|
||||||
|
|
||||||
|
_meshShader.Use();
|
||||||
|
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||||
|
mesh.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Matrix4x4 BuildLocalBoundsModel(Vector3 boundsMin, Vector3 boundsMax)
|
||||||
|
{
|
||||||
|
var center = (boundsMin + boundsMax) * 0.5f;
|
||||||
|
var size = boundsMax - boundsMin;
|
||||||
|
|
||||||
|
return Matrix4x4.CreateScale(size * 0.5f) * Matrix4x4.CreateTranslation(center);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (_initialized)
|
if (_initialized)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CreateShaders();
|
CreateShaders();
|
||||||
|
_unitWireBoxMesh = PrimitiveMeshes.CreateUnitWireBox(_gl);
|
||||||
|
_axesMesh = PrimitiveMeshes.CreateAxes(_gl);
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Numerics;
|
||||||
using NResUI.Rendering.Viewport.Meshes;
|
using NResUI.Rendering.Viewport.Meshes;
|
||||||
|
|
||||||
namespace NResUI.Rendering.Viewport;
|
namespace NResUI.Rendering.Viewport;
|
||||||
@@ -10,6 +11,8 @@ public sealed class ViewportScene
|
|||||||
|
|
||||||
public ViewportGrid? Grid { get; set; }
|
public ViewportGrid? Grid { get; set; }
|
||||||
|
|
||||||
|
public ViewportDebugSettings Debug { get; } = new();
|
||||||
|
|
||||||
public int SelectedPieceId { get; set; } = -1;
|
public int SelectedPieceId { get; set; } = -1;
|
||||||
|
|
||||||
public ViewportPiece? SelectedPiece
|
public ViewportPiece? SelectedPiece
|
||||||
@@ -39,6 +42,43 @@ public sealed class ViewportScene
|
|||||||
SelectedPieceId = -1;
|
SelectedPieceId = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetPieceWorldBounds(ViewportPiece piece, out ViewportBounds bounds)
|
||||||
|
{
|
||||||
|
return TryTransformBounds(piece.BoundsMin, piece.BoundsMax, piece.LocalTransform, out bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSelectedPieceWorldBounds(out ViewportBounds bounds)
|
||||||
|
{
|
||||||
|
var selectedPiece = SelectedPiece;
|
||||||
|
if (selectedPiece == null)
|
||||||
|
{
|
||||||
|
bounds = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TryGetPieceWorldBounds(selectedPiece, out bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetSceneWorldBounds(out ViewportBounds bounds)
|
||||||
|
{
|
||||||
|
var hasBounds = false;
|
||||||
|
var min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
|
||||||
|
var max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
|
||||||
|
|
||||||
|
foreach (var piece in _pieces)
|
||||||
|
{
|
||||||
|
if (!TryGetPieceWorldBounds(piece, out var pieceBounds))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
min = Vector3.Min(min, pieceBounds.Min);
|
||||||
|
max = Vector3.Max(max, pieceBounds.Max);
|
||||||
|
hasBounds = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = hasBounds ? new ViewportBounds(min, max) : default;
|
||||||
|
return hasBounds;
|
||||||
|
}
|
||||||
|
|
||||||
public static ViewportScene CreateDefaultCubeScene(GpuMesh cubeMesh, GpuMesh gridMesh)
|
public static ViewportScene CreateDefaultCubeScene(GpuMesh cubeMesh, GpuMesh gridMesh)
|
||||||
{
|
{
|
||||||
var scene = new ViewportScene
|
var scene = new ViewportScene
|
||||||
@@ -50,4 +90,36 @@ public sealed class ViewportScene
|
|||||||
|
|
||||||
return scene;
|
return scene;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryTransformBounds(
|
||||||
|
Vector3 localMin,
|
||||||
|
Vector3 localMax,
|
||||||
|
Matrix4x4 transform,
|
||||||
|
out ViewportBounds bounds)
|
||||||
|
{
|
||||||
|
var corners = new[]
|
||||||
|
{
|
||||||
|
new Vector3(localMin.X, localMin.Y, localMin.Z),
|
||||||
|
new Vector3(localMax.X, localMin.Y, localMin.Z),
|
||||||
|
new Vector3(localMin.X, localMax.Y, localMin.Z),
|
||||||
|
new Vector3(localMax.X, localMax.Y, localMin.Z),
|
||||||
|
new Vector3(localMin.X, localMin.Y, localMax.Z),
|
||||||
|
new Vector3(localMax.X, localMin.Y, localMax.Z),
|
||||||
|
new Vector3(localMin.X, localMax.Y, localMax.Z),
|
||||||
|
new Vector3(localMax.X, localMax.Y, localMax.Z),
|
||||||
|
};
|
||||||
|
|
||||||
|
var min = new Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity);
|
||||||
|
var max = new Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity);
|
||||||
|
|
||||||
|
foreach (var corner in corners)
|
||||||
|
{
|
||||||
|
var world = Vector3.Transform(corner, transform);
|
||||||
|
min = Vector3.Min(min, world);
|
||||||
|
max = Vector3.Max(max, world);
|
||||||
|
}
|
||||||
|
|
||||||
|
bounds = new ViewportBounds(min, max);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,28 +14,24 @@ public static class ViewportSelection
|
|||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
var aspect = viewportSize.X / viewportSize.Y;
|
var aspect = viewportSize.X / viewportSize.Y;
|
||||||
var fovRadians = ViewportCamera.ToRadians(60.0f);
|
var fovRadians = ViewportCamera.ToRadians(ViewportCamera.FieldOfViewDegrees);
|
||||||
var tanHalfFov = MathF.Tan(fovRadians * 0.5f);
|
var tanHalfFov = MathF.Tan(fovRadians * 0.5f);
|
||||||
|
|
||||||
var ndcX = (2.0f * localMouse.X / viewportSize.X) - 1.0f;
|
var ndcX = (2.0f * localMouse.X / viewportSize.X) - 1.0f;
|
||||||
var ndcY = 1.0f - (2.0f * localMouse.Y / viewportSize.Y);
|
var ndcY = 1.0f - (2.0f * localMouse.Y / viewportSize.Y);
|
||||||
|
|
||||||
var rayOriginWorld = new Vector3(0.0f, 0.0f, camera.Distance);
|
var rayOriginWorld = camera.GetEyePosition();
|
||||||
var rayDirectionWorld = Vector3.Normalize(new Vector3(
|
var rayDirectionWorld = Vector3.Normalize(
|
||||||
ndcX * aspect * tanHalfFov,
|
camera.GetForwardDirection() +
|
||||||
ndcY * tanHalfFov,
|
camera.GetRightDirection() * (ndcX * aspect * tanHalfFov) +
|
||||||
-1.0f));
|
camera.GetUpDirection() * (ndcY * tanHalfFov));
|
||||||
|
|
||||||
var sceneRotation = camera.GetSceneRotationMatrix();
|
|
||||||
|
|
||||||
var bestPieceId = -1;
|
var bestPieceId = -1;
|
||||||
var bestDistance = float.PositiveInfinity;
|
var bestDistance = float.PositiveInfinity;
|
||||||
|
|
||||||
foreach (var piece in scene.Pieces)
|
foreach (var piece in scene.Pieces)
|
||||||
{
|
{
|
||||||
var model = piece.LocalTransform * sceneRotation;
|
if (!Matrix4x4.Invert(piece.LocalTransform, out var inverseModel))
|
||||||
|
|
||||||
if (!Matrix4x4.Invert(model, out var inverseModel))
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var rayOriginLocal = Vector3.Transform(rayOriginWorld, inverseModel);
|
var rayOriginLocal = Vector3.Transform(rayOriginWorld, inverseModel);
|
||||||
|
|||||||
Reference in New Issue
Block a user