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:
parent
b899d51aba
commit
550ef48fe1
5 changed files with 105 additions and 79 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
{}
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue