Gui: Add tests to automatically verify the axonometric views
This commit is contained in:
committed by
Kacper Donat
parent
d427162d97
commit
93ea68a23b
@@ -29,70 +29,6 @@
|
||||
using namespace Gui;
|
||||
|
||||
|
||||
/**
|
||||
Formulas to get quaternion for axonometric views:
|
||||
|
||||
\code
|
||||
from math import sqrt, degrees, asin, atan
|
||||
p1=App.Rotation(App.Vector(1,0,0),90)
|
||||
p2=App.Rotation(App.Vector(0,0,1),alpha)
|
||||
p3=App.Rotation(p2.multVec(App.Vector(1,0,0)),beta)
|
||||
p4=p3.multiply(p2).multiply(p1)
|
||||
|
||||
from pivy import coin
|
||||
c=Gui.ActiveDocument.ActiveView.getCameraNode()
|
||||
c.orientation.setValue(*p4.Q)
|
||||
\endcode
|
||||
|
||||
The angles alpha and beta depend on the type of axonometry
|
||||
Isometric:
|
||||
\code
|
||||
alpha=45
|
||||
beta=degrees(asin(-sqrt(1.0/3.0)))
|
||||
\endcode
|
||||
|
||||
Dimetric:
|
||||
\code
|
||||
alpha=degrees(asin(sqrt(1.0/8.0)))
|
||||
beta=degrees(-asin(1.0/3.0))
|
||||
\endcode
|
||||
|
||||
Trimetric:
|
||||
\code
|
||||
alpha=30.0
|
||||
beta=-35.0
|
||||
\endcode
|
||||
|
||||
Verification code that the axonomtries are correct:
|
||||
|
||||
\code
|
||||
from pivy import coin
|
||||
c=Gui.ActiveDocument.ActiveView.getCameraNode()
|
||||
vo=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,0,0)).getValue())
|
||||
vx=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(10,0,0)).getValue())
|
||||
vy=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,10,0)).getValue())
|
||||
vz=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,0,10)).getValue())
|
||||
(vx-vo).Length
|
||||
(vy-vo).Length
|
||||
(vz-vo).Length
|
||||
|
||||
# Projection
|
||||
vo.z=0
|
||||
vx.z=0
|
||||
vy.z=0
|
||||
vz.z=0
|
||||
|
||||
(vx-vo).Length
|
||||
(vy-vo).Length
|
||||
(vz-vo).Length
|
||||
\endcode
|
||||
|
||||
See also:
|
||||
http://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_2_ger_web.html#1
|
||||
http://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/code_v2/Axonometric/qt/Axonometric.cpp
|
||||
https://de.wikipedia.org/wiki/Arkussinus_und_Arkuskosinus
|
||||
*/
|
||||
|
||||
SbRotation Camera::top()
|
||||
{
|
||||
return {0, 0, 0, 1};
|
||||
@@ -127,35 +63,31 @@ SbRotation Camera::left()
|
||||
|
||||
SbRotation Camera::isometric()
|
||||
{
|
||||
//from math import sqrt, degrees, asin
|
||||
//p1=App.Rotation(App.Vector(1,0,0),45)
|
||||
//p2=App.Rotation(App.Vector(0,0,1),-45)
|
||||
//p3=p2.multiply(p1)
|
||||
//return SbRotation(0.353553f, -0.146447f, -0.353553f, 0.853553f);
|
||||
|
||||
//from math import sqrt, degrees, asin
|
||||
//p1=App.Rotation(App.Vector(1,0,0),90)
|
||||
//p2=App.Rotation(App.Vector(0,0,1),135)
|
||||
//p3=App.Rotation(App.Vector(-1,1,0),degrees(asin(-sqrt(1.0/3.0))))
|
||||
//p4=p3.multiply(p2).multiply(p1)
|
||||
//return SbRotation(0.17592, 0.424708, 0.820473, 0.339851);
|
||||
|
||||
//from math import sqrt, degrees, asin
|
||||
//p1=App.Rotation(App.Vector(1,0,0),90)
|
||||
//p2=App.Rotation(App.Vector(0,0,1),45)
|
||||
//#p3=App.Rotation(App.Vector(1,1,0),45)
|
||||
//p3=App.Rotation(App.Vector(1,1,0),degrees(asin(-sqrt(1.0/3.0))))
|
||||
//p4=p3.multiply(p2).multiply(p1)
|
||||
// The values here are precalculated as our quaternion implementation
|
||||
// does not support calculating the values in compile time.
|
||||
// The values are verified with unit tests.
|
||||
return {0.424708F, 0.17592F, 0.339851F, 0.820473F};
|
||||
}
|
||||
|
||||
SbRotation Camera::dimetric()
|
||||
{
|
||||
// The values here are precalculated as our quaternion implementation
|
||||
// does not support calculating the values in compile time.
|
||||
// The values are verified with unit tests.
|
||||
|
||||
// While there are multiple ways to calculate the dimetric rotation,
|
||||
// we use one which is similar to other CAD applications.
|
||||
return {0.567952F, 0.103751F, 0.146726F, 0.803205F};
|
||||
}
|
||||
|
||||
SbRotation Camera::trimetric()
|
||||
{
|
||||
// The values here are precalculated as our quaternion implementation
|
||||
// does not support calculating the values in compile time.
|
||||
// The values are verified with unit tests.
|
||||
|
||||
// While there are multiple ways to calculate the trimetric rotation,
|
||||
// we use one which is similar to other CAD applications.
|
||||
return {0.446015F, 0.119509F, 0.229575F, 0.856787F};
|
||||
}
|
||||
|
||||
|
||||
@@ -134,6 +134,7 @@ target_link_libraries(Tests_run
|
||||
gmock_main
|
||||
${Google_Tests_LIBS}
|
||||
FreeCADApp
|
||||
FreeCADGui
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Standard C++ GTest tests
|
||||
target_sources(Tests_run PRIVATE
|
||||
Assistant.cpp
|
||||
Camera.cpp
|
||||
)
|
||||
|
||||
# Qt tests
|
||||
|
||||
215
tests/src/Gui/Camera.cpp
Normal file
215
tests/src/Gui/Camera.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
#include <Inventor/SbMatrix.h>
|
||||
#include <Inventor/SbRotation.h>
|
||||
#include <Inventor/SbViewVolume.h>
|
||||
#include <Base/Converter.h>
|
||||
#include <Base/Tools.h>
|
||||
#include <Gui/Camera.h>
|
||||
#include <Gui/Utilities.h>
|
||||
|
||||
/*
|
||||
This comment was previously used to get the hard coded axonometric view quaternions
|
||||
in the Camera class.
|
||||
This has since been replaced with unit tests that verify the correctness of the
|
||||
quaternion calculations.
|
||||
|
||||
The old code is kept for reference and to show how the quaternions were calculated.
|
||||
|
||||
---
|
||||
|
||||
Formulas to get quaternion for axonometric views:
|
||||
|
||||
\code
|
||||
from math import sqrt, degrees, asin, atan
|
||||
p1=App.Rotation(App.Vector(1,0,0),90)
|
||||
p2=App.Rotation(App.Vector(0,0,1),alpha)
|
||||
p3=App.Rotation(p2.multVec(App.Vector(1,0,0)),beta)
|
||||
p4=p3.multiply(p2).multiply(p1)
|
||||
|
||||
from pivy import coin
|
||||
c=Gui.ActiveDocument.ActiveView.getCameraNode()
|
||||
c.orientation.setValue(*p4.Q)
|
||||
\endcode
|
||||
|
||||
The angles alpha and beta depend on the type of axonometry
|
||||
Isometric:
|
||||
\code
|
||||
alpha=45
|
||||
beta=degrees(asin(-sqrt(1.0/3.0)))
|
||||
\endcode
|
||||
|
||||
Dimetric:
|
||||
\code
|
||||
alpha=degrees(asin(sqrt(1.0/8.0)))
|
||||
beta=degrees(-asin(1.0/3.0))
|
||||
\endcode
|
||||
|
||||
Trimetric:
|
||||
\code
|
||||
alpha=30.0
|
||||
beta=-35.0
|
||||
\endcode
|
||||
|
||||
Verification code that the axonomtries are correct:
|
||||
|
||||
\code
|
||||
from pivy import coin
|
||||
c=Gui.ActiveDocument.ActiveView.getCameraNode()
|
||||
vo=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,0,0)).getValue())
|
||||
vx=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(10,0,0)).getValue())
|
||||
vy=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,10,0)).getValue())
|
||||
vz=App.Vector(c.getViewVolume().getMatrix().multVecMatrix(coin.SbVec3f(0,0,10)).getValue())
|
||||
(vx-vo).Length
|
||||
(vy-vo).Length
|
||||
(vz-vo).Length
|
||||
|
||||
# Projection
|
||||
vo.z=0
|
||||
vx.z=0
|
||||
vy.z=0
|
||||
vz.z=0
|
||||
|
||||
(vx-vo).Length
|
||||
(vy-vo).Length
|
||||
(vz-vo).Length
|
||||
\endcode
|
||||
|
||||
See also:
|
||||
http://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/graphics_6_2_ger_web.html#1
|
||||
http://www.mathematik.uni-marburg.de/~thormae/lectures/graphics1/code_v2/Axonometric/qt/Axonometric.cpp
|
||||
https://de.wikipedia.org/wiki/Arkussinus_und_Arkuskosinus
|
||||
*/
|
||||
|
||||
using Base::convertTo;
|
||||
using Base::Rotation;
|
||||
using Base::toRadians;
|
||||
using Base::Vector3d;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
Rotation buildAxonometricRotation(double alphaRad, double betaRad)
|
||||
{
|
||||
const auto p1 = Rotation(Vector3d::UnitX, toRadians<float>(90.0));
|
||||
const auto p2 = Rotation(Vector3d::UnitZ, alphaRad);
|
||||
const auto p3 = Rotation(p2.multVec(Vector3d::UnitX), betaRad);
|
||||
const auto p4 = p3 * p2 * p1;
|
||||
|
||||
return p4;
|
||||
}
|
||||
|
||||
// Returns a tuple of 2D lengths of X, Y, Z unit vectors after applying rotation
|
||||
std::array<double, 3> getProjectedLengths(const SbRotation& rot)
|
||||
{
|
||||
// Set up a simple view volume to test the projection of the unit vectors.
|
||||
// The actual values don't matter much, as we are only interested in the
|
||||
// relative lengths of the projected vectors.
|
||||
SbViewVolume volume;
|
||||
// left, right, bottom, top, near, far
|
||||
volume.ortho(-10, 10, -10, 10, -10, 10);
|
||||
|
||||
volume.rotateCamera(rot);
|
||||
const auto matrix = volume.getMatrix();
|
||||
|
||||
// Get the transformed unit vectors
|
||||
SbVec3f vo, vx, vy, vz;
|
||||
matrix.multVecMatrix(SbVec3f(0, 0, 0), vo);
|
||||
matrix.multVecMatrix(SbVec3f(10, 0, 0), vx);
|
||||
matrix.multVecMatrix(SbVec3f(0, 10, 0), vy);
|
||||
matrix.multVecMatrix(SbVec3f(0, 0, 10), vz);
|
||||
|
||||
// Project to XY plane by setting Z to 0
|
||||
vo[2] = 0;
|
||||
vx[2] = 0;
|
||||
vy[2] = 0;
|
||||
vz[2] = 0;
|
||||
|
||||
// Return the lengths of the projected vectors
|
||||
return {(vx - vo).length(), (vy - vo).length(), (vz - vo).length()};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CameraPrecalculatedQuaternions, testIsometric)
|
||||
{
|
||||
// Use the formula to get the isometric rotation
|
||||
double alpha = toRadians(45.0f);
|
||||
double beta = std::asin(-std::sqrt(1.0 / 3.0));
|
||||
|
||||
const Rotation actual = buildAxonometricRotation(alpha, beta);
|
||||
const Rotation expected = convertTo<Rotation>(Gui::Camera::isometric());
|
||||
|
||||
EXPECT_TRUE(actual.isSame(expected, 1e-6));
|
||||
}
|
||||
|
||||
TEST(CameraPrecalculatedQuaternions, testDimetric)
|
||||
{
|
||||
// Use the formula to get the dimetric rotation
|
||||
double alpha = std::asin(std::sqrt(1.0 / 8.0));
|
||||
double beta = -std::asin(1.0 / 3.0);
|
||||
|
||||
const Rotation actual = buildAxonometricRotation(alpha, beta);
|
||||
const Rotation expected = convertTo<Rotation>(Gui::Camera::dimetric());
|
||||
|
||||
EXPECT_TRUE(actual.isSame(expected, 1e-6));
|
||||
}
|
||||
|
||||
TEST(CameraPrecalculatedQuaternions, testTrimetric)
|
||||
{
|
||||
// Use the formula to get the trimetric rotation
|
||||
double alpha = toRadians(30.0);
|
||||
double beta = toRadians(-35.0);
|
||||
|
||||
const Rotation actual = buildAxonometricRotation(alpha, beta);
|
||||
const Rotation expected = convertTo<Rotation>(Gui::Camera::trimetric());
|
||||
|
||||
EXPECT_TRUE(actual.isSame(expected, 1e-6));
|
||||
}
|
||||
|
||||
TEST(CameraRotation, testIsometricProjection)
|
||||
{
|
||||
auto rot = Gui::Camera::isometric();
|
||||
auto lengths = getProjectedLengths(rot);
|
||||
|
||||
// In isometric, expect all lengths to be roughly equal
|
||||
EXPECT_NEAR(lengths[0], lengths[1], 1e-6); // X == Y
|
||||
EXPECT_NEAR(lengths[0], lengths[2], 1e-6); // X == Z
|
||||
EXPECT_NEAR(lengths[1], lengths[2], 1e-6); // Y == Z
|
||||
}
|
||||
|
||||
TEST(CameraRotation, testDimetricProjection)
|
||||
{
|
||||
const auto rot = Gui::Camera::dimetric();
|
||||
const auto lengths = getProjectedLengths(rot);
|
||||
|
||||
// In dimetric, expect two lengths to be roughly equal, one different
|
||||
const std::initializer_list<std::pair<double, double>> pairs = {
|
||||
{lengths[0], lengths[1]},
|
||||
{lengths[1], lengths[2]},
|
||||
{lengths[0], lengths[2]},
|
||||
};
|
||||
|
||||
constexpr double tolerance = 1e-6;
|
||||
const auto isSimilar = [&](std::pair<double, double> lengths) -> bool {
|
||||
return std::abs(lengths.first - lengths.second) < tolerance;
|
||||
};
|
||||
|
||||
unsigned similarCount = std::ranges::count_if(pairs, isSimilar);
|
||||
|
||||
EXPECT_EQ(similarCount, 1); // Exactly two are equal
|
||||
}
|
||||
|
||||
TEST(CameraRotation, testTrimetricProjection)
|
||||
{
|
||||
auto rot = Gui::Camera::trimetric();
|
||||
auto lengths = getProjectedLengths(rot);
|
||||
|
||||
// In trimetric, all should differ significantly
|
||||
EXPECT_GT(std::abs(lengths[0] - lengths[1]), 1e-3);
|
||||
EXPECT_GT(std::abs(lengths[1] - lengths[2]), 1e-3);
|
||||
EXPECT_GT(std::abs(lengths[0] - lengths[2]), 1e-3);
|
||||
}
|
||||
Reference in New Issue
Block a user