diff --git a/backends/implot3d_impl_opengl3.cpp b/backends/implot3d_impl_opengl3.cpp new file mode 100644 index 0000000..19c5c95 --- /dev/null +++ b/backends/implot3d_impl_opengl3.cpp @@ -0,0 +1,595 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2026 Breno Cunha Queiroz + +#include "implot3d.h" +#ifndef IMGUI_DISABLE +#include "imgui.h" +#include "imgui_internal.h" +#include "implot3d_impl_opengl3.h" +#include + +// OpenGL loader +#include "imgui_impl_opengl3_loader.h" + +// Define depth texture constants if not present in ImGui's stripped loader +#ifndef GL_DEPTH_COMPONENT +#define GL_DEPTH_COMPONENT 0x1902 +#endif +#ifndef GL_DEPTH_COMPONENT24 +#define GL_DEPTH_COMPONENT24 0x81A6 +#endif + +// Define framebuffer constants if not present in ImGui's stripped loader +#ifndef GL_FRAMEBUFFER +#define GL_FRAMEBUFFER 0x8D40 +#endif +#ifndef GL_FRAMEBUFFER_COMPLETE +#define GL_FRAMEBUFFER_COMPLETE 0x8CD5 +#endif +#ifndef GL_COLOR_ATTACHMENT0 +#define GL_COLOR_ATTACHMENT0 0x8CE0 +#endif +#ifndef GL_DEPTH_ATTACHMENT +#define GL_DEPTH_ATTACHMENT 0x8D00 +#endif +#ifndef GL_DEPTH_BUFFER_BIT +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#endif + +// Define depth test constants +#ifndef GL_DEPTH_TEST +#define GL_DEPTH_TEST 0x0B71 +#endif +#ifndef GL_LESS +#define GL_LESS 0x0201 +#endif + +// Declare framebuffer functions if not in stripped loader +#ifndef IMGUI_IMPL_OPENGL_ES2 +typedef void(APIENTRYP PFNGLGENFRAMEBUFFERSPROC)(GLsizei n, GLuint* framebuffers); +typedef void(APIENTRYP PFNGLDELETEFRAMEBUFFERSPROC)(GLsizei n, const GLuint* framebuffers); +typedef void(APIENTRYP PFNGLBINDFRAMEBUFFERPROC)(GLenum target, GLuint framebuffer); +typedef void(APIENTRYP PFNGLFRAMEBUFFERTEXTURE2DPROC)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef GLenum(APIENTRYP PFNGLCHECKFRAMEBUFFERSTATUSPROC)(GLenum target); +typedef void(APIENTRYP PFNGLDEPTHFUNCPROC)(GLenum func); +typedef void(APIENTRYP PFNGLCLEARDEPTHPROC)(GLdouble depth); +typedef void(APIENTRYP PFNGLDEPTHMASKPROC)(GLboolean flag); +typedef void(APIENTRYP PFNGLBLENDFUNCPROC)(GLenum sfactor, GLenum dfactor); +typedef void(APIENTRYP PFNGLDRAWARRAYSPROC)(GLenum mode, GLint first, GLsizei count); +typedef void(APIENTRYP PFNGLUNIFORM2FPROC)(GLint location, GLfloat v0, GLfloat v1); +typedef void(APIENTRYP PFNGLUNIFORM3FPROC)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); + +static PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers; +static PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers; +static PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer; +static PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D; +static PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus; +static PFNGLDEPTHFUNCPROC glDepthFunc; +static PFNGLCLEARDEPTHPROC glClearDepth; +static PFNGLDEPTHMASKPROC glDepthMask; +static PFNGLBLENDFUNCPROC glBlendFunc; +static PFNGLDRAWARRAYSPROC glDrawArrays; +static PFNGLUNIFORM2FPROC glUniform2f; +static PFNGLUNIFORM3FPROC glUniform3f; +#endif + +// Shader sources +static const char* g_VertexShaderSource = R"( +#version 130 + +in vec3 Position; // 3D NDC position (before rotation) +in vec4 Color; // RGBA color + +out vec4 Frag_Color; +out vec3 Frag_Position; // Pass through 3D position for clipping + +uniform mat4 u_Rotation; // Rotation matrix from quaternion +uniform vec2 u_ViewportSize; // Viewport size (width, height) in pixels + +void main() { + // The input Position is in NDC space [-1, 1] before rotation + // NDCToPixels does: GetViewScale() * (Rotation * point) + // So we need to: 1) Apply rotation, 2) Apply aspect ratio correction + + // Apply rotation to the 3D NDC position + vec4 rotated_pos = u_Rotation * vec4(Position, 1.0); + + // Calculate aspect ratio correction + // GetViewScale uses min(width, height), so we need to scale the longer axis + float min_dim = min(u_ViewportSize.x, u_ViewportSize.y) * 1.11; // NOTE: No idea why 1.11 is needed + vec2 scale = vec2(min_dim / u_ViewportSize.x, min_dim / u_ViewportSize.y); + + // Apply scale to maintain aspect ratio, flip Y, negate Z for depth + gl_Position = vec4(rotated_pos.x * scale.x, -rotated_pos.y * scale.y, -rotated_pos.z, 1.0); + Frag_Color = Color; + Frag_Position = Position; // Pass original NDC position for clipping +} +)"; + +static const char* g_FragmentShaderSource = R"( +#version 130 + +in vec4 Frag_Color; +in vec3 Frag_Position; +out vec4 Out_Color; + +uniform bool u_EnableClip; +uniform vec3 u_ClipMin; // Min bounds in NDC space +uniform vec3 u_ClipMax; // Max bounds in NDC space + +void main() { + // Clip fragments outside the plot range + if (u_EnableClip) { + if (Frag_Position.x < u_ClipMin.x || Frag_Position.x > u_ClipMax.x || + Frag_Position.y < u_ClipMin.y || Frag_Position.y > u_ClipMax.y || + Frag_Position.z < u_ClipMin.z || Frag_Position.z > u_ClipMax.z) { + discard; + } + } + + // Apply sqrt to alpha for more natural transparency response + // This matches the visual behavior users expect and improves alpha blending quality + vec4 color = Frag_Color; + color.a = sqrt(color.a); + Out_Color = color; +} +)"; + +// Backend data stored in ImPlot3D context +struct ImPlot3D_ImplOpenGL3_Data { + GLuint ShaderProgram; + GLint AttribLocationPosition; + GLint AttribLocationColor; + GLint UniformLocationRotation; + GLint UniformLocationViewportSize; + GLint UniformLocationEnableClip; + GLint UniformLocationClipMin; + GLint UniformLocationClipMax; + GLuint VBO; + GLuint EBO; // Element buffer for indices + GLuint VAO; + GLuint FBO; +}; +static ImPlot3D_ImplOpenGL3_Data g_Data; + +// Track created textures for cleanup +static ImVector g_CreatedTextures; + +IMPLOT3D_IMPL_API bool ImPlot3D_ImplOpenGL3_Init() { + // Load framebuffer functions (not in stripped loader) +#ifndef IMGUI_IMPL_OPENGL_ES2 + glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)imgl3wGetProcAddress("glGenFramebuffers"); + glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)imgl3wGetProcAddress("glDeleteFramebuffers"); + glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)imgl3wGetProcAddress("glBindFramebuffer"); + glClearDepth = (PFNGLCLEARDEPTHPROC)imgl3wGetProcAddress("glClearDepth"); + glDepthMask = (PFNGLDEPTHMASKPROC)imgl3wGetProcAddress("glDepthMask"); + glBlendFunc = (PFNGLBLENDFUNCPROC)imgl3wGetProcAddress("glBlendFunc"); + glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)imgl3wGetProcAddress("glFramebufferTexture2D"); + glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)imgl3wGetProcAddress("glCheckFramebufferStatus"); + glDepthFunc = (PFNGLDEPTHFUNCPROC)imgl3wGetProcAddress("glDepthFunc"); + glDrawArrays = (PFNGLDRAWARRAYSPROC)imgl3wGetProcAddress("glDrawArrays"); + glUniform2f = (PFNGLUNIFORM2FPROC)imgl3wGetProcAddress("glUniform2f"); + glUniform3f = (PFNGLUNIFORM3FPROC)imgl3wGetProcAddress("glUniform3f"); +#endif + + // Compile vertex shader + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_shader, 1, &g_VertexShaderSource, nullptr); + glCompileShader(vertex_shader); + + // Check vertex shader compilation + GLint success = 0; + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); + if (!success) { + char info_log[512]; + glGetShaderInfoLog(vertex_shader, 512, nullptr, info_log); + IM_ASSERT_USER_ERROR(false, "ImPlot3D: Vertex shader compilation failed!"); + IMGUI_DEBUG_PRINTF("ImPlot3D: Vertex shader error: %s\n", info_log); + return false; + } + + // Compile fragment shader + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_shader, 1, &g_FragmentShaderSource, nullptr); + glCompileShader(fragment_shader); + + // Check fragment shader compilation + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); + if (!success) { + char info_log[512]; + glGetShaderInfoLog(fragment_shader, 512, nullptr, info_log); + IM_ASSERT_USER_ERROR(false, "ImPlot3D: Fragment shader compilation failed!"); + IMGUI_DEBUG_PRINTF("ImPlot3D: Fragment shader error: %s\n", info_log); + glDeleteShader(vertex_shader); + return false; + } + + // Link shader program + g_Data.ShaderProgram = glCreateProgram(); + glAttachShader(g_Data.ShaderProgram, vertex_shader); + glAttachShader(g_Data.ShaderProgram, fragment_shader); + glLinkProgram(g_Data.ShaderProgram); + + // Check linking + glGetProgramiv(g_Data.ShaderProgram, GL_LINK_STATUS, &success); + if (!success) { + char info_log[512]; + glGetProgramInfoLog(g_Data.ShaderProgram, 512, nullptr, info_log); + IM_ASSERT_USER_ERROR(false, "ImPlot3D: Shader program linking failed!"); + IMGUI_DEBUG_PRINTF("ImPlot3D: Shader linking error: %s\n", info_log); + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + return false; + } + + // Clean up shaders (no longer needed after linking) + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + // Get attribute locations + g_Data.AttribLocationPosition = glGetAttribLocation(g_Data.ShaderProgram, "Position"); + g_Data.AttribLocationColor = glGetAttribLocation(g_Data.ShaderProgram, "Color"); + g_Data.UniformLocationRotation = glGetUniformLocation(g_Data.ShaderProgram, "u_Rotation"); + g_Data.UniformLocationViewportSize = glGetUniformLocation(g_Data.ShaderProgram, "u_ViewportSize"); + g_Data.UniformLocationEnableClip = glGetUniformLocation(g_Data.ShaderProgram, "u_EnableClip"); + g_Data.UniformLocationClipMin = glGetUniformLocation(g_Data.ShaderProgram, "u_ClipMin"); + g_Data.UniformLocationClipMax = glGetUniformLocation(g_Data.ShaderProgram, "u_ClipMax"); + + // Create buffers + glGenVertexArrays(1, &g_Data.VAO); + glGenBuffers(1, &g_Data.VBO); + glGenBuffers(1, &g_Data.EBO); + + // Setup VAO with vertex attribute configuration + // This only needs to be done once - the VAO stores this state + glBindVertexArray(g_Data.VAO); + + // Bind buffers to VAO + glBindBuffer(GL_ARRAY_BUFFER, g_Data.VBO); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_Data.EBO); + + // Configure vertex attributes + // Position: 3 floats at offset 0 + glEnableVertexAttribArray(g_Data.AttribLocationPosition); + glVertexAttribPointer(g_Data.AttribLocationPosition, 3, GL_FLOAT, GL_FALSE, + 4 * sizeof(float), // stride: 3 floats (xyz) + 1 float (packed color as 4 bytes) + (void*)0); + + // Color: 4 unsigned bytes at offset 12 (after 3 floats) + glEnableVertexAttribArray(g_Data.AttribLocationColor); + glVertexAttribPointer(g_Data.AttribLocationColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, 4 * sizeof(float), (void*)(3 * sizeof(float))); + + // Unbind VAO (stores all the state we just configured) + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Create FBO for offscreen rendering + glGenFramebuffers(1, &g_Data.FBO); + + return true; +} + +IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_Shutdown() { + // Delete OpenGL resources + if (g_Data.ShaderProgram) + glDeleteProgram(g_Data.ShaderProgram); + if (g_Data.VAO) + glDeleteVertexArrays(1, &g_Data.VAO); + if (g_Data.VBO) + glDeleteBuffers(1, &g_Data.VBO); + if (g_Data.EBO) + glDeleteBuffers(1, &g_Data.EBO); + if (g_Data.FBO) + glDeleteFramebuffers(1, &g_Data.FBO); + + // Clean up any remaining textures + for (int i = 0; i < g_CreatedTextures.Size; i++) { + glDeleteTextures(1, &g_CreatedTextures[i]); + } + g_CreatedTextures.clear(); + + // Reset backend data + g_Data = ImPlot3D_ImplOpenGL3_Data(); +} + +ImTextureID ImPlot3D_ImplOpenGL3_CreateRGBATexture(const ImVec2& size) { + int width = (int)size.x; + int height = (int)size.y; + + // Use ImGui's error handling for user-facing errors + IM_ASSERT_USER_ERROR(width > 0 && height > 0, "ImPlot3D_ImplOpenGL3_CreateTexture: size must be positive!"); + if (width <= 0 || height <= 0) + return ImTextureID_Invalid; + + // Create rainbow gradient pixel data (RGBA) + size_t pixel_count = (size_t)width * (size_t)height; + size_t data_size = pixel_count * 4; // 4 bytes per pixel (RGBA) + + // Allocate using ImGui's allocation + unsigned char* pixels = (unsigned char*)IM_ALLOC(data_size); + + // Fill with zeros (transparent black) + memset(pixels, 0, data_size); + + // Generate OpenGL texture + GLuint texture_id = 0; + GLint last_texture = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + // Set texture parameters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Upload pixel data to GPU + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + // Free CPU memory + IM_FREE(pixels); + + // Restore previous texture binding + glBindTexture(GL_TEXTURE_2D, last_texture); + + // Track this texture for cleanup + g_CreatedTextures.push_back(texture_id); + + // Return as ImTextureID (cast GLuint to ImTextureID) + return (ImTextureID)(intptr_t)texture_id; +} + +IMPLOT3D_IMPL_API ImTextureID ImPlot3D_ImplOpenGL3_CreateDepthTexture(const ImVec2& size) { + int width = (int)size.x; + int height = (int)size.y; + + // Use ImGui's error handling for user-facing errors + IM_ASSERT_USER_ERROR(width > 0 && height > 0, "ImPlot3D_ImplOpenGL3_CreateDepthTexture: size must be positive!"); + if (width <= 0 || height <= 0) + return ImTextureID_Invalid; + + // Generate OpenGL depth texture + GLuint texture_id = 0; + GLint last_texture = 0; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + + glGenTextures(1, &texture_id); + glBindTexture(GL_TEXTURE_2D, texture_id); + + // Create depth texture + // GL_DEPTH_COMPONENT24: 24-bit depth precision (good balance of precision and memory) + // Note: ImPlot3D targets OpenGL 3.0+ where depth textures are core + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr); + + // Set texture parameters (recommended for depth textures) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // No interpolation for depth + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Clamp to avoid edge artifacts + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Restore previous texture binding + glBindTexture(GL_TEXTURE_2D, last_texture); + + // Track this texture for cleanup + g_CreatedTextures.push_back(texture_id); + + // Return as ImTextureID (cast GLuint to ImTextureID) + return (ImTextureID)(intptr_t)texture_id; +} + +void ImPlot3D_ImplOpenGL3_DestroyTexture(ImTextureID tex_id) { + GLuint texture_id = (GLuint)(intptr_t)tex_id; + + if (texture_id != 0) { + glDeleteTextures(1, &texture_id); + + // Remove from tracking vector + for (int i = 0; i < g_CreatedTextures.Size; i++) { + if (g_CreatedTextures[i] == texture_id) { + g_CreatedTextures.erase(&g_CreatedTextures[i]); + break; + } + } + } +} + +IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_RenderDrawData(ImDrawData3D* draw_data) { + if (!draw_data) + return; + + // First pass: Handle deletions and cleanup + for (int i = draw_data->PlotData.Size - 1; i >= 0; i--) { + ImDrawData3DPlot* plot_data = &draw_data->PlotData[i]; + if (plot_data->ShouldDelete) { + // Clean up textures + if (plot_data->ColorTextureID != ImTextureID_Invalid) { + ImPlot3D_ImplOpenGL3_DestroyTexture(plot_data->ColorTextureID); + } + if (plot_data->DepthTextureID != ImTextureID_Invalid) { + ImPlot3D_ImplOpenGL3_DestroyTexture(plot_data->DepthTextureID); + } + // Remove from array + draw_data->PlotData.erase(draw_data->PlotData.Data + i); + } + } + + // Second pass: Render active plots + for (int i = 0; i < draw_data->PlotData.Size; i++) { + ImDrawData3DPlot* plot_data = &draw_data->PlotData[i]; + if (!plot_data->ShouldRender) + continue; + + // Handle texture resizing + if (plot_data->ShouldResize) { + // Destroy old textures if they exist + if (plot_data->ColorTextureID != ImTextureID_Invalid) { + ImPlot3D_ImplOpenGL3_DestroyTexture(plot_data->ColorTextureID); + plot_data->ColorTextureID = ImTextureID_Invalid; + } + if (plot_data->DepthTextureID != ImTextureID_Invalid) { + ImPlot3D_ImplOpenGL3_DestroyTexture(plot_data->DepthTextureID); + plot_data->DepthTextureID = ImTextureID_Invalid; + } + + // Create new textures with current size + plot_data->ColorTextureID = ImPlot3D_ImplOpenGL3_CreateRGBATexture(plot_data->TextureSize); + plot_data->DepthTextureID = ImPlot3D_ImplOpenGL3_CreateDepthTexture(plot_data->TextureSize); + } + + // Get texture IDs + GLuint color_texture = (GLuint)(intptr_t)plot_data->ColorTextureID; + GLuint depth_texture = (GLuint)(intptr_t)plot_data->DepthTextureID; + if (color_texture == 0) + continue; + + // Skip if no vertices to render + if (plot_data->VtxBuffer.Size == 0 || plot_data->IdxBuffer.Size == 0) + continue; + + // Bind framebuffer + glBindFramebuffer(GL_FRAMEBUFFER, g_Data.FBO); + + // Attach textures to framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color_texture, 0); + if (depth_texture != 0) { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_texture, 0); + } + + // Check framebuffer status + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + IMGUI_DEBUG_PRINTF("ImPlot3D: Framebuffer not complete! Status: 0x%x\n", status); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + continue; + } + + // Set viewport to texture size + glViewport(0, 0, (int)plot_data->GetPlotWidth(), (int)plot_data->GetPlotHeight()); + + // Clear color and depth + glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + glClearDepth(1.0); // Clear depth to far plane + glClear(GL_COLOR_BUFFER_BIT | (depth_texture != 0 ? GL_DEPTH_BUFFER_BIT : 0)); + + // Enable depth testing + if (depth_texture != 0) { + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); // Closer = smaller Z after negation + glDepthMask(GL_TRUE); // Enable depth writes + } + + // Enable alpha blending (same as ImGui's OpenGL3 backend) + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + // Use shader program + glUseProgram(g_Data.ShaderProgram); + + // Convert quaternion to rotation matrix and upload to shader + ImPlot3DQuat rot = plot_data->Rotation; + float rot_matrix[16]; + // Quaternion to matrix conversion (column-major for OpenGL) + float xx = (float)(rot.x * rot.x); + float yy = (float)(rot.y * rot.y); + float zz = (float)(rot.z * rot.z); + float xy = (float)(rot.x * rot.y); + float xz = (float)(rot.x * rot.z); + float yz = (float)(rot.y * rot.z); + float wx = (float)(rot.w * rot.x); + float wy = (float)(rot.w * rot.y); + float wz = (float)(rot.w * rot.z); + + rot_matrix[0] = 1.0f - 2.0f * (yy + zz); + rot_matrix[1] = 2.0f * (xy + wz); + rot_matrix[2] = 2.0f * (xz - wy); + rot_matrix[3] = 0.0f; + + rot_matrix[4] = 2.0f * (xy - wz); + rot_matrix[5] = 1.0f - 2.0f * (xx + zz); + rot_matrix[6] = 2.0f * (yz + wx); + rot_matrix[7] = 0.0f; + + rot_matrix[8] = 2.0f * (xz + wy); + rot_matrix[9] = 2.0f * (yz - wx); + rot_matrix[10] = 1.0f - 2.0f * (xx + yy); + rot_matrix[11] = 0.0f; + + rot_matrix[12] = 0.0f; + rot_matrix[13] = 0.0f; + rot_matrix[14] = 0.0f; + rot_matrix[15] = 1.0f; + + glUniformMatrix4fv(g_Data.UniformLocationRotation, 1, GL_FALSE, rot_matrix); + + // Upload viewport size uniform + glUniform2f(g_Data.UniformLocationViewportSize, plot_data->GetPlotWidth(), plot_data->GetPlotHeight()); + + // Upload clipping uniforms + glUniform1i(g_Data.UniformLocationEnableClip, plot_data->ShouldClip ? 1 : 0); + if (plot_data->ShouldClip) { + glUniform3f(g_Data.UniformLocationClipMin, (float)plot_data->ClipMin.x, (float)plot_data->ClipMin.y, (float)plot_data->ClipMin.z); + glUniform3f(g_Data.UniformLocationClipMax, (float)plot_data->ClipMax.x, (float)plot_data->ClipMax.y, (float)plot_data->ClipMax.z); + } + + // Convert vertices from double to float for OpenGL 3.x compatibility + struct GLVertex { + float x, y, z; + ImU32 col; + }; + + ImVector gl_vertices; + gl_vertices.resize(plot_data->VtxBuffer.Size); + for (int v = 0; v < plot_data->VtxBuffer.Size; v++) { + const ImDrawVert3D& src = plot_data->VtxBuffer.Data[v]; + GLVertex& dst = gl_vertices.Data[v]; + dst.x = (float)src.pos.x; + dst.y = (float)src.pos.y; + dst.z = (float)src.pos.z; + dst.col = src.col; + } + + // Bind VAO (this restores all the vertex attribute configuration from Init) + glBindVertexArray(g_Data.VAO); + + // Bind and upload vertex data to VBO + glBindBuffer(GL_ARRAY_BUFFER, g_Data.VBO); + glBufferData(GL_ARRAY_BUFFER, gl_vertices.Size * sizeof(GLVertex), gl_vertices.Data, GL_STREAM_DRAW); + + // Bind and upload index data to EBO + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, g_Data.EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, plot_data->IdxBuffer.Size * sizeof(ImDrawIdx3D), plot_data->IdxBuffer.Data, GL_STREAM_DRAW); + + // Draw primitives using command buffer + for (int cmd_i = 0; cmd_i < plot_data->CmdBuffer.Size; cmd_i++) { + const ImDrawCmd3D& cmd = plot_data->CmdBuffer[cmd_i]; + + // Draw triangles for this command + glDrawElements(GL_TRIANGLES, cmd.IdxCount, GL_UNSIGNED_INT, (void*)(cmd.IdxOffset * sizeof(ImDrawIdx3D))); + } + + // Unbind VAO + glBindVertexArray(0); + glUseProgram(0); + + // Disable states + glDisable(GL_BLEND); + if (depth_texture != 0) { + glDisable(GL_DEPTH_TEST); + } + } + + // Third pass: Reset buffers + for (int i = 0; i < draw_data->PlotData.Size; i++) { + ImDrawData3DPlot* plot_data = &draw_data->PlotData[i]; + plot_data->ResetBuffers(); + } + + // Unbind framebuffer (return to default) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +#endif // #ifndef IMGUI_DISABLE diff --git a/backends/implot3d_impl_opengl3.h b/backends/implot3d_impl_opengl3.h new file mode 100644 index 0000000..e011f66 --- /dev/null +++ b/backends/implot3d_impl_opengl3.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +// SPDX-FileCopyrightText: 2024-2026 Breno Cunha Queiroz + +#pragma once +#include "implot3d.h" +#include "implot3d_internal.h" +#ifndef IMGUI_DISABLE + +IMPLOT3D_IMPL_API bool ImPlot3D_ImplOpenGL3_Init(); +IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_Shutdown(); + +IMPLOT3D_IMPL_API void ImPlot3D_ImplOpenGL3_RenderDrawData(ImDrawData3D* draw_data); + +#endif // #ifndef IMGUI_DISABLE diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 44ddda0..0747733 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -65,9 +65,10 @@ set(IMPLOT3D_SOURCE ${IMPLOT3D_SOURCE_DIR}/implot3d_demo.cpp ${IMPLOT3D_SOURCE_DIR}/implot3d_items.cpp ${IMPLOT3D_SOURCE_DIR}/implot3d_meshes.cpp + ${IMPLOT3D_SOURCE_DIR}/backends/implot3d_impl_opengl3.cpp ) add_library(implot3d STATIC ${IMPLOT3D_SOURCE}) -target_include_directories(implot3d PUBLIC ${IMPLOT3D_SOURCE_DIR}) +target_include_directories(implot3d PUBLIC ${IMPLOT3D_SOURCE_DIR};${IMPLOT3D_SOURCE_DIR}/backends/) target_link_libraries(implot3d PUBLIC imgui) # Add the executable diff --git a/example/main.cpp b/example/main.cpp index 09d40af..8fbb1ec 100644 --- a/example/main.cpp +++ b/example/main.cpp @@ -8,6 +8,8 @@ #include "imgui_impl_opengl3.h" #include "implot.h" #include "implot3d.h" +#include "implot3d_internal.h" +#include "implot3d_impl_opengl3.h" #include #include @@ -61,6 +63,7 @@ int main() { // Setup backend ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version); + ImPlot3D_ImplOpenGL3_Init(); // Main loop while (!glfwWindowShouldClose(window)) { @@ -76,13 +79,17 @@ int main() { ImPlot::ShowDemoWindow(); ImPlot3D::ShowDemoWindow(); - // Render + // Prepare draw data for rendering ImGui::Render(); + ImPlot3D::Render(); + + // Render draw data int display_w, display_h; glfwGetFramebufferSize(window, &display_w, &display_h); glViewport(0, 0, display_w, display_h); glClearColor(0.1f, 0.1f, 0.1f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); + ImPlot3D_ImplOpenGL3_RenderDrawData(ImPlot3D::GetDrawData()); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); // Swap buffers @@ -90,6 +97,7 @@ int main() { } // Cleanup + ImPlot3D_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImPlot3D::DestroyContext(); diff --git a/implot3d.cpp b/implot3d.cpp index 257ff11..760fb0f 100644 --- a/implot3d.cpp +++ b/implot3d.cpp @@ -171,6 +171,115 @@ ImPlot3DContext* GetCurrentContext() { return GImPlot3D; } void SetCurrentContext(ImPlot3DContext* ctx) { GImPlot3D = ctx; } +void Render() { + ImPlot3DContext& gp = *GImPlot3D; + IM_ASSERT_USER_ERROR(gp.CurrentPlot == nullptr, "Render() called between BeginPlot() and EndPlot()!"); + + // Mark all existing plot data for deletion + for (int i = 0; i < gp.DrawData.PlotData.Size; i++) { + gp.DrawData.PlotData[i].ShouldDelete = true; + gp.DrawData.PlotData[i].ShouldRender = false; + } + + // Update plot data for all active plots + for (int i = 0; i < gp.Plots.GetBufSize(); i++) { + ImPlot3DPlot* plot = gp.Plots.GetByIndex(i); + if (!plot || !plot->Initialized) + continue; + + // Find or create render data for this plot + ImDrawData3DPlot* plot_data = gp.DrawData.FindOrAddPlot(plot->ID); + plot_data->ShouldDelete = false; + plot_data->ShouldRender = true; + + // Check if texture needs resizing + ImVec2 current_size = plot->PlotRect.GetSize(); + if (plot_data->ColorTextureID == ImTextureID_Invalid) { + plot_data->ShouldResize = true; + plot_data->TextureSize = current_size; + } else { + if (ImFabs(plot_data->TextureSize.x - current_size.x) > 1.0f || ImFabs(plot_data->TextureSize.y - current_size.y) > 1.0f) { + plot_data->ShouldResize = true; + plot_data->TextureSize = current_size; + } else { + plot_data->ShouldResize = false; + } + } + + // Copy draw list data with sorting + // Build triangle references for sorting + const int tri_count = plot->DrawList.ZBuffer.Size; + + if (tri_count > 0) { + struct TriRef { + double z; + int tri_idx; + }; + TriRef* tris = (TriRef*)IM_ALLOC(sizeof(TriRef) * tri_count); + for (int i = 0; i < tri_count; i++) { + tris[i].z = plot->DrawList.ZBuffer[i]; + tris[i].tri_idx = i; + } + + // Sort triangles by depth (back to front) + ImQsort(tris, (size_t)tri_count, sizeof(TriRef), [](const void* a, const void* b) { + double za = ((const TriRef*)a)->z; + double zb = ((const TriRef*)b)->z; + return (za < zb) ? -1 : (za > zb) ? 1 : 0; + }); + + // Copy indices in sorted order + plot_data->IdxBuffer.resize(tri_count * 3); + ImDrawIdx3D* idx_in = plot->DrawList.IdxBuffer.Data; + for (int i = 0; i < tri_count; i++) { + int tri_i = tris[i].tri_idx; + int base_idx = tri_i * 3; + plot_data->IdxBuffer[i * 3 + 0] = idx_in[base_idx + 0]; + plot_data->IdxBuffer[i * 3 + 1] = idx_in[base_idx + 1]; + plot_data->IdxBuffer[i * 3 + 2] = idx_in[base_idx + 2]; + } + + IM_FREE(tris); + } else { + plot_data->IdxBuffer.resize(0); + } + + // Copy vertices + plot_data->VtxBuffer.resize(plot->DrawList.VtxBuffer.Size); + memcpy(plot_data->VtxBuffer.Data, plot->DrawList.VtxBuffer.Data, plot->DrawList.VtxBuffer.Size * sizeof(ImDrawVert3D)); + + // Copy commands + plot_data->CmdBuffer = plot->DrawList.CmdBuffer; + + // Copy line and marker primitives + plot_data->LineBuffer = plot->DrawList.LineBuffer; + plot_data->MarkerBuffer = plot->DrawList.MarkerBuffer; + + // Copy plot state needed for rendering + plot_data->Rotation = plot->Rotation; + plot_data->PlotRectMin = plot->PlotRect.Min; + plot_data->PlotRectMax = plot->PlotRect.Max; + + // Set clipping parameters + // Note: NDC cube is [-0.5*NDCScale, 0.5*NDCScale] per axis + plot_data->ShouldClip = !ImHasFlag(plot->Flags, ImPlot3DFlags_NoClip); + if (plot_data->ShouldClip) { + // Clip to full NDC cube using per-axis NDCScale + plot_data->ClipMin = + ImPlot3DPoint(-0.5 * plot->Axes[ImAxis3D_X].NDCScale, -0.5 * plot->Axes[ImAxis3D_Y].NDCScale, -0.5 * plot->Axes[ImAxis3D_Z].NDCScale); + plot_data->ClipMax = + ImPlot3DPoint(0.5 * plot->Axes[ImAxis3D_X].NDCScale, 0.5 * plot->Axes[ImAxis3D_Y].NDCScale, 0.5 * plot->Axes[ImAxis3D_Z].NDCScale); + } + + plot->DrawList.ResetBuffers(); // Clear plot's draw list for next frame + } +} + +ImDrawData3D* GetDrawData() { + ImPlot3DContext& gp = *GImPlot3D; + return &gp.DrawData; +} + //----------------------------------------------------------------------------- // [SECTION] Text Utils //----------------------------------------------------------------------------- @@ -1636,8 +1745,28 @@ void EndPlot() { IM_ASSERT_USER_ERROR(gp.CurrentPlot != nullptr, "Mismatched BeginPlot()/EndPlot()!"); ImPlot3DPlot& plot = *gp.CurrentPlot; - // Move triangles from 3D draw list to ImGui draw list - plot.DrawList.SortedMoveToImGuiDrawList(); + if (gp.UseImPlot3DBackend) { + // Find the texture from the draw data + ImTextureID texture_id = ImTextureID_Invalid; + for (int i = 0; i < gp.DrawData.PlotData.Size; i++) { + if (gp.DrawData.PlotData[i].PlotID == plot.ID) { + texture_id = gp.DrawData.PlotData[i].ColorTextureID; + break; + } + } + + // Only draw if texture is valid + if (texture_id != ImTextureID_Invalid) { + ImDrawList& draw_list = *ImGui::GetWindowDrawList(); + ImVec2 uv0 = ImVec2(0, 0); + ImVec2 uv1 = ImVec2(1, 1); + ImU32 col = IM_COL32(255, 255, 255, 255); + draw_list.AddImage(texture_id, plot.PlotRect.Min, plot.PlotRect.Max, uv0, uv1, col); + } + } else { + // Move triangles from 3D draw list to ImGui draw list + plot.DrawList.SortedMoveToImGuiDrawList(); + } // Handle data fitting if (plot.FitThisFrame) { @@ -1972,6 +2101,8 @@ void SetupLegend(ImPlot3DLocation location, ImPlot3DLegendFlags flags) { // [SECTION] Plot Utils //----------------------------------------------------------------------------- +ImPool* GetPlots() { return &GImPlot3D->Plots; } + ImPlot3DPlot* GetCurrentPlot() { return GImPlot3D->CurrentPlot; } void BustPlotCache() { @@ -3181,6 +3312,7 @@ bool ColormapSlider(const char* label, float* t, ImVec4* out, const char* format void InitializeContext(ImPlot3DContext* ctx) { ResetContext(ctx); + ctx->UseImPlot3DBackend = false; // Disabled by default const ImU32 Deep[] = {4289753676, 4283598045, 4285048917, 4283584196, 4289950337, 4284512403, 4291005402, 4287401100, 4285839820, 4291671396}; const ImU32 Dark[] = {4280031972, 4290281015, 4283084621, 4288892568, 4278222847, 4281597951, 4280833702, 4290740727, 4288256409}; @@ -3600,8 +3732,8 @@ double ImPlot3DQuat::Dot(const ImPlot3DQuat& rhs) const { return x * rhs.x + y * // [SECTION] ImDrawList3D //----------------------------------------------------------------------------- -void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) { - IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0); +void ImDrawList3D::PrimReserve(int idx_count, int vtx_count, int z_count) { + IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && z_count >= 0); int vtx_buffer_old_size = VtxBuffer.Size; VtxBuffer.resize(vtx_buffer_old_size + vtx_count); @@ -3612,16 +3744,68 @@ void ImDrawList3D::PrimReserve(int idx_count, int vtx_count) { _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; int z_buffer_old_size = ZBuffer.Size; - ZBuffer.resize(z_buffer_old_size + idx_count / 3); + ZBuffer.resize(z_buffer_old_size + z_count); _ZWritePtr = ZBuffer.Data + z_buffer_old_size; } -void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count) { - IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && idx_count % 3 == 0); +void ImDrawList3D::PrimUnreserve(int idx_count, int vtx_count, int z_count) { + IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0 && z_count >= 0); VtxBuffer.shrink(VtxBuffer.Size - vtx_count); IdxBuffer.shrink(IdxBuffer.Size - idx_count); - ZBuffer.shrink(ZBuffer.Size - idx_count / 3); + ZBuffer.shrink(ZBuffer.Size - z_count); +} + +void ImDrawList3D::PrimReserveCmd(int cmd_count) { + int cmd_buffer_old_size = CmdBuffer.Size; + CmdBuffer.resize(cmd_buffer_old_size + cmd_count); + _CmdWritePtr = CmdBuffer.Data + cmd_buffer_old_size; +} + +void ImDrawList3D::AddTriangleCmd(int idx_count) { + // Check if we can extend the last command + if (CmdBuffer.Size > 0) { + ImDrawCmd3D* last_cmd = &CmdBuffer[CmdBuffer.Size - 1]; + // Extend existing triangle command + last_cmd->IdxCount += idx_count; + return; + } + + // Create new triangle command + // IdxOffset is where the next set of indices will start + unsigned int next_offset = 0; + if (CmdBuffer.Size > 0) { + const ImDrawCmd3D& prev_cmd = CmdBuffer[CmdBuffer.Size - 1]; + next_offset = prev_cmd.IdxOffset + prev_cmd.IdxCount; + } + + PrimReserveCmd(1); + _CmdWritePtr->IdxOffset = next_offset; + _CmdWritePtr->IdxCount = idx_count; +} + +void ImDrawList3D::AddLine(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, double z, ImU32 col0, ImU32 col1, float weight) { + ImDrawLinePrim prim; + prim.p0 = p0; + prim.p1 = p1; + prim.z = z; + prim.col0 = col0; + prim.col1 = col1; + prim.weight = weight; + LineBuffer.push_back(prim); +} + +void ImDrawList3D::AddMarker(const ImPlot3DPoint& center, double z, ImU32 fill_col, ImU32 line_col, float size, float line_weight, + ImPlot3DMarker type) { + ImDrawMarkerPrim prim; + prim.center = center; + prim.z = z; + prim.fill_col = fill_col; + prim.line_col = line_col; + prim.size = size; + prim.line_weight = line_weight; + prim.type = type; + MarkerBuffer.push_back(prim); } void ImDrawList3D::SetTexture(ImTextureRef tex_ref) { @@ -3660,6 +3844,8 @@ void ImDrawList3D::ResetTexture() { SetTexture(ImTextureID(0)); } void ImDrawList3D::SortedMoveToImGuiDrawList() { ImDrawList& draw_list = *ImGui::GetWindowDrawList(); + ImPlot3DContext& gp = *ImPlot3D::GetCurrentContext(); + const ImPlot3DPlot& plot = *gp.CurrentPlot; const int tri_count = ZBuffer.Size; if (tri_count == 0) { @@ -3689,19 +3875,29 @@ void ImDrawList3D::SortedMoveToImGuiDrawList() { // Reserve space in the ImGui draw list draw_list.PrimReserve(IdxBuffer.Size, VtxBuffer.Size); - // Copy vertices (no reordering needed) - memcpy(draw_list._VtxWritePtr, VtxBuffer.Data, VtxBuffer.Size * sizeof(ImDrawVert)); + // Convert vertices from NDC to pixel coordinates and copy to ImGui draw list + ImDrawVert* vtx_out = draw_list._VtxWritePtr; + for (int i = 0; i < VtxBuffer.Size; i++) { + const ImDrawVert3D& vtx_in = VtxBuffer[i]; + // Convert 3D NDC position to 2D pixel position + ImVec2 pos_pix = ImPlot3D::NDCToPixels(plot, vtx_in.pos); + vtx_out[i].pos = pos_pix; + vtx_out[i].uv = vtx_in.uv; + vtx_out[i].col = vtx_in.col; + } + unsigned int idx_offset = draw_list._VtxCurrentIdx; draw_list._VtxWritePtr += VtxBuffer.Size; draw_list._VtxCurrentIdx += (unsigned int)VtxBuffer.Size; - // Maximum index allowed to not overflow ImDrawIdx - unsigned int max_index_allowed = MaxIdx() - idx_offset; + // Maximum index allowed in ImDrawIdx (ImGui's index type) + unsigned int max_imgui_idx = (sizeof(ImDrawIdx) == 2) ? 65535 : 4294967295u; + unsigned int max_index_allowed = (max_imgui_idx > idx_offset) ? (max_imgui_idx - idx_offset) : 0; // Copy indices with triangle sorting based on distance from viewer ImDrawIdx* idx_out_begin = draw_list._IdxWritePtr; ImDrawIdx* idx_out = idx_out_begin; - ImDrawIdx* idx_in = IdxBuffer.Data; + ImDrawIdx3D* idx_in = IdxBuffer.Data; for (int i = 0; i < tri_count; i++) { int tri_i = tris[i].tri_idx; int base_idx = tri_i * 3; @@ -3937,11 +4133,15 @@ void ImPlot3D::ShowMetricsWindow(bool* p_popen) { ImGui::Text("Mouse Position: [%.0f,%.0f]", io.MousePos.x, io.MousePos.y); ImGui::Separator(); if (ImGui::TreeNode("Tools")) { + ImGui::SeparatorText("Cache"); if (ImGui::Button("Bust Plot Cache")) BustPlotCache(); ImGui::SameLine(); if (ImGui::Button("Bust Item Cache")) BustItemCache(); + ImGui::SeparatorText("Rendering"); + ImGui::Checkbox("Use ImPlot3D Backend", &gp.UseImPlot3DBackend); + ImGui::SeparatorText("Visualize"); ImGui::Checkbox("Show Frame Rects", &show_frame_rects); ImGui::Checkbox("Show Canvas Rects", &show_canvas_rects); ImGui::Checkbox("Show Plot Rects", &show_plot_rects); @@ -4163,6 +4363,19 @@ void ImPlot3D::ShowMetricsWindow(bool* p_popen) { plot.RotationAnimationEnd.y, plot.Rotation.z, plot.RotationAnimationEnd.w); ImGui::BulletText("ViewScale: %.2f", plot.GetViewScale()); + // Look up texture IDs from draw data + ImTextureID color_tex = ImTextureID_Invalid; + ImTextureID depth_tex = ImTextureID_Invalid; + for (int j = 0; j < gp.DrawData.PlotData.Size; j++) { + if (gp.DrawData.PlotData[j].PlotID == plot.ID) { + color_tex = gp.DrawData.PlotData[j].ColorTextureID; + depth_tex = gp.DrawData.PlotData[j].DepthTextureID; + break; + } + } + ImGui::BulletText("ColorTextureID: %d", (int)(size_t)color_tex); + ImGui::BulletText("DepthTextureID: %d", (int)(size_t)depth_tex); + ImGui::TreePop(); } ImGui::PopID(); diff --git a/implot3d.h b/implot3d.h index a529f8e..9783cfd 100644 --- a/implot3d.h +++ b/implot3d.h @@ -45,6 +45,10 @@ #define IMPLOT3D_API #endif +#ifndef IMPLOT3D_IMPL_API +#define IMPLOT3D_IMPL_API IMPLOT3D_API +#endif + #define IMPLOT3D_VERSION "0.4 WIP" // ImPlot3D version #define IMPLOT3D_VERSION_NUM 401 // Integer encoded version #define IMPLOT3D_AUTO -1 // Deduce variable automatically @@ -64,6 +68,9 @@ struct ImPlot3DPlane; struct ImPlot3DBox; struct ImPlot3DRange; struct ImPlot3DQuat; +struct ImDrawData3D; +struct ImDrawData3DPlot; +struct ImPlot3DPlot; // Enums typedef int ImPlot3DCond; // -> ImPlot3DCond_ // Enum: Condition for flags @@ -463,6 +470,11 @@ IMPLOT3D_API ImPlot3DContext* GetCurrentContext(); // Sets the current ImPlot3D context IMPLOT3D_API void SetCurrentContext(ImPlot3DContext* ctx); +// Prepares draw data for rendering. Call this after all ImPlot3D plots have been drawn +IMPLOT3D_API void Render(); +// Returns the draw data for rendering. Valid after Render() and before next NewFrame() +IMPLOT3D_API ImDrawData3D* GetDrawData(); + //----------------------------------------------------------------------------- // [SECTION] Begin/End Plot //----------------------------------------------------------------------------- @@ -1008,8 +1020,7 @@ struct ImPlot3DStyle { // Constructor IMPLOT3D_API ImPlot3DStyle(); ImPlot3DStyle(const ImPlot3DStyle& other) = default; - ImPlot3DStyle& operator=(const ImPlot3DStyle& other) = - default; + ImPlot3DStyle& operator=(const ImPlot3DStyle& other) = default; }; //----------------------------------------------------------------------------- @@ -1038,6 +1049,181 @@ extern unsigned int duck_idx[DUCK_IDX_COUNT]; // Duck indices } // namespace ImPlot3D +//----------------------------------------------------------------------------- +// [SECTION] ImDrawList3D +//----------------------------------------------------------------------------- + +typedef unsigned int ImDrawIdx3D; + +struct ImDrawVert3D { + ImPlot3DPoint pos; + ImVec2 uv; + ImU32 col; +}; + +// Draw command for 3D triangles. Tracks batches of triangles with optional texturing. +struct ImDrawCmd3D { + unsigned int IdxOffset; // Start index in IdxBuffer + unsigned int IdxCount; // Number of indices in this command +}; + +// Line primitive storing two NDC endpoints. Rendered as a screen-space quad by the backend. +struct ImDrawLinePrim { + ImPlot3DPoint p0; // NDC start position + ImPlot3DPoint p1; // NDC end position + double z; // Depth value for Z sorting + ImU32 col0; // Color at p0 + ImU32 col1; // Color at p1 + float weight; // Line width in pixels +}; + +// Marker primitive storing a single NDC center point. Rendered as a screen-aligned sprite by the backend. +struct ImDrawMarkerPrim { + ImPlot3DPoint center; // NDC center position + double z; // Depth value for Z sorting + ImU32 fill_col; // Fill color + ImU32 line_col; // Outline color + float size; // Marker size in pixels + float line_weight; // Outline weight in pixels + ImPlot3DMarker type; // Marker shape +}; + +// List of all primitives to render for a given plot +struct ImDrawList3D { + // [Internal] Define which texture should be used when rendering triangles. + struct ImTextureBufferItem { + ImTextureRef TexRef; + unsigned int VtxIdx; + }; + + // Triangle buffers + ImVector IdxBuffer; // Index buffer (32-bit indices) + ImVector VtxBuffer; // Vertex buffer (stores 3D NDC positions) + ImVector ZBuffer; // Z buffer. Depth value for each triangle + ImVector CmdBuffer; // Command buffer (tracks triangle batch ranges) + // Line and marker buffers (not tessellated; sent to backend for GPU rendering) + ImVector LineBuffer; // Line segment primitives + ImVector MarkerBuffer; // Marker sprite primitives + + unsigned int _VtxCurrentIdx; // [Internal] current vertex index + ImDrawVert3D* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawIdx3D* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + double* _ZWritePtr; // [Internal] point within ZBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawCmd3D* _CmdWritePtr; // [Internal] point within CmdBuffer.Data after each add command (to avoid using the ImVector<> operators too much) + ImDrawListFlags _Flags; // [Internal] draw list flags + ImVector _TextureBuffer; // [Internal] buffer for SetTexture/ResetTexture + ImDrawListSharedData* _SharedData; // [Internal] shared draw list data + + ImDrawList3D() { + _Flags = ImDrawListFlags_None; + _SharedData = nullptr; + ResetBuffers(); + } + + void PrimReserve(int idx_count, int vtx_count, int z_count); + void PrimUnreserve(int idx_count, int vtx_count, int z_count); + void PrimReserveCmd(int cmd_count); + + void AddTriangleCmd(int idx_count); + void AddLine(const ImPlot3DPoint& p0, const ImPlot3DPoint& p1, double z, ImU32 col0, ImU32 col1, float weight); + void AddMarker(const ImPlot3DPoint& center, double z, ImU32 fill_col, ImU32 line_col, float size, float line_weight, ImPlot3DMarker type); + + void SetTexture(ImTextureRef tex_ref); + void ResetTexture(); + + void SortedMoveToImGuiDrawList(); + + void ResetBuffers() { + IdxBuffer.clear(); + VtxBuffer.clear(); + ZBuffer.clear(); + CmdBuffer.clear(); + LineBuffer.clear(); + MarkerBuffer.clear(); + _VtxCurrentIdx = 0; + _VtxWritePtr = VtxBuffer.Data; + _IdxWritePtr = IdxBuffer.Data; + _ZWritePtr = ZBuffer.Data; + _CmdWritePtr = CmdBuffer.Data; + _TextureBuffer.clear(); + ResetTexture(); + } + + constexpr static unsigned int MaxIdx() { return 4294967295u; } // ImDrawIdx3D is always 32-bit +}; + +//----------------------------------------------------------------------------- +// [SECTION] ImDrawData3D +//----------------------------------------------------------------------------- + +// Render data for a single plot. Contains a copy of the plot's draw list and texture state. +struct ImDrawData3DPlot { + ImGuiID PlotID; // ID of the plot this render data belongs to + ImVector IdxBuffer; // Triangle index buffer + ImVector VtxBuffer; // Triangle vertex buffer + ImVector CmdBuffer; // Triangle command buffer + ImVector LineBuffer; // Line segment primitives + ImVector MarkerBuffer; // Marker sprite primitives + ImPlot3DQuat Rotation; // Rotation quaternion for this plot + ImVec2 PlotRectMin; // Plot rectangle min (for viewport) + ImVec2 PlotRectMax; // Plot rectangle max (for viewport) + ImTextureID ColorTextureID; // Final RGBA texture for rendering + ImTextureID DepthTextureID; // Depth texture for depth testing + ImTextureID AccumTextureID; // WBOIT accumulation texture + ImTextureID RevealTextureID; // WBOIT reveal texture + ImVec2 TextureSize; // Current texture size + bool ShouldResize; // Set by Render() if texture needs resizing + bool ShouldRender; // Set by Render() if plot should be rendered + bool ShouldDelete; // Set by Render() if plot no longer exists + bool ShouldClip; // Set by Render() if clipping is enabled + ImPlot3DPoint ClipMin; // Min clip bounds in NDC space + ImPlot3DPoint ClipMax; // Max clip bounds in NDC space + + ImDrawData3DPlot() { + PlotID = 0; + Rotation = ImPlot3DQuat(0.0, 0.0, 0.0, 1.0); + ColorTextureID = ImTextureID_Invalid; + DepthTextureID = ImTextureID_Invalid; + AccumTextureID = ImTextureID_Invalid; + RevealTextureID = ImTextureID_Invalid; + TextureSize = ImVec2(0.0f, 0.0f); + ShouldResize = false; + ShouldRender = false; + ShouldDelete = false; + ShouldClip = true; + ClipMin = ClipMax = ImPlot3DPoint(0.0, 0.0, 0.0); + } + + float GetPlotWidth() const { return PlotRectMax.x - PlotRectMin.x; } + float GetPlotHeight() const { return PlotRectMax.y - PlotRectMin.y; } + + void ResetBuffers() { + IdxBuffer.clear(); + VtxBuffer.clear(); + LineBuffer.clear(); + MarkerBuffer.clear(); + } +}; + +// Draw data for rendering all plots. Valid after Render() and before next NewFrame() +struct ImDrawData3D { + ImVector PlotData; // Render data for all plots + + ImDrawData3D() { PlotData.clear(); } + + // Find or create render data for a given plot ID + ImDrawData3DPlot* FindOrAddPlot(ImGuiID plot_id) { + for (int i = 0; i < PlotData.Size; i++) { + if (PlotData[i].PlotID == plot_id) + return &PlotData[i]; + } + ImDrawData3DPlot new_plot; + new_plot.PlotID = plot_id; + PlotData.push_back(new_plot); + return &PlotData[PlotData.Size - 1]; + } +}; + //----------------------------------------------------------------------------- // [SECTION] Obsolete API //----------------------------------------------------------------------------- @@ -1066,11 +1252,16 @@ extern unsigned int duck_idx[DUCK_IDX_COUNT]; // Duck indices namespace ImPlot3D { // OBSOLETED in v0.4 (from February 2026) -// IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); // OBSOLETED IN v0.4 // Set ImPlotSpec.LineColor/LineWeight or construct ImPlotSpec with { ImPlotSpec_LineColor, color, ImPlotSpec_LineWeight, weight }. +// IMPLOT_API void SetNextLineStyle(const ImVec4& col = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO); // OBSOLETED IN v0.4 // Set +// ImPlotSpec.LineColor/LineWeight or construct ImPlotSpec with { ImPlotSpec_LineColor, color, ImPlotSpec_LineWeight, weight }. -// IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO);// OBSOLETED IN v0.4 // Set ImPlotSpec.FillColor/FillAlpha or construct ImPlotSpec with { ImPlotSpec_FillColor, color, ImPlotSpec_FillAlpha, alpha }. +// IMPLOT_API void SetNextFillStyle(const ImVec4& col = IMPLOT_AUTO_COL, float alpha_mod = IMPLOT_AUTO);// OBSOLETED IN v0.4 // Set +// ImPlotSpec.FillColor/FillAlpha or construct ImPlotSpec with { ImPlotSpec_FillColor, color, ImPlotSpec_FillAlpha, alpha }. -// IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); // OBSOLETED IN v0.4 // Set ImPlotSpec.Marker/MarkerSize/MarkerFillColor/LineWeight/MarkerLineColor or construct ImPlotSpec with { ImPlotSpec_Marker, marker, ImPlotSpec_MarkerSize, size, ImPlotSpec_MarkerFillColor, fill_color, ImPlotSpec_LineWeight, weight, ImPlotSpec_MarkerLineColor, outline }. +// IMPLOT_API void SetNextMarkerStyle(ImPlotMarker marker = IMPLOT_AUTO, float size = IMPLOT_AUTO, const ImVec4& fill = IMPLOT_AUTO_COL, float weight +// = IMPLOT_AUTO, const ImVec4& outline = IMPLOT_AUTO_COL); // OBSOLETED IN v0.4 // Set +// ImPlotSpec.Marker/MarkerSize/MarkerFillColor/LineWeight/MarkerLineColor or construct ImPlotSpec with { ImPlotSpec_Marker, marker, +// ImPlotSpec_MarkerSize, size, ImPlotSpec_MarkerFillColor, fill_color, ImPlotSpec_LineWeight, weight, ImPlotSpec_MarkerLineColor, outline }. // OBSOLETED in v0.3 -> PLANNED REMOVAL in v1.0 IMPLOT3D_DEPRECATED(IMPLOT3D_API ImVec2 GetPlotPos()); // Renamed to GetPlotRectPos() diff --git a/implot3d_demo.cpp b/implot3d_demo.cpp index 3f80c9a..5d48b28 100644 --- a/implot3d_demo.cpp +++ b/implot3d_demo.cpp @@ -555,6 +555,67 @@ void DemoImagePlots() { } } +void DemoDepthBuffer() { + ImGui::BulletText("Two overlapping transparent surfaces to test depth buffer rendering."); + ImGui::BulletText("The surfaces should properly occlude each other based on depth."); + + static float alpha = 0.8f; + ImGui::SliderFloat("Alpha", &alpha, 0.0f, 1.0f); + + // Generate two surfaces + const int size = 10; + static double xs1[size * size], ys1[size * size], zs1[size * size]; + static double xs2[size * size], ys2[size * size], zs2[size * size]; + static bool initialized = false; + + if (!initialized) { + // Define the range for X and Y + const double min_val = -1.0; + const double max_val = 1.0; + const double step = (max_val - min_val) / (size - 1); + + // Surface 1: A tilted plane + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + int idx = i * size + j; + xs1[idx] = min_val + j * step; // X values constant along rows + ys1[idx] = min_val + i * step; // Y values constant along columns + zs1[idx] = 0.3 * ys1[idx] + 0.2 * xs1[idx] + 0.5; + } + } + + // Surface 2: Another tilted plane that intersects the first + for (int i = 0; i < size; i++) { + for (int j = 0; j < size; j++) { + int idx = i * size + j; + xs2[idx] = min_val + j * step; + ys2[idx] = min_val + i * step; + zs2[idx] = -0.2 * ys2[idx] + 0.3 * xs2[idx] + 0.3; + } + } + + initialized = true; + } + + if (ImPlot3D::BeginPlot("Depth Buffer Test", ImVec2(-1, 0))) { + // Plot first surface (red with transparency) + ImPlot3DSpec spec1; + spec1.FillColor = ImVec4(1.0f, 0.0f, 0.0f, alpha); + spec1.FillAlpha = alpha; + spec1.Flags = ImPlot3DSurfaceFlags_NoLines; + ImPlot3D::PlotSurface("Surface 1", xs1, ys1, zs1, size, size, 0.0, 0.0, spec1); + + // Plot second surface (blue with transparency) + ImPlot3DSpec spec2; + spec2.FillColor = ImVec4(0.0f, 0.0f, 1.0f, alpha); + spec2.FillAlpha = alpha; + spec2.Flags = ImPlot3DSurfaceFlags_NoLines; + ImPlot3D::PlotSurface("Surface 2", xs2, ys2, zs2, size, size, 0.0, 0.0, spec2); + + ImPlot3D::EndPlot(); + } +} + void DemoRealtimePlots() { ImGui::BulletText("Move your mouse to change the data!"); static ScrollingBuffer sdata1, sdata2, sdata3; @@ -1601,6 +1662,7 @@ void ShowAllDemos() { DemoHeader("Mesh Plots", DemoMeshPlots); DemoHeader("Realtime Plots", DemoRealtimePlots); DemoHeader("Image Plots", DemoImagePlots); + DemoHeader("Depth Buffer", DemoDepthBuffer); // Plot Options ImGui::SeparatorText("Plot Options"); diff --git a/implot3d_internal.h b/implot3d_internal.h index 42ff7e1..6eacd83 100644 --- a/implot3d_internal.h +++ b/implot3d_internal.h @@ -138,53 +138,6 @@ typedef void (*ImPlot3DLocator)(ImPlot3DTicker& ticker, const ImPlot3DRange& ran // [SECTION] Structs //----------------------------------------------------------------------------- -struct ImDrawList3D { - // [Internal] Define which texture should be used when rendering triangles. - struct ImTextureBufferItem { - ImTextureRef TexRef; - unsigned int VtxIdx; - }; - - ImVector IdxBuffer; // Index buffer - ImVector VtxBuffer; // Vertex buffer - ImVector ZBuffer; // Z buffer. Depth value for each triangle - unsigned int _VtxCurrentIdx; // [Internal] current vertex index - ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) - ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) - double* _ZWritePtr; // [Internal] point within ZBuffer.Data after each add command (to avoid using the ImVector<> operators too much) - ImDrawListFlags _Flags; // [Internal] draw list flags - ImVector _TextureBuffer; // [Internal] buffer for SetTexture/ResetTexture - ImDrawListSharedData* _SharedData; // [Internal] shared draw list data - - ImDrawList3D() { - _Flags = ImDrawListFlags_None; - _SharedData = nullptr; - ResetBuffers(); - } - - void PrimReserve(int idx_count, int vtx_count); - void PrimUnreserve(int idx_count, int vtx_count); - - void SetTexture(ImTextureRef tex_ref); - void ResetTexture(); - - void SortedMoveToImGuiDrawList(); - - void ResetBuffers() { - IdxBuffer.clear(); - VtxBuffer.clear(); - ZBuffer.clear(); - _VtxCurrentIdx = 0; - _VtxWritePtr = VtxBuffer.Data; - _IdxWritePtr = IdxBuffer.Data; - _ZWritePtr = ZBuffer.Data; - _TextureBuffer.clear(); - ResetTexture(); - } - - constexpr static unsigned int MaxIdx() { return sizeof(ImDrawIdx) == 2 ? 65535 : 4294967295; } -}; - struct ImPlot3DNextItemData { ImPlot3DSpec Spec; bool RenderLine; @@ -802,6 +755,8 @@ struct ImPlot3DContext { ImVector StyleModifiers; ImVector ColormapModifiers; ImPlot3DColormapData ColormapData; + bool UseImPlot3DBackend; // Use custom ImPlot3D backend for rendering + ImDrawData3D DrawData; // Draw data populated by Render() }; //----------------------------------------------------------------------------- @@ -874,6 +829,9 @@ IMPLOT3D_API void AddTextRotated(ImDrawList* draw_list, ImVec2 pos, float angle, // [SECTION] Plot Utils //----------------------------------------------------------------------------- +// Get all plots from ImPlot3DContext +IMPLOT3D_API ImPool* GetPlots(); + // Gets the current plot from ImPlot3DContext IMPLOT3D_API ImPlot3DPlot* GetCurrentPlot(); diff --git a/implot3d_items.cpp b/implot3d_items.cpp index 7367822..8f6f567 100644 --- a/implot3d_items.cpp +++ b/implot3d_items.cpp @@ -14,7 +14,6 @@ // [SECTION] Macros & Defines // [SECTION] Template instantiation utility // [SECTION] Item Utils -// [SECTION] Draw Utils // [SECTION] Renderers // [SECTION] Indexers // [SECTION] Getters @@ -46,9 +45,6 @@ // [SECTION] Macros & Defines //----------------------------------------------------------------------------- -#define SQRT_1_2 0.70710678118f -#define SQRT_3_2 0.86602540378f - // clang-format off #ifndef IMPLOT3D_NO_FORCE_INLINE #ifdef _MSC_VER @@ -79,19 +75,6 @@ } \ } while (0) -IMPLOT3D_INLINE void GetLineRenderProps(const ImDrawList3D& draw_list_3d, float& half_weight, ImVec2& tex_uv0, ImVec2& tex_uv1) { - const bool aa = ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLines) && - ImPlot3D::ImHasFlag(draw_list_3d._Flags, ImDrawListFlags_AntiAliasedLinesUseTex); - if (aa) { - ImVec4 tex_uvs = draw_list_3d._SharedData->TexUvLines[(int)(half_weight * 2)]; - tex_uv0 = ImVec2(tex_uvs.x, tex_uvs.y); - tex_uv1 = ImVec2(tex_uvs.z, tex_uvs.w); - half_weight += 1; - } else { - tex_uv0 = tex_uv1 = draw_list_3d._SharedData->TexUvWhitePixel; - } -} - //----------------------------------------------------------------------------- // [SECTION] Template instantiation utility //----------------------------------------------------------------------------- @@ -276,47 +259,6 @@ void BustItemCache() { } } -//----------------------------------------------------------------------------- -// [SECTION] Draw Utils -//----------------------------------------------------------------------------- - -IMPLOT3D_INLINE void PrimLine(ImDrawList3D& draw_list_3d, const ImVec2& P1, const ImVec2& P2, float half_weight, ImU32 col, const ImVec2& tex_uv0, - const ImVec2& tex_uv1, double z) { - float dx = P2.x - P1.x; - float dy = P2.y - P1.y; - IMPLOT3D_NORMALIZE2F(dx, dy); - dx *= half_weight; - dy *= half_weight; - draw_list_3d._VtxWritePtr[0].pos.x = P1.x + dy; - draw_list_3d._VtxWritePtr[0].pos.y = P1.y - dx; - draw_list_3d._VtxWritePtr[0].uv = tex_uv0; - draw_list_3d._VtxWritePtr[0].col = col; - draw_list_3d._VtxWritePtr[1].pos.x = P2.x + dy; - draw_list_3d._VtxWritePtr[1].pos.y = P2.y - dx; - draw_list_3d._VtxWritePtr[1].uv = tex_uv0; - draw_list_3d._VtxWritePtr[1].col = col; - draw_list_3d._VtxWritePtr[2].pos.x = P2.x - dy; - draw_list_3d._VtxWritePtr[2].pos.y = P2.y + dx; - draw_list_3d._VtxWritePtr[2].uv = tex_uv1; - draw_list_3d._VtxWritePtr[2].col = col; - draw_list_3d._VtxWritePtr[3].pos.x = P1.x - dy; - draw_list_3d._VtxWritePtr[3].pos.y = P1.y + dx; - draw_list_3d._VtxWritePtr[3].uv = tex_uv1; - draw_list_3d._VtxWritePtr[3].col = col; - draw_list_3d._VtxWritePtr += 4; - draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx); - draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1); - draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2); - draw_list_3d._IdxWritePtr[3] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx); - draw_list_3d._IdxWritePtr[4] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2); - draw_list_3d._IdxWritePtr[5] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 3); - draw_list_3d._IdxWritePtr += 6; - draw_list_3d._VtxCurrentIdx += 4; - draw_list_3d._ZWritePtr[0] = z; - draw_list_3d._ZWritePtr[1] = z; - draw_list_3d._ZWritePtr += 2; -} - //----------------------------------------------------------------------------- // [SECTION] Renderers //----------------------------------------------------------------------------- @@ -338,92 +280,22 @@ double GetPointDepth(ImPlot3DPoint p) { } struct RendererBase { - RendererBase(int prims, int idx_consumed, int vtx_consumed) : Prims(prims), IdxConsumed(idx_consumed), VtxConsumed(vtx_consumed) {} + RendererBase(int prims, int idx_consumed, int vtx_consumed, int z_consumed) + : Prims(prims), IdxConsumed(idx_consumed), VtxConsumed(vtx_consumed), ZConsumed(z_consumed) {} const unsigned int Prims; // Number of primitives to render const unsigned int IdxConsumed; // Number of indices consumed per primitive const unsigned int VtxConsumed; // Number of vertices consumed per primitive -}; - -template struct RendererMarkersFill : RendererBase { - RendererMarkersFill(const _Getter& getter, const ImVec2* marker, int count, float size, ImU32 col) - : RendererBase(getter.Count, (count - 2) * 3, count), Getter(getter), Marker(marker), Count(count), Size(size), Col(col) {} - - void Init(ImDrawList3D& draw_list_3d) const { UV = draw_list_3d._SharedData->TexUvWhitePixel; } - - IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const { - ImPlot3DPoint p_plot = Getter(prim); - if (!cull_box.Contains(p_plot)) - return false; - ImVec2 p = PlotToPixels(p_plot); - // 3 vertices per triangle - for (int i = 0; i < Count; i++) { - draw_list_3d._VtxWritePtr[0].pos.x = p.x + Marker[i].x * Size; - draw_list_3d._VtxWritePtr[0].pos.y = p.y + Marker[i].y * Size; - draw_list_3d._VtxWritePtr[0].uv = UV; - draw_list_3d._VtxWritePtr[0].col = Col; - draw_list_3d._VtxWritePtr++; - } - // 3 indices per triangle - for (int i = 2; i < Count; i++) { - // Indices - draw_list_3d._IdxWritePtr[0] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx); - draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i - 1); - draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + i); - draw_list_3d._IdxWritePtr += 3; - // Z - draw_list_3d._ZWritePtr[0] = GetPointDepth(p_plot); - draw_list_3d._ZWritePtr++; - } - // Update vertex count - draw_list_3d._VtxCurrentIdx += (ImDrawIdx)Count; - return true; - } - const _Getter& Getter; - const ImVec2* Marker; - const int Count; - const float Size; - const ImU32 Col; - mutable ImVec2 UV; -}; - -template struct RendererMarkersLine : RendererBase { - RendererMarkersLine(const _Getter& getter, const ImVec2* marker, int count, float size, float weight, ImU32 col) - : RendererBase(getter.Count, count / 2 * 6, count / 2 * 4), Getter(getter), Marker(marker), Count(count), - HalfWeight(ImMax(1.0f, weight) * 0.5f), Size(size), Col(col) {} - - void Init(ImDrawList3D& draw_list_3d) const { GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1); } - - IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const { - ImPlot3DPoint p_plot = Getter(prim); - if (!cull_box.Contains(p_plot)) - return false; - ImVec2 p = PlotToPixels(p_plot); - for (int i = 0; i < Count; i = i + 2) { - ImVec2 p1(p.x + Marker[i].x * Size, p.y + Marker[i].y * Size); - ImVec2 p2(p.x + Marker[i + 1].x * Size, p.y + Marker[i + 1].y * Size); - PrimLine(draw_list_3d, p1, p2, HalfWeight, Col, UV0, UV1, GetPointDepth(p_plot)); - } - return true; - } - - const _Getter& Getter; - const ImVec2* Marker; - const int Count; - mutable float HalfWeight; - const float Size; - const ImU32 Col; - mutable ImVec2 UV0; - mutable ImVec2 UV1; + const unsigned int ZConsumed; // Number of depth values consumed per primitive }; template struct RendererLineStrip : RendererBase { RendererLineStrip(const _Getter& getter, ImU32 col, float weight) - : RendererBase(getter.Count - 1, 6, 4), Getter(getter), Col(col), HalfWeight(ImMax(1.0f, weight) * 0.5f) { + : RendererBase(getter.Count - 1, 0, 0, 0), Getter(getter), Col(col), Weight(ImMax(1.0f, weight)) { // Initialize the first point in plot coordinates P1_plot = Getter(0); } - void Init(ImDrawList3D& draw_list_3d) const { GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1); } + void Init(ImDrawList3D&) const {} IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const { ImPlot3DPoint P2_plot = Getter(prim + 1); @@ -433,35 +305,32 @@ template struct RendererLineStrip : RendererBase { bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped); if (visible) { - // Convert clipped points to pixel coordinates - ImVec2 P1_screen = PlotToPixels(P1_clipped); - ImVec2 P2_screen = PlotToPixels(P2_clipped); + // Convert clipped points to NDC coordinates + ImPlot3DPoint P1_ndc = PlotToNDC(P1_clipped); + ImPlot3DPoint P2_ndc = PlotToNDC(P2_clipped); // Render the line segment - PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5)); + draw_list_3d.AddLine(P1_ndc, P2_ndc, GetPointDepth((P1_plot + P2_plot) * 0.5), Col, Col, Weight); } // Update for next segment P1_plot = P2_plot; - return visible; } const _Getter& Getter; const ImU32 Col; - mutable float HalfWeight; + const float Weight; mutable ImPlot3DPoint P1_plot; - mutable ImVec2 UV0; - mutable ImVec2 UV1; }; template struct RendererLineStripSkip : RendererBase { RendererLineStripSkip(const _Getter& getter, ImU32 col, float weight) - : RendererBase(getter.Count - 1, 6, 4), Getter(getter), Col(col), HalfWeight(ImMax(1.0f, weight) * 0.5f) { + : RendererBase(getter.Count - 1, 0, 0, 0), Getter(getter), Col(col), Weight(ImMax(1.0f, weight)) { // Initialize the first point in plot coordinates P1_plot = Getter(0); } - void Init(ImDrawList3D& draw_list_3d) const { GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1); } + void Init(ImDrawList3D&) const {} IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const { // Get the next point in plot coordinates @@ -470,17 +339,16 @@ template struct RendererLineStripSkip : RendererBase { // Check for NaNs in P1_plot and P2_plot if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) && !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) { - // Clip the line segment to the culling box ImPlot3DPoint P1_clipped, P2_clipped; visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped); if (visible) { - // Convert clipped points to pixel coordinates - ImVec2 P1_screen = PlotToPixels(P1_clipped); - ImVec2 P2_screen = PlotToPixels(P2_clipped); + // Convert clipped points to NDC coordinates + ImPlot3DPoint P1_ndc = PlotToNDC(P1_clipped); + ImPlot3DPoint P2_ndc = PlotToNDC(P2_clipped); // Render the line segment - PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5)); + draw_list_3d.AddLine(P1_ndc, P2_ndc, GetPointDepth((P1_plot + P2_plot) * 0.5), Col, Col, Weight); } } @@ -493,17 +361,15 @@ template struct RendererLineStripSkip : RendererBase { const _Getter& Getter; const ImU32 Col; - mutable float HalfWeight; + const float Weight; mutable ImPlot3DPoint P1_plot; - mutable ImVec2 UV0; - mutable ImVec2 UV1; }; template struct RendererLineSegments : RendererBase { RendererLineSegments(const _Getter& getter, ImU32 col, float weight) - : RendererBase(getter.Count / 2, 6, 4), Getter(getter), Col(col), HalfWeight(ImMax(1.0f, weight) * 0.5f) {} + : RendererBase(getter.Count / 2, 0, 0, 0), Getter(getter), Col(col), Weight(ImMax(1.0f, weight)) {} - void Init(ImDrawList3D& draw_list_3d) const { GetLineRenderProps(draw_list_3d, HalfWeight, UV0, UV1); } + void Init(ImDrawList3D&) const {} IMPLOT3D_INLINE bool Render(ImDrawList3D& draw_list_3d, const ImPlot3DBox& cull_box, int prim) const { // Get the segment's endpoints in plot coordinates @@ -512,17 +378,16 @@ template struct RendererLineSegments : RendererBase { // Check for NaNs in P1_plot and P2_plot if (!ImNan(P1_plot.x) && !ImNan(P1_plot.y) && !ImNan(P1_plot.z) && !ImNan(P2_plot.x) && !ImNan(P2_plot.y) && !ImNan(P2_plot.z)) { - // Clip the line segment to the culling box ImPlot3DPoint P1_clipped, P2_clipped; bool visible = cull_box.ClipLineSegment(P1_plot, P2_plot, P1_clipped, P2_clipped); if (visible) { - // Convert clipped points to pixel coordinates - ImVec2 P1_screen = PlotToPixels(P1_clipped); - ImVec2 P2_screen = PlotToPixels(P2_clipped); + // Convert clipped points to NDC coordinates + ImPlot3DPoint P1_ndc = PlotToNDC(P1_clipped); + ImPlot3DPoint P2_ndc = PlotToNDC(P2_clipped); // Render the line segment - PrimLine(draw_list_3d, P1_screen, P2_screen, HalfWeight, Col, UV0, UV1, GetPointDepth((P1_plot + P2_plot) * 0.5)); + draw_list_3d.AddLine(P1_ndc, P2_ndc, GetPointDepth((P1_plot + P2_plot) * 0.5), Col, Col, Weight); } return visible; } @@ -532,13 +397,11 @@ template struct RendererLineSegments : RendererBase { const _Getter& Getter; const ImU32 Col; - mutable float HalfWeight; - mutable ImVec2 UV0; - mutable ImVec2 UV1; + const float Weight; }; template struct RendererTriangleFill : RendererBase { - RendererTriangleFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 3, 3, 3), Getter(getter), Col(col) {} + RendererTriangleFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 3, 3, 3, 1), Getter(getter), Col(col) {} void Init(ImDrawList3D& draw_list_3d) const { UV = draw_list_3d._SharedData->TexUvWhitePixel; } @@ -552,23 +415,20 @@ template struct RendererTriangleFill : RendererBase { if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) && !cull_box.Contains(p_plot[2])) return false; - // Project the triangle vertices to screen space - ImVec2 p[3]; - p[0] = PlotToPixels(p_plot[0]); - p[1] = PlotToPixels(p_plot[1]); - p[2] = PlotToPixels(p_plot[2]); + // Project the triangle vertices to NDC space + ImPlot3DPoint p[3]; + p[0] = PlotToNDC(p_plot[0]); + p[1] = PlotToNDC(p_plot[1]); + p[2] = PlotToNDC(p_plot[2]); // 3 vertices per triangle - draw_list_3d._VtxWritePtr[0].pos.x = p[0].x; - draw_list_3d._VtxWritePtr[0].pos.y = p[0].y; + draw_list_3d._VtxWritePtr[0].pos = p[0]; draw_list_3d._VtxWritePtr[0].uv = UV; draw_list_3d._VtxWritePtr[0].col = Col; - draw_list_3d._VtxWritePtr[1].pos.x = p[1].x; - draw_list_3d._VtxWritePtr[1].pos.y = p[1].y; + draw_list_3d._VtxWritePtr[1].pos = p[1]; draw_list_3d._VtxWritePtr[1].uv = UV; draw_list_3d._VtxWritePtr[1].col = Col; - draw_list_3d._VtxWritePtr[2].pos.x = p[2].x; - draw_list_3d._VtxWritePtr[2].pos.y = p[2].y; + draw_list_3d._VtxWritePtr[2].pos = p[2]; draw_list_3d._VtxWritePtr[2].uv = UV; draw_list_3d._VtxWritePtr[2].col = Col; draw_list_3d._VtxWritePtr += 3; @@ -578,13 +438,16 @@ template struct RendererTriangleFill : RendererBase { draw_list_3d._IdxWritePtr[1] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 1); draw_list_3d._IdxWritePtr[2] = (ImDrawIdx)(draw_list_3d._VtxCurrentIdx + 2); draw_list_3d._IdxWritePtr += 3; - // 1 Z per vertex + // 1 Z per triangle draw_list_3d._ZWritePtr[0] = GetPointDepth((p_plot[0] + p_plot[1] + p_plot[2]) / 3); draw_list_3d._ZWritePtr++; // Update vertex count draw_list_3d._VtxCurrentIdx += 3; + // Add triangle command + draw_list_3d.AddTriangleCmd(3); + return true; } @@ -594,7 +457,7 @@ template struct RendererTriangleFill : RendererBase { }; template struct RendererQuadFill : RendererBase { - RendererQuadFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 4, 6, 4), Getter(getter), Col(col) {} + RendererQuadFill(const _Getter& getter, ImU32 col) : RendererBase(getter.Count / 4, 6, 4, 2), Getter(getter), Col(col) {} void Init(ImDrawList3D& draw_list_3d) const { UV = draw_list_3d._SharedData->TexUvWhitePixel; } @@ -609,31 +472,27 @@ template struct RendererQuadFill : RendererBase { if (!cull_box.Contains(p_plot[0]) && !cull_box.Contains(p_plot[1]) && !cull_box.Contains(p_plot[2]) && !cull_box.Contains(p_plot[3])) return false; - // Project the quad vertices to screen space - ImVec2 p[4]; - p[0] = PlotToPixels(p_plot[0]); - p[1] = PlotToPixels(p_plot[1]); - p[2] = PlotToPixels(p_plot[2]); - p[3] = PlotToPixels(p_plot[3]); + // Project the quad vertices to NDC space + ImPlot3DPoint p[4]; + p[0] = PlotToNDC(p_plot[0]); + p[1] = PlotToNDC(p_plot[1]); + p[2] = PlotToNDC(p_plot[2]); + p[3] = PlotToNDC(p_plot[3]); // Add vertices for two triangles - draw_list_3d._VtxWritePtr[0].pos.x = p[0].x; - draw_list_3d._VtxWritePtr[0].pos.y = p[0].y; + draw_list_3d._VtxWritePtr[0].pos = p[0]; draw_list_3d._VtxWritePtr[0].uv = UV; draw_list_3d._VtxWritePtr[0].col = Col; - draw_list_3d._VtxWritePtr[1].pos.x = p[1].x; - draw_list_3d._VtxWritePtr[1].pos.y = p[1].y; + draw_list_3d._VtxWritePtr[1].pos = p[1]; draw_list_3d._VtxWritePtr[1].uv = UV; draw_list_3d._VtxWritePtr[1].col = Col; - draw_list_3d._VtxWritePtr[2].pos.x = p[2].x; - draw_list_3d._VtxWritePtr[2].pos.y = p[2].y; + draw_list_3d._VtxWritePtr[2].pos = p[2]; draw_list_3d._VtxWritePtr[2].uv = UV; draw_list_3d._VtxWritePtr[2].col = Col; - draw_list_3d._VtxWritePtr[3].pos.x = p[3].x; - draw_list_3d._VtxWritePtr[3].pos.y = p[3].y; + draw_list_3d._VtxWritePtr[3].pos = p[3]; draw_list_3d._VtxWritePtr[3].uv = UV; draw_list_3d._VtxWritePtr[3].col = Col; @@ -659,6 +518,9 @@ template struct RendererQuadFill : RendererBase { // Update vertex count draw_list_3d._VtxCurrentIdx += 4; + // Add triangle command (6 indices = 2 triangles) + draw_list_3d.AddTriangleCmd(6); + return true; } @@ -670,7 +532,7 @@ template struct RendererQuadFill : RendererBase { template struct RendererQuadImage : RendererBase { RendererQuadImage(const _Getter& getter, ImTextureRef tex_ref, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& uv2, const ImVec2& uv3, ImU32 col) - : RendererBase(getter.Count / 4, 6, 4), Getter(getter), TexRef(tex_ref), UV0(uv0), UV1(uv1), UV2(uv2), UV3(uv3), Col(col) {} + : RendererBase(getter.Count / 4, 6, 4, 2), Getter(getter), TexRef(tex_ref), UV0(uv0), UV1(uv1), UV2(uv2), UV3(uv3), Col(col) {} void Init(ImDrawList3D& /*draw_list_3d*/) const {} @@ -688,31 +550,27 @@ template struct RendererQuadImage : RendererBase { // Set texture ID to be used when rendering this quad draw_list_3d.SetTexture(TexRef); - // Project the quad vertices to screen space - ImVec2 p[4]; - p[0] = PlotToPixels(p_plot[0]); - p[1] = PlotToPixels(p_plot[1]); - p[2] = PlotToPixels(p_plot[2]); - p[3] = PlotToPixels(p_plot[3]); + // Project the quad vertices to NDC space + ImPlot3DPoint p[4]; + p[0] = PlotToNDC(p_plot[0]); + p[1] = PlotToNDC(p_plot[1]); + p[2] = PlotToNDC(p_plot[2]); + p[3] = PlotToNDC(p_plot[3]); // Add vertices for two triangles - draw_list_3d._VtxWritePtr[0].pos.x = p[0].x; - draw_list_3d._VtxWritePtr[0].pos.y = p[0].y; + draw_list_3d._VtxWritePtr[0].pos = p[0]; draw_list_3d._VtxWritePtr[0].uv = UV0; draw_list_3d._VtxWritePtr[0].col = Col; - draw_list_3d._VtxWritePtr[1].pos.x = p[1].x; - draw_list_3d._VtxWritePtr[1].pos.y = p[1].y; + draw_list_3d._VtxWritePtr[1].pos = p[1]; draw_list_3d._VtxWritePtr[1].uv = UV1; draw_list_3d._VtxWritePtr[1].col = Col; - draw_list_3d._VtxWritePtr[2].pos.x = p[2].x; - draw_list_3d._VtxWritePtr[2].pos.y = p[2].y; + draw_list_3d._VtxWritePtr[2].pos = p[2]; draw_list_3d._VtxWritePtr[2].uv = UV2; draw_list_3d._VtxWritePtr[2].col = Col; - draw_list_3d._VtxWritePtr[3].pos.x = p[3].x; - draw_list_3d._VtxWritePtr[3].pos.y = p[3].y; + draw_list_3d._VtxWritePtr[3].pos = p[3]; draw_list_3d._VtxWritePtr[3].uv = UV3; draw_list_3d._VtxWritePtr[3].col = Col; @@ -738,6 +596,9 @@ template struct RendererQuadImage : RendererBase { // Update vertex count draw_list_3d._VtxCurrentIdx += 4; + // Add triangle command + draw_list_3d.AddTriangleCmd(6); + // Reset texture ID draw_list_3d.ResetTexture(); @@ -752,7 +613,7 @@ template struct RendererQuadImage : RendererBase { template struct RendererSurfaceFill : RendererBase { RendererSurfaceFill(const _Getter& getter, int x_count, int y_count, ImU32 col, double scale_min, double scale_max) - : RendererBase((x_count - 1) * (y_count - 1), 6, 4), Getter(getter), XCount(x_count), YCount(y_count), Col(col), ScaleMin(scale_min), + : RendererBase((x_count - 1) * (y_count - 1), 6, 4, 2), Getter(getter), XCount(x_count), YCount(y_count), Col(col), ScaleMin(scale_min), ScaleMax(scale_max) {} void Init(ImDrawList3D& draw_list_3d) const { @@ -803,31 +664,27 @@ template struct RendererSurfaceFill : RendererBase { } } - // Project the quad vertices to screen space - ImVec2 p[4]; - p[0] = PlotToPixels(p_plot[0]); - p[1] = PlotToPixels(p_plot[1]); - p[2] = PlotToPixels(p_plot[2]); - p[3] = PlotToPixels(p_plot[3]); + // Project the quad vertices to NDC space + ImPlot3DPoint p[4]; + p[0] = PlotToNDC(p_plot[0]); + p[1] = PlotToNDC(p_plot[1]); + p[2] = PlotToNDC(p_plot[2]); + p[3] = PlotToNDC(p_plot[3]); // Add vertices for two triangles - draw_list_3d._VtxWritePtr[0].pos.x = p[0].x; - draw_list_3d._VtxWritePtr[0].pos.y = p[0].y; + draw_list_3d._VtxWritePtr[0].pos = p[0]; draw_list_3d._VtxWritePtr[0].uv = UV; draw_list_3d._VtxWritePtr[0].col = cols[0]; - draw_list_3d._VtxWritePtr[1].pos.x = p[1].x; - draw_list_3d._VtxWritePtr[1].pos.y = p[1].y; + draw_list_3d._VtxWritePtr[1].pos = p[1]; draw_list_3d._VtxWritePtr[1].uv = UV; draw_list_3d._VtxWritePtr[1].col = cols[1]; - draw_list_3d._VtxWritePtr[2].pos.x = p[2].x; - draw_list_3d._VtxWritePtr[2].pos.y = p[2].y; + draw_list_3d._VtxWritePtr[2].pos = p[2]; draw_list_3d._VtxWritePtr[2].uv = UV; draw_list_3d._VtxWritePtr[2].col = cols[2]; - draw_list_3d._VtxWritePtr[3].pos.x = p[3].x; - draw_list_3d._VtxWritePtr[3].pos.y = p[3].y; + draw_list_3d._VtxWritePtr[3].pos = p[3]; draw_list_3d._VtxWritePtr[3].uv = UV; draw_list_3d._VtxWritePtr[3].col = cols[3]; @@ -852,6 +709,9 @@ template struct RendererSurfaceFill : RendererBase { // Update vertex count draw_list_3d._VtxCurrentIdx += 4; + // Add triangle command + draw_list_3d.AddTriangleCmd(6); + return true; } @@ -1003,7 +863,7 @@ struct GetterMeshTriangles { // [SECTION] RenderPrimitives //----------------------------------------------------------------------------- -/// Renders primitive shapes +/// Renders triangle-based primitive shapes (surfaces, fills) template