From d8dc1c051f631279a8160d819a91e459bddcd5b9 Mon Sep 17 00:00:00 2001 From: Magne Sjaastad Date: Mon, 19 Oct 2026 11:17:52 +0200 Subject: [PATCH] Fix crash in Camera::computeFitViewEyePosition with zero view direction Zoom All could pass a zero view direction (eye equal to view reference point). The up vector then also became zero, giving a plane with a zero normal and an assert in Plane::distanceToOrigin. Fall back to the Y axis when the direction is zero or undefined. Follow-up to the parallel dir/up fix in #14849. Call stack: [5] cvf::AssertHandlerConsole::handleAssert(char const*, int, char const*, char const*) at cvfAssert.cpp:125 [6] cvf::Plane::distanceToOrigin() const at cvfPlane.cpp:313 [7] cvf::Camera::computeFitViewEyePosition(cvf::BoundingBox const&, cvf::Vector3 const&, cvf::Vector3 const&, double, double, double) at cvfCamera.cpp:361 [8] caf::Viewer::zoomAll() at cafViewer.cpp:992 [9] caf::CmdFeature::actionTriggered(bool) at cafCmdFeature.cpp:176 --- .../UnitTests/CvfCameraCrashTriage-Test.cpp | 41 +++++++++++++++++++ Fwk/VizFwk/LibRender/cvfCamera.cpp | 21 +++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) diff --git a/ApplicationLibCode/UnitTests/CvfCameraCrashTriage-Test.cpp b/ApplicationLibCode/UnitTests/CvfCameraCrashTriage-Test.cpp index 4de036bfdb..6340d3ef43 100644 --- a/ApplicationLibCode/UnitTests/CvfCameraCrashTriage-Test.cpp +++ b/ApplicationLibCode/UnitTests/CvfCameraCrashTriage-Test.cpp @@ -85,3 +85,44 @@ TEST( CvfCameraCrashTriageTest, FitViewWithParallelDirAndUpProducesValidViewMatr EXPECT_NEAR( c.direction() * c.right(), 0.0, 1e-6 ); EXPECT_NEAR( c.up() * c.right(), 0.0, 1e-6 ); } + +//-------------------------------------------------------------------------------------------------- +/// A zero view direction (eye == view reference point, as in caf::Viewer::zoomAll()) gave a zero up +/// vector and a zero-normal plane, which asserted in Plane::distanceToOrigin(). +//-------------------------------------------------------------------------------------------------- +TEST( CvfCameraCrashTriageTest, FitViewEyePositionWithZeroDirDoesNotCrash ) +{ + Camera c; + + BoundingBox bb; + bb.add( Vec3d( 0, 0, 0 ) ); + bb.add( Vec3d( 1, 1, 1 ) ); + + for ( const auto& dir : { Vec3d::ZERO, Vec3d::UNDEFINED } ) + { + Vec3d eye = Camera::computeFitViewEyePosition( bb, dir, Vec3d( 0, 0, 1 ), 0.9, c.fieldOfViewYDeg(), 1.0 ); + + EXPECT_FALSE( eye.isUndefined() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// fitView() must derive the up vector from the same valid direction as the eye position +//-------------------------------------------------------------------------------------------------- +TEST( CvfCameraCrashTriageTest, FitViewWithZeroOrUndefinedDirProducesValidViewMatrix ) +{ + BoundingBox bb; + bb.add( Vec3d( 0, 0, 0 ) ); + bb.add( Vec3d( 1, 1, 1 ) ); + + for ( const auto& dir : { Vec3d::ZERO, Vec3d::UNDEFINED } ) + { + Camera c; + c.setViewport( 0, 0, 100, 100 ); + c.fitView( bb, dir, Vec3d( 0, 0, 1 ), 0.9 ); + + EXPECT_NEAR( c.direction().length(), 1.0, 1e-6 ); + EXPECT_NEAR( c.up().length(), 1.0, 1e-6 ); + EXPECT_NEAR( c.right().length(), 1.0, 1e-6 ); + } +} diff --git a/Fwk/VizFwk/LibRender/cvfCamera.cpp b/Fwk/VizFwk/LibRender/cvfCamera.cpp index 698a571b6f..4014502e05 100644 --- a/Fwk/VizFwk/LibRender/cvfCamera.cpp +++ b/Fwk/VizFwk/LibRender/cvfCamera.cpp @@ -43,10 +43,23 @@ #include "cvfBoundingBox.h" #include "cvfGeometryUtils.h" +#include + namespace cvf { namespace { +//-------------------------------------------------------------------------------------------------- +/// A zero, undefined or non-finite view direction cannot define a view; fall back to looking along the Y axis +//-------------------------------------------------------------------------------------------------- +Vec3d validViewDirection(const Vec3d& dir) +{ + const double length = dir.length(); + if (!dir.isUndefined() && length > 0.0 && length < std::numeric_limits::max()) return dir; + + return Vec3d::Y_AXIS; +} + //-------------------------------------------------------------------------------------------------- /// Returns an up vector guaranteed to be non-parallel to dir. If the passed up vector is already /// non-parallel to dir, it is returned unchanged. Otherwise a corrected, orthogonal up vector is @@ -310,8 +323,10 @@ void Camera::setProjectionAsPixelExact2D() /// the view to. The relativeDistance parameter specifies the distance from the camera to the /// center of the bounding box //-------------------------------------------------------------------------------------------------- -void Camera::fitView(const BoundingBox& boundingBox, const Vec3d& dir, const Vec3d& up, double coverageFactor) +void Camera::fitView(const BoundingBox& boundingBox, const Vec3d& dirIn, const Vec3d& up, double coverageFactor) { + const cvf::Vec3d dir = validViewDirection(dirIn); + // dir and up may be parallel (degenerate camera orientation); derive a corrected, non-parallel // up vector so both the eye computation and the final look-at matrix use a consistent basis. cvf::Vec3d correctedUp = nonParallelUpVector(dir, up); @@ -327,8 +342,10 @@ void Camera::fitView(const BoundingBox& boundingBox, const Vec3d& dir, const Vec //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -Vec3d Camera::computeFitViewEyePosition(const BoundingBox& boundingBox, const Vec3d& dir, const Vec3d& up, double coverageFactor, double fieldOfViewYDeg, double aspectRatio) +Vec3d Camera::computeFitViewEyePosition(const BoundingBox& boundingBox, const Vec3d& dirIn, const Vec3d& up, double coverageFactor, double fieldOfViewYDeg, double aspectRatio) { + const cvf::Vec3d dir = validViewDirection(dirIn); + cvf::Vec3d corners[8]; boundingBox.cornerVertices(corners);