Add project files.
This commit is contained in:
+576
@@ -0,0 +1,576 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Mathematics;
|
||||
using OpenTK.WinForms;
|
||||
|
||||
namespace BoxPacker3D
|
||||
{
|
||||
public class GlViewer : IDisposable
|
||||
{
|
||||
private GLControl _gl;
|
||||
private bool _glReady = false;
|
||||
|
||||
private List<PackedItem> _items = new();
|
||||
private Box? _box = null;
|
||||
private float _rotX = 30f, _rotY = 30f, _zoom = 1f;
|
||||
private double _wallPad = 0, _itemPad = 0;
|
||||
|
||||
private bool _dragging;
|
||||
private Point _lastMouse;
|
||||
|
||||
public GlViewer(Panel container)
|
||||
{
|
||||
var settings = new GLControlSettings
|
||||
{
|
||||
API = OpenTK.Windowing.Common.ContextAPI.OpenGL,
|
||||
APIVersion = new Version(2, 1),
|
||||
Profile = OpenTK.Windowing.Common.ContextProfile.Any,
|
||||
Flags = OpenTK.Windowing.Common.ContextFlags.Default,
|
||||
NumberOfSamples = 4
|
||||
};
|
||||
|
||||
_gl = new GLControl(settings)
|
||||
{
|
||||
Dock = DockStyle.Fill,
|
||||
BackColor = Color.FromArgb(18, 18, 28)
|
||||
};
|
||||
|
||||
_gl.Load += OnLoad;
|
||||
_gl.Paint += OnPaint;
|
||||
_gl.Resize += OnResize;
|
||||
_gl.MouseDown += OnMouseDown;
|
||||
_gl.MouseMove += OnMouseMove;
|
||||
_gl.MouseUp += OnMouseUp;
|
||||
_gl.MouseWheel += OnMouseWheel;
|
||||
|
||||
container.Controls.Add(_gl);
|
||||
}
|
||||
|
||||
public Control Control => _gl;
|
||||
|
||||
public void Render(List<PackedItem> items, Box box,
|
||||
double rotX, double rotY, double zoom,
|
||||
double wallPad = 0, double itemPad = 0)
|
||||
{
|
||||
_items = items ?? new List<PackedItem>();
|
||||
_box = box;
|
||||
_rotX = (float)rotX;
|
||||
_rotY = (float)rotY;
|
||||
_zoom = (float)Math.Max(0.1, zoom);
|
||||
_wallPad = wallPad;
|
||||
_itemPad = itemPad;
|
||||
if (_glReady) _gl.Invalidate();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_items = new List<PackedItem>();
|
||||
_box = null;
|
||||
if (_glReady) _gl.Invalidate();
|
||||
}
|
||||
|
||||
public void SetView(float rotX, float rotY, float zoom)
|
||||
{
|
||||
_rotX = rotX; _rotY = rotY; _zoom = zoom;
|
||||
if (_glReady) _gl.Invalidate();
|
||||
}
|
||||
|
||||
private void OnLoad(object? sender, EventArgs e)
|
||||
{
|
||||
_gl.MakeCurrent();
|
||||
|
||||
GL.ClearColor(0.071f, 0.071f, 0.110f, 1f);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.CullFace);
|
||||
GL.CullFace(CullFaceMode.Back);
|
||||
GL.ShadeModel(ShadingModel.Smooth);
|
||||
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.Light0);
|
||||
GL.Enable(EnableCap.ColorMaterial);
|
||||
GL.ColorMaterial(MaterialFace.FrontAndBack, ColorMaterialParameter.AmbientAndDiffuse);
|
||||
|
||||
GL.Light(LightName.Light0, LightParameter.Position, new float[] { 1f, 2f, 1.5f, 0f });
|
||||
GL.Light(LightName.Light0, LightParameter.Diffuse, new float[] { 1f, 1f, 1f, 1f });
|
||||
GL.Light(LightName.Light0, LightParameter.Ambient, new float[] { 0.25f, 0.25f, 0.25f, 1f });
|
||||
GL.Light(LightName.Light0, LightParameter.Specular, new float[] { 0.4f, 0.4f, 0.4f, 1f });
|
||||
|
||||
GL.Enable(EnableCap.Multisample);
|
||||
GL.Enable(EnableCap.LineSmooth);
|
||||
GL.Hint(HintTarget.LineSmoothHint, HintMode.Nicest);
|
||||
|
||||
// Set initial viewport immediately so first render is centered
|
||||
GL.Viewport(0, 0, _gl.Width, _gl.Height);
|
||||
|
||||
_glReady = true;
|
||||
_gl.Invalidate(); // Force first paint
|
||||
}
|
||||
|
||||
private void OnResize(object? sender, EventArgs e)
|
||||
{
|
||||
if (!_glReady) return;
|
||||
_gl.MakeCurrent();
|
||||
GL.Viewport(0, 0, _gl.Width, _gl.Height);
|
||||
_gl.Invalidate();
|
||||
}
|
||||
|
||||
private void OnPaint(object? sender, PaintEventArgs e)
|
||||
{
|
||||
if (!_glReady) return;
|
||||
_gl.MakeCurrent();
|
||||
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
|
||||
if (_box == null) { DrawWelcome(); _gl.SwapBuffers(); return; }
|
||||
|
||||
SetupProjection();
|
||||
SetupModelView();
|
||||
|
||||
DrawBoxOutline(_box, alpha: 0.3f);
|
||||
if (_box.WallThickness > 0.001) DrawWallThickness(_box);
|
||||
if (_wallPad > 0.001) DrawPaddingOutline(_box, _wallPad);
|
||||
DrawItems();
|
||||
DrawBoxOutline(_box, alpha: 0.9f, frontOnly: true);
|
||||
DrawAxesLabels();
|
||||
if (_box != null) DrawDimensionLabelsGL();
|
||||
|
||||
_gl.SwapBuffers();
|
||||
}
|
||||
|
||||
private void SetupProjection()
|
||||
{
|
||||
GL.MatrixMode(MatrixMode.Projection);
|
||||
GL.LoadIdentity();
|
||||
float aspect = _gl.Width > 0 && _gl.Height > 0
|
||||
? (float)_gl.Width / _gl.Height : 1f;
|
||||
|
||||
// Increase far plane to 50000 to handle large boxes at low zoom
|
||||
// Near plane 1.0 (instead of 0.1) for better depth precision
|
||||
var proj = Matrix4.CreatePerspectiveFieldOfView(
|
||||
MathHelper.DegreesToRadians(45f), aspect, 1f, 50000f);
|
||||
GL.LoadMatrix(ref proj);
|
||||
}
|
||||
|
||||
private void SetupModelView()
|
||||
{
|
||||
GL.MatrixMode(MatrixMode.Modelview);
|
||||
GL.LoadIdentity();
|
||||
|
||||
double maxDim = _box == null ? 100
|
||||
: Math.Max(Math.Max(_box.W, _box.H), _box.D);
|
||||
float dist = (float)(maxDim * 2.2 / _zoom);
|
||||
|
||||
GL.Translate(0f, 0f, -dist);
|
||||
GL.Rotate(_rotX, 1f, 0f, 0f);
|
||||
GL.Rotate(_rotY, 0f, 1f, 0f);
|
||||
if (_box != null)
|
||||
GL.Translate(-(float)(_box.W / 2), -(float)(_box.H / 2), -(float)(_box.D / 2));
|
||||
}
|
||||
|
||||
private void DrawItems()
|
||||
{
|
||||
foreach (var pi in _items)
|
||||
{
|
||||
float r = pi.Item.Color.R / 255f;
|
||||
float g = pi.Item.Color.G / 255f;
|
||||
float b = pi.Item.Color.B / 255f;
|
||||
DrawSolidBox(
|
||||
(float)pi.X, (float)pi.Y, (float)pi.Z,
|
||||
(float)pi.RW, (float)pi.RH, (float)pi.RD,
|
||||
r, g, b, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSolidBox(float x, float y, float z,
|
||||
float w, float h, float d,
|
||||
float r, float g, float b, float a)
|
||||
{
|
||||
GL.Color4(r, g, b, a);
|
||||
GL.Begin(PrimitiveType.Quads);
|
||||
|
||||
GL.Normal3(0f, 0f, 1f);
|
||||
GL.Vertex3(x, y, z + d);
|
||||
GL.Vertex3(x + w, y, z + d);
|
||||
GL.Vertex3(x + w, y + h, z + d);
|
||||
GL.Vertex3(x, y + h, z + d);
|
||||
|
||||
GL.Normal3(0f, 0f, -1f);
|
||||
GL.Vertex3(x + w, y, z);
|
||||
GL.Vertex3(x, y, z);
|
||||
GL.Vertex3(x, y + h, z);
|
||||
GL.Vertex3(x + w, y + h, z);
|
||||
|
||||
GL.Normal3(-1f, 0f, 0f);
|
||||
GL.Vertex3(x, y, z);
|
||||
GL.Vertex3(x, y, z + d);
|
||||
GL.Vertex3(x, y + h, z + d);
|
||||
GL.Vertex3(x, y + h, z);
|
||||
|
||||
GL.Normal3(1f, 0f, 0f);
|
||||
GL.Vertex3(x + w, y, z + d);
|
||||
GL.Vertex3(x + w, y, z);
|
||||
GL.Vertex3(x + w, y + h, z);
|
||||
GL.Vertex3(x + w, y + h, z + d);
|
||||
|
||||
GL.Normal3(0f, -1f, 0f);
|
||||
GL.Vertex3(x, y, z);
|
||||
GL.Vertex3(x + w, y, z);
|
||||
GL.Vertex3(x + w, y, z + d);
|
||||
GL.Vertex3(x, y, z + d);
|
||||
|
||||
GL.Normal3(0f, 1f, 0f);
|
||||
GL.Vertex3(x, y + h, z + d);
|
||||
GL.Vertex3(x + w, y + h, z + d);
|
||||
GL.Vertex3(x + w, y + h, z);
|
||||
GL.Vertex3(x, y + h, z);
|
||||
|
||||
GL.End();
|
||||
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.LineWidth(1.2f);
|
||||
GL.Color4(0f, 0f, 0f, 0.55f);
|
||||
DrawWireBox(x, y, z, w, h, d);
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
private void DrawWireBox(float x, float y, float z, float w, float h, float d)
|
||||
{
|
||||
GL.Begin(PrimitiveType.LineLoop);
|
||||
GL.Vertex3(x, y, z); GL.Vertex3(x + w, y, z);
|
||||
GL.Vertex3(x + w, y + h, z); GL.Vertex3(x, y + h, z);
|
||||
GL.End();
|
||||
GL.Begin(PrimitiveType.LineLoop);
|
||||
GL.Vertex3(x, y, z + d); GL.Vertex3(x + w, y, z + d);
|
||||
GL.Vertex3(x + w, y + h, z + d); GL.Vertex3(x, y + h, z + d);
|
||||
GL.End();
|
||||
GL.Begin(PrimitiveType.Lines);
|
||||
GL.Vertex3(x, y, z); GL.Vertex3(x, y, z + d);
|
||||
GL.Vertex3(x + w, y, z); GL.Vertex3(x + w, y, z + d);
|
||||
GL.Vertex3(x + w, y + h, z); GL.Vertex3(x + w, y + h, z + d);
|
||||
GL.Vertex3(x, y + h, z); GL.Vertex3(x, y + h, z + d);
|
||||
GL.End();
|
||||
}
|
||||
|
||||
private void DrawBoxOutline(Box box, float alpha, bool frontOnly = false)
|
||||
{
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.LineWidth(frontOnly ? 2f : 1f);
|
||||
GL.LineStipple(1, 0x0F0F);
|
||||
GL.Enable(EnableCap.LineStipple);
|
||||
GL.Color4(0.39f, 0.78f, 1f, alpha);
|
||||
|
||||
DrawWireBox(0, 0, 0, (float)box.W, (float)box.H, (float)box.D);
|
||||
|
||||
GL.Disable(EnableCap.LineStipple);
|
||||
GL.Disable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
/// Draws the inner cardboard wall surface and corner connectors between outer/inner cubes.
|
||||
/// Visually represents the thickness of the box material.
|
||||
private void DrawWallThickness(Box box)
|
||||
{
|
||||
float t = (float)box.WallThickness;
|
||||
float w = (float)box.W, h = (float)box.H, d = (float)box.D;
|
||||
float iw = w - 2 * t, ih = h - 2 * t, id = d - 2 * t;
|
||||
if (iw <= 0 || ih <= 0 || id <= 0) return;
|
||||
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
// 1) Inner wire cube (solid line, brownish cardboard tone)
|
||||
GL.LineWidth(1.5f);
|
||||
GL.Color4(0.72f, 0.55f, 0.35f, 0.85f);
|
||||
DrawWireBox(t, t, t, iw, ih, id);
|
||||
|
||||
// 2) Corner connectors – 8 lines from outer corner to inner corner
|
||||
// (visualises the wall as a prism at each corner)
|
||||
GL.LineWidth(1.0f);
|
||||
GL.Color4(0.72f, 0.55f, 0.35f, 0.55f);
|
||||
GL.Begin(PrimitiveType.Lines);
|
||||
void Link(float ox, float oy, float oz, float ix, float iy, float iz)
|
||||
{ GL.Vertex3(ox, oy, oz); GL.Vertex3(ix, iy, iz); }
|
||||
|
||||
Link(0, 0, 0, t, t, t);
|
||||
Link(w, 0, 0, w - t, t, t);
|
||||
Link(w, h, 0, w - t, h - t, t);
|
||||
Link(0, h, 0, t, h - t, t);
|
||||
Link(0, 0, d, t, t, d - t);
|
||||
Link(w, 0, d, w - t, t, d - t);
|
||||
Link(w, h, d, w - t, h - t, d - t);
|
||||
Link(0, h, d, t, h - t, d - t);
|
||||
GL.End();
|
||||
|
||||
// 3) Subtle translucent shaded floor – shows where inner base begins
|
||||
GL.Color4(0.72f, 0.55f, 0.35f, 0.08f);
|
||||
GL.Begin(PrimitiveType.Quads);
|
||||
GL.Vertex3(t, t, t);
|
||||
GL.Vertex3(w - t, t, t);
|
||||
GL.Vertex3(w - t, t, d - t);
|
||||
GL.Vertex3(t, t, d - t);
|
||||
GL.End();
|
||||
|
||||
GL.Disable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
private void DrawPaddingOutline(Box box, double pad)
|
||||
{
|
||||
float edge = (float)(pad + box.WallThickness);
|
||||
float w = (float)box.W - 2 * edge;
|
||||
float h = (float)box.H - 2 * edge;
|
||||
float d = (float)box.D - 2 * edge;
|
||||
if (w <= 0 || h <= 0 || d <= 0) return;
|
||||
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.LineWidth(1.2f);
|
||||
GL.LineStipple(2, 0xAAAA);
|
||||
GL.Enable(EnableCap.LineStipple);
|
||||
GL.Color4(1f, 0.86f, 0.31f, 0.5f);
|
||||
|
||||
DrawWireBox(edge, edge, edge, w, h, d);
|
||||
|
||||
GL.Disable(EnableCap.LineStipple);
|
||||
GL.Disable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
private void DrawAxesLabels()
|
||||
{
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.LineWidth(2f);
|
||||
|
||||
float aw = _box == null ? 10f : (float)(_box.W * 0.12f);
|
||||
|
||||
GL.Begin(PrimitiveType.Lines);
|
||||
GL.Color3(1f, 0.3f, 0.3f); GL.Vertex3(0, 0, 0); GL.Vertex3(aw, 0, 0);
|
||||
GL.Color3(0.3f, 1f, 0.3f); GL.Vertex3(0, 0, 0); GL.Vertex3(0, aw, 0);
|
||||
GL.Color3(0.3f, 0.5f, 1f); GL.Vertex3(0, 0, 0); GL.Vertex3(0, 0, aw);
|
||||
GL.End();
|
||||
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
}
|
||||
|
||||
private void DrawDimensionLabelsGL()
|
||||
{
|
||||
if (_box == null) return;
|
||||
int sw = _gl.Width, sh = _gl.Height;
|
||||
|
||||
var labels = new (float wx, float wy, float wz, string text, float r, float g, float b, float ox, float oy)[]
|
||||
{
|
||||
((float)(_box.W / 2), 0f, 0f, $"šírka {_box.W:F0}", 1f, 0.43f, 0.43f, 0f, 26f),
|
||||
((float)_box.W, 0f, (float)(_box.D/2), $"dĺžka {_box.D:F0}", 0.43f,0.51f, 1f, 26f, 8f),
|
||||
((float)_box.W, (float)(_box.H/2), 0f, $"výška {_box.H:F0}", 0.43f,0.9f, 0.43f, 30f, 0f),
|
||||
};
|
||||
|
||||
foreach (var lbl in labels)
|
||||
{
|
||||
var pt = Project3DToScreen(lbl.wx, lbl.wy, lbl.wz);
|
||||
if (pt == null) continue;
|
||||
float sx = pt.Value.X + lbl.ox;
|
||||
float sy = pt.Value.Y + lbl.oy;
|
||||
DrawTextGL(lbl.text, sx, sy, sw, sh, lbl.r, lbl.g, lbl.b);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTextGL(string text, float sx, float sy, int sw, int sh, float r, float g, float b)
|
||||
{
|
||||
using var bmp = new Bitmap(1, 1);
|
||||
using var measure = Graphics.FromImage(bmp);
|
||||
using var font = new Font("Segoe UI", 11f, FontStyle.Bold);
|
||||
var sz = measure.MeasureString(text, font);
|
||||
int tw = (int)sz.Width + 6, th = (int)sz.Height + 4;
|
||||
if (tw <= 0 || th <= 0) return;
|
||||
|
||||
using var textBmp = new Bitmap(tw, th, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
using (var tg = Graphics.FromImage(textBmp))
|
||||
{
|
||||
tg.Clear(Color.FromArgb(0, 0, 0, 0));
|
||||
using var bgBr = new SolidBrush(Color.FromArgb(180, 15, 15, 25));
|
||||
tg.FillRectangle(bgBr, 0, 0, tw, th);
|
||||
using var br = new SolidBrush(Color.FromArgb(255, (int)(r*255), (int)(g*255), (int)(b*255)));
|
||||
tg.DrawString(text, font, br, 3, 2);
|
||||
}
|
||||
|
||||
// flip vertically (OpenGL vs GDI+ Y axis)
|
||||
textBmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
|
||||
|
||||
int tex = GL.GenTexture();
|
||||
GL.BindTexture(TextureTarget.Texture2D, tex);
|
||||
var data = textBmp.LockBits(
|
||||
new Rectangle(0, 0, tw, th),
|
||||
System.Drawing.Imaging.ImageLockMode.ReadOnly,
|
||||
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
|
||||
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba,
|
||||
tw, th, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
|
||||
textBmp.UnlockBits(data);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
|
||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
|
||||
|
||||
// switch to 2D ortho
|
||||
GL.Disable(EnableCap.DepthTest);
|
||||
GL.Disable(EnableCap.Lighting);
|
||||
GL.Enable(EnableCap.Blend);
|
||||
GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
GL.Enable(EnableCap.Texture2D);
|
||||
|
||||
GL.MatrixMode(MatrixMode.Projection);
|
||||
GL.PushMatrix();
|
||||
GL.LoadIdentity();
|
||||
GL.Ortho(0, sw, 0, sh, -1, 1);
|
||||
GL.MatrixMode(MatrixMode.Modelview);
|
||||
GL.PushMatrix();
|
||||
GL.LoadIdentity();
|
||||
|
||||
// screen Y is flipped: sy is from top, GL ortho is from bottom
|
||||
float glY = sh - sy - th;
|
||||
GL.Color4(1f, 1f, 1f, 1f);
|
||||
GL.Begin(PrimitiveType.Quads);
|
||||
GL.TexCoord2(0f, 0f); GL.Vertex2(sx, glY);
|
||||
GL.TexCoord2(1f, 0f); GL.Vertex2(sx + tw, glY);
|
||||
GL.TexCoord2(1f, 1f); GL.Vertex2(sx + tw, glY + th);
|
||||
GL.TexCoord2(0f, 1f); GL.Vertex2(sx, glY + th);
|
||||
GL.End();
|
||||
|
||||
GL.PopMatrix();
|
||||
GL.MatrixMode(MatrixMode.Projection);
|
||||
GL.PopMatrix();
|
||||
GL.MatrixMode(MatrixMode.Modelview);
|
||||
|
||||
GL.Disable(EnableCap.Texture2D);
|
||||
GL.Disable(EnableCap.Blend);
|
||||
GL.Enable(EnableCap.DepthTest);
|
||||
GL.Enable(EnableCap.Lighting);
|
||||
GL.DeleteTexture(tex);
|
||||
}
|
||||
|
||||
private PointF? Project3DToScreen(float wx, float wy, float wz)
|
||||
{
|
||||
if (_box == null) return null;
|
||||
int sw = _gl.Width, sh = _gl.Height;
|
||||
if (sw <= 0 || sh <= 0) return null;
|
||||
|
||||
double maxDim = Math.Max(Math.Max(_box.W, _box.H), _box.D);
|
||||
float dist = (float)(maxDim * 2.2 / _zoom);
|
||||
float aspect = (float)sw / sh;
|
||||
float fovY = (float)(Math.PI / 4); // 45°
|
||||
|
||||
// translate to box center
|
||||
float tx = wx - (float)(_box.W / 2);
|
||||
float ty = wy - (float)(_box.H / 2);
|
||||
float tz = wz - (float)(_box.D / 2);
|
||||
|
||||
// rotate Y
|
||||
float radY = _rotY * (float)(Math.PI / 180);
|
||||
float cosY = (float)Math.Cos(radY), sinY = (float)Math.Sin(radY);
|
||||
float rx = tx * cosY + tz * sinY;
|
||||
float ry = ty;
|
||||
float rz = -tx * sinY + tz * cosY;
|
||||
|
||||
// rotate X
|
||||
float radX = _rotX * (float)(Math.PI / 180);
|
||||
float cosX = (float)Math.Cos(radX), sinX = (float)Math.Sin(radX);
|
||||
float fx = rx;
|
||||
float fy = ry * cosX - rz * sinX;
|
||||
float fz = ry * sinX + rz * cosX - dist;
|
||||
|
||||
if (fz >= -0.1f) return null;
|
||||
|
||||
float f = 1f / (float)Math.Tan(fovY / 2);
|
||||
float ndcX = f / aspect * fx / (-fz);
|
||||
float ndcY = f * fy / (-fz);
|
||||
|
||||
return new PointF(
|
||||
(ndcX + 1f) / 2f * sw,
|
||||
(1f - ndcY) / 2f * sh);
|
||||
}
|
||||
|
||||
private void DrawDimensionLabels(Graphics g)
|
||||
{
|
||||
if (_box == null) return;
|
||||
using var font = new Font("Segoe UI", 11f, FontStyle.Bold);
|
||||
|
||||
var colW = Color.FromArgb(255, 255, 110, 110);
|
||||
var colD = Color.FromArgb(255, 110, 140, 255);
|
||||
var colH = Color.FromArgb(255, 110, 230, 110);
|
||||
|
||||
void Label(PointF? pt, string text, Color col, float ox, float oy)
|
||||
{
|
||||
if (pt == null) return;
|
||||
var sz = g.MeasureString(text, font);
|
||||
float tx = pt.Value.X + ox - sz.Width / 2;
|
||||
float ty = pt.Value.Y + oy - sz.Height / 2;
|
||||
using var bgBr = new SolidBrush(Color.FromArgb(170, 15, 15, 25));
|
||||
g.FillRectangle(bgBr, tx - 3, ty - 1, sz.Width + 6, sz.Height + 2);
|
||||
using var br = new SolidBrush(col);
|
||||
g.DrawString(text, font, br, tx, ty);
|
||||
}
|
||||
|
||||
// midpoints of three outer edges
|
||||
Label(Project3DToScreen((float)(_box.W / 2), 0f, 0f),
|
||||
$"šírka {_box.W:F0}", colW, 0, 22);
|
||||
Label(Project3DToScreen((float)_box.W, 0f, (float)(_box.D / 2)),
|
||||
$"dĺžka {_box.D:F0}", colD, 22, 8);
|
||||
Label(Project3DToScreen((float)_box.W, (float)(_box.H / 2), 0f),
|
||||
$"výška {_box.H:F0}", colH, 28, 0);
|
||||
}
|
||||
|
||||
private void DrawWelcome()
|
||||
{
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||
}
|
||||
|
||||
private void OnMouseDown(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left)
|
||||
{ _dragging = true; _lastMouse = e.Location; }
|
||||
}
|
||||
|
||||
private void OnMouseMove(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_dragging) return;
|
||||
float dx = e.X - _lastMouse.X;
|
||||
float dy = e.Y - _lastMouse.Y;
|
||||
_rotY += dx * 0.5f;
|
||||
_rotX += dy * 0.5f;
|
||||
_lastMouse = e.Location;
|
||||
_gl.Invalidate();
|
||||
ViewChanged?.Invoke(this, new ViewArgs(_rotX, _rotY, _zoom));
|
||||
}
|
||||
|
||||
private void OnMouseUp(object? sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.Button == MouseButtons.Left) _dragging = false;
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object? sender, MouseEventArgs e)
|
||||
{
|
||||
_zoom *= e.Delta > 0 ? 1.1f : 0.9f;
|
||||
_zoom = Math.Clamp(_zoom, 0.1f, 10f);
|
||||
_gl.Invalidate();
|
||||
ViewChanged?.Invoke(this, new ViewArgs(_rotX, _rotY, _zoom));
|
||||
}
|
||||
|
||||
public event EventHandler<ViewArgs>? ViewChanged;
|
||||
|
||||
public class ViewArgs : EventArgs
|
||||
{
|
||||
public float RotX, RotY, Zoom;
|
||||
public ViewArgs(float rx, float ry, float z) { RotX = rx; RotY = ry; Zoom = z; }
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user