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