Files
ResInsight/ApplicationLibCode/UnitTests/CvfCameraCrashTriage-Test.cpp
T
Magne Sjaastad d8dc1c051f 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<double> const&, cvf::Vector3<double> 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
2026-10-19 19:58:37 +02:00

129 lines
5.0 KiB
C++

#include "gtest/gtest.h"
#include "cvfBoundingBox.h"
#include "cvfCamera.h"
#include "cvfFrustum.h"
using namespace cvf;
//--------------------------------------------------------------------------------------------------
/// Camera::computeViewFrustum() returns an empty frustum for a degenerate projection matrix. View
/// frustum culling in ModelBasicList::partVisible() then asserted in Frustum::isOutside().
//--------------------------------------------------------------------------------------------------
TEST( CvfCameraCrashTriageTest, IncompleteFrustumDoesNotCull )
{
Frustum frustum;
BoundingBox bb;
bb.add( Vec3d( 0, 0, 0 ) );
bb.add( Vec3d( 1, 1, 1 ) );
EXPECT_FALSE( frustum.isOutside( bb ) );
}
//--------------------------------------------------------------------------------------------------
/// computeFitViewEyePosition() used to assert/abort (CVF_ASSERT in Plane::distanceToOrigin) when
/// the view direction and up vector are parallel: dir^up then becomes a zero vector, producing an
/// invalid (zero-normal) plane used internally. Reproduces the crash reported from
/// caf::Viewer::zoomAll() with a degenerate camera orientation.
//--------------------------------------------------------------------------------------------------
TEST( CvfCameraCrashTriageTest, FitViewWithParallelDirAndUpDoesNotCrash )
{
Camera c;
BoundingBox bb;
bb.add( Vec3d( 0, 0, 0 ) );
bb.add( Vec3d( 1, 1, 1 ) );
Vec3d inViewDir( 0, 0, 1 );
Vec3d inUp( 0, 0, 1 );
double coverageFactor = 0.9;
Vec3d eye = Camera::computeFitViewEyePosition( bb, inViewDir, inUp, coverageFactor, c.fieldOfViewYDeg(), 1.0 );
EXPECT_FALSE( eye.isUndefined() );
}
//--------------------------------------------------------------------------------------------------
/// fitView() must also produce a valid, non-singular camera orientation when dir and up are
/// parallel: it uses the (uncorrected) up vector both for setFromLookAt() and internally in
/// computeFitViewEyePosition()/createLookAtMatrix(), so the whole up vector derivation must be
/// consistent, not only the right vector used to compute the eye position.
//--------------------------------------------------------------------------------------------------
TEST( CvfCameraCrashTriageTest, FitViewWithParallelDirAndUpProducesValidViewMatrix )
{
Camera c;
c.setViewport( 0, 0, 100, 100 );
BoundingBox bb;
bb.add( Vec3d( 0, 0, 0 ) );
bb.add( Vec3d( 1, 1, 1 ) );
Vec3d inViewDir( 0, 0, 1 );
Vec3d inUp( 0, 0, 1 );
double coverageFactor = 0.9;
c.fitView( bb, inViewDir, inUp, coverageFactor );
Vec3d eye;
Vec3d vrp;
Vec3d up;
c.toLookAt( &eye, &vrp, &up );
EXPECT_FALSE( eye.isUndefined() );
EXPECT_FALSE( c.direction().isUndefined() );
EXPECT_FALSE( c.up().isUndefined() );
EXPECT_FALSE( c.right().isUndefined() );
// direction, up and right should form a valid, mutually orthogonal basis (within floating point
// tolerance). If the up vector correction is not applied consistently, the derived up/right
// vectors from the (singular) view matrix will be degenerate (zero length or not orthogonal).
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 );
EXPECT_NEAR( c.direction() * c.up(), 0.0, 1e-6 );
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 );
}
}