ENH: Rotate around the center of the bounding box

jira:none

code is from PrusaSlicer,thanks for enricoturri1966 and PrusaSlicer
commit dcec7a8ad40eaad72789f6dba15cafc94664119f
Author: enricoturri1966 <enricoturri@seznam.cz>
Date:   Tue Feb 28 08:08:56 2023 +0100
    Fixed Rotate Gizmo orientation for mirrored objects + ensure that instances and volumes always rotate as rigid body

Change-Id: I359d15814a6411bbd6bcb753661388bb5e6fb513
This commit is contained in:
enricoturri1966 2024-03-26 14:09:45 +08:00 committed by Lane.Wei
parent 82775a1a59
commit 818c7a345a
6 changed files with 224 additions and 50 deletions

View File

@ -500,6 +500,15 @@ void Transformation::set_rotation(Axis axis, double rotation)
}
}
Transform3d Transformation::get_scaling_factor_matrix() const
{
Transform3d scale = extract_scale(m_matrix);
scale(0, 0) = std::abs(scale(0, 0));
scale(1, 1) = std::abs(scale(1, 1));
scale(2, 2) = std::abs(scale(2, 2));
return scale;
}
void Transformation::set_scaling_factor(const Vec3d& scaling_factor)
{
set_scaling_factor(X, scaling_factor(0));

View File

@ -406,6 +406,7 @@ public:
void set_rotation(const Vec3d& rotation);
void set_rotation(Axis axis, double rotation);
Transform3d get_scaling_factor_matrix() const;
const Vec3d& get_scaling_factor() const { return m_scaling_factor; }
double get_scaling_factor(Axis axis) const { return m_scaling_factor(axis); }
@ -421,6 +422,7 @@ public:
void set_mirror(Axis axis, double mirror);
void set_from_transform(const Transform3d& transform);
void set_matrix(const Transform3d &transform) { set_from_transform(transform); }
void reset();
void reset_offset() { set_offset(Vec3d::Zero()); }

View File

@ -437,7 +437,7 @@ public:
const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
void set_instance_transformation(const Transform3d &transform){ m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
const Vec3d& get_instance_offset() const { return m_instance_transformation.get_offset(); }
double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
@ -464,6 +464,7 @@ public:
const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
void set_volume_transformation(const Transform3d &transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
const Vec3d& get_volume_offset() const { return m_volume_transformation.get_offset(); }
double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }

View File

@ -973,7 +973,26 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
case Rotate:
{
// Apply new temporary rotations
TransformationType transformation_type(TransformationType::World_Relative_Joint);
TransformationType transformation_type;
if (m_parent.get_selection().is_wipe_tower())
transformation_type = TransformationType::World_Relative_Joint;
else {
switch (wxGetApp().obj_manipul()->get_coordinates_type()) {
default:
case ECoordinatesType::World: {
transformation_type = TransformationType::World_Relative_Joint;
break;
}
case ECoordinatesType::Instance: {
transformation_type = TransformationType::Instance_Relative_Joint;
break;
}
case ECoordinatesType::Local: {
transformation_type = TransformationType::Local_Relative_Joint;
break;
}
}
}
if (evt.AltDown())
transformation_type.set_independent();
selection.rotate(get_rotation(), transformation_type);

View File

@ -23,6 +23,9 @@
#include <boost/algorithm/string/predicate.hpp>
#include <boost/log/trivial.hpp>
#include <CGAL/Simple_cartesian.h>
#include <CGAL/Min_sphere_of_spheres_d.h>
#include <CGAL/Min_sphere_of_points_d_traits_3.h>
static const std::array<float, 4> UNIFORM_SCALE_COLOR = { 0.923f, 0.504f, 0.264f, 1.0f };
namespace Slic3r {
@ -36,7 +39,7 @@ Selection::VolumeCache::TransformCache::TransformCache()
, rotation_matrix(Transform3d::Identity())
, scale_matrix(Transform3d::Identity())
, mirror_matrix(Transform3d::Identity())
, full_matrix(Transform3d::Identity())
, full_tran(Transform3d::Identity())
{
}
@ -45,7 +48,7 @@ Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transform
, rotation(transform.get_rotation())
, scaling_factor(transform.get_scaling_factor())
, mirror(transform.get_mirror())
, full_matrix(transform.get_matrix())
, full_tran(transform)
{
rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation);
scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor);
@ -1002,6 +1005,40 @@ void Selection::move_to_center(const Vec3d& displacement, bool local)
this->set_bounding_boxes_dirty();
}
const std::pair<Vec3d, double> Selection::get_bounding_sphere() const
{
if (!m_bounding_sphere.has_value()) {
std::optional<std::pair<Vec3d, double>> *sphere = const_cast<std::optional<std::pair<Vec3d, double>> *>(&m_bounding_sphere);
*sphere = {Vec3d::Zero(), 0.0};
using K = CGAL::Simple_cartesian<float>;
using Traits = CGAL::Min_sphere_of_points_d_traits_3<K, float>;
using Min_sphere = CGAL::Min_sphere_of_spheres_d<Traits>;
using Point = K::Point_3;
std::vector<Point> points;
if (m_valid) {
for (unsigned int i : m_list) {
const GLVolume & volume = *(*m_volumes)[i];
const TriangleMesh * hull = volume.convex_hull();
const indexed_triangle_set &its = (hull != nullptr) ? hull->its : m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh().its;
const Transform3d & matrix = volume.world_matrix();
for (const Vec3f &v : its.vertices) {
const Vec3d vv = matrix * v.cast<double>();
points.push_back(Point(vv.x(), vv.y(), vv.z()));
}
}
Min_sphere ms(points.begin(), points.end());
const float *center_x = ms.center_cartesian_begin();
(*sphere)->first = {*center_x, *(center_x + 1), *(center_x + 2)};
(*sphere)->second = ms.radius();
}
}
return *m_bounding_sphere;
}
void Selection::setup_cache()
{
if (!m_valid)
@ -1141,33 +1178,60 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
};
for (unsigned int i : m_list) {
Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
GLVolume &v = *(*m_volumes)[i];
if (is_single_full_instance())
rotate_instance(v, i);
else if (is_single_volume() || is_single_modifier()) {
if (transformation_type.independent())
v.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation() + rotation);
else {
const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
v.set_volume_rotation(new_rotation);
const VolumeCache &volume_data = m_cache.volumes_data[i];
const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform();
if (m_mode == Instance ||is_single_full_instance()) {
assert(is_from_fully_selected_instance(i));
if (transformation_type.instance()) {
// ensure that the instance rotates as a rigid body
Transform3d inst_rotation_matrix = inst_trafo.get_rotation_matrix();
if (inst_trafo.is_left_handed()) {
Geometry::TransformationSVD inst_svd(inst_trafo);
inst_rotation_matrix = inst_svd.u * inst_svd.v.transpose();
// ensure the rotation has the proper direction
if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX())) rotation_matrix = rotation_matrix.inverse();
}
const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset();
rotation_matrix = inst_matrix_no_offset.inverse() * inst_rotation_matrix * rotation_matrix * inst_rotation_matrix.inverse() * inst_matrix_no_offset;
// rotate around selection center
const Vec3d inst_pivot = inst_trafo.get_matrix_no_offset().inverse() * (m_cache.rotation_pivot - inst_trafo.get_offset());
rotation_matrix = Geometry::translation_transform(inst_pivot) * rotation_matrix * Geometry::translation_transform(-inst_pivot);
}
transform_instance_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot);
}
else if (!is_single_volume_or_modifier()) {
assert(transformation_type.world());
transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot);
}
else
{
if (m_mode == Instance)
rotate_instance(v, i);
else if (m_mode == Volume) {
// extracts rotations from the composed transformation
Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation);
Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix());
if (transformation_type.joint()) {
const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center;
const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot);
v.set_volume_offset(local_pivot + offset);
if (transformation_type.instance()) {//in object Coordinate System
// ensure that the volume rotates as a rigid body
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
rotation_matrix = inst_scale_matrix.inverse() * rotation_matrix * inst_scale_matrix;
} else {
if (transformation_type.local()) {
// ensure that the volume rotates as a rigid body
const Geometry::Transformation &vol_trafo = volume_data.get_volume_transform();
const Transform3d vol_matrix_no_offset = vol_trafo.get_matrix_no_offset();
const Transform3d inst_scale_matrix = inst_trafo.get_scaling_factor_matrix();
Transform3d vol_rotation_matrix = vol_trafo.get_rotation_matrix();
if (vol_trafo.is_left_handed()) {
Geometry::TransformationSVD vol_svd(vol_trafo);
vol_rotation_matrix = vol_svd.u * vol_svd.v.transpose();
// ensure the rotation has the proper direction
if (!rotation.normalized().cwiseAbs().isApprox(Vec3d::UnitX())) rotation_matrix = rotation_matrix.inverse();
}
rotation_matrix = vol_matrix_no_offset.inverse() * inst_scale_matrix.inverse() * vol_rotation_matrix * rotation_matrix *
vol_rotation_matrix.inverse() * inst_scale_matrix * vol_matrix_no_offset;
}
v.set_volume_rotation(new_rotation);
}
transform_volume_relative(v, volume_data, transformation_type, rotation_matrix, m_cache.rotation_pivot);
}
}
}
@ -2264,6 +2328,7 @@ void Selection::set_caches()
m_cache.sinking_volumes.push_back(i);
}
m_cache.dragging_center = get_bounding_box().center();
m_cache.rotation_pivot = get_bounding_sphere().first;
}
void Selection::do_add_volume(unsigned int volume_idx)
@ -2915,5 +2980,45 @@ void Selection::paste_objects_from_clipboard()
#endif /* _DEBUG */
}
void Selection::transform_instance_relative(
GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot)
{
assert(transformation_type.relative());
const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform();
if (transformation_type.world()) {
const Vec3d inst_pivot = transformation_type.independent() && !is_from_single_instance() ? inst_trafo.get_offset() : world_pivot;
const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot);
volume.set_instance_transformation(trafo * inst_trafo.get_matrix());
} else if (transformation_type.instance())
volume.set_instance_transformation(inst_trafo.get_matrix() * transform);
else
assert(false);
}
void Selection::transform_volume_relative(
GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot)
{
assert(transformation_type.relative());
const Geometry::Transformation &vol_trafo = volume_data.get_volume_transform();
const Geometry::Transformation &inst_trafo = volume_data.get_instance_transform();
if (transformation_type.world()) {
const Vec3d inst_pivot = transformation_type.independent() ? vol_trafo.get_offset() : (Vec3d) (inst_trafo.get_matrix().inverse() * world_pivot);
const Transform3d inst_matrix_no_offset = inst_trafo.get_matrix_no_offset();
const Transform3d trafo = Geometry::translation_transform(inst_pivot) * inst_matrix_no_offset.inverse() * transform * inst_matrix_no_offset *
Geometry::translation_transform(-inst_pivot);
volume.set_volume_transformation(trafo * vol_trafo.get_matrix());
} else if (transformation_type.instance()) {
const Vec3d inst_pivot = transformation_type.independent() ? vol_trafo.get_offset() : (Vec3d) (inst_trafo.get_matrix().inverse() * world_pivot);
const Transform3d trafo = Geometry::translation_transform(inst_pivot) * transform * Geometry::translation_transform(-inst_pivot);
volume.set_volume_transformation(trafo * vol_trafo.get_matrix());
} else if (transformation_type.local())
volume.set_volume_transformation(vol_trafo.get_matrix() * transform);
else
assert(false);
}
} // namespace GUI
} // namespace Slic3r

View File

@ -38,45 +38,69 @@ public:
enum Enum {
// Transforming in a world coordinate system
World = 0,
// Transforming in a instance coordinate system
Instance = 1,
// Transforming in a local coordinate system
Local = 1,
Local = 2,
// Absolute transformations, allowed in local coordinate system only.
Absolute = 0,
// Relative transformations, allowed in both local and world coordinate system.
Relative = 2,
Relative = 4,
// For group selection, the transformation is performed as if the group made a single solid body.
Joint = 0,
// For group selection, the transformation is performed on each object independently.
Independent = 4,
Independent = 8,
World_Relative_Joint = World | Relative | Joint,
World_Relative_Independent = World | Relative | Independent,
Local_Absolute_Joint = Local | Absolute | Joint,
Local_Absolute_Independent = Local | Absolute | Independent,
Local_Relative_Joint = Local | Relative | Joint,
Local_Relative_Independent = Local | Relative | Independent,
World_Relative_Joint = World | Relative | Joint,
World_Relative_Independent = World | Relative | Independent,
Instance_Absolute_Joint = Instance | Absolute | Joint,
Instance_Absolute_Independent = Instance | Absolute | Independent,
Instance_Relative_Joint = Instance | Relative | Joint,
Instance_Relative_Independent = Instance | Relative | Independent,
Local_Absolute_Joint = Local | Absolute | Joint,
Local_Absolute_Independent = Local | Absolute | Independent,
Local_Relative_Joint = Local | Relative | Joint,
Local_Relative_Independent = Local | Relative | Independent,
};
TransformationType() : m_value(World) {}
TransformationType(Enum value) : m_value(value) {}
TransformationType& operator=(Enum value) { m_value = value; return *this; }
TransformationType &operator=(Enum value)
{
m_value = value;
return *this;
}
Enum operator()() const { return m_value; }
bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; }
bool has(Enum v) const { return ((unsigned int) m_value & (unsigned int) v) != 0; }
void set_world() { this->remove(Local); }
void set_local() { this->add(Local); }
void set_absolute() { this->remove(Relative); }
void set_relative() { this->add(Relative); }
void set_joint() { this->remove(Independent); }
void set_independent() { this->add(Independent); }
void set_world()
{
this->remove(Instance);
this->remove(Local);
}
void set_instance()
{
this->remove(Local);
this->add(Instance);
}
void set_local()
{
this->remove(Instance);
this->add(Local);
}
void set_absolute() { this->remove(Relative); }
void set_relative() { this->add(Relative); }
void set_joint() { this->remove(Independent); }
void set_independent() { this->add(Independent); }
bool world() const { return !this->has(Local); }
bool local() const { return this->has(Local); }
bool absolute() const { return !this->has(Relative); }
bool relative() const { return this->has(Relative); }
bool joint() const { return !this->has(Independent); }
bool independent() const { return this->has(Independent); }
bool world() const { return !this->has(Instance) && !this->has(Local); }
bool instance() const { return this->has(Instance); }
bool local() const { return this->has(Local); }
bool absolute() const { return !this->has(Relative); }
bool relative() const { return this->has(Relative); }
bool joint() const { return !this->has(Independent); }
bool independent() const { return this->has(Independent); }
private:
void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); }
@ -125,7 +149,7 @@ private:
Transform3d rotation_matrix;
Transform3d scale_matrix;
Transform3d mirror_matrix;
Transform3d full_matrix;
Geometry::Transformation full_tran;
TransformCache();
explicit TransformCache(const Geometry::Transformation& transform);
@ -145,7 +169,7 @@ private:
const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; }
const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; }
const Transform3d& get_volume_mirror_matrix() const { return m_volume.mirror_matrix; }
const Transform3d& get_volume_full_matrix() const { return m_volume.full_matrix; }
const Transform3d &get_volume_full_matrix() const { return m_volume.full_tran.get_matrix(); }
const Vec3d& get_instance_position() const { return m_instance.position; }
const Vec3d& get_instance_rotation() const { return m_instance.rotation; }
@ -154,7 +178,10 @@ private:
const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; }
const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; }
const Transform3d& get_instance_mirror_matrix() const { return m_instance.mirror_matrix; }
const Transform3d& get_instance_full_matrix() const { return m_instance.full_matrix; }
const Transform3d &get_instance_full_matrix() const { return m_instance.full_tran.get_matrix(); }
const Geometry::Transformation &get_volume_transform() const { return m_volume.full_tran; }
const Geometry::Transformation &get_instance_transform() const { return m_instance.full_tran; }
};
public:
@ -199,6 +226,7 @@ private:
ObjectIdxsToInstanceIdxsMap content;
// List of ids of the volumes which are sinking when starting dragging
std::vector<unsigned int> sinking_volumes;
Vec3d rotation_pivot;
};
// Volumes owned by GLCanvas3D.
@ -223,6 +251,8 @@ private:
// Bounding box aligned to the axis of the currently selected reference system (World/Object/Part)
// and transform to place and orient it in world coordinates
std::optional<std::pair<BoundingBoxf3, Transform3d>> m_bounding_box_in_current_reference_system;
std::optional<std::pair<Vec3d, double>> m_bounding_sphere;
#if ENABLE_RENDER_SELECTION_CENTER
GLModel m_vbo_sphere;
#endif // ENABLE_RENDER_SELECTION_CENTER
@ -350,6 +380,8 @@ public:
void start_dragging();
void stop_dragging() { m_dragging = false; }
bool is_dragging() const { return m_dragging; }
// Returns the bounding sphere: first = center, second = radius
const std::pair<Vec3d, double> get_bounding_sphere() const;
void setup_cache();
void translate(const Vec3d& displacement, bool local = false);
@ -425,6 +457,7 @@ private:
m_bounding_box.reset();
m_unscaled_instance_bounding_box.reset(); m_scaled_instance_bounding_box.reset();
m_bounding_box_in_current_reference_system.reset();
m_bounding_sphere.reset();
}
void render_selected_volumes() const;
void render_synchronized_volumes() const;
@ -452,6 +485,11 @@ private:
void paste_volumes_from_clipboard();
void paste_objects_from_clipboard();
void transform_instance_relative(
GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot);
void transform_volume_relative(
GLVolume &volume, const VolumeCache &volume_data, TransformationType transformation_type, const Transform3d &transform, const Vec3d &world_pivot);
};
} // namespace GUI