Fix: Correct camera panning for the perspective view (#15212)

This commit is contained in:
Valerii Bokhan
2026-09-23 20:38:26 -03:00
committed by GitHub
parent 02746cd0f5
commit 037b859447
7 changed files with 505 additions and 71 deletions
+31 -10
View File
@@ -99,8 +99,11 @@ void SceneRaycaster::remove_raycaster(std::shared_ptr<SceneRaycasterItem> item)
}
}
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera, const ClippingPlane* clipping_plane) const
SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Camera& camera,
const ClippingPlane* clipping_plane, SceneRaycaster::EHitMode mode) const
{
// Orca: Picking may favor an already selected volume for interaction, while camera
// navigation must always use the geometrically closest visible scene surface.
// helper class used to return currently selected volume as hit when overlapping with other volumes
// to allow the user to click and drag on a selected volume
class VolumeKeeper
@@ -110,7 +113,11 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
bool m_selected_volume_already_found{ false };
public:
VolumeKeeper() {
explicit VolumeKeeper(bool enabled) {
// Orca: Disable selected-volume bias for navigation raycasts.
if (!enabled)
return;
const Selection& selection = wxGetApp().plater()->get_selection();
if (selection.is_single_volume() || selection.is_single_modifier()) {
const GLVolume* volume = selection.get_first_volume();
@@ -135,7 +142,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
VolumeKeeper volume_keeper;
VolumeKeeper volume_keeper(mode == EHitMode::Picking);
double closest_hit_squared_distance = std::numeric_limits<double>::max();
auto is_closest = [&closest_hit_squared_distance, &volume_keeper](const Camera& camera, const Vec3f& hit) {
@@ -154,7 +161,7 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
HitResult ret;
auto test_raycasters = [this, is_closest, clipping_plane, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
auto test_raycasters = [this, is_closest, clipping_plane, mode, &volume_keeper](EType type, const Vec2d& mouse_pos, const Camera& camera, HitResult& ret) {
const ClippingPlane* clip_plane = (clipping_plane != nullptr && type == EType::Volume) ? clipping_plane : nullptr;
const std::vector<std::shared_ptr<SceneRaycasterItem>>* raycasters = get_raycasters(type);
const Vec3f camera_forward = camera.get_dir_forward().cast<float>();
@@ -163,12 +170,22 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
if (!item->is_active())
continue;
// Each plate's component 0 is its surface; the remaining Bed IDs are controls.
// Keep controls clickable, but do not use them as camera-pan anchors.
if (mode != EHitMode::Picking && type == EType::Bed &&
decode_id(type, item->get_id()) % PartPlate::GRABBER_COUNT != 0)
continue;
current_hit.raycaster_id = item->get_id();
const Transform3d& trafo = item->get_transform();
if (item->get_raycaster()->closest_hit(mouse_pos, trafo, camera, current_hit.position, current_hit.normal, clip_plane)) {
current_hit.position = (trafo * current_hit.position.cast<double>()).cast<float>();
current_hit.normal = (trafo.matrix().block(0, 0, 3, 3).inverse().transpose() * current_hit.normal.cast<double>()).normalized().cast<float>();
if (item->use_back_faces() || current_hit.normal.dot(camera_forward) < 0.0f) {
// Orca: Perspective rays away from the viewport center are not parallel to camera_forward.
// Keep picking's legacy policy, but accept every front-facing navigation surface.
const Vec3f view_direction = mode != EHitMode::Picking && camera.get_type() == Camera::EType::Perspective ?
Vec3f((current_hit.position.cast<double>() - camera.get_position()).cast<float>()) : camera_forward;
if (item->use_back_faces() || current_hit.normal.dot(view_direction) < 0.0f) {
if (is_closest(camera, current_hit.position)) {
if (volume_keeper.is_active()) {
if (volume_keeper.check_hit_result(current_hit))
@@ -182,14 +199,18 @@ SceneRaycaster::HitResult SceneRaycaster::hit(const Vec2d& mouse_pos, const Came
}
};
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
// Orca: Gizmo geometry is an interaction target, not a valid depth anchor for camera movement.
if (mode == EHitMode::Picking) {
if (!m_gizmos.empty())
test_raycasters(EType::Gizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
if (!m_fallback_gizmos.empty() && !ret.is_valid())
test_raycasters(EType::FallbackGizmo, mouse_pos, camera, ret);
}
if (!m_gizmos_on_top || !ret.is_valid()) {
if (camera.is_looking_downward() && !m_bed.empty())
// Orca: In perspective the bottom of the viewport can see the bed even at a horizontal view.
if ((mode == EHitMode::SceneOnly || (mode == EHitMode::Picking && camera.is_looking_downward())) && !m_bed.empty())
test_raycasters(EType::Bed, mouse_pos, camera, ret);
if (!m_volumes.empty())
test_raycasters(EType::Volume, mouse_pos, camera, ret);