mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-24 17:26:47 +00:00
179 lines
8.2 KiB
C++
179 lines
8.2 KiB
C++
// Orca: This suite links libslic3r_gui; navigation raycasts need no wx application or GL context.
|
|
#ifdef WIN32
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <Windows.h>
|
|
// Match the GUI precompiled header: wx/msw/wrapcctl.h needs HDITEM from CommCtrl.h.
|
|
#include <CommCtrl.h>
|
|
#endif
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
|
|
#include "slic3r/GUI/Camera.hpp"
|
|
#include "slic3r/GUI/CameraUtils.hpp"
|
|
#include "slic3r/GUI/PartPlate.hpp"
|
|
#include "slic3r/GUI/SceneRaycaster.hpp"
|
|
|
|
using namespace Slic3r;
|
|
using namespace Slic3r::GUI;
|
|
|
|
namespace {
|
|
|
|
Camera horizontal_camera(Camera::EType type = Camera::EType::Perspective)
|
|
{
|
|
Camera camera;
|
|
camera.set_type(type);
|
|
camera.look_at({0.0, 0.0, 10.0}, {0.0, 100.0, 10.0}, Vec3d::UnitZ());
|
|
camera.set_viewport(0, 0, 600, 600);
|
|
camera.apply_projection(-1.0, 1.0, -1.0, 1.0, 1.0, 1000.0);
|
|
return camera;
|
|
}
|
|
|
|
SceneRaycaster::HitResult scene_hit(const SceneRaycaster& scene, const Camera& camera, const Vec3d& point,
|
|
SceneRaycaster::EHitMode mode = SceneRaycaster::EHitMode::SceneOnly)
|
|
{
|
|
return scene.hit(CameraUtils::project(camera, point).cast<double>(), camera, nullptr, mode);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("Navigation hits the visible bed below a horizontal perspective view", "[SceneRaycaster][Regression]")
|
|
{
|
|
const MeshRaycaster bed(TriangleMesh(
|
|
{{-100.f, 20.f, 0.f}, {100.f, 20.f, 0.f}, {100.f, 200.f, 0.f}, {-100.f, 200.f, 0.f}},
|
|
{{0, 1, 2}, {0, 2, 3}}));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Bed, 0, bed, Transform3d::Identity());
|
|
const Camera camera = horizontal_camera();
|
|
|
|
const auto hit = scene_hit(scene, camera, {0.0, 100.0, 0.0});
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == SceneRaycaster::EType::Bed);
|
|
CHECK_THAT(hit.position.z(), Catch::Matchers::WithinAbs(0.0, 1e-4));
|
|
}
|
|
|
|
TEST_CASE("Navigation hits a visible side face away from the perspective view center", "[SceneRaycaster][Regression]")
|
|
{
|
|
const auto mode = GENERATE(SceneRaycaster::EHitMode::SceneOnly, SceneRaycaster::EHitMode::VolumesOnly);
|
|
const MeshRaycaster side(TriangleMesh(
|
|
{{10.f, 20.f, -100.f}, {10.f, 20.f, 100.f}, {10.f, 200.f, 100.f}, {10.f, 200.f, -100.f}},
|
|
{{0, 1, 2}, {0, 2, 3}}));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, side, Transform3d::Identity());
|
|
const Camera camera = horizontal_camera();
|
|
|
|
const auto hit = scene_hit(scene, camera, {10.0, 100.0, 10.0}, mode);
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == SceneRaycaster::EType::Volume);
|
|
CHECK_THAT(hit.position.x(), Catch::Matchers::WithinAbs(10.0, 1e-4));
|
|
}
|
|
|
|
TEST_CASE("Navigation ignores gizmos and inactive volumes and chooses the nearest scene surface", "[SceneRaycaster]")
|
|
{
|
|
const bool gizmos_on_top = GENERATE(false, true);
|
|
const auto mode = GENERATE(SceneRaycaster::EHitMode::SceneOnly, SceneRaycaster::EHitMode::VolumesOnly);
|
|
const auto type = GENERATE(Camera::EType::Perspective, Camera::EType::Ortho);
|
|
const MeshRaycaster cube(make_cube(20.0, 20.0, 20.0));
|
|
SceneRaycaster scene;
|
|
scene.set_gizmos_on_top(gizmos_on_top);
|
|
scene.add_raycaster(SceneRaycaster::EType::Gizmo, 0, cube, Geometry::translation_transform({-10.0, 20.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::FallbackGizmo, 0, cube, Geometry::translation_transform({-10.0, 30.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, cube, Geometry::translation_transform({-10.0, 40.0, 0.0}))->set_active(false);
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 1, cube, Geometry::translation_transform({-10.0, 150.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 2, cube, Geometry::translation_transform({-10.0, 100.0, 0.0}));
|
|
const Camera camera = horizontal_camera(type);
|
|
|
|
const auto hit = scene_hit(scene, camera, {0.0, 100.0, 10.0}, mode);
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == SceneRaycaster::EType::Volume);
|
|
CHECK(hit.raycaster_id == 2);
|
|
CHECK_THAT(hit.position.y(), Catch::Matchers::WithinAbs(100.0, 1e-4));
|
|
}
|
|
|
|
TEST_CASE("Navigation skips bed raycasters when the bed is hidden", "[SceneRaycaster][Regression]")
|
|
{
|
|
const auto mode = GENERATE(SceneRaycaster::EHitMode::SceneOnly, SceneRaycaster::EHitMode::VolumesOnly);
|
|
const auto type = GENERATE(Camera::EType::Perspective, Camera::EType::Ortho);
|
|
const bool looking_downward = GENERATE(false, true);
|
|
const MeshRaycaster cube(make_cube(20.0, 20.0, 20.0));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Bed, 0, cube, Geometry::translation_transform({-10.0, 40.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, cube, Geometry::translation_transform({-10.0, 100.0, 0.0}));
|
|
Camera camera = horizontal_camera(type);
|
|
if (looking_downward)
|
|
camera.look_at({0.0, 0.0, 10.0}, {0.0, 100.0, 0.0}, Vec3d::UnitZ());
|
|
|
|
const auto hit = scene_hit(scene, camera, {0.0, 100.0, 10.0}, mode);
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == (mode == SceneRaycaster::EHitMode::SceneOnly ?
|
|
SceneRaycaster::EType::Bed : SceneRaycaster::EType::Volume));
|
|
|
|
scene.remove_raycasters(SceneRaycaster::EType::Volume);
|
|
CHECK(scene_hit(scene, camera, {0.0, 100.0, 10.0}, mode).is_valid() ==
|
|
(mode == SceneRaycaster::EHitMode::SceneOnly));
|
|
}
|
|
|
|
TEST_CASE("Navigation ignores plate controls while retaining plate surfaces and volumes", "[SceneRaycaster][Regression]")
|
|
{
|
|
const int plate_index = GENERATE(0, 2);
|
|
const int component = GENERATE(range(1, int(PartPlate::GRABBER_COUNT)));
|
|
const int bed_id = plate_index * PartPlate::GRABBER_COUNT;
|
|
const MeshRaycaster cube(make_cube(20.0, 20.0, 20.0));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Bed, bed_id + component, cube,
|
|
Geometry::translation_transform({-10.0, 40.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Bed, bed_id, cube,
|
|
Geometry::translation_transform({-10.0, 100.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, cube,
|
|
Geometry::translation_transform({-10.0, 150.0, 0.0}));
|
|
const Camera camera = horizontal_camera();
|
|
|
|
auto hit = scene_hit(scene, camera, {0.0, 100.0, 10.0});
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == SceneRaycaster::EType::Bed);
|
|
CHECK(hit.raycaster_id == bed_id);
|
|
|
|
scene.remove_raycasters(SceneRaycaster::EType::Bed, bed_id);
|
|
hit = scene_hit(scene, camera, {0.0, 100.0, 10.0});
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.type == SceneRaycaster::EType::Volume);
|
|
|
|
// A control alone must leave navigation free to choose its fallback anchor.
|
|
scene.remove_raycasters(SceneRaycaster::EType::Volume);
|
|
CHECK_FALSE(scene_hit(scene, camera, {0.0, 100.0, 10.0}).is_valid());
|
|
}
|
|
|
|
TEST_CASE("Navigation respects the back-face policy away from the perspective view center", "[SceneRaycaster]")
|
|
{
|
|
const bool use_back_faces = GENERATE(false, true);
|
|
const auto mode = GENERATE(SceneRaycaster::EHitMode::SceneOnly, SceneRaycaster::EHitMode::VolumesOnly);
|
|
const MeshRaycaster side(TriangleMesh(
|
|
{{10.f, 20.f, -100.f}, {10.f, 20.f, 100.f}, {10.f, 200.f, 100.f}, {10.f, 200.f, -100.f}},
|
|
{{0, 2, 1}, {0, 3, 2}}));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, side, Transform3d::Identity(), use_back_faces);
|
|
|
|
const auto hit = scene_hit(scene, horizontal_camera(), {10.0, 100.0, 10.0}, mode);
|
|
CHECK(hit.is_valid() == use_back_faces);
|
|
}
|
|
|
|
TEST_CASE("Navigation ignores volume surfaces removed by the clipping plane", "[SceneRaycaster]")
|
|
{
|
|
const auto mode = GENERATE(SceneRaycaster::EHitMode::SceneOnly, SceneRaycaster::EHitMode::VolumesOnly);
|
|
const MeshRaycaster cube(make_cube(20.0, 20.0, 20.0));
|
|
SceneRaycaster scene;
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 0, cube, Geometry::translation_transform({-10.0, 100.0, 0.0}));
|
|
scene.add_raycaster(SceneRaycaster::EType::Volume, 1, cube, Geometry::translation_transform({-10.0, 150.0, 0.0}));
|
|
const Camera camera = horizontal_camera();
|
|
const ClippingPlane clipping_plane(-Vec3d::UnitY(), -130.0);
|
|
|
|
const auto hit = scene.hit({300.0, 300.0}, camera, &clipping_plane, mode);
|
|
REQUIRE(hit.is_valid());
|
|
CHECK(hit.raycaster_id == 1);
|
|
CHECK_THAT(hit.position.y(), Catch::Matchers::WithinAbs(150.0, 1e-4));
|
|
}
|