Skip to content
Open
19 changes: 19 additions & 0 deletions java/Camera.java
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,25 @@ public Camera pan(double right, double up) {
public Camera resetToBounds() {
return resetToBounds(0.9);
}
/**
* Get the world-space azimuth angle of the camera in degrees.
*
* @return azimuth angle in degrees
*/
public native double getWorldAzimuth();

/**
* Get the world-space elevation angle of the camera in degrees.
*
* @return elevation angle in degrees
*/
public native double getWorldElevation();

/**
* Get the distance between the camera position and its focal point.
*
* @return distance value
*/
public native double getDistance();
private long mNativeAddress;
}
17 changes: 16 additions & 1 deletion java/F3DCameraBindings.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ extern "C"
return GetEngine(env, self)->getWindow().getCamera().getViewAngle();
}

JNIEXPORT jdouble JAVA_BIND(Camera, getDistance)(JNIEnv* env, jobject self)
{
return GetEngine(env, self)->getWindow().getCamera().getDistance();
}

JNIEXPORT jdouble JAVA_BIND(Camera, getWorldAzimuth)(JNIEnv* env, jobject self)
{
return GetEngine(env, self)->getWindow().getCamera().getWorldAzimuth();
}

JNIEXPORT jdouble JAVA_BIND(Camera, getWorldElevation)(JNIEnv* env, jobject self)
{
return GetEngine(env, self)->getWindow().getCamera().getWorldElevation();
}

JNIEXPORT jobject JAVA_BIND(Camera, setState)(JNIEnv* env, jobject self, jobject state)
{
jclass stateClass = env->GetObjectClass(state);
Expand Down Expand Up @@ -193,4 +208,4 @@ extern "C"
GetEngine(env, self)->getWindow().getCamera().resetToBounds(zoomFactor);
return self;
}
}
}
6 changes: 5 additions & 1 deletion library/private/camera_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ class camera_impl : public camera
camera_state_t getState() const override;
void getState(camera_state_t& state) const override;

double getDistance() const override;
double getWorldAzimuth() const override;
double getWorldElevation() const override;

camera& dolly(double val) override;
camera& pan(double right, double up, double forward) override;
camera& zoom(double factor) override;
Expand Down Expand Up @@ -90,4 +94,4 @@ class camera_impl : public camera
};
}

#endif
#endif
9 changes: 8 additions & 1 deletion library/public/camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ class F3D_EXPORT camera
/** Get the complete state of the camera into the provided arg */
virtual void getState(camera_state_t& state) const = 0;

/** Return the distance from the camera to the focal point */
[[nodiscard]] virtual double getDistance() const = 0;
/** Return the azimuth of the camera in world coordinates */
[[nodiscard]] virtual double getWorldAzimuth() const = 0;
/** Return the elevation of the camera in world coordinates */
[[nodiscard]] virtual double getWorldElevation() const = 0;

///@}

///@{ @name Manipulation
Expand Down Expand Up @@ -123,4 +130,4 @@ class F3D_EXPORT camera
};
}

#endif
#endif
46 changes: 46 additions & 0 deletions library/src/camera_impl.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include <vtkCamera.h>
#include <vtkMatrix4x4.h>
#include <vtkPlane.h>
#include <vtkRenderWindow.h>
#include <vtkRenderer.h>
#include <vtkVersion.h>
Expand Down Expand Up @@ -346,4 +347,49 @@ bool camera_impl::GetSuccessfullyReset() const
return this->Internals->SuccessfullyReset;
}

//----------------------------------------------------------------------------
double camera_impl::getDistance() const
{
return this->GetVTKCamera()->GetDistance();
}

//----------------------------------------------------------------------------
double camera_impl::getWorldAzimuth() const
{
vtkRenderer* ren = this->Internals->VTKRenderer;
const double* up = ren->GetEnvironmentUp();

double projectedView[3];
vtkPlane::ProjectVector(this->GetVTKCamera()->GetDirectionOfProjection(),
this->getFocalPoint().data(), up, projectedView);

static constexpr double EPS = 128 * std::numeric_limits<double>::epsilon();
if (vtkMath::Norm(projectedView) < EPS)
{
return 0.0;
}

const double angleRad =
vtkMath::SignedAngleBetweenVectors(ren->GetEnvironmentRight(), projectedView, up);

return vtkMath::DegreesFromRadians(angleRad) - 90.0;
}

//----------------------------------------------------------------------------
double camera_impl::getWorldElevation() const
{
double* view = this->GetVTKCamera()->GetDirectionOfProjection();

static constexpr double EPS = 128 * std::numeric_limits<double>::epsilon();
if (vtkMath::Norm(view) < EPS)
{
return 0.0;
}

vtkRenderer* ren = this->Internals->VTKRenderer;
const double angleRad = vtkMath::AngleBetweenVectors(ren->GetEnvironmentUp(), view);

return vtkMath::DegreesFromRadians(angleRad) - 90.0;
}

};
176 changes: 31 additions & 145 deletions library/testing/TestSDKCamera.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@
#include <camera.h>
#include <engine.h>
#include <log.h>
#include <options.h>
#include <window.h>

#include <cmath>
#include <iomanip>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

int TestSDKCamera([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
{
Expand All @@ -20,148 +23,31 @@ int TestSDKCamera([[maybe_unused]] int argc, [[maybe_unused]] char* argv[])
f3d::window& win = eng.getWindow();
f3d::camera& cam = win.getCamera();

// check coordinates conversion
f3d::point3_t point = { 0.1, 0.1, 0.1 };
f3d::point3_t pointDC = win.getDisplayFromWorld(point);
test("coordinates conversion", point, approx(win.getWorldFromDisplay(pointDC)));

// Test position
f3d::point3_t testPos = { 0., 0., 10. };
f3d::point3_t pos = cam.setPosition(testPos).getPosition();
test("set/get position", pos, testPos);

// Test focal point
f3d::point3_t testFoc = { 0., 0., -1. };
f3d::point3_t foc = cam.setFocalPoint(testFoc).getFocalPoint();
test("set/get focal point", foc, testFoc);

// Test view up
f3d::vector3_t testUp = { 1., 0., 0. };
f3d::vector3_t up = cam.setViewUp(testUp).getViewUp();
test("set/get view up", up, testUp);

// Test view angle
f3d::angle_deg_t testAngle = 20;
f3d::angle_deg_t angle = cam.setViewAngle(testAngle).getViewAngle();
test("set/get view angle", angle, testAngle);

// Test azimuth
cam.azimuth(90);
f3d::point3_t expectedPos = { 0., -11., -1. };
f3d::point3_t expectedFoc = { 0., 0., -1. };
f3d::vector3_t expectedUp = { 1., 0., 0. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("azimuth method position", pos, approx(expectedPos));
test("azimuth method focal point", foc, approx(expectedFoc));
test("azimuth method up", up, approx(expectedUp));

// Test roll
cam.roll(90);
expectedUp = { 0., 0., -1. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("roll method position", pos, approx(expectedPos));
test("roll method focal point", foc, approx(expectedFoc));
test("roll method up", up, approx(expectedUp));

// Test yaw
cam.yaw(90);
expectedFoc = { 11., -11., -1. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("yaw method position", pos, approx(expectedPos));
test("yaw method focal point", foc, approx(expectedFoc));
test("yaw method up", up, approx(expectedUp));

// Test elevation
cam.elevation(90);
expectedPos = { 11., -11., -12. };
expectedUp = { 1., 0., 0. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("elevation method position", pos, approx(expectedPos));
test("elevation method focal point", foc, approx(expectedFoc));
test("elevation method up", up, approx(expectedUp));

// Test pitch
cam.pitch(90);
expectedFoc = { 22., -11., -12. };
expectedUp = { 0., 0., -1. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("pitch method position", pos, approx(expectedPos));
test("pitch method focal point", foc, approx(expectedFoc));
test("pitch method up", up, approx(expectedUp));

// Test dolly
cam.dolly(10);
expectedPos = { 20.9, -11., -12. };
pos = cam.getPosition();
foc = cam.getFocalPoint();
up = cam.getViewUp();
test("dolly method position", pos, approx(expectedPos));
test("dolly method focal point", foc, approx(expectedFoc));
test("dolly method up", up, approx(expectedUp));

cam.setPosition({ 1, 2, 3 });
cam.setFocalPoint({ 1, 2, 13 });
cam.setViewUp({ 0, 1, 0 });
cam.pan(1, 2);
test("pos after pan", cam.getPosition(), { 0, 4, 3 });
test("foc after pan", cam.getFocalPoint(), { 0, 4, 13 });
test("up after pan", cam.getViewUp(), { 0, 1, 0 });

cam.setPosition({ 1, 2, 3 });
cam.setFocalPoint({ 1, -2, 3 });
cam.setViewUp({ 0, 0, 1 });
cam.pan(3, 4, 5);
test("pos after pan", cam.getPosition(), { -2, -3, 7 });
test("foc after pan", cam.getFocalPoint(), { -2, -7, 7 });
test("up after pan", cam.getViewUp(), { 0, 0, 1 });

cam.setPosition({ 1, 2, 3 });
cam.setFocalPoint({ 1, 2, 13 });
cam.setViewUp({ 0, 1, 0 });
cam.setViewAngle(25);
cam.zoom(1.5);
test("pos after zoom", cam.getPosition(), { 1, 2, 3 });
test("foc after zoom", cam.getFocalPoint(), { 1, 2, 13 });
test("up after zoom", cam.getViewUp(), { 0, 1, 0 });
test("angle after zoom", cam.getViewAngle(), 25 / 1.5);

cam.setPosition({ 1, 0, 0 });
cam.setFocalPoint({ 0, 0, 0 });
cam.setViewUp({ 1, 0, 0 });
test("pos when cross product of pos->foc and up is 0 - test 1", cam.getPosition(), { 1, 0, 0 });
test("foc when cross product of pos->foc and up is 0 - test 1", cam.getFocalPoint(), { 0, 0, 0 });
test("up when cross product of pos->foc and up is 0 - test 1", cam.getViewUp(), { 0, 1, 0 });

cam.setPosition({ 0, 1, 0 });
cam.setFocalPoint({ 0, 0, 0 });
cam.setViewUp({ 0, 1, 0 });
test("pos when cross product of pos->foc and up is 0 - test 2", cam.getPosition(), { 0, 1, 0 });
test("foc when cross product of pos->foc and up is 0 - test 2", cam.getFocalPoint(), { 0, 0, 0 });
test("up when cross product of pos->foc and up is 0 - test 2", cam.getViewUp(), { 1, 0, 0 });

cam.setPosition({ 0, 0, 1 });
cam.setFocalPoint({ 0, 0, 0 });
cam.setViewUp({ 0, 0, 1 });
test("pos when cross product of pos->foc and up is 0 - test 3", cam.getPosition(), { 0, 0, 1 });
test("foc when cross product of pos->foc and up is 0 - test 3", cam.getFocalPoint(), { 0, 0, 0 });
test("up when cross product of pos->foc and up is 0 - test 3", cam.getViewUp(), { 1, 0, 0 });

cam.setPosition({ 5, 0, 0 });
cam.setFocalPoint({ 1, 0, 0 });
cam.setViewUp({ 1, 0, 0 });
test("pos when cross product of pos->foc and up is 0 - test 4", cam.getPosition(), { 5, 0, 0 });
test("foc when cross product of pos->foc and up is 0 - test 4", cam.getFocalPoint(), { 1, 0, 0 });
test("up when cross product of pos->foc and up is 0 - test 4", cam.getViewUp(), { 0, 1, 0 });
// Basic tests
test("set/get position", cam.setPosition({ 0., 0., 10. }).getPosition(), { 0., 0., 10. });
test("set/get focal point", cam.setFocalPoint({ 0., 0., -1. }).getFocalPoint(), { 0., 0., -1. });

// Getter Equivalence tests
const std::vector<f3d::direction_t> up_directions = { { 0, 0, 1 }, { 0, 1, 0 }, { 1, 0, 0 } };
const std::vector<std::pair<double, double>> az_el = { { 0, 0 }, { 12, 34 }, { -12, -34 } };

for (const auto& up : up_directions)
{
for (const auto& [a, e] : az_el)
{
// Reset camera state to a known identity to avoid rotational drift
cam.setPosition({ 0, 0, 10 }).setFocalPoint({ 0, 0, 0 }).setViewUp(up);
cam.azimuth(a).elevation(e);

std::string title = " (up=" + f3d::options::format(up) + ", a=" + std::to_string(static_cast<int>(a)) + ")";

// We use a tolerance of 0.5 degrees. This is required because VTK internally
// re-normalizes the camera's orthonormal basis (Right, Up, Forward) after
// floating-point matrix rotations.
test("get azimuth" + title, cam.getWorldAzimuth(), approx(a, 0.5));
test("get elevation" + title, cam.getWorldElevation(), approx(e, 0.5));
}
}

return test.result();
}
}
Loading