Add function for check whether transformation contain reflection

Extend RayCast hit by square distance
Use distance to distiguish closest place on surface when move origin
Move origin after job (only on success)
This commit is contained in:
Filip Sykala - NTB T15p 2023-01-03 13:24:01 +01:00
parent b899d51aba
commit 550ef48fe1
5 changed files with 105 additions and 79 deletions

View file

@ -136,6 +136,14 @@ inline std::string to_string(const Vec3d &pt) { return std::string("[") + floa
std::vector<Vec3f> transform(const std::vector<Vec3f>& points, const Transform3f& t);
Pointf3s transform(const Pointf3s& points, const Transform3d& t);
/// <summary>
/// Check whether transformation matrix contains odd number of mirroring.
/// NOTE: In code is sometime function named is_left_handed
/// </summary>
/// <param name="transform">Transformation to check</param>
/// <returns>Is positive determinant</returns>
inline bool has_reflection(const Transform3d &transform) { return transform.matrix().determinant() < 0; }
template<int N, class T> using Vec = Eigen::Matrix<T, N, 1, Eigen::DontAlign, N, 1>;
class Point : public Vec2crd

View file

@ -1120,6 +1120,17 @@ static inline void execute_job(std::shared_ptr<Job> j)
});
}
namespace priv {
/// <summary>
/// Calculate translation of text volume onto surface of model
/// </summary>
/// <param name="volume">Text</param>
/// <param name="raycast_manager">AABB trees of object. Actualize object containing text</param>
/// <param name="selection">Transformation of actual instance</param>
/// <returns>Offset of volume in volume coordinate</returns>
std::optional<Vec3d> calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection);
} // namespace priv
bool GLGizmoEmboss::process()
{
// no volume is selected -> selection from right panel
@ -1160,6 +1171,13 @@ bool GLGizmoEmboss::process()
if (fix_3mf.has_value())
text_tr = text_tr * fix_3mf->inverse();
// when it is new applying of use surface than move origin onto surfaca
if (!m_volume->text_configuration->style.prop.use_surface) {
auto offset = priv::calc_surface_offset(*m_volume, m_raycast_manager, m_parent.get_selection());
if (offset.has_value())
text_tr *= Eigen::Translation<double, 3>(*offset);
}
bool is_outside = m_volume->is_model_part();
// check that there is not unexpected volume type
assert(is_outside || m_volume->is_negative_volume() ||
@ -2231,18 +2249,7 @@ void GLGizmoEmboss::draw_delete_style_button() {
}
}
namespace priv {
/// <summary>
/// Transform origin of Text volume onto surface of model.
/// </summary>
/// <param name="volume">Text</param>
/// <param name="raycast_manager">AABB trees of object</param>
/// <param name="selection">Transformation of actual instance</param>
/// <returns>True when transform otherwise false</returns>
bool transform_on_surface(ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection);
} // namespace priv
// FIX IT: it should not change volume position before successfull change
void GLGizmoEmboss::fix_transformation(const FontProp &from,
const FontProp &to)
{
@ -2264,10 +2271,6 @@ void GLGizmoEmboss::fix_transformation(const FontProp &from,
float t_move = t_move_opt.has_value() ? *t_move_opt : .0f;
do_translate(Vec3d::UnitZ() * (t_move - f_move));
}
// when start using surface than move volume origin onto surface
if (!from.use_surface && to.use_surface)
priv::transform_on_surface(*m_volume, m_raycast_manager, m_parent.get_selection());
}
void GLGizmoEmboss::draw_style_list() {
@ -2887,8 +2890,7 @@ void GLGizmoEmboss::do_rotate(float relative_z_angle)
m_parent.do_rotate(snapshot_name);
}
bool priv::transform_on_surface(ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection)
{
std::optional<Vec3d> priv::calc_surface_offset(const ModelVolume &volume, RaycastManager &raycast_manager, const Selection &selection) {
// Move object on surface
auto cond = RaycastManager::SkipVolume({volume.id().id});
raycast_manager.actualize(volume.get_object(), &cond);
@ -2901,26 +2903,31 @@ bool priv::transform_on_surface(ModelVolume &volume, RaycastManager &raycast_man
// ray in direction of text projection(from volume zero to z-dir)
std::optional<RaycastManager::Hit> hit_opt = raycast_manager.unproject(point, direction, &cond);
// start point lay on surface could appear slightly behind surface
std::optional<RaycastManager::Hit> hit_opt_opposit = raycast_manager.unproject(point, -direction, &cond);
if (!hit_opt.has_value() ||
(hit_opt_opposit.has_value() && hit_opt->squared_distance > hit_opt_opposit->squared_distance))
hit_opt = hit_opt_opposit;
// Try to find closest point when no hit object in emboss direction
if (!hit_opt.has_value())
hit_opt = raycast_manager.closest(point);
// It should NOT appear. Closest point always exists.
if (!hit_opt.has_value())
return false;
const RaycastManager::Hit &hit = *hit_opt;
return {};
// It is no neccesary to move with origin by very small value
if (hit_opt->squared_distance < EPSILON)
return {};
const RaycastManager::Hit &hit = *hit_opt;
Transform3d hit_tr = raycast_manager.get_transformation(hit.tr_key);
Vec3d hit_world = hit_tr * hit.position.cast<double>();
Vec3d offset_world = hit_world - point; // vector in world
// TIP: It should be close to only z move
Vec3d offset_volume = to_world.inverse().linear() * offset_world;
// when try to use surface on just loaded text from 3mf
auto fix = volume.text_configuration->fix_3mf_tr;
if (fix.has_value())
offset_volume = fix->linear() * offset_volume;
volume.set_transformation(volume.get_matrix() * Eigen::Translation<double, 3>(offset_volume));
return true;
return offset_volume;
}
void GLGizmoEmboss::draw_advanced()
@ -2975,8 +2982,6 @@ void GLGizmoEmboss::draw_advanced()
// there should be minimal embossing depth
if (font_prop.emboss < 0.1)
font_prop.emboss = 1;
priv::transform_on_surface(*m_volume, m_raycast_manager, m_parent.get_selection());
}
process();
}

View file

@ -63,7 +63,8 @@ static TriangleMesh create_default_mesh();
/// </summary>
/// <param name="mesh">New mesh data</param>
/// <param name="data">Text configuration, ...</param>
static void update_volume(TriangleMesh &&mesh, const DataUpdate &data);
/// <param name="mesh">Transformation of volume</param>
static void update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d *tr = nullptr);
/// <summary>
/// Add new volume to object
@ -316,16 +317,8 @@ void CreateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr) {
// doesn't care about exception when process was canceled by user
if (canceled) return;
if (priv::process(eptr)) return;
// TODO: Find better way to Not center volume data when add !!!
TriangleMesh mesh = m_result; // Part1: copy
priv::create_volume(std::move(m_result), m_input.object_id,
m_input.volume_type, m_input.text_tr, m_input);
// Part2: update volume data
//auto vol = wxGetApp().plater()->model().objects[m_input.object_idx]->volumes.back();
//UpdateJob::update_volume(vol, std::move(mesh), m_input.text_configuration, m_input.volume_name);
}
/////////////////
@ -358,7 +351,11 @@ void UpdateSurfaceVolumeJob::finalize(bool canceled, std::exception_ptr &eptr)
}
if (canceled) return;
if (priv::process(eptr)) return;
priv::update_volume(std::move(m_result), m_input);
// when start using surface it is wanted to move text origin on surface of model
// also when repeteadly move above surface result position should match
Transform3d *tr = &m_input.text_tr;
priv::update_volume(std::move(m_result), m_input, tr);
}
////////////////////////////
@ -535,31 +532,37 @@ void UpdateJob::update_volume(ModelVolume *volume,
canvas->reload_scene(refresh_immediately);
}
void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data)
void priv::update_volume(TriangleMesh &&mesh, const DataUpdate &data, Transform3d* tr)
{
// for sure that some object will be created
if (mesh.its.empty())
return priv::create_message("Empty mesh can't be created.");
if (mesh.its.empty())
return create_message("Empty mesh can't be created.");
Plater *plater = wxGetApp().plater();
GLCanvas3D *canvas = plater->canvas3D();
// Check emboss gizmo is still open
GLGizmosManager &manager = canvas->get_gizmos_manager();
if (manager.get_current_type() != GLGizmosManager::Emboss) return;
if (manager.get_current_type() != GLGizmosManager::Emboss)
return;
std::string snap_name = GUI::format(_L("Text: %1%"), data.text_configuration.text);
Plater::TakeSnapshot snapshot(plater, snap_name, UndoRedo::SnapshotType::GizmoAction);
ModelVolume *volume = get_volume(plater->model().objects, data.volume_id);
// could appear when user delete edited volume
if (volume == nullptr)
return;
// apply fix matrix made by store to .3mf
const auto &tc = volume->text_configuration;
assert(tc.has_value());
if (tc.has_value() && tc->fix_3mf_tr.has_value())
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
if (tr) {
volume->set_transformation(*tr);
} else {
// apply fix matrix made by store to .3mf
const auto &tc = volume->text_configuration;
assert(tc.has_value());
if (tc.has_value() && tc->fix_3mf_tr.has_value())
volume->set_transformation(volume->get_matrix() * tc->fix_3mf_tr->inverse());
}
UpdateJob::update_volume(volume, std::move(mesh), data.text_configuration, data.volume_name);
}
@ -726,30 +729,48 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
biggest_count = its.vertices.size();
biggest = &s;
}
s_to_itss[&s - &sources.front()] = itss.size();
size_t source_index = &s - &sources.front();
size_t its_index = itss.size();
s_to_itss[source_index] = its_index;
itss.emplace_back(std::move(its));
}
if (itss.empty())
throw JobException(_u8L("There is no volume in projection direction.").c_str());
Transform3d tr_inv = biggest->tr.inverse();
Transform3d tr_inv = biggest->tr.inverse();
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
// Cut surface in reflected system?
bool use_reflection = Slic3r::has_reflection(cut_projection_tr);
if (use_reflection)
cut_projection_tr *= Eigen::Scaling(-1., 1., 1.);
size_t itss_index = s_to_itss[biggest - &sources.front()];
BoundingBoxf3 mesh_bb = bounding_box(itss[itss_index]);
for (const SurfaceVolumeData::ModelSource &s : sources) {
if (&s == biggest) continue;
size_t itss_index = s_to_itss[&s - &sources.front()];
if (itss_index == std::numeric_limits<size_t>::max()) continue;
Transform3d tr = s.tr * tr_inv;
Transform3d tr;
if (&s == biggest) {
if (!use_reflection)
continue;
// add reflection for biggest source
tr = Eigen::Scaling(-1., 1., 1.);
} else {
tr = s.tr * tr_inv;
if (use_reflection)
tr *= Eigen::Scaling(-1., 1., 1.);
}
bool fix_reflected = true;
indexed_triangle_set &its = itss[itss_index];
its_transform(its, tr);
its_transform(its, tr, fix_reflected);
BoundingBoxf3 bb = bounding_box(its);
mesh_bb.merge(bb);
}
// tr_inv = transformation of mesh inverted
Transform3d cut_projection_tr = tr_inv * input2.text_tr;
Transform3d emboss_tr = cut_projection_tr.inverse();
BoundingBoxf3 mesh_bb_tr = mesh_bb.transformed(emboss_tr);
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);
@ -761,9 +782,13 @@ TriangleMesh priv::cut_surface(DataBase& input1, const SurfaceVolumeData& input2
// !! 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());
if (use_reflection) {
// when cut was made in reflected system it must be converted back
Transform3d tr(Eigen::Scaling(-1., 1., 1.));
its_transform(new_its, tr, true);
}
if (was_canceled()) return {};
return TriangleMesh(std::move(new_its));

View file

@ -73,23 +73,10 @@ void RaycastManager::actualize(const ModelObject *object, const ISkip *skip)
m_transformations.erase(m_transformations.begin() + i);
}
namespace priv {
struct HitWithDistance : public RaycastManager::Hit
{
double squared_distance;
HitWithDistance(double squared_distance,
const Hit::Key &key,
const SurfacePoint &surface_point)
: Hit(key, surface_point.position, surface_point.normal)
, squared_distance(squared_distance)
{}
};
}
std::optional<RaycastManager::Hit> RaycastManager::unproject(
const Vec2d &mouse_pos, const Camera &camera, const ISkip *skip) const
{
std::optional<priv::HitWithDistance> closest;
std::optional<Hit> closest;
for (const auto &item : m_transformations) {
const TrKey &key = item.first;
size_t volume_id = key.second;
@ -112,7 +99,7 @@ std::optional<RaycastManager::Hit> RaycastManager::unproject(
if (closest.has_value() &&
closest->squared_distance < squared_distance)
continue;
closest = priv::HitWithDistance(squared_distance, key, surface_point);
closest = Hit(key, surface_point, squared_distance);
}
//if (!closest.has_value()) return {};
@ -121,7 +108,7 @@ std::optional<RaycastManager::Hit> RaycastManager::unproject(
std::optional<RaycastManager::Hit> RaycastManager::unproject(const Vec3d &point, const Vec3d &direction, const ISkip *skip) const
{
std::optional<priv::HitWithDistance> closest;
std::optional<Hit> closest;
for (const auto &item : m_transformations) {
const TrKey &key = item.first;
size_t volume_id = key.second;
@ -144,14 +131,14 @@ std::optional<RaycastManager::Hit> RaycastManager::unproject(const Vec3d &point,
closest->squared_distance < squared_distance)
continue;
SurfacePoint surface_point(hit.position().cast<float>(), hit.normal().cast<float>());
closest = priv::HitWithDistance(squared_distance, key, surface_point);
closest = Hit(key, surface_point, squared_distance);
}
}
return closest;
}
std::optional<RaycastManager::Hit> RaycastManager::closest(const Vec3d &point, const ISkip *skip) const {
std::optional<priv::HitWithDistance> closest;
std::optional<Hit> closest;
for (const auto &item : m_transformations) {
const TrKey &key = item.first;
size_t volume_id = key.second;
@ -172,7 +159,7 @@ std::optional<RaycastManager::Hit> RaycastManager::closest(const Vec3d &point, c
if (closest.has_value() && closest->squared_distance < squared_distance)
continue;
SurfacePoint surface_point(p,n);
closest = priv::HitWithDistance(squared_distance, key, surface_point);
closest = Hit(key, surface_point, squared_distance);
}
return closest;
}

View file

@ -67,8 +67,9 @@ public:
{
using Key = TrKey;
Key tr_key;
Hit(Key tr_key, Vec3f position, Vec3f normal)
: SurfacePoint(position, normal), tr_key(tr_key)
double squared_distance;
Hit(const Key& tr_key, const SurfacePoint& surface_point, double squared_distance)
: SurfacePoint(surface_point), tr_key(tr_key), squared_distance(squared_distance)
{}
};