Tech ENABLE_TRANSFORMATIONS_BY_MATRICES - Reworked method void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type) to use matrix multiplication

This commit is contained in:
enricoturri1966 2022-05-04 10:28:59 +02:00
parent 29046275ac
commit 86dddbfe2b
7 changed files with 7884 additions and 7703 deletions

View File

@ -321,6 +321,18 @@ Transform3d assemble_transform(const Vec3d& translation, const Vec3d& rotation,
}
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
void assemble_transform(Transform3d& transform, const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
{
transform = translation * rotation * scale * mirror;
}
Transform3d assemble_transform(const Transform3d& translation, const Transform3d& rotation, const Transform3d& scale, const Transform3d& mirror)
{
Transform3d transform;
assemble_transform(transform, translation, rotation, scale, mirror);
return transform;
}
void rotation_transform(Transform3d& transform, const Vec3d& rotation)
{
transform = Transform3d::Identity();

View File

@ -324,7 +324,8 @@ bool arrange(
// 4) rotate Y
// 5) rotate Z
// 6) translate
void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
// Returns the transform obtained by assembling the given transformations in the following order:
// 1) mirror
@ -333,9 +334,21 @@ void assemble_transform(Transform3d& transform, const Vec3d& translation = Vec3d
// 4) rotate Y
// 5) rotate Z
// 6) translate
Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(), const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
Transform3d assemble_transform(const Vec3d& translation = Vec3d::Zero(), const Vec3d& rotation = Vec3d::Zero(),
const Vec3d& scale = Vec3d::Ones(), const Vec3d& mirror = Vec3d::Ones());
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
// Sets the given transform by multiplying the given transformations in the following order:
// T = translation * rotation * scale * mirror
void assemble_transform(Transform3d& transform, const Transform3d& translation = Transform3d::Identity(),
const Transform3d& rotation = Transform3d::Identity(), const Transform3d& scale = Transform3d::Identity(),
const Transform3d& mirror = Transform3d::Identity());
// Returns the transform obtained by multiplying the given transformations in the following order:
// T = translation * rotation * scale * mirror
Transform3d assemble_transform(const Transform3d& translation = Transform3d::Identity(), const Transform3d& rotation = Transform3d::Identity(),
const Transform3d& scale = Transform3d::Identity(), const Transform3d& mirror = Transform3d::Identity());
// Sets the given transform by assembling the given rotations in the following order:
// 1) rotate X
// 2) rotate Y

File diff suppressed because it is too large Load Diff

View File

@ -54,9 +54,7 @@ bool GLGizmoMove3D::on_mouse(const wxMouseEvent &mouse_event) {
}
void GLGizmoMove3D::data_changed() {
const Selection& selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
m_grabbers[2].enabled = !is_wipe_tower;
m_grabbers[2].enabled = !m_parent.get_selection().is_wipe_tower();
}
bool GLGizmoMove3D::on_init()
@ -100,11 +98,19 @@ void GLGizmoMove3D::on_start_dragging()
m_starting_drag_position = m_center + m_grabbers[m_hover_id].center;
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
#else
m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * Geometry::assemble_transform(Vec3d::Zero(), v.get_volume_rotation()) * m_grabbers[m_hover_id].center;
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
else {
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
m_starting_drag_position = m_center + v.get_instance_transformation().get_rotation_matrix() * m_grabbers[m_hover_id].center;
#else
m_starting_drag_position = m_center + Geometry::assemble_transform(Vec3d::Zero(), v.get_instance_rotation()) * m_grabbers[m_hover_id].center;
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
m_starting_box_center = m_center;
m_starting_box_bottom_center = m_center;

View File

@ -287,7 +287,15 @@ void GLGizmoRotate::on_render_for_picking()
#if ENABLE_WORLD_COORDINATE
void GLGizmoRotate::init_data_from_selection(const Selection& selection)
{
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
ECoordinatesType coordinates_type;
if (selection.is_wipe_tower())
coordinates_type = ECoordinatesType::Local;
else
coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
#else
const ECoordinatesType coordinates_type = wxGetApp().obj_manipul()->get_coordinates_type();
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
if (coordinates_type == ECoordinatesType::World) {
m_bounding_box = selection.get_bounding_box();
m_center = m_bounding_box.center();
@ -326,7 +334,11 @@ void GLGizmoRotate::init_data_from_selection(const Selection& selection)
if (coordinates_type == ECoordinatesType::World)
m_orient_matrix = Transform3d::Identity();
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
else if (coordinates_type == ECoordinatesType::Local && (selection.is_wipe_tower() || selection.is_single_volume_or_modifier())) {
#else
else if (coordinates_type == ECoordinatesType::Local && selection.is_single_volume_or_modifier()) {
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
const GLVolume& v = *selection.get_volume(*selection.get_volume_idxs().begin());
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
m_orient_matrix = v.get_instance_transformation().get_rotation_matrix() * v.get_volume_transformation().get_rotation_matrix();
@ -749,12 +761,20 @@ Transform3d GLGizmoRotate::local_transform(const Selection& selection) const
{
case X:
{
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
ret = Geometry::rotation_transform(0.5 * PI * Vec3d::UnitY()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ());
#else
ret = Geometry::assemble_transform(Vec3d::Zero(), 0.5 * PI * Vec3d::UnitY()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ());
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
break;
}
case Y:
{
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
ret = Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitZ()) * Geometry::rotation_transform(-0.5 * PI * Vec3d::UnitY());
#else
ret = Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitZ()) * Geometry::assemble_transform(Vec3d::Zero(), -0.5 * PI * Vec3d::UnitY());
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
break;
}
default:
@ -866,13 +886,21 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
// Apply new temporary rotations
#if ENABLE_WORLD_COORDINATE
TransformationType transformation_type;
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 ENABLE_TRANSFORMATIONS_BY_MATRICES
if (m_parent.get_selection().is_wipe_tower())
transformation_type = TransformationType::Instance_Relative_Joint;
else {
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
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 ENABLE_TRANSFORMATIONS_BY_MATRICES
}
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
#else
TransformationType transformation_type(TransformationType::World_Relative_Joint);
#endif // ENABLE_WORLD_COORDINATE
@ -883,22 +911,27 @@ bool GLGizmoRotate3D::on_mouse(const wxMouseEvent &mouse_event)
}
void GLGizmoRotate3D::data_changed() {
const Selection &selection = m_parent.get_selection();
bool is_wipe_tower = selection.is_wipe_tower();
if (is_wipe_tower) {
DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
float wipe_tower_rotation_angle =
dynamic_cast<const ConfigOptionFloat *>(
config.option("wipe_tower_rotation_angle"))
->value;
if (m_parent.get_selection().is_wipe_tower()) {
#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
const DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
const float wipe_tower_rotation_angle =
dynamic_cast<const ConfigOptionFloat*>(
config.option("wipe_tower_rotation_angle"))->value;
set_rotation(Vec3d(0., 0., (M_PI / 180.) * wipe_tower_rotation_angle));
#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
m_gizmos[0].disable_grabber();
m_gizmos[1].disable_grabber();
} else {
}
else {
#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
set_rotation(Vec3d::Zero());
#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
m_gizmos[0].enable_grabber();
m_gizmos[1].enable_grabber();
}
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
set_rotation(Vec3d::Zero());
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
}
bool GLGizmoRotate3D::on_init()

View File

@ -34,28 +34,24 @@ static const Slic3r::ColorRGBA TRANSPARENT_PLANE_COLOR = { 0.8f, 0.8f, 0.8f, 0.5
namespace Slic3r {
namespace GUI {
Selection::VolumeCache::TransformCache::TransformCache()
: position(Vec3d::Zero())
, rotation(Vec3d::Zero())
, scaling_factor(Vec3d::Ones())
, mirror(Vec3d::Ones())
, rotation_matrix(Transform3d::Identity())
, scale_matrix(Transform3d::Identity())
, mirror_matrix(Transform3d::Identity())
, full_matrix(Transform3d::Identity())
{
}
Selection::VolumeCache::TransformCache::TransformCache(const Geometry::Transformation& transform)
: position(transform.get_offset())
, rotation(transform.get_rotation())
, scaling_factor(transform.get_scaling_factor())
, mirror(transform.get_mirror())
, full_matrix(transform.get_matrix())
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
, transform(transform)
, rotation_matrix(transform.get_rotation_matrix())
, scale_matrix(transform.get_scaling_factor_matrix())
, mirror_matrix(transform.get_mirror_matrix())
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
{
#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation);
scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor);
mirror_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), Vec3d::Ones(), mirror);
#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
}
Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform)
@ -771,27 +767,19 @@ void Selection::translate(const Vec3d& displacement, ECoordinatesType type)
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
const VolumeCache& volume_data = m_cache.volumes_data[i];
if (m_mode == Instance && !v.is_wipe_tower) {
if (m_mode == Instance && !is_wipe_tower()) {
assert(is_from_fully_selected_instance(i));
switch (type)
{
case ECoordinatesType::World:
{
if (is_from_fully_selected_instance(i))
v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix());
else
assert(false);
v.set_instance_transformation(Geometry::assemble_transform(displacement) * volume_data.get_instance_full_matrix());
break;
}
case ECoordinatesType::Local:
{
if (is_from_fully_selected_instance(i)) {
const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement;
v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix());
}
else
assert(false);
const Vec3d world_displacemet = volume_data.get_instance_rotation_matrix() * displacement;
v.set_instance_transformation(Geometry::assemble_transform(world_displacemet) * volume_data.get_instance_full_matrix());
break;
}
default: { assert(false); break; }
@ -912,6 +900,88 @@ void Selection::translate(const Vec3d& displacement, bool local)
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
// Rotate an object around one of the axes. Only one rotation component is expected to be changing.
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type)
{
if (!m_valid)
return;
assert(transformation_type.relative() || (transformation_type.absolute() && transformation_type.local()));
const Transform3d rotation_matrix = Geometry::rotation_transform(rotation);
for (unsigned int i : m_list) {
GLVolume& v = *(*m_volumes)[i];
const VolumeCache& volume_data = m_cache.volumes_data[i];
if (m_mode == Instance && !is_wipe_tower()) {
assert(is_from_fully_selected_instance(i));
const Geometry::Transformation& old_trafo = volume_data.get_instance_transform();
Transform3d new_rotation_matrix = Transform3d::Identity();
if (transformation_type.absolute())
new_rotation_matrix = rotation_matrix;
else {
if (transformation_type.world())
new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix();
else if (transformation_type.local())
new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix;
else
assert(false);
}
const Vec3d new_offset = transformation_type.independent() ? old_trafo.get_offset() :
m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() *
(old_trafo.get_offset() - m_cache.dragging_center);
v.set_instance_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix,
old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix()));
}
else {
const Geometry::Transformation& old_trafo = volume_data.get_volume_transform();
Transform3d new_rotation_matrix = Transform3d::Identity();
if (transformation_type.absolute())
new_rotation_matrix = rotation_matrix;
else {
if (transformation_type.world()) {
const Transform3d inst_rotation_matrix = volume_data.get_instance_transform().get_rotation_matrix();
new_rotation_matrix = inst_rotation_matrix.inverse() * rotation_matrix * inst_rotation_matrix * old_trafo.get_rotation_matrix();
}
else if (transformation_type.instance())
new_rotation_matrix = rotation_matrix * old_trafo.get_rotation_matrix();
else if (transformation_type.local())
new_rotation_matrix = old_trafo.get_rotation_matrix() * rotation_matrix;
else
assert(false);
}
const Vec3d new_offset = !is_wipe_tower() ? old_trafo.get_offset() :
m_cache.dragging_center + new_rotation_matrix * old_trafo.get_rotation_matrix().inverse() *
(old_trafo.get_offset() - m_cache.dragging_center);
v.set_volume_transformation(Geometry::assemble_transform(Geometry::assemble_transform(new_offset), new_rotation_matrix,
old_trafo.get_scaling_factor_matrix(), old_trafo.get_mirror_matrix()));
}
}
#if !DISABLE_INSTANCES_SYNCH
if (m_mode == Instance) {
int rot_axis_max = 0;
rotation.cwiseAbs().maxCoeff(&rot_axis_max);
SyncRotationType synch;
if (transformation_type.world() && rot_axis_max == 2)
synch = SyncRotationType::NONE;
else if (transformation_type.local())
synch = SyncRotationType::FULL;
else
synch = SyncRotationType::GENERAL;
synchronize_unselected_instances(synch);
}
else if (m_mode == Volume)
synchronize_unselected_volumes();
#endif // !DISABLE_INSTANCES_SYNCH
set_bounding_boxes_dirty();
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
#else
void Selection::rotate(const Vec3d& rotation, TransformationType transformation_type)
{
if (!m_valid)
@ -1069,6 +1139,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_
set_bounding_boxes_dirty();
wxGetApp().plater()->canvas3D()->requires_check_outside_state();
}
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
void Selection::flattening_rotate(const Vec3d& normal)
{
@ -2969,7 +3040,14 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
if (v->object_idx() != object_idx || v->instance_idx() == instance_idx)
continue;
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
const Vec3d inst_rotation_i = m_cache.volumes_data[i].get_instance_transform().get_rotation();
const Vec3d inst_rotation_j = m_cache.volumes_data[j].get_instance_transform().get_rotation();
assert(is_rotation_xy_synchronized(inst_rotation_i, inst_rotation_j));
#else
assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()));
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
switch (sync_rotation_type) {
case SyncRotationType::NONE: {
// z only rotation -> synch instance z
@ -2981,17 +3059,29 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_
}
case SyncRotationType::GENERAL: {
// generic rotation -> update instance z with the delta of the rotation.
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
const double z_diff = Geometry::rotation_diff_z(inst_rotation_i, inst_rotation_j);
#else
const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation());
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
break;
}
#if ENABLE_WORLD_COORDINATE
case SyncRotationType::FULL: {
// generic rotation -> update instance z with the delta of the rotation.
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, inst_rotation_j));
#else
const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rotation, m_cache.volumes_data[j].get_instance_rotation()));
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
const Vec3d& axis = angle_axis.axis();
const double z_diff = (std::abs(axis.x()) > EPSILON || std::abs(axis.y()) > EPSILON) ?
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
angle_axis.angle()* axis.z() : Geometry::rotation_diff_z(rotation, inst_rotation_j);
#else
angle_axis.angle() * axis.z() : Geometry::rotation_diff_z(rotation, m_cache.volumes_data[j].get_instance_rotation());
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff });
break;

View File

@ -121,16 +121,19 @@ private:
private:
struct TransformCache
{
Vec3d position;
Vec3d rotation;
Vec3d scaling_factor;
Vec3d mirror;
Transform3d rotation_matrix;
Transform3d scale_matrix;
Transform3d mirror_matrix;
Transform3d full_matrix;
Vec3d position{ Vec3d::Zero() };
Vec3d rotation{ Vec3d::Zero() };
Vec3d scaling_factor{ Vec3d::Ones() };
Vec3d mirror{ Vec3d::Ones() };
Transform3d rotation_matrix{ Transform3d::Identity() };
Transform3d scale_matrix{ Transform3d::Identity() };
Transform3d mirror_matrix{ Transform3d::Identity() };
Transform3d full_matrix{ Transform3d::Identity() };
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
Geometry::Transformation transform;
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
TransformCache();
TransformCache() = default;
explicit TransformCache(const Geometry::Transformation& transform);
};
@ -142,13 +145,18 @@ private:
VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform);
const Vec3d& get_volume_position() const { return m_volume.position; }
#if !ENABLE_TRANSFORMATIONS_BY_MATRICES
const Vec3d& get_volume_rotation() const { return m_volume.rotation; }
const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; }
const Vec3d& get_volume_mirror() const { return m_volume.mirror; }
#endif // !ENABLE_TRANSFORMATIONS_BY_MATRICES
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; }
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
const Geometry::Transformation& get_volume_transform() const { return m_volume.transform; }
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
const Vec3d& get_instance_position() const { return m_instance.position; }
const Vec3d& get_instance_rotation() const { return m_instance.rotation; }
@ -158,6 +166,9 @@ private:
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; }
#if ENABLE_TRANSFORMATIONS_BY_MATRICES
const Geometry::Transformation& get_instance_transform() const { return m_instance.transform; }
#endif // ENABLE_TRANSFORMATIONS_BY_MATRICES
};
public: