You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Merge pull request #100209 from Flarkk/simplify_fix_projection
Simplify and fix `Projection`'s getter functions
This commit is contained in:
@@ -402,82 +402,35 @@ 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;
|
||||
// NOTE: This assumes z-facing near and far planes, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - near and far planes are z-facing (i.e. columns[0][2] and [1][2] == 0)
|
||||
return (columns[3][3] - columns[3][2]) / (columns[2][3] - columns[2][2]);
|
||||
}
|
||||
|
||||
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;
|
||||
// NOTE: This assumes z-facing near and far planes, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - near and far planes are z-facing (i.e. columns[0][2] and [1][2] == 0)
|
||||
return (columns[3][3] + columns[3][2]) / (columns[2][3] + columns[2][2]);
|
||||
}
|
||||
|
||||
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);
|
||||
// NOTE: This assumes a symmetrical frustum, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
// - there is no offset / skew (i.e. columns[2][0] == columns[2][1] == 0)
|
||||
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);
|
||||
// NOTE: This assumes a symmetrical frustum, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
// - there is no offset / skew (i.e. columns[2][0] == columns[2][1] == 0)
|
||||
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 {
|
||||
@@ -919,53 +872,45 @@ Projection::operator String() const {
|
||||
}
|
||||
|
||||
real_t Projection::get_aspect() const {
|
||||
Vector2 vp_he = get_viewport_half_extents();
|
||||
return vp_he.x / vp_he.y;
|
||||
// NOTE: This assumes a rectangular projection plane, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
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);
|
||||
// NOTE: This assumes a rectangular projection plane, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
real_t width = 2 * (-get_z_near() * columns[2][3] + columns[3][3]) / columns[0][0];
|
||||
return p_for_pixel_width / width; // Note : return type should be real_t (kept as int for compatibility for now).
|
||||
}
|
||||
|
||||
bool Projection::is_orthogonal() const {
|
||||
return columns[3][3] == 1.0;
|
||||
// NOTE: This assumes that the matrix is a projection across z-axis
|
||||
// i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0
|
||||
return columns[2][3] == 0.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;
|
||||
// NOTE: This assumes a rectangular projection plane, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
if (columns[2][0] == 0) {
|
||||
return Math::rad_to_deg(2 * Math::atan2(1, 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 = Math::atan2(columns[2][0] + 1, columns[0][0]);
|
||||
real_t left = Math::atan2(columns[2][0] - 1, columns[0][0]);
|
||||
return Math::rad_to_deg(right - 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
|
||||
// NOTE: This assumes a rectangular projection plane, i.e. that :
|
||||
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)
|
||||
// - the projection plane is rectangular (i.e. columns[0][2] and [1][2] == 0 if columns[2][3] != 0)
|
||||
return 2 / columns[0][0];
|
||||
}
|
||||
|
||||
void Projection::make_scale(const Vector3 &p_scale) {
|
||||
|
||||
@@ -207,7 +207,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">
|
||||
|
||||
@@ -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") {
|
||||
@@ -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);
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user