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();
|
||||
DrawGridToggle();
|
||||
DrawViewportToolbar();
|
||||
DrawDebugControls();
|
||||
|
||||
var imageSize = ImGui.GetContentRegionAvail();
|
||||
if (imageSize.X < 32 || imageSize.Y < 32)
|
||||
@@ -56,6 +57,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
||||
new Vector2(1, 0));
|
||||
|
||||
var viewportHovered = ImGui.IsItemHovered();
|
||||
var viewportFocused = ImGui.IsWindowFocused(ImGuiFocusedFlags.RootAndChildWindows);
|
||||
var imageMin = ImGui.GetItemRectMin();
|
||||
|
||||
DrawViewportHelpOverlay();
|
||||
@@ -64,6 +66,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
||||
_scene,
|
||||
_camera,
|
||||
viewportHovered,
|
||||
viewportFocused,
|
||||
imageMin,
|
||||
imageSize);
|
||||
|
||||
@@ -79,15 +82,69 @@ public sealed class ViewportPanel : IImGuiPanel
|
||||
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;
|
||||
if (grid == null)
|
||||
return;
|
||||
if (grid != null)
|
||||
{
|
||||
var isVisible = grid.IsVisible;
|
||||
if (ImGui.Checkbox("Grid", ref isVisible))
|
||||
grid.IsVisible = isVisible;
|
||||
}
|
||||
|
||||
var isVisible = grid.IsVisible;
|
||||
if (ImGui.Checkbox("Show grid", ref 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()
|
||||
@@ -97,7 +154,12 @@ public sealed class ViewportPanel : IImGuiPanel
|
||||
var imageMin = ImGui.GetItemRectMin();
|
||||
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 padding = new Vector2(8.0f, 6.0f);
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class GpuMesh
|
||||
PrimitiveType = primitiveType;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
public unsafe void Draw()
|
||||
{
|
||||
_gl.BindVertexArray(VertexArrayObject);
|
||||
_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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
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(
|
||||
GL gl,
|
||||
float[] vertices,
|
||||
|
||||
@@ -25,7 +25,7 @@ public sealed class MsaaFramebuffer
|
||||
_samples = samples;
|
||||
}
|
||||
|
||||
public void EnsureSize(int width, int height)
|
||||
public unsafe void EnsureSize(int width, int height)
|
||||
{
|
||||
width = Math.Max(1, width);
|
||||
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 const float FieldOfViewDegrees = 60.0f;
|
||||
|
||||
public float YawDegrees { get; set; } = 35.0f;
|
||||
public float PitchDegrees { get; set; } = 25.0f;
|
||||
public float Distance { get; set; } = 4.0f;
|
||||
public Vector3 Target { get; set; } = Vector3.Zero;
|
||||
|
||||
public Matrix4x4 GetSceneRotationMatrix()
|
||||
{
|
||||
return Matrix4x4.CreateRotationY(ToRadians(YawDegrees)) *
|
||||
Matrix4x4.CreateRotationX(ToRadians(PitchDegrees));
|
||||
// Kept as an explicit hook for future scene-wide transforms.
|
||||
// Camera orbit is handled by GetViewMatrix().
|
||||
return Matrix4x4.Identity;
|
||||
}
|
||||
|
||||
public Matrix4x4 GetViewMatrix()
|
||||
{
|
||||
return Matrix4x4.CreateLookAt(
|
||||
new Vector3(0.0f, 0.0f, Distance),
|
||||
Vector3.Zero,
|
||||
Vector3.UnitY);
|
||||
return Matrix4x4.CreateLookAt(GetEyePosition(), Target, Vector3.UnitY);
|
||||
}
|
||||
|
||||
public Matrix4x4 GetProjectionMatrix(float aspectRatio)
|
||||
{
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(
|
||||
ToRadians(60.0f),
|
||||
ToRadians(FieldOfViewDegrees),
|
||||
aspectRatio,
|
||||
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)
|
||||
|
||||
@@ -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 bool _isRotatingViewport;
|
||||
private bool _isPanningViewport;
|
||||
|
||||
public void Handle(
|
||||
ViewportScene scene,
|
||||
ViewportCamera camera,
|
||||
bool viewportHovered,
|
||||
bool viewportFocused,
|
||||
Vector2 imageMin,
|
||||
Vector2 imageSize)
|
||||
{
|
||||
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;
|
||||
|
||||
if (viewportHovered && wantsPanWithRight)
|
||||
_isPanningViewport = true;
|
||||
|
||||
if (!ImGui.IsMouseDown(ImGuiMouseButton.Middle))
|
||||
_isRotatingViewport = false;
|
||||
|
||||
if (!ImGui.IsMouseDown(ImGuiMouseButton.Right) && !(io.KeyAlt && ImGui.IsMouseDown(ImGuiMouseButton.Middle)))
|
||||
_isPanningViewport = false;
|
||||
|
||||
if (_isRotatingViewport)
|
||||
{
|
||||
camera.YawDegrees += io.MouseDelta.X * MouseRotationSpeed;
|
||||
camera.YawDegrees += -io.MouseDelta.X * MouseRotationSpeed;
|
||||
camera.PitchDegrees += io.MouseDelta.Y * MouseRotationSpeed;
|
||||
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)
|
||||
{
|
||||
camera.Distance -= io.MouseWheel * MouseZoomSpeed;
|
||||
camera.Distance = Math.Clamp(camera.Distance, 1.5f, 40.0f);
|
||||
camera.Distance -= io.MouseWheel * MouseZoomSpeed * MathF.Max(1.0f, camera.Distance * 0.15f);
|
||||
camera.Distance = Math.Clamp(camera.Distance, 0.25f, 400.0f);
|
||||
}
|
||||
|
||||
if (viewportHovered && ImGui.IsMouseClicked(ImGuiMouseButton.Left))
|
||||
@@ -49,5 +65,27 @@ public sealed class ViewportInputController
|
||||
localMouse,
|
||||
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 NResUI.Rendering.Viewport.Meshes;
|
||||
using NResUI.Rendering.Viewport.OpenGL;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.Windowing;
|
||||
@@ -14,6 +15,9 @@ public sealed class ViewportRenderer
|
||||
private ShaderProgram? _meshShader;
|
||||
private ShaderProgram? _outlineShader;
|
||||
|
||||
private GpuMesh? _unitWireBoxMesh;
|
||||
private GpuMesh? _axesMesh;
|
||||
|
||||
private int _meshMvpLocation;
|
||||
private int _outlineMvpLocation;
|
||||
private int _outlineColorLocation;
|
||||
@@ -55,7 +59,9 @@ public sealed class ViewportRenderer
|
||||
|
||||
DrawGrid(scene, sceneRotation, view, projection);
|
||||
DrawScene(scene, sceneRotation, view, projection);
|
||||
DrawDebugOverlays(scene, sceneRotation, view, projection);
|
||||
|
||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
_framebuffer.Resolve();
|
||||
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
@@ -71,7 +77,6 @@ public sealed class ViewportRenderer
|
||||
return _framebuffer.ColorTexture;
|
||||
}
|
||||
|
||||
|
||||
private void DrawGrid(
|
||||
ViewportScene scene,
|
||||
Matrix4x4 sceneRotation,
|
||||
@@ -90,6 +95,7 @@ public sealed class ViewportRenderer
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
|
||||
_meshShader.Use();
|
||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||
@@ -109,6 +115,9 @@ public sealed class ViewportRenderer
|
||||
|
||||
_gl.Disable(EnableCap.StencilTest);
|
||||
_meshShader.Use();
|
||||
_gl.PolygonMode(
|
||||
TriangleFace.FrontAndBack,
|
||||
scene.Debug.Wireframe ? PolygonMode.Line : PolygonMode.Fill);
|
||||
|
||||
foreach (var piece in scene.Pieces)
|
||||
{
|
||||
@@ -120,7 +129,10 @@ public sealed class ViewportRenderer
|
||||
|
||||
var selectedPiece = scene.SelectedPiece;
|
||||
if (selectedPiece == null)
|
||||
{
|
||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
return;
|
||||
}
|
||||
|
||||
_gl.Enable(EnableCap.StencilTest);
|
||||
_gl.StencilMask(0xFF);
|
||||
@@ -130,6 +142,7 @@ public sealed class ViewportRenderer
|
||||
_meshShader.Use();
|
||||
DrawPiece(selectedPiece, sceneRotation, view, projection);
|
||||
|
||||
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||
_gl.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
|
||||
_gl.StencilMask(0x00);
|
||||
_gl.Disable(EnableCap.DepthTest);
|
||||
@@ -141,21 +154,64 @@ public sealed class ViewportRenderer
|
||||
_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(
|
||||
ViewportPiece piece,
|
||||
Matrix4x4 sceneRotation,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection)
|
||||
{
|
||||
if (_meshShader == null)
|
||||
throw new InvalidOperationException("Viewport mesh shader is not initialized.");
|
||||
|
||||
var model = piece.LocalTransform * sceneRotation;
|
||||
var mvp = model * view * projection;
|
||||
|
||||
_meshShader.Use();
|
||||
_meshShader.SetMatrix4(_meshMvpLocation, mvp);
|
||||
piece.Mesh.Draw();
|
||||
DrawMesh(piece.Mesh, model, view, projection);
|
||||
}
|
||||
|
||||
private void DrawPieceOutline(
|
||||
@@ -179,12 +235,38 @@ public sealed class ViewportRenderer
|
||||
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()
|
||||
{
|
||||
if (_initialized)
|
||||
return;
|
||||
|
||||
CreateShaders();
|
||||
_unitWireBoxMesh = PrimitiveMeshes.CreateUnitWireBox(_gl);
|
||||
_axesMesh = PrimitiveMeshes.CreateAxes(_gl);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Numerics;
|
||||
using NResUI.Rendering.Viewport.Meshes;
|
||||
|
||||
namespace NResUI.Rendering.Viewport;
|
||||
@@ -10,6 +11,8 @@ public sealed class ViewportScene
|
||||
|
||||
public ViewportGrid? Grid { get; set; }
|
||||
|
||||
public ViewportDebugSettings Debug { get; } = new();
|
||||
|
||||
public int SelectedPieceId { get; set; } = -1;
|
||||
|
||||
public ViewportPiece? SelectedPiece
|
||||
@@ -39,6 +42,43 @@ public sealed class ViewportScene
|
||||
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)
|
||||
{
|
||||
var scene = new ViewportScene
|
||||
@@ -50,4 +90,36 @@ public sealed class ViewportScene
|
||||
|
||||
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;
|
||||
|
||||
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 ndcX = (2.0f * localMouse.X / viewportSize.X) - 1.0f;
|
||||
var ndcY = 1.0f - (2.0f * localMouse.Y / viewportSize.Y);
|
||||
|
||||
var rayOriginWorld = new Vector3(0.0f, 0.0f, camera.Distance);
|
||||
var rayDirectionWorld = Vector3.Normalize(new Vector3(
|
||||
ndcX * aspect * tanHalfFov,
|
||||
ndcY * tanHalfFov,
|
||||
-1.0f));
|
||||
|
||||
var sceneRotation = camera.GetSceneRotationMatrix();
|
||||
var rayOriginWorld = camera.GetEyePosition();
|
||||
var rayDirectionWorld = Vector3.Normalize(
|
||||
camera.GetForwardDirection() +
|
||||
camera.GetRightDirection() * (ndcX * aspect * tanHalfFov) +
|
||||
camera.GetUpDirection() * (ndcY * tanHalfFov));
|
||||
|
||||
var bestPieceId = -1;
|
||||
var bestDistance = float.PositiveInfinity;
|
||||
|
||||
foreach (var piece in scene.Pieces)
|
||||
{
|
||||
var model = piece.LocalTransform * sceneRotation;
|
||||
|
||||
if (!Matrix4x4.Invert(model, out var inverseModel))
|
||||
if (!Matrix4x4.Invert(piece.LocalTransform, out var inverseModel))
|
||||
continue;
|
||||
|
||||
var rayOriginLocal = Vector3.Transform(rayOriginWorld, inverseModel);
|
||||
|
||||
Reference in New Issue
Block a user