From d18e10c7c9454590421c6dd77914427bb8ef14ad Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 15 Mar 2017 16:33:25 +0100 Subject: [PATCH] Rewrote the OpenGL object rendering to indexed triangle / quad sets for lower memory consumption. Rewrote the print path 3D preview to generate these indexed triangle / quad sets, possibly with at least as possible duplication of vertices, with a crease angle of 45 degrees, leading to maximum 8% overshoots at the corners. --- lib/Slic3r/GUI/3DScene.pm | 26 +- xs/src/libslic3r/Geometry.hpp | 42 ++ xs/src/libslic3r/Point.hpp | 2 + xs/src/slic3r/GUI/3DScene.cpp | 734 ++++++++++++---------------------- xs/src/slic3r/GUI/3DScene.hpp | 160 ++++---- xs/xsp/GUI_3DScene.xsp | 16 +- 6 files changed, 414 insertions(+), 566 deletions(-) diff --git a/lib/Slic3r/GUI/3DScene.pm b/lib/Slic3r/GUI/3DScene.pm index ec6fe575b..b149047c3 100644 --- a/lib/Slic3r/GUI/3DScene.pm +++ b/lib/Slic3r/GUI/3DScene.pm @@ -1254,20 +1254,20 @@ sub draw_volumes { glPushMatrix(); glTranslatef(@{$volume->origin}); glCullFace(GL_BACK); - my $qverts_cnt = $volume->qverts_to_render_cnt; - if ($qverts_cnt) { - glVertexPointer_c(3, GL_FLOAT, 0, $volume->qverts_to_render_ptr); - glNormalPointer_c(GL_FLOAT, 0, $volume->qnorms_to_render_ptr); - glDrawArrays(GL_QUADS, 0, $qverts_cnt / 3); + if ($volume->indexed) { + my $quads_cnt = $volume->indexed_quads_to_render_cnt; + my $triangles_cnt = $volume->indexed_triangles_to_render_cnt; + if ($quads_cnt + $triangles_cnt > 0) { + glInterleavedArrays_c(GL_N3F_V3F, 0, $volume->triangles_to_render_ptr); + glDrawElements_c(GL_QUADS, $quads_cnt, GL_UNSIGNED_INT, $volume->quad_indices_to_render_ptr ) if ($quads_cnt); + glDrawElements_c(GL_TRIANGLES, $triangles_cnt, GL_UNSIGNED_INT, $volume->triangle_indices_to_render_ptr) if ($triangles_cnt); + glInterleavedArrays_c(GL_N3F_V3F, 0, 0); + } + } elsif (! $volume->empty) { + glInterleavedArrays_c(GL_N3F_V3F, 0, $volume->triangles_to_render_ptr); + glDrawArrays(GL_TRIANGLES, 0, $volume->triangles_to_render_cnt); + glInterleavedArrays_c(GL_N3F_V3F, 0, 0); } - my $tverts_cnt = $volume->tverts_to_render_cnt; - if ($tverts_cnt) { - glVertexPointer_c(3, GL_FLOAT, 0, $volume->tverts_to_render_ptr); - glNormalPointer_c(GL_FLOAT, 0, $volume->tnorms_to_render_ptr); - glDrawArrays(GL_TRIANGLES, 0, $tverts_cnt / 3); - } - glVertexPointer_c(3, GL_FLOAT, 0, 0); - glNormalPointer_c(GL_FLOAT, 0, 0); glPopMatrix(); if ($shader_active) { diff --git a/xs/src/libslic3r/Geometry.hpp b/xs/src/libslic3r/Geometry.hpp index 30582fdfd..a6d1bbb66 100644 --- a/xs/src/libslic3r/Geometry.hpp +++ b/xs/src/libslic3r/Geometry.hpp @@ -13,6 +13,48 @@ using boost::polygon::voronoi_diagram; namespace Slic3r { namespace Geometry { +inline bool ray_ray_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +{ + double denom = v1.x * v2.y - v2.x * v1.y; + if (std::abs(denom) < EPSILON) + return false; + double t = (v2.x * (p1.y - p2.y) - v2.y * (p1.x - p2.x)) / denom; + res.x = p1.x + t * v1.x; + res.y = p1.y + t * v1.y; + return true; +} + +inline bool segment_segment_intersection(const Pointf &p1, const Vectorf &v1, const Pointf &p2, const Vectorf &v2, Pointf &res) +{ + double denom = v1.x * v2.y - v2.x * v1.y; + if (std::abs(denom) < EPSILON) + // Lines are collinear. + return false; + double s12_x = p1.x - p2.x; + double s12_y = p1.y - p2.y; + double s_numer = v1.x * s12_y - v1.y * s12_x; + bool denom_is_positive = false; + if (denom < 0.) { + denom_is_positive = true; + denom = - denom; + s_numer = - s_numer; + } + if (s_numer < 0.) + // Intersection outside of the 1st segment. + return false; + double t_numer = v2.x * s12_y - v2.y * s12_x; + if (! denom_is_positive) + t_numer = - t_numer; + if (t_numer < 0. || s_numer > denom || t_numer > denom) + // Intersection outside of the 1st or 2nd segment. + return false; + // Intersection inside both of the segments. + double t = t_numer / denom; + res.x = p1.x + t * v1.x; + res.y = p1.y + t * v1.y; + return true; +} + Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); void chained_path(const Points &points, std::vector &retval, Point start_near); diff --git a/xs/src/libslic3r/Point.hpp b/xs/src/libslic3r/Point.hpp index 3727dd071..63040718b 100644 --- a/xs/src/libslic3r/Point.hpp +++ b/xs/src/libslic3r/Point.hpp @@ -210,6 +210,8 @@ inline Pointf operator*(const Pointf& point2, double scalar) { return Pointf(sca inline coordf_t cross(const Pointf &v1, const Pointf &v2) { return v1.x * v2.y - v1.y * v2.x; } inline coordf_t dot(const Pointf &v1, const Pointf &v2) { return v1.x * v2.x + v1.y * v2.y; } inline coordf_t dot(const Pointf &v) { return v.x * v.x + v.y * v.y; } +inline double length(const Vectorf &v) { return sqrt(dot(v)); } +inline double l2(const Vectorf &v) { return dot(v); } class Pointf3 : public Pointf { diff --git a/xs/src/slic3r/GUI/3DScene.cpp b/xs/src/slic3r/GUI/3DScene.cpp index 0c81bba8b..e71bd01b8 100644 --- a/xs/src/slic3r/GUI/3DScene.cpp +++ b/xs/src/slic3r/GUI/3DScene.cpp @@ -3,41 +3,39 @@ #include "../../libslic3r/libslic3r.h" #include "../../libslic3r/ExtrusionEntity.hpp" #include "../../libslic3r/ExtrusionEntityCollection.hpp" +#include "../../libslic3r/Geometry.hpp" #include "../../libslic3r/Print.hpp" #include "../../libslic3r/Slicing.hpp" #include #include #include -#include #include +#include #include #include -#include namespace Slic3r { -void GLVertexArray::load_mesh(const TriangleMesh &mesh) +void GLIndexedVertexArray::load_mesh_flat_shading(const TriangleMesh &mesh) { - this->reserve_more(3 * 3 * mesh.facets_count()); + this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); for (int i = 0; i < mesh.stl.stats.number_of_facets; ++ i) { - stl_facet &facet = mesh.stl.facet_start[i]; - for (int j = 0; j < 3; ++ j) { - this->push_norm(facet.normal.x, facet.normal.y, facet.normal.z); - this->push_vert(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z); - } + const stl_facet &facet = mesh.stl.facet_start[i]; + for (int j = 0; j < 3; ++ j) + this->push_geometry(facet.vertex[j].x, facet.vertex[j].y, facet.vertex[j].z, facet.normal.x, facet.normal.y, facet.normal.z); } } void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; - this->qverts_range.second = this->qverts.size(); + this->qverts_range.second = this->indexed_vertex_array.quad_indices.size(); this->tverts_range.first = 0; - this->tverts_range.second = this->tverts.size(); + this->tverts_range.second = this->indexed_vertex_array.triangle_indices.size(); if (! this->print_zs.empty()) { // The Z layer range is specified. // First test whether the Z span of this object is not out of (min_z, max_z) completely. @@ -131,8 +129,8 @@ std::vector GLVolumeCollection::load_object( color[3] = model_volume->modifier ? 0.5f : 1.f; this->volumes.emplace_back(new GLVolume(color)); GLVolume &v = *this->volumes.back(); - v.tverts.load_mesh(mesh); - v.bounding_box = v.tverts.bounding_box(); + v.indexed_vertex_array.load_mesh_flat_shading(mesh); + v.bounding_box = v.indexed_vertex_array.bounding_box(); v.composite_id = obj_idx * 1000000 + volume_idx * 1000 + instance_idx; if (select_by == "object") v.select_group_id = obj_idx * 1000000; @@ -153,6 +151,251 @@ std::vector GLVolumeCollection::load_object( } // caller is responsible for supplying NO lines with zero length +static void thick_lines_to_indexed_vertex_array( + const Lines &lines, + const std::vector &widths, + const std::vector &heights, + bool closed, + double top_z, + GLIndexedVertexArray &volume) +{ + assert(! lines.empty()); + if (lines.empty()) + return; + +#define LEFT 0 +#define RIGHT 1 +#define TOP 2 +#define BOTTOM 3 + + Line prev_line; + // right, left, top, bottom + int idx_prev[4] = { -1, -1, -1, -1 }; + double width_prev = 0.; + double bottom_z_prev = 0.; + Pointf b1_prev; + Pointf b2_prev; + Vectorf v_prev; + int idx_initial[4] = { -1, -1, -1, -1 }; + double width_initial = 0.; + double bottom_z_initial = 0.; + + // loop once more in case of closed loops + size_t lines_end = closed ? (lines.size() + 1) : lines.size(); + for (size_t ii = 0; ii < lines_end; ++ ii) { + size_t i = (ii == lines.size()) ? 0 : ii; + const Line &line = lines[i]; + double len = unscale(line.length()); + double bottom_z = top_z - heights[i]; + double middle_z = (top_z + bottom_z) / 2.; + double width = widths[i]; + + Vectorf v = Vectorf::new_unscale(line.vector()); + v.scale(1. / len); + + Pointf a = Pointf::new_unscale(line.a); + Pointf b = Pointf::new_unscale(line.b); + Pointf a1 = a; + Pointf a2 = a; + Pointf b1 = b; + Pointf b2 = b; + { + double dist = width / 2.; // scaled + a1.translate(+dist*v.y, -dist*v.x); + a2.translate(-dist*v.y, +dist*v.x); + b1.translate(+dist*v.y, -dist*v.x); + b2.translate(-dist*v.y, +dist*v.x); + } + + // calculate new XY normals + Vector n = line.normal(); + Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); + xy_right_normal.scale(1.f / len); + + int idx_a[4]; + int idx_b[4]; + int idx_last = int(volume.vertices_and_normals_interleaved.size() / 6); + + bool width_different = width_prev != width; + bool bottom_z_different = bottom_z_prev != bottom_z; + width_prev = width; + bottom_z_prev = bottom_z; + + // Share top / bottom vertices if possible. + if (ii == 0) { + idx_a[TOP] = idx_last ++; + volume.push_geometry(a.x, a.y, top_z , 0., 0., 1.); + } else { + idx_a[TOP] = idx_prev[TOP]; + } + if (ii == 0 || bottom_z_different) { + idx_a[BOTTOM] = idx_last ++; + volume.push_geometry(a.x, a.y, bottom_z, 0., 0., -1.); + } else { + idx_a[BOTTOM] = idx_prev[BOTTOM]; + } + + bool sharp = true; + if (ii == 0) { + // Start of the 1st line segment. + idx_a[LEFT ] = idx_last ++; + volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); + idx_a[RIGHT] = idx_last ++; + volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); + width_initial = width; + bottom_z_initial = bottom_z; + memcpy(idx_initial, idx_a, sizeof(int) * 4); + } else { + // Continuing a previous segment. + // Share left / right vertices if possible. + double v_dot = dot(v_prev, v); + bool sharp = v_dot < 0.707; // sin(45 degrees) + if (sharp) { + // Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn. + idx_a[RIGHT] = idx_last ++; + volume.push_geometry(a1.x, a1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); + idx_a[LEFT ] = idx_last ++; + volume.push_geometry(a2.x, a2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); + } + if (v_dot > 0.9) { + // The two successive segments are nearly collinear. + idx_a[LEFT ] = idx_prev[LEFT]; + idx_a[RIGHT] = idx_prev[RIGHT]; + } else if (! sharp) { + // Create a sharp corner with an overshot and average the left / right normals. + // At the crease angle of 45 degrees, the overshot at the corner will be less than (1-1/cos(PI/8)) = 8.2% over an arc. + Pointf intersection; + Geometry::ray_ray_intersection(b1_prev, v_prev, a1, v, intersection); + a1 = intersection; + a2 = 2. * a - intersection; + assert(length(a1.vector_to(a)) < width); + assert(length(a2.vector_to(a)) < width); + float *n_left_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6; + float *p_left_prev = n_left_prev + 3; + float *n_right_prev = volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6; + float *p_right_prev = n_right_prev + 3; + p_left_prev [0] = float(a2.x); + p_left_prev [1] = float(a2.y); + p_right_prev[0] = float(a1.x); + p_right_prev[1] = float(a1.y); + xy_right_normal.x += n_right_prev[0]; + xy_right_normal.y += n_right_prev[1]; + xy_right_normal.scale(1. / length(xy_right_normal)); + n_left_prev [0] = float(-xy_right_normal.x); + n_left_prev [1] = float(-xy_right_normal.y); + n_right_prev[0] = float( xy_right_normal.x); + n_right_prev[1] = float( xy_right_normal.y); + idx_a[LEFT ] = idx_prev[LEFT ]; + idx_a[RIGHT] = idx_prev[RIGHT]; + } else if (cross(v_prev, v) > 0.) { + // Right turn. Fill in the right turn wedge. + volume.triangle_indices.push_back(idx_prev[RIGHT]); + volume.triangle_indices.push_back(idx_a [RIGHT]); + volume.triangle_indices.push_back(idx_prev[TOP]); + volume.triangle_indices.push_back(idx_prev[RIGHT]); + volume.triangle_indices.push_back(idx_prev[BOTTOM]); + volume.triangle_indices.push_back(idx_a [RIGHT]); + } else { + // Left turn. Fill in the left turn wedge. + volume.triangle_indices.push_back(idx_prev[LEFT]); + volume.triangle_indices.push_back(idx_prev[TOP]); + volume.triangle_indices.push_back(idx_a [LEFT]); + volume.triangle_indices.push_back(idx_prev[LEFT]); + volume.triangle_indices.push_back(idx_a [LEFT]); + volume.triangle_indices.push_back(idx_prev[BOTTOM]); + } + if (ii == lines.size()) { + if (! sharp) { + // Closing a loop with smooth transition. Unify the closing left / right vertices. + memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[LEFT ] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[LEFT ] * 6, sizeof(float) * 6); + memcpy(volume.vertices_and_normals_interleaved.data() + idx_initial[RIGHT] * 6, volume.vertices_and_normals_interleaved.data() + idx_prev[RIGHT] * 6, sizeof(float) * 6); + volume.vertices_and_normals_interleaved.erase(volume.vertices_and_normals_interleaved.end() - 12, volume.vertices_and_normals_interleaved.end()); + // Replace the left / right vertex indices to point to the start of the loop. + for (size_t u = volume.quad_indices.size() - 16; u < volume.quad_indices.size(); ++ u) { + if (volume.quad_indices[u] == idx_prev[LEFT]) + volume.quad_indices[u] = idx_initial[LEFT]; + else if (volume.quad_indices[u] == idx_prev[RIGHT]) + volume.quad_indices[u] = idx_initial[RIGHT]; + } + } + // This is the last iteration, only required to solve the transition. + break; + } + } + + // Only new allocate top / bottom vertices, if not closing a loop. + if (closed && ii + 1 == lines.size()) { + idx_b[TOP] = idx_initial[TOP]; + } else { + idx_b[TOP] = idx_last ++; + volume.push_geometry(b.x, b.y, top_z , 0., 0., 1.); + } + if (closed && ii + 1 == lines.size() && width == width_initial) { + idx_b[BOTTOM] = idx_initial[BOTTOM]; + } else { + idx_b[BOTTOM] = idx_last ++; + volume.push_geometry(b.x, b.y, bottom_z, 0., 0., -1.); + } + // Generate new vertices for the end of this line segment. + idx_b[LEFT ] = idx_last ++; + volume.push_geometry(b2.x, b2.y, middle_z, -xy_right_normal.x, -xy_right_normal.y, -xy_right_normal.z); + idx_b[RIGHT ] = idx_last ++; + volume.push_geometry(b1.x, b1.y, middle_z, xy_right_normal.x, xy_right_normal.y, xy_right_normal.z); + + prev_line = line; + memcpy(idx_prev, idx_b, 4 * sizeof(int)); + width_prev = width; + bottom_z_prev = bottom_z; + b1_prev = b1; + b2_prev = b2; + v_prev = v; + + if (! closed) { + // Terminate open paths with caps. + if (i == 0) { + volume.quad_indices.push_back(idx_a[BOTTOM]); + volume.quad_indices.push_back(idx_a[RIGHT]); + volume.quad_indices.push_back(idx_a[TOP]); + volume.quad_indices.push_back(idx_a[LEFT]); + } + // We don't use 'else' because both cases are true if we have only one line. + if (i + 1 == lines.size()) { + volume.quad_indices.push_back(idx_b[BOTTOM]); + volume.quad_indices.push_back(idx_b[LEFT]); + volume.quad_indices.push_back(idx_b[TOP]); + volume.quad_indices.push_back(idx_b[RIGHT]); + } + } + + // Add quads for a straight hollow tube-like segment. + // bottom-right face + volume.quad_indices.push_back(idx_a[BOTTOM]); + volume.quad_indices.push_back(idx_b[BOTTOM]); + volume.quad_indices.push_back(idx_b[RIGHT]); + volume.quad_indices.push_back(idx_a[RIGHT]); + // top-right face + volume.quad_indices.push_back(idx_a[RIGHT]); + volume.quad_indices.push_back(idx_b[RIGHT]); + volume.quad_indices.push_back(idx_b[TOP]); + volume.quad_indices.push_back(idx_a[TOP]); + // top-left face + volume.quad_indices.push_back(idx_a[TOP]); + volume.quad_indices.push_back(idx_b[TOP]); + volume.quad_indices.push_back(idx_b[LEFT]); + volume.quad_indices.push_back(idx_a[LEFT]); + // bottom-left face + volume.quad_indices.push_back(idx_a[LEFT]); + volume.quad_indices.push_back(idx_b[LEFT]); + volume.quad_indices.push_back(idx_b[BOTTOM]); + volume.quad_indices.push_back(idx_a[BOTTOM]); + } + +#undef LEFT +#undef RIGHT +#undef TOP +#undef BOTTOM +} + static void thick_lines_to_verts( const Lines &lines, const std::vector &widths, @@ -161,451 +404,7 @@ static void thick_lines_to_verts( double top_z, GLVolume &volume) { - /* It looks like it's faster without reserving capacity... - // each segment has 4 quads, thus 16 vertices; + 2 caps - volume.qverts.reserve_more(3 * 4 * (4 * lines.size() + 2)); - - // two triangles for each corner - volume.tverts.reserve_more(3 * 3 * 2 * (lines.size() + 1)); - */ - - assert(! lines.empty()); - if (lines.empty()) - return; - - Line prev_line; - Pointf prev_b1, prev_b2; - Vectorf3 prev_xy_left_normal, prev_xy_right_normal; - - // loop once more in case of closed loops - for (size_t ii = 0; ii <= lines.size(); ++ ii) { - size_t i = ii; - if (ii == lines.size()) { - if (! closed) - break; - i = 0; - } - - const Line &line = lines[i]; - double len = line.length(); - double unscaled_len = unscale(len); - double bottom_z = top_z - heights[i]; - double middle_z = (top_z + bottom_z) / 2; - double dist = widths.at(i)/2; // scaled - - Vectorf v = Vectorf::new_unscale(line.vector()); - v.scale(1. / unscaled_len); - - Pointf a = Pointf::new_unscale(line.a); - Pointf b = Pointf::new_unscale(line.b); - Pointf a1 = a; - Pointf a2 = a; - a1.translate(+dist*v.y, -dist*v.x); - a2.translate(-dist*v.y, +dist*v.x); - Pointf b1 = b; - Pointf b2 = b; - b1.translate(+dist*v.y, -dist*v.x); - b2.translate(-dist*v.y, +dist*v.x); - - // calculate new XY normals - Vector n = line.normal(); - Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); - xy_right_normal.scale(1.f / unscaled_len); - Vectorf3 xy_left_normal = xy_right_normal; - xy_left_normal.scale(-1.f); - - if (ii > 0) { - // if we're making a ccw turn, draw the triangles on the right side, otherwise draw them on the left side - double ccw = line.b.ccw(prev_line); - if (ccw > EPSILON) { - // top-right vertex triangle between previous line and this one - { - // use the normal going to the right calculated for the previous line - volume.tverts.push_norm(prev_xy_right_normal); - volume.tverts.push_vert(prev_b1.x, prev_b1.y, middle_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_right_normal); - volume.tverts.push_vert(a1.x, a1.y, middle_z); - - // normal going upwards - volume.tverts.push_norm(0.f, 0.f, 1.f); - volume.tverts.push_vert(a.x, a.y, top_z); - } - // bottom-right vertex triangle between previous line and this one - { - // use the normal going to the right calculated for the previous line - volume.tverts.push_norm(prev_xy_right_normal); - volume.tverts.push_vert(prev_b1.x, prev_b1.y, middle_z); - - // normal going downwards - volume.tverts.push_norm(0.f, 0.f, -1.f); - volume.tverts.push_vert(a.x, a.y, bottom_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_right_normal); - volume.tverts.push_vert(a1.x, a1.y, middle_z); - } - } else if (ccw < -EPSILON) { - // top-left vertex triangle between previous line and this one - { - // use the normal going to the left calculated for the previous line - volume.tverts.push_norm(prev_xy_left_normal); - volume.tverts.push_vert(prev_b2.x, prev_b2.y, middle_z); - - // normal going upwards - volume.tverts.push_norm(0.f, 0.f, 1.f); - volume.tverts.push_vert(a.x, a.y, top_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_left_normal); - volume.tverts.push_vert(a2.x, a2.y, middle_z); - } - // bottom-left vertex triangle between previous line and this one - { - // use the normal going to the left calculated for the previous line - volume.tverts.push_norm(prev_xy_left_normal); - volume.tverts.push_vert(prev_b2.x, prev_b2.y, middle_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_left_normal); - volume.tverts.push_vert(a2.x, a2.y, middle_z); - - // normal going downwards - volume.tverts.push_norm(0.f, 0.f, -1.f); - volume.tverts.push_vert(a.x, a.y, bottom_z); - } - } - } - - // if this was the extra iteration we were only interested in the triangles - if (ii == lines.size()) - break; - - prev_line = line; - prev_b1 = b1; - prev_b2 = b2; - prev_xy_right_normal = xy_right_normal; - prev_xy_left_normal = xy_left_normal; - - if (! closed) { - // terminate open paths with caps - if (i == 0) { - // normal pointing downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(a.x, a.y, bottom_z); - - // normal pointing to the right - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - - // normal pointing upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(a.x, a.y, top_z); - - // normal pointing to the left - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - } - // we don't use 'else' because both cases are true if we have only one line - if (i + 1 == lines.size()) { - // normal pointing downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(b.x, b.y, bottom_z); - - // normal pointing to the left - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - - // normal pointing upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(b.x, b.y, top_z); - - // normal pointing to the right - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - } - } - - // bottom-right face - { - // normal going downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(a.x, a.y, bottom_z); - volume.qverts.push_vert(b.x, b.y, bottom_z); - - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - } - - // top-right face - { - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - - // normal going upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(b.x, b.y, top_z); - volume.qverts.push_vert(a.x, a.y, top_z); - } - - // top-left face - { - volume.qverts.push_norm(0,0,1); - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(a.x, a.y, top_z); - volume.qverts.push_vert(b.x, b.y, top_z); - - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - } - - // bottom-left face - { - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - - // normal going downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(b.x, b.y, bottom_z); - volume.qverts.push_vert(a.x, a.y, bottom_z); - } - } -} - -// caller is responsible for supplying NO lines with zero length -static void thick_lines_to_VBOs( - const Lines &lines, - const std::vector &widths, - const std::vector &heights, - bool closed, - double top_z, - GLVolume &volume) -{ - assert(! lines.empty()); - if (lines.empty()) - return; - - Line prev_line; - Pointf prev_b1, prev_b2; - Vectorf3 prev_xy_left_normal, prev_xy_right_normal; - - // loop once more in case of closed loops - for (size_t ii = 0; ii <= lines.size(); ++ ii) { - size_t i = ii; - if (ii == lines.size()) { - if (! closed) - break; - i = 0; - } - - const Line &line = lines[i]; - double len = line.length(); - double unscaled_len = unscale(len); - double bottom_z = top_z - heights[i]; - double middle_z = (top_z + bottom_z) / 2; - double dist = widths.at(i)/2; // scaled - - Vectorf v = Vectorf::new_unscale(line.vector()); - v.scale(1. / unscaled_len); - - Pointf a = Pointf::new_unscale(line.a); - Pointf b = Pointf::new_unscale(line.b); - Pointf a1 = a; - Pointf a2 = a; - a1.translate(+dist*v.y, -dist*v.x); - a2.translate(-dist*v.y, +dist*v.x); - Pointf b1 = b; - Pointf b2 = b; - b1.translate(+dist*v.y, -dist*v.x); - b2.translate(-dist*v.y, +dist*v.x); - - // calculate new XY normals - Vector n = line.normal(); - Vectorf3 xy_right_normal = Vectorf3::new_unscale(n.x, n.y, 0); - xy_right_normal.scale(1.f / unscaled_len); - Vectorf3 xy_left_normal = xy_right_normal; - xy_left_normal.scale(-1.f); - - if (ii > 0) { - // if we're making a ccw turn, draw the triangles on the right side, otherwise draw them on the left side - double ccw = line.b.ccw(prev_line); - if (ccw > EPSILON) { - // top-right vertex triangle between previous line and this one - { - // use the normal going to the right calculated for the previous line - volume.tverts.push_norm(prev_xy_right_normal); - volume.tverts.push_vert(prev_b1.x, prev_b1.y, middle_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_right_normal); - volume.tverts.push_vert(a1.x, a1.y, middle_z); - - // normal going upwards - volume.tverts.push_norm(0.f, 0.f, 1.f); - volume.tverts.push_vert(a.x, a.y, top_z); - } - // bottom-right vertex triangle between previous line and this one - { - // use the normal going to the right calculated for the previous line - volume.tverts.push_norm(prev_xy_right_normal); - volume.tverts.push_vert(prev_b1.x, prev_b1.y, middle_z); - - // normal going downwards - volume.tverts.push_norm(0.f, 0.f, -1.f); - volume.tverts.push_vert(a.x, a.y, bottom_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_right_normal); - volume.tverts.push_vert(a1.x, a1.y, middle_z); - } - } else if (ccw < -EPSILON) { - // top-left vertex triangle between previous line and this one - { - // use the normal going to the left calculated for the previous line - volume.tverts.push_norm(prev_xy_left_normal); - volume.tverts.push_vert(prev_b2.x, prev_b2.y, middle_z); - - // normal going upwards - volume.tverts.push_norm(0.f, 0.f, 1.f); - volume.tverts.push_vert(a.x, a.y, top_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_left_normal); - volume.tverts.push_vert(a2.x, a2.y, middle_z); - } - // bottom-left vertex triangle between previous line and this one - { - // use the normal going to the left calculated for the previous line - volume.tverts.push_norm(prev_xy_left_normal); - volume.tverts.push_vert(prev_b2.x, prev_b2.y, middle_z); - - // use the normal going to the right calculated for this line - volume.tverts.push_norm(xy_left_normal); - volume.tverts.push_vert(a2.x, a2.y, middle_z); - - // normal going downwards - volume.tverts.push_norm(0.f, 0.f, -1.f); - volume.tverts.push_vert(a.x, a.y, bottom_z); - } - } - } - - // if this was the extra iteration we were only interested in the triangles - if (ii == lines.size()) - break; - - prev_line = line; - prev_b1 = b1; - prev_b2 = b2; - prev_xy_right_normal = xy_right_normal; - prev_xy_left_normal = xy_left_normal; - - if (! closed) { - // terminate open paths with caps - if (i == 0) { - // normal pointing downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(a.x, a.y, bottom_z); - - // normal pointing to the right - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - - // normal pointing upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(a.x, a.y, top_z); - - // normal pointing to the left - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - } - // we don't use 'else' because both cases are true if we have only one line - if (i + 1 == lines.size()) { - // normal pointing downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(b.x, b.y, bottom_z); - - // normal pointing to the left - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - - // normal pointing upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(b.x, b.y, top_z); - - // normal pointing to the right - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - } - } - - // bottom-right face - { - // normal going downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(a.x, a.y, bottom_z); - volume.qverts.push_vert(b.x, b.y, bottom_z); - - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - } - - // top-right face - { - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_norm(xy_right_normal); - volume.qverts.push_vert(a1.x, a1.y, middle_z); - volume.qverts.push_vert(b1.x, b1.y, middle_z); - - // normal going upwards - volume.qverts.push_norm(0,0,1); - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(b.x, b.y, top_z); - volume.qverts.push_vert(a.x, a.y, top_z); - } - - // top-left face - { - volume.qverts.push_norm(0,0,1); - volume.qverts.push_norm(0,0,1); - volume.qverts.push_vert(a.x, a.y, top_z); - volume.qverts.push_vert(b.x, b.y, top_z); - - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - } - - // bottom-left face - { - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_norm(xy_left_normal); - volume.qverts.push_vert(a2.x, a2.y, middle_z); - volume.qverts.push_vert(b2.x, b2.y, middle_z); - - // normal going downwards - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_norm(0,0,-1); - volume.qverts.push_vert(b.x, b.y, bottom_z); - volume.qverts.push_vert(a.x, a.y, bottom_z); - } - } + thick_lines_to_indexed_vertex_array(lines, widths, heights, closed, top_z, volume.indexed_vertex_array); } // Fill in the qverts and tverts with quads and triangles for the extrusion_path. @@ -731,8 +530,8 @@ void _3DScene::_load_print_toolpaths( GLVolume &volume = *volumes->volumes.back(); for (size_t i = 0; i < skirt_height; ++ i) { volume.print_zs.push_back(print_zs[i]); - volume.offsets.push_back(volume.qverts.size()); - volume.offsets.push_back(volume.tverts.size()); + volume.offsets.push_back(volume.indexed_vertex_array.quad_indices.size()); + volume.offsets.push_back(volume.indexed_vertex_array.triangle_indices.size()); if (i == 0) extrusionentity_to_verts(print->brim, print_zs[i], Point(0, 0), volume); extrusionentity_to_verts(print->skirt, print_zs[i], Point(0, 0), volume); @@ -807,8 +606,7 @@ void _3DScene::_load_print_object_toolpaths( for (size_t i = 0; i < 3; ++ i) { GLVolume &volume = *volumes[i]; volume.bounding_box = ctxt.bbox; - volume.qverts.reserve(ctxt.alloc_size_reserve()); - volume.tverts.reserve(ctxt.alloc_size_reserve()); + volume.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { const Layer *layer = ctxt.layers[idx_layer]; @@ -816,8 +614,8 @@ void _3DScene::_load_print_object_toolpaths( GLVolume &vol = *volumes[vols[i]]; if (vol.print_zs.empty() || vol.print_zs.back() != layer->print_z) { vol.print_zs.push_back(layer->print_z); - vol.offsets.push_back(vol.qverts.size()); - vol.offsets.push_back(vol.tverts.size()); + vol.offsets.push_back(vol.indexed_vertex_array.quad_indices.size()); + vol.offsets.push_back(vol.indexed_vertex_array.triangle_indices.size()); } } for (const Point ©: *ctxt.shifted_copies) { @@ -837,17 +635,15 @@ void _3DScene::_load_print_object_toolpaths( } for (size_t i = 0; i < 3; ++ i) { GLVolume &vol = *volumes[vols[i]]; - if (vol.qverts.size() > ctxt.alloc_size_max() || vol.tverts.size() > ctxt.alloc_size_max()) { + if (vol.indexed_vertex_array.vertices_and_normals_interleaved.size() / 6 > ctxt.alloc_size_max()) { // Shrink the old vectors to preserve memory. - vol.qverts.shrink_to_fit(); - vol.tverts.shrink_to_fit(); + vol.indexed_vertex_array.shrink_to_fit(); // Store the vertex arrays and restart their containers. vols[i] = volumes.size(); volumes.emplace_back(new GLVolume(vol.color)); GLVolume &vol_new = *volumes.back(); vol_new.bounding_box = ctxt.bbox; - vol_new.qverts.reserve(ctxt.alloc_size_reserve()); - vol_new.tverts.reserve(ctxt.alloc_size_reserve()); + vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); } } } diff --git a/xs/src/slic3r/GUI/3DScene.hpp b/xs/src/slic3r/GUI/3DScene.hpp index 77ffb1fa6..0049dfe3f 100644 --- a/xs/src/slic3r/GUI/3DScene.hpp +++ b/xs/src/slic3r/GUI/3DScene.hpp @@ -13,64 +13,66 @@ class PrintObject; class Model; class ModelObject; -class GLVertexArray { +// A container for interleaved arrays of 3D vertices and normals, +// possibly indexed by triangles and / or quads. +class GLIndexedVertexArray { public: - GLVertexArray() {} - GLVertexArray(const GLVertexArray &rhs) : verts(rhs.verts), norms(rhs.norms) {} - GLVertexArray(GLVertexArray &&rhs) : verts(std::move(rhs.verts)), norms(std::move(rhs.norms)) {} + GLIndexedVertexArray() {} - GLVertexArray& operator=(const GLVertexArray &rhs) { verts = rhs.verts; norms = rhs.norms; return *this; } - GLVertexArray& operator=(GLVertexArray &&rhs) { verts = std::move(rhs.verts); norms = std::move(rhs.norms); return *this; } + // Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x) + std::vector vertices_and_normals_interleaved; + std::vector triangle_indices; + std::vector quad_indices; - std::vector verts, norms; - - void reserve(size_t len) { - this->verts.reserve(len); - this->norms.reserve(len); - }; - void reserve_more(size_t len) { - len += this->verts.size(); - this->reserve(len); - }; - void push_vert(const Pointf3 &point) { - this->verts.push_back(point.x); - this->verts.push_back(point.y); - this->verts.push_back(point.z); - }; - void push_vert(float x, float y, float z) { - this->verts.push_back(x); - this->verts.push_back(y); - this->verts.push_back(z); - }; - void push_norm(const Pointf3 &point) { - this->norms.push_back(point.x); - this->norms.push_back(point.y); - this->norms.push_back(point.z); - }; - void push_norm(float x, float y, float z) { - this->norms.push_back(x); - this->norms.push_back(y); - this->norms.push_back(z); - }; - void load_mesh(const TriangleMesh &mesh); + void load_mesh_flat_shading(const TriangleMesh &mesh); - size_t size() const { return verts.size(); } - bool empty() const { return verts.empty(); } - void shrink_to_fit() { this->verts.shrink_to_fit(); this->norms.shrink_to_fit(); } + inline void reserve(size_t sz) { + this->vertices_and_normals_interleaved.reserve(sz * 6); + this->triangle_indices.reserve(sz * 3); + this->quad_indices.reserve(sz * 4); + } + + inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) { + this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 6); + this->vertices_and_normals_interleaved.push_back(nx); + this->vertices_and_normals_interleaved.push_back(ny); + this->vertices_and_normals_interleaved.push_back(nz); + this->vertices_and_normals_interleaved.push_back(x); + this->vertices_and_normals_interleaved.push_back(y); + this->vertices_and_normals_interleaved.push_back(z); + }; + + inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { + push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz)); + } + + // Is there any geometry data stored? + bool empty() const { return vertices_and_normals_interleaved.empty(); } + + // Is this object indexed, or is it just a set of triangles? + bool indexed() const { return ! this->empty() && (! this->triangle_indices.empty() || ! this->quad_indices.empty()); } + + // Shrink the internal storage to tighly fit the data stored. + void shrink_to_fit() { + this->vertices_and_normals_interleaved.shrink_to_fit(); + this->triangle_indices.shrink_to_fit(); + this->quad_indices.shrink_to_fit(); + } BoundingBoxf3 bounding_box() const { BoundingBoxf3 bbox; - if (! this->verts.empty()) { - bbox.min.x = bbox.max.x = this->verts[0]; - bbox.min.y = bbox.max.y = this->verts[1]; - bbox.min.z = bbox.max.z = this->verts[2]; - for (size_t i = 3; i < this->verts.size(); i += 3) { - bbox.min.x = std::min(bbox.min.x, this->verts[i + 0]); - bbox.min.y = std::min(bbox.min.y, this->verts[i + 1]); - bbox.min.z = std::min(bbox.min.z, this->verts[i + 2]); - bbox.max.x = std::max(bbox.max.x, this->verts[i + 0]); - bbox.max.y = std::max(bbox.max.y, this->verts[i + 1]); - bbox.max.z = std::max(bbox.max.z, this->verts[i + 2]); + if (! this->vertices_and_normals_interleaved.empty()) { + bbox.min.x = bbox.max.x = this->vertices_and_normals_interleaved[3]; + bbox.min.y = bbox.max.y = this->vertices_and_normals_interleaved[4]; + bbox.min.z = bbox.max.z = this->vertices_and_normals_interleaved[5]; + for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) { + const float *verts = this->vertices_and_normals_interleaved.data() + i; + bbox.min.x = std::min(bbox.min.x, verts[0]); + bbox.min.y = std::min(bbox.min.y, verts[1]); + bbox.min.z = std::min(bbox.min.z, verts[2]); + bbox.max.x = std::max(bbox.max.x, verts[0]); + bbox.max.y = std::max(bbox.max.y, verts[1]); + bbox.max.z = std::max(bbox.max.z, verts[2]); } } return bbox; @@ -116,7 +118,7 @@ public: GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} std::vector load_object( - const ModelObject *model_object, + const ModelObject *model_object, const std::vector &instance_idxs, const std::string &color_by, const std::string &select_by, @@ -140,43 +142,47 @@ public: // Boolean: Is mouse over this object? bool hover; - // Geometric data. - // Quad vertices. - GLVertexArray qverts; - std::pair qverts_range; - // Triangle vertices. - GLVertexArray tverts; + // Interleaved triangles & normals with indexed triangles & quads. + GLIndexedVertexArray indexed_vertex_array; + // Ranges of triangle and quad indices to be rendered. std::pair tverts_range; - // OpenGL buffers for vertices and their normals. - int name_vertex_buffer; - int name_normal_buffer; - // OpenGL buffer of the indices. - int name_index_buffer; - // Triangle indices for the vertex buffer object. - std::vector triangle_indices; + std::pair qverts_range; + // If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts // of the extrusions per layer. std::vector print_zs; // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. std::vector offsets; - int object_idx() const { return this->composite_id / 1000000; } - int volume_idx() const { return (this->composite_id / 1000) % 1000; } - int instance_idx() const { return this->composite_id % 1000; } - BoundingBoxf3 transformed_bounding_box() const { BoundingBoxf3 bb = this->bounding_box; bb.translate(this->origin); return bb; } - bool empty() const { return qverts.size() < 4 && tverts.size() < 3; } + // OpenGL buffers for vertices and their normals. + int name_vertex_buffer; + int name_normal_buffer; + // OpenGL buffer of the indices. + int name_index_buffer; - void set_range(coordf_t low, coordf_t high); + int object_idx() const { return this->composite_id / 1000000; } + int volume_idx() const { return (this->composite_id / 1000) % 1000; } + int instance_idx() const { return this->composite_id % 1000; } + BoundingBoxf3 transformed_bounding_box() const { BoundingBoxf3 bb = this->bounding_box; bb.translate(this->origin); return bb; } - void* qverts_to_render_ptr() { return qverts.verts.data() + qverts_range.first; } - void* qnorms_to_render_ptr() { return qverts.norms.data() + qverts_range.first; } - size_t qverts_to_render_cnt() { return std::min(qverts.verts.size(), qverts_range.second - qverts_range.first); } - void* tverts_to_render_ptr() { return tverts.verts.data() + tverts_range.first; } - void* tnorms_to_render_ptr() { return tverts.norms.data() + tverts_range.first; } - size_t tverts_to_render_cnt() { return std::min(tverts.verts.size(), tverts_range.second - tverts_range.first); } + bool empty() const { return this->indexed_vertex_array.empty(); } + bool indexed() const { return this->indexed_vertex_array.indexed(); } + + void set_range(coordf_t low, coordf_t high); + + // Non-indexed interleaved vertices & normals, likely forming triangles. + void* triangles_to_render_ptr() { return indexed_vertex_array.vertices_and_normals_interleaved.data(); } + size_t triangles_to_render_cnt() { return indexed_vertex_array.vertices_and_normals_interleaved.size() / (3 * 2); } + // Indexed triangles & quads. + void* triangle_indices_to_render_ptr() { return indexed_vertex_array.triangle_indices.data() + tverts_range.first; } + void* quad_indices_to_render_ptr() { return indexed_vertex_array.quad_indices.data() + qverts_range.first; } + size_t indexed_triangles_to_render_cnt() { return std::min(indexed_vertex_array.triangle_indices.size(), tverts_range.second - tverts_range.first); } + size_t indexed_quads_to_render_cnt() { return std::min(indexed_vertex_array.quad_indices.size(), qverts_range.second - qverts_range.first); } void render_VBOs() const; + + /************************************************ Layer height texture ****************************************************/ std::shared_ptr layer_height_texture; bool has_layer_height_texture() const diff --git a/xs/xsp/GUI_3DScene.xsp b/xs/xsp/GUI_3DScene.xsp index e06b640a7..48f9a3f96 100644 --- a/xs/xsp/GUI_3DScene.xsp +++ b/xs/xsp/GUI_3DScene.xsp @@ -27,7 +27,6 @@ int object_idx() const; int volume_idx() const; int instance_idx() const; - bool empty() const; Clone origin() const %code%{ RETVAL = THIS->origin; %}; void translate(double x, double y, double z) @@ -36,12 +35,15 @@ %code%{ RETVAL = THIS->bounding_box; %}; Clone transformed_bounding_box() const; - void* qverts_to_render_ptr(); - void* qnorms_to_render_ptr(); - int qverts_to_render_cnt(); - void* tverts_to_render_ptr(); - void* tnorms_to_render_ptr(); - int tverts_to_render_cnt(); + bool empty() const; + bool indexed() const; + + void* triangles_to_render_ptr(); + size_t triangles_to_render_cnt(); + void* triangle_indices_to_render_ptr(); + void* quad_indices_to_render_ptr(); + size_t indexed_triangles_to_render_cnt(); + size_t indexed_quads_to_render_cnt(); bool has_layer_height_texture(); int layer_height_texture_width();