Use surface when emboss per letter.

This commit is contained in:
Filip Sykala - NTB T15p 2023-05-10 16:08:49 +02:00
parent cf57bf02ff
commit 50f09a751b
4 changed files with 189 additions and 68 deletions

View File

@ -1875,7 +1875,7 @@ PolygonPoints Emboss::sample_slice(const TextLine &slice, const BoundingBoxes &b
// find BB in center of line
size_t first_right_index = 0;
for (const BoundingBox &bb : bbs)
if (bb.min.x() > 0) {
if (bb.min.x() >= 0) {
break;
} else {
++first_right_index;

View File

@ -425,9 +425,9 @@ namespace Emboss
/// Sample slice polygon by bounding boxes centers
/// slice start point has shape_center_x coor
/// </summary>
/// <param name="slice">Polygon and start point</param>
/// <param name="bbs">Bounding boxes of letter on one line</param>
/// <param name="scale">Scale for bbs</param>
/// <param name="slice">Polygon and start point[Slic3r scaled milimeters]</param>
/// <param name="bbs">Bounding boxes of letter on one line[in font scales]</param>
/// <param name="scale">Scale for bbs (after multiply bb is in milimeters)</param>
/// <returns>Sampled polygon by bounding boxes</returns>
PolygonPoints sample_slice(const TextLine &slice, const BoundingBoxes &bbs, double scale);

View File

@ -42,6 +42,7 @@ bool check(const CreateSurfaceVolumeData &input, bool is_main_thread = false);
bool check(const UpdateSurfaceVolumeData &input, bool is_main_thread = false);
template<typename Fnc> static ExPolygons create_shape(DataBase &input, Fnc was_canceled);
template<typename Fnc> static std::vector<ExPolygons> create_shapes(DataBase &input, Fnc was_canceled);
// <summary>
/// Try to create mesh from text
@ -422,7 +423,7 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) {
const TextConfiguration &tc = input.text_configuration;
const char *text = tc.text.c_str();
const FontProp &prop = tc.style.prop;
assert(!prop.per_glyph);
assert(font.has_value());
if (!font.has_value())
return {};
@ -435,37 +436,44 @@ ExPolygons priv::create_shape(DataBase &input, Fnc was_canceled) {
return shapes;
}
#define STORE_SAMPLING
#ifdef STORE_SAMPLING
#include "libslic3r/SVG.hpp"
#endif // STORE_SAMPLING
namespace {
template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled)
{
// method use square of coord stored into int64_t
static_assert(std::is_same<Point::coord_type, int32_t>());
template<typename Fnc>
std::vector<ExPolygons> priv::create_shapes(DataBase &input, Fnc was_canceled) {
FontFileWithCache &font = input.font_file;
const TextConfiguration &tc = input.text_configuration;
assert(get_count_lines(tc.text) == input.text_lines.size());
size_t count_lines = input.text_lines.size();
std::wstring ws = boost::nowide::widen(tc.text.c_str());
const FontProp &prop = tc.style.prop;
const char *text = tc.text.c_str();
const FontProp &prop = tc.style.prop;
assert(prop.per_glyph);
assert(font.has_value());
if (!font.has_value())
return {};
std::wstring ws = boost::nowide::widen(text);
std::vector<ExPolygons> shapes = text2vshapes(font, ws, prop, was_canceled);
if (shapes.empty())
return {};
align_shape(prop.align, shapes);
align_shape(prop.align, shapes);
if (was_canceled())
return {};
const FontFile &ff = *font.font_file;
double shape_scale = get_shape_scale(prop, ff);
return shapes;
}
// Precalculate bounding boxes of glyphs
// Separate lines of text to vector of Bounds
std::vector<BoundingBoxes> bbs(count_lines);
size_t text_line_index = 0;
//#define STORE_SAMPLING
#ifdef STORE_SAMPLING
#include "libslic3r/SVG.hpp"
#endif // STORE_SAMPLING
namespace {
std::vector<BoundingBoxes> create_line_bounds(const std::vector<ExPolygons> &shapes, const std::wstring& text, size_t count_lines = 0)
{
assert(text.size() == shapes.size());
if (count_lines == 0)
count_lines = get_count_lines(text);
assert(count_lines == get_count_lines(text));
std::vector<BoundingBoxes> result(count_lines);
size_t text_line_index = 0;
// s_i .. shape index
for (size_t s_i = 0; s_i < shapes.size(); ++s_i) {
const ExPolygons &shape = shapes[s_i];
@ -473,12 +481,36 @@ template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w
if (!shape.empty()) {
bb = get_extents(shape);
}
BoundingBoxes &line_bbs = bbs[text_line_index];
BoundingBoxes &line_bbs = result[text_line_index];
line_bbs.push_back(bb);
if (ws[s_i] == '\n')
if (text[s_i] == '\n'){
// skip enters on beginig and tail
++text_line_index;
}
}
return result;
}
template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc was_canceled)
{
// method use square of coord stored into int64_t
static_assert(std::is_same<Point::coord_type, int32_t>());
std::vector<ExPolygons> shapes = priv::create_shapes(input, was_canceled);
if (shapes.empty())
return {};
// Precalculate bounding boxes of glyphs
// Separate lines of text to vector of Bounds
const TextConfiguration &tc = input.text_configuration;
std::wstring ws = boost::nowide::widen(tc.text.c_str());
assert(get_count_lines(ws) == input.text_lines.size());
size_t count_lines = input.text_lines.size();
std::vector<BoundingBoxes> bbs = create_line_bounds(shapes, ws, count_lines);
const FontProp &prop = tc.style.prop;
FontFileWithCache &font = input.font_file;
double shape_scale = get_shape_scale(prop, *font.font_file);
double projec_scale = shape_scale / SHAPE_SCALE;
double depth = prop.emboss / projec_scale;
auto projectZ = std::make_unique<ProjectZ>(depth);
@ -490,19 +522,18 @@ template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w
size_t s_i_offset = 0; // shape index offset(for next lines)
indexed_triangle_set result;
for (text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) {
for (size_t text_line_index = 0; text_line_index < input.text_lines.size(); ++text_line_index) {
const BoundingBoxes &line_bbs = bbs[text_line_index];
const TextLine &line = input.text_lines[text_line_index];
// IMPROVE: do not precalculate samples do it inline - store result its
PolygonPoints samples = sample_slice(line, line_bbs, shape_scale);
std::vector<double> angles = calculate_angles(em_2_polygon, samples, line.polygon);
for (size_t i = 0; i < line_bbs.size(); ++i) {
const BoundingBox &line_bb = line_bbs[i];
if (!line_bb.defined)
const BoundingBox &letter_bb = line_bbs[i];
if (!letter_bb.defined)
continue;
Vec2d to_zero_vec = line_bb.center().cast<double>() * shape_scale; // [in mm]
Vec2d to_zero_vec = letter_bb.center().cast<double>() * shape_scale; // [in mm]
auto to_zero = Eigen::Translation<double, 3>(-to_zero_vec.x(), 0., 0.);
const double &angle = angles[i];
@ -512,15 +543,16 @@ template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w
Vec2d offset_vec = unscale(sample.point); // [in mm]
auto offset_tr = Eigen::Translation<double, 3>(offset_vec.x(), 0., -offset_vec.y());
//Transform3d tr = offset_tr * rotate * to_zero * scale_tr;
Transform3d tr = offset_tr * rotate * to_zero * scale_tr;
//Transform3d tr = Transform3d::Identity() * scale_tr;
const ExPolygons &letter_shape = shapes[s_i_offset + i];
assert(get_extents(letter_shape) == letter_bb);
auto projectZ = std::make_unique<ProjectZ>(depth);
ProjectTransform project(std::move(projectZ), tr);
indexed_triangle_set glyph_its = polygons2model(letter_shape, project);
its_merge(result, std::move(glyph_its));
if (((s_i_offset + i) % 15) && was_canceled())
return {};
}
s_i_offset += line_bbs.size();
@ -549,10 +581,7 @@ template<typename Fnc> TriangleMesh create_mesh_per_glyph(DataBase &input, Fnc w
}
#endif // STORE_SAMPLING
}
// ProjectTransform project(std::move(projectZ), )
// if (was_canceled()) return {};
return TriangleMesh(result);
return TriangleMesh(std::move(result));
}
} // namespace
@ -864,22 +893,16 @@ OrthoProject3d priv::create_emboss_projection(
return OrthoProject3d(from_front_to_back);
}
// input can't be const - cache of font
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
{
ExPolygons shapes = create_shape(input1, was_canceled);
if (shapes.empty())
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
if (was_canceled()) return {};
namespace {
indexed_triangle_set cut_surface_to_its(const ExPolygons &shapes, const Transform3d& tr,const SurfaceVolumeData::ModelSources &sources, bool is_outside, DataBase& input, std::function<bool()> was_canceled) {
assert(!sources.empty());
BoundingBox bb = get_extents(shapes);
const FontFile &ff = *input1.font_file.font_file;
const FontProp &fp = input1.text_configuration.style.prop;
const FontFile &ff = *input.font_file.font_file;
const FontProp &fp = input.text_configuration.style.prop;
double shape_scale = get_shape_scale(fp, ff);
const SurfaceVolumeData::ModelSources &sources = input2.sources;
const SurfaceVolumeData::ModelSource *biggest = nullptr;
const SurfaceVolumeData::ModelSource *biggest = &sources.front();
size_t biggest_count = 0;
// convert index from (s)ources to (i)ndexed (t)riangle (s)ets
@ -888,9 +911,9 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
itss.reserve(sources.size());
for (const SurfaceVolumeData::ModelSource &s : sources) {
Transform3d mesh_tr_inv = s.tr.inverse();
Transform3d cut_projection_tr = mesh_tr_inv * input2.text_tr;
Transform3d cut_projection_tr = mesh_tr_inv * tr;
std::pair<float, float> z_range{0., 1.};
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
// copy only part of source model
indexed_triangle_set its = its_cut_AoI(s.mesh->its, bb, cut_projection);
if (its.indices.empty()) continue;
@ -904,10 +927,10 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
itss.emplace_back(std::move(its));
}
if (itss.empty())
throw JobException(_u8L("There is no volume in projection direction.").c_str());
return {};
Transform3d tr_inv = biggest->tr.inverse();
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
Transform3d cut_projection_tr = tr_inv * tr;
size_t itss_index = s_to_itss[biggest - &sources.front()];
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
@ -929,22 +952,27 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
Transform3d emboss_tr = cut_projection_tr.inverse();
BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr);
std::pair<float, float> z_range{mesh_bb_tr.min.z(), mesh_bb_tr.max.z()};
OrthoProject cut_projection = create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
float projection_ratio = (-z_range.first + safe_extension) / (z_range.second - z_range.first + 2 * safe_extension);
OrthoProject cut_projection = priv::create_projection_for_cut(cut_projection_tr, shape_scale, z_range);
float projection_ratio = (-z_range.first + priv::safe_extension) /
(z_range.second - z_range.first + 2 * priv::safe_extension);
bool is_text_reflected = Slic3r::has_reflection(input2.text_tr);
ExPolygons shapes_data; // is used only when text is reflected to reverse polygon points order
const ExPolygons *shapes_ptr = &shapes;
bool is_text_reflected = Slic3r::has_reflection(tr);
if (is_text_reflected) {
// revert order of points in expolygons
// CW --> CCW
for (ExPolygon &shape : shapes) {
shapes_data = shapes; // copy
for (ExPolygon &shape : shapes_data) {
shape.contour.reverse();
for (Slic3r::Polygon &hole : shape.holes)
hole.reverse();
}
shapes_ptr = &shapes_data;
}
// Use CGAL to cut surface from triangle mesh
SurfaceCut cut = cut_surface(shapes, itss, cut_projection, projection_ratio);
SurfaceCut cut = cut_surface(*shapes_ptr, itss, cut_projection, projection_ratio);
if (is_text_reflected) {
for (SurfaceCut::Contour &c : cut.contours)
@ -953,15 +981,105 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
std::swap(t[0], t[1]);
}
if (cut.empty()) throw JobException(_u8L("There is no valid surface for text projection.").c_str());
if (cut.empty()) return {}; // There is no valid surface for text projection.
if (was_canceled()) return {};
// !! Projection needs to transform cut
OrthoProject3d projection = create_emboss_projection(input2.is_outside, fp.emboss, emboss_tr, cut);
indexed_triangle_set new_its = cut2model(cut, projection);
assert(!new_its.empty());
OrthoProject3d projection = priv::create_emboss_projection(is_outside, fp.emboss, emboss_tr, cut);
return cut2model(cut, projection);
}
TriangleMesh cut_per_glyph_surface(DataBase &input1, const SurfaceVolumeData &input2, std::function<bool()> was_canceled)
{
std::vector<ExPolygons> shapes = priv::create_shapes(input1, was_canceled);
if (was_canceled()) return {};
return TriangleMesh(std::move(new_its));
if (shapes.empty())
throw priv::JobException(_u8L("Font doesn't have any shape for given text.").c_str());
// Precalculate bounding boxes of glyphs
// Separate lines of text to vector of Bounds
const TextConfiguration &tc = input1.text_configuration;
std::wstring ws = boost::nowide::widen(tc.text.c_str());
assert(get_count_lines(ws) == input1.text_lines.size());
size_t count_lines = input1.text_lines.size();
std::vector<BoundingBoxes> bbs = create_line_bounds(shapes, ws, count_lines);
const FontProp &prop = tc.style.prop;
FontFileWithCache &font = input1.font_file;
double shape_scale = get_shape_scale(prop, *font.font_file);
// half of font em size for direction of letter emboss
double em_2_mm = prop.size_in_mm / 2.;
int32_t em_2_polygon = static_cast<int32_t>(std::round(scale_(em_2_mm)));
size_t s_i_offset = 0; // shape index offset(for next lines)
indexed_triangle_set result;
for (size_t text_line_index = 0; text_line_index < input1.text_lines.size(); ++text_line_index) {
const BoundingBoxes &line_bbs = bbs[text_line_index];
const TextLine &line = input1.text_lines[text_line_index];
PolygonPoints samples = sample_slice(line, line_bbs, shape_scale);
std::vector<double> angles = calculate_angles(em_2_polygon, samples, line.polygon);
for (size_t i = 0; i < line_bbs.size(); ++i) {
const BoundingBox &glyph_bb = line_bbs[i];
if (!glyph_bb.defined)
continue;
const double &angle = angles[i];
auto rotate = Eigen::AngleAxisd(angle + M_PI_2, Vec3d::UnitY());
const PolygonPoint &sample = samples[i];
Vec2d offset_vec = unscale(sample.point); // [in mm]
auto offset_tr = Eigen::Translation<double, 3>(offset_vec.x(), 0., -offset_vec.y());
ExPolygons &glyph_shape = shapes[s_i_offset + i];
assert(get_extents(glyph_shape) == glyph_bb);
Point offset(-glyph_bb.center().x(), 0);
for (ExPolygon& s: glyph_shape)
s.translate(offset);
Transform3d modify = offset_tr * rotate;
Transform3d tr = input2.text_tr * modify;
indexed_triangle_set glyph_its = cut_surface_to_its(glyph_shape, tr, input2.sources, input2.is_outside, input1, was_canceled);
// move letter in volume on the right position
its_transform(glyph_its, modify);
// Improve: union instead of merge
its_merge(result, std::move(glyph_its));
if (((s_i_offset + i) % 15) && was_canceled())
return {};
}
s_i_offset += line_bbs.size();
}
if (was_canceled()) return {};
if (result.empty())
throw priv::JobException(_u8L("There is no valid surface for text projection.").c_str());
return TriangleMesh(std::move(result));
}
} // namespace
// input can't be const - cache of font
TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2, std::function<bool()> was_canceled)
{
const FontProp &fp = input1.text_configuration.style.prop;
if (fp.per_glyph)
return cut_per_glyph_surface(input1, input2, was_canceled);
ExPolygons shapes = create_shape(input1, was_canceled);
if (was_canceled()) return {};
if (shapes.empty())
throw JobException(_u8L("Font doesn't have any shape for given text.").c_str());
indexed_triangle_set its = cut_surface_to_its(shapes, input2.text_tr, input2.sources, input2.is_outside, input1, was_canceled);
if (was_canceled()) return {};
if (its.empty())
throw JobException(_u8L("There is no valid surface for text projection.").c_str());
return TriangleMesh(std::move(its));
}
bool priv::process(std::exception_ptr &eptr) {

View File

@ -317,7 +317,10 @@ void TextLinesModel::init(const Selection &selection, double line_height)
m_model.reset();
//*
m_model.init_from(create_geometry(m_lines));
GLModel::Geometry geometry = create_geometry(m_lines);
if (geometry.vertices_count() == 0 || geometry.indices_count() == 0)
return;
m_model.init_from(std::move(geometry));
/*/
ColorRGBA color(.7f, .7f, .7f, .7f); // Transparent Gray
m_model.set_color(color);