using OpenTK.Wpf; using OpenTK.Graphics.OpenGL; using System.Windows.Input; using UserControl = System.Windows.Controls.UserControl; namespace OperationControl.Controls { /// /// Interaction logic for Bbox3DViewer.xaml /// public partial class Bbox3DViewer : UserControl { // ======= estado de câmera/viewport ======= float zoom = 1.0f; float cameraDist = 12.0f; float rotX = 25f, rotY = 20f; float panX = 0f, panY = 0f; System.Windows.Point lastMouse; bool dragLeft, dragMid; // ======= parâmetros de cena ======= readonly List boxes = new(); public float GridHalf { get; set; } = 12f; public float RobotHalfW { get; set; } = 0.30f; public float RobotL { get; set; } = 0.60f; public Bbox3DViewer() { InitializeComponent(); // inicia contexto GL compatível com fixed pipeline (2.1) var settings = new GLWpfControlSettings { MajorVersion = 2, MinorVersion = 1, RenderContinuously = false, // redesenha sob demanda // para 60 fps contínuo, use true }; GLHost.Start(settings); // eventos do ciclo de vida GLHost.Ready += OnGLReady; // 1x GLHost.Render += OnGLRender; // quando precisar GLHost.SizeChanged += (_, __) => Invalidate(); // ajusta viewport // interações mouse GLHost.MouseWheel += OnWheel; GLHost.MouseDown += OnMouseDown; GLHost.MouseUp += OnMouseUp; GLHost.MouseMove += OnMouseMove; } // ======= API pública ======= public void SetBboxes(IEnumerable list) { boxes.Clear(); if (list != null) boxes.AddRange(list); Invalidate(); } public void Invalidate() => GLHost.InvalidateVisual(); // dispara Render // ======= GL ======= void OnGLReady() { GL.ClearColor(System.Drawing.Color.FromArgb(10, 12, 18)); GL.Enable(EnableCap.DepthTest); UpdateProjection(); } void UpdateProjection() { if (GLHost.ActualHeight <= 0) return; GL.Viewport(0, 0, (int)GLHost.ActualWidth, (int)GLHost.ActualHeight); GL.MatrixMode(MatrixMode.Projection); GL.LoadIdentity(); float aspect = (float)GLHost.ActualWidth / Math.Max(1f, (float)GLHost.ActualHeight); // perspectiva igual ao WinForms GL.Frustum(-aspect * zoom, aspect * zoom, -1.0 * zoom, 1.0 * zoom, 0.5, 200.0); } void OnGLRender(TimeSpan _) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // view GL.MatrixMode(MatrixMode.Modelview); GL.LoadIdentity(); GL.Translate(panX, panY, -cameraDist); GL.Rotate(rotX, 1, 0, 0); GL.Rotate(rotY, 0, 1, 0); // chão + eixos DrawGroundGrid(GridHalf, 1f); DrawAxes(2.0f); // robô na origem (nariz para +X) DrawRobotGlyph(); // bboxes foreach (var b in boxes) DrawBox((float)b.cx, (float)b.cy, (float)b.cz, (float)b.d, (float)b.w, (float)b.h); } // ======= desenho ======= void DrawGroundGrid(float half, float step) { GL.Disable(EnableCap.Lighting); GL.LineWidth(1f); GL.Begin(PrimitiveType.Lines); for (float x = -half; x <= half; x += step) { bool major = Math.Abs(x % 5f) < 1e-3; var c = major ? System.Drawing.Color.FromArgb(90, 220, 220, 220) : System.Drawing.Color.FromArgb(40, 180, 180, 180); GL.Color3(c); GL.Vertex3(x, -0.001, -half); GL.Vertex3(x, -0.001, +half); } for (float y = -half; y <= half; y += step) { bool major = Math.Abs(y % 5f) < 1e-3; var c = major ? System.Drawing.Color.FromArgb(90, 220, 220, 220) : System.Drawing.Color.FromArgb(40, 180, 180, 180); GL.Color3(c); GL.Vertex3(-half, -0.001, y); GL.Vertex3(+half, -0.001, y); } GL.End(); // eixos no chão GL.LineWidth(2f); GL.Begin(PrimitiveType.Lines); GL.Color3(System.Drawing.Color.FromArgb(160, 80, 180, 255)); // X GL.Vertex3(0, 0, 0); GL.Vertex3(3, 0, 0); GL.Color3(System.Drawing.Color.FromArgb(160, 80, 255, 180)); // Y GL.Vertex3(0, 0, 0); GL.Vertex3(0, 0, 3); GL.End(); } void DrawAxes(float len) { GL.LineWidth(2f); GL.Begin(PrimitiveType.Lines); GL.Color3(System.Drawing.Color.Red); GL.Vertex3(0, 0, 0); GL.Vertex3(len, 0, 0); // X GL.Color3(System.Drawing.Color.Green); GL.Vertex3(0, 0, 0); GL.Vertex3(0, 0, len); // Y GL.Color3(System.Drawing.Color.Blue); GL.Vertex3(0, 0, 0); GL.Vertex3(0, len, 0); // Z GL.End(); } void DrawRobotGlyph() { float hw = RobotHalfW, L = RobotL; // corpo GL.Color4(System.Drawing.Color.FromArgb(70, 0, 255, 255)); GL.Begin(PrimitiveType.Quads); GL.Vertex3(-0.1, 0.0, -hw); GL.Vertex3(L, 0.0, -hw); GL.Vertex3(L, 0.0, +hw); GL.Vertex3(-0.1, 0.0, +hw); GL.End(); // borda GL.Color3(System.Drawing.Color.Cyan); GL.LineWidth(2f); GL.Begin(PrimitiveType.LineLoop); GL.Vertex3(-0.1, 0.0, -hw); GL.Vertex3(L, 0.0, -hw); GL.Vertex3(L, 0.0, +hw); GL.Vertex3(-0.1, 0.0, +hw); GL.End(); // seta frontal GL.Begin(PrimitiveType.Triangles); GL.Color3(System.Drawing.Color.Cyan); GL.Vertex3(L + 0.10, 0.0, 0.0); GL.Vertex3(L - 0.10, 0.0, -0.10); GL.Vertex3(L - 0.10, 0.0, +0.10); GL.End(); } void DrawBox(float cx, float cy, float cz, float dx, float dy, float dz) { float x0 = cx - dx / 2f, x1 = cx + dx / 2f; float y0 = cy - dy / 2f, y1 = cy + dy / 2f; float z0 = cz - dz / 2f, z1 = cz + dz / 2f; // faces translúcidas GL.Enable(EnableCap.Blend); GL.BlendFunc((BlendingFactor)BlendingFactorSrc.SrcAlpha, (BlendingFactor)BlendingFactorDest.OneMinusSrcAlpha); GL.Color4(System.Drawing.Color.FromArgb(50, 255, 180, 0)); GL.Begin(PrimitiveType.Quads); // +X GL.Vertex3(x1, z0, y0); GL.Vertex3(x1, z0, y1); GL.Vertex3(x1, z1, y1); GL.Vertex3(x1, z1, y0); // -X GL.Vertex3(x0, z0, y1); GL.Vertex3(x0, z0, y0); GL.Vertex3(x0, z1, y0); GL.Vertex3(x0, z1, y1); // +Y GL.Vertex3(x0, z0, y1); GL.Vertex3(x1, z0, y1); GL.Vertex3(x1, z1, y1); GL.Vertex3(x0, z1, y1); // -Y GL.Vertex3(x1, z0, y0); GL.Vertex3(x0, z0, y0); GL.Vertex3(x0, z1, y0); GL.Vertex3(x1, z1, y0); // +Z (topo) GL.Vertex3(x0, z1, y0); GL.Vertex3(x1, z1, y0); GL.Vertex3(x1, z1, y1); GL.Vertex3(x0, z1, y1); // -Z (base) GL.Vertex3(x0, z0, y1); GL.Vertex3(x1, z0, y1); GL.Vertex3(x1, z0, y0); GL.Vertex3(x0, z0, y0); GL.End(); GL.Disable(EnableCap.Blend); // wireframe GL.Color3(System.Drawing.Color.Orange); GL.LineWidth(2f); GL.Begin(PrimitiveType.Lines); // base z0 Line(x0, z0, y0, x1, z0, y0); Line(x1, z0, y0, x1, z0, y1); Line(x1, z0, y1, x0, z0, y1); Line(x0, z0, y1, x0, z0, y0); // topo z1 Line(x0, z1, y0, x1, z1, y0); Line(x1, z1, y0, x1, z1, y1); Line(x1, z1, y1, x0, z1, y1); Line(x0, z1, y1, x0, z1, y0); // colunas Line(x0, z0, y0, x0, z1, y0); Line(x1, z0, y0, x1, z1, y0); Line(x1, z0, y1, x1, z1, y1); Line(x0, z0, y1, x0, z1, y1); GL.End(); } void Line(float x0, float z0, float y0, float x1, float z1, float y1) { GL.Vertex3(x0, z0, y0); GL.Vertex3(x1, z1, y1); } // ======= Interações ======= void OnWheel(object s, MouseWheelEventArgs e) { zoom *= (e.Delta > 0) ? 0.9f : 1.1f; zoom = Math.Clamp(zoom, 0.2f, 4f); UpdateProjection(); Invalidate(); } void OnMouseDown(object s, MouseButtonEventArgs e) { GLHost.Focus(); lastMouse = e.GetPosition(GLHost); if (e.ChangedButton == MouseButton.Left) dragLeft = true; if (e.ChangedButton == MouseButton.Middle) dragMid = true; Mouse.Capture(GLHost); } void OnMouseUp(object s, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left) dragLeft = false; if (e.ChangedButton == MouseButton.Middle) dragMid = false; if (!dragLeft && !dragMid) Mouse.Capture(null); } void OnMouseMove(object s, System.Windows.Input.MouseEventArgs e) { var p = e.GetPosition(GLHost); if (dragLeft) { float dx = (float)(p.X - lastMouse.X); float dy = (float)(p.Y - lastMouse.Y); rotY += dx * 0.5f; rotX += dy * 0.5f; rotX = MathF.Max(-89f, MathF.Min(89f, rotX)); lastMouse = p; Invalidate(); } else if (dragMid) { float dx = (float)(p.X - lastMouse.X); float dy = (float)(p.Y - lastMouse.Y); panX += dx * 0.01f; panY -= dy * 0.01f; lastMouse = p; Invalidate(); } } } // Modelo igual ao seu (ajuste namespace/props se preciso) public class LivoxBboxModel { public double cx, cy, cz; // centro public double d, w, h; // dimensões (X,Y,Z) -> usei d=wX } }