diff --git a/resources/images/copy_menu.svg b/resources/images/copy_menu.svg new file mode 100644 index 00000000000..0d1af6a0a7c --- /dev/null +++ b/resources/images/copy_menu.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/resources/images/measure.svg b/resources/images/measure.svg new file mode 100644 index 00000000000..a273e753473 --- /dev/null +++ b/resources/images/measure.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + diff --git a/resources/shaders/flat.fs b/resources/shaders/flat.fs new file mode 100644 index 00000000000..ab656998df7 --- /dev/null +++ b/resources/shaders/flat.fs @@ -0,0 +1,8 @@ +#version 110 + +uniform vec4 uniform_color; + +void main() +{ + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/flat.vs b/resources/shaders/flat.vs new file mode 100644 index 00000000000..d9063f0c70e --- /dev/null +++ b/resources/shaders/flat.vs @@ -0,0 +1,11 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; + +void main() +{ + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/flat_clip.fs b/resources/shaders/flat_clip.fs new file mode 100644 index 00000000000..ececb8eb1ae --- /dev/null +++ b/resources/shaders/flat_clip.fs @@ -0,0 +1,15 @@ +#version 110 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +uniform vec4 uniform_color; + +varying vec3 clipping_planes_dots; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + + gl_FragColor = uniform_color; +} diff --git a/resources/shaders/flat_clip.vs b/resources/shaders/flat_clip.vs new file mode 100644 index 00000000000..cdf7d4b3b2c --- /dev/null +++ b/resources/shaders/flat_clip.vs @@ -0,0 +1,23 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat4 volume_world_matrix; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +attribute vec3 v_position; + +varying vec3 clipping_planes_dots; + +void main() +{ + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + vec4 world_pos = volume_world_matrix * vec4(v_position, 1.0); + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); + + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/flat_texture.fs b/resources/shaders/flat_texture.fs new file mode 100644 index 00000000000..ffe193b1c00 --- /dev/null +++ b/resources/shaders/flat_texture.fs @@ -0,0 +1,10 @@ +#version 110 + +uniform sampler2D uniform_texture; + +varying vec2 tex_coord; + +void main() +{ + gl_FragColor = texture2D(uniform_texture, tex_coord); +} diff --git a/resources/shaders/flat_texture.vs b/resources/shaders/flat_texture.vs new file mode 100644 index 00000000000..dc4868b04df --- /dev/null +++ b/resources/shaders/flat_texture.vs @@ -0,0 +1,15 @@ +#version 110 + +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; + +attribute vec3 v_position; +attribute vec2 v_tex_coord; + +varying vec2 tex_coord; + +void main() +{ + tex_coord = v_tex_coord; + gl_Position = projection_matrix * view_model_matrix * vec4(v_position, 1.0); +} diff --git a/resources/shaders/gouraud_light.vs b/resources/shaders/gouraud_light.vs index d4f71938a99..135a23b3da8 100644 --- a/resources/shaders/gouraud_light.vs +++ b/resources/shaders/gouraud_light.vs @@ -14,25 +14,32 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + +attribute vec3 v_position; +attribute vec3 v_normal; + // x = tainted, y = specular; varying vec2 intensity; void main() { // First transform the normal into camera space and normalize the result. - vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + vec3 normal = normalize(view_normal_matrix * v_normal); // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; - vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + vec4 position = view_model_matrix * vec4(v_position, 1.0); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position.xyz), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source (no specular applied). NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - gl_Position = ftransform(); + gl_Position = projection_matrix * position; } diff --git a/resources/shaders/gouraud_light_instanced.vs b/resources/shaders/gouraud_light_instanced.vs index a42f8e9a4e2..f512a9cafcf 100644 --- a/resources/shaders/gouraud_light_instanced.vs +++ b/resources/shaders/gouraud_light_instanced.vs @@ -14,6 +14,10 @@ const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); #define INTENSITY_AMBIENT 0.3 +uniform mat4 view_model_matrix; +uniform mat4 projection_matrix; +uniform mat3 view_normal_matrix; + // vertex attributes attribute vec3 v_position; attribute vec3 v_normal; @@ -27,7 +31,7 @@ varying vec2 intensity; void main() { // First transform the normal into camera space and normalize the result. - vec3 eye_normal = normalize(gl_NormalMatrix * v_normal); + vec3 eye_normal = normalize(view_normal_matrix * v_normal); // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. @@ -35,12 +39,12 @@ void main() intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; vec4 world_position = vec4(v_position * vec3(vec2(1.5 * i_scales.x), 1.5 * i_scales.y) + i_offset - vec3(0.0, 0.0, 0.5 * i_scales.y), 1.0); - vec3 eye_position = (gl_ModelViewMatrix * world_position).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + vec4 eye_position = view_model_matrix * world_position; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(eye_position.xyz), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source (no specular applied). NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; - gl_Position = gl_ProjectionMatrix * vec4(eye_position, 1.0); + gl_Position = projection_matrix * eye_position; } diff --git a/resources/shaders/gouraud_light_legacy.vs b/resources/shaders/gouraud_light_legacy.vs new file mode 100644 index 00000000000..d4f71938a99 --- /dev/null +++ b/resources/shaders/gouraud_light_legacy.vs @@ -0,0 +1,38 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +// x = tainted, y = specular; +varying vec2 intensity; + +void main() +{ + // First transform the normal into camera space and normalize the result. + vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + gl_Position = ftransform(); +} diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index 1774eb28ac5..2bd30184a56 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -197,6 +197,9 @@ namespace ImGui const wchar_t CloseBlockNotifHoverButton = 0x0834; const wchar_t BlockNotifErrorIcon = 0x0835; + const wchar_t ClipboardBtnIcon = 0x2606; + + // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/BuildVolume.hpp b/src/libslic3r/BuildVolume.hpp index 4ab007f8b8c..cfb45371529 100644 --- a/src/libslic3r/BuildVolume.hpp +++ b/src/libslic3r/BuildVolume.hpp @@ -93,6 +93,10 @@ class BuildVolume // Called on initial G-code preview on OpenGL vertex buffer interleaved normals and vertices. bool all_paths_inside_vertices_and_normals_interleaved(const std::vector& paths, const Eigen::AlignedBox& bbox, bool ignore_bottom = true) const; + + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_scene() const { return m_top_bottom_convex_hull_decomposition_scene; } + const std::pair, std::vector>& top_bottom_convex_hull_decomposition_bed() const { return m_top_bottom_convex_hull_decomposition_bed; } + private: // Source definition of the print bed geometry (PrintConfig::printable_area) std::vector m_bed_shape; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 9aae0a8f0a2..85db2a26735 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -47,6 +47,8 @@ set(lisbslic3r_sources Clipper2Utils.hpp Config.cpp Config.hpp + Color.hpp + Color.cpp CurveAnalyzer.cpp CurveAnalyzer.hpp EdgeGrid.cpp @@ -195,6 +197,9 @@ set(lisbslic3r_sources ModelArrange.cpp MultiMaterialSegmentation.cpp MultiMaterialSegmentation.hpp + Measure.hpp + Measure.cpp + MeasureUtils.hpp CustomGCode.cpp CustomGCode.hpp Arrange.hpp @@ -294,6 +299,7 @@ set(lisbslic3r_sources Surface.hpp SurfaceCollection.cpp SurfaceCollection.hpp + SurfaceMesh.hpp SVG.cpp SVG.hpp Technologies.hpp diff --git a/src/libslic3r/Color.cpp b/src/libslic3r/Color.cpp new file mode 100644 index 00000000000..ed4441dbfd3 --- /dev/null +++ b/src/libslic3r/Color.cpp @@ -0,0 +1,408 @@ +#include "libslic3r.h" +#include "Color.hpp" + +#include + +static const float INV_255 = 1.0f / 255.0f; + +namespace Slic3r { + +// Conversion from RGB to HSV color space +// The input RGB values are in the range [0, 1] +// The output HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +static void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v) +{ + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); + + const float max_comp = std::max(std::max(r, g), b); + const float min_comp = std::min(std::min(r, g), b); + const float delta = max_comp - min_comp; + + if (delta > 0.0f) { + if (max_comp == r) + h = 60.0f * (std::fmod(((g - b) / delta), 6.0f)); + else if (max_comp == g) + h = 60.0f * (((b - r) / delta) + 2.0f); + else if (max_comp == b) + h = 60.0f * (((r - g) / delta) + 4.0f); + + s = (max_comp > 0.0f) ? delta / max_comp : 0.0f; + } + else { + h = 0.0f; + s = 0.0f; + } + v = max_comp; + + while (h < 0.0f) { h += 360.0f; } + while (h > 360.0f) { h -= 360.0f; } + + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); +} + +// Conversion from HSV to RGB color space +// The input HSV values are in the ranges h = [0, 360], and s, v = [0, 1] +// The output RGB values are in the range [0, 1] +static void HSVtoRGB(float h, float s, float v, float& r, float& g, float& b) +{ + assert(0.0f <= s && s <= 1.0f); + assert(0.0f <= v && v <= 1.0f); + assert(0.0f <= h && h <= 360.0f); + + const float chroma = v * s; + const float h_prime = std::fmod(h / 60.0f, 6.0f); + const float x = chroma * (1.0f - std::abs(std::fmod(h_prime, 2.0f) - 1.0f)); + const float m = v - chroma; + + if (0.0f <= h_prime && h_prime < 1.0f) { + r = chroma; + g = x; + b = 0.0f; + } + else if (1.0f <= h_prime && h_prime < 2.0f) { + r = x; + g = chroma; + b = 0.0f; + } + else if (2.0f <= h_prime && h_prime < 3.0f) { + r = 0.0f; + g = chroma; + b = x; + } + else if (3.0f <= h_prime && h_prime < 4.0f) { + r = 0.0f; + g = x; + b = chroma; + } + else if (4.0f <= h_prime && h_prime < 5.0f) { + r = x; + g = 0.0f; + b = chroma; + } + else if (5.0f <= h_prime && h_prime < 6.0f) { + r = chroma; + g = 0.0f; + b = x; + } + else { + r = 0.0f; + g = 0.0f; + b = 0.0f; + } + + r += m; + g += m; + b += m; + + assert(0.0f <= r && r <= 1.0f); + assert(0.0f <= g && g <= 1.0f); + assert(0.0f <= b && b <= 1.0f); +} + +class Randomizer +{ + std::random_device m_rd; + +public: + float random_float(float min, float max) { + std::mt19937 rand_generator(m_rd()); + std::uniform_real_distribution distrib(min, max); + return distrib(rand_generator); + } +}; + +ColorRGB::ColorRGB(float r, float g, float b) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f) }) +{ +} + +ColorRGB::ColorRGB(unsigned char r, unsigned char g, unsigned char b) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f) }) +{ +} + +bool ColorRGB::operator < (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + else if (m_data[i] > other.m_data[i]) + return false; + } + + return false; +} + +bool ColorRGB::operator > (const ColorRGB& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + else if (m_data[i] < other.m_data[i]) + return false; + } + + return false; +} + +ColorRGB ColorRGB::operator + (const ColorRGB& other) const +{ + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGB ColorRGB::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGB ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA::ColorRGBA(float r, float g, float b, float a) +: m_data({ std::clamp(r, 0.0f, 1.0f), std::clamp(g, 0.0f, 1.0f), std::clamp(b, 0.0f, 1.0f), std::clamp(a, 0.0f, 1.0f) }) +{ +} + +ColorRGBA::ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +: m_data({ std::clamp(r * INV_255, 0.0f, 1.0f), std::clamp(g * INV_255, 0.0f, 1.0f), std::clamp(b * INV_255, 0.0f, 1.0f), std::clamp(a * INV_255, 0.0f, 1.0f) }) +{ +} +ColorRGBA::ColorRGBA(const std::array &data) : m_data(data) {} +bool ColorRGBA::operator < (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] < other.m_data[i]) + return true; + else if (m_data[i] > other.m_data[i]) + return false; + } + + return false; +} + +bool ColorRGBA::operator > (const ColorRGBA& other) const +{ + for (size_t i = 0; i < 3; ++i) { + if (m_data[i] > other.m_data[i]) + return true; + else if (m_data[i] < other.m_data[i]) + return false; + } + + return false; +} + +ColorRGBA ColorRGBA::operator + (const ColorRGBA& other) const +{ + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(m_data[i] + other.m_data[i], 0.0f, 1.0f); + } + return ret; +} + +ColorRGBA ColorRGBA::operator * (float value) const +{ + assert(value >= 0.0f); + ColorRGBA ret; + for (size_t i = 0; i < 3; ++i) { + ret.m_data[i] = std::clamp(value * m_data[i], 0.0f, 1.0f); + } + ret.m_data[3] = m_data[3]; + return ret; +} + +ColorRGB operator * (float value, const ColorRGB& other) { return other * value; } +ColorRGBA operator * (float value, const ColorRGBA& other) { return other * value; } + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t) +{ + assert(0.0f <= t && t <= 1.0f); + return (1.0f - t) * a + t * b; +} + +ColorRGB complementary(const ColorRGB& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b() }; +} + +ColorRGBA complementary(const ColorRGBA& color) +{ + return { 1.0f - color.r(), 1.0f - color.g(), 1.0f - color.b(), color.a() }; +} + +ColorRGB saturate(const ColorRGB& color, float factor) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + s = std::clamp(s * factor, 0.0f, 1.0f); + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGBA saturate(const ColorRGBA& color, float factor) +{ + return to_rgba(saturate(to_rgb(color), factor), color.a()); +} + +ColorRGB opposite(const ColorRGB& color) +{ + float h, s, v; + RGBtoHSV(color.r(), color.g(), color.b(), h, s, v); + + h += 65.0f; // 65 instead 60 to avoid circle values + if (h > 360.0f) + h -= 360.0f; + + Randomizer rnd; + s = rnd.random_float(0.65f, 1.0f); + v = rnd.random_float(0.65f, 1.0f); + + float r, g, b; + HSVtoRGB(h, s, v, r, g, b); + return { r, g, b }; +} + +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b) +{ + float ha, sa, va; + RGBtoHSV(a.r(), a.g(), a.b(), ha, sa, va); + float hb, sb, vb; + RGBtoHSV(b.r(), b.g(), b.b(), hb, sb, vb); + + float delta_h = std::abs(ha - hb); + float start_h = (delta_h > 180.0f) ? std::min(ha, hb) : std::max(ha, hb); + + start_h += 5.0f; // to avoid circle change of colors for 120 deg + if (delta_h < 180.0f) + delta_h = 360.0f - delta_h; + + Randomizer rnd; + float out_h = start_h + 0.5f * delta_h; + if (out_h > 360.0f) + out_h -= 360.0f; + float out_s = rnd.random_float(0.65f, 1.0f); + float out_v = rnd.random_float(0.65f, 1.0f); + + float out_r, out_g, out_b; + HSVtoRGB(out_h, out_s, out_v, out_r, out_g, out_b); + return { out_r, out_g, out_b }; +} + +bool can_decode_color(const std::string& color) { return color.size() == 7 && color.front() == '#'; } + +bool decode_color(const std::string& color_in, ColorRGB& color_out) +{ + auto hex_digit_to_int = [](const char c) { + return + (c >= '0' && c <= '9') ? int(c - '0') : + (c >= 'A' && c <= 'F') ? int(c - 'A') + 10 : + (c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1; + }; + + color_out = ColorRGB::BLACK(); + if (can_decode_color(color_in)) { + const char* c = color_in.data() + 1; + for (unsigned int i = 0; i < 3; ++i) { + const int digit1 = hex_digit_to_int(*c++); + const int digit2 = hex_digit_to_int(*c++); + if (digit1 != -1 && digit2 != -1) + color_out.set(i, float(digit1 * 16 + digit2) * INV_255); + } + } + else + return false; + + assert(0.0f <= color_out.r() && color_out.r() <= 1.0f); + assert(0.0f <= color_out.g() && color_out.g() <= 1.0f); + assert(0.0f <= color_out.b() && color_out.b() <= 1.0f); + return true; +} + +bool decode_color(const std::string& color_in, ColorRGBA& color_out) +{ + ColorRGB rgb; + if (!decode_color(color_in, rgb)) + return false; + + color_out = to_rgba(rgb, color_out.a()); + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGB::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out) +{ + colors_out = std::vector(colors_in.size(), ColorRGBA::BLACK()); + for (size_t i = 0; i < colors_in.size(); ++i) { + if (!decode_color(colors_in[i], colors_out[i])) + return false; + } + return true; +} + +std::string encode_color(const ColorRGB& color) +{ + char buffer[64]; + ::sprintf(buffer, "#%02X%02X%02X", color.r_uchar(), color.g_uchar(), color.b_uchar()); + return std::string(buffer); +} + +std::string encode_color(const ColorRGBA& color) { return encode_color(to_rgb(color)); } + +ColorRGB to_rgb(const ColorRGBA& other_rgba) { return { other_rgba.r(), other_rgba.g(), other_rgba.b() }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), 1.0f }; } +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha) { return { other_rgb.r(), other_rgb.g(), other_rgb.b(), alpha }; } + +ColorRGBA picking_decode(unsigned int id) +{ + return { + float((id >> 0) & 0xff) * INV_255, // red + float((id >> 8) & 0xff) * INV_255, // green + float((id >> 16) & 0xff) * INV_255, // blue + float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff)) * INV_255 // checksum for validating against unwanted alpha blending and multi sampling + }; +} + +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b) { return r + (g << 8) + (b << 16); } + +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue) +{ + // 8 bit hash for the color + unsigned char b = ((((37 * red) + green) & 0x0ff) * 37 + blue) & 0x0ff; + // Increase enthropy by a bit reversal + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + // Flip every second bit to increase the enthropy even more. + b ^= 0x55; + return b; +} + +} // namespace Slic3r + diff --git a/src/libslic3r/Color.hpp b/src/libslic3r/Color.hpp new file mode 100644 index 00000000000..12c3a07a6e7 --- /dev/null +++ b/src/libslic3r/Color.hpp @@ -0,0 +1,194 @@ +#ifndef slic3r_Color_hpp_ +#define slic3r_Color_hpp_ + +#include +#include + +#include "Point.hpp" + +namespace Slic3r { + +class ColorRGB +{ + std::array m_data{1.0f, 1.0f, 1.0f}; + +public: + ColorRGB() = default; + ColorRGB(float r, float g, float b); + ColorRGB(unsigned char r, unsigned char g, unsigned char b); + ColorRGB(const ColorRGB& other) = default; + + ColorRGB& operator = (const ColorRGB& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGB& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGB& other) const { return !operator==(other); } + bool operator < (const ColorRGB& other) const; + bool operator > (const ColorRGB& other) const; + + ColorRGB operator + (const ColorRGB& other) const; + ColorRGB operator * (float value) const; + + const float* const data() const { return m_data.data(); } + + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 2); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + + static const ColorRGB BLACK() { return { 0.0f, 0.0f, 0.0f }; } + static const ColorRGB BLUE() { return { 0.0f, 0.0f, 1.0f }; } + static const ColorRGB BLUEISH() { return { 0.5f, 0.5f, 1.0f }; } + static const ColorRGB CYAN() { return { 0.0f, 1.0f, 1.0f }; } + static const ColorRGB DARK_GRAY() { return { 0.25f, 0.25f, 0.25f }; } + static const ColorRGB DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f }; } + static const ColorRGB GRAY() { return { 0.5f, 0.5f, 0.5f }; } + static const ColorRGB GREEN() { return { 0.0f, 1.0f, 0.0f }; } + static const ColorRGB GREENISH() { return { 0.5f, 1.0f, 0.5f }; } + static const ColorRGB LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f }; } + static const ColorRGB MAGENTA() { return { 1.0f, 0.0f, 1.0f }; } + static const ColorRGB ORANGE() { return { 0.92f, 0.50f, 0.26f }; } + static const ColorRGB RED() { return { 1.0f, 0.0f, 0.0f }; } + static const ColorRGB REDISH() { return { 1.0f, 0.5f, 0.5f }; } + static const ColorRGB YELLOW() { return { 1.0f, 1.0f, 0.0f }; } + static const ColorRGB WHITE() { return { 1.0f, 1.0f, 1.0f }; } + + static const ColorRGB X() { return { 0.75f, 0.0f, 0.0f }; } + static const ColorRGB Y() { return { 0.0f, 0.75f, 0.0f }; } + static const ColorRGB Z() { return { 0.0f, 0.0f, 0.75f }; } +}; + +class ColorRGBA +{ + std::array m_data{ 1.0f, 1.0f, 1.0f, 1.0f }; + +public: + ColorRGBA() = default; + ColorRGBA(float r, float g, float b, float a); + ColorRGBA(unsigned char r, unsigned char g, unsigned char b, unsigned char a); + ColorRGBA(const std::array& data); + ColorRGBA(const ColorRGBA& other) = default; + + ColorRGBA& operator = (const ColorRGBA& other) { m_data = other.m_data; return *this; } + + bool operator == (const ColorRGBA& other) const { return m_data == other.m_data; } + bool operator != (const ColorRGBA& other) const { return !operator==(other); } + bool operator < (const ColorRGBA& other) const; + bool operator > (const ColorRGBA& other) const; + + ColorRGBA operator + (const ColorRGBA& other) const; + ColorRGBA operator * (float value) const; + + const float* const data() const { return m_data.data(); } + const std::array& data_array() const { return m_data; } + float r() const { return m_data[0]; } + float g() const { return m_data[1]; } + float b() const { return m_data[2]; } + float a() const { return m_data[3]; } + + void r(float r) { m_data[0] = std::clamp(r, 0.0f, 1.0f); } + void g(float g) { m_data[1] = std::clamp(g, 0.0f, 1.0f); } + void b(float b) { m_data[2] = std::clamp(b, 0.0f, 1.0f); } + void a(float a) { m_data[3] = std::clamp(a, 0.0f, 1.0f); } + + void set(unsigned int comp, float value) { + assert(0 <= comp && comp <= 3); + m_data[comp] = std::clamp(value, 0.0f, 1.0f); + } + + unsigned char r_uchar() const { return static_cast(m_data[0] * 255.0f); } + unsigned char g_uchar() const { return static_cast(m_data[1] * 255.0f); } + unsigned char b_uchar() const { return static_cast(m_data[2] * 255.0f); } + unsigned char a_uchar() const { return static_cast(m_data[3] * 255.0f); } + + bool is_transparent() const { return m_data[3] < 1.0f; } + + static const ColorRGBA BLACK() { return { 0.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA BLUE() { return { 0.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA BLUEISH() { return { 0.5f, 0.5f, 1.0f, 1.0f }; } + static const ColorRGBA CYAN() { return { 0.0f, 1.0f, 1.0f, 1.0f }; } + static const ColorRGBA DARK_GRAY() { return { 0.25f, 0.25f, 0.25f, 1.0f }; } + static const ColorRGBA DARK_YELLOW() { return { 0.5f, 0.5f, 0.0f, 1.0f }; } + static const ColorRGBA GRAY() { return { 0.5f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA GREEN() { return { 0.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA GREENISH() { return { 0.5f, 1.0f, 0.5f, 1.0f }; } + static const ColorRGBA LIGHT_GRAY() { return { 0.75f, 0.75f, 0.75f, 1.0f }; } + static const ColorRGBA MAGENTA() { return { 1.0f, 0.0f, 1.0f, 1.0f }; } + static const ColorRGBA ORANGE() { return { 0.923f, 0.504f, 0.264f, 1.0f }; } + static const ColorRGBA RED() { return { 1.0f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA REDISH() { return { 1.0f, 0.5f, 0.5f, 1.0f }; } + static const ColorRGBA YELLOW() { return { 1.0f, 1.0f, 0.0f, 1.0f }; } + static const ColorRGBA WHITE() { return { 1.0f, 1.0f, 1.0f, 1.0f }; } + static const ColorRGBA ORCA() { return { 0.0f, 150.f/255.0f, 136.0f/255, 1.0f }; } + + static const ColorRGBA X() { return { 0.75f, 0.0f, 0.0f, 1.0f }; } + static const ColorRGBA Y() { return { 0.0f, 0.75f, 0.0f, 1.0f }; } + static const ColorRGBA Z() { return { 0.0f, 0.0f, 0.75f, 1.0f }; } +}; + +ColorRGB operator * (float value, const ColorRGB& other); +ColorRGBA operator * (float value, const ColorRGBA& other); + +ColorRGB lerp(const ColorRGB& a, const ColorRGB& b, float t); +ColorRGBA lerp(const ColorRGBA& a, const ColorRGBA& b, float t); + +ColorRGB complementary(const ColorRGB& color); +ColorRGBA complementary(const ColorRGBA& color); + +ColorRGB saturate(const ColorRGB& color, float factor); +ColorRGBA saturate(const ColorRGBA& color, float factor); + +ColorRGB opposite(const ColorRGB& color); +ColorRGB opposite(const ColorRGB& a, const ColorRGB& b); + +bool can_decode_color(const std::string& color); + +bool decode_color(const std::string& color_in, ColorRGB& color_out); +bool decode_color(const std::string& color_in, ColorRGBA& color_out); + +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); +bool decode_colors(const std::vector& colors_in, std::vector& colors_out); + +std::string encode_color(const ColorRGB& color); +std::string encode_color(const ColorRGBA& color); + +ColorRGB to_rgb(const ColorRGBA& other_rgba); +ColorRGBA to_rgba(const ColorRGB& other_rgb); +ColorRGBA to_rgba(const ColorRGB& other_rgb, float alpha); + +// Color mapping of a value into RGB false colors. +inline Vec3f value_to_rgbf(float minimum, float maximum, float value) +{ + float ratio = 2.0f * (value - minimum) / (maximum - minimum); + float b = std::max(0.0f, (1.0f - ratio)); + float r = std::max(0.0f, (ratio - 1.0f)); + float g = 1.0f - b - r; + return Vec3f { r, g, b }; +} + +// Color mapping of a value into RGB false colors. +inline Vec3i value_to_rgbi(float minimum, float maximum, float value) +{ + return (value_to_rgbf(minimum, maximum, value) * 255).cast(); +} + +ColorRGBA picking_decode(unsigned int id); +unsigned int picking_encode(unsigned char r, unsigned char g, unsigned char b); +// Produce an alpha channel checksum for the red green blue components. The alpha channel may then be used to verify, whether the rgb components +// were not interpolated by alpha blending or multi sampling. +unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue); + +} // namespace Slic3r + +#endif /* slic3r_Color_hpp_ */ diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index a2c59ec17d5..b4408a63966 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -408,6 +408,65 @@ void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, doubl *rotation_matrix = Matrix3d::Identity() + kmat + kmat * kmat * ((1 - c) / (s * s)); } } +TransformationSVD::TransformationSVD(const Transform3d& trafo) +{ + const auto &m0 = trafo.matrix().block<3, 3>(0, 0); + mirror = m0.determinant() < 0.0; + + Matrix3d m; + if (mirror) + m = m0 * Eigen::DiagonalMatrix(-1.0, 1.0, 1.0); + else + m = m0; + const Eigen::JacobiSVD svd(m, Eigen::ComputeFullU | Eigen::ComputeFullV); + u = svd.matrixU(); + v = svd.matrixV(); + s = svd.singularValues().asDiagonal(); + + scale = !s.isApprox(Matrix3d::Identity()); + anisotropic_scale = ! is_approx(s(0, 0), s(1, 1)) || ! is_approx(s(1, 1), s(2, 2)); + rotation = !v.isApprox(u); + + if (anisotropic_scale) { + rotation_90_degrees = true; + for (int i = 0; i < 3; ++i) { + const Vec3d row = v.row(i).cwiseAbs(); + const size_t num_zeros = is_approx(row[0], 0.) + is_approx(row[1], 0.) + is_approx(row[2], 0.); + const size_t num_ones = is_approx(row[0], 1.) + is_approx(row[1], 1.) + is_approx(row[2], 1.); + if (num_zeros != 2 || num_ones != 1) { + rotation_90_degrees = false; + break; + } + } + // Detect skew by brute force: check if the axes are still orthogonal after transformation + const Matrix3d trafo_linear = trafo.linear(); + const std::array axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() }; + std::array transformed_axes; + for (int i = 0; i < 3; ++i) { + transformed_axes[i] = trafo_linear * axes[i]; + } + skew = std::abs(transformed_axes[0].dot(transformed_axes[1])) > EPSILON || + std::abs(transformed_axes[1].dot(transformed_axes[2])) > EPSILON || + std::abs(transformed_axes[2].dot(transformed_axes[0])) > EPSILON; + + // This following old code does not work under all conditions. The v matrix can become non diagonal (see SPE-1492) +// skew = ! rotation_90_degrees; + } else + skew = false; +} +static Transform3d extract_rotation_matrix(const Transform3d& trafo) +{ + Matrix3d rotation; + Matrix3d scale; + trafo.computeRotationScaling(&rotation, &scale); + return Transform3d(rotation); +} + +void translation_transform(Transform3d& transform, const Vec3d& translation) +{ + transform = Transform3d::Identity(); + transform.translate(translation); +} Transform3d translation_transform(const Vec3d &translation) { @@ -416,6 +475,12 @@ Transform3d translation_transform(const Vec3d &translation) return transform; } +void rotation_transform(Transform3d& transform, const Vec3d& rotation) +{ + transform = Transform3d::Identity(); + transform.rotate(Eigen::AngleAxisd(rotation.z(), Vec3d::UnitZ()) * Eigen::AngleAxisd(rotation.y(), Vec3d::UnitY()) * Eigen::AngleAxisd(rotation.x(), Vec3d::UnitX())); +} + Transform3d rotation_transform(const Vec3d& rotation) { Transform3d transform = Transform3d::Identity(); @@ -423,6 +488,29 @@ Transform3d rotation_transform(const Vec3d& rotation) return transform; } +void scale_transform(Transform3d& transform, double scale) +{ + return scale_transform(transform, scale * Vec3d::Ones()); +} + +void scale_transform(Transform3d& transform, const Vec3d& scale) +{ + transform = Transform3d::Identity(); + transform.scale(scale); +} + +Transform3d scale_transform(double scale) +{ + return scale_transform(scale * Vec3d::Ones()); +} + +Transform3d scale_transform(const Vec3d& scale) +{ + Transform3d transform; + scale_transform(transform, scale); + return transform; +} + Transformation::Flags::Flags() : dont_translate(true) , dont_rotate(true) @@ -469,6 +557,22 @@ void Transformation::set_offset(Axis axis, double offset) m_dirty = true; } } +Transform3d Transformation::get_rotation_matrix() const +{ + return extract_rotation_matrix(m_matrix); +} + +Transform3d Transformation::get_offset_matrix() const +{ + return translation_transform(get_offset()); +} + +Transform3d Transformation::get_matrix_no_offset() const +{ + Transformation copy(*this); + copy.reset_offset(); + return copy.get_matrix(); +} void Transformation::set_rotation(const Vec3d& rotation) { @@ -577,6 +681,12 @@ void Transformation::reset() m_dirty = false; } +void Transformation::reset_scaling_factor() +{ + const Geometry::TransformationSVD svd(*this); + m_matrix = get_offset_matrix() * Transform3d(svd.u) * Transform3d(svd.v.transpose()) * svd.mirror_matrix(); +} + const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rotate, bool dont_scale, bool dont_mirror) const { if (m_dirty || m_flags.needs_update(dont_translate, dont_rotate, dont_scale, dont_mirror)) @@ -595,6 +705,13 @@ const Transform3d& Transformation::get_matrix(bool dont_translate, bool dont_rot return m_matrix; } +Transform3d Transformation::get_matrix_no_scaling_factor() const +{ + Transformation copy(*this); + copy.reset_scaling_factor(); + return copy.get_matrix(); +} + Transformation Transformation::operator * (const Transformation& other) const { return Transformation(get_matrix() * other.get_matrix()); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 8eb6195a107..354174f93c0 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -348,15 +348,32 @@ Vec3d extract_euler_angles(const Transform3d& transform); // Euler angles can be obtained by extract_euler_angles() void rotation_from_two_vectors(Vec3d from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix = nullptr); +// Sets the given transform by assembling the given translation +void translation_transform(Transform3d& transform, const Vec3d& translation); + // Returns the transform obtained by assembling the given translation Transform3d translation_transform(const Vec3d &translation); +// Sets the given transform by assembling the given rotations in the following order: +// 1) rotate X +// 2) rotate Y +// 3) rotate Z +void rotation_transform(Transform3d& transform, const Vec3d& rotation); + // Returns the transform obtained by assembling the given rotations in the following order: // 1) rotate X // 2) rotate Y // 3) rotate Z Transform3d rotation_transform(const Vec3d &rotation); +// Sets the given transform by assembling the given scale factors +void scale_transform(Transform3d& transform, double scale); +void scale_transform(Transform3d& transform, const Vec3d& scale); + +// Returns the transform obtained by assembling the given scale factors +Transform3d scale_transform(double scale); +Transform3d scale_transform(const Vec3d& scale); + class Transformation { struct Flags @@ -394,14 +411,20 @@ class Transformation void set_offset(const Vec3d& offset); void set_offset(Axis axis, double offset); + Transform3d get_offset_matrix() const; + const Vec3d& get_rotation() const { return m_rotation; } double get_rotation(Axis axis) const { return m_rotation(axis); } + void reset_offset() { set_offset(Vec3d::Zero()); } + + Transform3d get_rotation_matrix() const; void set_rotation(const Vec3d& rotation); void set_rotation(Axis axis, double rotation); const Vec3d& get_scaling_factor() const { return m_scaling_factor; } double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); } + Transform3d get_matrix_no_offset() const; void set_scaling_factor(const Vec3d& scaling_factor); void set_scaling_factor(Axis axis, double scaling_factor); @@ -420,6 +443,9 @@ class Transformation const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const; + Transform3d get_matrix_no_scaling_factor() const; + void reset_scaling_factor(); + Transformation operator * (const Transformation& other) const; // Find volume transformation, so that the chained (instance_trafo * volume_trafo) will be as close to identity @@ -443,7 +469,24 @@ class Transformation ar(construct.ptr()->m_offset, construct.ptr()->m_rotation, construct.ptr()->m_scaling_factor, construct.ptr()->m_mirror); } }; +struct TransformationSVD +{ + Matrix3d u{ Matrix3d::Identity() }; + Matrix3d s{ Matrix3d::Identity() }; + Matrix3d v{ Matrix3d::Identity() }; + bool mirror{ false }; + bool scale{ false }; + bool anisotropic_scale{ false }; + bool rotation{ false }; + bool rotation_90_degrees{ false }; + bool skew{ false }; + + explicit TransformationSVD(const Transformation& trafo) : TransformationSVD(trafo.get_matrix()) {} + explicit TransformationSVD(const Transform3d& trafo); + + Eigen::DiagonalMatrix mirror_matrix() const { return Eigen::DiagonalMatrix(this->mirror ? -1. : 1., 1., 1.); } +}; // For parsing a transformation matrix from 3MF / AMF. extern Transform3d transform3d_from_string(const std::string& transform_str); diff --git a/src/libslic3r/Geometry/Circle.cpp b/src/libslic3r/Geometry/Circle.cpp index 4d7c38ccc23..67966719549 100644 --- a/src/libslic3r/Geometry/Circle.cpp +++ b/src/libslic3r/Geometry/Circle.cpp @@ -108,7 +108,7 @@ Circled circle_taubin_newton(const Vec2ds& input, size_t cycles) return out; } -Circled circle_ransac(const Vec2ds& input, size_t iterations) +Circled circle_ransac(const Vec2ds& input, size_t iterations, double* min_error) { if (input.size() < 3) return Circled::make_invalid(); @@ -132,6 +132,8 @@ Circled circle_ransac(const Vec2ds& input, size_t iterations) circle_best = c; } } + if (min_error) + *min_error = err_min; return circle_best; } diff --git a/src/libslic3r/Geometry/Circle.hpp b/src/libslic3r/Geometry/Circle.hpp index 39973d916d9..653102e2abd 100644 --- a/src/libslic3r/Geometry/Circle.hpp +++ b/src/libslic3r/Geometry/Circle.hpp @@ -102,7 +102,7 @@ inline Vec2d circle_center_taubin_newton(const Vec2ds& input, size_t cycles = 20 Circled circle_taubin_newton(const Vec2ds& input, size_t cycles = 20); // Find circle using RANSAC randomized algorithm. -Circled circle_ransac(const Vec2ds& input, size_t iterations = 20); +Circled circle_ransac(const Vec2ds& input, size_t iterations = 20, double* min_error = nullptr); // Randomized algorithm by Emo Welzl, working with squared radii for efficiency. The returned circle radius is inflated by epsilon. template diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp new file mode 100644 index 00000000000..5593362949b --- /dev/null +++ b/src/libslic3r/Measure.cpp @@ -0,0 +1,1250 @@ +#include "libslic3r/libslic3r.h" +#include "Measure.hpp" +#include "MeasureUtils.hpp" + +#include "libslic3r/Geometry/Circle.hpp" +#include "libslic3r/SurfaceMesh.hpp" +#include + +#include + +#define DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE 0 + +namespace Slic3r { +namespace Measure { + + +constexpr double feature_hover_limit = 0.5; // how close to a feature the mouse must be to highlight it + +static std::tuple get_center_and_radius(const std::vector& points, const Transform3d& trafo, const Transform3d& trafo_inv) +{ + Vec2ds out; + double z = 0.; + for (const Vec3d& pt : points) { + Vec3d pt_transformed = trafo * pt; + z = pt_transformed.z(); + out.emplace_back(pt_transformed.x(), pt_transformed.y()); + } + + const int iter = points.size() < 10 ? 2 : + points.size() < 100 ? 4 : + 6; + + double error = std::numeric_limits::max(); + auto circle = Geometry::circle_ransac(out, iter, &error); + + return std::make_tuple(trafo.inverse() * Vec3d(circle.center.x(), circle.center.y(), z), circle.radius, error); +} + + + +static std::array orthonormal_basis(const Vec3d& v) +{ + std::array ret; + ret[2] = v.normalized(); + int index; + ret[2].cwiseAbs().maxCoeff(&index); + switch (index) + { + case 0: { ret[0] = Vec3d(ret[2].y(), -ret[2].x(), 0.0).normalized(); break; } + case 1: { ret[0] = Vec3d(0.0, ret[2].z(), -ret[2].y()).normalized(); break; } + case 2: { ret[0] = Vec3d(-ret[2].z(), 0.0, ret[2].x()).normalized(); break; } + } + ret[1] = ret[2].cross(ret[0]).normalized(); + return ret; +} + + + +class MeasuringImpl { +public: + explicit MeasuringImpl(const indexed_triangle_set& its); + struct PlaneData { + std::vector facets; + std::vector> borders; // FIXME: should be in fact local in update_planes() + std::vector surface_features; + Vec3d normal; + float area; + bool features_extracted = false; + }; + + std::optional get_feature(size_t face_idx, const Vec3d& point); + int get_num_of_planes() const; + const std::vector& get_plane_triangle_indices(int idx) const; + const std::vector& get_plane_features(unsigned int plane_id); + const indexed_triangle_set& get_its() const; + +private: + void update_planes(); + void extract_features(int plane_idx); + + std::vector m_planes; + std::vector m_face_to_plane; + indexed_triangle_set m_its; +}; + + + + + + +MeasuringImpl::MeasuringImpl(const indexed_triangle_set& its) +: m_its(its) +{ + update_planes(); + + // Extracting features will be done as needed. + // To extract all planes at once, run the following: +#if DEBUG_EXTRACT_ALL_FEATURES_AT_ONCE + for (int i=0; i face_normals = its_face_normals(m_its); + const std::vector face_neighbors = its_face_neighbors(m_its); + std::vector facet_queue(num_of_facets, 0); + int facet_queue_cnt = 0; + const stl_normal* normal_ptr = nullptr; + size_t seed_facet_idx = 0; + + auto is_same_normal = [](const stl_normal& a, const stl_normal& b) -> bool { + return (std::abs(a(0) - b(0)) < 0.001 && std::abs(a(1) - b(1)) < 0.001 && std::abs(a(2) - b(2)) < 0.001); + }; + + m_planes.clear(); + m_planes.reserve(num_of_facets / 5); // empty plane data object is quite lightweight, let's save the initial reallocations + + + // First go through all the triangles and fill in m_planes vector. For each "plane" + // detected on the model, it will contain list of facets that are part of it. + // We will also fill in m_face_to_plane, which contains index into m_planes + // for each of the source facets. + while (1) { + // Find next unvisited triangle: + for (; seed_facet_idx < num_of_facets; ++ seed_facet_idx) + if (m_face_to_plane[seed_facet_idx] == size_t(-1)) { + facet_queue[facet_queue_cnt ++] = seed_facet_idx; + normal_ptr = &face_normals[seed_facet_idx]; + m_face_to_plane[seed_facet_idx] = m_planes.size(); + m_planes.emplace_back(); + break; + } + if (seed_facet_idx == num_of_facets) + break; // Everything was visited already + + while (facet_queue_cnt > 0) { + int facet_idx = facet_queue[-- facet_queue_cnt]; + const stl_normal& this_normal = face_normals[facet_idx]; + if (is_same_normal(this_normal, *normal_ptr)) { +// const Vec3i& face = m_its.indices[facet_idx]; + + m_face_to_plane[facet_idx] = m_planes.size() - 1; + m_planes.back().facets.emplace_back(facet_idx); + for (int j = 0; j < 3; ++ j) + if (int neighbor_idx = face_neighbors[facet_idx][j]; neighbor_idx >= 0 && m_face_to_plane[neighbor_idx] == size_t(-1)) + facet_queue[facet_queue_cnt ++] = neighbor_idx; + } + } + + m_planes.back().normal = normal_ptr->cast(); + std::sort(m_planes.back().facets.begin(), m_planes.back().facets.end()); + } + + // Check that each facet is part of one of the planes. + assert(std::none_of(m_face_to_plane.begin(), m_face_to_plane.end(), [](size_t val) { return val == size_t(-1); })); + + // Now we will walk around each of the planes and save vertices which form the border. + const SurfaceMesh sm(m_its); + + const auto& face_to_plane = m_face_to_plane; + auto& planes = m_planes; + + tbb::parallel_for(tbb::blocked_range(0, m_planes.size()), + [&planes, &face_to_plane, &face_neighbors, &sm](const tbb::blocked_range& range) { + for (size_t plane_id = range.begin(); plane_id != range.end(); ++plane_id) { + + const auto& facets = planes[plane_id].facets; + planes[plane_id].borders.clear(); + std::vector> visited(facets.size(), {false, false, false}); + + for (int face_id=0; face_id& last_border = planes[plane_id].borders.back(); + last_border.reserve(4); + last_border.emplace_back(sm.point(sm.source(he)).cast()); + //Vertex_index target = sm.target(he); + const Halfedge_index he_start = he; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + assert(face_it != facets.end()); + assert(*face_it == int(fi)); + visited[face_it - facets.begin()][he.side()] = true; + + do { + const Halfedge_index he_orig = he; + he = sm.next_around_target(he); + if (he.is_invalid()) + goto PLANE_FAILURE; + + // For broken meshes, the iteration might never get back to he_orig. + // Remember all halfedges we saw to break out of such infinite loops. + boost::container::small_vector he_seen; + + while ( (int)face_to_plane[sm.face(he)] == plane_id && he != he_orig) { + he_seen.emplace_back(he); + he = sm.next_around_target(he); + if (he.is_invalid() || std::find(he_seen.begin(), he_seen.end(), he) != he_seen.end()) + goto PLANE_FAILURE; + } + he = sm.opposite(he); + if (he.is_invalid()) + goto PLANE_FAILURE; + + Face_index fi = he.face(); + auto face_it = std::lower_bound(facets.begin(), facets.end(), int(fi)); + if (face_it == facets.end() || *face_it != int(fi)) // This indicates a broken mesh. + goto PLANE_FAILURE; + + if (visited[face_it - facets.begin()][he.side()] && he != he_start) { + last_border.resize(1); + break; + } + visited[face_it - facets.begin()][he.side()] = true; + + last_border.emplace_back(sm.point(sm.source(he)).cast()); + + // In case of broken meshes, this loop might be infinite. Break + // out in case it is clearly going bad. + if (last_border.size() > 3*facets.size()+1) + goto PLANE_FAILURE; + + } while (he != he_start); + + if (last_border.size() == 1) + planes[plane_id].borders.pop_back(); + else { + assert(last_border.front() == last_border.back()); + last_border.pop_back(); + } + } + } + continue; // There was no failure. + + PLANE_FAILURE: + planes[plane_id].borders.clear(); + }}); + m_planes.shrink_to_fit(); +} + + + + + + +void MeasuringImpl::extract_features(int plane_idx) +{ + assert(! m_planes[plane_idx].features_extracted); + + PlaneData& plane = m_planes[plane_idx]; + plane.surface_features.clear(); + const Vec3d& normal = plane.normal; + + Eigen::Quaterniond q; + q.setFromTwoVectors(plane.normal, Vec3d::UnitZ()); + Transform3d trafo = Transform3d::Identity(); + trafo.rotate(q); + const Transform3d trafo_inv = trafo.inverse(); + + std::vector angles; // placed in outer scope to prevent reallocations + std::vector lengths; + + for (const std::vector& border : plane.borders) { + if (border.size() <= 1) + continue; + + bool done = false; + + if (border.size() > 4) { + const auto& [center, radius, err] = get_center_and_radius(border, trafo, trafo_inv); + + if (err < 0.05) { + // The whole border is one circle. Just add it into the list of features + // and we are done. + + bool is_polygon = border.size()>4 && border.size()<=8; + bool lengths_match = std::all_of(border.begin()+2, border.end(), [is_polygon](const Vec3d& pt) { + return Slic3r::is_approx((pt - *((&pt)-1)).squaredNorm(), (*((&pt)-1) - *((&pt)-2)).squaredNorm(), is_polygon ? 0.01 : 0.01); + }); + + if (lengths_match && (is_polygon || border.size() > 8)) { + if (is_polygon) { + // This is a polygon, add the separate edges with the center. + for (int j=0; j int { + assert(std::abs(offset) < border_size); + int out = idx+offset; + if (out >= border_size) + out = out - border_size; + else if (out < 0) + out = border_size + out; + + return out; + }; + + // First calculate angles at all the vertices. + angles.clear(); + lengths.clear(); + int first_different_angle_idx = 0; + for (int i=0; i M_PI) + angle = 2*M_PI - angle; + + angles.push_back(angle); + lengths.push_back(v2.norm()); + if (first_different_angle_idx == 0 && angles.size() > 1) { + if (! are_angles_same(angles.back(), angles[angles.size()-2])) + first_different_angle_idx = angles.size()-1; + } + } + assert(border.size() == angles.size()); + assert(border.size() == lengths.size()); + + // First go around the border and pick what might be circular segments. + // Save pair of indices to where such potential segments start and end. + // Also remember the length of these segments. + int start_idx = -1; + bool circle = false; + bool first_iter = true; + std::vector circles; + std::vector edges; + std::vector> circles_idxs; + //std::vector circles_lengths; + std::vector single_circle; // could be in loop-scope, but reallocations + double single_circle_length = 0.; + int first_pt_idx = offset_to_index(first_different_angle_idx, 1); + int i = first_pt_idx; + while (i != first_pt_idx || first_iter) { + if (are_angles_same(angles[i], angles[offset_to_index(i,-1)]) + && i != offset_to_index(first_pt_idx, -1) // not the last point + && i != start_idx ) { + // circle + if (! circle) { + circle = true; + single_circle.clear(); + single_circle_length = 0.; + start_idx = offset_to_index(i, -2); + single_circle = { border[start_idx], border[offset_to_index(start_idx,1)] }; + single_circle_length += lengths[offset_to_index(i, -1)]; + } + single_circle.emplace_back(border[i]); + single_circle_length += lengths[i]; + } else { + if (circle && single_circle.size() >= 5) { // Less than 5 vertices? Not a circle. + single_circle.emplace_back(border[i]); + single_circle_length += lengths[i]; + + bool accept_circle = true; + { + // Check that lengths of internal (!!!) edges match. + int j = offset_to_index(start_idx, 3); + while (j != i) { + if (! are_lengths_same(lengths[offset_to_index(j,-1)], lengths[j])) { + accept_circle = false; + break; + } + j = offset_to_index(j, 1); + } + } + + if (accept_circle) { + const auto& [center, radius, err] = get_center_and_radius(single_circle, trafo, trafo_inv); + + // Check that the fit went well. The tolerance is high, only to + // reject complete failures. + accept_circle &= err < 0.05; + + // If the segment subtends less than 90 degrees, throw it away. + accept_circle &= single_circle_length / radius > 0.9*M_PI/2.; + + if (accept_circle) { + // Add the circle and remember indices into borders. + circles_idxs.emplace_back(start_idx, i); + circles.emplace_back(SurfaceFeature(SurfaceFeatureType::Circle, center, plane.normal, std::nullopt, radius)); + } + } + } + circle = false; + } + // Take care of the wrap around. + first_iter = false; + i = offset_to_index(i, 1); + } + + // We have the circles. Now go around again and pick edges, while jumping over circles. + if (circles_idxs.empty()) { + // Just add all edges. + for (int i=1; i 1 || circles_idxs.front().first != circles_idxs.front().second) { + // There is at least one circular segment. Start at its end and add edges until the start of the next one. + int i = circles_idxs.front().second; + int circle_idx = 1; + while (true) { + i = offset_to_index(i, 1); + edges.emplace_back(SurfaceFeature(SurfaceFeatureType::Edge, border[offset_to_index(i,-1)], border[i])); + if (circle_idx < int(circles_idxs.size()) && i == circles_idxs[circle_idx].first) { + i = circles_idxs[circle_idx].second; + ++circle_idx; + } + if (i == circles_idxs.front().first) + break; + } + } + + // Merge adjacent edges where needed. + assert(std::all_of(edges.begin(), edges.end(), + [](const SurfaceFeature& f) { return f.get_type() == SurfaceFeatureType::Edge; })); + for (int i=edges.size()-1; i>=0; --i) { + const auto& [first_start, first_end] = edges[i==0 ? edges.size()-1 : i-1].get_edge(); + const auto& [second_start, second_end] = edges[i].get_edge(); + + if (Slic3r::is_approx(first_end, second_start) + && Slic3r::is_approx((first_end-first_start).normalized().dot((second_end-second_start).normalized()), 1.)) { + // The edges have the same direction and share a point. Merge them. + edges[i==0 ? edges.size()-1 : i-1] = SurfaceFeature(SurfaceFeatureType::Edge, first_start, second_end); + edges.erase(edges.begin() + i); + } + } + + // Now move the circles and edges into the feature list for the plane. + assert(std::all_of(circles.begin(), circles.end(), [](const SurfaceFeature& f) { + return f.get_type() == SurfaceFeatureType::Circle; + })); + assert(std::all_of(edges.begin(), edges.end(), [](const SurfaceFeature& f) { + return f.get_type() == SurfaceFeatureType::Edge; + })); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(circles.begin()), + std::make_move_iterator(circles.end())); + plane.surface_features.insert(plane.surface_features.end(), std::make_move_iterator(edges.begin()), + std::make_move_iterator(edges.end())); + } + } + + // The last surface feature is the plane itself. + Vec3d cog = Vec3d::Zero(); + size_t counter = 0; + for (const std::vector& b : plane.borders) { + for (size_t i = 1; i < b.size(); ++i) { + cog += b[i]; + ++counter; + } + } + cog /= double(counter); + plane.surface_features.emplace_back(SurfaceFeature(SurfaceFeatureType::Plane, + plane.normal, cog, std::optional(), plane_idx + 0.0001)); + + plane.borders.clear(); + plane.borders.shrink_to_fit(); + + plane.features_extracted = true; +} + + + + + + + + +std::optional MeasuringImpl::get_feature(size_t face_idx, const Vec3d& point) +{ + if (face_idx >= m_face_to_plane.size()) + return std::optional(); + + const PlaneData& plane = m_planes[m_face_to_plane[face_idx]]; + + if (! plane.features_extracted) + extract_features(m_face_to_plane[face_idx]); + + size_t closest_feature_idx = size_t(-1); + double min_dist = std::numeric_limits::max(); + + MeasurementResult res; + SurfaceFeature point_sf(point); + + assert(plane.surface_features.empty() || plane.surface_features.back().get_type() == SurfaceFeatureType::Plane); + + for (size_t i=0; idist; + if (dist < feature_hover_limit && dist < min_dist) { + min_dist = std::min(dist, min_dist); + closest_feature_idx = i; + } + } + } + + if (closest_feature_idx != size_t(-1)) { + const SurfaceFeature& f = plane.surface_features[closest_feature_idx]; + if (f.get_type() == SurfaceFeatureType::Edge) { + // If this is an edge, check if we are not close to the endpoint. If so, + // we will include the endpoint as well. Close = 10% of the lenghth of + // the edge, clamped between 0.025 and 0.5 mm. + const auto& [sp, ep] = f.get_edge(); + double len_sq = (ep-sp).squaredNorm(); + double limit_sq = std::max(0.025*0.025, std::min(0.5*0.5, 0.1 * 0.1 * len_sq)); + + if ((point-sp).squaredNorm() < limit_sq) + return std::make_optional(SurfaceFeature(sp)); + if ((point-ep).squaredNorm() < limit_sq) + return std::make_optional(SurfaceFeature(ep)); + } + return std::make_optional(f); + } + + // Nothing detected, return the plane as a whole. + assert(plane.surface_features.back().get_type() == SurfaceFeatureType::Plane); + return std::make_optional(plane.surface_features.back()); +} + + + + + +int MeasuringImpl::get_num_of_planes() const +{ + return (m_planes.size()); +} + + + +const std::vector& MeasuringImpl::get_plane_triangle_indices(int idx) const +{ + assert(idx >= 0 && idx < int(m_planes.size())); + return m_planes[idx].facets; +} + +const std::vector& MeasuringImpl::get_plane_features(unsigned int plane_id) +{ + assert(plane_id < m_planes.size()); + if (! m_planes[plane_id].features_extracted) + extract_features(plane_id); + return m_planes[plane_id].surface_features; +} + +const indexed_triangle_set& MeasuringImpl::get_its() const +{ + return this->m_its; +} + + + + + + + + + + + +Measuring::Measuring(const indexed_triangle_set& its) +: priv{std::make_unique(its)} +{} + +Measuring::~Measuring() {} + + + +std::optional Measuring::get_feature(size_t face_idx, const Vec3d& point) const +{ + return priv->get_feature(face_idx, point); +} + + +int Measuring::get_num_of_planes() const +{ + return priv->get_num_of_planes(); +} + + +const std::vector& Measuring::get_plane_triangle_indices(int idx) const +{ + return priv->get_plane_triangle_indices(idx); +} + +const std::vector& Measuring::get_plane_features(unsigned int plane_id) const +{ + return priv->get_plane_features(plane_id); +} + +const indexed_triangle_set& Measuring::get_its() const +{ + return priv->get_its(); +} + +const AngleAndEdges AngleAndEdges::Dummy = { 0.0, Vec3d::Zero(), { Vec3d::Zero(), Vec3d::Zero() }, { Vec3d::Zero(), Vec3d::Zero() }, 0.0, true }; + +static AngleAndEdges angle_edge_edge(const std::pair& e1, const std::pair& e2) +{ + if (are_parallel(e1, e2)) + return AngleAndEdges::Dummy; + + Vec3d e1_unit = edge_direction(e1.first, e1.second); + Vec3d e2_unit = edge_direction(e2.first, e2.second); + + // project edges on the plane defined by them + Vec3d normal = e1_unit.cross(e2_unit).normalized(); + const Eigen::Hyperplane plane(normal, e1.first); + Vec3d e11_proj = plane.projection(e1.first); + Vec3d e12_proj = plane.projection(e1.second); + Vec3d e21_proj = plane.projection(e2.first); + Vec3d e22_proj = plane.projection(e2.second); + + const bool coplanar = (e2.first - e21_proj).norm() < EPSILON && (e2.second - e22_proj).norm() < EPSILON; + + // rotate the plane to become the XY plane + auto qp = Eigen::Quaternion::FromTwoVectors(normal, Vec3d::UnitZ()); + auto qp_inverse = qp.inverse(); + const Vec3d e11_rot = qp * e11_proj; + const Vec3d e12_rot = qp * e12_proj; + const Vec3d e21_rot = qp * e21_proj; + const Vec3d e22_rot = qp * e22_proj; + + // discard Z + const Vec2d e11_rot_2d = Vec2d(e11_rot.x(), e11_rot.y()); + const Vec2d e12_rot_2d = Vec2d(e12_rot.x(), e12_rot.y()); + const Vec2d e21_rot_2d = Vec2d(e21_rot.x(), e21_rot.y()); + const Vec2d e22_rot_2d = Vec2d(e22_rot.x(), e22_rot.y()); + + // find intersection (arc center) of edges in XY plane + const Eigen::Hyperplane e1_rot_2d_line = Eigen::Hyperplane::Through(e11_rot_2d, e12_rot_2d); + const Eigen::Hyperplane e2_rot_2d_line = Eigen::Hyperplane::Through(e21_rot_2d, e22_rot_2d); + const Vec2d center_rot_2d = e1_rot_2d_line.intersection(e2_rot_2d_line); + + // arc center in original coordinate + const Vec3d center = qp_inverse * Vec3d(center_rot_2d.x(), center_rot_2d.y(), e11_rot.z()); + + // ensure the edges are pointing away from the center + std::pair out_e1 = e1; + std::pair out_e2 = e2; + if ((center_rot_2d - e11_rot_2d).squaredNorm() > (center_rot_2d - e12_rot_2d).squaredNorm()) { + std::swap(e11_proj, e12_proj); + std::swap(out_e1.first, out_e1.second); + e1_unit = -e1_unit; + } + if ((center_rot_2d - e21_rot_2d).squaredNorm() > (center_rot_2d - e22_rot_2d).squaredNorm()) { + std::swap(e21_proj, e22_proj); + std::swap(out_e2.first, out_e2.second); + e2_unit = -e2_unit; + } + + // arc angle + const double angle = std::acos(std::clamp(e1_unit.dot(e2_unit), -1.0, 1.0)); + // arc radius + const Vec3d e1_proj_mid = 0.5 * (e11_proj + e12_proj); + const Vec3d e2_proj_mid = 0.5 * (e21_proj + e22_proj); + const double radius = std::min((center - e1_proj_mid).norm(), (center - e2_proj_mid).norm()); + + return { angle, center, out_e1, out_e2, radius, coplanar }; +} + +static AngleAndEdges angle_edge_plane(const std::pair& e, const std::tuple& p) +{ + const auto& [idx, normal, origin] = p; + Vec3d e1e2_unit = edge_direction(e); + if (are_perpendicular(e1e2_unit, normal)) + return AngleAndEdges::Dummy; + + // ensure the edge is pointing away from the intersection + // 1st calculate instersection between edge and plane + const Eigen::Hyperplane plane(normal, origin); + const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second); + const Vec3d inters = line.intersectionPoint(plane); + + // then verify edge direction and revert it, if needed + Vec3d e1 = e.first; + Vec3d e2 = e.second; + if ((e1 - inters).squaredNorm() > (e2 - inters).squaredNorm()) { + std::swap(e1, e2); + e1e2_unit = -e1e2_unit; + } + + if (are_parallel(e1e2_unit, normal)) { + const std::array basis = orthonormal_basis(e1e2_unit); + const double radius = (0.5 * (e1 + e2) - inters).norm(); + const Vec3d edge_on_plane_dir = (basis[1].dot(origin - inters) >= 0.0) ? basis[1] : -basis[1]; + std::pair edge_on_plane = std::make_pair(inters, inters + radius * edge_on_plane_dir); + if (!inters.isApprox(e1)) { + edge_on_plane.first += radius * edge_on_plane_dir; + edge_on_plane.second += radius * edge_on_plane_dir; + } + return AngleAndEdges(0.5 * double(PI), inters, std::make_pair(e1, e2), edge_on_plane, radius, inters.isApprox(e1)); + } + + const Vec3d e1e2 = e2 - e1; + const double e1e2_len = e1e2.norm(); + + // calculate 2nd edge (on the plane) + const Vec3d temp = normal.cross(e1e2); + const Vec3d edge_on_plane_unit = normal.cross(temp).normalized(); + std::pair edge_on_plane = { origin, origin + e1e2_len * edge_on_plane_unit }; + + // ensure the 2nd edge is pointing in the correct direction + const Vec3d test_edge = (edge_on_plane.second - edge_on_plane.first).cross(e1e2); + if (test_edge.dot(temp) < 0.0) + edge_on_plane = { origin, origin - e1e2_len * edge_on_plane_unit }; + + AngleAndEdges ret = angle_edge_edge({ e1, e2 }, edge_on_plane); + ret.radius = (inters - 0.5 * (e1 + e2)).norm(); + return ret; +} + +static AngleAndEdges angle_plane_plane(const std::tuple& p1, const std::tuple& p2) +{ + const auto& [idx1, normal1, origin1] = p1; + const auto& [idx2, normal2, origin2] = p2; + + // are planes parallel ? + if (are_parallel(normal1, normal2)) + return AngleAndEdges::Dummy; + + auto intersection_plane_plane = [](const Vec3d& n1, const Vec3d& o1, const Vec3d& n2, const Vec3d& o2) { + Eigen::MatrixXd m(2, 3); + m << n1.x(), n1.y(), n1.z(), n2.x(), n2.y(), n2.z(); + Eigen::VectorXd b(2); + b << o1.dot(n1), o2.dot(n2); + Eigen::VectorXd x = m.colPivHouseholderQr().solve(b); + return std::make_pair(n1.cross(n2).normalized(), Vec3d(x(0), x(1), x(2))); + }; + + // Calculate intersection line between planes + const auto [intersection_line_direction, intersection_line_origin] = intersection_plane_plane(normal1, origin1, normal2, origin2); + + // Project planes' origin on intersection line + const Eigen::ParametrizedLine intersection_line = Eigen::ParametrizedLine(intersection_line_origin, intersection_line_direction); + const Vec3d origin1_proj = intersection_line.projection(origin1); + const Vec3d origin2_proj = intersection_line.projection(origin2); + + // Calculate edges on planes + const Vec3d edge_on_plane1_unit = (origin1 - origin1_proj).normalized(); + const Vec3d edge_on_plane2_unit = (origin2 - origin2_proj).normalized(); + const double radius = std::max(10.0, std::max((origin1 - origin1_proj).norm(), (origin2 - origin2_proj).norm())); + const std::pair edge_on_plane1 = { origin1_proj + radius * edge_on_plane1_unit, origin1_proj + 2.0 * radius * edge_on_plane1_unit }; + const std::pair edge_on_plane2 = { origin2_proj + radius * edge_on_plane2_unit, origin2_proj + 2.0 * radius * edge_on_plane2_unit }; + + AngleAndEdges ret = angle_edge_edge(edge_on_plane1, edge_on_plane2); + ret.radius = radius; + return ret; +} + + + + + + + +MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring) +{ + assert(a.get_type() != SurfaceFeatureType::Undef && b.get_type() != SurfaceFeatureType::Undef); + + const bool swap = int(a.get_type()) > int(b.get_type()); + const SurfaceFeature& f1 = swap ? b : a; + const SurfaceFeature& f2 = swap ? a : b; + + MeasurementResult result; + + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + if (f1.get_type() == SurfaceFeatureType::Point) { + if (f2.get_type() == SurfaceFeatureType::Point) { + Vec3d diff = (f2.get_point() - f1.get_point()); + result.distance_strict = std::make_optional(DistAndPoints{diff.norm(), f1.get_point(), f2.get_point()}); + result.distance_xyz = diff.cwiseAbs(); + + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Edge) { + const auto [s,e] = f2.get_edge(); + const Eigen::ParametrizedLine line(s, (e-s).normalized()); + const double dist_inf = line.distance(f1.get_point()); + const Vec3d proj = line.projection(f1.get_point()); + const double len_sq = (e-s).squaredNorm(); + const double dist_start_sq = (proj-s).squaredNorm(); + const double dist_end_sq = (proj-e).squaredNorm(); + if (dist_start_sq < len_sq && dist_end_sq < len_sq) { + // projection falls on the line - the strict distance is the same as infinite + result.distance_strict = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj}); + } else { // the result is the closer of the endpoints + const bool s_is_closer = dist_start_sq < dist_end_sq; + result.distance_strict = std::make_optional(DistAndPoints{std::sqrt(std::min(dist_start_sq, dist_end_sq) + sqr(dist_inf)), f1.get_point(), s_is_closer ? s : e}); + } + result.distance_infinite = std::make_optional(DistAndPoints{dist_inf, f1.get_point(), proj}); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Circle) { + // Find a plane containing normal, center and the point. + const auto [c, radius, n] = f2.get_circle(); + const Eigen::Hyperplane circle_plane(n, c); + const Vec3d proj = circle_plane.projection(f1.get_point()); + if (proj.isApprox(c)) { + const Vec3d p_on_circle = c + radius * get_orthogonal(n, true); + result.distance_strict = std::make_optional(DistAndPoints{ radius, c, p_on_circle }); + } + else { + const Eigen::Hyperplane circle_plane(n, c); + const Vec3d proj = circle_plane.projection(f1.get_point()); + const double dist = std::sqrt(std::pow((proj - c).norm() - radius, 2.) + + (f1.get_point() - proj).squaredNorm()); + + const Vec3d p_on_circle = c + radius * (proj - c).normalized(); + result.distance_strict = std::make_optional(DistAndPoints{ dist, f1.get_point(), p_on_circle }); // TODO + } + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + const auto [idx, normal, pt] = f2.get_plane(); + Eigen::Hyperplane plane(normal, pt); + result.distance_infinite = std::make_optional(DistAndPoints{plane.absDistance(f1.get_point()), f1.get_point(), plane.projection(f1.get_point())}); // TODO + // TODO: result.distance_strict = + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } + else if (f1.get_type() == SurfaceFeatureType::Edge) { + if (f2.get_type() == SurfaceFeatureType::Edge) { + std::vector distances; + + auto add_point_edge_distance = [&distances](const Vec3d& v, const std::pair& e) { + const MeasurementResult res = get_measurement(SurfaceFeature(v), SurfaceFeature(SurfaceFeatureType::Edge, e.first, e.second)); + double distance = res.distance_strict->dist; + Vec3d v2 = res.distance_strict->to; + + const Vec3d e1e2 = e.second - e.first; + const Vec3d e1v2 = v2 - e.first; + if (e1v2.dot(e1e2) >= 0.0 && e1v2.norm() < e1e2.norm()) + distances.emplace_back(distance, v, v2); + }; + + std::pair e1 = f1.get_edge(); + std::pair e2 = f2.get_edge(); + + distances.emplace_back((e2.first - e1.first).norm(), e1.first, e2.first); + distances.emplace_back((e2.second - e1.first).norm(), e1.first, e2.second); + distances.emplace_back((e2.first - e1.second).norm(), e1.second, e2.first); + distances.emplace_back((e2.second - e1.second).norm(), e1.second, e2.second); + add_point_edge_distance(e1.first, e2); + add_point_edge_distance(e1.second, e2); + add_point_edge_distance(e2.first, e1); + add_point_edge_distance(e2.second, e1); + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(*it); + + result.angle = angle_edge_edge(f1.get_edge(), f2.get_edge()); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Circle) { + const std::pair e = f1.get_edge(); + const auto& [center, radius, normal] = f2.get_circle(); + const Vec3d e1e2 = (e.second - e.first); + const Vec3d e1e2_unit = e1e2.normalized(); + + std::vector distances; + distances.emplace_back(*get_measurement(SurfaceFeature(e.first), f2).distance_strict); + distances.emplace_back(*get_measurement(SurfaceFeature(e.second), f2).distance_strict); + + const Eigen::Hyperplane plane(e1e2_unit, center); + const Eigen::ParametrizedLine line = Eigen::ParametrizedLine::Through(e.first, e.second); + const Vec3d inter = line.intersectionPoint(plane); + const Vec3d e1inter = inter - e.first; + if (e1inter.dot(e1e2) >= 0.0 && e1inter.norm() < e1e2.norm()) + distances.emplace_back(*get_measurement(SurfaceFeature(inter), f2).distance_strict); + + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{it->dist, it->from, it->to}); + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + assert(measuring != nullptr); + + const auto [from, to] = f1.get_edge(); + const auto [idx, normal, origin] = f2.get_plane(); + + const Vec3d edge_unit = (to - from).normalized(); + if (are_perpendicular(edge_unit, normal)) { + std::vector distances; + const Eigen::Hyperplane plane(normal, origin); + distances.push_back(DistAndPoints{ plane.absDistance(from), from, plane.projection(from) }); + distances.push_back(DistAndPoints{ plane.absDistance(to), to, plane.projection(to) }); + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + else { + const std::vector& plane_features = measuring->get_plane_features(idx); + std::vector distances; + for (const SurfaceFeature& sf : plane_features) { + if (sf.get_type() == SurfaceFeatureType::Edge) { + const auto m = get_measurement(sf, f1); + if (!m.distance_infinite.has_value()) { + distances.clear(); + break; + } + else + distances.push_back(*m.distance_infinite); + } + } + if (!distances.empty()) { + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + } + result.angle = angle_edge_plane(f1.get_edge(), f2.get_plane()); + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } else if (f1.get_type() == SurfaceFeatureType::Circle) { + if (f2.get_type() == SurfaceFeatureType::Circle) { + const auto [c0, r0, n0] = f1.get_circle(); + const auto [c1, r1, n1] = f2.get_circle(); + + // The following code is an adaptation of the algorithm found in: + // https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/DistCircle3Circle3.h + // and described in: + // https://www.geometrictools.com/Documentation/DistanceToCircle3.pdf + + struct ClosestInfo + { + double sqrDistance{ 0.0 }; + Vec3d circle0Closest{ Vec3d::Zero() }; + Vec3d circle1Closest{ Vec3d::Zero() }; + + inline bool operator < (const ClosestInfo& other) const { return sqrDistance < other.sqrDistance; } + }; + std::array candidates{}; + + const double zero = 0.0; + + const Vec3d D = c1 - c0; + + if (!are_parallel(n0, n1)) { + // Get parameters for constructing the degree-8 polynomial phi. + const double one = 1.0; + const double two = 2.0; + const double r0sqr = sqr(r0); + const double r1sqr = sqr(r1); + + // Compute U1 and V1 for the plane of circle1. + const std::array basis = orthonormal_basis(n1); + const Vec3d U1 = basis[0]; + const Vec3d V1 = basis[1]; + + // Construct the polynomial phi(cos(theta)). + const Vec3d N0xD = n0.cross(D); + const Vec3d N0xU1 = n0.cross(U1); + const Vec3d N0xV1 = n0.cross(V1); + const double a0 = r1 * D.dot(U1); + const double a1 = r1 * D.dot(V1); + const double a2 = N0xD.dot(N0xD); + const double a3 = r1 * N0xD.dot(N0xU1); + const double a4 = r1 * N0xD.dot(N0xV1); + const double a5 = r1sqr * N0xU1.dot(N0xU1); + const double a6 = r1sqr * N0xU1.dot(N0xV1); + const double a7 = r1sqr * N0xV1.dot(N0xV1); + Polynomial1 p0{ a2 + a7, two * a3, a5 - a7 }; + Polynomial1 p1{ two * a4, two * a6 }; + Polynomial1 p2{ zero, a1 }; + Polynomial1 p3{ -a0 }; + Polynomial1 p4{ -a6, a4, two * a6 }; + Polynomial1 p5{ -a3, a7 - a5 }; + Polynomial1 tmp0{ one, zero, -one }; + Polynomial1 tmp1 = p2 * p2 + tmp0 * p3 * p3; + Polynomial1 tmp2 = two * p2 * p3; + Polynomial1 tmp3 = p4 * p4 + tmp0 * p5 * p5; + Polynomial1 tmp4 = two * p4 * p5; + Polynomial1 p6 = p0 * tmp1 + tmp0 * p1 * tmp2 - r0sqr * tmp3; + Polynomial1 p7 = p0 * tmp2 + p1 * tmp1 - r0sqr * tmp4; + + // Parameters for polynomial root finding. The roots[] array + // stores the roots. We need only the unique ones, which is + // the responsibility of the set uniqueRoots. The pairs[] + // array stores the (cosine,sine) information mentioned in the + // PDF. TODO: Choose the maximum number of iterations for root + // finding based on specific polynomial data? + const uint32_t maxIterations = 128; + int32_t degree = 0; + size_t numRoots = 0; + std::array roots{}; + std::set uniqueRoots{}; + size_t numPairs = 0; + std::array, 16> pairs{}; + double temp = zero; + double sn = zero; + + if (p7.GetDegree() > 0 || p7[0] != zero) { + // H(cs,sn) = p6(cs) + sn * p7(cs) + Polynomial1 phi = p6 * p6 - tmp0 * p7 * p7; + degree = static_cast(phi.GetDegree()); + assert(degree > 0); + numRoots = RootsPolynomial::Find(degree, &phi[0], maxIterations, roots.data()); + for (size_t i = 0; i < numRoots; ++i) { + uniqueRoots.insert(roots[i]); + } + + for (auto const& cs : uniqueRoots) { + if (std::fabs(cs) <= one) { + temp = p7(cs); + if (temp != zero) { + sn = -p6(cs) / temp; + pairs[numPairs++] = std::make_pair(cs, sn); + } + else { + temp = std::max(one - sqr(cs), zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + } + else { + // H(cs,sn) = p6(cs) + degree = static_cast(p6.GetDegree()); + assert(degree > 0); + numRoots = RootsPolynomial::Find(degree, &p6[0], maxIterations, roots.data()); + for (size_t i = 0; i < numRoots; ++i) { + uniqueRoots.insert(roots[i]); + } + + for (auto const& cs : uniqueRoots) { + if (std::fabs(cs) <= one) { + temp = std::max(one - sqr(cs), zero); + sn = std::sqrt(temp); + pairs[numPairs++] = std::make_pair(cs, sn); + if (sn != zero) + pairs[numPairs++] = std::make_pair(cs, -sn); + } + } + } + + for (size_t i = 0; i < numPairs; ++i) { + ClosestInfo& info = candidates[i]; + Vec3d delta = D + r1 * (pairs[i].first * U1 + pairs[i].second * V1); + info.circle1Closest = c0 + delta; + const double N0dDelta = n0.dot(delta); + const double lenN0xDelta = n0.cross(delta).norm(); + if (lenN0xDelta > 0.0) { + const double diff = lenN0xDelta - r0; + info.sqrDistance = sqr(N0dDelta) + sqr(diff); + delta -= N0dDelta * n0; + delta.normalize(); + info.circle0Closest = c0 + r0 * delta; + } + else { + const Vec3d r0U0 = r0 * get_orthogonal(n0, true); + const Vec3d diff = delta - r0U0; + info.sqrDistance = diff.dot(diff); + info.circle0Closest = c0 + r0U0; + } + } + + std::sort(candidates.begin(), candidates.begin() + numPairs); + } + else { + ClosestInfo& info = candidates[0]; + + const double N0dD = n0.dot(D); + const Vec3d normProj = N0dD * n0; + const Vec3d compProj = D - normProj; + Vec3d U = compProj; + const double d = U.norm(); + U.normalize(); + + // The configuration is determined by the relative location of the + // intervals of projection of the circles on to the D-line. + // Circle0 projects to [-r0,r0] and circle1 projects to + // [d-r1,d+r1]. + const double dmr1 = d - r1; + double distance; + if (dmr1 >= r0) { + // d >= r0 + r1 + // The circles are separated (d > r0 + r1) or tangent with one + // outside the other (d = r0 + r1). + distance = dmr1 - r0; + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 - r1 * U; + } + else { + // d < r0 + r1 + // The cases implicitly use the knowledge that d >= 0. + const double dpr1 = d + r1; + if (dpr1 <= r0) { + // Circle1 is inside circle0. + distance = r0 - dpr1; + if (d > 0.0) { + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + else { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = get_orthogonal(n0, true); + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + } + else if (dmr1 <= -r0) { + // Circle0 is inside circle1. + distance = -r0 - dmr1; + if (d > 0.0) { + info.circle0Closest = c0 - r0 * U; + info.circle1Closest = c1 - r1 * U; + } + else { + // The circles are concentric, so U = (0,0,0). + // Construct a vector perpendicular to N0 to use for + // closest points. + U = get_orthogonal(n0, true); + info.circle0Closest = c0 + r0 * U; + info.circle1Closest = c1 + r1 * U; + } + } + else { + distance = (c1 - c0).norm(); + info.circle0Closest = c0; + info.circle1Closest = c1; + } + } + + info.sqrDistance = distance * distance + N0dD * N0dD; + } + + result.distance_infinite = std::make_optional(DistAndPoints{ std::sqrt(candidates[0].sqrDistance), candidates[0].circle0Closest, candidates[0].circle1Closest }); // TODO + /////////////////////////////////////////////////////////////////////////// + } else if (f2.get_type() == SurfaceFeatureType::Plane) { + assert(measuring != nullptr); + + const auto [center, radius, normal1] = f1.get_circle(); + const auto [idx2, normal2, origin2] = f2.get_plane(); + + const bool coplanar = are_parallel(normal1, normal2) && Eigen::Hyperplane(normal1, center).absDistance(origin2) < EPSILON; + if (!coplanar) { + const std::vector& plane_features = measuring->get_plane_features(idx2); + std::vector distances; + for (const SurfaceFeature& sf : plane_features) { + if (sf.get_type() == SurfaceFeatureType::Edge) { + const auto m = get_measurement(sf, f1); + if (!m.distance_infinite.has_value()) { + distances.clear(); + break; + } + else + distances.push_back(*m.distance_infinite); + } + } + if (!distances.empty()) { + auto it = std::min_element(distances.begin(), distances.end(), + [](const DistAndPoints& item1, const DistAndPoints& item2) { + return item1.dist < item2.dist; + }); + result.distance_infinite = std::make_optional(DistAndPoints{ it->dist, it->from, it->to }); + } + } + } + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////// + } else if (f1.get_type() == SurfaceFeatureType::Plane) { + const auto [idx1, normal1, pt1] = f1.get_plane(); + const auto [idx2, normal2, pt2] = f2.get_plane(); + + if (are_parallel(normal1, normal2)) { + // The planes are parallel, calculate distance. + const Eigen::Hyperplane plane(normal1, pt1); + result.distance_infinite = std::make_optional(DistAndPoints{ plane.absDistance(pt2), pt2, plane.projection(pt2) }); // TODO + } + else + result.angle = angle_plane_plane(f1.get_plane(), f2.get_plane()); + } + + return result; +} + + + + + + + + +} // namespace Measure +} // namespace Slic3r + diff --git a/src/libslic3r/Measure.hpp b/src/libslic3r/Measure.hpp new file mode 100644 index 00000000000..70f446afd99 --- /dev/null +++ b/src/libslic3r/Measure.hpp @@ -0,0 +1,196 @@ +#ifndef Slic3r_Measure_hpp_ +#define Slic3r_Measure_hpp_ + +#include +#include + +#include "Point.hpp" + + +struct indexed_triangle_set; + + + +namespace Slic3r { + +class TriangleMesh; + +namespace Measure { + + +enum class SurfaceFeatureType : int { + Undef = 0, + Point = 1 << 0, + Edge = 1 << 1, + Circle = 1 << 2, + Plane = 1 << 3 +}; + +class SurfaceFeature { +public: + SurfaceFeature(SurfaceFeatureType type, const Vec3d& pt1, const Vec3d& pt2, std::optional pt3 = std::nullopt, double value = 0.0) + : m_type(type), m_pt1(pt1), m_pt2(pt2), m_pt3(pt3), m_value(value) {} + + explicit SurfaceFeature(const Vec3d& pt) + : m_type{SurfaceFeatureType::Point}, m_pt1{pt} {} + + // Get type of this feature. + SurfaceFeatureType get_type() const { return m_type; } + + // For points, return the point. + Vec3d get_point() const { assert(m_type == SurfaceFeatureType::Point); return m_pt1; } + + // For edges, return start and end. + std::pair get_edge() const { assert(m_type == SurfaceFeatureType::Edge); return std::make_pair(m_pt1, m_pt2); } + + // For circles, return center, radius and normal. + std::tuple get_circle() const { assert(m_type == SurfaceFeatureType::Circle); return std::make_tuple(m_pt1, m_value, m_pt2); } + + // For planes, return index into vector provided by Measuring::get_plane_triangle_indices, normal and point. + std::tuple get_plane() const { assert(m_type == SurfaceFeatureType::Plane); return std::make_tuple(int(m_value), m_pt1, m_pt2); } + + // For anything, return an extra point that should also be considered a part of this. + std::optional get_extra_point() const { assert(m_type != SurfaceFeatureType::Undef); return m_pt3; } + + bool operator == (const SurfaceFeature& other) const { + if (this->m_type != other.m_type) return false; + switch (this->m_type) + { + case SurfaceFeatureType::Undef: { break; } + case SurfaceFeatureType::Point: { return (this->m_pt1.isApprox(other.m_pt1)); } + case SurfaceFeatureType::Edge: { + return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2)) || + (this->m_pt1.isApprox(other.m_pt2) && this->m_pt2.isApprox(other.m_pt1)); + } + case SurfaceFeatureType::Plane: + case SurfaceFeatureType::Circle: { + return (this->m_pt1.isApprox(other.m_pt1) && this->m_pt2.isApprox(other.m_pt2) && std::abs(this->m_value - other.m_value) < EPSILON); + } + } + + return false; + } + + bool operator != (const SurfaceFeature& other) const { + return !operator == (other); + } + +private: + SurfaceFeatureType m_type{ SurfaceFeatureType::Undef }; + Vec3d m_pt1{ Vec3d::Zero() }; + Vec3d m_pt2{ Vec3d::Zero() }; + std::optional m_pt3; + double m_value{ 0.0 }; +}; + + + +class MeasuringImpl; + + +class Measuring { +public: + // Construct the measurement object on a given its. + explicit Measuring(const indexed_triangle_set& its); + ~Measuring(); + + + // Given a face_idx where the mouse cursor points, return a feature that + // should be highlighted (if any). + std::optional get_feature(size_t face_idx, const Vec3d& point) const; + + // Return total number of planes. + int get_num_of_planes() const; + + // Returns a list of triangle indices for given plane. + const std::vector& get_plane_triangle_indices(int idx) const; + + // Returns the surface features of the plane with the given index + const std::vector& get_plane_features(unsigned int plane_id) const; + + // Returns the mesh used for measuring + const indexed_triangle_set& get_its() const; + +private: + std::unique_ptr priv; +}; + + +struct DistAndPoints { + DistAndPoints(double dist_, Vec3d from_, Vec3d to_) : dist(dist_), from(from_), to(to_) {} + double dist; + Vec3d from; + Vec3d to; +}; + +struct AngleAndEdges { + AngleAndEdges(double angle_, const Vec3d& center_, const std::pair& e1_, const std::pair& e2_, double radius_, bool coplanar_) + : angle(angle_), center(center_), e1(e1_), e2(e2_), radius(radius_), coplanar(coplanar_) {} + double angle; + Vec3d center; + std::pair e1; + std::pair e2; + double radius; + bool coplanar; + + static const AngleAndEdges Dummy; +}; + +struct MeasurementResult { + std::optional angle; + std::optional distance_infinite; + std::optional distance_strict; + std::optional distance_xyz; + + bool has_distance_data() const { + return distance_infinite.has_value() || distance_strict.has_value(); + } + + bool has_any_data() const { + return angle.has_value() || distance_infinite.has_value() || distance_strict.has_value() || distance_xyz.has_value(); + } +}; + +// Returns distance/angle between two SurfaceFeatures. +MeasurementResult get_measurement(const SurfaceFeature& a, const SurfaceFeature& b, const Measuring* measuring = nullptr); + +inline Vec3d edge_direction(const Vec3d& from, const Vec3d& to) { return (to - from).normalized(); } +inline Vec3d edge_direction(const std::pair& e) { return edge_direction(e.first, e.second); } +inline Vec3d edge_direction(const SurfaceFeature& edge) { + assert(edge.get_type() == SurfaceFeatureType::Edge); + return edge_direction(edge.get_edge()); +} + +inline Vec3d plane_normal(const SurfaceFeature& plane) { + assert(plane.get_type() == SurfaceFeatureType::Plane); + return std::get<1>(plane.get_plane()); +} + +inline bool are_parallel(const Vec3d& v1, const Vec3d& v2) { return std::abs(std::abs(v1.dot(v2)) - 1.0) < EPSILON; } +inline bool are_perpendicular(const Vec3d& v1, const Vec3d& v2) { return std::abs(v1.dot(v2)) < EPSILON; } + +inline bool are_parallel(const std::pair& e1, const std::pair& e2) { + return are_parallel(e1.second - e1.first, e2.second - e2.first); +} +inline bool are_parallel(const SurfaceFeature& f1, const SurfaceFeature& f2) { + if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge) + return are_parallel(edge_direction(f1), edge_direction(f2)); + else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane) + return are_perpendicular(edge_direction(f1), plane_normal(f2)); + else + return false; +} + +inline bool are_perpendicular(const SurfaceFeature& f1, const SurfaceFeature& f2) { + if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Edge) + return are_perpendicular(edge_direction(f1), edge_direction(f2)); + else if (f1.get_type() == SurfaceFeatureType::Edge && f2.get_type() == SurfaceFeatureType::Plane) + return are_parallel(edge_direction(f1), plane_normal(f2)); + else + return false; +} + +} // namespace Measure +} // namespace Slic3r + +#endif // Slic3r_Measure_hpp_ diff --git a/src/libslic3r/MeasureUtils.hpp b/src/libslic3r/MeasureUtils.hpp new file mode 100644 index 00000000000..0ab4ac121d8 --- /dev/null +++ b/src/libslic3r/MeasureUtils.hpp @@ -0,0 +1,386 @@ +#ifndef Slic3r_MeasureUtils_hpp_ +#define Slic3r_MeasureUtils_hpp_ + +#include + +namespace Slic3r { +namespace Measure { + +// Utility class used to calculate distance circle-circle +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Polynomial1.h + +class Polynomial1 +{ +public: + Polynomial1(std::initializer_list values) + { + // C++ 11 will call the default constructor for + // Polynomial1 p{}, so it is guaranteed that + // values.size() > 0. + m_coefficient.resize(values.size()); + std::copy(values.begin(), values.end(), m_coefficient.begin()); + EliminateLeadingZeros(); + } + + // Construction and destruction. The first constructor creates a + // polynomial of the specified degree but sets all coefficients to + // zero (to ensure initialization). You are responsible for setting + // the coefficients, presumably with the degree-term set to a nonzero + // number. In the second constructor, the degree is the number of + // initializers plus 1, but then adjusted so that coefficient[degree] + // is not zero (unless all initializer values are zero). + explicit Polynomial1(uint32_t degree) + : m_coefficient(static_cast(degree) + 1, 0.0) + {} + + // Eliminate any leading zeros in the polynomial, except in the case + // the degree is 0 and the coefficient is 0. The elimination is + // necessary when arithmetic operations cause a decrease in the degree + // of the result. For example, (1 + x + x^2) + (1 + 2*x - x^2) = + // (2 + 3*x). The inputs both have degree 2, so the result is created + // with degree 2. After the addition we find that the degree is in + // fact 1 and resize the array of coefficients. This function is + // called internally by the arithmetic operators, but it is exposed in + // the public interface in case you need it for your own purposes. + void EliminateLeadingZeros() + { + const size_t size = m_coefficient.size(); + if (size > 1) { + const double zero = 0.0; + int32_t leading; + for (leading = static_cast(size) - 1; leading > 0; --leading) { + if (m_coefficient[leading] != zero) + break; + } + + m_coefficient.resize(++leading); + } + } + + // Set all coefficients to the specified value. + void SetCoefficients(double value) + { + std::fill(m_coefficient.begin(), m_coefficient.end(), value); + } + + inline uint32_t GetDegree() const + { + // By design, m_coefficient.size() > 0. + return static_cast(m_coefficient.size() - 1); + } + + inline const double& operator[](uint32_t i) const { return m_coefficient[i]; } + inline double& operator[](uint32_t i) { return m_coefficient[i]; } + + // Evaluate the polynomial. If the polynomial is invalid, the + // function returns zero. + double operator()(double t) const + { + int32_t i = static_cast(m_coefficient.size()); + double result = m_coefficient[--i]; + for (--i; i >= 0; --i) { + result *= t; + result += m_coefficient[i]; + } + return result; + } + +protected: + // The class is designed so that m_coefficient.size() >= 1. + std::vector m_coefficient; +}; + +inline Polynomial1 operator * (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + Polynomial1 result(p0Degree + p1Degree); + result.SetCoefficients(0.0); + for (uint32_t i0 = 0; i0 <= p0Degree; ++i0) { + for (uint32_t i1 = 0; i1 <= p1Degree; ++i1) { + result[i0 + i1] += p0[i0] * p1[i1]; + } + } + return result; +} + +inline Polynomial1 operator + (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + uint32_t i; + if (p0Degree >= p1Degree) { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p0Degree; ++i) { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) { + result[i] = p0[i] + p1[i]; + } + for (/**/; i <= p1Degree; ++i) { + result[i] = p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } +} + +inline Polynomial1 operator - (const Polynomial1& p0, const Polynomial1& p1) +{ + const uint32_t p0Degree = p0.GetDegree(); + const uint32_t p1Degree = p1.GetDegree(); + uint32_t i; + if (p0Degree >= p1Degree) { + Polynomial1 result(p0Degree); + for (i = 0; i <= p1Degree; ++i) { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p0Degree; ++i) { + result[i] = p0[i]; + } + result.EliminateLeadingZeros(); + return result; + } + else { + Polynomial1 result(p1Degree); + for (i = 0; i <= p0Degree; ++i) { + result[i] = p0[i] - p1[i]; + } + for (/**/; i <= p1Degree; ++i) { + result[i] = -p1[i]; + } + result.EliminateLeadingZeros(); + return result; + } +} + +inline Polynomial1 operator * (double scalar, const Polynomial1& p) +{ + const uint32_t degree = p.GetDegree(); + Polynomial1 result(degree); + for (uint32_t i = 0; i <= degree; ++i) { + result[i] = scalar * p[i]; + } + return result; +} + +// Utility class used to calculate distance circle-circle +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/RootsPolynomial.h + +class RootsPolynomial +{ +public: + // General equations: sum_{i=0}^{d} c(i)*t^i = 0. The input array 'c' + // must have at least d+1 elements and the output array 'root' must + // have at least d elements. + + // Find the roots on (-infinity,+infinity). + static int32_t Find(int32_t degree, const double* c, uint32_t maxIterations, double* roots) + { + if (degree >= 0 && c != nullptr) { + const double zero = 0.0; + while (degree >= 0 && c[degree] == zero) { + --degree; + } + + if (degree > 0) { + // Compute the Cauchy bound. + const double one = 1.0; + const double invLeading = one / c[degree]; + double maxValue = zero; + for (int32_t i = 0; i < degree; ++i) { + const double value = std::fabs(c[i] * invLeading); + if (value > maxValue) + maxValue = value; + } + const double bound = one + maxValue; + + return FindRecursive(degree, c, -bound, bound, maxIterations, roots); + } + else if (degree == 0) + // The polynomial is a nonzero constant. + return 0; + else { + // The polynomial is identically zero. + roots[0] = zero; + return 1; + } + } + else + // Invalid degree or c. + return 0; + } + + // If you know that p(tmin) * p(tmax) <= 0, then there must be at + // least one root in [tmin, tmax]. Compute it using bisection. + static bool Find(int32_t degree, const double* c, double tmin, double tmax, uint32_t maxIterations, double& root) + { + const double zero = 0.0; + double pmin = Evaluate(degree, c, tmin); + if (pmin == zero) { + root = tmin; + return true; + } + double pmax = Evaluate(degree, c, tmax); + if (pmax == zero) { + root = tmax; + return true; + } + + if (pmin * pmax > zero) + // It is not known whether the interval bounds a root. + return false; + + if (tmin >= tmax) + // Invalid ordering of interval endpoitns. + return false; + + for (uint32_t i = 1; i <= maxIterations; ++i) { + root = 0.5 * (tmin + tmax); + + // This test is designed for 'float' or 'double' when tmin + // and tmax are consecutive floating-point numbers. + if (root == tmin || root == tmax) + break; + + const double p = Evaluate(degree, c, root); + const double product = p * pmin; + if (product < zero) { + tmax = root; + pmax = p; + } + else if (product > zero) { + tmin = root; + pmin = p; + } + else + break; + } + + return true; + } + + // Support for the Find functions. + static int32_t FindRecursive(int32_t degree, double const* c, double tmin, double tmax, uint32_t maxIterations, double* roots) + { + // The base of the recursion. + const double zero = 0.0; + double root = zero; + if (degree == 1) { + int32_t numRoots; + if (c[1] != zero) { + root = -c[0] / c[1]; + numRoots = 1; + } + else if (c[0] == zero) { + root = zero; + numRoots = 1; + } + else + numRoots = 0; + + if (numRoots > 0 && tmin <= root && root <= tmax) { + roots[0] = root; + return 1; + } + return 0; + } + + // Find the roots of the derivative polynomial scaled by 1/degree. + // The scaling avoids the factorial growth in the coefficients; + // for example, without the scaling, the high-order term x^d + // becomes (d!)*x through multiple differentiations. With the + // scaling we instead get x. This leads to better numerical + // behavior of the root finder. + const int32_t derivDegree = degree - 1; + std::vector derivCoeff(static_cast(derivDegree) + 1); + std::vector derivRoots(derivDegree); + for (int32_t i = 0, ip1 = 1; i <= derivDegree; ++i, ++ip1) { + derivCoeff[i] = c[ip1] * (double)(ip1) / (double)degree; + } + const int32_t numDerivRoots = FindRecursive(degree - 1, &derivCoeff[0], tmin, tmax, maxIterations, &derivRoots[0]); + + int32_t numRoots = 0; + if (numDerivRoots > 0) { + // Find root on [tmin,derivRoots[0]]. + if (Find(degree, c, tmin, derivRoots[0], maxIterations, root)) + roots[numRoots++] = root; + + // Find root on [derivRoots[i],derivRoots[i+1]]. + for (int32_t i = 0, ip1 = 1; i <= numDerivRoots - 2; ++i, ++ip1) { + if (Find(degree, c, derivRoots[i], derivRoots[ip1], maxIterations, root)) + roots[numRoots++] = root; + } + + // Find root on [derivRoots[numDerivRoots-1],tmax]. + if (Find(degree, c, derivRoots[static_cast(numDerivRoots) - 1], tmax, maxIterations, root)) + roots[numRoots++] = root; + } + else { + // The polynomial is monotone on [tmin,tmax], so has at most one root. + if (Find(degree, c, tmin, tmax, maxIterations, root)) + roots[numRoots++] = root; + } + return numRoots; + } + + static double Evaluate(int32_t degree, const double* c, double t) + { + int32_t i = degree; + double result = c[i]; + while (--i >= 0) { + result = t * result + c[i]; + } + return result; + } +}; + +// Adaptation of code found in: +// https://github.com/davideberly/GeometricTools/blob/master/GTE/Mathematics/Vector.h + +// Construct a single vector orthogonal to the nonzero input vector. If +// the maximum absolute component occurs at index i, then the orthogonal +// vector U has u[i] = v[i+1], u[i+1] = -v[i], and all other components +// zero. The index addition i+1 is computed modulo N. +inline Vec3d get_orthogonal(const Vec3d& v, bool unitLength) +{ + double cmax = std::fabs(v[0]); + int32_t imax = 0; + for (int32_t i = 1; i < 3; ++i) { + double c = std::fabs(v[i]); + if (c > cmax) { + cmax = c; + imax = i; + } + } + + Vec3d result = Vec3d::Zero(); + int32_t inext = imax + 1; + if (inext == 3) + inext = 0; + + result[imax] = v[inext]; + result[inext] = -v[imax]; + if (unitLength) { + const double sqrDistance = result[imax] * result[imax] + result[inext] * result[inext]; + const double invLength = 1.0 / std::sqrt(sqrDistance); + result[imax] *= invLength; + result[inext] *= invLength; + } + return result; +} + +} // namespace Slic3r +} // namespace Measure + +#endif // Slic3r_MeasureUtils_hpp_ diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 481552c058c..ac1a7558ad9 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -1325,6 +1325,7 @@ class ModelInstance final : public ObjectBase void transform_polygon(Polygon* polygon) const; const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } + Transform3d get_matrix_no_offset() const { return m_transformation.get_matrix_no_offset(); } bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } bool is_assemble_initialized() { return m_assemble_initialized; } diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index dacf49b0803..47750d9e688 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -42,6 +42,7 @@ using Vec3i64 = Eigen::Matrix; // Vector types with a double coordinate base type. using Vec2f = Eigen::Matrix; using Vec3f = Eigen::Matrix; +using Vec4f = Eigen::Matrix; using Vec2d = Eigen::Matrix; using Vec3d = Eigen::Matrix; // BBS diff --git a/src/libslic3r/SurfaceMesh.hpp b/src/libslic3r/SurfaceMesh.hpp new file mode 100644 index 00000000000..93eb9fdaa6e --- /dev/null +++ b/src/libslic3r/SurfaceMesh.hpp @@ -0,0 +1,163 @@ +#ifndef slic3r_SurfaceMesh_hpp_ +#define slic3r_SurfaceMesh_hpp_ + +#include +#include + +#include "boost/container/small_vector.hpp" + +namespace Slic3r { + +class TriangleMesh; + + + +enum Face_index : int; + +class Halfedge_index { + friend class SurfaceMesh; + +public: + Halfedge_index() : m_face(Face_index(-1)), m_side(0) {} + Face_index face() const { return m_face; } + unsigned char side() const { return m_side; } + bool is_invalid() const { return int(m_face) < 0; } + bool operator!=(const Halfedge_index& rhs) const { return ! ((*this) == rhs); } + bool operator==(const Halfedge_index& rhs) const { return m_face == rhs.m_face && m_side == rhs.m_side; } + +private: + Halfedge_index(int face_idx, unsigned char side_idx) : m_face(Face_index(face_idx)), m_side(side_idx) {} + + Face_index m_face; + unsigned char m_side; +}; + + + +class Vertex_index { + friend class SurfaceMesh; + +public: + Vertex_index() : m_face(Face_index(-1)), m_vertex_idx(0) {} + bool is_invalid() const { return int(m_face) < 0; } + bool operator==(const Vertex_index& rhs) const = delete; // Use SurfaceMesh::is_same_vertex. + +private: + Vertex_index(int face_idx, unsigned char vertex_idx) : m_face(Face_index(face_idx)), m_vertex_idx(vertex_idx) {} + + Face_index m_face; + unsigned char m_vertex_idx; +}; + + + +class SurfaceMesh { +public: + explicit SurfaceMesh(const indexed_triangle_set& its) + : m_its(its), + m_face_neighbors(its_face_neighbors_par(its)) + {} + SurfaceMesh(const SurfaceMesh&) = delete; + SurfaceMesh& operator=(const SurfaceMesh&) = delete; + + Vertex_index source(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side); } + Vertex_index target(Halfedge_index h) const { assert(! h.is_invalid()); return Vertex_index(h.m_face, h.m_side == 2 ? 0 : h.m_side + 1); } + Face_index face(Halfedge_index h) const { assert(! h.is_invalid()); return h.m_face; } + + Halfedge_index next(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side + 1) % 3; return h; } + Halfedge_index prev(Halfedge_index h) const { assert(! h.is_invalid()); h.m_side = (h.m_side == 0 ? 2 : h.m_side - 1); return h; } + Halfedge_index halfedge(Vertex_index v) const { return Halfedge_index(v.m_face, (v.m_vertex_idx == 0 ? 2 : v.m_vertex_idx - 1)); } + Halfedge_index halfedge(Face_index f) const { return Halfedge_index(f, 0); } + Halfedge_index opposite(Halfedge_index h) const { + if (h.is_invalid()) + return h; + + int face_idx = m_face_neighbors[h.m_face][h.m_side]; + Halfedge_index h_candidate = halfedge(Face_index(face_idx)); + + if (h_candidate.is_invalid()) + return Halfedge_index(); // invalid + + for (int i=0; i<3; ++i) { + if (is_same_vertex(source(h_candidate), target(h))) { + // Meshes in PrusaSlicer should be fixed enough for the following not to happen. + assert(is_same_vertex(target(h_candidate), source(h))); + return h_candidate; + } + h_candidate = next(h_candidate); + } + return Halfedge_index(); // invalid + } + + Halfedge_index next_around_target(Halfedge_index h) const { return opposite(next(h)); } + Halfedge_index prev_around_target(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : prev(op)); } + Halfedge_index next_around_source(Halfedge_index h) const { Halfedge_index op = opposite(h); return (op.is_invalid() ? Halfedge_index() : next(op)); } + Halfedge_index prev_around_source(Halfedge_index h) const { return opposite(prev(h)); } + Halfedge_index halfedge(Vertex_index source, Vertex_index target) const + { + Halfedge_index hi(source.m_face, source.m_vertex_idx); + assert(! hi.is_invalid()); + + const Vertex_index orig_target = this->target(hi); + Vertex_index current_target = orig_target; + + while (! is_same_vertex(current_target, target)) { + hi = next_around_source(hi); + if (hi.is_invalid()) + break; + current_target = this->target(hi); + if (is_same_vertex(current_target, orig_target)) + return Halfedge_index(); // invalid + } + + return hi; + } + + const stl_vertex& point(Vertex_index v) const { return m_its.vertices[m_its.indices[v.m_face][v.m_vertex_idx]]; } + + size_t degree(Vertex_index v) const + { + // In case the mesh is broken badly, the loop might end up to be infinite, + // never getting back to the first halfedge. Remember list of all half-edges + // and trip if any is encountered for the second time. + Halfedge_index h_first = halfedge(v); + boost::container::small_vector he_visited; + Halfedge_index h = next_around_target(h_first); + size_t degree = 2; + while (! h.is_invalid() && h != h_first) { + he_visited.emplace_back(h); + h = next_around_target(h); + if (std::find(he_visited.begin(), he_visited.end(), h) == he_visited.end()) + return 0; + ++degree; + } + return h.is_invalid() ? 0 : degree - 1; + } + + size_t degree(Face_index f) const { + size_t total = 0; + for (unsigned char i=0; i<3; ++i) { + size_t d = degree(Vertex_index(f, i)); + if (d == 0) + return 0; + total += d; + } + assert(total - 6 >= 0); + return total - 6; // we counted 3 halfedges from f, and one more for each neighbor + } + + bool is_border(Halfedge_index h) const { return m_face_neighbors[h.m_face][h.m_side] == -1; } + + bool is_same_vertex(const Vertex_index& a, const Vertex_index& b) const { return m_its.indices[a.m_face][a.m_vertex_idx] == m_its.indices[b.m_face][b.m_vertex_idx]; } + Vec3i get_face_neighbors(Face_index face_id) const { assert(int(face_id) < int(m_face_neighbors.size())); return m_face_neighbors[face_id]; } + + + +private: + const std::vector m_face_neighbors; + const indexed_triangle_set& m_its; +}; + +} //namespace Slic3r + +#endif // slic3r_SurfaceMesh_hpp_ diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index aa014a6dc9c..28c1ac27f7e 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -62,5 +62,8 @@ // Enable fit print volume command for circular printbeds #define ENABLE_ENHANCED_PRINT_VOLUME_FIT (1 && ENABLE_2_4_0_BETA2) +// Enable picking using raytracing +#define ENABLE_RAYCAST_PICKING (1 && ENABLE_LEGACY_OPENGL_REMOVAL) +#define ENABLE_RAYCAST_PICKING_DEBUG (0 && ENABLE_RAYCAST_PICKING) #endif // _prusaslicer_technologies_h_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 61dc8c9491f..f1e658b23d2 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -99,6 +99,8 @@ set(SLIC3R_GUI_SOURCES GUI/GLShader.hpp GUI/GLCanvas3D.hpp GUI/GLCanvas3D.cpp + GUI/SceneRaycaster.hpp + GUI/SceneRaycaster.cpp GUI/OpenGLManager.hpp GUI/OpenGLManager.cpp GUI/Selection.hpp @@ -139,6 +141,8 @@ set(SLIC3R_GUI_SOURCES GUI/Gizmos/GLGizmoText.hpp GUI/Gizmos/GLGizmoMeshBoolean.cpp GUI/Gizmos/GLGizmoMeshBoolean.hpp + GUI/Gizmos/GLGizmoMeasure.cpp + GUI/Gizmos/GLGizmoMeasure.hpp GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.hpp GUI/Gizmos/GizmoObjectManipulation.cpp @@ -205,6 +209,8 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_AuxiliaryList.hpp GUI/GUI_ObjectSettings.cpp GUI/GUI_ObjectSettings.hpp + GUI/GUI_Geometry.cpp + GUI/GUI_Geometry.hpp GUI/GUI_ObjectTable.cpp GUI/GUI_ObjectTable.hpp GUI/GUI_ObjectTableSettings.cpp @@ -271,6 +277,8 @@ set(SLIC3R_GUI_SOURCES GUI/2DBed.hpp GUI/3DBed.cpp GUI/3DBed.hpp + GUI/CoordAxes.cpp + GUI/CoordAxes.hpp GUI/Camera.cpp GUI/Camera.hpp GUI/CameraUtils.cpp diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index a7b6e050ebc..1b06b31ea77 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -12,6 +12,7 @@ #include "GUI_App.hpp" #include "GUI_Colors.hpp" #include "GLCanvas3D.hpp" +#include "Plater.hpp" #include @@ -132,67 +133,6 @@ const float* GeometryBuffer::get_vertices_data() const return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; } -const float Bed3D::Axes::DefaultStemRadius = 0.5f; -const float Bed3D::Axes::DefaultStemLength = 25.0f; -const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius; -const float Bed3D::Axes::DefaultTipLength = 5.0f; - -std::array Bed3D::AXIS_X_COLOR = decode_color_to_float_array("#FF0000"); -std::array Bed3D::AXIS_Y_COLOR = decode_color_to_float_array("#00FF00"); -std::array Bed3D::AXIS_Z_COLOR = decode_color_to_float_array("#0000FF"); - -void Bed3D::update_render_colors() -{ - Bed3D::AXIS_X_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_X]); - Bed3D::AXIS_Y_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Y]); - Bed3D::AXIS_Z_COLOR = GLColor(RenderColor::colors[RenderCol_Axis_Z]); -} - -void Bed3D::load_render_colors() -{ - RenderColor::colors[RenderCol_Axis_X] = IMColor(Bed3D::AXIS_X_COLOR); - RenderColor::colors[RenderCol_Axis_Y] = IMColor(Bed3D::AXIS_Y_COLOR); - RenderColor::colors[RenderCol_Axis_Z] = IMColor(Bed3D::AXIS_Z_COLOR); -} - -void Bed3D::Axes::render() const -{ - auto render_axis = [this](const Transform3f& transform) { - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(transform.data())); - m_arrow.render(); - glsafe(::glPopMatrix()); - }; - - if (!m_arrow.is_initialized()) - const_cast(&m_arrow)->init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length)); - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - glsafe(::glEnable(GL_DEPTH_TEST)); - - shader->start_using(); - shader->set_uniform("emission_factor", 0.0f); - - // x axis - const_cast(&m_arrow)->set_color(-1, AXIS_X_COLOR); - render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }).cast()); - - // y axis - const_cast(&m_arrow)->set_color(-1, AXIS_Y_COLOR); - render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }).cast()); - - // z axis - const_cast(&m_arrow)->set_color(-1, AXIS_Z_COLOR); - render_axis(Geometry::assemble_transform(m_origin).cast()); - - shader->stop_using(); - - glsafe(::glDisable(GL_DEPTH_TEST)); -} - //BBS: add part plate logic bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_height, const std::string& custom_model, bool force_as_custom, const Vec2d position, bool with_reset) @@ -290,6 +230,9 @@ bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_heig m_axes.set_origin({ 0.0, 0.0, static_cast(GROUND_Z) }); m_axes.set_stem_length(0.1f * static_cast(m_build_volume.bounding_volume().max_size())); + // unregister from picking + wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed); + // Let the calee to update the UI. return true; } @@ -325,38 +268,66 @@ void Bed3D::on_change_color_mode(bool is_dark) m_is_dark = is_dark; } -void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes) +void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes) +{ + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_axes, false); +} + +void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor) { - render_internal(canvas, bottom, scale_factor, show_axes); + render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true); } + /*void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor) { render_internal(canvas, bottom, scale_factor, false, false, true); }*/ -void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, - bool show_axes) -{ - float* factor = const_cast(&m_scale_factor); - *factor = scale_factor; +// void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, +// bool show_axes) +// { +// float* factor = const_cast(&m_scale_factor); +// *factor = scale_factor; + +// if (show_axes) +// render_axes(); +// glsafe(::glEnable(GL_DEPTH_TEST)); + +// m_model.model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); +// switch (m_type) +// { +// case Type::System: { render_system(canvas, bottom); break; } +// default: +// case Type::Custom: { render_custom(canvas, bottom); break; } +// } + +// glsafe(::glDisable(GL_DEPTH_TEST)); +// } + +void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, + bool show_axes, bool picking) +{ + m_scale_factor = scale_factor; + if (show_axes) render_axes(); glsafe(::glEnable(GL_DEPTH_TEST)); - m_model.set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); + m_model.model.set_color(picking ? PICKING_MODEL_COLOR : m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); switch (m_type) { - case Type::System: { render_system(canvas, bottom); break; } + case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom); break; } default: - case Type::Custom: { render_custom(canvas, bottom); break; } + case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom, picking); break; } } glsafe(::glDisable(GL_DEPTH_TEST)); } + //BBS: add partplate related logic // Calculate an extended bounding box from axes and current model for visualization purposes. BoundingBoxf3 Bed3D::calc_extended_bounding_box(bool consider_model_offset) const @@ -375,11 +346,11 @@ BoundingBoxf3 Bed3D::calc_extended_bounding_box(bool consider_model_offset) cons Vec3d offset{ m_position.x(), m_position.y(), 0.f }; //out.merge(m_axes.get_origin() + offset + m_axes.get_total_length() * Vec3d::Ones()); out.merge(Vec3d(0.f, 0.f, GROUND_Z) + offset + m_axes.get_total_length() * Vec3d::Ones()); - out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z())); + out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z())); //BBS: add part plate related logic. if (consider_model_offset) { // extend to contain model, if any - BoundingBoxf3 model_bb = m_model.get_bounding_box(); + BoundingBoxf3 model_bb = m_model.model.get_bounding_box(); if (model_bb.defined) { model_bb.translate(m_model_offset); out.merge(model_bb); @@ -454,21 +425,32 @@ std::tuple Bed3D::detect_type(const Point return { Type::Custom, {}, {} }; } -void Bed3D::render_axes() const +void Bed3D::render_axes() { if (m_build_volume.valid()) - m_axes.render(); + m_axes.render(Transform3d::Identity(), 0.25f); } -void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) const +void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) { if (!bottom) - render_model(); + render_model(view_matrix, projection_matrix); - /*if (show_texture) - render_texture(bottom, canvas);*/ + // if (show_texture) + // render_texture(bottom, canvas, view_matrix, projection_matrix); + // else if (bottom) + // render_contour(view_matrix, projection_matrix); } +// void Bed3D::render_system(GLCanvas3D& canvas, bool bottom) +// { +// if (!bottom) +// render_model(); + +// /*if (show_texture) +// render_texture(bottom, canvas);*/ +// } + /*void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const { GLTexture* texture = const_cast(&m_texture); @@ -624,7 +606,7 @@ void Bed3D::update_model_offset() const const_cast(m_extended_bounding_box) = calc_extended_bounding_box(); } -GeometryBuffer Bed3D::update_bed_triangles() const +GeometryBuffer Bed3D::update_bed_triangles() { GeometryBuffer new_triangles; Vec3d shift = m_extended_bounding_box.center(); @@ -649,22 +631,37 @@ GeometryBuffer Bed3D::update_bed_triangles() const if (!new_triangles.set_from_triangles(triangulate_expolygon_2f(poly, NORMALS_UP), GROUND_Z)) { ; } + if (m_model.model.get_filename().empty() && m_model.mesh_raycaster == nullptr) + // register for picking + register_raycasters_for_picking(m_model.model.get_geometry(), Transform3d::Identity()); + // update extended bounding box const_cast(m_extended_bounding_box) = calc_extended_bounding_box(); return new_triangles; } -void Bed3D::render_model() const +void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix) { if (m_model_filename.empty()) return; - GLModel* model = const_cast(&m_model); + GLModel* model = const_cast(&m_model.model); if (model->get_filename() != m_model_filename && model->init_from_file(m_model_filename)) { - model->set_color(-1, m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); + model->set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR); update_model_offset(); + // register for picking + const std::vector>* const raycaster = wxGetApp().plater()->canvas3D()->get_raycasters_for_picking(SceneRaycaster::EType::Bed); + if (!raycaster->empty()) { + // The raycaster may have been set by the call to init_triangles() made from render_texture() if the printbed was + // changed while the camera was pointing upward. + // In this case we need to remove it before creating a new using the model geometry + wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed); + m_model.mesh_raycaster.reset(); + } + register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::translation_transform(m_model_offset)); + } if (!model->get_filename().empty()) { @@ -672,38 +669,37 @@ void Bed3D::render_model() const if (shader != nullptr) { shader->start_using(); shader->set_uniform("emission_factor", 0.0f); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_model_offset.x(), m_model_offset.y(), m_model_offset.z())); - model->render(); - glsafe(::glPopMatrix()); + const Transform3d model_matrix = Geometry::translation_transform(m_model_offset); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + m_model.model.render(); shader->stop_using(); } } } -void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const +void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool picking) { if (m_model_filename.empty()) { - render_default(bottom); + render_default(bottom, picking, view_matrix, projection_matrix); return; } if (!bottom) - render_model(); + render_model(view_matrix, projection_matrix); - /*if (show_texture) - render_texture(bottom, canvas);*/ } -void Bed3D::render_default(bool bottom) const +void Bed3D::render_default(bool bottom, bool picking, const Transform3d& view_matrix, const Transform3d& projection_matrix) { - bool picking = false; const_cast(&m_texture)->reset(); unsigned int triangles_vcount = m_triangles.get_vertices_count(); GeometryBuffer default_triangles = update_bed_triangles(); if (triangles_vcount > 0) { - bool has_model = !m_model.get_filename().empty(); + bool has_model = !m_model.model.get_filename().empty(); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); @@ -746,5 +742,23 @@ void Bed3D::release_VBOs() } } +void Bed3D::register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo) +{ + assert(m_model.mesh_raycaster == nullptr); + + indexed_triangle_set its; + its.vertices.reserve(geometry.vertices_count()); + for (size_t i = 0; i < geometry.vertices_count(); ++i) { + its.vertices.emplace_back(geometry.extract_position_3(i)); + } + its.indices.reserve(geometry.indices_count() / 3); + for (size_t i = 0; i < geometry.indices_count() / 3; ++i) { + const size_t tri_id = i * 3; + its.indices.emplace_back(geometry.extract_index(tri_id), geometry.extract_index(tri_id + 1), geometry.extract_index(tri_id + 2)); + } + + m_model.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + wxGetApp().plater()->canvas3D()->add_raycaster_for_picking(SceneRaycaster::EType::Bed, 0, *m_model.mesh_raycaster, trafo); +} } // GUI } // Slic3r diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index f6daadddf13..7a466b806a9 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,7 +3,8 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#include "GLModel.hpp" +#include "CoordAxes.hpp" +#include "MeshUtils.hpp" #include @@ -41,38 +42,6 @@ class GeometryBuffer class Bed3D { -public: - static std::array AXIS_X_COLOR; - static std::array AXIS_Y_COLOR; - static std::array AXIS_Z_COLOR; - - static void update_render_colors(); - static void load_render_colors(); - - class Axes - { - public: - static const float DefaultStemRadius; - static const float DefaultStemLength; - static const float DefaultTipRadius; - static const float DefaultTipLength; - - private: - Vec3d m_origin{ Vec3d::Zero() }; - float m_stem_length{ DefaultStemLength }; - GLModel m_arrow; - - public: - const Vec3d& get_origin() const { return m_origin; } - void set_origin(const Vec3d& origin) { m_origin = origin; } - void set_stem_length(float length) { - m_stem_length = length; - m_arrow.reset(); - } - float get_total_length() const { return m_stem_length + DefaultTipLength; } - void render() const; - }; - public: enum class Type : unsigned char { @@ -96,10 +65,10 @@ class Bed3D GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed //GLTexture m_temp_texture; - GLModel m_model; + PickingModel m_model; Vec3d m_model_offset{ Vec3d::Zero() }; unsigned int m_vbo_id{ 0 }; - Axes m_axes; + CoordAxes m_axes; float m_scale_factor{ 1.0f }; //BBS: add part plate related logic @@ -142,8 +111,10 @@ class Bed3D bool contains(const Point& point) const; Point point_projection(const Point& point) const; - void render(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes); - //void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor); + void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes); + void render_axes(); + void render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor); + void on_change_color_mode(bool is_dark); @@ -155,17 +126,24 @@ class Bed3D void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); void update_model_offset() const; //BBS: with offset - GeometryBuffer update_bed_triangles() const; + GeometryBuffer update_bed_triangles(); static std::tuple detect_type(const Pointfs& shape); - void render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, - bool show_axes); - void render_axes() const; - void render_system(GLCanvas3D& canvas, bool bottom) const; + void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); + + void render_internal(GLCanvas3D &canvas, + const Transform3d &view_matrix, + const Transform3d &projection_matrix, + bool bottom, + float scale_factor, + bool show_axes, + bool picking); //void render_texture(bool bottom, GLCanvas3D& canvas) const; - void render_model() const; - void render_custom(GLCanvas3D& canvas, bool bottom) const; - void render_default(bool bottom) const; + void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix); + // void render_model(); + void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool picking); + void render_default(bool bottom, bool picking, const Transform3d& view_matrix, const Transform3d& projection_matrix); void release_VBOs(); + void register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo); }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index be821f64fed..af366ffb046 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,6 +1,6 @@ #include "slic3r/GUI/3DScene.hpp" #include - +#include "MeshUtils.hpp" #if ENABLE_SMOOTH_NORMALS #include #include @@ -353,28 +353,30 @@ void GLVolume::SinkingContours::update() const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh(); m_model.reset(); - GUI::GLModel::InitializationData init_data; + GUI::GLModel::Geometry init_data; + init_data.format = {GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3}; + init_data.color = ColorRGBA::WHITE(); + unsigned int vertices_counter = 0; MeshSlicingParams slicing_params; - slicing_params.trafo = m_parent.world_matrix(); - Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); - for (ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::Triangles; + slicing_params.trafo = m_parent.world_matrix(); + const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params)); + if (polygons.empty()) + return; + + for (const ExPolygon &expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) { const std::vector triangulation = triangulate_expolygon_3d(expoly); - for (const Vec3d& v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.015f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } + init_data.reserve_vertices(init_data.vertices_count() + triangulation.size()); + init_data.reserve_indices(init_data.indices_count() + triangulation.size()); + for (const Vec3d &v : triangulation) { + init_data.add_vertex((Vec3f) (v.cast() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } - init_data.entities.emplace_back(entity); } - m_model.init_from(init_data); + if (init_data.vertices_count() > 0) + m_model.init_from(std::move(init_data)); } else m_shift = box.center() - m_old_box.center(); @@ -453,12 +455,19 @@ GLVolume::GLVolume(float r, float g, float b, float a) , force_sinking_contours(false) , tverts_range(0, size_t(-1)) , qverts_range(0, size_t(-1)) + , mesh_raycaster(nullptr) { color = { r, g, b, a }; set_render_color(color); mmuseg_ts = 0; } +GLVolume::~GLVolume(){ + if(mesh_raycaster){ + delete mesh_raycaster; + mesh_raycaster = nullptr; + } +} void GLVolume::set_color(const std::array& rgba) { color = rgba; @@ -1105,6 +1114,9 @@ int GLVolumeCollection::load_object_volume( else v.model_object_ID = instance->id().id; + if (m_use_raycasters) + v.mesh_raycaster = new GUI::MeshRaycaster(std::make_shared(mesh)); + return int(this->volumes.size() - 1); } @@ -1140,6 +1152,7 @@ void GLVolumeCollection::load_object_auxiliary( v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); + v.mesh_raycaster = new GUI::MeshRaycaster(std::make_shared(mesh)); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. if (&instance_idx == &instances.back()) v.set_convex_hull(std::move(convex_hull)); @@ -1198,6 +1211,7 @@ int GLVolumeCollection::load_wipe_tower_preview( v.indexed_vertex_array.load_mesh(wipe_tower_shell); v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_convex_hull(wipe_tower_shell); + v.mesh_raycaster = new GUI::MeshRaycaster(std::make_shared(wipe_tower_shell)); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); @@ -1270,9 +1284,12 @@ int GLVolumeCollection::get_selection_support_threshold_angle(bool &enable_suppo return support_threshold_angle ; } -//BBS: add outline drawing logic -void GLVolumeCollection::render( - GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d &view_matrix, std::function filter_func, bool with_outline) const +void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, + bool disable_cullface, + const Transform3d &view_matrix, + const Transform3d &projection_matrix, + std::function filter_func, + bool with_outline) const { GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func); if (to_render.empty()) @@ -1292,6 +1309,8 @@ void GLVolumeCollection::render( glsafe(::glDisable(GL_CULL_FACE)); for (GLVolumeWithIdAndZ& volume : to_render) { + const Transform3d& world_matrix = volume.first->world_matrix(); + #if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT if (type == ERenderType::Transparent) { volume.first->force_transparent = true; @@ -1346,7 +1365,11 @@ void GLVolumeCollection::render( float normal_z = -::cos(Geometry::deg2rad((float) support_threshold_angle)); - shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); + const Transform3d model_matrix = world_matrix; + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", projection_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); shader->set_uniform("slope.normal_z", normal_z); @@ -2322,5 +2345,4 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height { thick_point_to_verts(point, width, height, volume); } - } // namespace Slic3r diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index c850884da84..7499a4e87d6 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -35,7 +35,10 @@ extern float FullyTransparentMaterialThreshold; extern float FullTransparentModdifiedToFixAlpha; extern std::array adjust_color_for_rendering(const std::array &colors); - +namespace Slic3r { +namespace GUI { + class MeshRaycaster; +}} namespace Slic3r { class SLAPrintObject; enum SLAPrintObjectStep : unsigned int; @@ -289,7 +292,7 @@ class GLVolume { GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const std::array& rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} - virtual ~GLVolume() = default; + virtual ~GLVolume(); // BBS protected: @@ -398,6 +401,8 @@ class GLVolume { // Is mouse or rectangle selection over this object to select/deselect it ? EHoverState hover; + // raycaster used for picking + GUI::MeshRaycaster* mesh_raycaster; // Interleaved triangles & normals with indexed triangles & quads. GLIndexedVertexArray indexed_vertex_array; @@ -614,6 +619,7 @@ class GLVolumeCollection Slope m_slope; bool m_show_sinking_contours = false; + bool m_use_raycasters{ true }; public: GLVolumePtrs volumes; @@ -662,10 +668,11 @@ class GLVolumeCollection int get_selection_support_threshold_angle(bool&) const; // Render the volumes by OpenGL. - //BBS: add outline drawing logic + //Orca: support new shader and add outline drawing logic void render(ERenderType type, bool disable_cullface, const Transform3d & view_matrix, + const Transform3d &projection_matrix, std::function filter_func = std::function(), bool with_outline = true) const; @@ -681,6 +688,7 @@ class GLVolumeCollection bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } + void set_use_raycasters(bool value) { m_use_raycasters = value; } void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } diff --git a/src/slic3r/GUI/CoordAxes.cpp b/src/slic3r/GUI/CoordAxes.cpp new file mode 100644 index 00000000000..734f8ddfaf4 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.cpp @@ -0,0 +1,69 @@ +#include "libslic3r/libslic3r.h" + +#include "CoordAxes.hpp" +#include "GUI_App.hpp" +#include "3DScene.hpp" +#include "Plater.hpp" +#include "Camera.hpp" + +#include + +namespace Slic3r { +namespace GUI { + +const float CoordAxes::DefaultStemRadius = 0.5f; +const float CoordAxes::DefaultStemLength = 25.0f; +const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius; +const float CoordAxes::DefaultTipLength = 5.0f; + +void CoordAxes::render(const Transform3d& trafo, float emission_factor) +{ + auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d matrix = view_matrix * transform; + shader.set_uniform("view_model_matrix", matrix); + shader.set_uniform("projection_matrix", camera.get_projection_matrix()); + shader.set_uniform("view_normal_matrix", (Matrix3d)(view_matrix.matrix().block(0, 0, 3, 3) * transform.matrix().block(0, 0, 3, 3).inverse().transpose())); + m_arrow.render(); + }; + + if (!m_arrow.is_initialized()) + m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length)); + + GLShaderProgram* curr_shader = wxGetApp().get_current_shader(); + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + if (curr_shader != nullptr) + curr_shader->stop_using(); + + shader->start_using(); + shader->set_uniform("emission_factor", emission_factor); + + // Scale the axes if the camera is close to them to avoid issues + // such as https://github.com/prusa3d/PrusaSlicer/issues/9483 + const Camera& camera = wxGetApp().plater()->get_camera(); + Transform3d scale_tr = Transform3d::Identity(); + scale_tr.scale(std::min(1., camera.get_inv_zoom() * 10.)); + + // x axis + m_arrow.set_color(ColorRGBA::X()); + render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * Geometry::rotation_transform({ 0.0, 0.5 * M_PI, 0.0 }) * scale_tr); + + // y axis + m_arrow.set_color(ColorRGBA::Y()); + render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * Geometry::rotation_transform({ -0.5 * M_PI, 0.0, 0.0 }) * scale_tr); + + // z axis + m_arrow.set_color(ColorRGBA::Z()); + render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * scale_tr); + + shader->stop_using(); + if (curr_shader != nullptr) + curr_shader->start_using(); +} + +} // GUI +} // Slic3r diff --git a/src/slic3r/GUI/CoordAxes.hpp b/src/slic3r/GUI/CoordAxes.hpp new file mode 100644 index 00000000000..1d934751f23 --- /dev/null +++ b/src/slic3r/GUI/CoordAxes.hpp @@ -0,0 +1,57 @@ +#ifndef slic3r_CoordAxes_hpp_ +#define slic3r_CoordAxes_hpp_ + +#include "GLModel.hpp" + +namespace Slic3r { +namespace GUI { + +class CoordAxes +{ +public: + static const float DefaultStemRadius; + static const float DefaultStemLength; + static const float DefaultTipRadius; + static const float DefaultTipLength; + +private: + Vec3d m_origin{ Vec3d::Zero() }; + float m_stem_radius{ DefaultStemRadius }; + float m_stem_length{ DefaultStemLength }; + float m_tip_radius{ DefaultTipRadius }; + float m_tip_length{ DefaultTipLength }; + GLModel m_arrow; + +public: + const Vec3d& get_origin() const { return m_origin; } + void set_origin(const Vec3d& origin) { m_origin = origin; } + void set_stem_radius(float radius) { + m_stem_radius = radius; + m_arrow.reset(); + } + void set_stem_length(float length) { + m_stem_length = length; + m_arrow.reset(); + } + void set_tip_radius(float radius) { + m_tip_radius = radius; + m_arrow.reset(); + } + void set_tip_length(float length) { + m_tip_length = length; + m_arrow.reset(); + } + + float get_stem_radius() const { return m_stem_radius; } + float get_stem_length() const { return m_stem_length; } + float get_tip_radius() const { return m_tip_radius; } + float get_tip_length() const { return m_tip_length; } + float get_total_length() const { return m_stem_length + m_tip_length; } + + void render(const Transform3d& trafo, float emission_factor = 0.0f); +}; + +} // GUI +} // Slic3r + +#endif // slic3r_CoordAxes_hpp_ diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 3d0e4f2b9cd..eacd93471b2 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/Color.hpp" #include "libslic3r/libslic3r.h" #include "GCodeViewer.hpp" @@ -331,7 +332,7 @@ void GCodeViewer::SequentialView::Marker::init(std::string filename) } else { m_model.init_from_file(filename); } - m_model.set_color(-1, {1.0f, 1.0f, 1.0f, 0.5f}); + m_model.set_color(ColorRGBA(1.0f, 1.0f, 1.0f, 0.5f)); } void GCodeViewer::SequentialView::Marker::set_world_position(const Vec3f& position) @@ -345,7 +346,7 @@ void GCodeViewer::SequentialView::Marker::update_curr_move(const GCodeProcessorR } //BBS: GUI refactor: add canvas size from parameters -void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) const +void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_height, const EViewType& view_type) { if (!m_visible) return; @@ -359,14 +360,16 @@ void GCodeViewer::SequentialView::Marker::render(int canvas_width, int canvas_he shader->start_using(); shader->set_uniform("emission_factor", 0.0f); - - glsafe(::glPushMatrix()); - glsafe(::glMultMatrixf(m_world_transform.data())); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Transform3d model_matrix = m_world_transform.cast(); + shader->set_uniform("view_model_matrix", view_matrix * model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); m_model.render(); - glsafe(::glPopMatrix()); - shader->stop_using(); glsafe(::glDisable(GL_BLEND)); @@ -713,7 +716,7 @@ void GCodeViewer::SequentialView::GCodeWindow::stop_mapping_file() BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": finished mapping file " << m_filename; } } -void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const +void GCodeViewer::SequentialView::render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) { if (has_render_path) marker.render(canvas_width, canvas_height, view_type); @@ -790,6 +793,7 @@ GCodeViewer::GCodeViewer() m_moves_slider = new IMSlider(0, 0, 0, 100, wxSL_HORIZONTAL); m_layers_slider = new IMSlider(0, 0, 0, 100, wxSL_VERTICAL); m_extrusions.reset_role_visibility_flags(); + m_shells.volumes.set_use_raycasters(false); // m_sequential_view.skip_invisible_moves = true; } @@ -1387,7 +1391,7 @@ void GCodeViewer::_render_calibration_thumbnail_internal(ThumbnailData& thumbnai } if (range.vbo > 0) { - buffer.model.model.set_color(-1, range.color); + buffer.model.model.set_color(range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; @@ -2332,28 +2336,28 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const }; // format data into the buffers to be rendered as batched model - auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::InitializationData& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { + auto add_vertices_as_model_batch = [](const GCodeProcessorResult::MoveVertex& curr, const GLModel::Geometry& data, VertexBuffer& vertices, InstanceBuffer& instances, InstanceIdBuffer& instances_ids, size_t move_id) { const double width = static_cast(1.5f * curr.width); const double height = static_cast(1.5f * curr.height); - const Transform3d trafo = Geometry::assemble_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast(), Vec3d::Zero(), { width, width, height }); + const Transform3d trafo = Geometry::translation_transform((curr.position - 0.5f * curr.height * Vec3f::UnitZ()).cast()) * + Geometry::scale_transform({ width, width, height }); const Eigen::Matrix normal_matrix = trafo.matrix().template block<3, 3>(0, 0).inverse().transpose(); - for (const auto& entity : data.entities) { - // append vertices - for (size_t i = 0; i < entity.positions.size(); ++i) { - // append position - const Vec3d position = trafo * entity.positions[i].cast(); - vertices.push_back(static_cast(position.x())); - vertices.push_back(static_cast(position.y())); - vertices.push_back(static_cast(position.z())); - - // append normal - const Vec3d normal = normal_matrix * entity.normals[i].cast(); - vertices.push_back(static_cast(normal.x())); - vertices.push_back(static_cast(normal.y())); - vertices.push_back(static_cast(normal.z())); - } + // append vertices + const size_t vertices_count = data.vertices_count(); + for (size_t i = 0; i < vertices_count; ++i) { + // append position + const Vec3d position = trafo * data.extract_position_3(i).cast(); + vertices.push_back(float(position.x())); + vertices.push_back(float(position.y())); + vertices.push_back(float(position.z())); + + // append normal + const Vec3d normal = normal_matrix * data.extract_normal_3(i).cast(); + vertices.push_back(float(normal.x())); + vertices.push_back(float(normal.y())); + vertices.push_back(float(normal.z())); } // append instance position @@ -2364,11 +2368,10 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result, const instances_ids.push_back(move_id); }; - auto add_indices_as_model_batch = [](const GLModel::InitializationData& data, IndexBuffer& indices, IBufferType base_index) { - for (const auto& entity : data.entities) { - for (size_t i = 0; i < entity.indices.size(); ++i) { - indices.push_back(static_cast(entity.indices[i] + base_index)); - } + auto add_indices_as_model_batch = [](const GLModel::Geometry& data, IndexBuffer& indices, IBufferType base_index) { + const size_t indices_count = data.indices_count(); + for (size_t i = 0; i < indices_count; ++i) { + indices.push_back(static_cast(data.extract_index(i) + base_index)); } }; @@ -3909,7 +3912,7 @@ void GCodeViewer::render_toolpaths() } if (range.vbo > 0) { - buffer.model.model.set_color(-1, range.color); + buffer.model.model.set_color(range.color); buffer.model.model.render_instanced(range.vbo, range.count); #if ENABLE_GCODE_VIEWER_STATISTICS ++m_statistics.gl_instanced_models_calls_count; @@ -3920,9 +3923,9 @@ void GCodeViewer::render_toolpaths() }; #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_batched_model = [this](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { #else - auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader) { + auto render_as_batched_model = [](TBuffer& buffer, GLShaderProgram& shader, int position_id, int normal_id) { #endif // ENABLE_GCODE_VIEWER_STATISTICS struct Range @@ -3932,30 +3935,38 @@ void GCodeViewer::render_toolpaths() bool intersects(const Range& other) const { return (other.last < first || other.first > last) ? false : true; } }; Range buffer_range = { 0, 0 }; - size_t indices_per_instance = buffer.model.data.indices_count(); + const size_t indices_per_instance = buffer.model.data.indices_count(); for (size_t j = 0; j < buffer.indices.size(); ++j) { const IBuffer& i_buffer = buffer.indices[j]; buffer_range.last = buffer_range.first + i_buffer.count / indices_per_instance; +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(i_buffer.vao)); +#endif // ENABLE_GL_CORE_PROFILE glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer.vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); for (auto& range : buffer.model.instances.render_ranges.ranges) { - Range range_range = { range.offset, range.offset + range.count }; + const Range range_range = { range.offset, range.offset + range.count }; if (range_range.intersects(buffer_range)) { shader.set_uniform("uniform_color", range.color); - unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; - size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); - Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; - size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; + const unsigned int offset = (range_range.first > buffer_range.first) ? range_range.first - buffer_range.first : 0; + const size_t offset_bytes = static_cast(offset) * indices_per_instance * sizeof(IBufferType); + const Range render_range = { std::max(range_range.first, buffer_range.first), std::min(range_range.last, buffer_range.last) }; + const size_t count = static_cast(render_range.last - render_range.first) * indices_per_instance; if (count > 0) { glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)count, GL_UNSIGNED_SHORT, (const void*)offset_bytes)); #if ENABLE_GCODE_VIEWER_STATISTICS @@ -3967,11 +3978,15 @@ void GCodeViewer::render_toolpaths() glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(0)); +#endif // ENABLE_GL_CORE_PROFILE buffer_range.first = buffer_range.last; } @@ -3994,14 +4009,21 @@ void GCodeViewer::render_toolpaths() if (shader != nullptr) { shader->start_using(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); + if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::InstancedModel) { shader->set_uniform("emission_factor", 0.25f); render_as_instanced_model(buffer, *shader); shader->set_uniform("emission_factor", 0.0f); + } else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::BatchedModel) { shader->set_uniform("emission_factor", 0.25f); - render_as_batched_model(buffer, *shader); + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + render_as_batched_model(buffer, *shader, position_id, normal_id); shader->set_uniform("emission_factor", 0.0f); } else { @@ -4010,7 +4032,11 @@ void GCodeViewer::render_toolpaths() case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break; default: break; } - int uniform_color = shader->get_uniform_location("uniform_color"); + + shader->set_uniform("emission_factor", 0.15f); + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + const int uniform_color = shader->get_uniform_location("uniform_color"); auto it_path = buffer.render_paths.rbegin(); //BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":buffer indices size %1%, render_path size %2% ")%buffer.indices.size() %buffer.render_paths.size(); unsigned int indices_count = static_cast(buffer.indices.size()); @@ -4028,8 +4054,10 @@ void GCodeViewer::render_toolpaths() glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); bool has_normals = buffer.vertices.normal_size_floats() > 0; if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer.vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); + } } glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo)); @@ -4055,8 +4083,10 @@ void GCodeViewer::render_toolpaths() glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); @@ -4068,42 +4098,64 @@ void GCodeViewer::render_toolpaths() } #if ENABLE_GCODE_VIEWER_STATISTICS - auto render_sequential_range_cap = [this] + auto render_sequential_range_cap = [this, &camera] #else - auto render_sequential_range_cap = [] + auto render_sequential_range_cap = [&camera] #endif // ENABLE_GCODE_VIEWER_STATISTICS (const SequentialRangeCap& cap) { - GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); - if (shader != nullptr) { - shader->start_using(); + const TBuffer* buffer = cap.buffer; + GLShaderProgram* shader = wxGetApp().get_shader(buffer->shader.c_str()); + if (shader == nullptr) + return; - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); - glsafe(::glVertexPointer(cap.buffer->vertices.position_size_floats(), GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.position_offset_bytes())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = cap.buffer->vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, cap.buffer->vertices.vertex_size_bytes(), (const void*)cap.buffer->vertices.normal_offset_bytes())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); + shader->start_using(); + + shader->set_uniform("view_model_matrix", camera.get_view_matrix()); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_normal_matrix", (Matrix3d)Matrix3d::Identity()); + + const int position_id = shader->get_attrib_location("v_position"); + const int normal_id = shader->get_attrib_location("v_normal"); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(cap.vao)); +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, cap.vbo)); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, buffer->vertices.position_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.position_offset_bytes())); + glsafe(::glEnableVertexAttribArray(position_id)); + } + const bool has_normals = buffer->vertices.normal_size_floats() > 0; + if (has_normals) { + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, buffer->vertices.normal_size_floats(), GL_FLOAT, GL_FALSE, buffer->vertices.vertex_size_bytes(), (const void*)buffer->vertices.normal_offset_bytes())); + glsafe(::glEnableVertexAttribArray(normal_id)); } + } - shader->set_uniform("uniform_color", cap.color); + shader->set_uniform("uniform_color", cap.color); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); - glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cap.ibo)); + glsafe(::glDrawElements(GL_TRIANGLES, (GLsizei)cap.indices_count(), GL_UNSIGNED_SHORT, nullptr)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); #if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.gl_triangles_calls_count; + ++m_statistics.gl_triangles_calls_count; #endif // ENABLE_GCODE_VIEWER_STATISTICS - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(0)); +#endif // ENABLE_GL_CORE_PROFILE - shader->stop_using(); - } + shader->stop_using(); }; for (unsigned int i = 0; i < 2; ++i) { @@ -4135,8 +4187,12 @@ void GCodeViewer::render_shells() // glsafe(::glDepthMask(GL_FALSE)); shader->start_using(); - //BBS: reopen cul faces - m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix()); + //reopen cul faces + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("emission_factor", 0.1f); + m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, false, camera.get_view_matrix(), camera.get_projection_matrix()); + shader->set_uniform("emission_factor", 0.0f); + shader->stop_using(); // glsafe(::glDepthMask(GL_TRUE)); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 818f3a3e5eb..74172b43b30 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -314,7 +314,7 @@ class GCodeViewer GLModel model; Color color; InstanceVBuffer instances; - GLModel::InitializationData data; + GLModel::Geometry data; void reset(); }; @@ -645,7 +645,7 @@ Range layer_duration_log; bool is_visible() const { return m_visible; } void set_visible(bool visible) { m_visible = visible; } - void render(int canvas_width, int canvas_height, const EViewType& view_type) const; + void render(int canvas_width, int canvas_height, const EViewType& view_type); void on_change_color_mode(bool is_dark) { m_is_dark = is_dark; } void update_curr_move(const GCodeProcessorResult::MoveVertex move); @@ -712,7 +712,7 @@ Range layer_duration_log; std::vector gcode_ids; float m_scale = 1.0; bool m_show_gcode_window = false; - void render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type) const; + void render(const bool has_render_path, float legend_height, int canvas_width, int canvas_height, int right_margin, const EViewType& view_type); }; struct ETools diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 44302be4939..b8d3498b2fe 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1,3 +1,4 @@ +#include "libslic3r/Point.hpp" #include "libslic3r/libslic3r.h" #include "GLCanvas3D.hpp" @@ -884,50 +885,45 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons const size_t vertices_count = 3 * triangles_count; if (m_render_fill) { - GLModel::InitializationData fill_data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; - entity.color = {0.8f, 0.8f, 1.0f, 0.5f}; - entity.positions.reserve(vertices_count); - entity.normals.reserve(vertices_count); - entity.indices.reserve(vertices_count); - - const ExPolygons polygons_union = union_ex(polygons); + GLModel::Geometry fill_data; + fill_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3}; + fill_data.color = {0.3333f, 0.0f, 0.0f, 0.5f}; + + // vertices + indices + const ExPolygons polygons_union = union_ex(polygons); + unsigned int vertices_counter = 0; for (const ExPolygon &poly : polygons_union) { const std::vector triangulation = triangulate_expolygon_3d(poly); for (const Vec3d &v : triangulation) { - entity.positions.emplace_back(v.cast() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting - entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t positions_count = entity.positions.size(); - if (positions_count % 3 == 0) { - entity.indices.emplace_back(positions_count - 3); - entity.indices.emplace_back(positions_count - 2); - entity.indices.emplace_back(positions_count - 1); - } + fill_data.add_vertex( + (Vec3f) (v.cast() + 0.0125f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting + ++vertices_counter; + if (vertices_counter % 3 == 0) + fill_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } } - fill_data.entities.emplace_back(entity); - m_fill.init_from(fill_data); + m_fill.init_from(std::move(fill_data)); } - GLModel::InitializationData perimeter_data; + GLModel::Geometry perimeter_data; + perimeter_data.format = {GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P3N3}; for (const Polygon &poly : polygons) { - GLModel::InitializationData::Entity ent; - ent.type = GLModel::PrimitiveType::LineLoop; - ent.positions.reserve(poly.points.size()); - ent.indices.reserve(poly.points.size()); + // GLModel::Geometry::Entity ent; + // ent.type = GLModel::EPrimitiveType::LineLoop; + // ent.positions.reserve(poly.points.size()); + // ent.indices.reserve(poly.points.size()); + perimeter_data.reserve_vertices(perimeter_data.vertices_count() + poly.points.size()); + perimeter_data.reserve_indices(perimeter_data.indices_count() + poly.points.size()); unsigned int id_count = 0; for (const Point &p : poly.points) { - ent.positions.emplace_back(unscale(p.x()), unscale(p.y()), 0.025f); // add a small positive z to avoid z-fighting - ent.normals.emplace_back(Vec3f::UnitZ()); - ent.indices.emplace_back(id_count++); + perimeter_data.add_vertex(Vec3f(unscale(p.x()), unscale(p.y()), 0.025f), + Vec3f(0, 0, 1)); // add a small positive z to avoid z-fighting + perimeter_data.indices.emplace_back(id_count++); } - - perimeter_data.entities.emplace_back(ent); } - m_perimeter.init_from(perimeter_data); + m_perimeter.init_from(std::move(perimeter_data)); } //BBS: add the height limit compute logic @@ -936,32 +932,27 @@ void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons for (const auto &poly : height_polygons) { height_triangles_count += poly.first.points.size() - 2; } const size_t height_vertices_count = 3 * height_triangles_count; - GLModel::InitializationData height_fill_data; - GLModel::InitializationData::Entity height_entity; - height_entity.type = GLModel::PrimitiveType::Triangles; - height_entity.color = {0.8f, 0.8f, 1.0f, 0.5f}; - height_entity.positions.reserve(height_vertices_count); - height_entity.normals.reserve(height_vertices_count); - height_entity.indices.reserve(height_vertices_count); + GLModel::Geometry height_fill_data; + height_fill_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3}; + height_fill_data.color = {0.8f, 0.8f, 1.0f, 0.5f}; + height_fill_data.reserve_vertices(height_vertices_count); + height_fill_data.reserve_indices(height_vertices_count); for (const auto &poly : height_polygons) { ExPolygon ex_poly(poly.first); const std::vector height_triangulation = triangulate_expolygon_3d(ex_poly); for (const Vec3d &v : height_triangulation) { Vec3d point{v.x(), v.y(), poly.second}; - height_entity.positions.emplace_back(point.cast()); - height_entity.normals.emplace_back(Vec3f::UnitZ()); - const size_t height_positions_count = height_entity.positions.size(); + height_fill_data.add_vertex(point.cast(), Vec3f(0,0,1)); // add a small positive z to avoid z-fighting + const size_t height_positions_count = height_fill_data.vertices_count(); if (height_positions_count % 3 == 0) { - height_entity.indices.emplace_back(height_positions_count - 3); - height_entity.indices.emplace_back(height_positions_count - 2); - height_entity.indices.emplace_back(height_positions_count - 1); + height_fill_data.add_triangle(height_positions_count - 3, height_positions_count - 2, + height_positions_count - 1); } } } - height_fill_data.entities.emplace_back(height_entity); - m_height_limit.init_from(height_fill_data); + m_height_limit.init_from(std::move(height_fill_data)); } } @@ -981,11 +972,11 @@ void GLCanvas3D::SequentialPrintClearance::render() glsafe(::glEnable(GL_BLEND)); glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); - m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); + m_perimeter.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); m_perimeter.render(); m_fill.render(); //BBS: add height limit - m_height_limit.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR); + m_height_limit.set_color(m_render_fill ? FILL_COLOR : NO_FILL_COLOR); m_height_limit.render(); glsafe(::glDisable(GL_BLEND)); @@ -1868,14 +1859,20 @@ void GLCanvas3D::render(bool only_init) wxGetApp().imgui()->new_frame(); if (m_picking_enabled) { - if (m_rectangle_selection.is_dragging()) + if (m_rectangle_selection.is_dragging() && !m_rectangle_selection.is_empty()) // picking pass using rectangle selection _rectangular_selection_picking_pass(); - //BBS: enable picking when no volumes for partplate logic - //else if (!m_volumes.empty()) else // regular picking pass _picking_pass(); +#if ENABLE_RAYCAST_PICKING_DEBUG + else { + ImGuiWrapper& imgui = *wxGetApp().imgui(); + imgui.begin(std::string("Hit result"), ImGuiWindowFlags_AlwaysAutoResize); + imgui.text("Picking disabled"); + imgui.end(); + } +#endif // ENABLE_RAYCAST_PICKING_DEBUG } #if ENABLE_RENDER_PICKING_PASS @@ -1903,7 +1900,7 @@ void GLCanvas3D::render(bool only_init) _render_sla_slices(); _render_selection(); if (!no_partplate) - _render_bed(!camera.is_looking_downward(), show_axes); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); if (!no_partplate) //BBS: add outline logic _render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id, true); _render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running()); @@ -1913,7 +1910,7 @@ void GLCanvas3D::render(bool only_init) _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); _render_sla_slices(); _render_selection(); - _render_bed(!camera.is_looking_downward(), show_axes); + _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); _render_platelist(!camera.is_looking_downward(), only_current, true, hover_id); // BBS: GUI refactor: add canvas size as parameters _render_gcode(cnv_size.get_width(), cnv_size.get_height()); @@ -2684,6 +2681,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re else m_selection.volumes_changed(map_glvolume_old_to_new); + // @Enrico suggest this solution to preven accessing pointer on caster without data + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Volume); m_gizmos.update_data(); m_gizmos.update_assemble_view_data(); m_gizmos.refresh_on_off_state(); @@ -2737,6 +2736,22 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re manip->set_dirty(); #endif } + + // refresh volume raycasters for picking + for (size_t i = 0; i < m_volumes.volumes.size(); ++i) { + const GLVolume* v = m_volumes.volumes[i]; + assert(v->mesh_raycaster != nullptr); + std::shared_ptr raycaster = add_raycaster_for_picking(SceneRaycaster::EType::Volume, i, *v->mesh_raycaster, v->world_matrix()); + raycaster->set_active(v->is_active); + } + // refresh gizmo elements raycasters for picking + GLGizmoBase* curr_gizmo = m_gizmos.get_current(); + if (curr_gizmo != nullptr) + curr_gizmo->unregister_raycasters_for_picking(); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::Gizmo); + m_scene_raycaster.remove_raycasters(SceneRaycaster::EType::FallbackGizmo); + if (curr_gizmo != nullptr && !m_selection.is_empty()) + curr_gizmo->register_raycasters_for_picking(); // and force this canvas to be redrawn. m_dirty = true; @@ -4044,7 +4059,6 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) m_layers_editing.state = LayersEditing::Editing; _perform_layer_editing_action(&evt); } - else { // BBS: define Alt key to enable volume selection mode m_selection.set_volume_selection_mode(evt.AltDown() ? Selection::Volume : Selection::Instance); @@ -4052,6 +4066,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::FdmSupports && m_gizmos.get_current_type() != GLGizmosManager::Seam + && m_gizmos.get_current_type() != GLGizmosManager::Measure && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) { m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect); m_dirty = true; @@ -4304,7 +4319,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) // if right clicking on volume, propagate event through callback (shows context menu) int volume_idx = get_first_hover_volume_idx(); if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open + && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports && m_gizmos.get_current_type() != GLGizmosManager::Measure)) // disable context menu when the gizmo is open { // forces the selection of the volume /* m_selection.add(volume_idx); // #et_FIXME_if_needed @@ -4345,7 +4360,9 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) else { // do not post the event if the user is panning the scene // or if right click was done over the wipe tower - bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower; + bool post_right_click_event = (m_hover_volume_idxs.empty() || + !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower) && + m_gizmos.get_current_type() != GLGizmosManager::Measure; if (post_right_click_event) post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() })); } @@ -6447,7 +6464,7 @@ void GLCanvas3D::_refresh_if_shown_on_screen() void GLCanvas3D::_picking_pass() { - std::vector* hover_volume_idxs = const_cast*>(&m_hover_volume_idxs); + std::vector* hover_volume_idxs = const_cast*>(&m_hover_volume_idxs); std::vector* hover_plate_idxs = const_cast*>(&m_hover_plate_idxs); if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) { @@ -6471,26 +6488,8 @@ void GLCanvas3D::_picking_pass() if (m_canvas_type == ECanvasType::CanvasView3D) { _render_plates_for_picking(); } - m_camera_clipping_plane = m_gizmos.get_clipping_plane(); - if (m_camera_clipping_plane.is_active()) { - ::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data()); - ::glEnable(GL_CLIP_PLANE0); - } - _render_volumes_for_picking(); - if (m_camera_clipping_plane.is_active()) - ::glDisable(GL_CLIP_PLANE0); - - //BBS: remove the bed picking logic - //_render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward()); - - m_gizmos.render_current_gizmo_for_picking_pass(); - - if (m_multisample_allowed) - glsafe(::glEnable(GL_MULTISAMPLE)); - int volume_id = -1; - int gizmo_id = -1; GLubyte color[4] = { 0, 0, 0, 0 }; const Size& cnv_size = get_canvas_size(); @@ -6502,39 +6501,70 @@ void GLCanvas3D::_picking_pass() // we reserve color = (0,0,0) for occluders (as the printbed) // volumes' id are shifted by 1 // see: _render_volumes_for_picking() - //BBS: remove the bed picking logic - //volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1; + volume_id = color[0] + (color[1] << 8) + (color[2] << 16); - // gizmos' id are instead properly encoded by the color - gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16); } } - else - m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1); - - //BBS: add plate picking logic + // Picking plate int plate_hover_id = PartPlate::PLATE_BASE_ID - volume_id; if (plate_hover_id >= 0 && plate_hover_id < PartPlateList::MAX_PLATES_COUNT * PartPlate::GRABBER_COUNT) { wxGetApp().plater()->get_partplate_list().set_hover_id(plate_hover_id); hover_plate_idxs->emplace_back(plate_hover_id); - const_cast(&m_gizmos)->set_hover_id(-1); } else { wxGetApp().plater()->get_partplate_list().reset_hover_id(); - if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) { - // do not add the volume id if any gizmo is active and CTRL is pressed - if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) - hover_volume_idxs->emplace_back(volume_id); - const_cast(&m_gizmos)->set_hover_id(-1); + } + + const ClippingPlane clipping_plane = m_gizmos.get_clipping_plane().inverted_normal(); + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(m_mouse.position, wxGetApp().plater()->get_camera(), &clipping_plane); + if (hit.is_valid()) { + switch (hit.type) + { + case SceneRaycaster::EType::Volume: + { + if (0 <= hit.raycaster_id && hit.raycaster_id < (int)m_volumes.volumes.size()) { + const GLVolume* volume = m_volumes.volumes[hit.raycaster_id]; + if (volume->is_active && !volume->disabled && (volume->composite_id.volume_id >= 0 || m_render_sla_auxiliaries)) { + // do not add the volume id if any gizmo is active and CTRL is pressed + if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL)) + m_hover_volume_idxs.emplace_back(hit.raycaster_id); + m_gizmos.set_hover_id(-1); + } + } + else + assert(false); + + break; + } + case SceneRaycaster::EType::Gizmo: + case SceneRaycaster::EType::FallbackGizmo: + { + const Size& cnv_size = get_canvas_size(); + const bool inside = 0 <= m_mouse.position.x() && m_mouse.position.x() < cnv_size.get_width() && + 0 <= m_mouse.position.y() && m_mouse.position.y() < cnv_size.get_height(); + m_gizmos.set_hover_id(inside ? hit.raycaster_id : -1); + break; + } + case SceneRaycaster::EType::Bed: + { + m_gizmos.set_hover_id(-1); + break; + } + default: + { + assert(false); + break; + } } - else - const_cast(&m_gizmos)->set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1); } + else + m_gizmos.set_hover_id(-1); _update_volumes_hover_state(); } } + void GLCanvas3D::_rectangular_selection_picking_pass() { m_gizmos.set_hover_id(-1); @@ -6676,35 +6706,26 @@ void GLCanvas3D::_render_background() const glsafe(::glPopMatrix()); } -void GLCanvas3D::_render_bed(bool bottom, bool show_axes) +void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes) { float scale_factor = 1.0; #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - /* - bool show_texture = ! bottom || - (m_gizmos.get_current_type() != GLGizmosManager::FdmSupports - && m_gizmos.get_current_type() != GLGizmosManager::SlaSupports - && m_gizmos.get_current_type() != GLGizmosManager::Hollow - && m_gizmos.get_current_type() != GLGizmosManager::Seam - && m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation); - */ - //bool show_texture = true; - //BBS set axes mode m_bed.set_axes_mode(m_main_toolbar.is_enabled()); - m_bed.render(*this, bottom, scale_factor, show_axes); + m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes); } -void GLCanvas3D::_render_bed_for_picking(bool bottom) + +void GLCanvas3D::_render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom) { float scale_factor = 1.0; #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL - //m_bed.render_for_picking(*this, bottom, scale_factor); + m_bed.render_for_picking(*this, view_matrix, projection_matrix, bottom, scale_factor); } void GLCanvas3D::_render_platelist(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) const @@ -6797,12 +6818,13 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with default: case GLVolumeCollection::ERenderType::Opaque: { - const GLGizmosManager& gm = get_gizmos_manager(); + GLGizmosManager& gm = get_gizmos_manager(); if (dynamic_cast(gm.get_current()) == nullptr) { + const Camera& camera = wxGetApp().plater()->get_camera(); if (m_picking_enabled && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) { int object_id = m_layers_editing.last_object_id; - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) { + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [object_id](const GLVolume& volume) { // Which volume to paint without the layer height profile shader? return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id); }); @@ -6817,7 +6839,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with }*/ //BBS:add assemble view related logic // do not cull backfaces to show broken geometry, if any - m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) { + m_volumes.render(type, m_picking_enabled, camera.get_view_matrix(), camera.get_projection_matrix(), [this, canvas_type](const GLVolume& volume) { if (canvas_type == ECanvasType::CanvasAssembleView) { return !volume.is_modifier && !volume.is_wipe_tower; } @@ -6850,7 +6872,8 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with shader->set_uniform("show_wireframe", false); }*/ //BBS:add assemble view related logic - m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) { + const Camera& camera = wxGetApp().plater()->get_camera(); + m_volumes.render(type, false, camera.get_view_matrix(), camera.get_projection_matrix(), [this, canvas_type](const GLVolume& volume) { if (canvas_type == ECanvasType::CanvasAssembleView) { return !volume.is_modifier; } @@ -6910,7 +6933,7 @@ void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height) } } -void GLCanvas3D::_render_selection() const +void GLCanvas3D::_render_selection() { float scale_factor = 1.0; #if ENABLE_RETINA_GL @@ -6930,6 +6953,7 @@ void GLCanvas3D::_render_sequential_clearance() { case GLGizmosManager::EType::Flatten: case GLGizmosManager::EType::Cut: + case GLGizmosManager::EType::Measure: case GLGizmosManager::EType::Hollow: case GLGizmosManager::EType::SlaSupports: case GLGizmosManager::EType::FdmSupports: @@ -7201,7 +7225,6 @@ void GLCanvas3D::_render_style_editor() PartPlate::update_render_colors(); GLGizmoBase::update_render_colors(); GLCanvas3D::update_render_colors(); - Bed3D::update_render_colors(); } ImGui::SameLine(0.0f, 3.0f); ImGui::TextUnformatted(name); @@ -8194,13 +8217,17 @@ void GLCanvas3D::_render_sla_slices() } } -void GLCanvas3D::_render_selection_sidebar_hints() const +void GLCanvas3D::_render_selection_sidebar_hints() { m_selection.render_sidebar_hints(m_sidebar_field, m_gizmos.get_uniform_scaling()); } void GLCanvas3D::_update_volumes_hover_state() { + // skip update if the Gizmo Measure is active + if (m_gizmos.get_current_type() == GLGizmosManager::Measure) + return; + for (GLVolume* v : m_volumes.volumes) { v->hover = GLVolume::HS_None; } @@ -8304,21 +8331,17 @@ Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z) if (m_canvas == nullptr) return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); - const Camera& camera = wxGetApp().plater()->get_camera(); - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - GLint y = viewport[3] - (GLint)mouse_pos(1); - GLfloat mouse_z; - if (z == nullptr) - glsafe(::glReadPixels((GLint)mouse_pos(0), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z)); - else - mouse_z = *z; - - Vec3d out; - igl::unproject(Vec3d(mouse_pos(0), y, mouse_z), modelview, projection, viewport, out); - return out; + if (z == nullptr) { + const SceneRaycaster::HitResult hit = m_scene_raycaster.hit(mouse_pos.cast(), wxGetApp().plater()->get_camera(), nullptr); + return hit.is_valid() ? hit.position.cast() : _mouse_to_bed_3d(mouse_pos); + } + else { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Vec4i viewport(camera.get_viewport().data()); + Vec3d out; + igl::unproject(Vec3d(mouse_pos.x(), viewport[3] - mouse_pos.y(), *z), camera.get_view_matrix().matrix(), camera.get_projection_matrix().matrix(), viewport, out); + return out; + } } Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos) diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 303a2d04b4c..a47ab739f8e 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -19,6 +19,7 @@ #include "IMToolbar.hpp" #include "libslic3r/Slicing.hpp" +#include "slic3r/GUI/SceneRaycaster.hpp" #include @@ -506,6 +507,8 @@ class GLCanvas3D bool m_is_dark = false; wxGLCanvas* m_canvas; wxGLContext* m_context; + SceneRaycaster m_scene_raycaster; + Bed3D &m_bed; #if ENABLE_RETINA_GL std::unique_ptr m_retina_helper; @@ -724,6 +727,26 @@ class GLCanvas3D bool init(); void post_event(wxEvent &&event); + std::shared_ptr add_raycaster_for_picking(SceneRaycaster::EType type, int id, const MeshRaycaster& raycaster, + const Transform3d& trafo = Transform3d::Identity(), bool use_back_faces = false) { + return m_scene_raycaster.add_raycaster(type, id, raycaster, trafo, use_back_faces); + } + void remove_raycasters_for_picking(SceneRaycaster::EType type, int id) { + m_scene_raycaster.remove_raycasters(type, id); + } + void remove_raycasters_for_picking(SceneRaycaster::EType type) { + m_scene_raycaster.remove_raycasters(type); + } + + std::vector>* get_raycasters_for_picking(SceneRaycaster::EType type) { + return m_scene_raycaster.get_raycasters(type); + } + + void set_raycaster_gizmos_on_top(bool value) { + m_scene_raycaster.set_gizmos_on_top(value); + } + + void reset_explosion_ratio() { m_explosion_ratio = 1.0; } void on_change_color_mode(bool is_dark, bool reinit = true); const bool get_dark_mode_status() { return m_is_dark; } @@ -1114,8 +1137,8 @@ class GLCanvas3D void _picking_pass(); void _rectangular_selection_picking_pass(); void _render_background() const; - void _render_bed(bool bottom, bool show_axes); - void _render_bed_for_picking(bool bottom); + void _render_bed(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_axes); + void _render_bed_for_picking(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom); //BBS: add part plate related logic void _render_platelist(bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false) const; void _render_plates_for_picking() const; @@ -1125,7 +1148,7 @@ class GLCanvas3D void _render_gcode(int canvas_width, int canvas_height); //BBS: render a plane for assemble void _render_plane() const; - void _render_selection() const; + void _render_selection(); void _render_sequential_clearance(); #if ENABLE_RENDER_SELECTION_CENTER void _render_selection_center() const; @@ -1152,7 +1175,7 @@ class GLCanvas3D void _render_camera_target() const; #endif // ENABLE_SHOW_CAMERA_TARGET void _render_sla_slices(); - void _render_selection_sidebar_hints() const; + void _render_selection_sidebar_hints(); //BBS: GUI refactor: adjust main toolbar position bool _render_orient_menu(float left, float right, float bottom, float top); bool _render_arrange_menu(float left, float right, float bottom, float top); diff --git a/src/slic3r/GUI/GLModel.cpp b/src/slic3r/GUI/GLModel.cpp index 422b6540807..7b168c0f76a 100644 --- a/src/slic3r/GUI/GLModel.cpp +++ b/src/slic3r/GUI/GLModel.cpp @@ -4,139 +4,671 @@ #include "3DScene.hpp" #include "GUI_App.hpp" #include "GLShader.hpp" +#if ENABLE_GLMODEL_STATISTICS +#include "Plater.hpp" +#include "GLCanvas3D.hpp" +#endif // ENABLE_GLMODEL_STATISTICS #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" +#include "libslic3r/BuildVolume.hpp" +#include "libslic3r/Geometry/ConvexHull.hpp" + +#if ENABLE_GLMODEL_STATISTICS +#include +#endif // ENABLE_GLMODEL_STATISTICS #include #include +#if ENABLE_SMOOTH_NORMALS +#include +#include +#include +#endif // ENABLE_SMOOTH_NORMALS + #include namespace Slic3r { namespace GUI { -size_t GLModel::InitializationData::vertices_count() const +#if ENABLE_SMOOTH_NORMALS +static void smooth_normals_corner(const TriangleMesh& mesh, std::vector& normals) +{ + using MapMatrixXfUnaligned = Eigen::Map>; + using MapMatrixXiUnaligned = Eigen::Map>; + + std::vector face_normals = its_face_normals(mesh.its); + + Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), + Eigen::Index(mesh.its.vertices.size()), 3).cast(); + Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), + Eigen::Index(mesh.its.indices.size()), 3); + Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(), + Eigen::Index(face_normals.size()), 3).cast(); + Eigen::MatrixXd out_normals; + + igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals); + + normals = std::vector(mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast(); + } + } +} +#endif // ENABLE_SMOOTH_NORMALS + +void GLModel::Geometry::add_vertex(const Vec2f& position) +{ + assert(format.vertex_layout == EVertexLayout::P2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); +} + +void GLModel::Geometry::add_vertex(const Vec2f& position, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P2T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position) +{ + assert(format.vertex_layout == EVertexLayout::P3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P3T2); + vertices.insert(vertices.end(), position.data(), position.data() + 3); + vertices.insert(vertices.end(), tex_coord.data(), tex_coord.data() + 2); +} + +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord) +{ + assert(format.vertex_layout == EVertexLayout::P3N3T2); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(normal.x()); + vertices.emplace_back(normal.y()); + vertices.emplace_back(normal.z()); + vertices.emplace_back(tex_coord.x()); + vertices.emplace_back(tex_coord.y()); +} + +#if ENABLE_OPENGL_ES +void GLModel::Geometry::add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra) +{ + assert(format.vertex_layout == EVertexLayout::P3N3E3); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(normal.x()); + vertices.emplace_back(normal.y()); + vertices.emplace_back(normal.z()); + vertices.emplace_back(extra.x()); + vertices.emplace_back(extra.y()); + vertices.emplace_back(extra.z()); +} +#endif // ENABLE_OPENGL_ES + +void GLModel::Geometry::add_vertex(const Vec4f& position) +{ + assert(format.vertex_layout == EVertexLayout::P4); + vertices.emplace_back(position.x()); + vertices.emplace_back(position.y()); + vertices.emplace_back(position.z()); + vertices.emplace_back(position.w()); +} + +void GLModel::Geometry::add_index(unsigned int id) +{ + indices.emplace_back(id); +} + +void GLModel::Geometry::add_line(unsigned int id1, unsigned int id2) +{ + indices.emplace_back(id1); + indices.emplace_back(id2); +} + +Vec2f GLModel::Geometry::extract_position_2(size_t id) const +{ + const size_t p_stride = position_stride_floats(format); + if (p_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +Vec3f GLModel::Geometry::extract_position_3(size_t id) const { - size_t ret = 0; - for (const Entity& entity : entities) { - ret += entity.positions.size(); + const size_t p_stride = position_stride_floats(format); + if (p_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; } - return ret; + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + position_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; +} + +Vec3f GLModel::Geometry::extract_normal_3(size_t id) const +{ + const size_t n_stride = normal_stride_floats(format); + if (n_stride != 3) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX, FLT_MAX }; + } + + const float* start = &vertices[id * vertex_stride_floats(format) + normal_offset_floats(format)]; + return { *(start + 0), *(start + 1), *(start + 2) }; } -size_t GLModel::InitializationData::indices_count() const +Vec2f GLModel::Geometry::extract_tex_coord_2(size_t id) const { - size_t ret = 0; - for (const Entity& entity : entities) { - ret += entity.indices.size(); + const size_t t_stride = tex_coord_stride_floats(format); + if (t_stride != 2) { + assert(false); + return { FLT_MAX, FLT_MAX }; + } + + if (vertices_count() <= id) { + assert(false); + return { FLT_MAX, FLT_MAX }; } - return ret; + + const float* start = &vertices[id * vertex_stride_floats(format) + tex_coord_offset_floats(format)]; + return { *(start + 0), *(start + 1) }; +} + +void GLModel::Geometry::set_vertex(size_t id, const Vec3f& position, const Vec3f& normal) +{ + assert(format.vertex_layout == EVertexLayout::P3N3); + assert(id < vertices_count()); + if (id < vertices_count()) { + float* start = &vertices[id * vertex_stride_floats(format)]; + *(start + 0) = position.x(); + *(start + 1) = position.y(); + *(start + 2) = position.z(); + *(start + 3) = normal.x(); + *(start + 4) = normal.y(); + *(start + 5) = normal.z(); + } +} + +void GLModel::Geometry::set_index(size_t id, unsigned int index) +{ + assert(id < indices_count()); + if (id < indices_count()) + indices[id] = index; +} + +unsigned int GLModel::Geometry::extract_index(size_t id) const +{ + if (indices_count() <= id) { + assert(false); + return -1; + } + + return indices[id]; +} + +void GLModel::Geometry::remove_vertex(size_t id) +{ + assert(id < vertices_count()); + if (id < vertices_count()) { + const size_t stride = vertex_stride_floats(format); + std::vector::const_iterator it = vertices.begin() + id * stride; + vertices.erase(it, it + stride); + } +} + +indexed_triangle_set GLModel::Geometry::get_as_indexed_triangle_set() const +{ + indexed_triangle_set its; + its.vertices.reserve(vertices_count()); + for (size_t i = 0; i < vertices_count(); ++i) { + its.vertices.emplace_back(extract_position_3(i)); + } + its.indices.reserve(indices_count() / 3); + for (size_t i = 0; i < indices_count() / 3; ++i) { + const size_t tri_id = i * 3; + its.indices.emplace_back(extract_index(tri_id), extract_index(tri_id + 1), extract_index(tri_id + 2)); + } + return its; +} + +size_t GLModel::Geometry::vertex_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: { return 2; } + case EVertexLayout::P2T2: { return 4; } + case EVertexLayout::P3: { return 3; } + case EVertexLayout::P3T2: { return 5; } + case EVertexLayout::P3N3: { return 6; } + case EVertexLayout::P3N3T2: { return 8; } +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3E3: { return 9; } +#endif // ENABLE_OPENGL_ES + case EVertexLayout::P4: { return 4; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3T2: + case EVertexLayout::P3N3E3: { return 3; } +#else + case EVertexLayout::P3N3T2: { return 3; } +#endif // ENABLE_OPENGL_ES + case EVertexLayout::P4: { return 4; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::position_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3E3: +#endif // ENABLE_OPENGL_ES + case EVertexLayout::P4: { return 0; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3T2: + case EVertexLayout::P3N3E3: { return 3; } +#else + case EVertexLayout::P3N3T2: { return 3; } +#endif // ENABLE_OPENGL_ES + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::normal_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3T2: + case EVertexLayout::P3N3E3: { return 3; } +#else + case EVertexLayout::P3N3T2: { return 3; } +#endif // ENABLE_OPENGL_ES + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3T2: { return 2; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::tex_coord_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: { return 2; } + case EVertexLayout::P3T2: { return 3; } + case EVertexLayout::P3N3T2: { return 6; } + default: { assert(false); return 0; } + }; +} + +#if ENABLE_OPENGL_ES +size_t GLModel::Geometry::extra_stride_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3E3: { return 3; } + default: { assert(false); return 0; } + }; +} + +size_t GLModel::Geometry::extra_offset_floats(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P3N3E3: { return 6; } + default: { assert(false); return 0; } + }; +} +#endif // ENABLE_OPENGL_ES + +size_t GLModel::Geometry::index_stride_bytes(const Geometry& data) +{ + switch (data.index_type) + { + case EIndexType::UINT: { return sizeof(unsigned int); } + case EIndexType::USHORT: { return sizeof(unsigned short); } + case EIndexType::UBYTE: { return sizeof(unsigned char); } + default: { assert(false); return 0; } + }; +} + +bool GLModel::Geometry::has_position(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3E3: +#endif // ENABLE_OPENGL_ES + case EVertexLayout::P4: { return true; } + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_normal(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P4: { return false; } + case EVertexLayout::P3N3: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3T2: + case EVertexLayout::P3N3E3: { return true; } +#else + case EVertexLayout::P3N3T2: { return true; } +#endif // ENABLE_OPENGL_ES + default: { assert(false); return false; } + }; +} + +bool GLModel::Geometry::has_tex_coord(const Format& format) +{ + switch (format.vertex_layout) + { + case EVertexLayout::P2T2: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3T2: { return true; } + case EVertexLayout::P2: + case EVertexLayout::P3: + case EVertexLayout::P3N3: +#if ENABLE_OPENGL_ES + case EVertexLayout::P3N3E3: +#endif // ENABLE_OPENGL_ES + case EVertexLayout::P4: { return false; } + default: { assert(false); return false; } + }; } -void GLModel::init_from(const InitializationData& data) +#if ENABLE_OPENGL_ES +bool GLModel::Geometry::has_extra(const Format& format) { - if (!m_render_data.empty()) // call reset() if you want to reuse this model + switch (format.vertex_layout) + { + case EVertexLayout::P3N3E3: { return true; } + case EVertexLayout::P2: + case EVertexLayout::P2T2: + case EVertexLayout::P3: + case EVertexLayout::P3T2: + case EVertexLayout::P3N3: + case EVertexLayout::P3N3T2: + case EVertexLayout::P4: { return false; } + default: { assert(false); return false; } + }; +} +#endif // ENABLE_OPENGL_ES + +#if ENABLE_GLMODEL_STATISTICS +GLModel::Statistics GLModel::s_statistics; +#endif // ENABLE_GLMODEL_STATISTICS + +void GLModel::init_from(Geometry&& data) +{ + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); return; + } - for (const InitializationData::Entity& entity : data.entities) { - if (entity.positions.empty() || entity.indices.empty()) - continue; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return; + } - assert(entity.normals.empty() || entity.normals.size() == entity.positions.size()); + m_render_data.geometry = std::move(data); - RenderData rdata; - rdata.type = entity.type; - rdata.color = entity.color; + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + const size_t position_stride = Geometry::position_stride_floats(data.format); + if (position_stride == 3) + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + else if (position_stride == 2) { + const Vec2f position = m_render_data.geometry.extract_position_2(i); + m_bounding_box.merge(Vec3f(position.x(), position.y(), 0.0f).cast()); + } + } +} - // vertices/normals data - std::vector vertices(6 * entity.positions.size()); - for (size_t i = 0; i < entity.positions.size(); ++i) { - const size_t offset = i * 6; - ::memcpy(static_cast(&vertices[offset]), static_cast(entity.positions[i].data()), 3 * sizeof(float)); - if (!entity.normals.empty()) - ::memcpy(static_cast(&vertices[3 + offset]), static_cast(entity.normals[i].data()), 3 * sizeof(float)); +#if ENABLE_SMOOTH_NORMALS +void GLModel::init_from(const TriangleMesh& mesh, bool smooth_normals) +{ + if (smooth_normals) { + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; } - // indices data - std::vector indices = entity.indices; + if (mesh.its.vertices.empty() || mesh.its.indices.empty()) { + assert(false); + return; + } - rdata.indices_count = static_cast(indices.size()); + std::vector normals; + smooth_normals_corner(mesh, normals); - // update bounding box - for (size_t i = 0; i < entity.positions.size(); ++i) { - m_bounding_box.merge(entity.positions[i].cast()); + const indexed_triangle_set& its = mesh.its; + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(3 * its.indices.size()); + data.reserve_indices(3 * its.indices.size()); + + // vertices + for (size_t i = 0; i < its.vertices.size(); ++i) { + data.add_vertex(its.vertices[i], normals[i]); + } + + // indices + for (size_t i = 0; i < its.indices.size(); ++i) { + const stl_triangle_vertex_indices& idx = its.indices[i]; + data.add_triangle((unsigned int)idx(0), (unsigned int)idx(1), (unsigned int)idx(2)); } - send_to_gpu(rdata, vertices, indices); - m_render_data.emplace_back(rdata); + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(m_render_data.geometry.extract_position_3(i).cast()); + } } + else + init_from(mesh.its); } +#else +void GLModel::init_from(const TriangleMesh& mesh) +{ + init_from(mesh.its); +} +#endif // ENABLE_SMOOTH_NORMALS -void GLModel::init_from(const indexed_triangle_set& its, const BoundingBoxf3 &bbox) +void GLModel::init_from(const indexed_triangle_set& its) { - if (!m_render_data.empty()) // call reset() if you want to reuse this model + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); return; + } - RenderData data; - data.type = PrimitiveType::Triangles; + if (its.vertices.empty() || its.indices.empty()){ + assert(false); + return; + } - std::vector vertices = std::vector(18 * its.indices.size()); - std::vector indices = std::vector(3 * its.indices.size()); + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Triangles, Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(3 * its.indices.size()); + data.reserve_indices(3 * its.indices.size()); - unsigned int vertices_count = 0; + // vertices + indices + unsigned int vertices_counter = 0; for (uint32_t i = 0; i < its.indices.size(); ++i) { - stl_triangle_vertex_indices face = its.indices[i]; - stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; - stl_vertex n = face_normal_normalized(vertex); - for (size_t j = 0; j < 3; ++ j) { - size_t offset = i * 18 + j * 6; - ::memcpy(static_cast(&vertices[offset]), static_cast(vertex[j].data()), 3 * sizeof(float)); - ::memcpy(static_cast(&vertices[3 + offset]), static_cast(n.data()), 3 * sizeof(float)); + const stl_triangle_vertex_indices face = its.indices[i]; + const stl_vertex vertex[3] = { its.vertices[face[0]], its.vertices[face[1]], its.vertices[face[2]] }; + const stl_vertex n = face_normal_normalized(vertex); + for (size_t j = 0; j < 3; ++j) { + data.add_vertex(vertex[j], n); } - for (size_t j = 0; j < 3; ++j) - indices[i * 3 + j] = vertices_count + j; - vertices_count += 3; + vertices_counter += 3; + data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1); } - data.indices_count = static_cast(indices.size()); - m_bounding_box = bbox; - - send_to_gpu(data, vertices, indices); - m_render_data.emplace_back(data); + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); + } } -void GLModel::init_from(const indexed_triangle_set& its) +void GLModel::init_from(const Polygon& polygon, float z) { - this->init_from(its, bounding_box(its)); + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3 }; + + const size_t segments_count = polygon.points.size(); + data.reserve_vertices(2 * segments_count); + data.reserve_indices(2 * segments_count); + + // vertices + indices + unsigned int vertices_counter = 0; + for (size_t i = 0; i < segments_count; ++i) { + const Point& p0 = polygon.points[i]; + const Point& p1 = (i == segments_count - 1) ? polygon.points.front() : polygon.points[i + 1]; + data.add_vertex(Vec3f(unscale(p0.x()), unscale(p0.y()), z)); + data.add_vertex(Vec3f(unscale(p1.x()), unscale(p1.y()), z)); + vertices_counter += 2; + data.add_line(vertices_counter - 2, vertices_counter - 1); + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); + } } void GLModel::init_from(const Polygons& polygons, float z) { - auto append_polygon = [](const Polygon& polygon, float z, GUI::GLModel::InitializationData& data) { - if (!polygon.empty()) { - GUI::GLModel::InitializationData::Entity entity; - entity.type = GUI::GLModel::PrimitiveType::LineLoop; - // contour - entity.positions.reserve(polygon.size() + 1); - entity.indices.reserve(polygon.size() + 1); - unsigned int id = 0; - for (const Point& p : polygon) { - Vec3f position = unscale(p.x(), p.y(), 0.0).cast(); - position.z() = z; - entity.positions.emplace_back(position); - entity.indices.emplace_back(id++); - } - data.entities.emplace_back(entity); - } - }; + if (is_initialized()) { + // call reset() if you want to reuse this model + assert(false); + return; + } + + if (polygons.empty()) { + assert(false); + return; + } + + Geometry& data = m_render_data.geometry; + data.format = { Geometry::EPrimitiveType::Lines, Geometry::EVertexLayout::P3 }; - InitializationData init_data; + size_t segments_count = 0; for (const Polygon& polygon : polygons) { - append_polygon(polygon, z, init_data); + segments_count += polygon.points.size(); + } + + data.reserve_vertices(2 * segments_count); + data.reserve_indices(2 * segments_count); + + // vertices + indices + unsigned int vertices_counter = 0; + for (const Polygon& poly : polygons) { + for (size_t i = 0; i < poly.points.size(); ++i) { + const Point& p0 = poly.points[i]; + const Point& p1 = (i == poly.points.size() - 1) ? poly.points.front() : poly.points[i + 1]; + data.add_vertex(Vec3f(unscale(p0.x()), unscale(p0.y()), z)); + data.add_vertex(Vec3f(unscale(p1.x()), unscale(p1.y()), z)); + vertices_counter += 2; + data.add_line(vertices_counter - 2, vertices_counter - 1); + } + } + + // update bounding box + for (size_t i = 0; i < vertices_count(); ++i) { + m_bounding_box.merge(data.extract_position_3(i).cast()); } - init_from(init_data); } bool GLModel::init_from_file(const std::string& filename) @@ -148,206 +680,494 @@ bool GLModel::init_from_file(const std::string& filename) return false; Model model; - try - { + try { model = Model::read_from_file(filename); } - catch (std::exception&) - { + catch (std::exception&) { return false; } - TriangleMesh mesh = model.mesh(); - init_from(mesh.its, mesh.bounding_box()); + init_from(model.mesh()); m_filename = filename; return true; } -void GLModel::set_color(int entity_id, const std::array& color) -{ - for (size_t i = 0; i < m_render_data.size(); ++i) { - if (entity_id == -1 || static_cast(i) == entity_id) - m_render_data[i].color = color; - } -} - void GLModel::reset() { - for (RenderData& data : m_render_data) { - // release gpu memory - if (data.ibo_id > 0) - glsafe(::glDeleteBuffers(1, &data.ibo_id)); - if (data.vbo_id > 0) - glsafe(::glDeleteBuffers(1, &data.vbo_id)); + // release gpu memory + if (m_render_data.ibo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.ibo_id)); + m_render_data.ibo_id = 0; +#if ENABLE_GLMODEL_STATISTICS + s_statistics.gpu_memory.indices.current -= indices_size_bytes(); +#endif // ENABLE_GLMODEL_STATISTICS + } + if (m_render_data.vbo_id > 0) { + glsafe(::glDeleteBuffers(1, &m_render_data.vbo_id)); + m_render_data.vbo_id = 0; +#if ENABLE_GLMODEL_STATISTICS + s_statistics.gpu_memory.vertices.current -= vertices_size_bytes(); +#endif // ENABLE_GLMODEL_STATISTICS + } +#if ENABLE_GL_CORE_PROFILE + if (m_render_data.vao_id > 0) { + glsafe(::glDeleteVertexArrays(1, &m_render_data.vao_id)); + m_render_data.vao_id = 0; } +#endif // ENABLE_GL_CORE_PROFILE - m_render_data.clear(); + m_render_data.vertices_count = 0; + m_render_data.indices_count = 0; + m_render_data.geometry.vertices = std::vector(); + m_render_data.geometry.indices = std::vector(); m_bounding_box = BoundingBoxf3(); m_filename = std::string(); } -void GLModel::render() const +static GLenum get_primitive_mode(const GLModel::Geometry::Format& format) { - GLShaderProgram* shader = wxGetApp().get_current_shader(); + switch (format.type) + { + case GLModel::Geometry::EPrimitiveType::Points: { return GL_POINTS; } + default: + case GLModel::Geometry::EPrimitiveType::Triangles: { return GL_TRIANGLES; } + case GLModel::Geometry::EPrimitiveType::TriangleStrip: { return GL_TRIANGLE_STRIP; } + case GLModel::Geometry::EPrimitiveType::TriangleFan: { return GL_TRIANGLE_FAN; } + case GLModel::Geometry::EPrimitiveType::Lines: { return GL_LINES; } + case GLModel::Geometry::EPrimitiveType::LineStrip: { return GL_LINE_STRIP; } + case GLModel::Geometry::EPrimitiveType::LineLoop: { return GL_LINE_LOOP; } + } +} - for (const RenderData& data : m_render_data) { - if (data.vbo_id == 0 || data.ibo_id == 0) - continue; - - GLenum mode; - switch (data.type) - { - default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } - } +static GLenum get_index_type(const GLModel::Geometry& data) +{ + switch (data.index_type) + { + default: + case GLModel::Geometry::EIndexType::UINT: { return GL_UNSIGNED_INT; } + case GLModel::Geometry::EIndexType::USHORT: { return GL_UNSIGNED_SHORT; } + case GLModel::Geometry::EIndexType::UBYTE: { return GL_UNSIGNED_BYTE; } + } +} - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); - glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0)); - glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float)))); +void GLModel::render() +{ + render(std::make_pair(0, indices_count())); +} - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); +void GLModel::render(const std::pair& range) +{ + if (m_render_disabled) + return; - if (shader != nullptr) - shader->set_uniform("uniform_color", data.color); - else - glsafe(::glColor4fv(data.color.data())); + if (range.second == range.first) + return; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glDrawElements(mode, static_cast(data.indices_count), GL_UNSIGNED_INT, (const void*)0)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader == nullptr) + return; - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); + // sends data to gpu if not done yet + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (m_render_data.geometry.vertices_count() > 0 && m_render_data.geometry.indices_count() > 0 && !send_to_gpu()) + return; + } - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + const Geometry& data = m_render_data.geometry; + + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); + + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + const bool tex_coord = Geometry::has_tex_coord(data.format); +#if ENABLE_OPENGL_ES + const bool extra = Geometry::has_extra(data.format); +#endif // ENABLE_OPENGL_ES + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(m_render_data.vao_id)); + // the following binding is needed to set the vertex attributes +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + int position_id = -1; + int normal_id = -1; + int tex_coord_id = -1; +#if ENABLE_OPENGL_ES + int extra_id = -1; +#endif // ENABLE_OPENGL_ES + + if (position) { + position_id = shader->get_attrib_location("v_position"); + if (position_id != -1) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); + } + } + if (normal) { + normal_id = shader->get_attrib_location("v_normal"); + if (normal_id != -1) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + } + if (tex_coord) { + tex_coord_id = shader->get_attrib_location("v_tex_coord"); + if (tex_coord_id != -1) { + glsafe(::glVertexAttribPointer(tex_coord_id, Geometry::tex_coord_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::tex_coord_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(tex_coord_id)); + } + } +#if ENABLE_OPENGL_ES + if (extra) { + extra_id = shader->get_attrib_location("v_extra"); + if (extra_id != -1) { + glsafe(::glVertexAttribPointer(extra_id, Geometry::extra_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::extra_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(extra_id)); + } } +#endif // ENABLE_OPENGL_ES + + shader->set_uniform("uniform_color", data.color); + +#if ENABLE_GL_CORE_PROFILE + if (!OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + glsafe(::glDrawElements(mode, range.second - range.first, index_type, (const void*)(range.first * Geometry::index_stride_bytes(data)))); +#if !ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); +#endif // !ENABLE_GL_CORE_PROFILE + +#if ENABLE_OPENGL_ES + if (extra_id != -1) + glsafe(::glDisableVertexAttribArray(extra_id)); +#endif // ENABLE_OPENGL_ES + if (tex_coord_id != -1) + glsafe(::glDisableVertexAttribArray(tex_coord_id)); + if (normal_id != -1) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position_id != -1) + glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(0)); +#endif // ENABLE_GL_CORE_PROFILE + +#if ENABLE_GLMODEL_STATISTICS + ++s_statistics.render_calls; +#endif // ENABLE_GLMODEL_STATISTICS } -void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) const +void GLModel::render_instanced(unsigned int instances_vbo, unsigned int instances_count) { - if (instances_vbo == 0) + if (instances_vbo == 0 || instances_count == 0) return; GLShaderProgram* shader = wxGetApp().get_current_shader(); - assert(shader == nullptr || boost::algorithm::iends_with(shader->get_name(), "_instanced")); + if (shader == nullptr || !boost::algorithm::iends_with(shader->get_name(), "_instanced")) + return; // vertex attributes - GLint position_id = (shader != nullptr) ? shader->get_attrib_location("v_position") : -1; - GLint normal_id = (shader != nullptr) ? shader->get_attrib_location("v_normal") : -1; - assert(position_id != -1 && normal_id != -1); + const GLint position_id = shader->get_attrib_location("v_position"); + const GLint normal_id = shader->get_attrib_location("v_normal"); + if (position_id == -1 || normal_id == -1) + return; // instance attributes - GLint offset_id = (shader != nullptr) ? shader->get_attrib_location("i_offset") : -1; - GLint scales_id = (shader != nullptr) ? shader->get_attrib_location("i_scales") : -1; - assert(offset_id != -1 && scales_id != -1); + const GLint offset_id = shader->get_attrib_location("i_offset"); + const GLint scales_id = shader->get_attrib_location("i_scales"); + if (offset_id == -1 || scales_id == -1) + return; + + if (m_render_data.vbo_id == 0 || m_render_data.ibo_id == 0) { + if (!send_to_gpu()) + return; + } + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(m_render_data.vao_id)); +#endif // ENABLE_GL_CORE_PROFILE glsafe(::glBindBuffer(GL_ARRAY_BUFFER, instances_vbo)); - if (offset_id != -1) { - glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)0)); - glsafe(::glEnableVertexAttribArray(offset_id)); - glsafe(::glVertexAttribDivisor(offset_id, 1)); - } - if (scales_id != -1) { - glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); - glsafe(::glEnableVertexAttribArray(scales_id)); - glsafe(::glVertexAttribDivisor(scales_id, 1)); - } - - for (const RenderData& data : m_render_data) { - if (data.vbo_id == 0 || data.ibo_id == 0) - continue; - - GLenum mode; - switch (data.type) - { - default: - case PrimitiveType::Triangles: { mode = GL_TRIANGLES; break; } - case PrimitiveType::Lines: { mode = GL_LINES; break; } - case PrimitiveType::LineStrip: { mode = GL_LINE_STRIP; break; } - case PrimitiveType::LineLoop: { mode = GL_LINE_LOOP; break; } - } + const size_t instance_stride = 5 * sizeof(float); + glsafe(::glVertexAttribPointer(offset_id, 3, GL_FLOAT, GL_FALSE, instance_stride, (const void*)0)); + glsafe(::glEnableVertexAttribArray(offset_id)); + glsafe(::glVertexAttribDivisor(offset_id, 1)); - if (shader != nullptr) - shader->set_uniform("uniform_color", data.color); - else - glsafe(::glColor4fv(data.color.data())); + glsafe(::glVertexAttribPointer(scales_id, 2, GL_FLOAT, GL_FALSE, instance_stride, (const void*)(3 * sizeof(float)))); + glsafe(::glEnableVertexAttribArray(scales_id)); + glsafe(::glVertexAttribDivisor(scales_id, 1)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); - if (position_id != -1) { - glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)0)); - glsafe(::glEnableVertexAttribArray(position_id)); - } - if (normal_id != -1) { - glsafe(::glVertexAttribPointer(normal_id, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (GLvoid*)(3 * sizeof(float)))); - glsafe(::glEnableVertexAttribArray(normal_id)); - } + const Geometry& data = m_render_data.geometry; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glDrawElementsInstanced(mode, static_cast(data.indices_count), GL_UNSIGNED_INT, (const void*)0, instances_count)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + const GLenum mode = get_primitive_mode(data.format); + const GLenum index_type = get_index_type(data); - if (normal_id != -1) - glsafe(::glDisableVertexAttribArray(normal_id)); - if (position_id != -1) - glsafe(::glDisableVertexAttribArray(position_id)); + const size_t vertex_stride_bytes = Geometry::vertex_stride_bytes(data.format); + const bool position = Geometry::has_position(data.format); + const bool normal = Geometry::has_normal(data.format); + +#if ENABLE_GL_CORE_PROFILE + // the following binding is needed to set the vertex attributes +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + + if (position) { + glsafe(::glVertexAttribPointer(position_id, Geometry::position_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::position_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(position_id)); } - if (scales_id != -1) - glsafe(::glDisableVertexAttribArray(scales_id)); - if (offset_id != -1) - glsafe(::glDisableVertexAttribArray(offset_id)); + if (normal) { + glsafe(::glVertexAttribPointer(normal_id, Geometry::normal_stride_floats(data.format), GL_FLOAT, GL_FALSE, vertex_stride_bytes, (const void*)Geometry::normal_offset_bytes(data.format))); + glsafe(::glEnableVertexAttribArray(normal_id)); + } + + shader->set_uniform("uniform_color", data.color); + +#if !ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); +#endif // !ENABLE_GL_CORE_PROFILE + glsafe(::glDrawElementsInstanced(mode, indices_count(), index_type, (const void*)0, instances_count)); +#if !ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); +#endif // !ENABLE_GL_CORE_PROFILE + + if (normal) + glsafe(::glDisableVertexAttribArray(normal_id)); + if (position) + glsafe(::glDisableVertexAttribArray(position_id)); + + glsafe(::glDisableVertexAttribArray(scales_id)); + glsafe(::glDisableVertexAttribArray(offset_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(0)); +#endif // ENABLE_GL_CORE_PROFILE + +#if ENABLE_GLMODEL_STATISTICS + ++s_statistics.render_instanced_calls; +#endif // ENABLE_GLMODEL_STATISTICS } -void GLModel::send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices) +bool GLModel::send_to_gpu() { - assert(data.vbo_id == 0); - assert(data.ibo_id == 0); + if (m_render_data.vbo_id > 0 || m_render_data.ibo_id > 0) { + assert(false); + return false; + } + + Geometry& data = m_render_data.geometry; + if (data.vertices.empty() || data.indices.empty()) { + assert(false); + return false; + } - // vertex data -> send to gpu - glsafe(::glGenBuffers(1, &data.vbo_id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, data.vbo_id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW)); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) { + glsafe(::glGenVertexArrays(1, &m_render_data.vao_id)); + glsafe(::glBindVertexArray(m_render_data.vao_id)); + } +#endif // ENABLE_GL_CORE_PROFILE + + // vertices + glsafe(::glGenBuffers(1, &m_render_data.vbo_id)); + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_render_data.vbo_id)); + glsafe(::glBufferData(GL_ARRAY_BUFFER, data.vertices_size_bytes(), data.vertices.data(), GL_STATIC_DRAW)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); + m_render_data.vertices_count = vertices_count(); +#if ENABLE_GLMODEL_STATISTICS + s_statistics.gpu_memory.vertices.current += data.vertices_size_bytes(); + s_statistics.gpu_memory.vertices.max = std::max(s_statistics.gpu_memory.vertices.current, s_statistics.gpu_memory.vertices.max); +#endif // ENABLE_GLMODEL_STATISTICS + data.vertices = std::vector(); + + // indices + const size_t indices_count = data.indices.size(); + glsafe(::glGenBuffers(1, &m_render_data.ibo_id)); + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_render_data.ibo_id)); + if (m_render_data.vertices_count <= 256) { + // convert indices to unsigned char to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < indices_count; ++i) { + reduced_indices[i] = (unsigned char)data.indices[i]; + } + data.index_type = Geometry::EIndexType::UBYTE; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned char), reduced_indices.data(), GL_STATIC_DRAW)); + } + else if (m_render_data.vertices_count <= 65536) { + // convert indices to unsigned short to save gpu memory + std::vector reduced_indices(indices_count); + for (size_t i = 0; i < data.indices.size(); ++i) { + reduced_indices[i] = (unsigned short)data.indices[i]; + } + data.index_type = Geometry::EIndexType::USHORT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_count * sizeof(unsigned short), reduced_indices.data(), GL_STATIC_DRAW)); + } + else { + data.index_type = Geometry::EIndexType::UINT; + glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, data.indices_size_bytes(), data.indices.data(), GL_STATIC_DRAW)); + } - // indices data -> send to gpu - glsafe(::glGenBuffers(1, &data.ibo_id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, data.ibo_id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); +#if ENABLE_GL_CORE_PROFILE + if (!OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); + + m_render_data.indices_count = indices_count; +#if ENABLE_GLMODEL_STATISTICS + s_statistics.gpu_memory.indices.current += data.indices_size_bytes(); + s_statistics.gpu_memory.indices.max = std::max(s_statistics.gpu_memory.indices.current, s_statistics.gpu_memory.indices.max); +#endif // ENABLE_GLMODEL_STATISTICS + data.indices = std::vector(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_version_greater_or_equal_to(3, 0)) + glsafe(::glBindVertexArray(0)); +#endif // ENABLE_GL_CORE_PROFILE + + return true; } -GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +#if ENABLE_GLMODEL_STATISTICS +void GLModel::render_statistics() { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); + static const float offset = 175.0f; + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + auto add_memory = [&imgui](const std::string& label, int64_t memory) { + auto format_string = [memory](const std::string& units, float value) { + return std::to_string(memory) + " bytes (" + + Slic3r::float_to_string_decimal_point(float(memory) * value, 3) + + " " + units + ")"; + }; + + static const float kb = 1024.0f; + static const float inv_kb = 1.0f / kb; + static const float mb = 1024.0f * kb; + static const float inv_mb = 1.0f / mb; + static const float gb = 1024.0f * mb; + static const float inv_gb = 1.0f / gb; + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + if (static_cast(memory) < mb) + imgui.text(format_string("KB", inv_kb)); + else if (static_cast(memory) < gb) + imgui.text(format_string("MB", inv_mb)); + else + imgui.text(format_string("GB", inv_gb)); }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); + + auto add_counter = [&imgui](const std::string& label, int64_t counter) { + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, label); + ImGui::SameLine(offset); + imgui.text(std::to_string(counter)); }; - resolution = std::max(4, resolution); + imgui.set_next_window_pos(0.5f * wxGetApp().plater()->get_current_canvas3D()->get_canvas_size().get_width(), 0.0f, ImGuiCond_Once, 0.5f, 0.0f); + ImGui::SetNextWindowSizeConstraints({ 300.0f, 100.0f }, { 600.0f, 900.0f }); + imgui.begin(std::string("GLModel Statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + add_counter(std::string("Render calls:"), s_statistics.render_calls); + add_counter(std::string("Render instanced calls:"), s_statistics.render_instanced_calls); - const float angle_step = 2.0f * M_PI / static_cast(resolution); + if (ImGui::CollapsingHeader("GPU memory")) { + ImGui::Indent(10.0f); + if (ImGui::CollapsingHeader("Vertices")) { + add_memory(std::string("Current:"), s_statistics.gpu_memory.vertices.current); + add_memory(std::string("Max:"), s_statistics.gpu_memory.vertices.max); + } + if (ImGui::CollapsingHeader("Indices")) { + add_memory(std::string("Current:"), s_statistics.gpu_memory.indices.current); + add_memory(std::string("Max:"), s_statistics.gpu_memory.indices.max); + } + ImGui::Unindent(10.0f); + } + + imgui.end(); +} +#endif // ENABLE_GLMODEL_STATISTICS + +template +inline bool all_vertices_inside(const GLModel::Geometry& geometry, Fn fn) +{ + const size_t position_stride_floats = geometry.position_stride_floats(geometry.format); + const size_t position_offset_floats = geometry.position_offset_floats(geometry.format); + assert(position_stride_floats == 3); + if (geometry.vertices.empty() || position_stride_floats != 3) + return false; + + for (auto it = geometry.vertices.begin(); it != geometry.vertices.end(); ) { + it += position_offset_floats; + if (!fn({ *it, *(it + 1), *(it + 2) })) + return false; + it += (geometry.vertex_stride_floats(geometry.format) - position_offset_floats - position_stride_floats); + } + return true; +} + +bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom) +{ + static constexpr const double epsilon = BuildVolume::BedEpsilon; + switch (volume.type()) { + case BuildVolume_Type::Rectangle: + { + BoundingBox3Base build_volume = volume.bounding_volume().inflated(epsilon); + if (volume.printable_height() == 0.0) + build_volume.max.z() = std::numeric_limits::max(); + if (ignore_bottom) + build_volume.min.z() = -std::numeric_limits::max(); + const BoundingBoxf3& model_box = model.get_bounding_box(); + return build_volume.contains(model_box.min) && build_volume.contains(model_box.max); + } + case BuildVolume_Type::Circle: + { + const Geometry::Circled& circle = volume.circle(); + const Vec2f c = unscaled(circle.center); + const float r = unscaled(circle.radius) + float(epsilon); + const float r2 = sqr(r); + return volume.printable_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [c, r2](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2; }) : + + all_vertices_inside(model.get_geometry(), [c, r2, z = volume.printable_height() + epsilon](const Vec3f& p) { return (to_2d(p) - c).squaredNorm() <= r2 && p.z() <= z; }); + } + case BuildVolume_Type::Convex: + //FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently. + case BuildVolume_Type::Custom: + return volume.printable_height() == 0.0 ? + all_vertices_inside(model.get_geometry(), [&volume](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()); }) : + all_vertices_inside(model.get_geometry(), [&volume, z = volume.printable_height() + epsilon](const Vec3f& p) { return Geometry::inside_convex_polygon(volume.top_bottom_convex_hull_decomposition_bed(), to_2d(p).cast()) && p.z() <= z; }); + default: + return true; + } +} + +GLModel::Geometry stilized_arrow(unsigned int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height) +{ + resolution = std::max(4, resolution); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(6 * resolution + 2); + data.reserve_indices(6 * resolution * 3); + + const float angle_step = 2.0f * float(PI) / float(resolution); std::vector cosines(resolution); std::vector sines(resolution); - for (int i = 0; i < resolution; ++i) { - const float angle = angle_step * static_cast(i); + for (unsigned int i = 0; i < resolution; ++i) { + const float angle = angle_step * float(i); cosines[i] = ::cos(angle); sines[i] = -::sin(angle); } @@ -355,86 +1175,76 @@ GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, flo const float total_height = tip_height + stem_height; // tip vertices/normals - append_vertex(entity, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ()); - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + data.add_vertex(Vec3f(0.0f, 0.0f, total_height), (Vec3f)Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(tip_radius * sines[i], tip_radius * cosines[i], stem_height), Vec3f(sines[i], cosines[i], 0.0f)); } // tip triangles - for (int i = 0; i < resolution; ++i) { - const int v3 = (i < resolution - 1) ? i + 2 : 1; - append_indices(entity, 0, i + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v3 = (i < resolution - 1) ? i + 2 : 1; + data.add_triangle(0, i + 1, v3); } // tip cap outer perimeter vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(tip_radius * sines[i], tip_radius * cosines[i], stem_height), (Vec3f)(-Vec3f::UnitZ())); } // tip cap inner perimeter vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], stem_height), (Vec3f)(-Vec3f::UnitZ())); } // tip cap triangles - for (int i = 0; i < resolution; ++i) { - const int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; - const int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; - append_indices(entity, i + resolution + 1, v3, v2); - append_indices(entity, i + resolution + 1, i + 2 * resolution + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1; + const unsigned int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1; + data.add_triangle(i + resolution + 1, v3, v2); + data.add_triangle(i + resolution + 1, i + 2 * resolution + 1, v3); } // stem bottom vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f }); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], stem_height), Vec3f(sines[i], cosines[i], 0.0f)); } // stem top vertices - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f }); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], 0.0f), Vec3f(sines[i], cosines[i], 0.0f)); } // stem triangles - for (int i = 0; i < resolution; ++i) { - const int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; - const int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; - append_indices(entity, i + 3 * resolution + 1, v3, v2); - append_indices(entity, i + 3 * resolution + 1, i + 4 * resolution + 1, v3); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1; + const unsigned int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1; + data.add_triangle(i + 3 * resolution + 1, v3, v2); + data.add_triangle(i + 3 * resolution + 1, i + 4 * resolution + 1, v3); } // stem cap vertices - append_vertex(entity, Vec3f::Zero(), -Vec3f::UnitZ()); - for (int i = 0; i < resolution; ++i) { - append_vertex(entity, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ()); + data.add_vertex((Vec3f)Vec3f::Zero(), (Vec3f)(-Vec3f::UnitZ())); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_vertex(Vec3f(stem_radius * sines[i], stem_radius * cosines[i], 0.0f), (Vec3f)(-Vec3f::UnitZ())); } // stem cap triangles - for (int i = 0; i < resolution; ++i) { - const int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; - append_indices(entity, 5 * resolution + 1, v3, i + 5 * resolution + 2); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2; + data.add_triangle(5 * resolution + 1, v3, i + 5 * resolution + 2); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) +GLModel::Geometry circular_arrow(unsigned int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); - }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); - }; - - resolution = std::max(2, resolution); + resolution = std::max(2, resolution); - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(8 * (resolution + 1) + 30); + data.reserve_indices((8 * resolution + 16) * 3); const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; @@ -442,171 +1252,161 @@ GLModel::InitializationData circular_arrow(int resolution, float radius, float t const float outer_radius = radius + half_stem_width; const float inner_radius = radius - half_stem_width; - const float step_angle = 0.5f * PI / static_cast(resolution); + const float step_angle = 0.5f * float(PI) / float(resolution); // tip // top face vertices - append_vertex(entity, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -tip_height, radius, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, outer_radius, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, inner_radius, half_thickness), (Vec3f)Vec3f::UnitZ()); // top face triangles - append_indices(entity, 0, 1, 2); - append_indices(entity, 0, 2, 4); - append_indices(entity, 4, 2, 3); + data.add_triangle(0, 1, 2); + data.add_triangle(0, 2, 4); + data.add_triangle(4, 2, 3); // bottom face vertices - append_vertex(entity, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, outer_radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, inner_radius, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); // bottom face triangles - append_indices(entity, 5, 7, 6); - append_indices(entity, 5, 9, 7); - append_indices(entity, 9, 8, 7); + data.add_triangle(5, 7, 6); + data.add_triangle(5, 9, 7); + data.add_triangle(9, 8, 7); // side faces vertices - append_vertex(entity, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, outer_radius, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, outer_radius, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), (Vec3f)Vec3f::UnitX()); Vec3f normal(-half_tip_width, tip_height, 0.0f); normal.normalize(); - append_vertex(entity, { 0.0f, radius + half_tip_width, -half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, -half_thickness }, normal); - append_vertex(entity, { 0.0f, radius + half_tip_width, half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, half_thickness }, normal); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, -half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius + half_tip_width, half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), normal); - normal = Vec3f(-half_tip_width, -tip_height, 0.0f); + normal = { -half_tip_width, -tip_height, 0.0f }; normal.normalize(); - append_vertex(entity, { -tip_height, radius, -half_thickness }, normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, normal); - append_vertex(entity, { -tip_height, radius, half_thickness }, normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, normal); + data.add_vertex(Vec3f(-tip_height, radius, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), normal); + data.add_vertex(Vec3f(-tip_height, radius, half_thickness), normal); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), normal); - append_vertex(entity, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, inner_radius, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, radius - half_tip_width, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(0.0f, inner_radius, half_thickness), (Vec3f)Vec3f::UnitX()); // side face triangles - for (int i = 0; i < 4; ++i) { - const int ii = i * 4; - append_indices(entity, 10 + ii, 11 + ii, 13 + ii); - append_indices(entity, 10 + ii, 13 + ii, 12 + ii); + for (unsigned int i = 0; i < 4; ++i) { + const unsigned int ii = i * 4; + data.add_triangle(10 + ii, 11 + ii, 13 + ii); + data.add_triangle(10 + ii, 13 + ii, 12 + ii); } // stem // top face vertices - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness), (Vec3f)Vec3f::UnitZ()); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness), (Vec3f)Vec3f::UnitZ()); } // top face triangles - for (int i = 0; i < resolution; ++i) { - append_indices(entity, 26 + i, 27 + i, 27 + resolution + i); - append_indices(entity, 27 + i, 28 + resolution + i, 27 + resolution + i); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(26 + i, 27 + i, 27 + resolution + i); + data.add_triangle(27 + i, 28 + resolution + i, 27 + resolution + i); } // bottom face vertices - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness), (Vec3f)(-Vec3f::UnitZ())); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; - append_vertex(entity, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ()); + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; + data.add_vertex(Vec3f(outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness), (Vec3f)(-Vec3f::UnitZ())); } // bottom face triangles - for (int i = 0; i < resolution; ++i) { - append_indices(entity, 28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); - append_indices(entity, 29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i); + data.add_triangle(29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i); } // side faces vertices and triangles - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f }); + data.add_vertex(Vec3f(inner_radius * s, inner_radius * c, -half_thickness), Vec3f(-s, -c, 0.0f)); } - for (int i = 0; i <= resolution; ++i) { - const float angle = static_cast(i) * step_angle; + for (unsigned int i = 0; i <= resolution; ++i) { + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f }); + data.add_vertex(Vec3f(inner_radius * s, inner_radius * c, half_thickness), Vec3f(-s, -c, 0.0f)); } - int first_id = 26 + 4 * (resolution + 1); - for (int i = 0; i < resolution; ++i) { - const int ii = first_id + i; - append_indices(entity, ii, ii + 1, ii + resolution + 2); - append_indices(entity, ii, ii + resolution + 2, ii + resolution + 1); + unsigned int first_id = 26 + 4 * (resolution + 1); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int ii = first_id + i; + data.add_triangle(ii, ii + 1, ii + resolution + 2); + data.add_triangle(ii, ii + resolution + 2, ii + resolution + 1); } - append_vertex(entity, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(inner_radius, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(outer_radius, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(inner_radius, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(outer_radius, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); first_id = 26 + 6 * (resolution + 1); - append_indices(entity, first_id, first_id + 1, first_id + 3); - append_indices(entity, first_id, first_id + 3, first_id + 2); + data.add_triangle(first_id, first_id + 1, first_id + 3); + data.add_triangle(first_id, first_id + 3, first_id + 2); for (int i = resolution; i >= 0; --i) { - const float angle = static_cast(i) * step_angle; + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f }); + data.add_vertex(Vec3f(outer_radius * s, outer_radius * c, -half_thickness), Vec3f(s, c, 0.0f)); } for (int i = resolution; i >= 0; --i) { - const float angle = static_cast(i) * step_angle; + const float angle = float(i) * step_angle; const float c = ::cos(angle); const float s = ::sin(angle); - append_vertex(entity, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f }); + data.add_vertex(Vec3f(outer_radius * s, outer_radius * c, +half_thickness), Vec3f(s, c, 0.0f)); } first_id = 30 + 6 * (resolution + 1); - for (int i = 0; i < resolution; ++i) { - const int ii = first_id + i; - append_indices(entity, ii, ii + 1, ii + resolution + 2); - append_indices(entity, ii, ii + resolution + 2, ii + resolution + 1); + for (unsigned int i = 0; i < resolution; ++i) { + const unsigned int ii = first_id + i; + data.add_triangle(ii, ii + 1, ii + resolution + 2); + data.add_triangle(ii, ii + resolution + 2, ii + resolution + 1); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) +GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness) { - auto append_vertex = [](GLModel::InitializationData::Entity& entity, const Vec3f& position, const Vec3f& normal) { - entity.positions.emplace_back(position); - entity.normals.emplace_back(normal); - }; - auto append_indices = [](GLModel::InitializationData::Entity& entity, unsigned int v1, unsigned int v2, unsigned int v3) { - entity.indices.emplace_back(v1); - entity.indices.emplace_back(v2); - entity.indices.emplace_back(v3); - }; - - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(42); + data.reserve_indices(72); const float half_thickness = 0.5f * thickness; const float half_stem_width = 0.5f * stem_width; @@ -614,133 +1414,307 @@ GLModel::InitializationData straight_arrow(float tip_width, float tip_height, fl const float total_height = tip_height + stem_height; // top face vertices - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { 0.0, total_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitZ()); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitZ()); // top face triangles - append_indices(entity, 0, 1, 6); - append_indices(entity, 6, 1, 5); - append_indices(entity, 4, 5, 3); - append_indices(entity, 5, 1, 3); - append_indices(entity, 1, 2, 3); + data.add_triangle(0, 1, 6); + data.add_triangle(6, 1, 5); + data.add_triangle(4, 5, 3); + data.add_triangle(5, 1, 3); + data.add_triangle(1, 2, 3); // bottom face vertices - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ()); - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitZ())); // bottom face triangles - append_indices(entity, 7, 13, 8); - append_indices(entity, 13, 12, 8); - append_indices(entity, 12, 11, 10); - append_indices(entity, 8, 12, 10); - append_indices(entity, 9, 8, 10); + data.add_triangle(7, 13, 8); + data.add_triangle(13, 12, 8); + data.add_triangle(12, 11, 10); + data.add_triangle(8, 12, 10); + data.add_triangle(9, 8, 10); // side faces vertices - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)Vec3f::UnitX()); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)Vec3f::UnitX()); - append_vertex(entity, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); Vec3f normal(tip_height, half_tip_width, 0.0f); normal.normalize(); - append_vertex(entity, { half_tip_width, stem_height, -half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, -half_thickness }, normal); - append_vertex(entity, { half_tip_width, stem_height, half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, half_thickness }, normal); + data.add_vertex(Vec3f(half_tip_width, stem_height, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), normal); + data.add_vertex(Vec3f(half_tip_width, stem_height, half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), normal); - normal = Vec3f(-tip_height, half_tip_width, 0.0f); + normal = { -tip_height, half_tip_width, 0.0f }; normal.normalize(); - append_vertex(entity, { 0.0, total_height, -half_thickness }, normal); - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, normal); - append_vertex(entity, { 0.0, total_height, half_thickness }, normal); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, normal); - - append_vertex(entity, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY()); - - append_vertex(entity, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX()); - - append_vertex(entity, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); - append_vertex(entity, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY()); + data.add_vertex(Vec3f(0.0f, total_height, -half_thickness), normal); + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), normal); + data.add_vertex(Vec3f(0.0f, total_height, half_thickness), normal); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), normal); + + data.add_vertex(Vec3f(-half_tip_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_tip_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitY())); + + data.add_vertex(Vec3f(-half_stem_width, stem_height, -half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, stem_height, half_thickness), (Vec3f)(-Vec3f::UnitX())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitX())); + + data.add_vertex(Vec3f(-half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, 0.0f, -half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(-half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); + data.add_vertex(Vec3f(half_stem_width, 0.0f, half_thickness), (Vec3f)(-Vec3f::UnitY())); // side face triangles - for (int i = 0; i < 7; ++i) { - const int ii = i * 4; - append_indices(entity, 14 + ii, 15 + ii, 17 + ii); - append_indices(entity, 14 + ii, 17 + ii, 16 + ii); + for (unsigned int i = 0; i < 7; ++i) { + const unsigned int ii = i * 4; + data.add_triangle(14 + ii, 15 + ii, 17 + ii); + data.add_triangle(14 + ii, 17 + ii, 16 + ii); } - data.entities.emplace_back(entity); return data; } -GLModel::InitializationData diamond(int resolution) +GLModel::Geometry diamond(unsigned int resolution) { - resolution = std::max(4, resolution); + resolution = std::max(4, resolution); - GLModel::InitializationData data; - GLModel::InitializationData::Entity entity; - entity.type = GLModel::PrimitiveType::Triangles; + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(resolution + 2); + data.reserve_indices((2 * (resolution + 1)) * 3); const float step = 2.0f * float(PI) / float(resolution); - // positions - for (int i = 0; i < resolution; ++i) { - float ii = float(i) * step; - entity.positions.emplace_back(0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f); - } - entity.positions.emplace_back(0.0f, 0.0f, 0.5f); - entity.positions.emplace_back(0.0f, 0.0f, -0.5f); - - // normals - for (const Vec3f& v : entity.positions) { - entity.normals.emplace_back(v.normalized()); + // vertices + for (unsigned int i = 0; i < resolution; ++i) { + const float ii = float(i) * step; + const Vec3f p = { 0.5f * ::cos(ii), 0.5f * ::sin(ii), 0.0f }; + data.add_vertex(p, (Vec3f)p.normalized()); } + Vec3f p = { 0.0f, 0.0f, 0.5f }; + data.add_vertex(p, (Vec3f)p.normalized()); + p = { 0.0f, 0.0f, -0.5f }; + data.add_vertex(p, (Vec3f)p.normalized()); // triangles // top - for (int i = 0; i < resolution; ++i) { - entity.indices.push_back(i + 0); - entity.indices.push_back(i + 1); - entity.indices.push_back(resolution); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(i + 0, i + 1, resolution); } - entity.indices.push_back(resolution - 1); - entity.indices.push_back(0); - entity.indices.push_back(resolution); + data.add_triangle(resolution - 1, 0, resolution); // bottom - for (int i = 0; i < resolution; ++i) { - entity.indices.push_back(i + 0); - entity.indices.push_back(resolution + 1); - entity.indices.push_back(i + 1); + for (unsigned int i = 0; i < resolution; ++i) { + data.add_triangle(i + 0, resolution + 1, i + 1); + } + data.add_triangle(resolution - 1, resolution + 1, 0); + + return data; +} + +GLModel::Geometry smooth_sphere(unsigned int resolution, float radius) +{ + resolution = std::max(4, resolution); + + const unsigned int sectorCount = resolution; + const unsigned int stackCount = resolution; + + const float sectorStep = float(2.0 * M_PI / sectorCount); + const float stackStep = float(M_PI / stackCount); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices((stackCount - 1) * sectorCount + 2); + data.reserve_indices((2 * (stackCount - 1) * sectorCount) * 3); + + // vertices + for (unsigned int i = 0; i <= stackCount; ++i) { + // from pi/2 to -pi/2 + const double stackAngle = 0.5 * M_PI - stackStep * i; + const double xy = double(radius) * ::cos(stackAngle); + const double z = double(radius) * ::sin(stackAngle); + if (i == 0 || i == stackCount) { + const Vec3f v(float(xy), 0.0f, float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + else { + for (unsigned int j = 0; j < sectorCount; ++j) { + // from 0 to 2pi + const double sectorAngle = sectorStep * j; + const Vec3f v(float(xy * std::cos(sectorAngle)), float(xy * std::sin(sectorAngle)), float(z)); + data.add_vertex(v, (Vec3f)v.normalized()); + } + } + } + + // triangles + for (unsigned int i = 0; i < stackCount; ++i) { + // Beginning of current stack. + unsigned int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + const unsigned int k1_first = k1; + // Beginning of next stack. + unsigned int k2 = (i == 0) ? 1 : (k1 + sectorCount); + const unsigned int k2_first = k2; + for (unsigned int j = 0; j < sectorCount; ++j) { + // 2 triangles per sector excluding first and last stacks + unsigned int k1_next = k1; + unsigned int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + data.add_triangle(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + data.add_triangle(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + + return data; +} + +GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height) +{ + resolution = std::max(4, resolution); + + const unsigned int sectorCount = resolution; + const float sectorStep = 2.0f * float(M_PI) / float(sectorCount); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(sectorCount * 4 + 2); + data.reserve_indices(sectorCount * 4 * 3); + + auto generate_vertices_on_circle = [sectorCount, sectorStep](float radius) { + std::vector ret; + ret.reserve(sectorCount); + for (unsigned int i = 0; i < sectorCount; ++i) { + // from 0 to 2pi + const float sectorAngle = sectorStep * i; + ret.emplace_back(radius * std::cos(sectorAngle), radius * std::sin(sectorAngle), 0.0f); + } + return ret; + }; + + const std::vector base_vertices = generate_vertices_on_circle(radius); + const Vec3f h = height * Vec3f::UnitZ(); + + // stem vertices + for (unsigned int i = 0; i < sectorCount; ++i) { + const Vec3f& v = base_vertices[i]; + const Vec3f n = v.normalized(); + data.add_vertex(v, n); + data.add_vertex(v + h, n); + } + + // stem triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + unsigned int v1 = i * 2; + unsigned int v2 = (i < sectorCount - 1) ? v1 + 2 : 0; + unsigned int v3 = v2 + 1; + unsigned int v4 = v1 + 1; + data.add_triangle(v1, v2, v3); + data.add_triangle(v1, v3, v4); + } + + // bottom cap vertices + Vec3f cap_center = Vec3f::Zero(); + unsigned int cap_center_id = data.vertices_count(); + Vec3f normal = -Vec3f::UnitZ(); + + data.add_vertex(cap_center, normal); + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_vertex(base_vertices[i], normal); + } + + // bottom cap triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_triangle(cap_center_id, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1, cap_center_id + i + 1); + } + + // top cap vertices + cap_center += h; + cap_center_id = data.vertices_count(); + normal = -normal; + + data.add_vertex(cap_center, normal); + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_vertex(base_vertices[i] + h, normal); + } + + // top cap triangles + for (unsigned int i = 0; i < sectorCount; ++i) { + data.add_triangle(cap_center_id, cap_center_id + i + 1, (i < sectorCount - 1) ? cap_center_id + i + 2 : cap_center_id + 1); + } + + return data; +} + +GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness) +{ + const unsigned int torus_sector_count = std::max(4, primary_resolution); + const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count); + const unsigned int section_sector_count = std::max(4, secondary_resolution); + const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(torus_sector_count * section_sector_count); + data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3); + + // vertices + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const float section_angle = torus_sector_step * i; + const float csa = std::cos(section_angle); + const float ssa = std::sin(section_angle); + const Vec3f section_center(radius * csa, radius * ssa, 0.0f); + for (unsigned int j = 0; j < section_sector_count; ++j) { + const float circle_angle = section_sector_step * j; + const float thickness_xy = thickness * std::cos(circle_angle); + const float thickness_z = thickness * std::sin(circle_angle); + const Vec3f v(thickness_xy * csa, thickness_xy * ssa, thickness_z); + data.add_vertex(section_center + v, (Vec3f)v.normalized()); + } + } + + // triangles + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const unsigned int ii = i * section_sector_count; + const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const unsigned int j_next = (j + 1) % section_sector_count; + const unsigned int i0 = ii + j; + const unsigned int i1 = ii_next + j; + const unsigned int i2 = ii_next + j_next; + const unsigned int i3 = ii + j_next; + data.add_triangle(i0, i1, i2); + data.add_triangle(i0, i2, i3); + } } - entity.indices.push_back(resolution - 1); - entity.indices.push_back(resolution + 1); - entity.indices.push_back(0); - data.entities.emplace_back(entity); return data; } diff --git a/src/slic3r/GUI/GLModel.hpp b/src/slic3r/GUI/GLModel.hpp index d47c56fd93c..685fd6e1169 100644 --- a/src/slic3r/GUI/GLModel.hpp +++ b/src/slic3r/GUI/GLModel.hpp @@ -3,6 +3,8 @@ #include "libslic3r/Point.hpp" #include "libslic3r/BoundingBox.hpp" +#include "libslic3r/Color.hpp" +#include "libslic3r/Utils.hpp" #include #include @@ -13,53 +15,193 @@ namespace Slic3r { class TriangleMesh; class Polygon; using Polygons = std::vector; +// using Polygons = std::vector>; +class BuildVolume; namespace GUI { class GLModel { public: - enum class PrimitiveType : unsigned char + struct Geometry { - Triangles, - Lines, - LineStrip, - LineLoop + enum class EPrimitiveType : unsigned char + { + Points, + Triangles, + TriangleStrip, + TriangleFan, + Lines, + LineStrip, + LineLoop + }; + + enum class EVertexLayout : unsigned char + { + P2, // position 2 floats + P2T2, // position 2 floats + texture coords 2 floats + P3, // position 3 floats + P3T2, // position 3 floats + texture coords 2 floats + P3N3, // position 3 floats + normal 3 floats + P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats +#if ENABLE_OPENGL_ES + P3N3E3, // position 3 floats + normal 3 floats + extra 3 floats +#endif // ENABLE_OPENGL_ES + P4, // position 4 floats + }; + + enum class EIndexType : unsigned char + { + UINT, // unsigned int + USHORT, // unsigned short + UBYTE // unsigned byte + }; + + struct Format + { + EPrimitiveType type{ EPrimitiveType::Triangles }; + EVertexLayout vertex_layout{ EVertexLayout::P3N3 }; + }; + + Format format; + std::vector vertices; + std::vector indices; + EIndexType index_type{ EIndexType::UINT }; + ColorRGBA color{ ColorRGBA::BLACK() }; + + void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); } + void reserve_more_vertices(size_t vertices_count) { vertices.reserve(next_highest_power_of_2(vertices.size() + vertices_count * vertex_stride_floats(format))); } + void reserve_indices(size_t indices_count) { indices.reserve(indices_count); } + void reserve_more_indices(size_t indices_count) { indices.reserve(next_highest_power_of_2(indices.size() + indices_count)); } + + void add_vertex(const Vec2f& position); // EVertexLayout::P2 + void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2 + void add_vertex(const Vec3f& position); // EVertexLayout::P3 + void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2 + void add_vertex(const Vec3f& position, const Vec3f& normal) { // EVertexLayout::P3N3 + assert(format.vertex_layout == EVertexLayout::P3N3); + vertices.insert(vertices.end(), position.data(), position.data() + 3); + vertices.insert(vertices.end(), normal.data(), normal.data() + 3); + } + void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord); // EVertexLayout::P3N3T2 +#if ENABLE_OPENGL_ES + void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra); // EVertexLayout::P3N3E3 +#endif // ENABLE_OPENGL_ES + void add_vertex(const Vec4f& position); // EVertexLayout::P4 + + void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3 + + void set_index(size_t id, unsigned int index); + + void add_index(unsigned int id); + void add_line(unsigned int id1, unsigned int id2); + void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3){ + indices.emplace_back(id1); + indices.emplace_back(id2); + indices.emplace_back(id3); + } + + Vec2f extract_position_2(size_t id) const; + Vec3f extract_position_3(size_t id) const; + Vec3f extract_normal_3(size_t id) const; + Vec2f extract_tex_coord_2(size_t id) const; + + unsigned int extract_index(size_t id) const; + + void remove_vertex(size_t id); + + bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; } + + size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); } + size_t indices_count() const { return indices.size(); } + + size_t vertices_size_floats() const { return vertices.size(); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + size_t indices_size_bytes() const { return indices.size() * index_stride_bytes(*this); } + + indexed_triangle_set get_as_indexed_triangle_set() const; + + static size_t vertex_stride_floats(const Format& format); + static size_t vertex_stride_bytes(const Format& format) { return vertex_stride_floats(format) * sizeof(float); } + + static size_t position_stride_floats(const Format& format); + static size_t position_stride_bytes(const Format& format) { return position_stride_floats(format) * sizeof(float); } + static size_t position_offset_floats(const Format& format); + static size_t position_offset_bytes(const Format& format) { return position_offset_floats(format) * sizeof(float); } + + static size_t normal_stride_floats(const Format& format); + static size_t normal_stride_bytes(const Format& format) { return normal_stride_floats(format) * sizeof(float); } + static size_t normal_offset_floats(const Format& format); + static size_t normal_offset_bytes(const Format& format) { return normal_offset_floats(format) * sizeof(float); } + + static size_t tex_coord_stride_floats(const Format& format); + static size_t tex_coord_stride_bytes(const Format& format) { return tex_coord_stride_floats(format) * sizeof(float); } + static size_t tex_coord_offset_floats(const Format& format); + static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); } + +#if ENABLE_OPENGL_ES + static size_t extra_stride_floats(const Format& format); + static size_t extra_stride_bytes(const Format& format) { return extra_stride_floats(format) * sizeof(float); } + static size_t extra_offset_floats(const Format& format); + static size_t extra_offset_bytes(const Format& format) { return extra_offset_floats(format) * sizeof(float); } +#endif // ENABLE_OPENGL_ES + + static size_t index_stride_bytes(const Geometry& data); + + static bool has_position(const Format& format); + static bool has_normal(const Format& format); + static bool has_tex_coord(const Format& format); +#if ENABLE_OPENGL_ES + static bool has_extra(const Format& format); +#endif // ENABLE_OPENGL_ES }; struct RenderData { - PrimitiveType type; + Geometry geometry; +#if ENABLE_GL_CORE_PROFILE + unsigned int vao_id{ 0 }; +#endif // ENABLE_GL_CORE_PROFILE unsigned int vbo_id{ 0 }; unsigned int ibo_id{ 0 }; + size_t vertices_count{ 0 }; size_t indices_count{ 0 }; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; }; - struct InitializationData + private: +#if ENABLE_GLMODEL_STATISTICS + struct Statistics { - struct Entity + struct Buffers { - PrimitiveType type; - std::vector positions; - std::vector normals; - std::vector indices; - std::array color{ 1.0f, 1.0f, 1.0f, 1.0f }; + struct Data + { + size_t current{ 0 }; + size_t max{ 0 }; + }; + Data indices; + Data vertices; }; - std::vector entities; - - size_t vertices_count() const; - size_t vertices_size_floats() const { return vertices_count() * 6; } - size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + Buffers gpu_memory; - size_t indices_count() const; - size_t indices_size_bytes() const { return indices_count() * sizeof(unsigned int); } + int64_t render_calls{ 0 }; + int64_t render_instanced_calls{ 0 }; }; - private: - std::vector m_render_data; + static Statistics s_statistics; +#endif // ENABLE_GLMODEL_STATISTICS + + RenderData m_render_data; + // By default the vertex and index buffers data are sent to gpu at the first call to render() method. + // If you need to initialize a model from outside the main thread, so that a call to render() may happen + // before the initialization is complete, use the methods: + // disable_render() + // ... do your initialization ... + // enable_render() + // to keep the data on cpu side until needed. + bool m_render_disabled{ false }; BoundingBoxf3 m_bounding_box; std::string m_filename; @@ -67,50 +209,115 @@ namespace GUI { GLModel() = default; virtual ~GLModel() { reset(); } - void init_from(const InitializationData& data); - void init_from(const indexed_triangle_set& its, const BoundingBoxf3& bbox); + size_t vertices_count() const { return m_render_data.vertices_count > 0 ? + m_render_data.vertices_count : m_render_data.geometry.vertices_count(); } + size_t indices_count() const { return m_render_data.indices_count > 0 ? + m_render_data.indices_count : m_render_data.geometry.indices_count(); } + + size_t vertices_size_floats() const { return vertices_count() * Geometry::vertex_stride_floats(m_render_data.geometry.format); } + size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); } + + size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry); } + + const Geometry& get_geometry() const { return m_render_data.geometry; } + + void init_from(Geometry&& data); +#if ENABLE_SMOOTH_NORMALS + void init_from(const TriangleMesh& mesh, bool smooth_normals = false); +#else + void init_from(const TriangleMesh& mesh); +#endif // ENABLE_SMOOTH_NORMALS void init_from(const indexed_triangle_set& its); + void init_from(const Polygon& polygon, float z); void init_from(const Polygons& polygons, float z); bool init_from_file(const std::string& filename); - // if entity_id == -1 set the color of all entities - void set_color(int entity_id, const std::array& color); + void set_color(const ColorRGBA& color) { m_render_data.geometry.color = color; } + const ColorRGBA& get_color() const { return m_render_data.geometry.color; } + void set_color(const std::array& color){ + m_render_data.geometry.color = color; + } void reset(); - void render() const; - void render_instanced(unsigned int instances_vbo, unsigned int instances_count) const; + void render(); + void render(const std::pair& range); + void render_instanced(unsigned int instances_vbo, unsigned int instances_count); - bool is_initialized() const { return !m_render_data.empty(); } + bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; } + bool is_empty() const { return m_render_data.geometry.is_empty(); } const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; } const std::string& get_filename() const { return m_filename; } + bool is_render_disabled() const { return m_render_disabled; } + void enable_render() { m_render_disabled = false; } + void disable_render() { m_render_disabled = true; } + + size_t cpu_memory_used() const { + size_t ret = 0; + if (!m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (!m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } + size_t gpu_memory_used() const { + size_t ret = 0; + if (m_render_data.geometry.vertices.empty()) + ret += vertices_size_bytes(); + if (m_render_data.geometry.indices.empty()) + ret += indices_size_bytes(); + return ret; + } + +#if ENABLE_GLMODEL_STATISTICS + static void render_statistics(); + static void reset_statistics_counters() { + s_statistics.render_calls = 0; + s_statistics.render_instanced_calls = 0; + } +#endif // ENABLE_GLMODEL_STATISTICS + private: - void send_to_gpu(RenderData& data, const std::vector& vertices, const std::vector& indices); + bool send_to_gpu(); }; + bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom = true); + // create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution // the origin of the arrow is in the center of the stem cap // the arrow has its axis of symmetry along the Z axis and is pointing upward // used to render bed axes and sequential marker - GLModel::InitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); + GLModel::Geometry stilized_arrow(unsigned int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height); // create an arrow whose stem is a quarter of circle, with the given dimensions and resolution // the origin of the arrow is in the center of the circle // the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise // used to render sidebar hints for rotations - GLModel::InitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); + GLModel::Geometry circular_arrow(unsigned int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness); // create an arrow with the given dimensions // the origin of the arrow is in the center of the stem cap // the arrow is contained in XY plane and has its main axis along the Y axis // used to render sidebar hints for position and scale - GLModel::InitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); + GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness); // create a diamond with the given resolution // the origin of the diamond is in its center // the diamond is contained into a box with size [1, 1, 1] - GLModel::InitializationData diamond(int resolution); + GLModel::Geometry diamond(unsigned int resolution); + + // create a sphere with smooth normals + // the origin of the sphere is in its center + GLModel::Geometry smooth_sphere(unsigned int resolution, float radius); + // create a cylinder with smooth normals + // the axis of the cylinder is the Z axis + // the origin of the cylinder is the center of its bottom cap face + GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height); + // create a torus with smooth normals + // the axis of the torus is the Z axis + // the origin of the torus is in its center + GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness); } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/GLSelectionRectangle.hpp b/src/slic3r/GUI/GLSelectionRectangle.hpp index d9869771ef9..38568fe27c2 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.hpp +++ b/src/slic3r/GUI/GLSelectionRectangle.hpp @@ -33,6 +33,7 @@ class GLSelectionRectangle { void render(const GLCanvas3D& canvas) const; bool is_dragging() const { return m_state != Off; } + bool is_empty() const { return m_state == EState::Off || m_start_corner.isApprox(m_end_corner); } EState get_state() const { return m_state; } float get_width() const { return std::abs(m_start_corner(0) - m_end_corner(0)); } diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 9c1e936525e..5d2f16f4473 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -4,6 +4,7 @@ #include "3DScene.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/format.hpp" +#include "libslic3r/Color.hpp" #include #include @@ -206,6 +207,140 @@ void GLShaderProgram::stop_using() const glsafe(::glUseProgram(0)); } + +void GLShaderProgram::set_uniform(int id, int value) const +{ + if (id >= 0) + glsafe(::glUniform1i(id, value)); +} + +void GLShaderProgram::set_uniform(int id, bool value) const +{ + set_uniform(id, value ? 1 : 0); +} + +void GLShaderProgram::set_uniform(int id, float value) const +{ + if (id >= 0) + glsafe(::glUniform1f(id, value)); +} + +void GLShaderProgram::set_uniform(int id, double value) const +{ + set_uniform(id, static_cast(value)); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform2iv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform3iv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform4iv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + if (id >= 0) + glsafe(::glUniform4fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const std::array& value) const +{ + const std::array f_value = { float(value[0]), float(value[1]), float(value[2]), float(value[3]) }; + set_uniform(id, f_value); +} + +void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const +{ + if (id >= 0) { + if (size == 1) + set_uniform(id, value[0]); + else if (size == 2) + glsafe(::glUniform2fv(id, 1, static_cast(value))); + else if (size == 3) + glsafe(::glUniform3fv(id, 1, static_cast(value))); + else if (size == 4) + glsafe(::glUniform4fv(id, 1, static_cast(value))); + } +} + +void GLShaderProgram::set_uniform(int id, const Transform3f& value) const +{ + if (id >= 0) + glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.matrix().data()))); +} + +void GLShaderProgram::set_uniform(int id, const Transform3d& value) const +{ + set_uniform(id, value.cast()); +} + +void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const +{ + if (id >= 0) + glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Matrix3d& value) const +{ + set_uniform(id, (Matrix3f)value.cast()); +} + +void GLShaderProgram::set_uniform(int id, const Matrix4f& value) const +{ + if (id >= 0) + glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Matrix4d& value) const +{ + set_uniform(id, (Matrix4f)value.cast()); +} + +void GLShaderProgram::set_uniform(int id, const Vec2f& value) const +{ + if (id >= 0) + glsafe(::glUniform2fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Vec2d& value) const +{ + set_uniform(id, static_cast(value.cast())); +} + +void GLShaderProgram::set_uniform(int id, const Vec3f& value) const +{ + if (id >= 0) + glsafe(::glUniform3fv(id, 1, static_cast(value.data()))); +} + +void GLShaderProgram::set_uniform(int id, const Vec3d& value) const +{ + set_uniform(id, static_cast(value.cast())); +} + bool GLShaderProgram::set_uniform(const char* name, int value) const { int id = get_uniform_location(name); @@ -340,6 +475,9 @@ bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const } return false; } +void GLShaderProgram::set_uniform(const char *name, const Matrix3d &value) const { + set_uniform(get_uniform_location(name), value); +} bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const { @@ -355,7 +493,15 @@ bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const { return set_uniform(name, static_cast(value.cast())); } +void GLShaderProgram::set_uniform(int id, const ColorRGB& value) const +{ + set_uniform(id, value.data(), 3); +} +void GLShaderProgram::set_uniform(int id, const ColorRGBA& value) const +{ + set_uniform(id, value.data(), 4); +} int GLShaderProgram::get_attrib_location(const char* name) const { assert(m_id > 0); diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index d7b92000dfc..1c40cdcbf6e 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -9,6 +9,9 @@ namespace Slic3r { +class ColorRGB; +class ColorRGBA; + class GLShaderProgram { public: @@ -58,8 +61,38 @@ class GLShaderProgram bool set_uniform(const char* name, const Transform3f& value) const; bool set_uniform(const char* name, const Transform3d& value) const; bool set_uniform(const char* name, const Matrix3f& value) const; + void set_uniform(const char* name, const Matrix3d& value) const; bool set_uniform(const char* name, const Vec3f& value) const; bool set_uniform(const char* name, const Vec3d& value) const; + void set_uniform(const char* name, const ColorRGB& value) const { set_uniform(get_uniform_location(name), value); } + void set_uniform(const char* name, const ColorRGBA& value) const { set_uniform(get_uniform_location(name), value); } + + void set_uniform(int id, const ColorRGB& value) const; + void set_uniform(int id, const ColorRGBA& value) const; + void set_uniform(int id, int value) const; + void set_uniform(int id, bool value) const; + void set_uniform(int id, float value) const; + void set_uniform(int id, double value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const std::array& value) const; + void set_uniform(int id, const float* value, size_t size) const; + void set_uniform(int id, const Transform3f& value) const; + void set_uniform(int id, const Transform3d& value) const; + void set_uniform(int id, const Matrix3f& value) const; + void set_uniform(int id, const Matrix3d& value) const; + void set_uniform(int id, const Matrix4f& value) const; + void set_uniform(int id, const Matrix4d& value) const; + void set_uniform(int id, const Vec2f& value) const; + void set_uniform(int id, const Vec2d& value) const; + void set_uniform(int id, const Vec3f& value) const; + void set_uniform(int id, const Vec3d& value) const; + // void set_uniform(int id, const ColorRGB& value) const; + // void set_uniform(int id, const ColorRGBA& value) const; // returns -1 if not found int get_attrib_location(const char* name) const; diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 107fcb5627f..0508785da16 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -35,6 +35,7 @@ std::pair GLShadersManager::init() // used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" }); + valid &= append_shader("gouraud_light_legacy", { "gouraud_light_legacy.vs", "gouraud_light.fs" }); //used to render thumbnail valid &= append_shader("thumbnail", { "thumbnail.vs", "thumbnail.fs" }); // used to render first layer for calibration @@ -46,6 +47,7 @@ std::pair GLShadersManager::init() valid &= append_shader("gouraud_light_instanced", { "gouraud_light_instanced.vs", "gouraud_light_instanced.fs" }); // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); + valid &= append_shader("flat", { "flat.vs", "flat.fs" }); // used to render objects in 3d editor //if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 0)) { diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1f078392f15..3ed2069db80 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -5865,6 +5865,12 @@ Sidebar& GUI_App::sidebar() return plater_->sidebar(); } +GizmoObjectManipulation* GUI_App::obj_manipul() +{ + // If this method is called before plater_ has been initialized, return nullptr (to avoid a crash) + return (plater_ != nullptr) ? &plater_->get_view3D_canvas3D()->get_gizmos_manager().get_object_manipulation() : nullptr; +} + ObjectSettings* GUI_App::obj_settings() { return sidebar().obj_settings(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 44411b361c6..d72a217b93c 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -129,6 +129,7 @@ enum CameraMenuIDs { class Tab; class ConfigWizard; +class GizmoObjectManipulation; static wxString dots("...", wxConvUTF8); @@ -529,6 +530,7 @@ class GUI_App : public wxApp #endif /* __APPLE */ Sidebar& sidebar(); + GizmoObjectManipulation* obj_manipul(); ObjectSettings* obj_settings(); ObjectList* obj_list(); ObjectLayers* obj_layers(); diff --git a/src/slic3r/GUI/GUI_Geometry.cpp b/src/slic3r/GUI/GUI_Geometry.cpp new file mode 100644 index 00000000000..b0ed0e04fc5 --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.cpp @@ -0,0 +1,9 @@ +#include "libslic3r/libslic3r.h" +#include "GUI_Geometry.hpp" + +namespace Slic3r { +namespace GUI { + + +} // namespace Slic3r +} // namespace GUI diff --git a/src/slic3r/GUI/GUI_Geometry.hpp b/src/slic3r/GUI/GUI_Geometry.hpp new file mode 100644 index 00000000000..b18e4ae5a53 --- /dev/null +++ b/src/slic3r/GUI/GUI_Geometry.hpp @@ -0,0 +1,78 @@ +#ifndef slic3r_GUI_Geometry_hpp_ +#define slic3r_GUI_Geometry_hpp_ + +namespace Slic3r { +namespace GUI { + +enum class ECoordinatesType : unsigned char +{ + World, + Instance, + Local +}; + +class TransformationType +{ +public: + enum Enum { + // Transforming in a world coordinate system + World = 0, + // Transforming in a instance coordinate system + Instance = 1, + // Transforming in a local coordinate system + Local = 2, + // Absolute transformations, allowed in local coordinate system only. + Absolute = 0, + // Relative transformations, allowed in both local and world coordinate system. + Relative = 4, + // For group selection, the transformation is performed as if the group made a single solid body. + Joint = 0, + // For group selection, the transformation is performed on each object independently. + Independent = 8, + + World_Relative_Joint = World | Relative | Joint, + World_Relative_Independent = World | Relative | Independent, + Instance_Absolute_Joint = Instance | Absolute | Joint, + Instance_Absolute_Independent = Instance | Absolute | Independent, + Instance_Relative_Joint = Instance | Relative | Joint, + Instance_Relative_Independent = Instance | Relative | Independent, + Local_Absolute_Joint = Local | Absolute | Joint, + Local_Absolute_Independent = Local | Absolute | Independent, + Local_Relative_Joint = Local | Relative | Joint, + Local_Relative_Independent = Local | Relative | Independent, + }; + + TransformationType() : m_value(World) {} + TransformationType(Enum value) : m_value(value) {} + TransformationType& operator=(Enum value) { m_value = value; return *this; } + + Enum operator()() const { return m_value; } + bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } + + void set_world() { this->remove(Instance); this->remove(Local); } + void set_instance() { this->remove(Local); this->add(Instance); } + void set_local() { this->remove(Instance); this->add(Local); } + void set_absolute() { this->remove(Relative); } + void set_relative() { this->add(Relative); } + void set_joint() { this->remove(Independent); } + void set_independent() { this->add(Independent); } + + bool world() const { return !this->has(Instance) && !this->has(Local); } + bool instance() const { return this->has(Instance); } + bool local() const { return this->has(Local); } + bool absolute() const { return !this->has(Relative); } + bool relative() const { return this->has(Relative); } + bool joint() const { return !this->has(Independent); } + bool independent() const { return this->has(Independent); } + +private: + void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } + void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } + + Enum m_value; +}; + +} // namespace Slic3r +} // namespace GUI + +#endif // slic3r_GUI_Geometry_hpp_ diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp new file mode 100644 index 00000000000..0cc6416a84c --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -0,0 +1,1300 @@ +#include "GUI_ObjectManipulation.hpp" +#include "I18N.hpp" +#include "format.hpp" +#include "BitmapComboBox.hpp" + +#include "GLCanvas3D.hpp" +#include "OptionsGroup.hpp" +#include "GUI_App.hpp" +#include "wxExtensions.hpp" +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/Model.hpp" +#include "libslic3r/Geometry.hpp" +#include "Selection.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" +#include "MsgDialog.hpp" + +#include + +#include +#include "slic3r/Utils/FixModelByWin10.hpp" + +namespace Slic3r +{ +namespace GUI +{ + +const double ObjectManipulation::in_to_mm = 25.4; +const double ObjectManipulation::mm_to_in = 1 / ObjectManipulation::in_to_mm; + +// Helper function to be used by drop to bed button. Returns lowest point of this +// volume in world coordinate system. +static double get_volume_min_z(const GLVolume& volume) +{ + return volume.transformed_convex_hull_bounding_box().min.z(); +} + +static choice_ctrl* create_word_local_combo(wxWindow *parent) +{ + wxSize size(15 * wxGetApp().em_unit(), -1); + + choice_ctrl* temp = nullptr; +#ifdef __WXOSX__ + /* wxBitmapComboBox with wxCB_READONLY style return NULL for GetTextCtrl(), + * so ToolTip doesn't shown. + * Next workaround helps to solve this problem + */ + temp = new wxBitmapComboBox(); + temp->SetTextCtrlStyle(wxTE_READONLY); + temp->Create(parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr); +#else + temp = new choice_ctrl(parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxCB_READONLY | wxBORDER_SIMPLE); +#endif //__WXOSX__ + + temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); + if (!wxOSX) temp->SetBackgroundStyle(wxBG_STYLE_PAINT); + + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + temp->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); + temp->Select((int)ECoordinatesType::World); + + temp->SetToolTip(_L("Select coordinate space, in which the transformation will be performed.")); + return temp; +} + +void msw_rescale_word_local_combo(choice_ctrl* combo) +{ +#ifdef __WXOSX__ + const wxString selection = combo->GetString(combo->GetSelection()); + + /* To correct scaling (set new controll size) of a wxBitmapCombobox + * we need to refill control with new bitmaps. So, in our case : + * 1. clear control + * 2. add content + * 3. add scaled "empty" bitmap to the at least one item + */ + combo->Clear(); + wxSize size(wxDefaultSize); + size.SetWidth(15 * wxGetApp().em_unit()); + + // Set rescaled min height to correct layout + combo->SetMinSize(wxSize(-1, int(1.5f*combo->GetFont().GetPixelSize().y + 0.5f))); + // Set rescaled size + combo->SetSize(size); + + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::World)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Instance)); + combo->Append(ObjectManipulation::coordinate_type_str(ECoordinatesType::Local)); + + combo->SetValue(selection); +#else +#ifdef _WIN32 + combo->Rescale(); +#endif + combo->SetMinSize(wxSize(15 * wxGetApp().em_unit(), -1)); +#endif +} + +static void set_font_and_background_style(wxWindow* win, const wxFont& font) +{ + win->SetFont(font); + win->SetBackgroundStyle(wxBG_STYLE_PAINT); +} + +static const wxString axes_color_text[] = { "#990000", "#009900", "#000099" }; +static const wxString axes_color_back[] = { "#f5dcdc", "#dcf5dc", "#dcdcf5" }; + +ObjectManipulation::ObjectManipulation(wxWindow* parent) : + OG_Settings(parent, true) +{ + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_use_colors = wxGetApp().app_config->get("color_mapinulation_panel") == "1"; + + m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation"); + + // Load bitmaps to be used for the mirroring buttons: + m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on"); + + const int border = wxOSX ? 0 : 4; + const int em = wxGetApp().em_unit(); + m_main_grid_sizer = new wxFlexGridSizer(2, 3, 3); // "Name/label", "String name / Editors" + m_main_grid_sizer->SetFlexibleDirection(wxBOTH); + + // Add "Name" label with warning icon + auto sizer = new wxBoxSizer(wxHORIZONTAL); + + m_fix_throught_netfab_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + if (is_windows10()) + m_fix_throught_netfab_bitmap->Bind(wxEVT_CONTEXT_MENU, [this](wxCommandEvent& e) + { + // if object/sub-object has no errors + if (m_fix_throught_netfab_bitmap->GetBitmap().GetRefData() == wxNullBitmap.GetRefData()) + return; + + wxGetApp().obj_list()->fix_through_netfabb(); + update_warning_icon_state(wxGetApp().obj_list()->get_mesh_errors_info()); + }); + + sizer->Add(m_fix_throught_netfab_bitmap); + + auto name_label = new wxStaticText(m_parent, wxID_ANY, _L("Name")+":"); + set_font_and_background_style(name_label, wxGetApp().normal_font()); + name_label->SetToolTip(_L("Object name")); + sizer->Add(name_label); + + m_main_grid_sizer->Add(sizer); + + // Add name of the item + const wxSize name_size = wxSize(20 * em, wxDefaultCoord); + m_item_name = new wxStaticText(m_parent, wxID_ANY, "", wxDefaultPosition, name_size, wxST_ELLIPSIZE_MIDDLE); + set_font_and_background_style(m_item_name, wxGetApp().bold_font()); + + m_main_grid_sizer->Add(m_item_name, 0, wxEXPAND); + + // Add labels grid sizer + m_labels_grid_sizer = new wxFlexGridSizer(1, 3, 3); // "Name/label", "String name / Editors" + m_labels_grid_sizer->SetFlexibleDirection(wxBOTH); + + // Add world local combobox + m_word_local_combo = create_word_local_combo(parent); + m_word_local_combo->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent& evt) { this->set_coordinates_type(evt.GetString()); }), m_word_local_combo->GetId()); + + // Small trick to correct layouting in different view_mode : + // Show empty string of a same height as a m_word_local_combo, when m_word_local_combo is hidden + m_word_local_combo_sizer = new wxBoxSizer(wxHORIZONTAL); + m_empty_str = new wxStaticText(parent, wxID_ANY, ""); + m_word_local_combo_sizer->Add(m_word_local_combo); + m_word_local_combo_sizer->Add(m_empty_str); + m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); + m_labels_grid_sizer->Add(m_word_local_combo_sizer); + + // Text trick to grid sizer layout: + // Height of labels should be equivalent to the edit boxes + int height = wxTextCtrl(parent, wxID_ANY, "Br").GetBestHeight(-1); +#ifdef __WXGTK__ + // On Linux button with bitmap has bigger height then regular button or regular TextCtrl + // It can cause a wrong alignment on show/hide of a reset buttons + const int bmp_btn_height = ScalableButton(parent, wxID_ANY, "undo") .GetBestHeight(-1); + if (bmp_btn_height > height) + height = bmp_btn_height; +#endif //__WXGTK__ + + auto add_label = [this, height](wxStaticText** label, const std::string& name, wxSizer* reciver = nullptr) + { + *label = new wxStaticText(m_parent, wxID_ANY, _(name) + ":"); + set_font_and_background_style(*label, wxGetApp().normal_font()); + + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->SetMinSize(wxSize(-1, height)); + sizer->Add(*label, 0, wxALIGN_CENTER_VERTICAL); + + if (reciver) + reciver->Add(sizer); + else + m_labels_grid_sizer->Add(sizer); + + m_rescalable_sizers.push_back(sizer); + }; + + // Add labels + add_label(&m_move_Label, L("Position")); + add_label(&m_rotate_Label, L("Rotation")); + + // additional sizer for lock and labels "Scale" & "Size" + sizer = new wxBoxSizer(wxHORIZONTAL); + + m_lock_bnt = new LockButton(parent, wxID_ANY); + m_lock_bnt->Bind(wxEVT_BUTTON, [this](wxCommandEvent& event) { + event.Skip(); + wxTheApp->CallAfter([this]() { set_uniform_scaling(m_lock_bnt->IsLocked()); }); + }); + sizer->Add(m_lock_bnt, 0, wxALIGN_CENTER_VERTICAL); + + auto v_sizer = new wxGridSizer(1, 3, 3); + + add_label(&m_scale_Label, L("Scale"), v_sizer); + wxStaticText* size_Label {nullptr}; + add_label(&size_Label, L("Size [World]"), v_sizer); + if (wxOSX) set_font_and_background_style(size_Label, wxGetApp().normal_font()); + + sizer->Add(v_sizer, 0, wxLEFT, border); + m_labels_grid_sizer->Add(sizer); + m_main_grid_sizer->Add(m_labels_grid_sizer, 0, wxEXPAND); + + + // Add editors grid sizer + wxFlexGridSizer* editors_grid_sizer = new wxFlexGridSizer(5, 3, 3); // "Name/label", "String name / Editors" + editors_grid_sizer->SetFlexibleDirection(wxBOTH); + + // Add Axes labels with icons + static const char axes[] = { 'X', 'Y', 'Z' }; +// std::vector axes_color = {"#EE0000", "#00EE00", "#0000EE"}; + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) { + const char label = axes[axis_idx]; + + wxStaticText* axis_name = new wxStaticText(m_parent, wxID_ANY, wxString(label)); + set_font_and_background_style(axis_name, wxGetApp().bold_font()); + //if (m_use_colors) + // axis_name->SetForegroundColour(wxColour(axes_color_text[axis_idx])); + + sizer = new wxBoxSizer(wxHORIZONTAL); + // Under OSX or Linux with GTK3 we use font, smaller than default font, so + // there is a next trick for an equivalent layout of coordinates combobox and axes labels in they own sizers + // if (wxOSX || wxGTK3) + sizer->SetMinSize(-1, m_word_local_combo->GetBestHeight(-1)); + sizer->Add(axis_name, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); + + // We will add a button to toggle mirroring to each axis: + auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW); + btn->SetToolTip(format_wxstr(_L("Mirror along %1% axis"), label)); + m_mirror_buttons[axis_idx] = btn; + + sizer->AddStretchSpacer(2); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL); + + btn->Bind(wxEVT_BUTTON, [this, axis_idx](wxCommandEvent&) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + transformation_type.set_relative(); + + selection.setup_cache(); + selection.mirror((Axis)axis_idx); + + // Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_mirror(L("Set Mirror")); + UpdateAndShow(true); + }); + + editors_grid_sizer->Add(sizer, 0, wxALIGN_CENTER_HORIZONTAL); + } + + m_mirror_warning_bitmap = new wxStaticBitmap(parent, wxID_ANY, wxNullBitmap); + editors_grid_sizer->Add(m_mirror_warning_bitmap, 0, wxALIGN_CENTER_VERTICAL); + editors_grid_sizer->AddStretchSpacer(1); + + // add EditBoxes + auto add_edit_boxes = [this, editors_grid_sizer](const std::string& opt_key, int axis) + { + ManipulationEditor* editor = new ManipulationEditor(this, opt_key, axis); + m_editors.push_back(editor); + + editors_grid_sizer->Add(editor, 0, wxALIGN_CENTER_VERTICAL); + }; + + // add Units + auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit, wxStaticText** unit_text) + { + *unit_text = new wxStaticText(parent, wxID_ANY, _(unit)); + set_font_and_background_style(*unit_text, wxGetApp().normal_font()); + + // Unit text should be the same height as labels + wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->SetMinSize(wxSize(-1, height)); + sizer->Add(*unit_text, 0, wxALIGN_CENTER_VERTICAL); + + editors_grid_sizer->Add(sizer); + m_rescalable_sizers.push_back(sizer); + }; + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("position", axis_idx); + add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_position_unit); + + // Add drop to bed button + m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); + m_drop_to_bed_button->SetToolTip(_L("Drop to bed")); + m_drop_to_bed_button->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) { + // ??? + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + if (selection.is_single_volume_or_modifier()) { + const GLVolume* volume = selection.get_first_volume(); + const double min_z = get_volume_min_z(*volume); + if (!is_world_coordinates()) { + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } + } + else if (selection.is_single_full_instance()) { + const double min_z = selection.get_scaled_instance_bounding_box().min.z(); + if (!is_world_coordinates()) { + const GLVolume* volume = selection.get_first_volume(); + const Vec3d diff = m_cache.position - volume->get_instance_transformation().get_matrix_no_offset().inverse() * (min_z * Vec3d::UnitZ()); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(0, diff.x()); + change_position_value(1, diff.y()); + change_position_value(2, diff.z()); + } + else { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } + } + }); + editors_grid_sizer->Add(m_drop_to_bed_button); + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("rotation", axis_idx); + wxStaticText* rotation_unit{ nullptr }; + add_unit_text("°", &rotation_unit); + + // Add reset rotation button + m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_rotation_button->SetToolTip(_L("Reset rotation")); + m_reset_rotation_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + selection.setup_cache(); + if (selection.is_single_volume_or_modifier()) { + GLVolume* vol = const_cast(selection.get_first_volume()); + Geometry::Transformation trafo = vol->get_volume_transformation(); + trafo.reset_rotation(); + vol->set_volume_transformation(trafo); + } + else if (selection.is_single_full_instance()) { + Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation(); + trafo.reset_rotation(); + for (unsigned int idx : selection.get_volume_idxs()) { + const_cast(selection.get_volume(idx))->set_instance_transformation(trafo); + } + } + else + return; + + // Synchronize instances/volumes. + + selection.synchronize_unselected_instances(Selection::SyncRotationType::RESET); + selection.synchronize_unselected_volumes(); + + // Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing. + canvas->do_rotate(L("Reset Rotation")); + + UpdateAndShow(true); + }); + editors_grid_sizer->Add(m_reset_rotation_button); + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("scale", axis_idx); + wxStaticText* scale_unit{ nullptr }; + add_unit_text("%", &scale_unit); + + // Add reset scale button + m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_scale_button->SetToolTip(_L("Reset scale")); + m_reset_scale_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + selection.setup_cache(); + if (selection.is_single_volume_or_modifier()) { + GLVolume* vol = const_cast(selection.get_first_volume()); + Geometry::Transformation trafo = vol->get_volume_transformation(); + trafo.reset_scaling_factor(); + vol->set_volume_transformation(trafo); + } + else if (selection.is_single_full_instance()) { + Geometry::Transformation trafo = selection.get_first_volume()->get_instance_transformation(); + trafo.reset_scaling_factor(); + for (unsigned int idx : selection.get_volume_idxs()) { + const_cast(selection.get_volume(idx))->set_instance_transformation(trafo); + } + } + else + return; + + // Synchronize instances/volumes. + selection.synchronize_unselected_instances(Selection::SyncRotationType::GENERAL); + selection.synchronize_unselected_volumes(); + + canvas->do_scale(L("Reset scale")); + UpdateAndShow(true); + }); + editors_grid_sizer->Add(m_reset_scale_button); + + for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) + add_edit_boxes("size", axis_idx); + add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_size_unit); + editors_grid_sizer->AddStretchSpacer(1); + + m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); + + m_skew_label = new wxStaticText(parent, wxID_ANY, _L("Skew [World]")); + m_main_grid_sizer->Add(m_skew_label, 1, wxEXPAND); + + m_reset_skew_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); + m_reset_skew_button->SetToolTip(_L("Reset skew")); + m_reset_skew_button->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) { + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + selection.setup_cache(); + selection.reset_skew(); + canvas->do_reset_skew(L("Reset skew")); + UpdateAndShow(true); + } + }); + m_main_grid_sizer->Add(m_reset_skew_button); + + m_check_inch = new wxCheckBox(parent, wxID_ANY, _L("Inches")); + m_check_inch->SetFont(wxGetApp().normal_font()); + + m_check_inch->SetValue(m_imperial_units); + m_check_inch->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + wxGetApp().app_config->set("use_inches", m_check_inch->GetValue() ? "1" : "0"); + wxGetApp().sidebar().update_ui_from_settings(); + }); + + m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND); + + m_og->activate(); + m_og->sizer->Clear(true); + m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border); +} + +void ObjectManipulation::Show(const bool show) +{ + if (show != IsShown()) { + // Show all lines of the panel. Some of these lines will be hidden in the lines below. + m_og->Show(show); + + if (show && wxGetApp().get_mode() != comSimple) { + // Show the label and the name of the STL in simple mode only. + // Label "Name: " + m_main_grid_sizer->Show(size_t(0), false); + // The actual name of the STL. + m_main_grid_sizer->Show(size_t(1), false); + } + } + + if (show) { + ECoordinatesType coordinates_type = m_coordinates_type; + + // Show the "World Coordinates" / "Local Coordintes" Combo in Advanced / Expert mode only. + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + bool show_world_local_combo = wxGetApp().get_mode() != comSimple && (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()); + if (selection.is_single_volume_or_modifier() && m_word_local_combo->GetCount() < 3) { +#ifdef __linux__ + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), 2); +#else + m_word_local_combo->Insert(coordinate_type_str(ECoordinatesType::Local), wxNullBitmap, 2); +#endif // __linux__ + } + else if (selection.is_single_full_instance() && m_word_local_combo->GetCount() > 2) { + m_word_local_combo->Delete(2); + if (coordinates_type > ECoordinatesType::Instance) + coordinates_type = ECoordinatesType::World; + } + m_word_local_combo->Show(show_world_local_combo); + m_empty_str->Show(!show_world_local_combo); + } +} + +bool ObjectManipulation::IsShown() +{ + return dynamic_cast(m_og->sizer)->GetStaticBox()->IsShown(); // m_og->get_grid_sizer()->IsShown(2); +} + +void ObjectManipulation::UpdateAndShow(const bool show) +{ + if (show) { + this->set_dirty(); + this->update_if_dirty(); + } + + OG_Settings::UpdateAndShow(show); +} + +void ObjectManipulation::Enable(const bool enable) +{ + m_is_enabled = m_is_enabled_size_and_scale = enable; + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_reset_rotation_button, m_drop_to_bed_button, m_check_inch, m_lock_bnt + , m_reset_skew_button }) + win->Enable(enable); +} + +void ObjectManipulation::DisableScale() +{ + m_is_enabled = true; + m_is_enabled_size_and_scale = false; + for (wxWindow* win : std::initializer_list{ m_reset_scale_button, m_lock_bnt, m_reset_skew_button }) + win->Enable(false); +} + +void ObjectManipulation::DisableUnuniformScale() +{ + m_lock_bnt->Enable(false); +} + +void ObjectManipulation::update_ui_from_settings() +{ + if (m_imperial_units != wxGetApp().app_config->get_bool("use_inches")) { + m_imperial_units = wxGetApp().app_config->get_bool("use_inches"); + + auto update_unit_text = [](const wxString& new_unit_text, wxStaticText* widget) { + widget->SetLabel(new_unit_text); + if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font()); + }; + update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_position_unit); + update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_size_unit); + + for (int i = 0; i < 3; ++i) { + auto update = [this, i](/*ManipulationEditorKey*/int key_id, const Vec3d& new_value) { + double value = new_value(i); + if (m_imperial_units) + value *= mm_to_in; + wxString new_text = double_to_string(value, m_imperial_units && key_id == 3/*meSize*/ ? 4 : 2); + const int id = key_id * 3 + i; + if (id >= 0) m_editors[id]->set_value(new_text); + }; + update(0/*mePosition*/, m_new_position); + update(3/*meSize*/, m_new_size); + } + } + m_check_inch->SetValue(m_imperial_units); + + if (m_use_colors != wxGetApp().app_config->get_bool("color_mapinulation_panel")) { + m_use_colors = wxGetApp().app_config->get_bool("color_mapinulation_panel"); + // update colors for edit-boxes + int axis_id = 0; + for (ManipulationEditor* editor : m_editors) { +// editor->SetForegroundColour(m_use_colors ? wxColour(axes_color_text[axis_id]) : wxGetApp().get_label_clr_default()); + if (m_use_colors) { + editor->SetBackgroundColour(wxColour(axes_color_back[axis_id])); + if (wxGetApp().dark_mode()) + editor->SetForegroundColour(*wxBLACK); + } + else { +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(editor); +#else + editor->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + editor->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); +#endif /* _WIN32 */ + } + editor->Refresh(); + if (++axis_id == 3) + axis_id = 0; + } + } +} + +void ObjectManipulation::update_settings_value(const Selection& selection) +{ + if (selection.is_empty()) { + // No selection, reset the cache. + reset_settings_value(); + return; + } + + m_new_move_label_string = L("Position"); + m_new_rotate_label_string = L("Rotation"); + m_new_scale_label_string = L("Scale factors"); + + ObjectList* obj_list = wxGetApp().obj_list(); + if (selection.is_single_full_instance()) { + // all volumes in the selection belongs to the same instance, any of them contains the needed instance data, so we take the first one + const GLVolume* volume = selection.get_first_volume(); + + if (is_world_coordinates()) { + m_new_position = volume->get_instance_offset(); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_scale = m_new_size.cwiseQuotient(selection.get_unscaled_instance_bounding_box().size()) * 100.0; + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_rotation = Vec3d::Zero(); + } + else { + m_new_move_label_string = L("Translate (relative) [World]"); + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_position = Vec3d::Zero(); + m_new_rotation = Vec3d::Zero(); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_scale = m_new_size.cwiseQuotient(selection.get_full_unscaled_instance_local_bounding_box().size()) * 100.0; + } + + m_new_enabled = true; + } + else if (selection.is_single_full_object() && obj_list->is_selected(itObject)) { + const BoundingBoxf3& box = selection.get_bounding_box(); + m_new_position = box.center(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_rotate_label_string = L("Rotate"); + m_new_scale_label_string = L("Scale"); + m_new_enabled = true; + } + else if (selection.is_single_volume_or_modifier()) { + // the selection contains a single volume + const GLVolume* volume = selection.get_first_volume(); + if (is_world_coordinates()) { + const Geometry::Transformation trafo(volume->world_matrix()); + + const Vec3d& offset = trafo.get_offset(); + + m_new_position = offset; + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_scale_label_string = L("Scale"); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_rotation = Vec3d::Zero(); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + } + else if (is_local_coordinates()) { + m_new_move_label_string = L("Translate (relative) [World]"); + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_position = Vec3d::Zero(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = volume->get_volume_scaling_factor() * 100.0; + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + } + else { + m_new_position = volume->get_volume_offset(); + m_new_rotate_label_string = L("Rotate (relative)"); + m_new_rotation = Vec3d::Zero(); + m_new_scale_label_string = L("Scale"); + m_new_scale = Vec3d(100.0, 100.0, 100.0); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + } + m_new_enabled = true; + } + else if (obj_list->is_connectors_item_selected() || obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) { + reset_settings_value(); + m_new_move_label_string = L("Translate"); + m_new_rotate_label_string = L("Rotate"); + m_new_scale_label_string = L("Scale"); + m_new_size = selection.get_bounding_box_in_current_reference_system().first.size(); + m_new_enabled = true; + } +} + +void ObjectManipulation::update_if_dirty() +{ + if (! m_dirty) + return; + + const Selection &selection = wxGetApp().plater()->canvas3D()->get_selection(); + this->update_settings_value(selection); + + auto update_label = [](wxString &label_cache, const std::string &new_label, wxStaticText *widget) { + wxString new_label_localized = _(new_label) + ":"; + if (label_cache != new_label_localized) { + label_cache = new_label_localized; + widget->SetLabel(new_label_localized); + if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font()); + } + }; + update_label(m_cache.move_label_string, m_new_move_label_string, m_move_Label); + update_label(m_cache.rotate_label_string, m_new_rotate_label_string, m_rotate_Label); + update_label(m_cache.scale_label_string, m_new_scale_label_string, m_scale_Label); + + enum ManipulationEditorKey + { + mePosition = 0, + meRotation, + meScale, + meSize + }; + + for (int i = 0; i < 3; ++ i) { + auto update = [this, i](Vec3d &cached, Vec3d &cached_rounded, ManipulationEditorKey key_id, const Vec3d &new_value) { + wxString new_text = double_to_string(new_value(i), m_imperial_units && key_id == meSize ? 4 : 2); + double new_rounded; + new_text.ToDouble(&new_rounded); + if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) { + cached_rounded(i) = new_rounded; + const int id = key_id*3+i; + if (m_imperial_units) { + double inch_value = new_value(i) * mm_to_in; + if (key_id == mePosition) + new_text = double_to_string(inch_value, 2); + if (key_id == meSize) { + if(std::abs(m_cache.size_inches(i) - inch_value) > EPSILON) + m_cache.size_inches(i) = inch_value; + new_text = double_to_string(inch_value, 4); + } + } + if (id >= 0) m_editors[id]->set_value(new_text); + } + cached(i) = new_value(i); + }; + update(m_cache.position, m_cache.position_rounded, mePosition, m_new_position); + update(m_cache.scale, m_cache.scale_rounded, meScale, m_new_scale); + update(m_cache.size, m_cache.size_rounded, meSize, m_new_size); + update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); + } + + m_lock_bnt->SetLock(m_uniform_scale); + m_lock_bnt->SetToolTip(wxEmptyString); + m_lock_bnt->enable(); + + if (m_new_enabled) + m_og->enable(); + else + m_og->disable(); + + if (!wxGetApp().plater()->canvas3D()->is_dragging()) { + update_reset_buttons_visibility(); + update_mirror_buttons_visibility(); + } + + m_dirty = false; +} + +void ObjectManipulation::update_reset_buttons_visibility() +{ + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + if (!canvas) + return; + + bool show_drop_to_bed = false; + bool show_rotation = false; + bool show_scale = false; + bool show_mirror = false; + bool show_skew = false; + + const Selection& selection = canvas->get_selection(); + if (selection.is_single_full_instance() || selection.is_single_volume_or_modifier()) { + const double min_z = selection.is_single_full_instance() ? selection.get_scaled_instance_bounding_box().min.z() : + get_volume_min_z(*selection.get_first_volume()); + + show_drop_to_bed = std::abs(min_z) > EPSILON; + const GLVolume* volume = selection.get_first_volume(); + const Geometry::Transformation trafo = selection.is_single_full_instance() ? volume->get_instance_transformation() : volume->get_volume_transformation(); + + const Geometry::TransformationSVD trafo_svd(trafo); + show_rotation = trafo_svd.rotation; + show_scale = trafo_svd.scale; + show_mirror = trafo_svd.mirror; + show_skew = Geometry::TransformationSVD(volume->world_matrix()).skew; + } + + wxGetApp().CallAfter([this, show_drop_to_bed, show_rotation, show_scale, show_mirror, show_skew] { + // There is a case (under OSX), when this function is called after the Manipulation panel is hidden + // So, let check if Manipulation panel is still shown for this moment + if (!this->IsShown()) + return; + m_drop_to_bed_button->Show(show_drop_to_bed); + m_reset_rotation_button->Show(show_rotation); + m_reset_scale_button->Show(show_scale); + m_mirror_warning_bitmap->SetBitmap(show_mirror ? m_manifold_warning_bmp.bmp() : wxNullBitmap); + m_mirror_warning_bitmap->SetMinSize(show_mirror ? m_manifold_warning_bmp.GetSize() : wxSize(0, 0)); + m_mirror_warning_bitmap->SetToolTip(show_mirror ? _L("Left handed") : ""); + m_reset_skew_button->Show(show_skew); + m_skew_label->Show(show_skew); + + // Because of CallAfter we need to layout sidebar after Show/hide of reset buttons one more time + Sidebar& panel = wxGetApp().sidebar(); + if (!panel.IsFrozen()) { + panel.Freeze(); + panel.Layout(); + panel.Thaw(); + } + }); +} + +void ObjectManipulation::update_mirror_buttons_visibility() +{ + const bool can_mirror = wxGetApp().plater()->can_mirror(); + for (ScalableButton* button : m_mirror_buttons) { + button->Enable(can_mirror); + } +} + + + + +#ifndef __APPLE__ +void ObjectManipulation::emulate_kill_focus() +{ + if (!m_focused_editor) + return; + + m_focused_editor->kill_focus(this); +} +#endif // __APPLE__ + +void ObjectManipulation::update_item_name(const wxString& item_name) +{ + m_item_name->SetLabel(item_name); +} + +void ObjectManipulation::update_warning_icon_state(const MeshErrorsInfo& warning) +{ + if (const std::string& warning_icon_name = warning.warning_icon_name; + !warning_icon_name.empty()) + m_manifold_warning_bmp = ScalableBitmap(m_parent, warning_icon_name); + const wxString& tooltip = warning.tooltip; + m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0,0) : m_manifold_warning_bmp.GetSize()); + m_fix_throught_netfab_bitmap->SetToolTip(tooltip); +} + +wxString ObjectManipulation::coordinate_type_str(ECoordinatesType type) +{ + switch (type) + { + case ECoordinatesType::World: { return _L("World coordinates"); } + case ECoordinatesType::Instance: { return _L("Object coordinates"); } + case ECoordinatesType::Local: { return _L("Part coordinates"); } + default: { assert(false); return _L("Unknown"); } + } +} + +#if ENABLE_OBJECT_MANIPULATION_DEBUG +void ObjectManipulation::render_debug_window() +{ + ImGuiWrapper& imgui = *wxGetApp().imgui(); +// ImGui::SetNextWindowCollapsed(true, ImGuiCond_Once); + imgui.begin(std::string("ObjectManipulation"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize); + imgui.text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Coordinates type"); + ImGui::SameLine(); + imgui.text(coordinate_type_str(m_coordinates_type)); + imgui.end(); +} +#endif // ENABLE_OBJECT_MANIPULATION_DEBUG + +void ObjectManipulation::reset_settings_value() +{ + m_new_position = Vec3d::Zero(); + m_new_rotation = Vec3d::Zero(); + m_new_scale = Vec3d::Ones() * 100.; + m_new_size = Vec3d::Zero(); + m_new_enabled = false; + // no need to set the dirty flag here as this method is called from update_settings_value(), + // which is called from update_if_dirty(), which resets the dirty flag anyways. +// m_dirty = true; +} + +void ObjectManipulation::change_position_value(int axis, double value) +{ + if (std::abs(m_cache.position_rounded(axis) - value) < EPSILON) + return; + + Vec3d position = m_cache.position; + position(axis) = value; + + auto canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + selection.setup_cache(); + TransformationType trafo_type; + trafo_type.set_relative(); + switch (get_coordinates_type()) + { + case ECoordinatesType::Instance: { trafo_type.set_instance(); break; } + case ECoordinatesType::Local: { trafo_type.set_local(); break; } + default: { break; } + } + selection.translate(position - m_cache.position, trafo_type); + canvas->do_move(L("Set Position")); + + m_cache.position = position; + m_cache.position_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); +} + +void ObjectManipulation::change_rotation_value(int axis, double value) +{ + if (std::abs(m_cache.rotation_rounded(axis) - value) < EPSILON) + return; + + Vec3d rotation = m_cache.rotation; + rotation(axis) = value; + + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + Selection& selection = canvas->get_selection(); + + TransformationType transformation_type; + transformation_type.set_relative(); + if (selection.is_single_full_instance()) + transformation_type.set_independent(); + + if (is_local_coordinates()) + transformation_type.set_local(); + + if (is_instance_coordinates()) + transformation_type.set_instance(); + + selection.setup_cache(); + selection.rotate( + (M_PI / 180.0) * (transformation_type.absolute() ? rotation : rotation - m_cache.rotation), + transformation_type); + canvas->do_rotate(L("Set Orientation")); + + m_cache.rotation = rotation; + m_cache.rotation_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); +} + +void ObjectManipulation::change_scale_value(int axis, double value) +{ + if (value <= 0.0) + return; + + if (std::abs(m_cache.scale_rounded(axis) - value) < EPSILON) + return; + + Vec3d scale = m_cache.scale; + scale(axis) = value; + + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + Vec3d ref_scale = m_cache.scale; + if (selection.is_single_volume_or_modifier()) { + scale = scale.cwiseQuotient(ref_scale); + ref_scale = Vec3d::Ones(); + } + else if (selection.is_single_full_instance()) + ref_scale = 100.0 * Vec3d::Ones(); + + this->do_scale(axis, scale.cwiseQuotient(ref_scale)); + + m_cache.scale = scale; + m_cache.scale_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); +} + + +void ObjectManipulation::change_size_value(int axis, double value) +{ + if (value <= 0.0) + return; + + if (m_imperial_units) { + if (std::abs(m_cache.size_inches(axis) - value) < EPSILON) + return; + m_cache.size_inches(axis) = value; + value *= in_to_mm; + } + + if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON) + return; + + Vec3d size = m_cache.size; + size(axis) = value; + + const Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + Vec3d ref_size = m_cache.size; + if (selection.is_single_volume_or_modifier()) { + size = size.cwiseQuotient(ref_size); + ref_size = Vec3d::Ones(); + } + else if (selection.is_single_full_instance()) { + if (is_world_coordinates()) + ref_size = selection.get_full_unscaled_instance_bounding_box().size(); + else + ref_size = selection.get_full_unscaled_instance_local_bounding_box().size(); + } + + this->do_size(axis, size.cwiseQuotient(ref_size)); + + m_cache.size = size; + m_cache.size_rounded(axis) = DBL_MAX; + this->UpdateAndShow(true); +} + +void ObjectManipulation::do_scale(int axis, const Vec3d &scale) const +{ + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + if (selection.is_single_volume_or_modifier() && !is_local_coordinates()) + transformation_type.set_relative(); + + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + selection.setup_cache(); + selection.scale(scaling_factor, transformation_type); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Scale")); +} + +void ObjectManipulation::do_size(int axis, const Vec3d& scale) const +{ + Selection& selection = wxGetApp().plater()->canvas3D()->get_selection(); + + TransformationType transformation_type; + if (is_local_coordinates()) + transformation_type.set_local(); + else if (is_instance_coordinates()) + transformation_type.set_instance(); + + const Vec3d scaling_factor = m_uniform_scale ? scale(axis) * Vec3d::Ones() : scale; + selection.setup_cache(); + selection.scale(scaling_factor, transformation_type); + wxGetApp().plater()->canvas3D()->do_scale(L("Set Size")); +} + +void ObjectManipulation::on_change(const std::string& opt_key, int axis, double new_value) +{ + if (!m_cache.is_valid()) + return; + + if (opt_key == "position") { + if (m_imperial_units) + new_value *= in_to_mm; + change_position_value(axis, new_value); + } + else if (opt_key == "rotation") + change_rotation_value(axis, new_value); + else if (opt_key == "scale") { + if (new_value > 0.0) + change_scale_value(axis, new_value); + else { + new_value = m_cache.scale(axis); + m_cache.scale(axis) = 0.0; + m_cache.scale_rounded(axis) = DBL_MAX; + change_scale_value(axis, new_value); + } + } + else if (opt_key == "size") { + if (new_value > 0.0) + change_size_value(axis, new_value); + else { + Vec3d& size = m_imperial_units ? m_cache.size_inches : m_cache.size; + new_value = size(axis); + size(axis) = 0.0; + m_cache.size_rounded(axis) = DBL_MAX; + change_size_value(axis, new_value); + } + } +} + +void ObjectManipulation::set_uniform_scaling(const bool use_uniform_scale) +{ + if (!use_uniform_scale) + // Recalculate cached values at this panel, refresh the screen. + this->UpdateAndShow(true); + + m_uniform_scale = use_uniform_scale; + set_dirty(); +} + +void ObjectManipulation::set_coordinates_type(ECoordinatesType type) +{ + if (wxGetApp().get_mode() == comSimple) + type = ECoordinatesType::World; + + if (m_coordinates_type == type) + return; + + m_coordinates_type = type; + m_word_local_combo->SetSelection((int)m_coordinates_type); + this->UpdateAndShow(true); + GLCanvas3D* canvas = wxGetApp().plater()->canvas3D(); + canvas->get_gizmos_manager().update_data(); + canvas->set_as_dirty(); + canvas->request_extra_frame(); +} + +ECoordinatesType ObjectManipulation::get_coordinates_type() const +{ + return m_coordinates_type; +} + +void ObjectManipulation::msw_rescale() +{ + const int em = wxGetApp().em_unit(); + m_item_name->SetMinSize(wxSize(20*em, wxDefaultCoord)); + msw_rescale_word_local_combo(m_word_local_combo); + m_word_local_combo_sizer->SetMinSize(wxSize(-1, m_word_local_combo->GetBestHeight(-1))); + + const wxString& tooltip = m_fix_throught_netfab_bitmap->GetToolTipText(); + m_fix_throught_netfab_bitmap->SetBitmap(tooltip.IsEmpty() ? wxNullBitmap : m_manifold_warning_bmp.bmp()); + m_fix_throught_netfab_bitmap->SetMinSize(tooltip.IsEmpty() ? wxSize(0, 0) : m_manifold_warning_bmp.GetSize()); + + // rescale label-heights + // Text trick to grid sizer layout: + // Height of labels should be equivalent to the edit boxes + const int height = wxTextCtrl(parent(), wxID_ANY, "Br").GetBestHeight(-1); + for (wxBoxSizer* sizer : m_rescalable_sizers) + sizer->SetMinSize(wxSize(-1, height)); + + // rescale edit-boxes + for (ManipulationEditor* editor : m_editors) + editor->msw_rescale(); + + // rescale "inches" checkbox + m_check_inch->SetInitialSize(m_check_inch->GetBestSize()); + + get_og()->msw_rescale(); +} + +void ObjectManipulation::sys_color_changed() +{ +#ifdef _WIN32 + get_og()->sys_color_changed(); + wxGetApp().UpdateDarkUI(m_word_local_combo); + wxGetApp().UpdateDarkUI(m_check_inch); +#endif + for (ManipulationEditor* editor : m_editors) + editor->sys_color_changed(this); + + m_mirror_bitmap_on.sys_color_changed(); + m_reset_scale_button->sys_color_changed(); + m_reset_rotation_button->sys_color_changed(); + m_drop_to_bed_button->sys_color_changed(); + m_lock_bnt->sys_color_changed(); + + for (int id = 0; id < 3; ++id) { + m_mirror_buttons[id]->sys_color_changed(); + } +} + +void ObjectManipulation::set_coordinates_type(const wxString& type_string) +{ + if (type_string == coordinate_type_str(ECoordinatesType::Instance)) + this->set_coordinates_type(ECoordinatesType::Instance); + else if (type_string == coordinate_type_str(ECoordinatesType::Local)) + this->set_coordinates_type(ECoordinatesType::Local); + else + this->set_coordinates_type(ECoordinatesType::World); +} + +static const char axes[] = { 'x', 'y', 'z' }; +ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, + const std::string& opt_key, + int axis) : + wxTextCtrl(parent->parent(), wxID_ANY, wxEmptyString, wxDefaultPosition, + wxSize((wxOSX ? 5 : 6)*int(wxGetApp().em_unit()), wxDefaultCoord), wxTE_PROCESS_ENTER +#ifdef _WIN32 + | wxBORDER_SIMPLE +#endif + ), + m_opt_key(opt_key), + m_axis(axis) +{ + set_font_and_background_style(this, wxGetApp().normal_font()); +#ifdef __WXOSX__ + this->OSXDisableAllSmartSubstitutions(); +#endif // __WXOSX__ + if (parent->use_colors()) { + this->SetBackgroundColour(wxColour(axes_color_back[axis])); + this->SetForegroundColour(*wxBLACK); + } else { + wxGetApp().UpdateDarkUI(this); + } + + // A name used to call handle_sidebar_focus_event() + m_full_opt_name = m_opt_key+"_"+axes[axis]; + + // Reset m_enter_pressed flag to _false_, when value is editing + this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId()); + + this->Bind(wxEVT_TEXT_ENTER, [this, parent](wxEvent&) + { + m_enter_pressed = true; + parent->on_change(m_opt_key, m_axis, get_value()); + }, this->GetId()); + + this->Bind(wxEVT_KILL_FOCUS, [this, parent](wxFocusEvent& e) + { + parent->set_focused_editor(nullptr); + + if (!m_enter_pressed) + kill_focus(parent); + + e.Skip(); + }, this->GetId()); + + this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e) + { + parent->set_focused_editor(this); + + // needed to show the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, true); + e.Skip(); + }, this->GetId()); + + this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event) + { + // select all text using Ctrl+A + if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL)) + this->SetSelection(-1, -1); //select all + event.Skip(); + })); + + this->Bind(wxEVT_UPDATE_UI, [parent, this](wxUpdateUIEvent& evt) { + const bool is_gizmo_in_editing_mode = wxGetApp().plater()->canvas3D()->get_gizmos_manager().is_in_editing_mode(); + const bool is_enabled_editing = has_opt_key("scale") || has_opt_key("size") ? parent->is_enabled_size_and_scale() : true; + evt.Enable(!is_gizmo_in_editing_mode && parent->is_enabled() && is_enabled_editing); + }); +} + +void ManipulationEditor::msw_rescale() +{ + const int em = wxGetApp().em_unit(); + SetMinSize(wxSize(5 * em, wxDefaultCoord)); +} + +void ManipulationEditor::sys_color_changed(ObjectManipulation* parent) +{ + if (parent->use_colors()) + SetForegroundColour(*wxBLACK); + else +#ifdef _WIN32 + wxGetApp().UpdateDarkUI(this); +#else + SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); +#endif // _WIN32 +} + +double ManipulationEditor::get_value() +{ + wxString str = GetValue(); + + double value; + const char dec_sep = is_decimal_separator_point() ? '.' : ','; + const char dec_sep_alt = dec_sep == '.' ? ',' : '.'; + // Replace the first incorrect separator in decimal number. + if (str.Replace(dec_sep_alt, dec_sep, false) != 0) + SetValue(str); + + if (str == ".") + value = 0.0; + + if ((str.IsEmpty() || !str.ToDouble(&value)) && !m_valid_value.IsEmpty()) { + str = m_valid_value; + SetValue(str); + str.ToDouble(&value); + } + + return value; +} + +void ManipulationEditor::set_value(const wxString& new_value) +{ + if (new_value.IsEmpty()) + return; + m_valid_value = new_value; + SetValue(m_valid_value); +} + +void ManipulationEditor::kill_focus(ObjectManipulation* parent) +{ + parent->on_change(m_opt_key, m_axis, get_value()); + + // if the change does not come from the user pressing the ENTER key + // we need to hide the visual hints in 3D scene + wxGetApp().plater()->canvas3D()->handle_sidebar_focus_event(m_full_opt_name, false); +} + +} //namespace GUI +} //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp new file mode 100644 index 00000000000..139171d999a --- /dev/null +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -0,0 +1,250 @@ +#ifndef slic3r_GUI_ObjectManipulation_hpp_ +#define slic3r_GUI_ObjectManipulation_hpp_ + +#include + +#include "GUI_ObjectSettings.hpp" +#include "GUI_ObjectList.hpp" +#include "GUI_Geometry.hpp" +#include "libslic3r/Point.hpp" +#include + +#ifdef __WXOSX__ +class wxBitmapComboBox; +#else +class wxComboBox; +#endif // __WXOSX__ +class wxStaticText; +class LockButton; +class wxStaticBitmap; +class wxCheckBox; + +namespace Slic3r { +namespace GUI { + +#ifdef _WIN32 +class BitmapComboBox; +#endif + +#ifdef __WXOSX__ + static_assert(wxMAJOR_VERSION >= 3, "Use of wxBitmapComboBox on Manipulation panel requires wxWidgets 3.0 and newer"); + using choice_ctrl = wxBitmapComboBox; +#else +#ifdef _WIN32 + using choice_ctrl = BitmapComboBox; +#else + using choice_ctrl = wxComboBox; +#endif +#endif // __WXOSX__ + +class Selection; + +class ObjectManipulation; +class ManipulationEditor : public wxTextCtrl +{ + std::string m_opt_key; + int m_axis; + bool m_enter_pressed { false }; + wxString m_valid_value {wxEmptyString}; + + std::string m_full_opt_name; + +public: + ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis); + ~ManipulationEditor() {} + + void msw_rescale(); + void sys_color_changed(ObjectManipulation* parent); + void set_value(const wxString& new_value); + void kill_focus(ObjectManipulation *parent); + + const std::string& get_full_opt_name() const { return m_full_opt_name; } + + bool has_opt_key(const std::string& key) { return m_opt_key == key; } + +private: + double get_value(); +}; + + +class ObjectManipulation : public OG_Settings +{ +public: + static const double in_to_mm; + static const double mm_to_in; + +private: + struct Cache + { + Vec3d position; + Vec3d position_rounded; + Vec3d rotation; + Vec3d rotation_rounded; + Vec3d scale; + Vec3d scale_rounded; + Vec3d size; + Vec3d size_inches; + Vec3d size_rounded; + + wxString move_label_string; + wxString rotate_label_string; + wxString scale_label_string; + + Cache() { reset(); } + void reset() + { + position = position_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + rotation = rotation_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + scale = scale_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + size = size_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + move_label_string = wxString(); + rotate_label_string = wxString(); + scale_label_string = wxString(); + } + bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); } + }; + + Cache m_cache; + + wxStaticText* m_move_Label = nullptr; + wxStaticText* m_scale_Label = nullptr; + wxStaticText* m_rotate_Label = nullptr; + + bool m_imperial_units { false }; + bool m_use_colors { false }; + wxStaticText* m_position_unit { nullptr }; + wxStaticText* m_size_unit { nullptr }; + + wxStaticText* m_item_name = nullptr; + wxStaticText* m_empty_str = nullptr; + + // Non-owning pointers to the reset buttons, so we can hide and show them. + ScalableButton* m_reset_scale_button{ nullptr }; + ScalableButton* m_reset_rotation_button{ nullptr }; + ScalableButton* m_reset_skew_button{ nullptr }; + ScalableButton* m_drop_to_bed_button{ nullptr }; + + wxCheckBox* m_check_inch {nullptr}; + + std::array m_mirror_buttons; + + // Bitmaps for the mirroring buttons. + ScalableBitmap m_mirror_bitmap_on; + + // Needs to be updated from OnIdle? + bool m_dirty = false; + // Cached labels for the delayed update, not localized! + std::string m_new_move_label_string; + std::string m_new_rotate_label_string; + std::string m_new_scale_label_string; + Vec3d m_new_position; + Vec3d m_new_rotation; + Vec3d m_new_scale; + Vec3d m_new_size; + bool m_new_enabled {true}; + bool m_uniform_scale {true}; + ECoordinatesType m_coordinates_type{ ECoordinatesType::World }; + LockButton* m_lock_bnt{ nullptr }; + choice_ctrl* m_word_local_combo { nullptr }; + + ScalableBitmap m_manifold_warning_bmp; + wxStaticBitmap* m_fix_throught_netfab_bitmap{ nullptr }; + wxStaticBitmap* m_mirror_warning_bitmap{ nullptr }; + + // Currently focused editor (nullptr if none) + ManipulationEditor* m_focused_editor{ nullptr }; + + wxFlexGridSizer* m_main_grid_sizer; + wxFlexGridSizer* m_labels_grid_sizer; + + wxStaticText* m_skew_label{ nullptr }; + + // sizers, used for msw_rescale + wxBoxSizer* m_word_local_combo_sizer; + std::vector m_rescalable_sizers; + + std::vector m_editors; + + // parameters for enabling/disabling of editors + bool m_is_enabled { true }; + bool m_is_enabled_size_and_scale { true }; + + +public: + ObjectManipulation(wxWindow* parent); + ~ObjectManipulation() {} + + void Show(const bool show) override; + bool IsShown() override; + void UpdateAndShow(const bool show) override; + void Enable(const bool enadle = true); + void Disable() { Enable(false); } + void DisableScale(); + void DisableUnuniformScale(); + void update_ui_from_settings(); + bool use_colors() { return m_use_colors; } + + void set_dirty() { m_dirty = true; } + // Called from the App to update the UI if dirty. + void update_if_dirty(); + + void set_uniform_scaling(const bool use_uniform_scale); + bool get_uniform_scaling() const { return m_uniform_scale; } + + void set_coordinates_type(ECoordinatesType type); + ECoordinatesType get_coordinates_type() const; + bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; } + bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; } + bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; } + + void reset_cache() { m_cache.reset(); } +#ifndef __APPLE__ + // On Windows and Linux, emulates a kill focus event on the currently focused option (if any) + // Used only in ObjectList wxEVT_DATAVIEW_SELECTION_CHANGED handler which is called before the regular kill focus event + // bound to this class when changing selection in the objects list + void emulate_kill_focus(); +#endif // __APPLE__ + + void update_item_name(const wxString &item_name); + void update_warning_icon_state(const MeshErrorsInfo& warning); + void msw_rescale(); + void sys_color_changed(); + void on_change(const std::string& opt_key, int axis, double new_value); + void set_focused_editor(ManipulationEditor* focused_editor) { + m_focused_editor = focused_editor; + } + + ManipulationEditor* get_focused_editor() { return m_focused_editor; } + + static wxString coordinate_type_str(ECoordinatesType type); + + bool is_enabled() const { return m_is_enabled; } + bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; } + +#if ENABLE_OBJECT_MANIPULATION_DEBUG + void render_debug_window(); +#endif // ENABLE_OBJECT_MANIPULATION_DEBUG + +private: + void reset_settings_value(); + void update_settings_value(const Selection& selection); + + // Show or hide scale/rotation reset buttons if needed + void update_reset_buttons_visibility(); + //Show or hide mirror buttons + void update_mirror_buttons_visibility(); + + // change values + void change_position_value(int axis, double value); + void change_rotation_value(int axis, double value); + void change_scale_value(int axis, double value); + void change_size_value(int axis, double value); + void do_scale(int axis, const Vec3d &scale) const; + void do_size(int axis, const Vec3d& scale) const; + + void set_coordinates_type(const wxString& type_string); +}; + +}} + +#endif // slic3r_GUI_ObjectManipulation_hpp_ diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index bf2c4277b88..f0c3d30a6fb 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -501,6 +501,16 @@ bool generate_image(const std::string &filename, wxImage &image, wxSize img_size int get_dpi_for_window(const wxWindow *window); +class KeyAutoRepeatFilter +{ + size_t m_count{ 0 }; + +public: + void increase_count() { ++m_count; } + void reset_count() { m_count = 0; } + bool is_first() const { return m_count == 0; } +}; + }} #endif diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp index ba978050134..89640471c0e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.cpp @@ -28,31 +28,12 @@ const float UndefFloat = -999.f; // connector colors -using ColorRGBA = std::array; - -static const ColorRGBA BLACK() { return {0.0f, 0.0f, 0.0f, 1.0f}; } -static const ColorRGBA BLUE() { return {0.0f, 0.0f, 1.0f, 1.0f}; } -static const ColorRGBA BLUEISH() { return {0.5f, 0.5f, 1.0f, 1.0f}; } -static const ColorRGBA CYAN() { return {0.0f, 1.0f, 1.0f, 1.0f}; } -static const ColorRGBA DARK_GRAY() { return {0.25f, 0.25f, 0.25f, 1.0f}; } -static const ColorRGBA DARK_YELLOW() { return {0.5f, 0.5f, 0.0f, 1.0f}; } -static const ColorRGBA GRAY() { return {0.5f, 0.5f, 0.5f, 1.0f}; } -static const ColorRGBA GREEN() { return {0.0f, 1.0f, 0.0f, 1.0f}; } -static const ColorRGBA GREENISH() { return {0.5f, 1.0f, 0.5f, 1.0f}; } -static const ColorRGBA LIGHT_GRAY() { return {0.75f, 0.75f, 0.75f, 1.0f}; } -static const ColorRGBA MAGENTA() { return {1.0f, 0.0f, 1.0f, 1.0f}; } -static const ColorRGBA ORANGE() { return {0.923f, 0.504f, 0.264f, 1.0f}; } -static const ColorRGBA RED() { return {1.0f, 0.0f, 0.0f, 1.0f}; } -static const ColorRGBA REDISH() { return {1.0f, 0.5f, 0.5f, 1.0f}; } -static const ColorRGBA YELLOW() { return {1.0f, 1.0f, 0.0f, 1.0f}; } -static const ColorRGBA WHITE() { return {1.0f, 1.0f, 1.0f, 1.0f}; } - -static const ColorRGBA PLAG_COLOR = YELLOW(); -static const ColorRGBA DOWEL_COLOR = DARK_YELLOW(); -static const ColorRGBA HOVERED_PLAG_COLOR = CYAN(); +static const ColorRGBA PLAG_COLOR = ColorRGBA::YELLOW(); +static const ColorRGBA DOWEL_COLOR = ColorRGBA::DARK_YELLOW(); +static const ColorRGBA HOVERED_PLAG_COLOR = ColorRGBA::CYAN(); static const ColorRGBA HOVERED_DOWEL_COLOR = {0.0f, 0.5f, 0.5f, 1.0f}; -static const ColorRGBA SELECTED_PLAG_COLOR = GRAY(); -static const ColorRGBA SELECTED_DOWEL_COLOR = GRAY(); // DARK_GRAY(); +static const ColorRGBA SELECTED_PLAG_COLOR = ColorRGBA::GRAY(); +static const ColorRGBA SELECTED_DOWEL_COLOR = ColorRGBA::GRAY(); // DARK_GRAY(); static const ColorRGBA CONNECTOR_DEF_COLOR = {1.0f, 1.0f, 1.0f, 0.5f}; static const ColorRGBA CONNECTOR_ERR_COLOR = {1.0f, 0.3f, 0.3f, 0.5f}; static const ColorRGBA HOVERED_ERR_COLOR = {1.0f, 0.3f, 0.3f, 1.0f}; @@ -512,62 +493,6 @@ void GLGizmoAdvancedCut::on_render() render_cut_line(); } -void GLGizmoAdvancedCut::on_render_for_picking() -{ - GLGizmoRotate3D::on_render_for_picking(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - BoundingBoxf3 box = m_parent.get_selection().get_bounding_box(); -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - - std::array color = picking_color_component(0); - m_move_grabber.color[0] = color[0]; - m_move_grabber.color[1] = color[1]; - m_move_grabber.color[2] = color[2]; - m_move_grabber.color[3] = color[3]; - m_move_grabber.render_for_picking(mean_size); - - glsafe(::glEnable(GL_DEPTH_TEST)); - auto inst_id = m_c->selection_info()->get_active_instance(); - if (inst_id < 0) - return; - - const ModelObject *mo = m_c->selection_info()->model_object(); - const ModelInstance *mi = mo->instances[inst_id]; - const Vec3d & instance_offset = mi->get_offset(); - const double sla_shift = double(m_c->selection_info()->get_sla_shift()); - - const CutConnectors &connectors = mo->cut_connectors; - for (int i = 0; i < connectors.size(); ++i) { - CutConnector connector = connectors[i]; - Vec3d pos = connector.pos + instance_offset + sla_shift * Vec3d::UnitZ(); - float height = connector.height; - - const Camera &camera = wxGetApp().plater()->get_camera(); - if (connector.attribs.type == CutConnectorType::Dowel && connector.attribs.style == CutConnectorStyle::Prizm) { - pos -= height * m_cut_plane_normal; - height *= 2; - } else if (!is_looking_forward()) - pos -= 0.05 * m_cut_plane_normal; - - Transform3d translate_tf = Transform3d::Identity(); - translate_tf.translate(pos); - - Transform3d scale_tf = Transform3d::Identity(); - scale_tf.scale(Vec3f(connector.radius, connector.radius, height).cast()); - - const Transform3d view_model_matrix = translate_tf * m_rotate_matrix * scale_tf; - - - std::array color = picking_color_component(i+1); - render_connector_model(m_shapes[connectors[i].attribs], color, view_model_matrix, true); - } -} void GLGizmoAdvancedCut::on_render_input_window(float x, float y, float bottom_limit) { @@ -926,23 +851,24 @@ void GLGizmoAdvancedCut::render_cut_plane_and_grabbers() else render_color = GrabberColor; - const GLModel &cube = m_move_grabber.get_cube(); - // BBS set to fixed size grabber - // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; - } + // Orca todo + // GLModel &cube = m_move_grabber.get_cube(); + // // BBS set to fixed size grabber + // // float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); + // float fullsize = 8.0f; + // if (GLGizmoBase::INV_ZOOM > 0) { + // fullsize = m_move_grabber.FixedGrabberSize * GLGizmoBase::INV_ZOOM; + // } - const_cast(&cube)->set_color(-1, render_color); + // cube.set_color(render_color); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z())); - glsafe(::glMultMatrixd(m_rotate_matrix.data())); + // glsafe(::glPushMatrix()); + // glsafe(::glTranslated(m_move_grabber.center.x(), m_move_grabber.center.y(), m_move_grabber.center.z())); + // glsafe(::glMultMatrixd(m_rotate_matrix.data())); - glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); - glsafe(::glPopMatrix()); + // glsafe(::glScaled(fullsize, fullsize, fullsize)); + // cube.render(); + // glsafe(::glPopMatrix()); // Should be placed at last, because GLGizmoRotate3D clears depth buffer set_center(m_cut_plane_center); @@ -1009,7 +935,7 @@ void GLGizmoAdvancedCut::render_connectors() const Transform3d view_model_matrix = translate_tf * m_rotate_matrix * scale_tf; - render_connector_model(m_shapes[connector.attribs], render_color, view_model_matrix); + render_connector_model(m_shapes[connector.attribs], render_color.data_array(), view_model_matrix); } } @@ -1049,7 +975,7 @@ void GLGizmoAdvancedCut::render_connector_model(GLModel &model, const std::array glsafe(::glMultMatrixd(view_model_matrix.data())); - model.set_color(-1, color); + model.set_color(color); model.render(); shader->stop_using(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp index 5cb9e765cc0..e1aafbbf26e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp @@ -147,7 +147,6 @@ struct Rotate_data { virtual void on_stop_dragging() override; virtual void on_update(const UpdateData& data); virtual void on_render(); - virtual void on_render_for_picking(); virtual void on_render_input_window(float x, float y, float bottom_limit); void show_tooltip_information(float x, float y); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index b1d98d3dc2f..fed1f275dcf 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_Colors.hpp" +#include "slic3r/GUI/Plater.hpp" // TODO: Display tooltips quicker on Linux @@ -13,14 +14,6 @@ namespace GUI { float GLGizmoBase::INV_ZOOM = 1.0f; - -const float GLGizmoBase::Grabber::SizeFactor = 0.05f; -const float GLGizmoBase::Grabber::MinHalfSize = 4.0f; -const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; -const float GLGizmoBase::Grabber::FixedGrabberSize = 16.0f; -const float GLGizmoBase::Grabber::FixedRadiusSize = 80.0f; - - std::array GLGizmoBase::DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f }; std::array GLGizmoBase::DEFAULT_DRAG_COLOR = { 1.0f, 1.0f, 1.0f, 1.0f }; std::array GLGizmoBase::DEFAULT_HIGHLIGHT_COLOR = { 1.0f, 0.38f, 0.0f, 1.0f }; @@ -67,27 +60,20 @@ void GLGizmoBase::load_render_colors() RenderColor::colors[RenderCol_Flatten_Plane] = IMColor(GLGizmoBase::FLATTEN_COLOR); RenderColor::colors[RenderCol_Flatten_Plane_Hover] = IMColor(GLGizmoBase::FLATTEN_HOVER_COLOR); } +const float GLGizmoBase::Grabber::SizeFactor = 0.05f; +const float GLGizmoBase::Grabber::MinHalfSize = 1.5f; +const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f; -GLGizmoBase::Grabber::Grabber() - : center(Vec3d::Zero()) - , angles(Vec3d::Zero()) - , dragging(false) - , enabled(true) -{ - color = GRABBER_NORMAL_COL; - hover_color = GRABBER_HOVER_COL; -} +PickingModel GLGizmoBase::Grabber::s_cube; +PickingModel GLGizmoBase::Grabber::s_cone; -void GLGizmoBase::Grabber::render(bool hover, float size) const +GLGizmoBase::Grabber::~Grabber() { - std::array render_color; - if (hover) { - render_color = hover_color; - } - else - render_color = color; + if (s_cube.model.is_initialized()) + s_cube.model.reset(); - render(size, render_color, false); + if (s_cone.model.is_initialized()) + s_cone.model.reset(); } float GLGizmoBase::Grabber::get_half_size(float size) const @@ -100,51 +86,115 @@ float GLGizmoBase::Grabber::get_dragging_half_size(float size) const return get_half_size(size) * DraggingScaleFactor; } -const GLModel& GLGizmoBase::Grabber::get_cube() const +void GLGizmoBase::Grabber::register_raycasters_for_picking(int id) { - if (! cube_initialized) { - // This cannot be done in constructor, OpenGL is not yet - // initialized at that point (on Linux at least). - indexed_triangle_set mesh = its_make_cube(1., 1., 1.); - its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); - const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; - } - return cube; + picking_id = id; + // registration will happen on next call to render() } -void GLGizmoBase::Grabber::render(float size, const std::array& render_color, bool picking) const +void GLGizmoBase::Grabber::unregister_raycasters_for_picking() { - if (! cube_initialized) { + wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, picking_id); + picking_id = -1; + raycasters = { nullptr }; +} + +void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color) +{ + GLShaderProgram* shader = wxGetApp().get_current_shader(); + if (shader == nullptr) + return; + + if (!s_cube.model.is_initialized()) { // This cannot be done in constructor, OpenGL is not yet // initialized at that point (on Linux at least). - indexed_triangle_set mesh = its_make_cube(1., 1., 1.); - its_translate(mesh, Vec3f(-0.5, -0.5, -0.5)); - const_cast(cube).init_from(mesh, BoundingBoxf3{ { -0.5, -0.5, -0.5 }, { 0.5, 0.5, 0.5 } }); - const_cast(cube_initialized) = true; + indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0); + its_translate(its, -0.5f * Vec3f::Ones()); + s_cube.model.init_from(its); + s_cube.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } - //BBS set to fixed size grabber - //float fullsize = 2 * (dragging ? get_dragging_half_size(size) : get_half_size(size)); - float fullsize = 8.0f; - if (GLGizmoBase::INV_ZOOM > 0) { - fullsize = FixedGrabberSize * GLGizmoBase::INV_ZOOM; + if (!s_cone.model.is_initialized()) { + indexed_triangle_set its = its_make_cone(0.375, 1.5, double(PI) / 18.0); + s_cone.model.init_from(its); + s_cone.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); } + const float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size); + + s_cube.model.set_color(render_color); + s_cone.model.set_color(render_color); + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Transform3d& view_matrix = camera.get_view_matrix(); + const Matrix3d view_matrix_no_offset = view_matrix.matrix().block(0, 0, 3, 3); + std::vector elements_matrices(GRABBER_ELEMENTS_MAX_COUNT, Transform3d::Identity()); + elements_matrices[0] = matrix * Geometry::translation_transform(center) * Geometry::rotation_transform(angles) * Geometry::scale_transform(2.0 * half_size); + Transform3d view_model_matrix = view_matrix * elements_matrices[0]; + + shader->set_uniform("view_model_matrix", view_model_matrix); + Matrix3d view_normal_matrix = view_matrix_no_offset * elements_matrices[0].matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + s_cube.model.render(); + + auto render_extension = [&view_matrix, &view_matrix_no_offset, shader](const Transform3d& matrix) { + const Transform3d view_model_matrix = view_matrix * matrix; + shader->set_uniform("view_model_matrix", view_model_matrix); + const Matrix3d view_normal_matrix = view_matrix_no_offset * matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + s_cone.model.render(); + }; + + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) { + elements_matrices[1] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, 0.5 * double(PI), 0.0 }); + render_extension(elements_matrices[1]); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) { + elements_matrices[2] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, -0.5 * double(PI), 0.0 }); + render_extension(elements_matrices[2]); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) { + elements_matrices[3] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitY()) * Geometry::rotation_transform({ -0.5 * double(PI), 0.0, 0.0 }); + render_extension(elements_matrices[3]); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) { + elements_matrices[4] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitY()) * Geometry::rotation_transform({ 0.5 * double(PI), 0.0, 0.0 }); + render_extension(elements_matrices[4]); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) { + elements_matrices[5] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitZ()); + render_extension(elements_matrices[5]); + } + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) { + elements_matrices[6] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitZ()) * Geometry::rotation_transform({ double(PI), 0.0, 0.0 }); + render_extension(elements_matrices[6]); + } - const_cast(&cube)->set_color(-1, render_color); - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(center.x(), center.y(), center.z())); - glsafe(::glRotated(Geometry::rad2deg(angles.z()), 0.0, 0.0, 1.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.y()), 0.0, 1.0, 0.0)); - glsafe(::glRotated(Geometry::rad2deg(angles.x()), 1.0, 0.0, 0.0)); - glsafe(::glScaled(fullsize, fullsize, fullsize)); - cube.render(); - glsafe(::glPopMatrix()); + if (raycasters[0] == nullptr) { + GLCanvas3D& canvas = *wxGetApp().plater()->canvas3D(); + raycasters[0] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cube.mesh_raycaster, elements_matrices[0]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) + raycasters[1] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[1]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) + raycasters[2] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[2]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) + raycasters[3] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[3]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) + raycasters[4] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[4]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) + raycasters[5] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[5]); + if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) + raycasters[6] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[6]); + } + else { + for (size_t i = 0; i < GRABBER_ELEMENTS_MAX_COUNT; ++i) { + if (raycasters[i] != nullptr) + raycasters[i]->set_transform(elements_matrices[i]); + } + } } - GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : m_parent(parent) , m_group_id(-1) @@ -238,7 +288,19 @@ bool GLGizmoBase::update_items_state() m_dirty = false; return res; }; +void GLGizmoBase::register_grabbers_for_picking() +{ + for (size_t i = 0; i < m_grabbers.size(); ++i) { + m_grabbers[i].register_raycasters_for_picking((m_group_id >= 0) ? m_group_id : i); + } +} +void GLGizmoBase::unregister_grabbers_for_picking() +{ + for (size_t i = 0; i < m_grabbers.size(); ++i) { + m_grabbers[i].unregister_raycasters_for_picking(); + } +} bool GLGizmoBase::GizmoImguiBegin(const std::string &name, int flags) { return m_imgui->begin(name, flags); @@ -265,64 +327,33 @@ void GLGizmoBase::GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float m_imgui->set_next_window_pos(x, y, flag, pivot_x, pivot_y); } -std::array GLGizmoBase::picking_color_component(unsigned int id) const -{ - static const float INV_255 = 1.0f / 255.0f; - - id = BASE_ID - id; - - if (m_group_id > -1) - id -= m_group_id; - - // color components are encoded to match the calculation of volume_id made into GLCanvas3D::_picking_pass() - return std::array { - float((id >> 0) & 0xff) * INV_255, // red - float((id >> 8) & 0xff) * INV_255, // green - float((id >> 16) & 0xff) * INV_255, // blue - float(picking_checksum_alpha_channel(id & 0xff, (id >> 8) & 0xff, (id >> 16) & 0xff))* INV_255 // checksum for validating against unwanted alpha blending and multi sampling - }; -} void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const { -#if ENABLE_FIXED_GRABBER - render_grabbers((float)(GLGizmoBase::Grabber::FixedGrabberSize)); -#else render_grabbers((float)((box.size().x() + box.size().y() + box.size().z()) / 3.0)); -#endif } void GLGizmoBase::render_grabbers(float size) const +{ + render_grabbers(0, m_grabbers.size() - 1, size, false); +} + +void GLGizmoBase::render_grabbers(size_t first, size_t last, float size, bool force_hover) const { GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); if (shader == nullptr) return; shader->start_using(); shader->set_uniform("emission_factor", 0.1f); - for (int i = 0; i < (int)m_grabbers.size(); ++i) { + glsafe(::glDisable(GL_CULL_FACE)); + for (size_t i = first; i <= last; ++i) { if (m_grabbers[i].enabled) - m_grabbers[i].render(m_hover_id == i, size); + m_grabbers[i].render(force_hover ? true : m_hover_id == (int)i, size); } + glsafe(::glEnable(GL_CULL_FACE)); shader->stop_using(); } -void GLGizmoBase::render_grabbers_for_picking(const BoundingBoxf3& box) const -{ -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - - for (unsigned int i = 0; i < (unsigned int)m_grabbers.size(); ++i) { - if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; - m_grabbers[i].render_for_picking(mean_size); - } - } -} - std::string GLGizmoBase::format(float value, unsigned int decimals) const { return Slic3r::string_printf("%.*f", decimals, value); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index c725809bbae..48f2d688de1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/SceneRaycaster.hpp" #include @@ -34,6 +35,20 @@ class GLGizmoBase // Starting value for ids to avoid clashing with ids used by GLVolumes // (254 is choosen to leave some space for forward compatibility) static const unsigned int BASE_ID = 255 * 255 * 254; + static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7; + + enum class EGrabberExtension { + None = 0, + PosX = 1 << 0, + NegX = 1 << 1, + PosY = 1 << 2, + NegY = 1 << 3, + PosZ = 1 << 4, + NegZ = 1 << 5, + }; + + // Represents NO key(button on keyboard) value + static const int NO_SHORTCUT_KEY_VALUE = 0; static float INV_ZOOM; @@ -60,30 +75,35 @@ class GLGizmoBase static const float SizeFactor; static const float MinHalfSize; static const float DraggingScaleFactor; - static const float FixedGrabberSize; - static const float FixedRadiusSize; - Vec3d center; - Vec3d angles; - std::array color; - std::array hover_color; - bool enabled; - bool dragging; + bool enabled{true}; + bool dragging{false}; + Vec3d center{Vec3d::Zero()}; + Vec3d angles{Vec3d::Zero()}; + Transform3d matrix{Transform3d::Identity()}; + ColorRGBA color{ColorRGBA::WHITE()}; + ColorRGBA hover_color{ColorRGBA::ORCA()}; + EGrabberExtension extensions{EGrabberExtension::None}; + // the picking id shared by all the elements + int picking_id{-1}; + std::array, GRABBER_ELEMENTS_MAX_COUNT> raycasters = {nullptr}; - Grabber(); + Grabber() = default; + ~Grabber(); - void render(bool hover, float size) const; - void render_for_picking(float size) const { render(size, color, true); } + void render(bool hover, float size) { render(size, hover ? complementary(color) : color); } float get_half_size(float size) const; float get_dragging_half_size(float size) const; - const GLModel& get_cube() const; + + void register_raycasters_for_picking(int id); + void unregister_raycasters_for_picking(); private: - void render(float size, const std::array& render_color, bool picking) const; + void render(float size, const ColorRGBA &render_color); - GLModel cube; - bool cube_initialized = false; + static PickingModel s_cube; + static PickingModel s_cone; }; public: @@ -159,7 +179,7 @@ class GLGizmoBase virtual bool wants_enter_leave_snapshots() const { return false; } virtual std::string get_gizmo_entering_text() const { assert(false); return ""; } virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; } - virtual std::string get_action_snapshot_name() { return "Gizmo action"; } + virtual std::string get_action_snapshot_name() const { return "Gizmo action"; } void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; } virtual bool apply_clipping_plane() { return true; } @@ -185,12 +205,30 @@ class GLGizmoBase bool update_items_state(); void render() { m_tooltip.clear(); on_render(); } - void render_for_picking() { on_render_for_picking(); } void render_input_window(float x, float y, float bottom_limit); virtual void on_change_color_mode(bool is_dark) { m_is_dark_mode = is_dark; } + /// + /// Mouse tooltip text + /// + /// Text to be visible in mouse tooltip virtual std::string get_tooltip() const { return ""; } + /// + /// Is called when data (Selection) is changed + /// + virtual void data_changed(bool is_serializing){}; + + /// + /// Implement when want to process mouse events in gizmo + /// Click, Right click, move, drag, ... + /// + /// Keep information about mouse click + /// Return True when use the information and don't want to propagate it otherwise False. + virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; } + void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); } + void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); } + int get_count() { return ++count; } std::string get_gizmo_name() { return on_get_name(); } @@ -211,18 +249,21 @@ class GLGizmoBase virtual void on_stop_dragging() {} virtual void on_update(const UpdateData& data) {} virtual void on_render() = 0; - virtual void on_render_for_picking() = 0; virtual void on_render_input_window(float x, float y, float bottom_limit) {} + void register_grabbers_for_picking(); + void unregister_grabbers_for_picking(); + virtual void on_register_raycasters_for_picking() {} + virtual void on_unregister_raycasters_for_picking() {} bool GizmoImguiBegin(const std::string& name, int flags); void GizmoImguiEnd(); void GizmoImguiSetNextWIndowPos(float &x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f); // Returns the picking color for the given id, based on the BASE_ID constant // No check is made for clashing with other picking color (i.e. GLVolumes) - std::array picking_color_component(unsigned int id) const; - void render_grabbers(const BoundingBoxf3& box) const; + + void render_grabbers(const BoundingBoxf3 &box) const; void render_grabbers(float size) const; - void render_grabbers_for_picking(const BoundingBoxf3& box) const; + void render_grabbers(size_t first, size_t last, float size, bool force_hover) const; std::string format(float value, unsigned int decimals) const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index d5008725e4d..9b44acab281 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -146,12 +146,6 @@ void GLGizmoCut::on_render() glsafe(::glPopMatrix()); } -void GLGizmoCut::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit) { //static float last_y = 0.0f; @@ -328,7 +322,7 @@ void GLGizmoCut::update_contours() const Polygons polys = slice_mesh(m_cut_contours.mesh.its, m_cut_z, slicing_params); if (!polys.empty()) { m_cut_contours.contours.init_from(polys, static_cast(m_cut_z)); - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); + m_cut_contours.contours.set_color({ 1.0f, 1.0f, 1.0f, 1.0f }); } } else if (box.center() != m_cut_contours.position) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp index 0d745fe3cb2..87ae89a052b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.hpp @@ -60,7 +60,6 @@ class GLGizmoCut : public GLGizmoBase virtual void on_start_dragging() override; virtual void on_update(const UpdateData& data) override; virtual void on_render() override; - virtual void on_render_for_picking() override; virtual void on_render_input_window(float x, float y, float bottom_limit) override; private: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp index c20cf4c2090..2cfed8d8370 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp @@ -16,7 +16,6 @@ class GLGizmoFaceDetector : public GLGizmoBase protected: void on_render() override; - void on_render_for_picking() override {} void on_render_input_window(float x, float y, float bottom_limit) override; std::string on_get_name() const override; void on_set_state() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index c095c0915e0..1fbbf0ec37f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -105,7 +105,7 @@ bool GLGizmoFdmSupports::on_init() return true; } -void GLGizmoFdmSupports::render_painter_gizmo() const +void GLGizmoFdmSupports::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index 6960a81dc62..c24fe578589 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -15,7 +15,7 @@ class GLGizmoFdmSupports : public GLGizmoPainterBase public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void render_painter_gizmo() const override; + void render_painter_gizmo() override; //BBS: add edit state enum EditState { @@ -39,7 +39,7 @@ class GLGizmoFdmSupports : public GLGizmoPainterBase std::string get_gizmo_entering_text() const override { return "Entering Paint-on supports"; } std::string get_gizmo_leaving_text() const override { return "Leaving Paint-on supports"; } - std::string get_action_snapshot_name() override { return "Paint-on supports editing"; } + std::string get_action_snapshot_name() const override { return "Paint-on supports editing"; } // BBS wchar_t m_current_tool = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 4ad8136bbaa..efe83ff4fe1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,6 +1,12 @@ -// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš MatÄ›na @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01, VojtÄ›ch Bubník @bubnikv +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" #include "libslic3r/Geometry/ConvexHull.hpp" @@ -13,17 +19,48 @@ namespace Slic3r { namespace GUI { +static const Slic3r::ColorRGBA DEFAULT_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.5f }; +static const Slic3r::ColorRGBA DEFAULT_HOVER_PLANE_COLOR = { 0.9f, 0.9f, 0.9f, 0.75f }; GLGizmoFlatten::GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) - , m_normal(Vec3d::Zero()) - , m_starting_center(Vec3d::Zero()) +{} + +bool GLGizmoFlatten::on_mouse(const wxMouseEvent &mouse_event) +{ + if (mouse_event.LeftDown()) { + if (m_hover_id != -1) { + Selection &selection = m_parent.get_selection(); + if (selection.is_single_full_instance()) { + // Rotate the object so the normal points downward: + selection.flattening_rotate(m_planes[m_hover_id].normal); + m_parent.do_rotate(L("Gizmo-Place on Face")); + wxGetApp().obj_manipul()->set_dirty(); + } + return true; + } + } + else if (mouse_event.LeftUp()) + return m_hover_id != -1; + + return false; +} + +void GLGizmoFlatten::data_changed(bool is_serializing) { + const Selection & selection = m_parent.get_selection(); + const ModelObject *model_object = nullptr; + int instance_id = -1; + if (selection.is_single_full_instance() || + selection.is_from_single_object() ) { + model_object = selection.get_model()->objects[selection.get_object_idx()]; + instance_id = selection.get_instance_idx(); + } + set_flattening_data(model_object, instance_id); } bool GLGizmoFlatten::on_init() { - // BBS m_shortcut_key = WXK_CONTROL_F; return true; } @@ -39,7 +76,7 @@ CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const std::string GLGizmoFlatten::on_get_name() const { - return _u8L("Lay on face"); + return _u8L("Place on face"); } bool GLGizmoFlatten::on_is_activable() const @@ -49,77 +86,73 @@ bool GLGizmoFlatten::on_is_activable() const return m_parent.get_selection().is_single_full_instance(); } -void GLGizmoFlatten::on_start_dragging() -{ - if (m_hover_id != -1) { - assert(m_planes_valid); - m_normal = m_planes[m_hover_id].normal; - m_starting_center = m_parent.get_selection().get_bounding_box().center(); - } -} - void GLGizmoFlatten::on_render() { const Selection& selection = m_parent.get_selection(); + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_BLEND)); if (selection.is_single_full_instance()) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); + const Transform3d& inst_matrix = selection.get_first_volume()->get_instance_transformation().get_matrix(); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d model_matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst_matrix; + const Transform3d view_model_matrix = camera.get_view_matrix() * model_matrix; + + shader->set_uniform("view_model_matrix", view_model_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); if (this->is_plane_update_necessary()) update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - if (i == m_hover_id) - glsafe(::glColor4fv(GLGizmoBase::FLATTEN_HOVER_COLOR.data())); - else - glsafe(::glColor4fv(GLGizmoBase::FLATTEN_COLOR.data())); - - if (m_planes[i].vbo.has_VBOs()) - m_planes[i].vbo.render(); + m_planes[i].vbo.model.set_color(i == m_hover_id ? DEFAULT_HOVER_PLANE_COLOR : DEFAULT_PLANE_COLOR); + m_planes[i].vbo.model.render(); } - glsafe(::glPopMatrix()); } glsafe(::glEnable(GL_CULL_FACE)); glsafe(::glDisable(GL_BLEND)); + + shader->stop_using(); } -void GLGizmoFlatten::on_render_for_picking() +void GLGizmoFlatten::on_register_raycasters_for_picking() { - const Selection& selection = m_parent.get_selection(); + // the gizmo grabbers are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); - glsafe(::glDisable(GL_DEPTH_TEST)); - glsafe(::glDisable(GL_BLEND)); + assert(m_planes_casters.empty()); + + if (!m_planes.empty()) { + const Selection& selection = m_parent.get_selection(); + const Transform3d matrix = Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * + selection.get_first_volume()->get_instance_transformation().get_matrix(); - if (selection.is_single_full_instance() && !wxGetKeyState(WXK_CONTROL)) { - const Transform3d& m = selection.get_volume(*selection.get_volume_idxs().begin())->get_instance_transformation().get_matrix(); - glsafe(::glPushMatrix()); - glsafe(::glTranslatef(0.f, 0.f, selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z())); - glsafe(::glMultMatrixd(m.data())); - if (this->is_plane_update_necessary()) - update_planes(); for (int i = 0; i < (int)m_planes.size(); ++i) { - glsafe(::glColor4fv(picking_color_component(i).data())); - m_planes[i].vbo.render(); + m_planes_casters.emplace_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, i, *m_planes[i].vbo.mesh_raycaster, matrix)); } - glsafe(::glPopMatrix()); } +} - glsafe(::glEnable(GL_CULL_FACE)); +void GLGizmoFlatten::on_unregister_raycasters_for_picking() +{ + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.set_raycaster_gizmos_on_top(false); + m_planes_casters.clear(); } -void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object) +void GLGizmoFlatten::set_flattening_data(const ModelObject* model_object, int instance_id) { - m_starting_center = Vec3d::Zero(); - if (model_object != m_old_model_object) { + if (model_object != m_old_model_object || instance_id != m_old_instance_id) { m_planes.clear(); - m_planes_valid = false; + on_unregister_raycasters_for_picking(); } } @@ -136,12 +169,12 @@ void GLGizmoFlatten::update_planes() } ch = ch.convex_hull_3d(); m_planes.clear(); - const Transform3d& inst_matrix = mo->instances.front()->get_matrix(true); + on_unregister_raycasters_for_picking(); + const Transform3d inst_matrix = mo->instances.front()->get_matrix_no_offset(); // Following constants are used for discarding too small polygons. const float minimal_area = 5.f; // in square mm (world coordinates) const float minimal_side = 1.f; // mm - const float minimal_angle = 1.f; // degree, initial value was 10, but cause bugs // Now we'll go through all the facets and append Points of facets sharing the same normal. // This part is still performed in mesh coordinate system. @@ -152,7 +185,7 @@ void GLGizmoFlatten::update_planes() std::vector facet_visited(num_of_facets, false); int facet_queue_cnt = 0; const stl_normal* normal_ptr = nullptr; - int facet_idx = 0; + int facet_idx = 0; while (1) { // Find next unvisited triangle: for (; facet_idx < num_of_facets; ++ facet_idx) @@ -196,9 +229,7 @@ void GLGizmoFlatten::update_planes() } // Let's prepare transformation of the normal vector from mesh to instance coordinates. - Geometry::Transformation t(inst_matrix); - Vec3d scaling = t.get_scaling_factor(); - t.set_scaling_factor(Vec3d(1./scaling(0), 1./scaling(1), 1./scaling(2))); + const Matrix3d normal_matrix = inst_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); // Now we'll go through all the polygons, transform the points into xy plane to process them: for (unsigned int polygon_id=0; polygon_id < m_planes.size(); ++polygon_id) { @@ -206,7 +237,7 @@ void GLGizmoFlatten::update_planes() const Vec3d& normal = m_planes[polygon_id].normal; // transform the normal according to the instance matrix: - Vec3d normal_transformed = t.get_matrix() * normal; + const Vec3d normal_transformed = normal_matrix * normal; // We are going to rotate about z and y to flatten the plane Eigen::Quaterniond q; @@ -219,7 +250,7 @@ void GLGizmoFlatten::update_planes() // And yes, it is a nasty thing to do. Whoever has time is free to refactor. Vec3d bb_size = BoundingBoxf3(polygon).size(); float sf = std::min(1./bb_size(0), 1./bb_size(1)); - Transform3d tr = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d(sf, sf, 1.f)); + Transform3d tr = Geometry::scale_transform({ sf, sf, 1.f }); polygon = transform(polygon, tr); polygon = Slic3r::Geometry::convex_hull(polygon); polygon = transform(polygon, tr.inverse()); @@ -236,7 +267,7 @@ void GLGizmoFlatten::update_planes() discard = true; else { // We also check the inner angles and discard polygons with angles smaller than the following threshold - const double angle_threshold = ::cos(minimal_angle * (double)PI / 180.0); + const double angle_threshold = ::cos(10.0 * (double)PI / 180.0); for (unsigned int i = 0; i < polygon.size(); ++i) { const Vec3d& prec = polygon[(i == 0) ? polygon.size() - 1 : i - 1]; @@ -324,34 +355,46 @@ void GLGizmoFlatten::update_planes() m_first_instance_scale = mo->instances.front()->get_scaling_factor(); m_first_instance_mirror = mo->instances.front()->get_mirror(); m_old_model_object = mo; + m_old_instance_id = m_c->selection_info()->get_active_instance(); // And finally create respective VBOs. The polygon is convex with // the vertices in order, so triangulation is trivial. for (auto& plane : m_planes) { - plane.vbo.reserve(plane.vertices.size()); - for (const auto& vert : plane.vertices) - plane.vbo.push_geometry(vert, plane.normal); - for (size_t i=1; i()); + } + for (size_t i = 1; i < plane.vertices.size() - 1; ++i) { + its.indices.emplace_back(0, i, i + 1); // triangle fan + } + + plane.vbo.model.init_from(its); + if (Geometry::Transformation(inst_matrix).is_left_handed()) { + // we need to swap face normals in case the object is mirrored + // for the raycaster to work properly + for (stl_triangle_vertex_indices& face : its.indices) { + if (its_face_normal(its, face).cast().dot(plane.normal) < 0.0) + std::swap(face[1], face[2]); + } + } + plane.vbo.mesh_raycaster = std::make_unique(std::make_shared(std::move(its))); + // vertices are no more needed, clear memory + plane.vertices = std::vector(); } - m_planes_valid = true; + on_register_raycasters_for_picking(); } - bool GLGizmoFlatten::is_plane_update_necessary() const { const ModelObject* mo = m_c->selection_info()->model_object(); if (m_state != On || ! mo || mo->instances.empty()) return false; - if (! m_planes_valid || mo != m_old_model_object - || mo->volumes.size() != m_volumes_matrices.size()) + if (m_planes.empty() || mo != m_old_model_object + || mo->volumes.size() != m_volumes_matrices.size()) return true; // We want to recalculate when the scale changes - some planes could (dis)appear. @@ -367,13 +410,5 @@ bool GLGizmoFlatten::is_plane_update_necessary() const return false; } -Vec3d GLGizmoFlatten::get_flattening_normal() const -{ - Vec3d out = m_normal; - m_normal = Vec3d::Zero(); - m_starting_center = Vec3d::Zero(); - return out; -} - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp index ab3c2c7bab1..3dfbaf3b78a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.hpp @@ -1,9 +1,13 @@ +///|/ Copyright (c) Prusa Research 2019 - 2023 Oleksandra Iushchenko @YuSanka, Lukáš MatÄ›na @lukasmatena, Enrico Turri @enricoturri1966, Filip Sykala @Jony01 +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #ifndef slic3r_GLGizmoFlatten_hpp_ #define slic3r_GLGizmoFlatten_hpp_ #include "GLGizmoBase.hpp" -#include "slic3r/GUI/3DScene.hpp" - +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/MeshUtils.hpp" namespace Slic3r { @@ -18,13 +22,15 @@ class GLGizmoFlatten : public GLGizmoBase // This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself. private: - mutable Vec3d m_normal; + + GLModel arrow; struct PlaneData { std::vector vertices; // should be in fact local in update_planes() - GLIndexedVertexArray vbo; + PickingModel vbo; Vec3d normal; float area; + int picking_id{ -1 }; }; // This holds information to decide whether recalculation is necessary: @@ -34,10 +40,9 @@ class GLGizmoFlatten : public GLGizmoBase Vec3d m_first_instance_mirror; std::vector m_planes; - bool m_planes_valid = false; - mutable Vec3d m_starting_center; + std::vector> m_planes_casters; const ModelObject* m_old_model_object = nullptr; - std::vector instances_matrices; + int m_old_instance_id{ -1 }; void update_planes(); bool is_plane_update_necessary() const; @@ -45,18 +50,25 @@ class GLGizmoFlatten : public GLGizmoBase public: GLGizmoFlatten(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void set_flattening_data(const ModelObject* model_object); - Vec3d get_flattening_normal() const; + void set_flattening_data(const ModelObject* model_object, int instance_id); + + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + void data_changed(bool is_serializing) override; protected: - virtual bool on_init() override; - virtual std::string on_get_name() const override; - virtual bool on_is_activable() const override; - virtual void on_start_dragging() override; - virtual void on_render() override; - virtual void on_render_for_picking() override; - virtual void on_set_state() override; - virtual CommonGizmosDataID on_get_requirements() const override; + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_render() override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + void on_set_state() override; + CommonGizmosDataID on_get_requirements() const override; }; } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 39bc98d7fbb..f8ea6d7f1b0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -88,18 +88,18 @@ void GLGizmoHollow::on_render() } -void GLGizmoHollow::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); -//#if ENABLE_RENDER_PICKING_PASS -// m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); -//#endif - - glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoHollow::render_points(const Selection& selection, bool picking) const +// void GLGizmoHollow::on_render_for_picking() +// { +// const Selection& selection = m_parent.get_selection(); +// //#if ENABLE_RENDER_PICKING_PASS +// // m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z(); +// //#endif + +// glsafe(::glEnable(GL_DEPTH_TEST)); +// render_points(selection, true); +// } + +void GLGizmoHollow::render_points(const Selection& selection, bool picking) { GLShaderProgram* shader = picking ? nullptr : wxGetApp().get_shader("gouraud_light"); if (shader) @@ -127,8 +127,8 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons // First decide about the color of the point. if (picking) { - std::array color = picking_color_component(i); - render_color = color; + // std::array color = picking_color_component(i); + // render_color = color; } else { if (size_t(m_hover_id) == i) { @@ -148,7 +148,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons } } - const_cast(&m_vbo_cylinder)->set_color(-1, render_color); + m_vbo_cylinder.set_color(render_color); // Inverse matrix of the instance scaling is applied so that the mark does not scale with the object. glsafe(::glPushMatrix()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 2cf08de2a0a..c2ee014fc88 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -40,9 +40,8 @@ class GLGizmoHollow : public GLGizmoBase bool on_init() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); void hollow_mesh(bool postpone_error_messages = false); bool unsaved_changes() const; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp new file mode 100644 index 00000000000..40b1fd9caf5 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.cpp @@ -0,0 +1,2154 @@ +#include "GLGizmoMeasure.hpp" +#include "libslic3r/Color.hpp" +#include "libslic3r/Format/STL.hpp" +#include "slic3r/GUI/GLCanvas3D.hpp" +#include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp" + + +#include "libslic3r/PresetBundle.hpp" +#include "libslic3r/MeasureUtils.hpp" + +#include + +#include + +#include + +#include + +#include + +namespace Slic3r { +namespace GUI { + +static const Slic3r::ColorRGBA SELECTED_1ST_COLOR = { 0.25f, 0.75f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA SELECTED_2ND_COLOR = { 0.75f, 0.25f, 0.75f, 1.0f }; +static const Slic3r::ColorRGBA NEUTRAL_COLOR = { 0.5f, 0.5f, 0.5f, 1.0f }; + +static const int POINT_ID = 100; +static const int EDGE_ID = 200; +static const int CIRCLE_ID = 300; +static const int PLANE_ID = 400; +static const int SEL_SPHERE_1_ID = 501; +static const int SEL_SPHERE_2_ID = 502; + +static const float TRIANGLE_BASE = 10.0f; +static const float TRIANGLE_HEIGHT = TRIANGLE_BASE * 1.618033f; + +const auto SELECTED_COLOR = ColorRGBA::BLUE(); +static const std::string CTRL_STR = +#ifdef __APPLE__ +"⌘" +#else +"Ctrl" +#endif //__APPLE__ +; + +static std::string format_double(double value) +{ + char buf[1024]; + sprintf(buf, "%.3f", value); + return std::string(buf); +} + +static std::string format_vec3(const Vec3d& v) +{ + char buf[1024]; + sprintf(buf, "X: %.3f, Y: %.3f, Z: %.3f", v.x(), v.y(), v.z()); + return std::string(buf); +} + +static std::string surface_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + switch (type) + { + default: + case Measure::SurfaceFeatureType::Undef: { return ("No feature"); } + case Measure::SurfaceFeatureType::Point: { return _u8L("Vertex"); } + case Measure::SurfaceFeatureType::Edge: { return _u8L("Edge"); } + case Measure::SurfaceFeatureType::Circle: { return _u8L("Circle"); } + case Measure::SurfaceFeatureType::Plane: { return _u8L("Plane"); } + } +} + +static std::string point_on_feature_type_as_string(Measure::SurfaceFeatureType type, int hover_id) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Point: { ret = _u8L("Vertex"); break; } + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Point on edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Point on circle"); break; } + case Measure::SurfaceFeatureType::Plane: { ret = _u8L("Point on plane"); break; } + default: { assert(false); break; } + } + return ret; +} + +static std::string center_on_feature_type_as_string(Measure::SurfaceFeatureType type) +{ + std::string ret; + switch (type) { + case Measure::SurfaceFeatureType::Edge: { ret = _u8L("Center of edge"); break; } + case Measure::SurfaceFeatureType::Circle: { ret = _u8L("Center of circle"); break; } + default: { assert(false); break; } + } + return ret; +} + +static GLModel::Geometry init_plane_data(const indexed_triangle_set& its, const std::vector& triangle_indices) +{ + GLModel::Geometry init_data; + init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + init_data.reserve_indices(3 * triangle_indices.size()); + init_data.reserve_vertices(3 * triangle_indices.size()); + unsigned int i = 0; + for (int idx : triangle_indices) { + const Vec3f& v0 = its.vertices[its.indices[idx][0]]; + const Vec3f& v1 = its.vertices[its.indices[idx][1]]; + const Vec3f& v2 = its.vertices[its.indices[idx][2]]; + + const Vec3f n = (v1 - v0).cross(v2 - v0).normalized(); + init_data.add_vertex(v0, n); + init_data.add_vertex(v1, n); + init_data.add_vertex(v2, n); + init_data.add_triangle(i, i + 1, i + 2); + i += 3; + } + + return init_data; +} + +static GLModel::Geometry init_torus_data(unsigned int primary_resolution, unsigned int secondary_resolution, const Vec3f& center, + float radius, float thickness, const Vec3f& model_axis, const Transform3f& world_trafo) +{ + const unsigned int torus_sector_count = std::max(4, primary_resolution); + const unsigned int section_sector_count = std::max(4, secondary_resolution); + const float torus_sector_step = 2.0f * float(M_PI) / float(torus_sector_count); + const float section_sector_step = 2.0f * float(M_PI) / float(section_sector_count); + + GLModel::Geometry data; + data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3 }; + data.reserve_vertices(torus_sector_count * section_sector_count); + data.reserve_indices(torus_sector_count * section_sector_count * 2 * 3); + + // vertices + const Transform3f local_to_world_matrix = world_trafo * Geometry::translation_transform(center.cast()).cast() * + Eigen::Quaternion::FromTwoVectors(Vec3f::UnitZ(), model_axis); + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const float section_angle = torus_sector_step * i; + const Vec3f radius_dir(std::cos(section_angle), std::sin(section_angle), 0.0f); + const Vec3f local_section_center = radius * radius_dir; + const Vec3f world_section_center = local_to_world_matrix * local_section_center; + const Vec3f local_section_normal = local_section_center.normalized().cross(Vec3f::UnitZ()).normalized(); + const Vec3f world_section_normal = (Vec3f)(local_to_world_matrix.matrix().block(0, 0, 3, 3) * local_section_normal).normalized(); + const Vec3f base_v = thickness * radius_dir; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const Vec3f v = Eigen::AngleAxisf(section_sector_step * j, world_section_normal) * base_v; + data.add_vertex(world_section_center + v, (Vec3f)v.normalized()); + } + } + + // triangles + for (unsigned int i = 0; i < torus_sector_count; ++i) { + const unsigned int ii = i * section_sector_count; + const unsigned int ii_next = ((i + 1) % torus_sector_count) * section_sector_count; + for (unsigned int j = 0; j < section_sector_count; ++j) { + const unsigned int j_next = (j + 1) % section_sector_count; + const unsigned int i0 = ii + j; + const unsigned int i1 = ii_next + j; + const unsigned int i2 = ii_next + j_next; + const unsigned int i3 = ii + j_next; + data.add_triangle(i0, i1, i2); + data.add_triangle(i0, i2, i3); + } + } + + return data; +} + +static bool is_feature_with_center(const Measure::SurfaceFeature& feature) +{ + const Measure::SurfaceFeatureType type = feature.get_type(); + return (type == Measure::SurfaceFeatureType::Circle || (type == Measure::SurfaceFeatureType::Edge && feature.get_extra_point().has_value())); +} + +static Vec3d get_feature_offset(const Measure::SurfaceFeature& feature) +{ + Vec3d ret; + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + ret = center; + break; + } + case Measure::SurfaceFeatureType::Edge: + { + std::optional p = feature.get_extra_point(); + assert(p.has_value()); + ret = *p; + break; + } + case Measure::SurfaceFeatureType::Point: + { + ret = feature.get_point(); + break; + } + default: { assert(false); } + } + + return ret; +} + +class TransformHelper +{ + struct Cache + { + std::array viewport; + Matrix4d ndc_to_ss_matrix; + Transform3d ndc_to_ss_matrix_inverse; + }; + + static Cache s_cache; + +public: + static Vec3d model_to_world(const Vec3d& model, const Transform3d& world_matrix) { + return world_matrix * model; + } + + static Vec4d world_to_clip(const Vec3d& world, const Matrix4d& projection_view_matrix) { + return projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); + } + + static Vec3d clip_to_ndc(const Vec4d& clip) { + return Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); + } + + static Vec2d ndc_to_ss(const Vec3d& ndc, const std::array& viewport) { + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + return { half_w * ndc.x() + double(viewport[0]) + half_w, half_h * ndc.y() + double(viewport[1]) + half_h }; + }; + + static Vec4d model_to_clip(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return world_to_clip(model_to_world(model, world_matrix), projection_view_matrix); + } + + static Vec3d model_to_ndc(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix) { + return clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)); + } + + static Vec2d model_to_ss(const Vec3d& model, const Transform3d& world_matrix, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(model_to_world(model, world_matrix), projection_view_matrix)), viewport); + } + + static Vec2d world_to_ss(const Vec3d& world, const Matrix4d& projection_view_matrix, const std::array& viewport) { + return ndc_to_ss(clip_to_ndc(world_to_clip(world, projection_view_matrix)), viewport); + } + + static const Matrix4d& ndc_to_ss_matrix(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix; + } + + static const Transform3d ndc_to_ss_matrix_inverse(const std::array& viewport) { + update(viewport); + return s_cache.ndc_to_ss_matrix_inverse; + } + +private: + static void update(const std::array& viewport) { + if (s_cache.viewport == viewport) + return; + + const double half_w = 0.5 * double(viewport[2]); + const double half_h = 0.5 * double(viewport[3]); + s_cache.ndc_to_ss_matrix << half_w, 0.0, 0.0, double(viewport[0]) + half_w, + 0.0, half_h, 0.0, double(viewport[1]) + half_h, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0; + + s_cache.ndc_to_ss_matrix_inverse = s_cache.ndc_to_ss_matrix.inverse(); + s_cache.viewport = viewport; + } +}; + +TransformHelper::Cache TransformHelper::s_cache = { { 0, 0, 0, 0 }, Matrix4d::Identity(), Transform3d::Identity() }; + +GLGizmoMeasure::GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) +: GLGizmoBase(parent, icon_filename, sprite_id) +{ + GLModel::Geometry sphere_geometry = smooth_sphere(16, 7.5f); + m_sphere.mesh_raycaster = std::make_unique(std::make_shared(sphere_geometry.get_as_indexed_triangle_set())); + m_sphere.model.init_from(std::move(sphere_geometry)); + + GLModel::Geometry cylinder_geometry = smooth_cylinder(16, 5.0f, 1.0f); + m_cylinder.mesh_raycaster = std::make_unique(std::make_shared(cylinder_geometry.get_as_indexed_triangle_set())); + m_cylinder.model.init_from(std::move(cylinder_geometry)); +} + +bool GLGizmoMeasure::on_mouse(const wxMouseEvent &mouse_event) +{ + m_mouse_pos = { double(mouse_event.GetX()), double(mouse_event.GetY()) }; + + if (mouse_event.Moving()) { + // only for sure + m_mouse_left_down = false; + return false; + } + else if (mouse_event.Dragging()) { + // Enable/Disable panning/rotating the 3D scene + // Ctrl is pressed or the mouse is not hovering a selected volume + bool unlock_dragging = mouse_event.CmdDown() || (m_hover_id == -1 && !m_parent.get_selection().contains_volume(m_parent.get_first_hover_volume_idx())); + // mode is not center selection or mouse is not hovering a center + unlock_dragging &= !mouse_event.ShiftDown() || (m_hover_id != SEL_SPHERE_1_ID && m_hover_id != SEL_SPHERE_2_ID && m_hover_id != POINT_ID); + return !unlock_dragging; + } + else if (mouse_event.LeftDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + + if (m_hover_id != -1) { + m_mouse_left_down = true; + + auto detect_current_item = [this]() { + SelectedFeatures::Item item; + if (m_hover_id == SEL_SPHERE_1_ID) { + if (m_selected_features.first.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.first.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.source)) } }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // mouse is hovering over a unselected center + item = { true, m_selected_features.first.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.first.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.first; + } + else if (m_hover_id == SEL_SPHERE_2_ID) { + if (m_selected_features.second.is_center) + // mouse is hovering over a selected center + item = { true, m_selected_features.second.source, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.source)) } }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // mouse is hovering over a center + item = { true, m_selected_features.second.feature, { Measure::SurfaceFeature(get_feature_offset(*m_selected_features.second.feature)) } }; + else + // mouse is hovering over a point + item = m_selected_features.second; + } + else { + switch (m_mode) + { + case EMode::FeatureSelection: { item = { false, m_curr_feature, m_curr_feature }; break; } + case EMode::PointSelection: { item = { false, m_curr_feature, Measure::SurfaceFeature(*m_curr_point_on_feature_position) }; break; } + } + } + return item; + }; + + auto requires_sphere_raycaster_for_picking = [this](const SelectedFeatures::Item& item) { + if (m_mode == EMode::PointSelection || item.feature->get_type() == Measure::SurfaceFeatureType::Point) + return true; + else if (m_mode == EMode::FeatureSelection) { + if (is_feature_with_center(*item.feature)) + return true; + } + return false; + }; + + if (m_selected_features.first.feature.has_value()) { + const SelectedFeatures::Item item = detect_current_item(); + if (m_selected_features.first != item) { + bool processed = false; + if (item.is_center) { + if (item.source == m_selected_features.first.feature) { + // switch 1st selection from feature to its center + m_selected_features.first = item; + processed = true; + } + else if (item.source == m_selected_features.second.feature) { + // switch 2nd selection from feature to its center + m_selected_features.second = item; + processed = true; + } + } + else if (is_feature_with_center(*item.feature)) { + if (m_selected_features.first.is_center && m_selected_features.first.source == item.feature) { + // switch 1st selection from center to its feature + m_selected_features.first = item; + processed = true; + } + else if (m_selected_features.second.is_center && m_selected_features.second.source == item.feature) { + // switch 2nd selection from center to its feature + m_selected_features.second = item; + processed = true; + } + } + + if (!processed) { + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + if (m_selected_features.second == item) + // 2nd feature deselection + m_selected_features.second.reset(); + else { + // 2nd feature selection + m_selected_features.second = item; + if (requires_sphere_raycaster_for_picking(item)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_2_ID, *m_sphere.mesh_raycaster)); + } + } + } + else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + if (m_selected_features.second.feature.has_value()) { + // promote 2nd feature to 1st feature + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + m_selected_features.first = m_selected_features.second; + if (requires_sphere_raycaster_for_picking(m_selected_features.first)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_1_ID, *m_sphere.mesh_raycaster)); + m_selected_features.second.reset(); + } + else + // 1st feature deselection + m_selected_features.first.reset(); + } + } + else { + // 1st feature selection + const SelectedFeatures::Item item = detect_current_item(); + m_selected_features.first = item; + if (requires_sphere_raycaster_for_picking(item)) + m_selected_sphere_raycasters.push_back(m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, SEL_SPHERE_1_ID, *m_sphere.mesh_raycaster)); + } + + update_measurement_result(); + + m_imgui->set_requires_extra_frame(); + + return true; + } + else + // if the mouse pointer is on any volume, filter out the event to prevent the user to move it + // equivalent tp: return (m_parent.get_first_hover_volume_idx() != -1); + return m_curr_feature.has_value(); + + // fix: prevent restart gizmo when reselect object + // take responsibility for left up + if (m_parent.get_first_hover_volume_idx() >= 0) + m_mouse_left_down = true; + } + else if (mouse_event.LeftUp()) { + if (m_mouse_left_down) { + // responsible for mouse left up after selecting plane + m_mouse_left_down = false; + return true; + } + if (m_hover_id == -1 && !m_parent.is_mouse_dragging()) + // avoid closing the gizmo if the user clicks outside of any volume + return true; + } + else if (mouse_event.RightDown()) { + // let the event pass through to allow panning/rotating the 3D scene + if (mouse_event.CmdDown()) + return false; + } + else if (mouse_event.Leaving()) + m_mouse_left_down = false; + + return false; +} + +void GLGizmoMeasure::data_changed(bool is_serializing) +{ + m_parent.toggle_sla_auxiliaries_visibility(false, nullptr, -1); + + update_if_needed(); + + m_last_inv_zoom = 0.0f; + m_last_plane_idx = -1; + if (m_pending_scale) { + update_measurement_result(); + m_pending_scale = false; + } + else + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_editing_distance = false; + m_is_editing_distance_first_frame = true; +} + +bool GLGizmoMeasure::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down) +{ + if (action == SLAGizmoEventType::ShiftDown) { + if (m_shift_kar_filter.is_first()) { + m_mode = EMode::PointSelection; + disable_scene_raycasters(); + } + m_shift_kar_filter.increase_count(); + } + else if (action == SLAGizmoEventType::ShiftUp) { + m_shift_kar_filter.reset_count(); + m_mode = EMode::FeatureSelection; + restore_scene_raycasters_state(); + } + else if (action == SLAGizmoEventType::Delete) { + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_parent.request_extra_frame(); + } + else if (action == SLAGizmoEventType::Escape) { + if (!m_selected_features.first.feature.has_value()) { + update_measurement_result(); + return false; + } + else { + if (m_selected_features.second.feature.has_value()) { + remove_selected_sphere_raycaster(SEL_SPHERE_2_ID); + m_selected_features.second.feature.reset(); + } + else { + remove_selected_sphere_raycaster(SEL_SPHERE_1_ID); + m_selected_features.first.feature.reset(); + } + + update_measurement_result(); + } + } + + return true; +} + +bool GLGizmoMeasure::on_init() +{ + m_shortcut_key = WXK_CONTROL_U; + return true; +} + +void GLGizmoMeasure::on_set_state() +{ + if (m_state == Off) { + m_parent.toggle_sla_auxiliaries_visibility(true, nullptr, -1); + m_shift_kar_filter.reset_count(); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + restore_scene_raycasters_state(); + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + m_measuring.reset(); + m_raycaster.reset(); + } + else { + m_mode = EMode::FeatureSelection; + // store current state of scene raycaster for later use + m_scene_raycasters.clear(); + auto scene_raycasters = m_parent.get_raycasters_for_picking(SceneRaycaster::EType::Volume); + if (scene_raycasters != nullptr) { + m_scene_raycasters.reserve(scene_raycasters->size()); + for (auto r : *scene_raycasters) { + SceneRaycasterState state = { r, r->is_active() }; + m_scene_raycasters.emplace_back(state); + } + } + } +} + +std::string GLGizmoMeasure::on_get_name() const +{ + return _u8L("Measure"); +} + +bool GLGizmoMeasure::on_is_activable() const +{ + const Selection& selection = m_parent.get_selection(); + bool res = (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) ? + selection.is_single_full_instance() : + selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); + if (res) + res &= !selection.contains_sinking_volumes(); + + return res; +} + +void GLGizmoMeasure::on_render() +{ +#if ENABLE_MEASURE_GIZMO_DEBUG + render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +// // do not render if the user is panning/rotating the 3d scene +// if (m_parent.is_mouse_dragging()) +// return; + + update_if_needed(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + const float inv_zoom = (float)camera.get_inv_zoom(); + + Vec3f position_on_model; + Vec3f normal_on_model; + size_t model_facet_idx; + const bool mouse_on_object = m_raycaster->unproject_on_mesh(m_mouse_pos, Transform3d::Identity(), camera, position_on_model, normal_on_model, nullptr, &model_facet_idx); + const bool is_hovering_on_feature = m_mode == EMode::PointSelection && m_hover_id != -1; + + auto update_circle = [this, inv_zoom]() { + if (m_last_inv_zoom != inv_zoom || m_last_circle != m_curr_feature) { + m_last_inv_zoom = inv_zoom; + m_last_circle = m_curr_feature; + m_circle.reset(); + const auto [center, radius, normal] = m_curr_feature->get_circle(); + GLModel::Geometry circle_geometry = init_torus_data(64, 16, center.cast(), float(radius), 5.0f * inv_zoom, normal.cast(), Transform3f::Identity()); + m_circle.mesh_raycaster = std::make_unique(std::make_shared(circle_geometry.get_as_indexed_triangle_set())); + m_circle.model.init_from(std::move(circle_geometry)); + return true; + } + return false; + }; + + if (m_mode == EMode::FeatureSelection || m_mode == EMode::PointSelection) { + if (m_hover_id == SEL_SPHERE_1_ID || m_hover_id == SEL_SPHERE_2_ID) { + // Skip feature detection if hovering on a selected point/center + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + m_curr_feature.reset(); + m_curr_point_on_feature_position.reset(); + } + else { + std::optional curr_feature = wxGetMouseState().LeftIsDown() ? m_curr_feature : + mouse_on_object ? m_measuring->get_feature(model_facet_idx, position_on_model.cast()) : std::nullopt; + + if (m_curr_feature != curr_feature || + (curr_feature.has_value() && curr_feature->get_type() == Measure::SurfaceFeatureType::Circle && (m_curr_feature != curr_feature || m_last_inv_zoom != inv_zoom))) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + m_raycasters.clear(); + m_curr_feature = curr_feature; + if (!m_curr_feature.has_value()) + return; + + switch (m_curr_feature->get_type()) { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_raycasters.insert({ POINT_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, POINT_ID, *m_sphere.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + m_raycasters.insert({ EDGE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, EDGE_ID, *m_cylinder.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + update_circle(); + m_raycasters.insert({ CIRCLE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID, *m_circle.mesh_raycaster) }); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto [idx, normal, point] = m_curr_feature->get_plane(); + if (m_last_plane_idx != idx) { + m_last_plane_idx = idx; + const indexed_triangle_set& its = m_measuring->get_its(); + const std::vector& plane_triangles = m_measuring->get_plane_triangle_indices(idx); + GLModel::Geometry init_data = init_plane_data(its, plane_triangles); + m_plane.reset(); + m_plane.mesh_raycaster = std::make_unique(std::make_shared(init_data.get_as_indexed_triangle_set())); + } + + m_raycasters.insert({ PLANE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, PLANE_ID, *m_plane.mesh_raycaster) }); + break; + } + } + } + } + } + + if (m_mode != EMode::PointSelection) + m_curr_point_on_feature_position.reset(); + else if (is_hovering_on_feature) { + auto position_on_feature = [this](int feature_type_id, const Camera& camera, std::function callback = nullptr) -> Vec3d { + auto it = m_raycasters.find(feature_type_id); + if (it != m_raycasters.end() && it->second != nullptr) { + Vec3f p; + Vec3f n; + const Transform3d& trafo = it->second->get_transform(); + bool res = it->second->get_raycaster()->closest_hit(m_mouse_pos, trafo, camera, p, n); + if (res) { + if (callback) + p = callback(p); + return trafo * p.cast(); + } + } + return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); + }; + + if (m_curr_feature.has_value()) { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + m_curr_point_on_feature_position = m_curr_feature->get_point(); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const std::optional extra = m_curr_feature->get_extra_point(); + if (extra.has_value() && m_hover_id == POINT_ID) + m_curr_point_on_feature_position = *extra; + else { + const Vec3d pos = position_on_feature(EDGE_ID, camera, [](const Vec3f& v) { return Vec3f(0.0f, 0.0f, v.z()); }); + if (!pos.isApprox(Vec3d(DBL_MAX, DBL_MAX, DBL_MAX))) + m_curr_point_on_feature_position = pos; + } + break; + } + case Measure::SurfaceFeatureType::Plane: + { + m_curr_point_on_feature_position = position_on_feature(PLANE_ID, camera); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = m_curr_feature->get_circle(); + if (m_hover_id == POINT_ID) + m_curr_point_on_feature_position = center; + else { + const Vec3d world_pof = position_on_feature(CIRCLE_ID, camera, [](const Vec3f& v) { return v; }); + const Eigen::Hyperplane plane(normal, center); + const Transform3d local_to_model_matrix = Geometry::translation_transform(center) * Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), normal); + const Vec3d local_proj = local_to_model_matrix.inverse() * plane.projection(world_pof); + double angle = std::atan2(local_proj.y(), local_proj.x()); + if (angle < 0.0) + angle += 2.0 * double(M_PI); + + const Vec3d local_pos = radius * Vec3d(std::cos(angle), std::sin(angle), 0.0); + m_curr_point_on_feature_position = local_to_model_matrix * local_pos; + } + break; + } + } + } + } + else { + m_curr_point_on_feature_position.reset(); + if (m_curr_feature.has_value() && m_curr_feature->get_type() == Measure::SurfaceFeatureType::Circle) { + if (update_circle()) { + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID); + auto it = m_raycasters.find(CIRCLE_ID); + if (it != m_raycasters.end()) + m_raycasters.erase(it); + m_raycasters.insert({ CIRCLE_ID, m_parent.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, CIRCLE_ID, *m_circle.mesh_raycaster) }); + } + } + } + + if (!m_curr_feature.has_value() && !m_selected_features.first.feature.has_value()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); + glsafe(::glEnable(GL_DEPTH_TEST)); + const bool old_cullface = ::glIsEnabled(GL_CULL_FACE); + glsafe(::glDisable(GL_CULL_FACE)); + + const Transform3d& view_matrix = camera.get_view_matrix(); + + auto set_matrix_uniforms = [shader, &view_matrix](const Transform3d& model_matrix) { + const Transform3d view_model_matrix = view_matrix * model_matrix; + shader->set_uniform("view_model_matrix", view_model_matrix); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + }; + + auto set_emission_uniform = [shader](const ColorRGBA& color, bool hover) { + shader->set_uniform("emission_factor", (color == SELECTED_COLOR) ? 0.0f : + hover ? 0.5f : 0.25f); + }; + + auto render_feature = [this, set_matrix_uniforms, set_emission_uniform](const Measure::SurfaceFeature& feature, const std::vector& colors, + float inv_zoom, bool hover, bool update_raycasters_transform) { + switch (feature.get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + const Transform3d feature_matrix = Geometry::translation_transform(feature.get_point()) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(feature_matrix); + set_emission_uniform(colors.front(), hover); + m_sphere.model.set_color(colors.front().data_array()); + m_sphere.model.render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(feature_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto& [center, radius, normal] = feature.get_circle(); + // render circle + const Transform3d circle_matrix = Transform3d::Identity(); + set_matrix_uniforms(circle_matrix); + if (update_raycasters_transform) { + set_emission_uniform(colors.front(), hover); + m_circle.model.set_color(colors.front().data_array()); + m_circle.model.render(); + auto it = m_raycasters.find(CIRCLE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(circle_matrix); + } + else { + GLModel circle; + GLModel::Geometry circle_geometry = init_torus_data(64, 16, center.cast(), float(radius), 5.0f * inv_zoom, normal.cast(), Transform3f::Identity()); + circle.init_from(std::move(circle_geometry)); + set_emission_uniform(colors.front(), hover); + circle.set_color(colors.front().data_array()); + circle.render(); + } + // render center + if (colors.size() > 1) { + const Transform3d center_matrix = Geometry::translation_transform(center) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(center_matrix); + set_emission_uniform(colors.back(), hover); + m_sphere.model.set_color(colors.back().data_array()); + m_sphere.model.render(); + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(center_matrix); + } + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto& [from, to] = feature.get_edge(); + // render edge + const Transform3d edge_matrix = Geometry::translation_transform(from) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), to - from) * + Geometry::scale_transform({ (double)inv_zoom, (double)inv_zoom, (to - from).norm() }); + set_matrix_uniforms(edge_matrix); + set_emission_uniform(colors.front(), hover); + m_cylinder.model.set_color(colors.front().data_array()); + m_cylinder.model.render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(EDGE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(edge_matrix); + } + + // render extra point + if (colors.size() > 1) { + const std::optional extra = feature.get_extra_point(); + if (extra.has_value()) { + const Transform3d point_matrix = Geometry::translation_transform(*extra) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(point_matrix); + set_emission_uniform(colors.back(), hover); + m_sphere.model.set_color(colors.back().data_array()); + m_sphere.model.render(); + auto it = m_raycasters.find(POINT_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(point_matrix); + } + } + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto& [idx, normal, pt] = feature.get_plane(); + assert(idx < m_plane_models_cache.size()); + set_matrix_uniforms(Transform3d::Identity()); + set_emission_uniform(colors.front(), hover); + m_plane_models_cache[idx].set_color(colors.front().data_array()); + m_plane_models_cache[idx].render(); + if (update_raycasters_transform) { + auto it = m_raycasters.find(PLANE_ID); + if (it != m_raycasters.end() && it->second != nullptr) + it->second->set_transform(Transform3d::Identity()); + } + break; + } + } + }; + + auto hover_selection_color = [this]() { + return ((m_mode == EMode::PointSelection && !m_selected_features.first.feature.has_value()) || + (m_mode != EMode::PointSelection && (!m_selected_features.first.feature.has_value() || *m_curr_feature == *m_selected_features.first.feature))) ? + SELECTED_1ST_COLOR : SELECTED_2ND_COLOR; + }; + + auto hovering_color = [this, hover_selection_color]() { + return (m_mode == EMode::PointSelection) ? SELECTED_COLOR : hover_selection_color(); + }; + + if (m_curr_feature.has_value()) { + // render hovered feature + + std::vector colors; + if (m_selected_features.first.feature.has_value() && *m_curr_feature == *m_selected_features.first.feature) { + // hovering over the 1st selected feature + if (m_selected_features.first.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.first.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else if (m_selected_features.second.feature.has_value() && *m_curr_feature == *m_selected_features.second.feature) { + // hovering over the 2nd selected feature + if (m_selected_features.second.is_center) + // hovering over a center + colors = { NEUTRAL_COLOR, hovering_color() }; + else if (is_feature_with_center(*m_selected_features.second.feature)) + // hovering over a feature with center + colors = { hovering_color(), NEUTRAL_COLOR }; + else + colors = { hovering_color() }; + } + else { + switch (m_curr_feature->get_type()) + { + default: { assert(false); break; } + case Measure::SurfaceFeatureType::Point: + { + colors.emplace_back(hover_selection_color()); + break; + } + case Measure::SurfaceFeatureType::Edge: + case Measure::SurfaceFeatureType::Circle: + { + if (m_selected_features.first.is_center && m_curr_feature == m_selected_features.first.source) + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + else if (m_selected_features.second.is_center && m_curr_feature == m_selected_features.second.source) + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + else + colors = { hovering_color(), hovering_color() }; + break; + } + case Measure::SurfaceFeatureType::Plane: + { + colors.emplace_back(hovering_color()); + break; + } + } + } + + render_feature(*m_curr_feature, colors, inv_zoom, true, true); + } + + if (m_selected_features.first.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.first.feature)) { + // render 1st selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_1_ID && (m_selected_features.first.is_center || is_feature_with_center(*m_selected_features.first.feature))) { + // hovering over a center + feature_to_render = m_selected_features.first.source; + colors = { NEUTRAL_COLOR, SELECTED_1ST_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.first.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.first.feature; + colors = { SELECTED_1ST_COLOR }; + requires_raycaster_update = m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_1_ID, false); + + if (requires_raycaster_update) { + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == SEL_SPHERE_1_ID; }); + if (it != m_selected_sphere_raycasters.end()) + (*it)->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.first.feature)) * Geometry::scale_transform(inv_zoom)); + } + } + + if (m_selected_features.second.feature.has_value() && (!m_curr_feature.has_value() || *m_curr_feature != *m_selected_features.second.feature)) { + // render 2nd selected feature + + std::optional feature_to_render; + std::vector colors; + bool requires_raycaster_update = false; + if (m_hover_id == SEL_SPHERE_2_ID && (m_selected_features.second.is_center || is_feature_with_center(*m_selected_features.second.feature))) { + // hovering over a center + feature_to_render = m_selected_features.second.source; + colors = { NEUTRAL_COLOR, SELECTED_2ND_COLOR }; + requires_raycaster_update = true; + } + else if (is_feature_with_center(*m_selected_features.second.feature)) { + // hovering over a feature with center + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR, NEUTRAL_COLOR }; + requires_raycaster_update = true; + } + else { + feature_to_render = m_selected_features.second.feature; + colors = { SELECTED_2ND_COLOR }; + requires_raycaster_update = m_selected_features.second.feature->get_type() == Measure::SurfaceFeatureType::Point; + } + + render_feature(*feature_to_render, colors, inv_zoom, m_hover_id == SEL_SPHERE_2_ID, false); + + if (requires_raycaster_update) { + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == SEL_SPHERE_2_ID; }); + if (it != m_selected_sphere_raycasters.end()) + (*it)->set_transform(Geometry::translation_transform(get_feature_offset(*m_selected_features.second.feature)) * Geometry::scale_transform(inv_zoom)); + } + } + + if (is_hovering_on_feature && m_curr_point_on_feature_position.has_value()) { + if (m_hover_id != POINT_ID) { + // render point on feature while SHIFT is pressed + const Transform3d matrix = Geometry::translation_transform(*m_curr_point_on_feature_position) * Geometry::scale_transform(inv_zoom); + set_matrix_uniforms(matrix); + const ColorRGBA color = hover_selection_color(); + set_emission_uniform(color, true); + m_sphere.model.set_color(color.data_array()); + m_sphere.model.render(); + } + } + + shader->stop_using(); + + if (old_cullface) + glsafe(::glEnable(GL_CULL_FACE)); + + render_dimensioning(); +} + +void GLGizmoMeasure::update_if_needed() +{ + auto update_plane_models_cache = [this](const indexed_triangle_set& its) { + m_plane_models_cache.clear(); + m_plane_models_cache.resize(m_measuring->get_num_of_planes(), GLModel()); + + auto& plane_models_cache = m_plane_models_cache; + const auto& measuring = m_measuring; + + //for (int idx = 0; idx < m_measuring->get_num_of_planes(); ++idx) { + tbb::parallel_for(tbb::blocked_range(0, m_measuring->get_num_of_planes()), + [&plane_models_cache, &measuring, &its](const tbb::blocked_range& range) { + for (size_t idx = range.begin(); idx != range.end(); ++idx) { + GLModel::Geometry init_data = init_plane_data(its, measuring->get_plane_triangle_indices(idx)); + plane_models_cache[idx].init_from(std::move(init_data)); + } + }); + }; + + auto do_update = [this, update_plane_models_cache](const std::vector& volumes_cache, const Selection& selection) { + TriangleMesh composite_mesh; + for (const auto& vol : volumes_cache) { +// if (selection.is_single_full_instance() && vol.volume->is_modifier()) +// continue; + + TriangleMesh volume_mesh = vol.volume->mesh(); + volume_mesh.transform(vol.world_trafo); + + if (vol.world_trafo.matrix().determinant() < 0.0) + volume_mesh.flip_triangles(); + + composite_mesh.merge(volume_mesh); + } + + m_measuring.reset(new Measure::Measuring(composite_mesh.its)); + update_plane_models_cache(m_measuring->get_its()); + m_raycaster.reset(new MeshRaycaster(std::make_shared(composite_mesh))); + m_volumes_cache = volumes_cache; + }; + + const Selection& selection = m_parent.get_selection(); + if (selection.is_empty()) + return; + + const Selection::IndicesList& idxs = selection.get_volume_idxs(); + std::vector volumes_cache; + volumes_cache.reserve(idxs.size()); + for (unsigned int idx : idxs) { + const GLVolume* v = selection.get_volume(idx); + const int volume_idx = v->volume_idx(); + if (volume_idx < 0) + continue; + + const ModelObject* obj = selection.get_model()->objects[v->object_idx()]; + const ModelInstance* inst = obj->instances[v->instance_idx()]; + const ModelVolume* vol = obj->volumes[volume_idx]; + const VolumeCacheItem item = { + obj, inst, vol, + Geometry::translation_transform(selection.get_first_volume()->get_sla_shift_z() * Vec3d::UnitZ()) * inst->get_matrix() * vol->get_matrix() + }; + volumes_cache.emplace_back(item); + } + + if (m_state != On || volumes_cache.empty()) + return; + + if (m_measuring == nullptr || m_volumes_cache != volumes_cache) + do_update(volumes_cache, selection); +} + +void GLGizmoMeasure::disable_scene_raycasters() +{ + for (auto r : m_scene_raycasters) { + r.raycaster->set_active(false); + } +} + +void GLGizmoMeasure::restore_scene_raycasters_state() +{ + for (auto r : m_scene_raycasters) { + r.raycaster->set_active(r.state); + } +} + +void GLGizmoMeasure::render_dimensioning() +{ + static SelectedFeatures last_selected_features; + + if (!m_selected_features.first.feature.has_value()) + return; + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + auto point_point = [this, &shader](const Vec3d& v1, const Vec3d& v2, float distance) { + if ((v2 - v1).squaredNorm() < 0.000001 || distance < 0.001f) + return; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + + // screen coordinates + const Vec2d v1ss = TransformHelper::world_to_ss(v1, projection_view_matrix, viewport); + const Vec2d v2ss = TransformHelper::world_to_ss(v2, projection_view_matrix, viewport); + + if (v1ss.isApprox(v2ss)) + return; + + const Vec2d v12ss = v2ss - v1ss; + const double v12ss_len = v12ss.norm(); + + const bool overlap = v12ss_len - 2.0 * TRIANGLE_HEIGHT < 0.0; + + const auto q12ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(v12ss.x(), v12ss.y(), 0.0)); + const auto q21ss = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(-v12ss.x(), -v12ss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + + const Vec3d v1ss_3 = { v1ss.x(), v1ss.y(), 0.0 }; + const Vec3d v2ss_3 = { v2ss.x(), v2ss.y(), 0.0 }; + + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + // stem + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::translation_transform(-2.0 * TRIANGLE_HEIGHT * Vec3d::UnitX()) * Geometry::scale_transform({ v12ss_len + 4.0 * TRIANGLE_HEIGHT, 1.0f, 1.0f }) : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss * Geometry::scale_transform({ v12ss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::WHITE().data_array()); + m_dimensioning.line.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrow 1 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q12ss : + ss_to_ndc_matrix * Geometry::translation_transform(v1ss_3) * q21ss); + m_dimensioning.triangle.render(); + + // arrow 2 + shader->set_uniform("view_model_matrix", overlap ? + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q21ss : + ss_to_ndc_matrix * Geometry::translation_transform(v2ss_3) * q12ss); + m_dimensioning.triangle.render(); + + const bool use_inches = wxGetApp().app_config->get("use_inches") == "1"; + const double curr_value = use_inches ? GizmoObjectManipulation::mm_to_in * distance : distance; + const std::string curr_value_str = format_double(curr_value); + const std::string units = use_inches ? _u8L("in") : _u8L("mm"); + const float value_str_width = 20.0f + ImGui::CalcTextSize(curr_value_str.c_str()).x; + static double edit_value = 0.0; + + const Vec2d label_position = 0.5 * (v1ss + v2ss); + m_imgui->set_next_window_pos(label_position.x(), viewport[3] - label_position.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + + if (!m_editing_distance) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + m_imgui->begin(std::string("distance"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = curr_value_str + " " + units; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, + ImGuiWrapper::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + ImGui::SameLine(); + if (m_imgui->image_button(ImGui::SliderFloatEditBtnIcon, _L("Edit to scale"))) { + m_editing_distance = true; + edit_value = curr_value; + m_imgui->requires_extra_frame(); + } + m_imgui->end(); + ImGui::PopStyleVar(3); + } + + if (m_editing_distance && !ImGui::IsPopupOpen("distance_popup")) + ImGui::OpenPopup("distance_popup"); + + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 1.0f, 1.0f }); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, { 4.0f, 0.0f }); + if (ImGui::BeginPopupModal("distance_popup", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration)) { + auto perform_scale = [this](double new_value, double old_value) { + if (new_value == old_value || new_value <= 0.0) + return; + + const double ratio = new_value / old_value; + wxGetApp().plater()->take_snapshot("Scale"); + + struct TrafoData + { + double ratio; + Vec3d old_pivot; + Vec3d new_pivot; + Transform3d scale_matrix; + + TrafoData(double ratio, const Vec3d& old_pivot, const Vec3d& new_pivot) { + this->ratio = ratio; + this->scale_matrix = Geometry::scale_transform(ratio); + this->old_pivot = old_pivot; + this->new_pivot = new_pivot; + } + + Vec3d transform(const Vec3d& point) const { return this->scale_matrix * (point - this->old_pivot) + this->new_pivot; } + }; + + auto scale_feature = [](Measure::SurfaceFeature& feature, const TrafoData& trafo_data) { + switch (feature.get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + feature = Measure::SurfaceFeature(trafo_data.transform(feature.get_point())); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + const auto [from, to] = feature.get_edge(); + const std::optional extra = feature.get_extra_point(); + const std::optional new_extra = extra.has_value() ? trafo_data.transform(*extra) : extra; + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, trafo_data.transform(from), trafo_data.transform(to), new_extra); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + const auto [center, radius, normal] = feature.get_circle(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Circle, trafo_data.transform(center), normal, std::nullopt, trafo_data.ratio * radius); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + const auto [idx, normal, origin] = feature.get_plane(); + feature = Measure::SurfaceFeature(Measure::SurfaceFeatureType::Plane, normal, trafo_data.transform(origin), std::nullopt, idx); + break; + } + default: { break; } + } + }; + + // apply scale + TransformationType type; + type.set_world(); + type.set_relative(); + type.set_joint(); + + // scale selection + Selection& selection = m_parent.get_selection(); + const Vec3d old_center = selection.get_bounding_box().center(); + selection.setup_cache(); + selection.scale(ratio * Vec3d::Ones(), type); + wxGetApp().plater()->canvas3D()->do_scale(""); // avoid storing another snapshot + // ORCA TODO + // wxGetApp().obj_manipul()->set_dirty(); + + // scale dimensioning + const Vec3d new_center = selection.get_bounding_box().center(); + const TrafoData trafo_data(ratio, old_center, new_center); + scale_feature(*m_selected_features.first.feature, trafo_data); + if (m_selected_features.second.feature.has_value()) + scale_feature(*m_selected_features.second.feature, trafo_data); + + // update measure on next call to data_changed() + m_pending_scale = true; + }; + auto action_exit = [this]() { + m_editing_distance = false; + m_is_editing_distance_first_frame = true; + ImGui::CloseCurrentPopup(); + }; + auto action_scale = [perform_scale, action_exit](double new_value, double old_value) { + perform_scale(new_value, old_value); + action_exit(); + }; + + // ORCA TODO + // m_imgui->disable_background_fadeout_animation(); + ImGui::PushItemWidth(value_str_width); + if (ImGui::InputDouble("##distance", &edit_value, 0.0f, 0.0f, "%.3f")) { + } + + // trick to auto-select text in the input widgets on 1st frame + if (m_is_editing_distance_first_frame) { + ImGui::SetKeyboardFocusHere(0); + m_is_editing_distance_first_frame = false; + m_imgui->set_requires_extra_frame(); + } + + // handle keys input + if (ImGui::IsKeyPressedMap(ImGuiKey_Enter) || ImGui::IsKeyPressedMap(ImGuiKey_KeyPadEnter)) + action_scale(edit_value, curr_value); + else if (ImGui::IsKeyPressedMap(ImGuiKey_Escape)) + action_exit(); + + ImGui::SameLine(); + if (m_imgui->button(_CTX(L_CONTEXT("Scale", "Verb"), "Verb"))) + action_scale(edit_value, curr_value); + ImGui::SameLine(); + if (m_imgui->button(_L("Cancel"))) + action_exit(); + ImGui::EndPopup(); + } + ImGui::PopStyleVar(4); + }; + + auto point_edge = [this, shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Point && f2.get_type() == Measure::SurfaceFeatureType::Edge); + std::pair e = f2.get_edge(); + const Vec3d v_proj = m_measurement_result.distance_infinite->to; + const Vec3d e1e2 = e.second - e.first; + const Vec3d v_proje1 = v_proj - e.first; + const bool on_e1_side = v_proje1.dot(e1e2) < -EPSILON; + const bool on_e2_side = !on_e1_side && v_proje1.norm() > e1e2.norm(); + if (on_e1_side || on_e2_side) { + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + const Transform3d ss_to_ndc_matrix = TransformHelper::ndc_to_ss_matrix_inverse(viewport); + + const Vec2d v_projss = TransformHelper::world_to_ss(v_proj, projection_view_matrix, viewport); + auto render_extension = [this, &v_projss, &projection_view_matrix, &viewport, &ss_to_ndc_matrix, shader](const Vec3d& p) { + const Vec2d pss = TransformHelper::world_to_ss(p, projection_view_matrix, viewport); + if (!pss.isApprox(v_projss)) { + const Vec2d pv_projss = v_projss - pss; + const double pv_projss_len = pv_projss.norm(); + + const auto q = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Vec3d(pv_projss.x(), pv_projss.y(), 0.0)); + + shader->set_uniform("projection_matrix", Transform3d::Identity()); + shader->set_uniform("view_model_matrix", ss_to_ndc_matrix * Geometry::translation_transform({ pss.x(), pss.y(), 0.0 }) * q * + Geometry::scale_transform({ pv_projss_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + }; + + render_extension(on_e1_side ? e.first : e.second); + } + }; + + auto arc_edge_edge = [this, &shader](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2, double radius = 0.0) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Edge); + if (!m_measurement_result.angle.has_value()) + return; + + const double angle = m_measurement_result.angle->angle; + const Vec3d center = m_measurement_result.angle->center; + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + const bool coplanar = m_measurement_result.angle->coplanar; + + if (std::abs(angle) < EPSILON || std::abs(calc_radius) < EPSILON) + return; + + const double draw_radius = (radius > 0.0) ? radius : calc_radius; + + const Vec3d e1_unit = Measure::edge_direction(e1); + const Vec3d e2_unit = Measure::edge_direction(e2); + + const unsigned int resolution = std::max(2, 64 * angle / double(PI)); + const double step = angle / double(resolution); + const Vec3d normal = e1_unit.cross(e2_unit).normalized(); + + if (!m_dimensioning.arc.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::LineStrip, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(resolution + 1); + init_data.reserve_indices(resolution + 1); + + // vertices + indices + for (unsigned int i = 0; i <= resolution; ++i) { + const double a = step * double(i); + const Vec3d v = draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(a, normal)) * e1_unit); + init_data.add_vertex((Vec3f)v.cast()); + init_data.add_index(i); + } + + m_dimensioning.arc.init_from(std::move(init_data)); + } + + const Camera& camera = wxGetApp().plater()->get_camera(); +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("dashed_thick_lines"); + if (shader == nullptr) + return; + + shader->start_using(); + shader->set_uniform("projection_matrix", Transform3d::Identity()); + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 1.0f); + shader->set_uniform("gap_size", 0.0f); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(2.0f)); + + // arc + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center)); + m_dimensioning.arc.render(); + +#if ENABLE_GL_CORE_PROFILE + if (OpenGLManager::get_gl_info().is_core_profile()) { + shader->stop_using(); + + shader = wxGetApp().get_shader("flat"); + if (shader == nullptr) + return; + + shader->start_using(); + } + else +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth(1.0f)); + + // arrows + auto render_arrow = [this, shader, &camera, &normal, ¢er, &e1_unit, draw_radius, step, resolution](unsigned int endpoint_id) { + const double angle = (endpoint_id == 1) ? 0.0 : step * double(resolution); + const Vec3d position_model = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(angle, normal)) * e1_unit)); + const Vec3d direction_model = (endpoint_id == 1) ? -normal.cross(position_model - center).normalized() : normal.cross(position_model - center).normalized(); + const auto qz = Eigen::Quaternion::FromTwoVectors(Vec3d::UnitZ(), (endpoint_id == 1) ? normal : -normal); + const auto qx = Eigen::Quaternion::FromTwoVectors(qz * Vec3d::UnitX(), direction_model); + const Transform3d view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform(position_model) * + qx * qz * Geometry::scale_transform(camera.get_inv_zoom()); + shader->set_uniform("view_model_matrix", view_model_matrix); + m_dimensioning.triangle.render(); + }; + + glsafe(::glDisable(GL_CULL_FACE)); + render_arrow(1); + render_arrow(2); + glsafe(::glEnable(GL_CULL_FACE)); + + // edge 1 extension + const Vec3d e11e12 = e1.second - e1.first; + const Vec3d e11center = center - e1.first; + const double e11center_len = e11center.norm(); + if (e11center_len > EPSILON && e11center.dot(e11e12) < 0.0) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e1.first, e1.second)) * + Geometry::scale_transform({ e11center_len, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + + // edge 2 extension + const Vec3d e21center = center - e2.first; + const double e21center_len = e21center.norm(); + if (e21center_len > EPSILON) { + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::translation_transform(center) * + Eigen::Quaternion::FromTwoVectors(Vec3d::UnitX(), Measure::edge_direction(e2.first, e2.second)) * + Geometry::scale_transform({ (coplanar && radius > 0.0) ? e21center_len : draw_radius, 1.0f, 1.0f })); + m_dimensioning.line.set_color(ColorRGBA::LIGHT_GRAY()); + m_dimensioning.line.render(); + } + + // label + // label world coordinates + const Vec3d label_position_world = Geometry::translation_transform(center) * (draw_radius * (Eigen::Quaternion(Eigen::AngleAxisd(step * 0.5 * double(resolution), normal)) * e1_unit)); + + // label screen coordinates + const std::array& viewport = camera.get_viewport(); + const Vec2d label_position_ss = TransformHelper::world_to_ss(label_position_world, + camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(), viewport); + + m_imgui->set_next_window_pos(label_position_ss.x(), viewport[3] - label_position_ss.y(), ImGuiCond_Always, 0.0f, 1.0f); + m_imgui->set_next_window_bg_alpha(0.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + m_imgui->begin(wxString("##angle"), ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); + ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow()); + ImGui::AlignTextToFramePadding(); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const std::string txt = format_double(Geometry::rad2deg(angle)) + "°"; + ImVec2 txt_size = ImGui::CalcTextSize(txt.c_str()); + const ImGuiStyle& style = ImGui::GetStyle(); + draw_list->AddRectFilled({ pos.x - style.FramePadding.x, pos.y + style.FramePadding.y }, { pos.x + txt_size.x + 2.0f * style.FramePadding.x , pos.y + txt_size.y + 2.0f * style.FramePadding.y }, + ImGuiWrapper::to_ImU32(ColorRGBA(0.5f, 0.5f, 0.5f, 0.5f))); + ImGui::SetCursorScreenPos({ pos.x + style.FramePadding.x, pos.y }); + m_imgui->text(txt); + m_imgui->end(); + ImGui::PopStyleVar(); + }; + + auto arc_edge_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Edge && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + auto arc_plane_plane = [this, arc_edge_edge](const Measure::SurfaceFeature& f1, const Measure::SurfaceFeature& f2) { + assert(f1.get_type() == Measure::SurfaceFeatureType::Plane && f2.get_type() == Measure::SurfaceFeatureType::Plane); + if (!m_measurement_result.angle.has_value()) + return; + + const std::pair e1 = m_measurement_result.angle->e1; + const std::pair e2 = m_measurement_result.angle->e2; + const double calc_radius = m_measurement_result.angle->radius; + + if (calc_radius == 0.0) + return; + + arc_edge_edge(Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e1.first, e1.second), + Measure::SurfaceFeature(Measure::SurfaceFeatureType::Edge, e2.first, e2.second), calc_radius); + }; + + shader->start_using(); + + if (!m_dimensioning.line.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(1.0f, 0.0f, 0.0f)); + + // indices + init_data.add_line(0, 1); + + m_dimensioning.line.init_from(std::move(init_data)); + } + + if (!m_dimensioning.triangle.is_initialized()) { + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::WHITE(); + init_data.reserve_vertices(3); + init_data.reserve_indices(3); + + // vertices + init_data.add_vertex(Vec3f(0.0f, 0.0f, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, 0.5f * TRIANGLE_BASE, 0.0f)); + init_data.add_vertex(Vec3f(-TRIANGLE_HEIGHT, -0.5f * TRIANGLE_BASE, 0.0f)); + + // indices + init_data.add_triangle(0, 1, 2); + + m_dimensioning.triangle.init_from(std::move(init_data)); + } + + if (last_selected_features != m_selected_features) + m_dimensioning.arc.reset(); + + glsafe(::glDisable(GL_DEPTH_TEST)); + + const bool has_distance = m_measurement_result.has_distance_data(); + + const Measure::SurfaceFeature* f1 = &(*m_selected_features.first.feature); + const Measure::SurfaceFeature* f2 = nullptr; + std::unique_ptr temp_feature; + if (m_selected_features.second.feature.has_value()) + f2 = &(*m_selected_features.second.feature); + else { + assert(m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle); + temp_feature = std::make_unique(std::get<0>(m_selected_features.first.feature->get_circle())); + f2 = temp_feature.get(); + } + + if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() != Measure::SurfaceFeatureType::Circle) + return; + + Measure::SurfaceFeatureType ft1 = f1->get_type(); + Measure::SurfaceFeatureType ft2 = f2->get_type(); + + // Order features by type so following conditions are simple. + if (ft1 > ft2) { + std::swap(ft1, ft2); + std::swap(f1, f2); + } + + // If there is an angle to show, draw the arc: + if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Edge) + arc_edge_edge(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Edge && ft2 == Measure::SurfaceFeatureType::Plane) + arc_edge_plane(*f1, *f2); + else if (ft1 == Measure::SurfaceFeatureType::Plane && ft2 == Measure::SurfaceFeatureType::Plane) + arc_plane_plane(*f1, *f2); + + if (has_distance){ + // Where needed, draw the extension of the edge to where the dist is measured: + if (ft1 == Measure::SurfaceFeatureType::Point && ft2 == Measure::SurfaceFeatureType::Edge) + point_edge(*f1, *f2); + + // Render the arrow between the points that the backend passed: + const Measure::DistAndPoints& dap = m_measurement_result.distance_infinite.has_value() + ? *m_measurement_result.distance_infinite + : *m_measurement_result.distance_strict; + point_point(dap.from, dap.to, dap.dist); + } + + glsafe(::glEnable(GL_DEPTH_TEST)); + + shader->stop_using(); +} + +static void add_row_to_table(std::function col_1 = nullptr, std::function col_2 = nullptr) +{ + assert(col_1 != nullptr && col_2 != nullptr); + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + col_1(); + ImGui::TableSetColumnIndex(1); + col_2(); +} + +static void add_strings_row_to_table(ImGuiWrapper& imgui, const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) +{ + add_row_to_table([&]() { imgui.text_colored(col_1_color, col_1); }, [&]() { imgui.text_colored(col_2_color, col_2); }); +}; + +#if ENABLE_MEASURE_GIZMO_DEBUG +void GLGizmoMeasure::render_debug_dialog() +{ + auto add_feature_data = [this](const SelectedFeatures::Item& item) { + const std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + add_strings_row_to_table(*m_imgui, "Type", ImGuiWrapper::COL_ORANGE_LIGHT, text, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + switch (item.feature->get_type()) + { + case Measure::SurfaceFeatureType::Point: + { + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(item.feature->get_point()), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Edge: + { + auto [from, to] = item.feature->get_edge(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(from), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(to), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Plane: + { + auto [idx, normal, origin] = item.feature->get_plane(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(origin), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_ORANGE_LIGHT, format_double(idx), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + case Measure::SurfaceFeatureType::Circle: + { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + add_strings_row_to_table(*m_imgui, "m_pt1", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(center), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_pt2", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(normal), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + add_strings_row_to_table(*m_imgui, "m_value", ImGuiWrapper::COL_ORANGE_LIGHT, format_double(radius), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + break; + } + } + std::optional extra_point = item.feature->get_extra_point(); + if (extra_point.has_value()) + add_strings_row_to_table(*m_imgui, "m_pt3", ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(*extra_point), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + }; + + m_imgui->begin("Measure tool debug", ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + if (ImGui::BeginTable("Mode", 2)) { + std::string txt; + switch (m_mode) + { + case EMode::FeatureSelection: { txt = "Feature selection"; break; } + case EMode::PointSelection: { txt = "Point selection"; break; } + default: { assert(false); break; } + } + add_strings_row_to_table(*m_imgui, "Mode", ImGuiWrapper::COL_ORANGE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (ImGui::BeginTable("Hover", 2)) { + add_strings_row_to_table(*m_imgui, "Hover id", ImGuiWrapper::COL_ORANGE_LIGHT, std::to_string(m_hover_id), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + const std::string txt = m_curr_feature.has_value() ? surface_feature_type_as_string(m_curr_feature->get_type()) : "None"; + add_strings_row_to_table(*m_imgui, "Current feature", ImGuiWrapper::COL_ORANGE_LIGHT, txt, ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ImGui::EndTable(); + } + + ImGui::Separator(); + if (!m_selected_features.first.feature.has_value() && !m_selected_features.second.feature.has_value()) + m_imgui->text("Empty selection"); + else { + const ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH; + if (m_selected_features.first.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Selection 1"); + if (ImGui::BeginTable("Selection 1", 2, flags)) { + add_feature_data(m_selected_features.first); + ImGui::EndTable(); + } + } + if (m_selected_features.second.feature.has_value()) { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Selection 2"); + if (ImGui::BeginTable("Selection 2", 2, flags)) { + add_feature_data(m_selected_features.second); + ImGui::EndTable(); + } + } + } + m_imgui->end(); +} +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +void GLGizmoMeasure::on_render_input_window(float x, float y, float bottom_limit) +{ + static std::optional last_feature; + static EMode last_mode = EMode::FeatureSelection; + static SelectedFeatures last_selected_features; + + static float last_y = 0.0f; + static float last_h = 0.0f; + + if (m_editing_distance) + return; + + m_imgui->begin(get_name(), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + // adjust window position to avoid overlap the view toolbar + const float win_h = ImGui::GetWindowHeight(); + y = std::min(y, bottom_limit - win_h); + ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always); + if (last_h != win_h || last_y != y) { + // ask canvas for another frame to render the window in the correct position + m_imgui->set_requires_extra_frame(); + if (last_h != win_h) + last_h = win_h; + if (last_y != y) + last_y = y; + } + + if (ImGui::BeginTable("Commands", 2)) { + unsigned int row_count = 1; + add_row_to_table( + [this]() { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Left mouse button")); + }, + [this]() { + std::string text; + ColorRGBA color; + if (m_selected_features.second.feature.has_value()) { + if (m_selected_features.first.feature == m_curr_feature && m_mode == EMode::FeatureSelection) { + // hovering over 1st selected feature + text = _u8L("Unselect feature"); + color = SELECTED_1ST_COLOR; + } + else if (m_hover_id == SEL_SPHERE_1_ID) { + if (m_selected_features.first.is_center) { + // hovering over center selected as 1st feature + text = _u8L("Unselect center"); + color = SELECTED_1ST_COLOR; + } + else if (is_feature_with_center(*m_selected_features.first.feature)) { + // hovering over center of 1st selected feature + text = _u8L("Select center"); + color = SELECTED_1ST_COLOR; + } + else { + // hovering over point selected as 1st feature + text = _u8L("Unselect point"); + color = SELECTED_1ST_COLOR; + } + } + else if (m_selected_features.first.is_center && m_selected_features.first.source == m_curr_feature) { + // hovering over feature whose center is selected as 1st feature + text = _u8L("Select feature"); + color = SELECTED_1ST_COLOR; + } + else if (m_selected_features.second.feature == m_curr_feature && m_mode == EMode::FeatureSelection) { + // hovering over 2nd selected feature + text = _u8L("Unselect feature"); + color = SELECTED_2ND_COLOR; + } + else if (m_hover_id == SEL_SPHERE_2_ID) { + if (m_selected_features.second.is_center) { + // hovering over center selected as 2nd feature + text = _u8L("Unselect feature"); + color = SELECTED_2ND_COLOR; + } + else if (is_feature_with_center(*m_selected_features.second.feature)) { + // hovering over center of 2nd selected feature + text = _u8L("Select center"); + color = SELECTED_2ND_COLOR; + } + else { + // hovering over point selected as 2nd feature + text = _u8L("Unselect point"); + color = SELECTED_2ND_COLOR; + } + } + else if (m_selected_features.second.is_center && m_selected_features.second.source == m_curr_feature) { + // hovering over feature whose center is selected as 2nd feature + text = _u8L("Select feature"); + color = SELECTED_2ND_COLOR; + } + else { + // 1st feature selected + text = (m_mode == EMode::PointSelection) ? _u8L("Select point") : _u8L("Select feature"); + color = SELECTED_2ND_COLOR; + } + } + else { + if (m_selected_features.first.feature.has_value()) { + if (m_selected_features.first.feature == m_curr_feature && m_mode == EMode::FeatureSelection) { + // hovering over 1st selected feature + text = _u8L("Unselect feature"); + color = SELECTED_1ST_COLOR; + } + else { + if (m_hover_id == SEL_SPHERE_1_ID) { + if (m_selected_features.first.is_center) { + // hovering over center selected as 1st feature + text = _u8L("Unselect feature"); + color = SELECTED_1ST_COLOR; + } + else if (is_feature_with_center(*m_selected_features.first.feature)) { + // hovering over center of 1st selected feature + text = _u8L("Select center"); + color = SELECTED_1ST_COLOR; + } + else { + // hovering over point selected as 1st feature + text = _u8L("Unselect point"); + color = SELECTED_1ST_COLOR; + } + } + else { + if (m_selected_features.first.is_center && m_selected_features.first.source == m_curr_feature) { + // hovering over feature whose center is selected as 1st feature + text = _u8L("Select feature"); + color = SELECTED_1ST_COLOR; + } + else { + // 1st feature selected + text = (m_mode == EMode::PointSelection) ? _u8L("Select point") : _u8L("Select feature"); + color = SELECTED_2ND_COLOR; + } + } + } + } + else { + // nothing is selected + text = (m_mode == EMode::PointSelection) ? _u8L("Select point") : _u8L("Select feature"); + color = SELECTED_1ST_COLOR; + } + } + + assert(!text.empty()); + + m_imgui->text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), text); + ImGui::SameLine(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const float rect_size = ImGui::GetTextLineHeight(); + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiWrapper::to_ImU32(color)); + ImGui::Dummy(ImVec2(rect_size, rect_size)); + } + ); + + if (m_mode == EMode::FeatureSelection && m_hover_id != -1) { + add_strings_row_to_table(*m_imgui, "Shift", ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Enable point selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++row_count; + } + + if (m_selected_features.first.feature.has_value()) { + add_strings_row_to_table(*m_imgui, "Delete", ImGuiWrapper::COL_ORANGE_LIGHT, _u8L("Restart selection"), ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++row_count; + } + + if (m_selected_features.first.feature.has_value() || m_selected_features.second.feature.has_value()) { + add_row_to_table( + [this]() { + m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, "Esc"); + }, + [this]() { + m_imgui->text_colored(ImGui::GetStyleColorVec4(ImGuiCol_Text), _u8L("Unselect")); + ImGui::SameLine(); + const ImVec2 pos = ImGui::GetCursorScreenPos(); + const float rect_size = ImGui::GetTextLineHeight(); + const ColorRGBA color = m_selected_features.second.feature.has_value() ? SELECTED_2ND_COLOR : SELECTED_1ST_COLOR; + ImGui::GetWindowDrawList()->AddRectFilled(ImVec2(pos.x + 1.0f, pos.y + 1.0f), ImVec2(pos.x + rect_size, pos.y + rect_size), ImGuiWrapper::to_ImU32(color)); + ImGui::Dummy(ImVec2(rect_size, rect_size)); + } + ); + + ++row_count; + } + + // add dummy rows to keep dialog size fixed + for (unsigned int i = row_count; i < 4; ++i) { + add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_ORANGE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + + ImGui::EndTable(); + } + + const bool use_inches = wxGetApp().app_config->get("use_inches") == "1"; + const std::string units = use_inches ? " " + _u8L("in") : " " + _u8L("mm"); + + ImGui::Separator(); + const ImGuiTableFlags flags = ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersH; + if (ImGui::BeginTable("Selection", 2, flags)) { + auto format_item_text = [this, use_inches, &units](const SelectedFeatures::Item& item) { + if (!item.feature.has_value()) + return _u8L("None"); + + std::string text = (item.source == item.feature) ? surface_feature_type_as_string(item.feature->get_type()) : + item.is_center ? center_on_feature_type_as_string(item.source->get_type()) : point_on_feature_type_as_string(item.source->get_type(), m_hover_id); + if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Circle) { + auto [center, radius, normal] = item.feature->get_circle(); + const Vec3d on_circle = center + radius * Measure::get_orthogonal(normal, true); + radius = (on_circle - center).norm(); + if (use_inches) + radius = GizmoObjectManipulation::mm_to_in * radius; + text += " (" + _u8L("Diameter") + ": " + format_double(2.0 * radius) + units + ")"; + } + else if (item.feature.has_value() && item.feature->get_type() == Measure::SurfaceFeatureType::Edge) { + auto [start, end] = item.feature->get_edge(); + double length = (end - start).norm(); + if (use_inches) + length = GizmoObjectManipulation::mm_to_in * length; + text += " (" + _u8L("Length") + ": " + format_double(length) + units + ")"; + } + return text; + }; + + add_strings_row_to_table(*m_imgui, _u8L("Selection") + " 1:", ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR), format_item_text(m_selected_features.first), + ImGuiWrapper::to_ImVec4(SELECTED_1ST_COLOR)); + add_strings_row_to_table(*m_imgui, _u8L("Selection") + " 2:", ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR), format_item_text(m_selected_features.second), + ImGuiWrapper::to_ImVec4(SELECTED_2ND_COLOR)); + ImGui::EndTable(); + } + + m_imgui->disabled_begin(!m_selected_features.first.feature.has_value()); + if (m_imgui->button(_L("Restart selection"))) { + m_selected_features.reset(); + m_selected_sphere_raycasters.clear(); + m_imgui->set_requires_extra_frame(); + } + m_imgui->disabled_end(); + + auto add_measure_row_to_table = [this](const std::string& col_1, const ImVec4& col_1_color, const std::string& col_2, const ImVec4& col_2_color) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + m_imgui->text_colored(col_1_color, col_1); + ImGui::TableSetColumnIndex(1); + m_imgui->text_colored(col_2_color, col_2); + ImGui::TableSetColumnIndex(2); + if (m_imgui->image_button(ImGui::ClipboardBtnIcon, _L("Copy to clipboard"))) { + wxTheClipboard->Open(); + wxTheClipboard->SetData(new wxTextDataObject(col_1 + ": " + col_2)); + wxTheClipboard->Close(); + } + }; + + ImGui::Separator(); + m_imgui->text(_u8L("Measure")); + + const unsigned int max_measure_row_count = 2; + unsigned int measure_row_count = 0; + if (ImGui::BeginTable("Measure", 4)) { + if (m_selected_features.second.feature.has_value()) { + const Measure::MeasurementResult& measure = m_measurement_result; + if (measure.angle.has_value()) { + ImGui::PushID("ClipboardAngle"); + add_measure_row_to_table(_u8L("Angle"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(Geometry::rad2deg(measure.angle->angle)) + "°", + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + + const bool show_strict = measure.distance_strict.has_value() && + (!measure.distance_infinite.has_value() || std::abs(measure.distance_strict->dist - measure.distance_infinite->dist) > EPSILON); + + if (measure.distance_infinite.has_value()) { + double distance = measure.distance_infinite->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceInfinite"); + add_measure_row_to_table(show_strict ? _u8L("Perpendicular distance") : _u8L("Distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (show_strict) { + double distance = measure.distance_strict->dist; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceStrict"); + add_measure_row_to_table(_u8L("Direct distance"), ImGuiWrapper::COL_ORANGE_LIGHT, format_double(distance) + units, + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + if (measure.distance_xyz.has_value() && measure.distance_xyz->norm() > EPSILON) { + Vec3d distance = *measure.distance_xyz; + if (use_inches) + distance = GizmoObjectManipulation::mm_to_in * distance; + ImGui::PushID("ClipboardDistanceXYZ"); + add_measure_row_to_table(_u8L("Distance XYZ"), ImGuiWrapper::COL_ORANGE_LIGHT, format_vec3(distance), + ImGui::GetStyleColorVec4(ImGuiCol_Text)); + ++measure_row_count; + ImGui::PopID(); + } + } + + // add dummy rows to keep dialog size fixed + for (unsigned int i = measure_row_count; i < max_measure_row_count; ++i) { + add_strings_row_to_table(*m_imgui, " ", ImGuiWrapper::COL_ORANGE_LIGHT, " ", ImGui::GetStyleColorVec4(ImGuiCol_Text)); + } + ImGui::EndTable(); + } + + if (last_feature != m_curr_feature || last_mode != m_mode || last_selected_features != m_selected_features) { + // the dialog may have changed its size, ask for an extra frame to render it properly + last_feature = m_curr_feature; + last_mode = m_mode; + last_selected_features = m_selected_features; + m_imgui->set_requires_extra_frame(); + } + + m_imgui->end(); +} + +void GLGizmoMeasure::on_register_raycasters_for_picking() +{ + // the features are rendered on top of the scene, so the raytraced picker should take it into account + m_parent.set_raycaster_gizmos_on_top(true); +} + +void GLGizmoMeasure::on_unregister_raycasters_for_picking() +{ + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo); + m_parent.set_raycaster_gizmos_on_top(false); + m_raycasters.clear(); + m_selected_sphere_raycasters.clear(); +} + +void GLGizmoMeasure::remove_selected_sphere_raycaster(int id) +{ + auto it = std::find_if(m_selected_sphere_raycasters.begin(), m_selected_sphere_raycasters.end(), + [id](std::shared_ptr item) { return SceneRaycaster::decode_id(SceneRaycaster::EType::Gizmo, item->get_id()) == id; }); + if (it != m_selected_sphere_raycasters.end()) + m_selected_sphere_raycasters.erase(it); + m_parent.remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, id); +} + +void GLGizmoMeasure::update_measurement_result() +{ + if (!m_selected_features.first.feature.has_value()) + m_measurement_result = Measure::MeasurementResult(); + else if (m_selected_features.second.feature.has_value()) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, *m_selected_features.second.feature, m_measuring.get()); + else if (!m_selected_features.second.feature.has_value() && m_selected_features.first.feature->get_type() == Measure::SurfaceFeatureType::Circle) + m_measurement_result = Measure::get_measurement(*m_selected_features.first.feature, Measure::SurfaceFeature(std::get<0>(m_selected_features.first.feature->get_circle())), m_measuring.get()); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp new file mode 100644 index 00000000000..6a8f21a1991 --- /dev/null +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeasure.hpp @@ -0,0 +1,186 @@ +#ifndef slic3r_GLGizmoMeasure_hpp_ +#define slic3r_GLGizmoMeasure_hpp_ + +#include "GLGizmoBase.hpp" +#include "slic3r/GUI/GLModel.hpp" +#include "slic3r/GUI/GUI_Utils.hpp" +#include "slic3r/GUI/MeshUtils.hpp" +#include "slic3r/GUI/I18N.hpp" +#include "libslic3r/Measure.hpp" +#include "libslic3r/Model.hpp" + +namespace Slic3r { + +enum class ModelVolumeType : int; + +namespace Measure { class Measuring; } + + +namespace GUI { + +enum class SLAGizmoEventType : unsigned char; + +class GLGizmoMeasure : public GLGizmoBase +{ + enum class EMode : unsigned char + { + FeatureSelection, + PointSelection + }; + + struct SelectedFeatures + { + struct Item + { + bool is_center{ false }; + std::optional source; + std::optional feature; + + bool operator == (const Item& other) const { + return this->is_center == other.is_center && this->source == other.source && this->feature == other.feature; + } + + bool operator != (const Item& other) const { + return !operator == (other); + } + + void reset() { + is_center = false; + source.reset(); + feature.reset(); + } + }; + + Item first; + Item second; + + void reset() { + first.reset(); + second.reset(); + } + + bool operator == (const SelectedFeatures & other) const { + if (this->first != other.first) return false; + return this->second == other.second; + } + + bool operator != (const SelectedFeatures & other) const { + return !operator == (other); + } + }; + + struct VolumeCacheItem + { + const ModelObject* object{ nullptr }; + const ModelInstance* instance{ nullptr }; + const ModelVolume* volume{ nullptr }; + Transform3d world_trafo; + + bool operator == (const VolumeCacheItem& other) const { + return this->object == other.object && this->instance == other.instance && this->volume == other.volume && + this->world_trafo.isApprox(other.world_trafo); + } + }; + + std::vector m_volumes_cache; + + EMode m_mode{ EMode::FeatureSelection }; + Measure::MeasurementResult m_measurement_result; + + std::unique_ptr m_measuring; // PIMPL + + PickingModel m_sphere; + PickingModel m_cylinder; + PickingModel m_circle; + PickingModel m_plane; + struct Dimensioning + { + GLModel line; + GLModel triangle; + GLModel arc; + }; + Dimensioning m_dimensioning; + + // Uses a standalone raycaster and not the shared one because of the + // difference in how the mesh is updated + std::unique_ptr m_raycaster; + + std::vector m_plane_models_cache; + std::map> m_raycasters; + // used to keep the raycasters for point/center spheres + std::vector> m_selected_sphere_raycasters; + std::optional m_curr_feature; + std::optional m_curr_point_on_feature_position; + struct SceneRaycasterState + { + std::shared_ptr raycaster{ nullptr }; + bool state{true}; + + }; + std::vector m_scene_raycasters; + + // These hold information to decide whether recalculation is necessary: + float m_last_inv_zoom{ 0.0f }; + std::optional m_last_circle; + int m_last_plane_idx{ -1 }; + + bool m_mouse_left_down{ false }; // for detection left_up of this gizmo + + Vec2d m_mouse_pos{ Vec2d::Zero() }; + + KeyAutoRepeatFilter m_shift_kar_filter; + + SelectedFeatures m_selected_features; + bool m_pending_scale{ false }; + bool m_editing_distance{ false }; + bool m_is_editing_distance_first_frame{ true }; + + void update_if_needed(); + + void disable_scene_raycasters(); + void restore_scene_raycasters_state(); + + void render_dimensioning(); + +#if ENABLE_MEASURE_GIZMO_DEBUG + void render_debug_dialog(); +#endif // ENABLE_MEASURE_GIZMO_DEBUG + +public: + GLGizmoMeasure(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); + + /// + /// Apply rotation on select plane + /// + /// Keep information about mouse click + /// Return True when use the information otherwise False. + bool on_mouse(const wxMouseEvent &mouse_event) override; + + void data_changed(bool is_serializing) override; + + bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); + + bool wants_enter_leave_snapshots() const override { return true; } + std::string get_gizmo_entering_text() const override { return _u8L("Entering Measure gizmo"); } + std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Measure gizmo"); } + std::string get_action_snapshot_name() const override { return _u8L("Measure gizmo editing"); } + +protected: + bool on_init() override; + std::string on_get_name() const override; + bool on_is_activable() const override; + void on_render() override; + void on_set_state() override; + + virtual void on_render_input_window(float x, float y, float bottom_limit) override; + virtual void on_register_raycasters_for_picking() override; + virtual void on_unregister_raycasters_for_picking() override; + + void remove_selected_sphere_raycaster(int id); + void update_measurement_result(); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_GLGizmoMeasure_hpp_ diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp index 42fa97eede1..f88ac85c976 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp @@ -62,7 +62,6 @@ class GLGizmoMeshBoolean : public GLGizmoBase virtual std::string on_get_name() const override; virtual bool on_is_activable() const override; virtual void on_render() override; - virtual void on_render_for_picking() override {} virtual void on_set_state() override; virtual CommonGizmosDataID on_get_requirements() const override; virtual void on_render_input_window(float x, float y, float bottom_limit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index 517e880d936..eff1f7878f1 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -146,7 +146,7 @@ GLGizmoMmuSegmentation::GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::st { } -void GLGizmoMmuSegmentation::render_painter_gizmo() const +void GLGizmoMmuSegmentation::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index 6b5cde25c73..8decc813c50 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -67,7 +67,7 @@ class GLGizmoMmuSegmentation : public GLGizmoPainterBase GLGizmoMmuSegmentation(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoMmuSegmentation() override = default; - void render_painter_gizmo() const override; + void render_painter_gizmo() override; void set_painter_gizmo_data(const Selection& selection) override; @@ -103,7 +103,7 @@ class GLGizmoMmuSegmentation : public GLGizmoPainterBase std::string get_gizmo_entering_text() const override { return "Entering color painting"; } std::string get_gizmo_leaving_text() const override { return "Leaving color painting"; } - std::string get_action_snapshot_name() override { return "Color painting editing"; } + std::string get_action_snapshot_name() const override { return "Color painting editing"; } // BBS size_t m_selected_extruder_idx = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp index 747d8ec89e2..e94882feedb 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.cpp @@ -74,12 +74,10 @@ bool GLGizmoMove3D::on_is_activable() const void GLGizmoMove3D::on_start_dragging() { if (m_hover_id != -1) { - m_displacement = Vec3d::Zero(); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - m_starting_drag_position = m_grabbers[m_hover_id].center; - m_starting_box_center = box.center(); - m_starting_box_bottom_center = box.center(); - m_starting_box_bottom_center(2) = box.min(2); + m_displacement = Vec3d::Zero(); + m_starting_drag_position = m_grabbers[m_hover_id].matrix * m_grabbers[m_hover_id].center; + m_starting_box_center = m_center; + m_starting_box_bottom_center = Vec3d(m_center.x(), m_center.y(), m_bounding_box.min.z()); } } @@ -100,86 +98,134 @@ void GLGizmoMove3D::on_update(const UpdateData& data) void GLGizmoMove3D::on_render() { - const Selection& selection = m_parent.get_selection(); - glsafe(::glClear(GL_DEPTH_BUFFER_BIT)); glsafe(::glEnable(GL_DEPTH_TEST)); - const BoundingBoxf3& box = selection.get_bounding_box(); - const Vec3d& center = box.center(); - float space_size = 20.f *INV_ZOOM; - -#if ENABLE_FIXED_GRABBER - // x axis - m_grabbers[0].center = { box.max.x() + space_size, center.y(), center.z() }; - // y axis - m_grabbers[1].center = { center.x(), box.max.y() + space_size, center.z() }; - // z axis - m_grabbers[2].center = { center.x(), center.y(), box.max.z() + space_size }; + const Selection& selection = m_parent.get_selection(); + const auto& [box, box_trafo] = selection.get_bounding_box_in_current_reference_system(); + m_bounding_box = box; + m_center = box_trafo.translation(); + const Transform3d base_matrix = box_trafo; for (int i = 0; i < 3; ++i) { - m_grabbers[i].color = AXES_COLOR[i]; - m_grabbers[i].hover_color = AXES_HOVER_COLOR[i]; + m_grabbers[i].matrix = base_matrix; } -#else + + const Vec3d zero = Vec3d::Zero(); + const Vec3d half_box_size = 0.5 * m_bounding_box.size(); + // x axis - m_grabbers[0].center = { box.max.x() + Offset, center.y(), center.z() }; + m_grabbers[0].center = { half_box_size.x() + Offset, 0.0, 0.0 }; m_grabbers[0].color = AXES_COLOR[0]; // y axis - m_grabbers[1].center = { center.x(), box.max.y() + Offset, center.z() }; + m_grabbers[1].center = { 0.0, half_box_size.y() + Offset, 0.0 }; m_grabbers[1].color = AXES_COLOR[1]; // z axis - m_grabbers[2].center = { center.x(), center.y(), box.max.z() + Offset }; + m_grabbers[2].center = { 0.0, 0.0, half_box_size.z() + Offset }; m_grabbers[2].color = AXES_COLOR[2]; -#endif - glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); +#if ENABLE_GL_CORE_PROFILE + if (!OpenGLManager::get_gl_info().is_core_profile()) +#endif // ENABLE_GL_CORE_PROFILE + glsafe(::glLineWidth((m_hover_id != -1) ? 2.0f : 1.5f)); - // draw grabbers - for (unsigned int i = 0; i < 3; ++i) { - if (m_grabbers[i].enabled) render_grabber_extension((Axis) i, box, false); - } + auto render_grabber_connection = [this, &zero](unsigned int id) { + if (m_grabbers[id].enabled) { + if (!m_grabber_connections[id].model.is_initialized() || !m_grabber_connections[id].old_center.isApprox(m_grabbers[id].center)) { + m_grabber_connections[id].old_center = m_grabbers[id].center; + m_grabber_connections[id].model.reset(); - // draw axes line - // draw axes - for (unsigned int i = 0; i < 3; ++i) { - if (m_grabbers[i].enabled) { - glsafe(::glColor4fv(AXES_COLOR[i].data())); - glLineStipple(1, 0x0FFF); - glEnable(GL_LINE_STIPPLE); - ::glBegin(GL_LINES); - ::glVertex3dv(center.data()); - // use extension center - ::glVertex3dv(m_grabbers[i].center.data()); - glsafe(::glEnd()); - glDisable(GL_LINE_STIPPLE); - } - } -} + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = AXES_COLOR[id]; + init_data.vertices.reserve(2); + init_data.indices.reserve(2); -void GLGizmoMove3D::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); + // vertices + init_data.add_vertex((Vec3f)zero.cast()); + init_data.add_vertex((Vec3f)m_grabbers[id].center.cast()); - const BoundingBoxf3& box = m_parent.get_selection().get_bounding_box(); - //BBS donot render base grabber for picking - //render_grabbers_for_picking(box); + // indices + init_data.add_line(0, 1); - //get picking colors only - for (unsigned int i = 0; i < (unsigned int) m_grabbers.size(); ++i) { - if (m_grabbers[i].enabled) { - std::array color = picking_color_component(i); - m_grabbers[i].color = color; + m_grabber_connections[id].model.init_from(std::move(init_data)); + } + + m_grabber_connections[id].model.render(); } + }; + + if (m_hover_id == -1) { +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix() * base_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.25f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + + // draw axes + for (unsigned int i = 0; i < 3; ++i) { + render_grabber_connection(i); + } + + shader->stop_using(); + } + + // draw grabbers + render_grabbers(m_bounding_box); } + else { + // draw axis +#if ENABLE_GL_CORE_PROFILE + GLShaderProgram* shader = OpenGLManager::get_gl_info().is_core_profile() ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat"); +#else + GLShaderProgram* shader = wxGetApp().get_shader("flat"); +#endif // ENABLE_GL_CORE_PROFILE + if (shader != nullptr) { + shader->start_using(); + + const Camera& camera = wxGetApp().plater()->get_camera(); + shader->set_uniform("view_model_matrix", camera.get_view_matrix()* base_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); +#if ENABLE_GL_CORE_PROFILE + const std::array& viewport = camera.get_viewport(); + shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3]))); + shader->set_uniform("width", 0.5f); + shader->set_uniform("gap_size", 0.0f); +#endif // ENABLE_GL_CORE_PROFILE + + render_grabber_connection(m_hover_id); + shader->stop_using(); + } - render_grabber_extension(X, box, true); - render_grabber_extension(Y, box, true); - render_grabber_extension(Z, box, true); + shader = wxGetApp().get_shader("gouraud_light"); + if (shader != nullptr) { + shader->start_using(); + shader->set_uniform("emission_factor", 0.1f); + glsafe(::glDisable(GL_CULL_FACE)); + // draw grabber + const Vec3d box_size = m_bounding_box.size(); + const float mean_size = (float)((box_size.x() + box_size.y() + box_size.z()) / 3.0); + m_grabbers[m_hover_id].render(true, mean_size); + glsafe(::glEnable(GL_CULL_FACE)); + shader->stop_using(); + } + } } + //BBS: add input window for move void GLGizmoMove3D::on_render_input_window(float x, float y, float bottom_limit) { @@ -214,49 +260,6 @@ double GLGizmoMove3D::calc_projection(const UpdateData& data) const return projection; } -void GLGizmoMove3D::render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const -{ -#if ENABLE_FIXED_GRABBER - float mean_size = (float)(GLGizmoBase::Grabber::FixedGrabberSize); -#else - float mean_size = (float)((box.size().x() + box.size().y() + box.size().z()) / 3.0); -#endif - - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; - - std::array color = m_grabbers[axis].color; - if (!picking && m_hover_id != -1) { - if (m_hover_id == axis) { - color = m_grabbers[axis].hover_color; - } - } - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - const_cast(&m_vbo_cone)->set_color(-1, color); - if (!picking) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - } - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[axis].center.x(), m_grabbers[axis].center.y(), m_grabbers[axis].center.z())); - if (axis == X) - glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - else if (axis == Y) - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - - //glsafe(::glTranslated(0.0, 0.0, 2.0 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 2.0 * size)); - m_vbo_cone.render(); - glsafe(::glPopMatrix()); - - if (! picking) - shader->stop_using(); -} - } // namespace GUI diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp index 21d19b4465a..e15fc4a8a12 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMove.hpp @@ -16,7 +16,8 @@ class GLGizmoMove3D : public GLGizmoBase static const double Offset; Vec3d m_displacement; - + Vec3d m_center{ Vec3d::Zero() }; + BoundingBoxf3 m_bounding_box; double m_snap_step; Vec3d m_starting_drag_position; @@ -27,6 +28,12 @@ class GLGizmoMove3D : public GLGizmoBase //BBS: add size adjust related GizmoObjectManipulation* m_object_manipulation; + struct GrabberConnection + { + GLModel model; + Vec3d old_center{Vec3d::Zero()}; + }; + std::array m_grabber_connections; public: //BBS: add obj manipulation logic @@ -49,13 +56,11 @@ class GLGizmoMove3D : public GLGizmoBase virtual void on_stop_dragging() override; virtual void on_update(const UpdateData& data) override; virtual void on_render() override; - virtual void on_render_for_picking() override; //BBS: GUI refactor: add object manipulation virtual void on_render_input_window(float x, float y, float bottom_limit); private: double calc_projection(const UpdateData& data) const; - void render_grabber_extension(Axis axis, const BoundingBoxf3& box, bool picking) const; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index f682d249ec2..3a3bfb4feb5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -1,4 +1,5 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. +#include "libslic3r/Color.hpp" #include "GLGizmoPainterBase.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" @@ -101,6 +102,13 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const if (is_left_handed) glsafe(::glFrontFace(GL_CW)); + const Camera& camera = wxGetApp().plater()->get_camera(); + const Transform3d& view_matrix = camera.get_view_matrix(); + shader->set_uniform("view_model_matrix", view_matrix * trafo_matrix); + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * trafo_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); + shader->set_uniform("view_normal_matrix", view_normal_matrix); + glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); @@ -119,7 +127,7 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const } -void GLGizmoPainterBase::render_cursor() const +void GLGizmoPainterBase::render_cursor() { // First check that the mouse pointer is on an object. const ModelObject* mo = m_c->selection_info()->model_object(); @@ -241,7 +249,7 @@ void GLGizmoPainterBase::render_cursor_sphere(const Transform3d& trafo) const } // BBS -void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) const +void GLGizmoPainterBase::render_cursor_height_range(const Transform3d& trafo) { const BoundingBoxf3 box = bounding_box(); Vec3d hit_world = trafo * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); @@ -291,7 +299,7 @@ BoundingBoxf3 GLGizmoPainterBase::bounding_box() const return ret; } -void GLGizmoPainterBase::update_contours(const TriangleMesh& vol_mesh, float cursor_z, float max_z, float min_z) const +void GLGizmoPainterBase::update_contours(const TriangleMesh& vol_mesh, float cursor_z, float max_z, float min_z) { const Selection& selection = m_parent.get_selection(); const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); @@ -317,7 +325,7 @@ void GLGizmoPainterBase::update_contours(const TriangleMesh& vol_mesh, float cur const Polygons polys = slice_mesh(m_cut_contours.mesh.its, cursor_z, slicing_params); if (!polys.empty()) { m_cut_contours.contours.init_from(polys, static_cast(cursor_z)); - m_cut_contours.contours.set_color(-1, { 1.0f, 1.0f, 1.0f, 1.0f }); + m_cut_contours.contours.set_color(ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f )); } } else if (box.center() != m_cut_contours.position) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp index d2f69ad2ae6..9a7d57cf6ab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp @@ -214,7 +214,6 @@ class GLGizmoPainterBase : public GLGizmoBase ObjectID m_old_mo_id; size_t m_old_volumes_size = 0; void on_render() override {} - void on_render_for_picking() override {} public: GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoPainterBase() override = default; @@ -225,7 +224,7 @@ class GLGizmoPainterBase : public GLGizmoBase // from usual on_render method allows to render them before transparent // objects, so they can be seen inside them. The usual on_render is called // after all volumes (including transparent ones) are rendered. - virtual void render_painter_gizmo() const = 0; + virtual void render_painter_gizmo() = 0; virtual const float get_cursor_radius_min() const { return CursorRadiusMin; } virtual const float get_cursor_radius_max() const { return CursorRadiusMax; } @@ -238,11 +237,11 @@ class GLGizmoPainterBase : public GLGizmoBase protected: virtual void render_triangles(const Selection& selection) const; - void render_cursor() const; + void render_cursor(); void render_cursor_circle() const; void render_cursor_sphere(const Transform3d& trafo) const; // BBS - void render_cursor_height_range(const Transform3d& trafo) const; + void render_cursor_height_range(const Transform3d& trafo); //BBS: add logic to distinguish the first_time_update and later_update virtual void update_model_object() = 0; virtual void update_from_model_object(bool first_update) = 0; @@ -372,7 +371,7 @@ class GLGizmoPainterBase : public GLGizmoBase mutable CutContours m_cut_contours; BoundingBoxf3 bounding_box() const; - void update_contours(const TriangleMesh& vol_mesh, float cursor_z, float max_z, float min_z) const; + void update_contours(const TriangleMesh& vol_mesh, float cursor_z, float max_z, float min_z); protected: void on_set_state() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp index 65479b064e9..28321683897 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.cpp @@ -165,27 +165,11 @@ void GLGizmoRotate::on_render() render_angle(); render_grabber(box); - render_grabber_extension(box, false); + // render_grabber_extension(box, false); glsafe(::glPopMatrix()); } -void GLGizmoRotate::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - - glsafe(::glDisable(GL_DEPTH_TEST)); - - glsafe(::glPushMatrix()); - - transform_to_local(selection); - - const BoundingBoxf3& box = selection.get_bounding_box(); - render_grabbers_for_picking(box); - render_grabber_extension(box, true); - - glsafe(::glPopMatrix()); -} //BBS: add input window for move void GLGizmoRotate3D::on_render_input_window(float x, float y, float bottom_limit) @@ -325,50 +309,50 @@ void GLGizmoRotate::render_grabber(const BoundingBoxf3& box) const render_grabbers(box); } -void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) const -{ - double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; - //float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); - //double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); - - std::array color = m_grabbers[0].color; - if (!picking && m_hover_id != -1) { - color = m_grabbers[0].hover_color; - //color[0] = 1.0f - color[0]; - //color[1] = 1.0f - color[1]; - //color[2] = 1.0f - color[2]; - } - - GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); - if (shader == nullptr) - return; - - const_cast(&m_cone)->set_color(-1, color); - if (!picking) { - shader->start_using(); - shader->set_uniform("emission_factor", 0.1f); - } - - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); - glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); - glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_cone.render(); - glsafe(::glPopMatrix()); - glsafe(::glPushMatrix()); - glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); - glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); - glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); - glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); - m_cone.render(); - glsafe(::glPopMatrix()); - - if (! picking) - shader->stop_using(); -} +// void GLGizmoRotate::render_grabber_extension(const BoundingBoxf3& box, bool picking) +// { +// double size = 0.75 * GLGizmoBase::Grabber::FixedGrabberSize * GLGizmoBase::INV_ZOOM; +// //float mean_size = (float)((box.size()(0) + box.size()(1) + box.size()(2)) / 3.0); +// //double size = m_dragging ? (double)m_grabbers[0].get_dragging_half_size(mean_size) : (double)m_grabbers[0].get_half_size(mean_size); + +// std::array color = m_grabbers[0].color; +// if (!picking && m_hover_id != -1) { +// color = m_grabbers[0].hover_color; +// //color[0] = 1.0f - color[0]; +// //color[1] = 1.0f - color[1]; +// //color[2] = 1.0f - color[2]; +// } + +// GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light"); +// if (shader == nullptr) +// return; + +// m_cone.set_color(color); +// if (!picking) { +// shader->start_using(); +// shader->set_uniform("emission_factor", 0.1f); +// } + +// glsafe(::glPushMatrix()); +// glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); +// glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); +// glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); +// glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); +// glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); +// m_cone.render(); +// glsafe(::glPopMatrix()); +// glsafe(::glPushMatrix()); +// glsafe(::glTranslated(m_grabbers[0].center.x(), m_grabbers[0].center.y(), m_grabbers[0].center.z())); +// glsafe(::glRotated(Geometry::rad2deg(m_angle), 0.0, 0.0, 1.0)); +// glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); +// glsafe(::glTranslated(0.0, 0.0, 1.5 * size)); +// glsafe(::glScaled(0.75 * size, 0.75 * size, 3.0 * size)); +// m_cone.render(); +// glsafe(::glPopMatrix()); + +// if (! picking) +// shader->stop_using(); +// } void GLGizmoRotate::transform_to_local(const Selection& selection) const { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp index 9862ec29014..2920b9fa67b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoRotate.hpp @@ -61,7 +61,6 @@ class GLGizmoRotate : public GLGizmoBase void on_start_dragging() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override; private: void render_circle() const; @@ -70,7 +69,7 @@ class GLGizmoRotate : public GLGizmoBase void render_reference_radius() const; void render_angle() const; void render_grabber(const BoundingBoxf3& box) const; - void render_grabber_extension(const BoundingBoxf3& box, bool picking) const; + // void render_grabber_extension(const BoundingBoxf3& box, bool picking); void transform_to_local(const Selection& selection) const; // returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate @@ -144,13 +143,6 @@ class GLGizmoRotate3D : public GLGizmoBase } } void on_render() override; - void on_render_for_picking() override - { - for (GLGizmoRotate& g : m_gizmos) { - g.render_for_picking(); - } - } - void on_render_input_window(float x, float y, float bottom_limit) override; private: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 0f98c5dd358..1a93ce893f9 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -252,12 +252,6 @@ void GLGizmoScale3D::on_render() render_grabbers(grabber_mean_size); } -void GLGizmoScale3D::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - render_grabbers_for_picking(m_parent.get_selection().get_bounding_box()); -} - void GLGizmoScale3D::render_grabbers_connection(unsigned int id_1, unsigned int id_2) const { unsigned int grabbers_count = (unsigned int)m_grabbers.size(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 839c7f6823c..b583c8f344f 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -63,7 +63,6 @@ class GLGizmoScale3D : public GLGizmoBase virtual void on_start_dragging() override; virtual void on_update(const UpdateData& data) override; virtual void on_render() override; - virtual void on_render_for_picking() override; //BBS: GUI refactor: add object manipulation virtual void on_render_input_window(float x, float y, float bottom_limit); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index d0fae7c0339..c8708991d5b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -62,7 +62,7 @@ std::string GLGizmoSeam::on_get_name() const -void GLGizmoSeam::render_painter_gizmo() const +void GLGizmoSeam::render_painter_gizmo() { const Selection& selection = m_parent.get_selection(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index 94cefa7f072..a4fccc1b3d3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -10,7 +10,7 @@ class GLGizmoSeam : public GLGizmoPainterBase public: GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); - void render_painter_gizmo() const override; + void render_painter_gizmo(); //BBS bool on_key_down_select_tool_type(int keyCode); @@ -34,7 +34,7 @@ class GLGizmoSeam : public GLGizmoPainterBase std::string get_gizmo_entering_text() const override { return "Entering Seam painting"; } std::string get_gizmo_leaving_text() const override { return "Leaving Seam painting"; } - std::string get_action_snapshot_name() override { return "Paint-on seam editing"; } + std::string get_action_snapshot_name() const override { return "Paint-on seam editing"; } static const constexpr float CursorRadiusMin = 0.05f; // cannot be zero private: diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp index 7b7cc2065de..aca431750d0 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.cpp @@ -622,7 +622,7 @@ void GLGizmoSimplify::init_model(const indexed_triangle_set& its) m_c->selection_info()->get_active_instance(), m_volume); if (const Selection&sel = m_parent.get_selection(); sel.get_volume_idxs().size() == 1) - m_glmodel.set_color(-1, sel.get_volume(*sel.get_volume_idxs().begin())->color); + m_glmodel.set_color(sel.get_volume(*sel.get_volume_idxs().begin())->color); m_triangle_count = its.indices.size(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp index 39093e14d67..b4be94d2c94 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSimplify.hpp @@ -37,7 +37,6 @@ class GLGizmoSimplify: public GLGizmoBase // must implement virtual bool on_init() override { return true;}; virtual void on_render() override; - virtual void on_render_for_picking() override{}; CommonGizmosDataID on_get_requirements() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 73ed826fc6a..a819a741b10 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -102,14 +102,7 @@ void GLGizmoSlaSupports::on_render() } -void GLGizmoSlaSupports::on_render_for_picking() -{ - const Selection& selection = m_parent.get_selection(); - //glsafe(::glEnable(GL_DEPTH_TEST)); - render_points(selection, true); -} - -void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const +void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) { size_t cache_size = m_editing_mode ? m_editing_cache.size() : m_normal_cache.size(); @@ -146,9 +139,10 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) continue; // First decide about the color of the point. - if (picking) - render_color = picking_color_component(i); - else { + if (picking) { + // orca todo + // render_color = picking_color_component(i); + } else { if (size_t(m_hover_id) == i && m_editing_mode) // ignore hover state unless editing mode is active render_color = { 0.f, 1.f, 1.f, 1.f }; else { // neigher hover nor picking @@ -167,8 +161,8 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) } } - const_cast(&m_cone)->set_color(-1, render_color); - const_cast(&m_sphere)->set_color(-1, render_color); + const_cast(&m_cone)->set_color(render_color); + const_cast(&m_sphere)->set_color(render_color); if (shader && !picking) shader->set_uniform("emission_factor", 0.5f); @@ -223,7 +217,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) render_color[1] = 0.7f; render_color[2] = 0.7f; render_color[3] = 0.7f; - const_cast(&m_cylinder)->set_color(-1, render_color); + const_cast(&m_cylinder)->set_color(render_color); if (shader) shader->set_uniform("emission_factor", 0.5f); for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index 8dd76336a35..87fb0cbd00b 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -75,9 +75,8 @@ class GLGizmoSlaSupports : public GLGizmoBase bool on_init() override; void on_update(const UpdateData& data) override; void on_render() override; - void on_render_for_picking() override; - void render_points(const Selection& selection, bool picking = false) const; + void render_points(const Selection& selection, bool picking = false); bool unsaved_changes() const; bool m_lock_unique_islands = false; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp index 553b477b329..d4ff3884cd4 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.cpp @@ -431,24 +431,25 @@ void GLGizmoText::on_render() return; } - if (m_is_modify && m_grabbers.size() == 1) { - std::vector trafo_matrices; - for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - } - - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); - - m_grabbers[0].center = m_mouse_position_world; - m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); - } +// orca todo + // if (m_is_modify && m_grabbers.size() == 1) { + // std::vector trafo_matrices; + // for (const ModelVolume *mv : mo->volumes) { + // if (mv->is_model_part()) { + // trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); + // } + // } + + // m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); + + // float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); + + // m_grabbers[0].center = m_mouse_position_world; + // m_grabbers[0].enabled = true; + // std::array color = picking_color_component(0); + // m_grabbers[0].color = color; + // m_grabbers[0].render_for_picking(mean_size); + // } delete_temp_preview_text_volume(); @@ -459,44 +460,6 @@ void GLGizmoText::on_render() plater->update(); } -void GLGizmoText::on_render_for_picking() -{ - glsafe(::glDisable(GL_DEPTH_TEST)); - - int obejct_idx, volume_idx; - ModelVolume *model_volume = get_selected_single_volume(obejct_idx, volume_idx); - if (model_volume && !model_volume->get_text_info().m_text.empty()) { - if (m_grabbers.size() == 1) { - ModelObject *mo = m_c->selection_info()->model_object(); - if (m_is_modify) { - const Selection &selection = m_parent.get_selection(); - mo = selection.get_model()->objects[m_object_idx]; - } - if (mo == nullptr) return; - - const Selection & selection = m_parent.get_selection(); - const ModelInstance *mi = mo->instances[selection.get_instance_idx()]; - - // Precalculate transformations of individual meshes. - std::vector trafo_matrices; - for (const ModelVolume *mv : mo->volumes) { - if (mv->is_model_part()) { - trafo_matrices.emplace_back(mi->get_transformation().get_matrix() * mv->get_matrix()); - } - } - - m_mouse_position_world = trafo_matrices[m_rr.mesh_id] * Vec3d(m_rr.hit(0), m_rr.hit(1), m_rr.hit(2)); - - float mean_size = (float) (GLGizmoBase::Grabber::FixedGrabberSize); - m_grabbers[0].center = m_mouse_position_world; - m_grabbers[0].enabled = true; - std::array color = picking_color_component(0); - m_grabbers[0].color = color; - m_grabbers[0].render_for_picking(mean_size); - } - } -} - void GLGizmoText::on_update(const UpdateData &data) { Vec2d mouse_pos = Vec2d(data.mouse_pos.x(), data.mouse_pos.y()); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp index 53b792364f2..0eaf1304ca3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoText.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoText.hpp @@ -91,7 +91,6 @@ class GLGizmoText : public GLGizmoBase virtual std::string on_get_name() const override; virtual bool on_is_activable() const override; virtual void on_render() override; - virtual void on_render_for_picking() override; virtual void on_update(const UpdateData &data) override; void push_combo_style(const float scale); void pop_combo_style(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp index f4211dfb59c..72dce7c03ea 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.hpp @@ -6,7 +6,7 @@ #include "slic3r/GUI/MeshUtils.hpp" #include "libslic3r/SLA/Hollowing.hpp" - +#include "slic3r/GUI/GLModel.hpp" namespace Slic3r { class ModelObject; @@ -24,8 +24,12 @@ enum class SLAGizmoEventType : unsigned char { Dragging, Delete, SelectAll, + CtrlDown, + CtrlUp, + ShiftDown, ShiftUp, AltUp, + Escape, ApplyChanges, DiscardChanges, AutomaticGeneration, diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 87c9a00c9ea..0abfd3739ad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -5,6 +5,7 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/Gizmos/GLGizmosManager.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/GUI/NotificationManager.hpp" @@ -15,7 +16,6 @@ #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp" -// BBS #include "slic3r/GUI/Gizmos/GLGizmoAdvancedCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFaceDetector.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" @@ -24,6 +24,7 @@ #include "slic3r/GUI/Gizmos/GLGizmoSimplify.hpp" #include "slic3r/GUI/Gizmos/GLGizmoText.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMeshBoolean.hpp" +#include "slic3r/GUI/Gizmos/GLGizmoMeasure.hpp" #include "libslic3r/format.hpp" #include "libslic3r/Model.hpp" @@ -171,6 +172,9 @@ void GLGizmosManager::switch_gizmos_icon_filename() case(EType::MmuSegmentation): gizmo->set_icon_filename(m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg"); break; + case(EType::Measure): + gizmo->set_icon_filename(m_is_dark ? "measure.svg" : "measure.svg"); + break; case(EType::MeshBoolean): gizmo->set_icon_filename(m_is_dark ? "toolbar_meshboolean_dark.svg" : "toolbar_meshboolean.svg"); break; @@ -210,6 +214,8 @@ bool GLGizmosManager::init() m_gizmos.emplace_back(new GLGizmoSeam(m_parent, m_is_dark ? "toolbar_seam_dark.svg" : "toolbar_seam.svg", EType::Seam)); m_gizmos.emplace_back(new GLGizmoText(m_parent, m_is_dark ? "toolbar_text_dark.svg" : "toolbar_text.svg", EType::Text)); m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, m_is_dark ? "mmu_segmentation_dark.svg" : "mmu_segmentation.svg", EType::MmuSegmentation)); + m_gizmos.emplace_back(new GLGizmoMeasure(m_parent, m_is_dark ? "measure.svg" : "measure.svg", EType::Measure)); + m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "reduce_triangles.svg", EType::Simplify)); //m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", sprite_id++)); //m_gizmos.emplace_back(new GLGizmoFaceDetector(m_parent, "face recognition.svg", sprite_id++)); @@ -590,7 +596,9 @@ Vec3d GLGizmosManager::get_flattening_normal() const if (!m_enabled || m_gizmos.empty()) return Vec3d::Zero(); - return dynamic_cast(m_gizmos[Flatten].get())->get_flattening_normal(); + // Orca todo: this function is not available in GLGizmoFlatten anymore + return Vec3d::Zero(); + // return dynamic_cast(m_gizmos[Flatten].get())->get_flattening_normal(); } void GLGizmosManager::set_flattening_data(const ModelObject* model_object) @@ -598,7 +606,8 @@ void GLGizmosManager::set_flattening_data(const ModelObject* model_object) if (!m_enabled || m_gizmos.empty()) return; - dynamic_cast(m_gizmos[Flatten].get())->set_flattening_data(model_object); + // Orca todo: revisit instance id + dynamic_cast(m_gizmos[Flatten].get())->set_flattening_data(model_object,0); } void GLGizmosManager::set_sla_support_data(ModelObject* model_object) @@ -646,6 +655,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p return dynamic_cast(m_gizmos[Cut].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else if (m_current == MeshBoolean) return dynamic_cast(m_gizmos[MeshBoolean].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); + else if (m_current == Measure) + return dynamic_cast(m_gizmos[Measure].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); else return false; } @@ -692,7 +703,7 @@ void GLGizmosManager::render_current_gizmo() const m_gizmos[m_current]->render(); } -void GLGizmosManager::render_painter_gizmo() const +void GLGizmosManager::render_painter_gizmo() { // This function shall only be called when current gizmo is // derived from GLGizmoPainterBase. @@ -711,14 +722,14 @@ void GLGizmosManager::render_painter_assemble_view() const m_assemble_view_data->model_objects_clipper()->render_cut(); } -void GLGizmosManager::render_current_gizmo_for_picking_pass() const -{ - if (! m_enabled || m_current == Undefined) +// void GLGizmosManager::render_current_gizmo_for_picking_pass() const +// { +// if (! m_enabled || m_current == Undefined) - return; +// return; - m_gizmos[m_current]->render_for_picking(); -} +// m_gizmos[m_current]->render_for_picking(); +// } void GLGizmosManager::render_overlay() { @@ -762,6 +773,9 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt) bool GLGizmosManager::on_mouse(wxMouseEvent& evt) { + if (m_current == Measure) { + return dynamic_cast(m_gizmos[Measure].get())->on_mouse(evt); + } // used to set a right up event as processed when needed static bool pending_right_up = false; @@ -1045,7 +1059,9 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) { if (m_current != Undefined) { - if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) + if (m_current == Measure && gizmo_event(SLAGizmoEventType::Escape)) { + // do nothing + } else if ((m_current != SlaSupports) || !gizmo_event(SLAGizmoEventType::DiscardChanges)) reset_all_states(); processed = true; @@ -1079,14 +1095,14 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt) //} - //case WXK_BACK: - //case WXK_DELETE: - //{ - // if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete)) - // processed = true; + case WXK_BACK: + case WXK_DELETE: + { + if ((m_current == SlaSupports || m_current == Hollow || m_current == Measure) && gizmo_event(SLAGizmoEventType::Delete)) + processed = true; - // break; - //} + break; + } //case 'A': //case 'a': //{ @@ -1190,6 +1206,12 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt) processed = true; } } + else if (m_current == Measure) { + if (keyCode == WXK_CONTROL) + gizmo_event(SLAGizmoEventType::CtrlUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + else if (keyCode == WXK_SHIFT) + gizmo_event(SLAGizmoEventType::ShiftUp, Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.CmdDown()); + } // if (processed) // m_parent.set_cursor(GLCanvas3D::Standard); @@ -1629,6 +1651,8 @@ bool GLGizmosManager::activate_gizmo(EType type) if (old_gizmo->get_state() != GLGizmoBase::Off) return false; // gizmo refused to be turned off, do nothing. + old_gizmo->unregister_raycasters_for_picking(); + if (! m_parent.get_gizmos_manager().is_serializing() && old_gizmo->wants_enter_leave_snapshots()) Plater::TakeSnapshot snapshot(wxGetApp().plater(), @@ -1649,7 +1673,9 @@ bool GLGizmosManager::activate_gizmo(EType type) // wxGetApp().imgui()->load_fonts_texture(); //} new_gizmo->set_state(GLGizmoBase::On); + new_gizmo->register_raycasters_for_picking(); } + return true; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index fefb85b6b9c..126a5ac1624 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -76,6 +76,7 @@ class GLGizmosManager : public Slic3r::ObjectBase // BBS Text, MmuSegmentation, + Measure, Simplify, SlaSupports, // BBS @@ -286,8 +287,8 @@ class GLGizmosManager : public Slic3r::ObjectBase void on_change_color_mode(bool is_dark); void render_current_gizmo() const; - void render_current_gizmo_for_picking_pass() const; - void render_painter_gizmo() const; + // void render_current_gizmo_for_picking_pass() const; + void render_painter_gizmo(); void render_painter_assemble_view() const; void render_overlay(); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 05ef3a4c950..1c11acb1148 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -84,6 +84,7 @@ static const std::map font_icons = { {ImGui::TextSearchIcon , "im_text_search" }, {ImGui::TextSearchCloseIcon , "im_text_search_close" }, + {ImGui::ClipboardBtnIcon , "copy_menu" }, {ImGui::ExpandBtn , "expand_btn" }, {ImGui::CollapseBtn , "collapse_btn" }, @@ -785,9 +786,96 @@ bool ImGuiWrapper::radio_button(const wxString &label, bool active) return ImGui::RadioButton(label_utf8.c_str(), active); } -bool ImGuiWrapper::image_button() +ImU32 ImGuiWrapper::to_ImU32(const ColorRGBA& color) { - return false; + return ImGui::GetColorU32({ color.r(), color.g(), color.b(), color.a() }); +} + +ImVec4 ImGuiWrapper::to_ImVec4(const ColorRGBA& color) +{ + return { color.r(), color.g(), color.b(), color.a() }; +} + +ColorRGBA ImGuiWrapper::from_ImU32(const ImU32& color) +{ + return from_ImVec4(ImGui::ColorConvertU32ToFloat4(color)); +} + +ColorRGBA ImGuiWrapper::from_ImVec4(const ImVec4& color) +{ + return { color.x, color.y, color.z, color.w }; +} + + +static bool image_button_ex(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = ImGui::GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2); + ImGui::ItemSize(bb); + if (!ImGui::ItemAdd(bb, id)) + return false; + + bool hovered, held; + bool pressed = ImGui::ButtonBehavior(bb, id, &hovered, &held, flags); + + // Render + const ImU32 col = ImGui::GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + ImGui::RenderNavHighlight(bb, id); + ImGui::RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); + if (bg_col.w > 0.0f) + window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, ImGui::GetColorU32(bg_col)); + window->DrawList->AddImage(texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, ImGui::GetColorU32(tint_col)); + + return pressed; +} + +bool ImGuiWrapper::image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + if (window->SkipItems) + return false; + + // Default to using texture ID as ID. User can still push string/integer prefixes. + ImGui::PushID((void*)(intptr_t)user_texture_id); + const ImGuiID id = window->GetID("#image"); + ImGui::PopID(); + + const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding; + return image_button_ex(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col, flags); +} + +bool ImGuiWrapper::image_button(const wchar_t icon, const wxString& tooltip) +{ + const ImGuiIO& io = ImGui::GetIO(); + const ImTextureID tex_id = io.Fonts->TexID; + assert(io.Fonts->TexWidth > 0 && io.Fonts->TexHeight > 0); + const float inv_tex_w = 1.0f / float(io.Fonts->TexWidth); + const float inv_tex_h = 1.0f / float(io.Fonts->TexHeight); + const ImFontAtlasCustomRect* const rect = GetTextureCustomRect(icon); + const ImVec2 size = { float(rect->Width), float(rect->Height) }; + const ImVec2 uv0 = ImVec2(float(rect->X) * inv_tex_w, float(rect->Y) * inv_tex_h); + const ImVec2 uv1 = ImVec2(float(rect->X + rect->Width) * inv_tex_w, float(rect->Y + rect->Height) * inv_tex_h); + ImGui::PushStyleColor(ImGuiCol_Button, { 0.25f, 0.25f, 0.25f, 0.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, { 0.4f, 0.4f, 0.4f, 1.0f }); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, { 0.25f, 0.25f, 0.25f, 1.0f }); + const bool res = image_button(tex_id, size, uv0, uv1); + ImGui::PopStyleColor(3); + + if (!tooltip.empty() && ImGui::IsItemHovered()) + this->tooltip(tooltip, ImGui::GetFontSize() * 20.0f); + + return res; +} + +ImFontAtlasCustomRect* ImGuiWrapper::GetTextureCustomRect(const wchar_t& tex_id) +{ + auto item = m_custom_glyph_rects_ids.find(tex_id); + return (item != m_custom_glyph_rects_ids.end()) ? ImGui::GetIO().Fonts->GetCustomRectByIndex(m_custom_glyph_rects_ids[tex_id]) : nullptr; } bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format) @@ -2128,11 +2216,12 @@ void ImGuiWrapper::init_font(bool compress) int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next // add rectangles for the icons to the font atlas for (auto& icon : font_icons) - io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); for (auto& icon : font_icons_large) - io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2); for (auto& icon : font_icons_extra_large) - io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); + m_custom_glyph_rects_ids[icon.first] = io.Fonts->AddCustomRectFontGlyph(default_font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4); + // Build texture atlas unsigned char* pixels; diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 052ea00e5ef..0ba07a2f4bb 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -8,6 +8,7 @@ #include +#include "libslic3r/Color.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/GCode/ThumbnailData.hpp" @@ -59,6 +60,7 @@ class ImGuiWrapper bool m_requires_extra_frame{ false }; #endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT std::string m_clipboard_text; + std::map m_custom_glyph_rects_ids; public: struct LastSliderStatus { @@ -114,7 +116,8 @@ class ImGuiWrapper bool bbl_button(const wxString &label); bool button(const wxString& label, float width, float height); bool radio_button(const wxString &label, bool active); - bool image_button(); + bool image_button(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0 = ImVec2(0.0, 0.0), const ImVec2& uv1 = ImVec2(1.0, 1.0), int frame_padding = -1, const ImVec4& bg_col = ImVec4(0.0, 0.0, 0.0, 0.0), const ImVec4& tint_col = ImVec4(1.0, 1.0, 1.0, 1.0), ImGuiButtonFlags flags = 0); + bool image_button(const wchar_t icon, const wxString& tooltip = L""); bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f"); bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f"); bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f"); @@ -134,6 +137,12 @@ class ImGuiWrapper void tooltip(const char *label, float wrap_width); void tooltip(const wxString &label, float wrap_width); + static ImU32 to_ImU32(const ColorRGBA& color); + static ImVec4 to_ImVec4(const ColorRGBA& color); + static ColorRGBA from_ImU32(const ImU32& color); + static ColorRGBA from_ImVec4(const ImVec4& color); + + ImFontAtlasCustomRect* GetTextureCustomRect(const wchar_t& tex_id); // Float sliders: Manually inserted values aren't clamped by ImGui.Using this wrapper function does (when clamp==true). #if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index cd9a617f4bb..56a682eaff4 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -216,38 +216,26 @@ void MeshRaycaster::line_from_mouse_pos_static(const Vec2d &mouse_pos, const Tra direction = inv.linear() * direction; } -void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const +void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) { - Matrix4d modelview = camera.get_view_matrix().matrix(); - Matrix4d projection= camera.get_projection_matrix().matrix(); - Vec4i viewport(camera.get_viewport().data()); - - Vec3d pt1; - Vec3d pt2; - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 0.), - modelview, projection, viewport, pt1); - igl::unproject(Vec3d(mouse_pos(0), viewport[3] - mouse_pos(1), 1.), - modelview, projection, viewport, pt2); - + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); Transform3d inv = trafo.inverse(); - pt1 = inv * pt1; - pt2 = inv * pt2; - - point = pt1; - direction = pt2-pt1; + point = inv*point; + direction = inv.linear()*direction; } - bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, size_t* facet_idx, bool sinking_limit) const { Vec3d point; Vec3d direction; - line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + CameraUtils::ray_from_screen_pos(camera, mouse_pos, point, direction); + Transform3d inv = trafo.inverse(); + point = inv*point; + direction = inv.linear()*direction; - std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector hits = m_emesh.query_ray_hits(point, direction); if (hits.empty()) return false; // no intersection found @@ -259,7 +247,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& for (i=0; i= (sinking_limit ? SINKING_Z_THRESHOLD : -std::numeric_limits::max()) && - (!clipping_plane || !clipping_plane->is_point_clipped(transformed_hit))) + (! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit))) break; } @@ -280,12 +268,27 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& } + +bool MeshRaycaster::intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const +{ + Transform3d trafo_inv = trafo.inverse(); + Vec3d to = trafo_inv * (point + direction); + point = trafo_inv * point; + direction = (to-point).normalized(); + + std::vector hits = m_emesh.query_ray_hits(point, direction); + std::vector neg_hits = m_emesh.query_ray_hits(point, -direction); + + return !hits.empty() || !neg_hits.empty(); +} + + std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transformation& trafo, const Camera& camera, const std::vector& points, const ClippingPlane* clipping_plane) const { std::vector out; - const Transform3d& instance_matrix_no_translation_no_scaling = trafo.get_matrix(true,false,true); + const Transform3d instance_matrix_no_translation_no_scaling = trafo.get_rotation_matrix(); Vec3d direction_to_camera = -camera.get_dir_forward(); Vec3d direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera).normalized().eval(); direction_to_camera_mesh = direction_to_camera_mesh.cwiseProduct(trafo.get_scaling_factor()); @@ -298,7 +301,7 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo bool is_obscured = false; // Cast a ray in the direction of the camera and look for intersection with the mesh: - std::vector hits; + std::vector hits; // Offset the start of the ray by EPSILON to account for numerical inaccuracies. hits = m_emesh.query_ray_hits((inverse_trafo * pt.cast() + direction_to_camera_mesh * EPSILON), direction_to_camera_mesh); @@ -328,13 +331,47 @@ std::vector MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo return out; } +bool MeshRaycaster::closest_hit(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane, size_t* facet_idx) const +{ + Vec3d point; + Vec3d direction; + line_from_mouse_pos(mouse_pos, trafo, camera, point, direction); + + const std::vector hits = m_emesh.query_ray_hits(point, direction.normalized()); + + if (hits.empty()) + return false; // no intersection found + + size_t hit_id = 0; + if (clipping_plane != nullptr) { + while (hit_id < hits.size() && clipping_plane->is_point_clipped(trafo * hits[hit_id].position())) { + ++hit_id; + } + } + + if (hit_id == hits.size()) + return false; // all points are obscured or cut by the clipping plane. + + const AABBMesh::hit_result& hit = hits[hit_id]; + + position = hit.position().cast(); + normal = hit.normal().cast(); + + if (facet_idx != nullptr) + *facet_idx = hit.face(); + + return true; +} Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const { int idx = 0; Vec3d closest_point; - m_emesh.squared_distance(point.cast(), idx, closest_point); + Vec3d pointd = point.cast(); + m_emesh.squared_distance(pointd, idx, closest_point); if (normal) + // TODO: consider: get_normal(m_emesh, pointd).cast(); *normal = m_normals[idx]; return closest_point.cast(); diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 123b8a78525..5133f915562 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -5,6 +5,7 @@ #include "libslic3r/Geometry.hpp" #include "libslic3r/SLA/IndexedMesh.hpp" #include "admesh/stl.h" +#include "libslic3r/AABBMesh.hpp" #include "slic3r/GUI/3DScene.hpp" @@ -55,6 +56,8 @@ class ClippingPlane void set_offset(double offset) { m_data[3] = offset; } double get_offset() const { return m_data[3]; } Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); } + void invert_normal() { m_data[0] *= -1.0; m_data[1] *= -1.0; m_data[2] *= -1.0; } + ClippingPlane inverted_normal() const { return ClippingPlane(-get_normal(), get_offset()); } bool is_active() const { return m_data[3] != DBL_MAX; } static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); } const double* get_data() const { return m_data; } @@ -128,19 +131,23 @@ class MeshClipper { // whether certain points are visible or obscured by the mesh etc. class MeshRaycaster { public: - // The class references extern TriangleMesh, which must stay alive - // during MeshRaycaster existence. - MeshRaycaster(const TriangleMesh& mesh) - : m_emesh(mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length - , m_normals(its_face_normals(mesh.its)) + explicit MeshRaycaster(std::shared_ptr mesh) + : m_mesh(std::move(mesh)) + , m_emesh(*m_mesh, true) // calculate epsilon for triangle-ray intersection from an average edge length + , m_normals(its_face_normals(m_mesh->its)) { + assert(m_mesh); } + explicit MeshRaycaster(const TriangleMesh &mesh) + : MeshRaycaster(std::make_unique(mesh)) + {} static void line_from_mouse_pos_static(const Vec2d &mouse_pos, const Transform3d &trafo, const Camera &camera, Vec3d &point, Vec3d &direction); - - void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, - Vec3d& point, Vec3d& direction) const; + + // DEPRICATED - use CameraUtils::ray_from_screen_pos + static void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, + Vec3d& point, Vec3d& direction); // Given a mouse position, this returns true in case it is on the mesh. bool unproject_on_mesh( @@ -151,8 +158,14 @@ class MeshRaycaster { Vec3f& normal, // normal of the triangle that was hit const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) size_t* facet_idx = nullptr, // index of the facet hit - bool sinking_limit = true +bool sinking_limit = true ) const; + + const AABBMesh &get_aabb_mesh() const { return m_emesh; } + + // Given a point and direction in world coords, returns whether the respective line + // intersects the mesh if it is transformed into world by trafo. + bool intersects_line(Vec3d point, Vec3d direction, const Transform3d& trafo) const; // Given a vector of points in woorld coordinates, this returns vector // of indices of points that are visible (i.e. not cut by clipping plane @@ -164,10 +177,22 @@ class MeshRaycaster { const ClippingPlane* clipping_plane = nullptr // clipping plane (if active) ) const; + // Returns true if the ray, built from mouse position and camera direction, intersects the mesh. + // In this case, position and normal contain the position and normal, in model coordinates, of the intersection closest to the camera, + // depending on the position/orientation of the clipping_plane, if specified + bool closest_hit( + const Vec2d& mouse_pos, + const Transform3d& trafo, // how to get the mesh into world coords + const Camera& camera, // current camera position + Vec3f& position, // where to save the positibon of the hit (mesh coords) + Vec3f& normal, // normal of the triangle that was hit + const ClippingPlane* clipping_plane = nullptr, // clipping plane (if active) + size_t* facet_idx = nullptr // index of the facet hit + ) const; + // Given a point in world coords, the method returns closest point on the mesh. // The output is in mesh coords. // normal* can be used to also get normal of the respective triangle. - Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; // Given a point in mesh coords, the method returns the closest facet from mesh. @@ -176,11 +201,21 @@ class MeshRaycaster { Vec3f get_triangle_normal(size_t facet_idx) const; private: - sla::IndexedMesh m_emesh; + std::shared_ptr m_mesh; + AABBMesh m_emesh; std::vector m_normals; }; +struct PickingModel +{ + GLModel model; + std::unique_ptr mesh_raycaster; + + void reset() { + model.reset(); + mesh_raycaster.reset(); + } +}; - } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 126623e40a0..01cbd4021a5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3240,7 +3240,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (load_config) { if (translate_old) { //set the size back - partplate_list.reset_size(current_width + Bed3D::Axes::DefaultTipRadius, current_depth + Bed3D::Axes::DefaultTipRadius, current_height, false); + partplate_list.reset_size(current_width + CoordAxes::DefaultTipRadius, current_depth + CoordAxes::DefaultTipRadius, current_height, false); } partplate_list.load_from_3mf_structure(plate_data); partplate_list.update_slice_context_to_current_plate(background_process); @@ -7127,7 +7127,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape, const Pointfs& exclude_ar double z = config->opt_float("printable_height"); //Pointfs& exclude_areas = config->option("bed_exclude_area")->values; - partplate_list.reset_size(max.x() - min.x() - Bed3D::Axes::DefaultTipRadius, max.y() - min.y() - Bed3D::Axes::DefaultTipRadius, z); + partplate_list.reset_size(max.x() - min.x() - CoordAxes::DefaultTipRadius, max.y() - min.y() - CoordAxes::DefaultTipRadius, z); partplate_list.set_shapes(shape, exclude_areas, custom_texture, height_to_lid, height_to_rod); Vec2d new_shape_position = partplate_list.get_current_shape_position(); diff --git a/src/slic3r/GUI/SceneRaycaster.cpp b/src/slic3r/GUI/SceneRaycaster.cpp new file mode 100644 index 00000000000..08c34076329 --- /dev/null +++ b/src/slic3r/GUI/SceneRaycaster.cpp @@ -0,0 +1,314 @@ +#include "libslic3r/libslic3r.h" +#include "SceneRaycaster.hpp" + +#include "Camera.hpp" +#include "GUI_App.hpp" +#include "Selection.hpp" +#include "Plater.hpp" + +namespace Slic3r { +namespace GUI { + +SceneRaycaster::SceneRaycaster() { +#if ENABLE_RAYCAST_PICKING_DEBUG + // hit point + m_sphere.init_from(its_make_sphere(1.0, double(PI) / 16.0)); + m_sphere.set_color(ColorRGBA::YELLOW()); + + // hit normal + GLModel::Geometry init_data; + init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 }; + init_data.color = ColorRGBA::YELLOW(); + init_data.reserve_vertices(2); + init_data.reserve_indices(2); + + // vertices + init_data.add_vertex((Vec3f)Vec3f::Zero()); + init_data.add_vertex((Vec3f)Vec3f::UnitZ()); + + // indices + init_data.add_line(0, 1); + + m_line.init_from(std::move(init_data)); +#endif // ENABLE_RAYCAST_PICKING_DEBUG +} + +std::shared_ptr SceneRaycaster::add_raycaster(EType type, int id, const MeshRaycaster& raycaster, + const Transform3d& trafo, bool use_back_faces) +{ + switch (type) { + case EType::Bed: { return m_bed.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::Volume: { return m_volumes.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::Gizmo: { return m_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + case EType::FallbackGizmo: { return m_fallback_gizmos.emplace_back(std::make_shared(encode_id(type, id), raycaster, trafo, use_back_faces)); } + default: { assert(false); return nullptr; } + }; +} + +void SceneRaycaster::remove_raycasters(EType type, int id) +{ + std::vector>* raycasters = get_raycasters(type); + auto it = raycasters->begin(); + while (it != raycasters->end()) { + if ((*it)->get_id() == encode_id(type, id)) + it = raycasters->erase(it); + else + ++it; + } +} + +void SceneRaycaster::remove_raycasters(EType type) +{ + switch (type) { + case EType::Bed: { m_bed.clear(); break; } + case EType::Volume: { m_volumes.clear(); break; } + case EType::Gizmo: { m_gizmos.clear(); break; } + case EType::FallbackGizmo: { m_fallback_gizmos.clear(); break; } + default: { break; } + }; +} + +void SceneRaycaster::remove_raycaster(std::shared_ptr item) +{ + for (auto it = m_bed.begin(); it != m_bed.end(); ++it) { + if (*it == item) { + m_bed.erase(it); + return; + } + } + for (auto it = m_volumes.begin(); it != m_volumes.end(); ++it) { + if (*it == item) { + m_volumes.erase(it); + return; + } + } + for (auto it = m_gizmos.begin(); it != m_gizmos.end(); ++it) { + if (*it == item) { + m_gizmos.erase(it); + return; + } + } + for (auto it = m_fallback_gizmos.begin(); it != m_fallback_gizmos.end(); ++it) { + if (*it == item) { + m_fallback_gizmos.erase(it); + return; + } + } +} + +SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const +{ + // helper class used to return currently selected volume as hit when overlapping with other volumes + // to allow the user to click and drag on a selected volume + class VolumeKeeper + { + std::optional m_selected_volume_id; + Vec3f m_closest_hit_pos{ std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max() }; + bool m_selected_volume_already_found{ false }; + + public: + VolumeKeeper() { + const Selection& selection = wxGetApp().plater()->get_selection(); + if (selection.is_single_volume() || selection.is_single_modifier()) { + const GLVolume* volume = selection.get_first_volume(); + if (!volume->is_wipe_tower && !volume->is_sla_pad() && !volume->is_sla_support()) + m_selected_volume_id = *selection.get_volume_idxs().begin(); + } + } + + bool is_active() const { return m_selected_volume_id.has_value(); } + const Vec3f& get_closest_hit_pos() const { return m_closest_hit_pos; } + bool check_hit_result(const HitResult& hit) { + assert(is_active()); + + if (m_selected_volume_already_found && hit.type == SceneRaycaster::EType::Volume && hit.position.isApprox(m_closest_hit_pos)) + return false; + + if (hit.type == SceneRaycaster::EType::Volume) + m_selected_volume_already_found = *m_selected_volume_id == (unsigned int)decode_id(hit.type, hit.raycaster_id); + + m_closest_hit_pos = hit.position; + return true; + } + }; + + VolumeKeeper volume_keeper; + + double closest_hit_squared_distance = std::numeric_limits::max(); + auto is_closest = [&closest_hit_squared_distance, &volume_keeper](const Camera& camera, const Vec3f& hit) { + const double hit_squared_distance = (camera.get_position() - hit.cast()).squaredNorm(); + bool ret = hit_squared_distance < closest_hit_squared_distance; + if (volume_keeper.is_active()) + ret |= hit.isApprox(volume_keeper.get_closest_hit_pos()); + if (ret) + closest_hit_squared_distance = hit_squared_distance; + return ret; + }; + +#if ENABLE_RAYCAST_PICKING_DEBUG + const_cast*>(&m_last_hit)->reset(); +#endif // ENABLE_RAYCAST_PICKING_DEBUG + + HitResult ret; + + auto test_raycasters = [this, is_closest, clipping_plane, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) { + const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr; + const std::vector>* raycasters = get_raycasters(type); + const Vec3f camera_forward = camera.get_dir_forward().cast(); + HitResult current_hit = { type }; + for (std::shared_ptr item : *raycasters) { + if (!item->is_active()) + continue; + + current_hit.raycaster_id = item->get_id(); + const Transform3d& trafo = item->get_transform(); + if (item->get_raycaster()->closest_hit(mouse_pos, trafo, camera, current_hit.position, current_hit.normal, clip_plane)) { + current_hit.position = (trafo * current_hit.position.cast()).cast(); + current_hit.normal = (trafo.matrix().block(0, 0, 3, 3).inverse().transpose() * current_hit.normal.cast()).normalized().cast(); + if (item->use_back_faces() || current_hit.normal.dot(camera_forward) < 0.0f) { + if (is_closest(camera, current_hit.position)) { + if (volume_keeper.is_active()) { + if (volume_keeper.check_hit_result(current_hit)) + ret = current_hit; + } + else + ret = current_hit; + } + } + } + } + }; + + if (!m_gizmos.empty()) + test_raycasters(EType::Gizmo, mouse_pos, camera, ret); + + if (!m_fallback_gizmos.empty() && !ret.is_valid()) + test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret); + + if (!m_gizmos_on_top || !ret.is_valid()) { + if (camera.is_looking_downward() && !m_bed.empty()) + test_raycasters(EType::Bed, mouse_pos, camera, ret); + if (!m_volumes.empty()) + test_raycasters(EType::Volume, mouse_pos, camera, ret); + } + + if (ret.is_valid()) + ret.raycaster_id = decode_id(ret.type, ret.raycaster_id); + +#if ENABLE_RAYCAST_PICKING_DEBUG + *const_cast*>(&m_last_hit) = ret; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + return ret; +} + +#if ENABLE_RAYCAST_PICKING_DEBUG +void SceneRaycaster::render_hit(const Camera& camera) +{ + if (!m_last_hit.has_value() || !(*m_last_hit).is_valid()) + return; + + GLShaderProgram* shader = wxGetApp().get_shader("flat"); + shader->start_using(); + + shader->set_uniform("projection_matrix", camera.get_projection_matrix()); + + const Transform3d sphere_view_model_matrix = camera.get_view_matrix() * Geometry::translation_transform((*m_last_hit).position.cast()) * + Geometry::scale_transform(4.0 * camera.get_inv_zoom()); + shader->set_uniform("view_model_matrix", sphere_view_model_matrix); + m_sphere.render(); + + Eigen::Quaterniond q; + Transform3d m = Transform3d::Identity(); + m.matrix().block(0, 0, 3, 3) = q.setFromTwoVectors(Vec3d::UnitZ(), (*m_last_hit).normal.cast()).toRotationMatrix(); + + const Transform3d line_view_model_matrix = sphere_view_model_matrix * m * Geometry::scale_transform(10.0); + shader->set_uniform("view_model_matrix", line_view_model_matrix); + m_line.render(); + + shader->stop_using(); +} + +size_t SceneRaycaster::active_beds_count() const { + size_t count = 0; + for (const auto& b : m_bed) { + if (b->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_volumes_count() const { + size_t count = 0; + for (const auto& v : m_volumes) { + if (v->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} +size_t SceneRaycaster::active_fallback_gizmos_count() const { + size_t count = 0; + for (const auto& g : m_fallback_gizmos) { + if (g->is_active()) + ++count; + } + return count; +} +#endif // ENABLE_RAYCAST_PICKING_DEBUG + +std::vector>* SceneRaycaster::get_raycasters(EType type) +{ + std::vector>* ret = nullptr; + switch (type) + { + case EType::Bed: { ret = &m_bed; break; } + case EType::Volume: { ret = &m_volumes; break; } + case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } + default: { break; } + } + assert(ret != nullptr); + return ret; +} + +const std::vector>* SceneRaycaster::get_raycasters(EType type) const +{ + const std::vector>* ret = nullptr; + switch (type) + { + case EType::Bed: { ret = &m_bed; break; } + case EType::Volume: { ret = &m_volumes; break; } + case EType::Gizmo: { ret = &m_gizmos; break; } + case EType::FallbackGizmo: { ret = &m_fallback_gizmos; break; } + default: { break; } + } + assert(ret != nullptr); + return ret; +} + +int SceneRaycaster::base_id(EType type) +{ + switch (type) + { + case EType::Bed: { return int(EIdBase::Bed); } + case EType::Volume: { return int(EIdBase::Volume); } + case EType::Gizmo: { return int(EIdBase::Gizmo); } + case EType::FallbackGizmo: { return int(EIdBase::FallbackGizmo); } + default: { break; } + }; + + assert(false); + return -1; +} + +int SceneRaycaster::encode_id(EType type, int id) { return base_id(type) + id; } +int SceneRaycaster::decode_id(EType type, int id) { return id - base_id(type); } + +} // namespace GUI +} // namespace Slic3r diff --git a/src/slic3r/GUI/SceneRaycaster.hpp b/src/slic3r/GUI/SceneRaycaster.hpp new file mode 100644 index 00000000000..8102e20de4e --- /dev/null +++ b/src/slic3r/GUI/SceneRaycaster.hpp @@ -0,0 +1,122 @@ +#ifndef slic3r_SceneRaycaster_hpp_ +#define slic3r_SceneRaycaster_hpp_ + +#include "MeshUtils.hpp" +#include "GLModel.hpp" +#include +#include +#include + +namespace Slic3r { +namespace GUI { + +struct Camera; + +class SceneRaycasterItem +{ + int m_id{ -1 }; + bool m_active{ true }; + bool m_use_back_faces{ false }; + const MeshRaycaster* m_raycaster; + Transform3d m_trafo; + +public: + SceneRaycasterItem(int id, const MeshRaycaster& raycaster, const Transform3d& trafo, bool use_back_faces = false) + : m_id(id), m_raycaster(&raycaster), m_trafo(trafo), m_use_back_faces(use_back_faces) + {} + + int get_id() const { return m_id; } + bool is_active() const { return m_active; } + void set_active(bool active) { m_active = active; } + bool use_back_faces() const { return m_use_back_faces; } + const MeshRaycaster* get_raycaster() const { return m_raycaster; } + const Transform3d& get_transform() const { return m_trafo; } + void set_transform(const Transform3d& trafo) { m_trafo = trafo; } +}; + +class SceneRaycaster +{ +public: + enum class EType + { + None, + Bed, + Volume, + Gizmo, + FallbackGizmo // Is used for gizmo grabbers which will be hit after all grabbers of Gizmo type + }; + + enum class EIdBase + { + Bed = 0, + Volume = 1000, + Gizmo = 1000000, + FallbackGizmo = 2000000 + }; + + struct HitResult + { + EType type{ EType::None }; + int raycaster_id{ -1 }; + Vec3f position{ Vec3f::Zero() }; + Vec3f normal{ Vec3f::Zero() }; + + bool is_valid() const { return raycaster_id != -1; } + }; + +private: + std::vector> m_bed; + std::vector> m_volumes; + std::vector> m_gizmos; + std::vector> m_fallback_gizmos; + + // When set to true, if checking gizmos returns a valid hit, + // the search is not performed on other types + bool m_gizmos_on_top{ false }; + +#if ENABLE_RAYCAST_PICKING_DEBUG + GLModel m_sphere; + GLModel m_line; + std::optional m_last_hit; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + +public: + SceneRaycaster(); + + std::shared_ptr add_raycaster(EType type, int picking_id, const MeshRaycaster& raycaster, + const Transform3d& trafo, bool use_back_faces = false); + void remove_raycasters(EType type, int id); + void remove_raycasters(EType type); + void remove_raycaster(std::shared_ptr item); + + std::vector>* get_raycasters(EType type); + const std::vector>* get_raycasters(EType type) const; + + void set_gizmos_on_top(bool value) { m_gizmos_on_top = value; } + + HitResult hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane = nullptr) const; + +#if ENABLE_RAYCAST_PICKING_DEBUG + void render_hit(const Camera& camera); + + size_t beds_count() const { return m_bed.size(); } + size_t volumes_count() const { return m_volumes.size(); } + size_t gizmos_count() const { return m_gizmos.size(); } + size_t fallback_gizmos_count() const { return m_fallback_gizmos.size(); } + size_t active_beds_count() const; + size_t active_volumes_count() const; + size_t active_gizmos_count() const; + size_t active_fallback_gizmos_count() const; +#endif // ENABLE_RAYCAST_PICKING_DEBUG + + static int decode_id(EType type, int id); + +private: + static int encode_id(EType type, int id); + static int base_id(EType type); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // slic3r_SceneRaycaster_hpp_ diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index c9e8fb9b4c1..f0a68a087bd 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -710,6 +710,17 @@ bool Selection::contains_any_volume(const std::vector& volume_idxs return false; } +bool Selection::contains_sinking_volumes(bool ignore_modifiers) const +{ + for (const GLVolume* v : *m_volumes) { + if (!ignore_modifiers || !v->is_modifier) { + if (v->is_sinking()) + return true; + } + } + return false; +} + bool Selection::matches(const std::vector& volume_idxs) const { unsigned int count = 0; @@ -811,6 +822,196 @@ const BoundingBoxf3& Selection::get_scaled_instance_bounding_box() const return *m_scaled_instance_bounding_box; } + +const BoundingBoxf3& Selection::get_full_unscaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix_no_scaling_factor() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_scaled_instance_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_scaled_instance_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_scaled_instance_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_instance_transformation().get_matrix() * volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_scaled_instance_bounding_box; +} + +const BoundingBoxf3& Selection::get_full_unscaled_instance_local_bounding_box() const +{ + assert(is_single_full_instance()); + + if (!m_full_unscaled_instance_local_bounding_box.has_value()) { + std::optional* bbox = const_cast*>(&m_full_unscaled_instance_local_bounding_box); + *bbox = BoundingBoxf3(); + if (m_valid) { + for (unsigned int i : m_list) { + const GLVolume& volume = *(*m_volumes)[i]; + Transform3d trafo = volume.get_volume_transformation().get_matrix(); + trafo.translation().z() += volume.get_sla_shift_z(); + (*bbox)->merge(volume.transformed_convex_hull_bounding_box(trafo)); + } + } + } + return *m_full_unscaled_instance_local_bounding_box; +} + +const std::pair& Selection::get_bounding_box_in_current_reference_system() const +{ + static int last_coordinates_type = -1; + + assert(!is_empty()); + + ECoordinatesType coordinates_type = ECoordinatesType::World;//wxGetApp().obj_manipul()->get_coordinates_type(); + if (m_mode == Instance && coordinates_type == ECoordinatesType::Local) + coordinates_type = ECoordinatesType::World; + + if (last_coordinates_type != int(coordinates_type)) + const_cast>*>(&m_bounding_box_in_current_reference_system)->reset(); + + if (!m_bounding_box_in_current_reference_system.has_value()) { + last_coordinates_type = int(coordinates_type); + *const_cast>*>(&m_bounding_box_in_current_reference_system) = get_bounding_box_in_reference_system(coordinates_type); + } + + return *m_bounding_box_in_current_reference_system; +} + +std::pair Selection::get_bounding_box_in_reference_system(ECoordinatesType type) const +{ + // + // trafo to current reference system + // + Transform3d trafo; + switch (type) + { + case ECoordinatesType::World: { trafo = Transform3d::Identity(); break; } + case ECoordinatesType::Instance: { trafo = get_first_volume()->get_instance_transformation().get_matrix(); break; } + case ECoordinatesType::Local: { trafo = get_first_volume()->world_matrix(); break; } + } + + // + // trafo basis in world coordinates + // + Geometry::Transformation t(trafo); + t.reset_scaling_factor(); + const Transform3d basis_trafo = t.get_matrix_no_offset(); + std::vector axes = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() }; + for (size_t i = 0; i < axes.size(); ++i) { + axes[i] = basis_trafo * axes[i]; + } + + // + // calculate bounding box aligned to trafo basis + // + Vec3d min = { DBL_MAX, DBL_MAX, DBL_MAX }; + Vec3d max = { -DBL_MAX, -DBL_MAX, -DBL_MAX }; + for (unsigned int id : m_list) { + const GLVolume& vol = *get_volume(id); + const Transform3d vol_world_rafo = vol.world_matrix(); + const TriangleMesh* mesh = vol.convex_hull(); + if (mesh == nullptr) + mesh = &m_model->objects[vol.object_idx()]->volumes[vol.volume_idx()]->mesh(); + assert(mesh != nullptr); + for (const stl_vertex& v : mesh->its.vertices) { + const Vec3d world_v = vol_world_rafo * v.cast(); + for (int i = 0; i < 3; ++i) { + const double i_comp = world_v.dot(axes[i]); + min(i) = std::min(min(i), i_comp); + max(i) = std::max(max(i), i_comp); + } + } + } + + const Vec3d box_size = max - min; + Vec3d half_box_size = 0.5 * box_size; + Geometry::Transformation out_trafo(trafo); + Vec3d center = 0.5 * (min + max); + + // Fix for non centered volume + // by move with calculated center(to volume center) and extend half box size + // e.g. for right aligned embossed text + if (m_list.size() == 1 && + type == ECoordinatesType::Local) { + const GLVolume& vol = *get_volume(*m_list.begin()); + const Transform3d vol_world_trafo = vol.world_matrix(); + Vec3d world_zero = vol_world_trafo * Vec3d::Zero(); + for (size_t i = 0; i < 3; i++){ + // move center to local volume zero + center[i] = world_zero.dot(axes[i]); + // extend half size to bigger distance from center + half_box_size[i] = std::max( + abs(center[i] - min[i]), + abs(center[i] - max[i])); + } + } + + const BoundingBoxf3 out_box(-half_box_size, half_box_size); + out_trafo.set_offset(basis_trafo * center); + return { out_box, out_trafo.get_matrix_no_scaling_factor() }; +} + +BoundingBoxf Selection::get_screen_space_bounding_box() +{ + BoundingBoxf ss_box; + if (!is_empty()) { + const auto& [box, box_trafo] = get_bounding_box_in_current_reference_system(); + + // vertices + std::vector vertices = { + { box.min.x(), box.min.y(), box.min.z() }, + { box.max.x(), box.min.y(), box.min.z() }, + { box.max.x(), box.max.y(), box.min.z() }, + { box.min.x(), box.max.y(), box.min.z() }, + { box.min.x(), box.min.y(), box.max.z() }, + { box.max.x(), box.min.y(), box.max.z() }, + { box.max.x(), box.max.y(), box.max.z() }, + { box.min.x(), box.max.y(), box.max.z() } + }; + + const Camera& camera = wxGetApp().plater()->get_camera(); + const Matrix4d projection_view_matrix = camera.get_projection_matrix().matrix() * camera.get_view_matrix().matrix(); + const std::array& viewport = camera.get_viewport(); + + const double half_w = 0.5 * double(viewport[2]); + const double h = double(viewport[3]); + const double half_h = 0.5 * h; + for (const Vec3d& v : vertices) { + const Vec3d world = box_trafo * v; + const Vec4d clip = projection_view_matrix * Vec4d(world.x(), world.y(), world.z(), 1.0); + const Vec3d ndc = Vec3d(clip.x(), clip.y(), clip.z()) / clip.w(); + const Vec2d ss = Vec2d(half_w * ndc.x() + double(viewport[0]) + half_w, h - (half_h * ndc.y() + double(viewport[1]) + half_h)); + ss_box.merge(ss); + } + } + + return ss_box; +} + void Selection::start_dragging() { if (!m_valid) @@ -853,6 +1054,14 @@ void Selection::move_to_center(const Vec3d& displacement, bool local) this->set_bounding_boxes_dirty(); } +void Selection::setup_cache() +{ + if (!m_valid) + return; + + set_caches(); +} + void Selection::translate(const Vec3d& displacement, bool local) { if (!m_valid) @@ -1552,7 +1761,7 @@ void Selection::erase() } } -void Selection::render(float scale_factor) const +void Selection::render(float scale_factor) { if (!m_valid || is_empty()) return; @@ -1583,7 +1792,7 @@ void Selection::render_center(bool gizmo_is_dragging) const #endif // ENABLE_RENDER_SELECTION_CENTER //BBS: GUI refactor, add uniform scale from gizmo -void Selection::render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) const +void Selection::render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) //void Selection::render_sidebar_hints(const std::string& sidebar_field) const { if (sidebar_field.empty()) @@ -2131,13 +2340,13 @@ void Selection::do_remove_object(unsigned int object_idx) } } -void Selection::render_selected_volumes() const +void Selection::render_selected_volumes() { float color[3] = { 1.0f, 1.0f, 1.0f }; render_bounding_box(get_bounding_box(), color); } -void Selection::render_synchronized_volumes() const +void Selection::render_synchronized_volumes() { if (m_mode == Instance) return; @@ -2161,7 +2370,7 @@ void Selection::render_synchronized_volumes() const } } -void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) const +void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) { if (color == nullptr) return; @@ -2220,25 +2429,25 @@ static std::array get_color(Axis axis) GLGizmoBase::AXES_COLOR[axis][3] }; }; -void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_position_hints(const std::string& sidebar_field) { if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0)); - const_cast(&m_arrow)->set_color(-1, get_color(X)); + const_cast(&m_arrow)->set_color(get_color(X)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "y")) { - const_cast(&m_arrow)->set_color(-1, get_color(Y)); + const_cast(&m_arrow)->set_color(get_color(Y)); m_arrow.render(); } else if (boost::ends_with(sidebar_field, "z")) { glsafe(::glRotated(90.0, 1.0, 0.0, 0.0)); - const_cast(&m_arrow)->set_color(-1, get_color(Z)); + const_cast(&m_arrow)->set_color(get_color(Z)); m_arrow.render(); } } -void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) { auto render_sidebar_rotation_hint = [this]() { m_curved_arrow.render(); @@ -2248,29 +2457,29 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) if (boost::ends_with(sidebar_field, "x")) { glsafe(::glRotated(90.0, 0.0, 1.0, 0.0)); - const_cast(&m_curved_arrow)->set_color(-1, get_color(X)); + const_cast(&m_curved_arrow)->set_color(get_color(X)); render_sidebar_rotation_hint(); } else if (boost::ends_with(sidebar_field, "y")) { glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0)); - const_cast(&m_curved_arrow)->set_color(-1, get_color(Y)); + const_cast(&m_curved_arrow)->set_color(get_color(Y)); render_sidebar_rotation_hint(); } else if (boost::ends_with(sidebar_field, "z")) { - const_cast(&m_curved_arrow)->set_color(-1, get_color(Z)); + const_cast(&m_curved_arrow)->set_color(get_color(Z)); render_sidebar_rotation_hint(); } } //BBS: GUI refactor: add gizmo uniform_scale -void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale) const +void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale) { // BBS //bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling(); bool uniform_scale = requires_uniform_scale() || gizmo_uniform_scale; auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) { - const_cast(&m_arrow)->set_color(-1, uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); + const_cast(&m_arrow)->set_color(uniform_scale ? UNIFORM_SCALE_COLOR : get_color(axis)); GLShaderProgram* shader = wxGetApp().get_current_shader(); if (shader != nullptr) shader->set_uniform("emission_factor", 0.0f); @@ -2304,7 +2513,7 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field, boo } } -void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const +void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) { static const double Margin = 10.0; diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index bfee49bd2ba..d4c6ebbac9b 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,6 +3,7 @@ #include "libslic3r/Geometry.hpp" #include "GLModel.hpp" +#include "GUI_Geometry.hpp" #include #include @@ -26,58 +27,6 @@ using ModelObjectPtrs = std::vector; namespace GUI { -class TransformationType -{ -public: - enum Enum { - // Transforming in a world coordinate system - World = 0, - // Transforming in a local coordinate system - Local = 1, - // Absolute transformations, allowed in local coordinate system only. - Absolute = 0, - // Relative transformations, allowed in both local and world coordinate system. - Relative = 2, - // For group selection, the transformation is performed as if the group made a single solid body. - Joint = 0, - // For group selection, the transformation is performed on each object independently. - Independent = 4, - - World_Relative_Joint = World | Relative | Joint, - World_Relative_Independent = World | Relative | Independent, - Local_Absolute_Joint = Local | Absolute | Joint, - Local_Absolute_Independent = Local | Absolute | Independent, - Local_Relative_Joint = Local | Relative | Joint, - Local_Relative_Independent = Local | Relative | Independent, - }; - - TransformationType() : m_value(World) {} - TransformationType(Enum value) : m_value(value) {} - TransformationType& operator=(Enum value) { m_value = value; return *this; } - - Enum operator()() const { return m_value; } - bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; } - - void set_world() { this->remove(Local); } - void set_local() { this->add(Local); } - void set_absolute() { this->remove(Relative); } - void set_relative() { this->add(Relative); } - void set_joint() { this->remove(Independent); } - void set_independent() { this->add(Independent); } - - bool world() const { return !this->has(Local); } - bool local() const { return this->has(Local); } - bool absolute() const { return !this->has(Relative); } - bool relative() const { return this->has(Relative); } - bool joint() const { return !this->has(Independent); } - bool independent() const { return this->has(Independent); } - -private: - void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); } - void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); } - - Enum m_value; -}; class Selection { @@ -213,6 +162,18 @@ class Selection // is useful for absolute scaling of tilted objects in world coordinate space. std::optional m_unscaled_instance_bounding_box; std::optional m_scaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_bounding_box; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + std::optional m_full_scaled_instance_bounding_box; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + std::optional m_full_unscaled_instance_local_bounding_box; + // Bounding box aligned to the axis of the currently selected reference system (World/Object/Part) + // and transform to place and orient it in world coordinates + std::optional> m_bounding_box_in_current_reference_system; #if ENABLE_RENDER_SELECTION_CENTER GLModel m_vbo_sphere; @@ -301,6 +262,8 @@ class Selection bool contains_all_volumes(const std::vector& volume_idxs) const; // returns true if the selection contains at least one of the given indices bool contains_any_volume(const std::vector& volume_idxs) const; + // returns true if the selection contains any sinking volume + bool contains_sinking_volumes(bool ignore_modifiers = true) const; // returns true if the selection contains all and only the given indices bool matches(const std::vector& volume_idxs) const; @@ -316,6 +279,7 @@ class Selection const IndicesList& get_volume_idxs() const { return m_list; } const GLVolume* get_volume(unsigned int volume_idx) const; + const GLVolume* get_first_volume() const { return get_volume(*m_list.begin()); } const ObjectIdxsToInstanceIdxsMap& get_content() const { return m_cache.content; } @@ -325,6 +289,24 @@ class Selection // is useful for absolute scaling of tilted objects in world coordinate space. const BoundingBoxf3& get_unscaled_instance_bounding_box() const; const BoundingBoxf3& get_scaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in world coordinates. + // Modifiers are taken in account + const BoundingBoxf3& get_full_scaled_instance_bounding_box() const; + // Bounding box of a single full instance selection, in local coordinates, with no instance scaling applied. + // Modifiers are taken in account + const BoundingBoxf3& get_full_unscaled_instance_local_bounding_box() const; + // Returns the bounding box aligned to the axes of the currently selected reference system (World/Object/Part) + // and the transform to place and orient it in world coordinates + const std::pair& get_bounding_box_in_current_reference_system() const; + // Returns the bounding box aligned to the axes of the given reference system + // and the transform to place and orient it in world coordinates + std::pair get_bounding_box_in_reference_system(ECoordinatesType type) const; + + // Returns the screen space bounding box + BoundingBoxf get_screen_space_bounding_box(); void start_dragging(); void stop_dragging() { m_dragging = false; } @@ -353,12 +335,12 @@ class Selection void erase(); - void render(float scale_factor = 1.0) const; + void render(float scale_factor = 1.0); #if ENABLE_RENDER_SELECTION_CENTER void render_center(bool gizmo_is_dragging) const; #endif // ENABLE_RENDER_SELECTION_CENTER //BBS: GUI refactor: add uniform scale from gizmo - void render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale) const; + void render_sidebar_hints(const std::string& sidebar_field, bool uniform_scale); bool requires_local_axes() const; @@ -366,6 +348,8 @@ class Selection m_scale_factor = scale; render_bounding_box(box, color); } + + void setup_cache(); //BBS void cut_to_clipboard(); @@ -398,15 +382,24 @@ class Selection void do_remove_volume(unsigned int volume_idx); void do_remove_instance(unsigned int object_idx, unsigned int instance_idx); void do_remove_object(unsigned int object_idx); - void set_bounding_boxes_dirty() { m_bounding_box.reset(); m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset(); } - void render_selected_volumes() const; - void render_synchronized_volumes() const; - void render_bounding_box(const BoundingBoxf3& box, float* color) const; - void render_sidebar_position_hints(const std::string& sidebar_field) const; - void render_sidebar_rotation_hints(const std::string& sidebar_field) const; + void set_bounding_boxes_dirty() + { + m_bounding_box.reset(); + m_unscaled_instance_bounding_box.reset(); + m_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_bounding_box.reset(); + m_full_scaled_instance_bounding_box.reset(); + m_full_unscaled_instance_local_bounding_box.reset(); + m_bounding_box_in_current_reference_system.reset(); + } + void render_selected_volumes(); + void render_synchronized_volumes(); + void render_bounding_box(const BoundingBoxf3& box, float* color); + void render_sidebar_position_hints(const std::string& sidebar_field); + void render_sidebar_rotation_hints(const std::string& sidebar_field); //BBS: GUI refactor: add uniform_scale from gizmo - void render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale) const; - void render_sidebar_layers_hints(const std::string& sidebar_field) const; + void render_sidebar_scale_hints(const std::string& sidebar_field, bool gizmo_uniform_scale); + void render_sidebar_layers_hints(const std::string& sidebar_field); public: enum SyncRotationType {