Skip to content

Commit

Permalink
Simplify and fix Projection's getter functions
Browse files Browse the repository at this point in the history
  • Loading branch information
Flarkk committed Dec 9, 2024
1 parent 47bc374 commit 3c82199
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 101 deletions.
115 changes: 16 additions & 99 deletions core/math/projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -402,82 +402,21 @@ void Projection::set_frustum(real_t p_size, real_t p_aspect, Vector2 p_offset, r
}

real_t Projection::get_z_far() const {
const real_t *matrix = (const real_t *)columns;
Plane new_plane = Plane(matrix[3] - matrix[2],
matrix[7] - matrix[6],
matrix[11] - matrix[10],
matrix[15] - matrix[14]);

new_plane.normalize();

return new_plane.d;
return -(columns[3][3] - columns[3][2]) / (columns[2][2] - columns[2][3]);
}

real_t Projection::get_z_near() const {
const real_t *matrix = (const real_t *)columns;
Plane new_plane = Plane(matrix[3] + matrix[2],
matrix[7] + matrix[6],
matrix[11] + matrix[10],
-matrix[15] - matrix[14]);

new_plane.normalize();
return new_plane.d;
return (columns[3][3] + columns[3][2]) / (columns[2][2] + columns[2][3]);
}

Vector2 Projection::get_viewport_half_extents() const {
const real_t *matrix = (const real_t *)columns;
///////--- Near Plane ---///////
Plane near_plane = Plane(matrix[3] + matrix[2],
matrix[7] + matrix[6],
matrix[11] + matrix[10],
-matrix[15] - matrix[14]);
near_plane.normalize();

///////--- Right Plane ---///////
Plane right_plane = Plane(matrix[3] - matrix[0],
matrix[7] - matrix[4],
matrix[11] - matrix[8],
-matrix[15] + matrix[12]);
right_plane.normalize();

Plane top_plane = Plane(matrix[3] - matrix[1],
matrix[7] - matrix[5],
matrix[11] - matrix[9],
-matrix[15] + matrix[13]);
top_plane.normalize();

Vector3 res;
near_plane.intersect_3(right_plane, top_plane, &res);

return Vector2(res.x, res.y);
real_t w = -get_z_near() * columns[2][3] + columns[3][3];
return Vector2(w / columns[0][0], w / columns[1][1]);
}

Vector2 Projection::get_far_plane_half_extents() const {
const real_t *matrix = (const real_t *)columns;
///////--- Far Plane ---///////
Plane far_plane = Plane(matrix[3] - matrix[2],
matrix[7] - matrix[6],
matrix[11] - matrix[10],
-matrix[15] + matrix[14]);
far_plane.normalize();

///////--- Right Plane ---///////
Plane right_plane = Plane(matrix[3] - matrix[0],
matrix[7] - matrix[4],
matrix[11] - matrix[8],
-matrix[15] + matrix[12]);
right_plane.normalize();

Plane top_plane = Plane(matrix[3] - matrix[1],
matrix[7] - matrix[5],
matrix[11] - matrix[9],
-matrix[15] + matrix[13]);
top_plane.normalize();

Vector3 res;
far_plane.intersect_3(right_plane, top_plane, &res);

return Vector2(res.x, res.y);
real_t w = -get_z_far() * columns[2][3] + columns[3][3];
return Vector2(w / columns[0][0], w / columns[1][1]);
}

bool Projection::get_endpoints(const Transform3D &p_transform, Vector3 *p_8points) const {
Expand Down Expand Up @@ -919,53 +858,31 @@ Projection::operator String() const {
}

real_t Projection::get_aspect() const {
Vector2 vp_he = get_viewport_half_extents();
return vp_he.x / vp_he.y;
return columns[1][1] / columns[0][0];
}

int Projection::get_pixels_per_meter(int p_for_pixel_width) const {
Vector3 result = xform(Vector3(1, 0, -1));

return int((result.x * 0.5 + 0.5) * p_for_pixel_width);
real_t width = 2.0f * (-get_z_near() * columns[2][3] + columns[3][3]) / columns[0][0];
return int(p_for_pixel_width / width); // The cast to int should be removed at some point (kept for compatibility for now)
}

bool Projection::is_orthogonal() const {
return columns[3][3] == 1.0;
}

real_t Projection::get_fov() const {
const real_t *matrix = (const real_t *)columns;

Plane right_plane = Plane(matrix[3] - matrix[0],
matrix[7] - matrix[4],
matrix[11] - matrix[8],
-matrix[15] + matrix[12]);
right_plane.normalize();

if ((matrix[8] == 0) && (matrix[9] == 0)) {
return Math::rad_to_deg(Math::acos(Math::abs(right_plane.normal.x))) * 2.0;
if (columns[2][0] == 0) {
return Math::rad_to_deg(2 * Math::atan(1.0 / columns[0][0]));
} else {
// our frustum is asymmetrical need to calculate the left planes angle separately..
Plane left_plane = Plane(matrix[3] + matrix[0],
matrix[7] + matrix[4],
matrix[11] + matrix[8],
matrix[15] + matrix[12]);
left_plane.normalize();

return Math::rad_to_deg(Math::acos(Math::abs(left_plane.normal.x))) + Math::rad_to_deg(Math::acos(Math::abs(right_plane.normal.x)));
// The frustum is asymmetrical so we need to calculate the left and right angles separately.
real_t right = (columns[2][0] + 1) / columns[0][0];
real_t left = (columns[2][0] - 1) / columns[0][0];
return Math::rad_to_deg(Math::atan(right) - Math::atan(left));
}
}

real_t Projection::get_lod_multiplier() const {
if (is_orthogonal()) {
return get_viewport_half_extents().x;
} else {
const real_t zn = get_z_near();
const real_t width = get_viewport_half_extents().x * 2.0f;
return 1.0f / (zn / width);
}

// Usage is lod_size / (lod_distance * multiplier) < threshold
return 2.0f / columns[0][0];
}

void Projection::make_scale(const Vector3 &p_scale) {
Expand Down
2 changes: 1 addition & 1 deletion doc/classes/Projection.xml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
<return type="int" />
<param index="0" name="for_pixel_width" type="int" />
<description>
Returns the number of pixels with the given pixel width displayed per meter, after this [Projection] is applied.
Returns [param for_pixel_width] divided by the viewport's width measured in meters on the near plane, after this [Projection] is applied.
</description>
</method>
<method name="get_projection_plane" qualifiers="const">
Expand Down
189 changes: 188 additions & 1 deletion tests/core/math/test_projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,78 @@ TEST_CASE("[Projection] Perspective values extraction") {
CHECK(zfar == doctest::Approx(50));
CHECK(aspect == doctest::Approx(0.5));
CHECK(fov == doctest::Approx(90));

persp.set_perspective(38, 1.3, 0.2, 8, false);

znear = persp.get_z_near();
zfar = persp.get_z_far();
aspect = persp.get_aspect();
fov = persp.get_fov();

CHECK(znear == doctest::Approx(0.2));
CHECK(zfar == doctest::Approx(8));
CHECK(aspect == doctest::Approx(1.3));
CHECK(fov == doctest::Approx(Projection::get_fovy(38, 1.3)));

persp.set_perspective(47, 2.5, 0.9, 14, true);

znear = persp.get_z_near();
zfar = persp.get_z_far();
aspect = persp.get_aspect();
fov = persp.get_fov();

CHECK(znear == doctest::Approx(0.9));
CHECK(zfar == doctest::Approx(14));
CHECK(aspect == doctest::Approx(2.5));
CHECK(fov == doctest::Approx(47));
}

TEST_CASE("[Projection] Frustum values extraction") {
Projection frustum = Projection::create_frustum_aspect(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, true);

double znear = frustum.get_z_near();
double zfar = frustum.get_z_far();
double aspect = frustum.get_aspect();
double fov = frustum.get_fov();

CHECK(znear == doctest::Approx(0.5));
CHECK(zfar == doctest::Approx(50));
CHECK(aspect == doctest::Approx(4.0 / 3.0));
CHECK(fov == doctest::Approx(Math::rad_to_deg(Math::atan(2.0))));

frustum.set_frustum(2.0, 1.5, Vector2(-0.5, 2), 2, 12, false);

znear = frustum.get_z_near();
zfar = frustum.get_z_far();
aspect = frustum.get_aspect();
fov = frustum.get_fov();

CHECK(znear == doctest::Approx(2));
CHECK(zfar == doctest::Approx(12));
CHECK(aspect == doctest::Approx(1.5));
CHECK(fov == doctest::Approx(Math::rad_to_deg(Math::atan(1.0) + Math::atan(0.5))));
}

TEST_CASE("[Projection] Orthographic values extraction") {
Projection ortho = Projection::create_orthogonal(-2, 3, -0.5, 1.5, 1.2, 15);

double znear = ortho.get_z_near();
double zfar = ortho.get_z_far();
double aspect = ortho.get_aspect();

CHECK(znear == doctest::Approx(1.2));
CHECK(zfar == doctest::Approx(15));
CHECK(aspect == doctest::Approx(2.5));

ortho.set_orthogonal(-7, 2, 2.5, 5.5, 0.5, 6);

znear = ortho.get_z_near();
zfar = ortho.get_z_far();
aspect = ortho.get_aspect();

CHECK(znear == doctest::Approx(0.5));
CHECK(zfar == doctest::Approx(6));
CHECK(aspect == doctest::Approx(3));
}

TEST_CASE("[Projection] Orthographic check") {
Expand Down Expand Up @@ -487,16 +559,48 @@ TEST_CASE("[Projection] Planes extraction") {
CHECK(plane_array[Projection::PLANE_BOTTOM].normalized().is_equal_approx(planes[Projection::PLANE_BOTTOM].normalized()));
}

TEST_CASE("[Projection] Half extents") {
TEST_CASE("[Projection] Perspective Half extents") {
constexpr real_t sqrt3 = 1.7320508;
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
Vector2 ne = persp.get_viewport_half_extents();
Vector2 fe = persp.get_far_plane_half_extents();

CHECK(ne.is_equal_approx(Vector2(1, 1) * 1));
CHECK(fe.is_equal_approx(Vector2(1, 1) * 40));

persp.set_perspective(120, sqrt3, 0.8, 10, true);
ne = persp.get_viewport_half_extents();
fe = persp.get_far_plane_half_extents();

CHECK(ne.is_equal_approx(Vector2(sqrt3, 1.0) * 0.8));
CHECK(fe.is_equal_approx(Vector2(sqrt3, 1.0) * 10));

persp.set_perspective(60, 1.2, 0.5, 15, false);
ne = persp.get_viewport_half_extents();
fe = persp.get_far_plane_half_extents();

CHECK(ne.is_equal_approx(Vector2(sqrt3 / 3 * 1.2, sqrt3 / 3) * 0.5));
CHECK(fe.is_equal_approx(Vector2(sqrt3 / 3 * 1.2, sqrt3 / 3) * 15));
}

TEST_CASE("[Projection] Orthographic Half extents") {
Projection ortho = Projection::create_orthogonal(-3, 3, -1.5, 1.5, 1.2, 15);
Vector2 ne = ortho.get_viewport_half_extents();
Vector2 fe = ortho.get_far_plane_half_extents();

CHECK(ne.is_equal_approx(Vector2(3, 1.5)));
CHECK(fe.is_equal_approx(Vector2(3, 1.5)));

ortho.set_orthogonal(-7, 7, -2.5, 2.5, 0.5, 6);
ne = ortho.get_viewport_half_extents();
fe = ortho.get_far_plane_half_extents();

CHECK(ne.is_equal_approx(Vector2(7, 2.5)));
CHECK(fe.is_equal_approx(Vector2(7, 2.5)));
}

TEST_CASE("[Projection] Endpoints") {
constexpr real_t sqrt3 = 1.7320508;
Projection persp = Projection::create_perspective(90, 1, 1, 40, false);
Vector3 ep[8];
persp.get_endpoints(Transform3D(), ep);
Expand All @@ -509,8 +613,91 @@ TEST_CASE("[Projection] Endpoints") {
CHECK(ep[5].is_equal_approx(Vector3(-1, -1, -1) * 1));
CHECK(ep[6].is_equal_approx(Vector3(1, 1, -1) * 1));
CHECK(ep[7].is_equal_approx(Vector3(1, -1, -1) * 1));

persp.set_perspective(120, sqrt3, 0.8, 10, true);
persp.get_endpoints(Transform3D(), ep);

CHECK(ep[0].is_equal_approx(Vector3(-sqrt3, 1, -1) * 10));
CHECK(ep[1].is_equal_approx(Vector3(-sqrt3, -1, -1) * 10));
CHECK(ep[2].is_equal_approx(Vector3(sqrt3, 1, -1) * 10));
CHECK(ep[3].is_equal_approx(Vector3(sqrt3, -1, -1) * 10));
CHECK(ep[4].is_equal_approx(Vector3(-sqrt3, 1, -1) * 0.8));
CHECK(ep[5].is_equal_approx(Vector3(-sqrt3, -1, -1) * 0.8));
CHECK(ep[6].is_equal_approx(Vector3(sqrt3, 1, -1) * 0.8));
CHECK(ep[7].is_equal_approx(Vector3(sqrt3, -1, -1) * 0.8));

persp.set_perspective(60, 1.2, 0.5, 15, false);
persp.get_endpoints(Transform3D(), ep);

CHECK(ep[0].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 15));
CHECK(ep[1].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 15));
CHECK(ep[2].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 15));
CHECK(ep[3].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 15));
CHECK(ep[4].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 0.5));
CHECK(ep[5].is_equal_approx(Vector3(-sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 0.5));
CHECK(ep[6].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, sqrt3 / 3, -1) * 0.5));
CHECK(ep[7].is_equal_approx(Vector3(sqrt3 / 3 * 1.2, -sqrt3 / 3, -1) * 0.5));
}

TEST_CASE("[Projection] LOD multiplier") {
constexpr real_t sqrt3 = 1.7320508;
Projection proj;
real_t multiplier;

proj.set_perspective(60, 1, 1, 40, false);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(2 * sqrt3 / 3));

proj.set_perspective(120, 1.5, 0.5, 20, false);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(3 * sqrt3));

proj.set_orthogonal(15, 20, 10, 12, 5, 15);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(5));

proj.set_orthogonal(-5, 15, -8, 10, 1.5, 10);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(20));

proj.set_frustum(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, false);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(8.0 / 3.0));

proj.set_frustum(2.0, 1.2, Vector2(-0.1, 0.8), 1, 10, true);
multiplier = proj.get_lod_multiplier();
CHECK(multiplier == doctest::Approx(2));
}

TEST_CASE("[Projection] Pixels per meter") {
constexpr real_t sqrt3 = 1.7320508;
Projection proj;
int ppm;

proj.set_perspective(60, 1, 1, 40, false);
ppm = proj.get_pixels_per_meter(1024);
CHECK(ppm == int(1536.0f / sqrt3));

proj.set_perspective(120, 1.5, 0.5, 20, false);
ppm = proj.get_pixels_per_meter(1200);
CHECK(ppm == int(800.0f / sqrt3));

proj.set_orthogonal(15, 20, 10, 12, 5, 15);
ppm = proj.get_pixels_per_meter(500);
CHECK(ppm == 100);

proj.set_orthogonal(-5, 15, -8, 10, 1.5, 10);
ppm = proj.get_pixels_per_meter(640);
CHECK(ppm == 32);

proj.set_frustum(1.0, 4.0 / 3.0, Vector2(0.5, -0.25), 0.5, 50, false);
ppm = proj.get_pixels_per_meter(2048);
CHECK(ppm == 1536);

proj.set_frustum(2.0, 1.2, Vector2(-0.1, 0.8), 1, 10, true);
ppm = proj.get_pixels_per_meter(800);
CHECK(ppm == 400);
}
} //namespace TestProjection

#endif // TEST_PROJECTION_H

0 comments on commit 3c82199

Please sign in to comment.