577 lines
22 KiB
C#
577 lines
22 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|