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.
This commit is contained in:
bubnikv 2017-03-15 16:33:25 +01:00
parent e7a920fe16
commit d18e10c7c9
6 changed files with 414 additions and 566 deletions

View File

@ -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) {

View File

@ -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<Points::size_type> &retval, Point start_near);

View File

@ -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
{

View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <utility>
#include <assert.h>
#include <boost/log/trivial.hpp>
#include <tbb/parallel_for.h>
#include <tbb/atomic.h>
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<int> 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<int> 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<double> &widths,
const std::vector<double> &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<double> &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<double> &widths,
const std::vector<double> &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 &copy: *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());
}
}
}

View File

@ -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<float> vertices_and_normals_interleaved;
std::vector<int> triangle_indices;
std::vector<int> quad_indices;
std::vector<float> 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<coordf_t>(bbox.min.x, this->verts[i + 0]);
bbox.min.y = std::min<coordf_t>(bbox.min.y, this->verts[i + 1]);
bbox.min.z = std::min<coordf_t>(bbox.min.z, this->verts[i + 2]);
bbox.max.x = std::max<coordf_t>(bbox.max.x, this->verts[i + 0]);
bbox.max.y = std::max<coordf_t>(bbox.max.y, this->verts[i + 1]);
bbox.max.z = std::max<coordf_t>(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<coordf_t>(bbox.min.x, verts[0]);
bbox.min.y = std::min<coordf_t>(bbox.min.y, verts[1]);
bbox.min.z = std::min<coordf_t>(bbox.min.z, verts[2]);
bbox.max.x = std::max<coordf_t>(bbox.max.x, verts[0]);
bbox.max.y = std::max<coordf_t>(bbox.max.y, verts[1]);
bbox.max.z = std::max<coordf_t>(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<int> load_object(
const ModelObject *model_object,
const ModelObject *model_object,
const std::vector<int> &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<size_t, size_t> 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<size_t, size_t> 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<size_t> triangle_indices;
std::pair<size_t, size_t> qverts_range;
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
// of the extrusions per layer.
std::vector<coordf_t> print_zs;
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> 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<GLTexture> layer_height_texture;
bool has_layer_height_texture() const

View File

@ -27,7 +27,6 @@
int object_idx() const;
int volume_idx() const;
int instance_idx() const;
bool empty() const;
Clone<Pointf3> origin() const
%code%{ RETVAL = THIS->origin; %};
void translate(double x, double y, double z)
@ -36,12 +35,15 @@
%code%{ RETVAL = THIS->bounding_box; %};
Clone<BoundingBoxf3> 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();