mirror of
https://github.com/sampletext32/ParkanPlayground.git
synced 2026-08-15 02:57:49 +04:00
gpu picking and qol
This commit is contained in:
@@ -14,6 +14,8 @@ public sealed class MeshInspectorPanel : IImGuiPanel
|
|||||||
{
|
{
|
||||||
private readonly MeshViewportViewModel _viewModel;
|
private readonly MeshViewportViewModel _viewModel;
|
||||||
private readonly HashSet<int> _expandedPieceDetails = [];
|
private readonly HashSet<int> _expandedPieceDetails = [];
|
||||||
|
private readonly HashSet<int> _pieceIdsToAutoOpen = [];
|
||||||
|
private int _lastSyncedSelectedPieceId = -1;
|
||||||
private static readonly string[] ModelStateLabels = ["Обычное", "Collapsed", "Неизвестное 2"];
|
private static readonly string[] ModelStateLabels = ["Обычное", "Collapsed", "Неизвестное 2"];
|
||||||
private static readonly string[] LodLabels = ["LOD 0", "LOD -1", "LOD -2", "LOD -3", "LOD -4"];
|
private static readonly string[] LodLabels = ["LOD 0", "LOD -1", "LOD -2", "LOD -3", "LOD -4"];
|
||||||
|
|
||||||
@@ -116,6 +118,8 @@ public sealed class MeshInspectorPanel : IImGuiPanel
|
|||||||
.GroupBy(x => x.ParentId)
|
.GroupBy(x => x.ParentId)
|
||||||
.ToDictionary(x => x.Key, x => x.OrderBy(p => p.Id).ToList());
|
.ToDictionary(x => x.Key, x => x.OrderBy(p => p.Id).ToList());
|
||||||
|
|
||||||
|
SyncTreeExpansionToSelection(document);
|
||||||
|
|
||||||
// Parent -1 приходит из 0xFFFF в 0x01 и означает корневой piece.
|
// Parent -1 приходит из 0xFFFF в 0x01 и означает корневой piece.
|
||||||
if (childrenByParent.TryGetValue(-1, out var roots))
|
if (childrenByParent.TryGetValue(-1, out var roots))
|
||||||
{
|
{
|
||||||
@@ -146,6 +150,9 @@ public sealed class MeshInspectorPanel : IImGuiPanel
|
|||||||
var childMarker = childCount > 0 ? $" | дочерних: {childCount}" : "";
|
var childMarker = childCount > 0 ? $" | дочерних: {childCount}" : "";
|
||||||
var hiddenMarker = hidden ? " | скрыта" : "";
|
var hiddenMarker = hidden ? " | скрыта" : "";
|
||||||
var label = $"{piece.Name}{childMarker}{hiddenMarker}##piece_{piece.Id}";
|
var label = $"{piece.Name}{childMarker}{hiddenMarker}##piece_{piece.Id}";
|
||||||
|
if (_pieceIdsToAutoOpen.Remove(piece.Id))
|
||||||
|
ImGui.SetNextItemOpen(true, ImGuiCond.Always);
|
||||||
|
|
||||||
var open = ImGui.TreeNodeEx(label, flags);
|
var open = ImGui.TreeNodeEx(label, flags);
|
||||||
var nodeClicked = ImGui.IsItemClicked();
|
var nodeClicked = ImGui.IsItemClicked();
|
||||||
if (nodeClicked)
|
if (nodeClicked)
|
||||||
@@ -194,6 +201,34 @@ public sealed class MeshInspectorPanel : IImGuiPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SyncTreeExpansionToSelection(MeshDocument document)
|
||||||
|
{
|
||||||
|
var selectedPieceId = _viewModel.RenderState.SelectedPieceId;
|
||||||
|
if (selectedPieceId == _lastSyncedSelectedPieceId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastSyncedSelectedPieceId = selectedPieceId;
|
||||||
|
_pieceIdsToAutoOpen.Clear();
|
||||||
|
|
||||||
|
if (selectedPieceId < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var piecesById = document.Pieces.ToDictionary(x => x.Id);
|
||||||
|
if (!piecesById.TryGetValue(selectedPieceId, out var piece))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// ImGui не раскрывает родителей выбранного leaf автоматически, поэтому раскрываем только путь до него.
|
||||||
|
var visited = new HashSet<int>();
|
||||||
|
while (piece.ParentId != -1 && piecesById.TryGetValue(piece.ParentId, out var parent))
|
||||||
|
{
|
||||||
|
if (!visited.Add(parent.Id))
|
||||||
|
break;
|
||||||
|
|
||||||
|
_pieceIdsToAutoOpen.Add(parent.Id);
|
||||||
|
piece = parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPieceDetailsToggle(MeshPieceInfo piece)
|
private void DrawPieceDetailsToggle(MeshPieceInfo piece)
|
||||||
{
|
{
|
||||||
var detailsOpen = _expandedPieceDetails.Contains(piece.Id);
|
var detailsOpen = _expandedPieceDetails.Contains(piece.Id);
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ public sealed class ViewportPanel : IImGuiPanel
|
|||||||
DrawViewportHelpOverlay();
|
DrawViewportHelpOverlay();
|
||||||
|
|
||||||
_inputController.Handle(
|
_inputController.Handle(
|
||||||
|
_renderer,
|
||||||
_scene,
|
_scene,
|
||||||
_camera,
|
_camera,
|
||||||
viewportHovered,
|
viewportHovered,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public sealed class ViewportInputController
|
|||||||
private bool _isPanningViewport;
|
private bool _isPanningViewport;
|
||||||
|
|
||||||
public void Handle(
|
public void Handle(
|
||||||
|
ViewportRenderer renderer,
|
||||||
ViewportScene scene,
|
ViewportScene scene,
|
||||||
ViewportCamera camera,
|
ViewportCamera camera,
|
||||||
bool viewportHovered,
|
bool viewportHovered,
|
||||||
@@ -59,11 +60,12 @@ public sealed class ViewportInputController
|
|||||||
var mouse = ImGui.GetMousePos();
|
var mouse = ImGui.GetMousePos();
|
||||||
var localMouse = mouse - imageMin;
|
var localMouse = mouse - imageMin;
|
||||||
|
|
||||||
scene.SelectedPieceId = ViewportSelection.PickPiece(
|
scene.SelectedPieceId = renderer.PickPiece(
|
||||||
|
Math.Max(1, (int)imageSize.X),
|
||||||
|
Math.Max(1, (int)imageSize.Y),
|
||||||
scene,
|
scene,
|
||||||
camera,
|
camera,
|
||||||
localMouse,
|
localMouse);
|
||||||
imageSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (viewportFocused || viewportHovered)
|
if (viewportFocused || viewportHovered)
|
||||||
|
|||||||
@@ -15,16 +15,25 @@ public sealed class ViewportRenderer
|
|||||||
public GL Gl => _gl;
|
public GL Gl => _gl;
|
||||||
|
|
||||||
private ShaderProgram? _meshShader;
|
private ShaderProgram? _meshShader;
|
||||||
|
private ShaderProgram? _pickingShader;
|
||||||
|
|
||||||
private GpuMesh? _unitWireBoxMesh;
|
private GpuMesh? _unitWireBoxMesh;
|
||||||
private GpuMesh? _axesMesh;
|
private GpuMesh? _axesMesh;
|
||||||
|
|
||||||
|
private uint _pickingFramebuffer;
|
||||||
|
private uint _pickingColorTexture;
|
||||||
|
private uint _pickingDepthRenderbuffer;
|
||||||
|
private int _pickingWidth;
|
||||||
|
private int _pickingHeight;
|
||||||
|
|
||||||
private int _modelLocation;
|
private int _modelLocation;
|
||||||
private int _mvpLocation;
|
private int _mvpLocation;
|
||||||
private int _lightDirectionLocation;
|
private int _lightDirectionLocation;
|
||||||
private int _useTextureLocation;
|
private int _useTextureLocation;
|
||||||
private int _texture0Location;
|
private int _texture0Location;
|
||||||
private int _colorAddLocation;
|
private int _colorAddLocation;
|
||||||
|
private int _pickingMvpLocation;
|
||||||
|
private int _pickingColorLocation;
|
||||||
|
|
||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
@@ -79,6 +88,58 @@ public sealed class ViewportRenderer
|
|||||||
return _framebuffer.ColorTexture;
|
return _framebuffer.ColorTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Делает GPU picking: рендерит pieces в служебный framebuffer уникальными цветами и читает один пиксель.
|
||||||
|
/// Так выбор совпадает с видимой геометрией и не требует CPU-копии вершин.
|
||||||
|
/// </summary>
|
||||||
|
public unsafe int PickPiece(int width, int height, ViewportScene scene, ViewportCamera camera, Vector2 localMouse)
|
||||||
|
{
|
||||||
|
EnsureInitialized();
|
||||||
|
|
||||||
|
if (_pickingShader == null || width <= 1 || height <= 1)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
var x = Math.Clamp((int)localMouse.X, 0, width - 1);
|
||||||
|
var y = Math.Clamp(height - 1 - (int)localMouse.Y, 0, height - 1);
|
||||||
|
|
||||||
|
EnsurePickingFramebuffer(width, height);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _pickingFramebuffer);
|
||||||
|
_gl.Viewport(0, 0, (uint)width, (uint)height);
|
||||||
|
_gl.Enable(EnableCap.DepthTest);
|
||||||
|
_gl.Disable(EnableCap.CullFace);
|
||||||
|
_gl.Disable(EnableCap.Blend);
|
||||||
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
|
_gl.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||||
|
_gl.Clear((uint)(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit));
|
||||||
|
|
||||||
|
var aspect = width / (float)Math.Max(1, height);
|
||||||
|
var sceneRotation = camera.GetSceneRotationMatrix();
|
||||||
|
var view = camera.GetViewMatrix();
|
||||||
|
var projection = camera.GetProjectionMatrix(aspect);
|
||||||
|
|
||||||
|
_pickingShader.Use();
|
||||||
|
foreach (var piece in scene.Pieces)
|
||||||
|
DrawPieceForPicking(piece, sceneRotation, view, projection);
|
||||||
|
|
||||||
|
Span<byte> pixel = stackalloc byte[4];
|
||||||
|
fixed (byte* pixelPtr = pixel)
|
||||||
|
{
|
||||||
|
_gl.ReadPixels(x, y, 1, 1, PixelFormat.Rgba, PixelType.UnsignedByte, pixelPtr);
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoded = pixel[0] | (pixel[1] << 8) | (pixel[2] << 16);
|
||||||
|
return encoded == 0 ? -1 : encoded - 1;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RestoreDefaultRenderTargetState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawGrid(
|
private void DrawGrid(
|
||||||
ViewportScene scene,
|
ViewportScene scene,
|
||||||
Matrix4x4 sceneRotation,
|
Matrix4x4 sceneRotation,
|
||||||
@@ -187,6 +248,27 @@ public sealed class ViewportRenderer
|
|||||||
DrawMesh(mesh, model, view, projection, colorAdd);
|
DrawMesh(mesh, model, view, projection, colorAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawPieceForPicking(
|
||||||
|
ViewportPiece piece,
|
||||||
|
Matrix4x4 sceneRotation,
|
||||||
|
Matrix4x4 view,
|
||||||
|
Matrix4x4 projection)
|
||||||
|
{
|
||||||
|
if (_pickingShader == null)
|
||||||
|
throw new InvalidOperationException("Viewport picking shader is not initialized.");
|
||||||
|
|
||||||
|
var encodedColor = EncodePickingColor(piece.Id);
|
||||||
|
var model = piece.LocalTransform * sceneRotation;
|
||||||
|
var mvp = model * view * projection;
|
||||||
|
|
||||||
|
_pickingShader.SetMatrix4(_pickingMvpLocation, mvp);
|
||||||
|
_pickingShader.SetVector4(_pickingColorLocation, encodedColor);
|
||||||
|
|
||||||
|
// Рисуем теми же VAO, что и обычный viewport. Depth buffer сам выберет ближайшую piece под курсором.
|
||||||
|
foreach (var mesh in piece.Meshes)
|
||||||
|
mesh.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawMesh(
|
private void DrawMesh(
|
||||||
GpuMesh mesh,
|
GpuMesh mesh,
|
||||||
Matrix4x4 model,
|
Matrix4x4 model,
|
||||||
@@ -231,6 +313,90 @@ public sealed class ViewportRenderer
|
|||||||
return Matrix4x4.CreateScale(size * 0.5f) * Matrix4x4.CreateTranslation(center);
|
return Matrix4x4.CreateScale(size * 0.5f) * Matrix4x4.CreateTranslation(center);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void EnsurePickingFramebuffer(int width, int height)
|
||||||
|
{
|
||||||
|
width = Math.Max(1, width);
|
||||||
|
height = Math.Max(1, height);
|
||||||
|
|
||||||
|
if (_pickingFramebuffer != 0 && _pickingWidth == width && _pickingHeight == height)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_pickingWidth = width;
|
||||||
|
_pickingHeight = height;
|
||||||
|
|
||||||
|
if (_pickingFramebuffer == 0)
|
||||||
|
{
|
||||||
|
_pickingFramebuffer = _gl.GenFramebuffer();
|
||||||
|
_pickingColorTexture = _gl.GenTexture();
|
||||||
|
_pickingDepthRenderbuffer = _gl.GenRenderbuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
_gl.BindTexture(TextureTarget.Texture2D, _pickingColorTexture);
|
||||||
|
_gl.TexImage2D(
|
||||||
|
TextureTarget.Texture2D,
|
||||||
|
0,
|
||||||
|
InternalFormat.Rgba8,
|
||||||
|
(uint)width,
|
||||||
|
(uint)height,
|
||||||
|
0,
|
||||||
|
PixelFormat.Rgba,
|
||||||
|
PixelType.UnsignedByte,
|
||||||
|
null);
|
||||||
|
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||||
|
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
|
||||||
|
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
|
||||||
|
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
|
||||||
|
|
||||||
|
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, _pickingDepthRenderbuffer);
|
||||||
|
_gl.RenderbufferStorage(RenderbufferTarget.Renderbuffer, InternalFormat.DepthComponent24, (uint)width, (uint)height);
|
||||||
|
|
||||||
|
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, _pickingFramebuffer);
|
||||||
|
_gl.FramebufferTexture2D(
|
||||||
|
FramebufferTarget.Framebuffer,
|
||||||
|
FramebufferAttachment.ColorAttachment0,
|
||||||
|
TextureTarget.Texture2D,
|
||||||
|
_pickingColorTexture,
|
||||||
|
0);
|
||||||
|
_gl.FramebufferRenderbuffer(
|
||||||
|
FramebufferTarget.Framebuffer,
|
||||||
|
FramebufferAttachment.DepthAttachment,
|
||||||
|
RenderbufferTarget.Renderbuffer,
|
||||||
|
_pickingDepthRenderbuffer);
|
||||||
|
|
||||||
|
var status = _gl.CheckFramebufferStatus(FramebufferTarget.Framebuffer);
|
||||||
|
if (status != GLEnum.FramebufferComplete)
|
||||||
|
throw new InvalidOperationException($"Viewport picking framebuffer is incomplete: {status}");
|
||||||
|
|
||||||
|
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||||
|
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||||
|
_gl.BindRenderbuffer(RenderbufferTarget.Renderbuffer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreDefaultRenderTargetState()
|
||||||
|
{
|
||||||
|
_gl.PolygonMode(TriangleFace.FrontAndBack, PolygonMode.Fill);
|
||||||
|
_gl.Disable(EnableCap.DepthTest);
|
||||||
|
_gl.Disable(EnableCap.StencilTest);
|
||||||
|
_gl.UseProgram(0);
|
||||||
|
_gl.BindVertexArray(0);
|
||||||
|
_gl.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
|
||||||
|
|
||||||
|
var framebufferSize = _window.FramebufferSize;
|
||||||
|
_gl.Viewport(0, 0, (uint)framebufferSize.X, (uint)framebufferSize.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector4 EncodePickingColor(int pieceId)
|
||||||
|
{
|
||||||
|
var encoded = pieceId + 1;
|
||||||
|
if (encoded <= 0 || encoded > 0x00FFFFFF)
|
||||||
|
return Vector4.Zero;
|
||||||
|
|
||||||
|
var r = (encoded & 0x0000FF) / 255.0f;
|
||||||
|
var g = ((encoded >> 8) & 0x0000FF) / 255.0f;
|
||||||
|
var b = ((encoded >> 16) & 0x0000FF) / 255.0f;
|
||||||
|
return new Vector4(r, g, b, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (_initialized)
|
if (_initialized)
|
||||||
@@ -304,7 +470,34 @@ public sealed class ViewportRenderer
|
|||||||
}
|
}
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
const string pickingVertexShaderSource = """
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 aPosition;
|
||||||
|
|
||||||
|
uniform mat4 uMvp;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = uMvp * vec4(aPosition, 1.0);
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const string pickingFragmentShaderSource = """
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
uniform vec4 uPickColor;
|
||||||
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
FragColor = uPickColor;
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
_meshShader = new ShaderProgram(_gl, meshVertexShaderSource, meshFragmentShaderSource, "Viewport mesh");
|
_meshShader = new ShaderProgram(_gl, meshVertexShaderSource, meshFragmentShaderSource, "Viewport mesh");
|
||||||
|
_pickingShader = new ShaderProgram(_gl, pickingVertexShaderSource, pickingFragmentShaderSource,
|
||||||
|
"Viewport picking");
|
||||||
|
|
||||||
_modelLocation = _meshShader.GetUniformLocation("uModel");
|
_modelLocation = _meshShader.GetUniformLocation("uModel");
|
||||||
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
|
_mvpLocation = _meshShader.GetUniformLocation("uMvp");
|
||||||
@@ -312,5 +505,8 @@ public sealed class ViewportRenderer
|
|||||||
_useTextureLocation = _meshShader.GetUniformLocation("uUseTexture");
|
_useTextureLocation = _meshShader.GetUniformLocation("uUseTexture");
|
||||||
_texture0Location = _meshShader.GetUniformLocation("uTexture0");
|
_texture0Location = _meshShader.GetUniformLocation("uTexture0");
|
||||||
_colorAddLocation = _meshShader.GetUniformLocation("uColorAdd");
|
_colorAddLocation = _meshShader.GetUniformLocation("uColorAdd");
|
||||||
|
|
||||||
|
_pickingMvpLocation = _pickingShader.GetUniformLocation("uMvp");
|
||||||
|
_pickingColorLocation = _pickingShader.GetUniformLocation("uPickColor");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace NResUI.Rendering.Viewport;
|
|
||||||
|
|
||||||
public static class ViewportSelection
|
|
||||||
{
|
|
||||||
public static int PickPiece(
|
|
||||||
ViewportScene scene,
|
|
||||||
ViewportCamera camera,
|
|
||||||
Vector2 localMouse,
|
|
||||||
Vector2 viewportSize)
|
|
||||||
{
|
|
||||||
if (viewportSize.X <= 1.0f || viewportSize.Y <= 1.0f)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
var aspect = viewportSize.X / viewportSize.Y;
|
|
||||||
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 = 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)
|
|
||||||
{
|
|
||||||
if (!Matrix4x4.Invert(piece.LocalTransform, out var inverseModel))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var rayOriginLocal = Vector3.Transform(rayOriginWorld, inverseModel);
|
|
||||||
var rayDirectionLocal = Vector3.Normalize(
|
|
||||||
Vector3.TransformNormal(rayDirectionWorld, inverseModel));
|
|
||||||
|
|
||||||
if (IntersectRayAabb(
|
|
||||||
rayOriginLocal,
|
|
||||||
rayDirectionLocal,
|
|
||||||
piece.BoundsMin,
|
|
||||||
piece.BoundsMax,
|
|
||||||
out var hitDistance) &&
|
|
||||||
hitDistance < bestDistance)
|
|
||||||
{
|
|
||||||
bestDistance = hitDistance;
|
|
||||||
bestPieceId = piece.Id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bestPieceId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IntersectRayAabb(
|
|
||||||
Vector3 rayOrigin,
|
|
||||||
Vector3 rayDirection,
|
|
||||||
Vector3 boundsMin,
|
|
||||||
Vector3 boundsMax,
|
|
||||||
out float hitDistance)
|
|
||||||
{
|
|
||||||
hitDistance = 0.0f;
|
|
||||||
|
|
||||||
var tMin = 0.0f;
|
|
||||||
var tMax = float.PositiveInfinity;
|
|
||||||
|
|
||||||
if (!IntersectSlab(rayOrigin.X, rayDirection.X, boundsMin.X, boundsMax.X, ref tMin, ref tMax) ||
|
|
||||||
!IntersectSlab(rayOrigin.Y, rayDirection.Y, boundsMin.Y, boundsMax.Y, ref tMin, ref tMax) ||
|
|
||||||
!IntersectSlab(rayOrigin.Z, rayDirection.Z, boundsMin.Z, boundsMax.Z, ref tMin, ref tMax))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hitDistance = tMin;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IntersectSlab(
|
|
||||||
float origin,
|
|
||||||
float direction,
|
|
||||||
float min,
|
|
||||||
float max,
|
|
||||||
ref float tMin,
|
|
||||||
ref float tMax)
|
|
||||||
{
|
|
||||||
const float epsilon = 1e-6f;
|
|
||||||
|
|
||||||
if (MathF.Abs(direction) < epsilon)
|
|
||||||
return origin >= min && origin <= max;
|
|
||||||
|
|
||||||
var invDirection = 1.0f / direction;
|
|
||||||
var t1 = (min - origin) * invDirection;
|
|
||||||
var t2 = (max - origin) * invDirection;
|
|
||||||
|
|
||||||
if (t1 > t2)
|
|
||||||
(t1, t2) = (t2, t1);
|
|
||||||
|
|
||||||
tMin = MathF.Max(tMin, t1);
|
|
||||||
tMax = MathF.Min(tMax, t2);
|
|
||||||
|
|
||||||
return tMin <= tMax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user