diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 43a94f91af..b2cf47945b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1053,6 +1053,7 @@ set( FASTMATH_PCH_SOURCES r_swrenderer2.cpp r_poly.cpp r_poly_triangle.cpp + r_poly_intersection.cpp r_3dfloors.cpp r_bsp.cpp r_draw.cpp diff --git a/src/r_poly.cpp b/src/r_poly.cpp index a4141b94c1..e8a34544b7 100644 --- a/src/r_poly.cpp +++ b/src/r_poly.cpp @@ -75,6 +75,8 @@ void RenderPolyBsp::Render() // Y shearing like the Doom renderer: //worldToClip = TriMatrix::viewToClip() * TriMatrix::worldToView(); + frustumPlanes = FrustumPlanes(worldToClip); + // Cull front to back if (numnodes == 0) { @@ -1023,28 +1025,20 @@ int RenderPolyBsp::PointOnSide(const DVector2 &pos, const node_t *node) bool RenderPolyBsp::CheckBBox(float *bspcoord) { - static const int checkcoord[12][4] = - { - { 3,0,2,1 }, - { 3,0,2,0 }, - { 3,1,2,0 }, - { 0 }, - { 2,0,2,1 }, - { 0,0,0,0 }, - { 3,1,3,0 }, - { 0 }, - { 2,0,3,1 }, - { 2,1,3,1 }, - { 2,1,3,0 } - }; + // Start using a quick frustum AABB test: + + AxisAlignedBoundingBox aabb(Vec3f(bspcoord[BOXLEFT], bspcoord[BOXBOTTOM], -1000.0f), Vec3f(bspcoord[BOXRIGHT], bspcoord[BOXTOP], 1000.0f)); + auto result = IntersectionTest::frustum_aabb(frustumPlanes, aabb); + if (result == IntersectionTest::outside) + return false; + + // Occlusion test using solid segments (which seems to be quite broken, actually): int boxx; int boxy; int boxpos; double x1, y1, x2, y2; - double rx1, ry1, rx2, ry2; - int sx1, sx2; // Find the corners of the box // that define the edges from current viewpoint. @@ -1066,64 +1060,31 @@ bool RenderPolyBsp::CheckBBox(float *bspcoord) if (boxpos == 5) return true; - x1 = bspcoord[checkcoord[boxpos][0]] - ViewPos.X; - y1 = bspcoord[checkcoord[boxpos][1]] - ViewPos.Y; - x2 = bspcoord[checkcoord[boxpos][2]] - ViewPos.X; - y2 = bspcoord[checkcoord[boxpos][3]] - ViewPos.Y; + static const int checkcoord[12][4] = + { + { 3,0,2,1 }, + { 3,0,2,0 }, + { 3,1,2,0 }, + { 0 }, + { 2,0,2,1 }, + { 0,0,0,0 }, + { 3,1,3,0 }, + { 0 }, + { 2,0,3,1 }, + { 2,1,3,1 }, + { 2,1,3,0 } + }; - // check clip list for an open space + x1 = bspcoord[checkcoord[boxpos][0]]; + y1 = bspcoord[checkcoord[boxpos][1]]; + x2 = bspcoord[checkcoord[boxpos][2]]; + y2 = bspcoord[checkcoord[boxpos][3]]; - // Sitting on a line? - if (y1 * (x1 - x2) + x1 * (y2 - y1) >= -EQUAL_EPSILON) + int sx1, sx2; + if (GetSegmentRangeForLine(x1, y1, x2, y2, sx1, sx2)) + return !IsSegmentCulled(sx1, sx2); + else return true; - - rx1 = x1 * ViewSin - y1 * ViewCos; - rx2 = x2 * ViewSin - y2 * ViewCos; - ry1 = x1 * ViewTanCos + y1 * ViewTanSin; - ry2 = x2 * ViewTanCos + y2 * ViewTanSin; - - /*if (MirrorFlags & RF_XFLIP) - { - double t = -rx1; - rx1 = -rx2; - rx2 = t; - swapvalues(ry1, ry2); - }*/ - - if (rx1 >= -ry1) - { - if (rx1 > ry1) return false; // left edge is off the right side - if (ry1 == 0) return false; - sx1 = xs_RoundToInt(CenterX + rx1 * CenterX / ry1); - } - else - { - if (rx2 < -ry2) return false; // wall is off the left side - if (rx1 - rx2 - ry2 + ry1 == 0) return false; // wall does not intersect view volume - sx1 = 0; - } - - if (rx2 <= ry2) - { - if (rx2 < -ry2) return false; // right edge is off the left side - if (ry2 == 0) return false; - sx2 = xs_RoundToInt(CenterX + rx2 * CenterX / ry2); - } - else - { - if (rx1 > ry1) return false; // wall is off the right side - if (ry2 - ry1 - rx2 + rx1 == 0) return false; // wall does not intersect view volume - sx2 = viewwidth; - } - - // Find the first clippost that touches the source post - // (adjacent pixels are touching). - - // Does not cross a pixel. - if (sx2 <= sx1) - return false; - - return !IsSegmentCulled(sx1, sx2); } bool RenderPolyBsp::GetSegmentRangeForLine(double x1, double y1, double x2, double y2, int &sx1, int &sx2) const diff --git a/src/r_poly.h b/src/r_poly.h index a9ae5e33df..f1718f2661 100644 --- a/src/r_poly.h +++ b/src/r_poly.h @@ -30,6 +30,7 @@ #include "r_utility.h" #include "r_main.h" #include "r_poly_triangle.h" +#include "r_poly_intersection.h" // DScreen accelerated sprite to be rendered class PolyScreenSprite @@ -143,6 +144,7 @@ private: double MinFloorHeight = 0.0; TriMatrix worldToClip; + FrustumPlanes frustumPlanes; std::vector SectorSpriteRanges; std::vector SortedSprites; diff --git a/src/r_poly_intersection.cpp b/src/r_poly_intersection.cpp new file mode 100644 index 0000000000..5e7ad374b7 --- /dev/null +++ b/src/r_poly_intersection.cpp @@ -0,0 +1,235 @@ +/* +** Various 3D intersection tests +** Copyright (c) 1997-2015 The UICore Team +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#include +#include "templates.h" +#include "doomdef.h" +#include "r_poly_intersection.h" + +IntersectionTest::Result IntersectionTest::plane_aabb(const Vec4f &plane, const AxisAlignedBoundingBox &aabb) +{ + Vec3f center = aabb.center(); + Vec3f extents = aabb.extents(); + float e = extents.x * std::abs(plane.x) + extents.y * std::abs(plane.y) + extents.z * std::abs(plane.z); + float s = center.x * plane.x + center.y * plane.y + center.z * plane.z + plane.w; + if (s - e > 0) + return inside; + else if (s + e < 0) + return outside; + else + return intersecting; +} + +IntersectionTest::Result IntersectionTest::plane_obb(const Vec4f &plane, const OrientedBoundingBox &obb) +{ + Vec3f n(plane); + float d = plane.w; + float e = obb.extents.x * std::abs(Vec3f::dot(obb.axis_x, n)) + obb.extents.y * std::abs(Vec3f::dot(obb.axis_y, n)) + obb.extents.z * std::abs(Vec3f::dot(obb.axis_z, n)); + float s = Vec3f::dot(obb.center, n) + d; + if (s - e > 0) + return inside; + else if (s + e < 0) + return outside; + else + return intersecting; +} + +IntersectionTest::OverlapResult IntersectionTest::sphere(const Vec3f ¢er1, float radius1, const Vec3f ¢er2, float radius2) +{ + Vec3f h = center1 - center2; + float square_distance = Vec3f::dot(h, h); + float radius_sum = radius1 + radius2; + if (square_distance > radius_sum * radius_sum) + return disjoint; + else + return overlap; +} + +IntersectionTest::OverlapResult IntersectionTest::sphere_aabb(const Vec3f ¢er, float radius, const AxisAlignedBoundingBox &aabb) +{ + Vec3f a = aabb.aabb_min - center; + Vec3f b = center - aabb.aabb_max; + a.x = std::max(a.x, 0.0f); + a.y = std::max(a.y, 0.0f); + a.z = std::max(a.z, 0.0f); + b.x = std::max(b.x, 0.0f); + b.y = std::max(b.y, 0.0f); + b.z = std::max(b.z, 0.0f); + Vec3f e = a + b; + float d = Vec3f::dot(e, e); + if (d > radius * radius) + return disjoint; + else + return overlap; +} + +IntersectionTest::OverlapResult IntersectionTest::aabb(const AxisAlignedBoundingBox &a, const AxisAlignedBoundingBox &b) +{ + if (a.aabb_min.x > b.aabb_max.x || b.aabb_min.x > a.aabb_max.x || + a.aabb_min.y > b.aabb_max.y || b.aabb_min.y > a.aabb_max.y || + a.aabb_min.z > b.aabb_max.z || b.aabb_min.z > a.aabb_max.z) + { + return disjoint; + } + else + { + return overlap; + } +} + +IntersectionTest::Result IntersectionTest::frustum_aabb(const FrustumPlanes &frustum, const AxisAlignedBoundingBox &box) +{ + bool is_intersecting = false; + for (int i = 0; i < 6; i++) + { + Result result = plane_aabb(frustum.planes[i], box); + if (result == outside) + return outside; + else if (result == intersecting) + is_intersecting = true; + break; + } + if (is_intersecting) + return intersecting; + else + return inside; +} + +IntersectionTest::Result IntersectionTest::frustum_obb(const FrustumPlanes &frustum, const OrientedBoundingBox &box) +{ + bool is_intersecting = false; + for (int i = 0; i < 6; i++) + { + Result result = plane_obb(frustum.planes[i], box); + if (result == outside) + return outside; + else if (result == intersecting) + is_intersecting = true; + } + if (is_intersecting) + return intersecting; + else + return inside; +} + +IntersectionTest::OverlapResult IntersectionTest::ray_aabb(const Vec3f &ray_start, const Vec3f &ray_end, const AxisAlignedBoundingBox &aabb) +{ + Vec3f c = (ray_start + ray_end) * 0.5f; + Vec3f w = ray_end - c; + Vec3f h = aabb.extents(); + + c -= aabb.center(); + + Vec3f v(std::abs(w.x), std::abs(w.y), std::abs(w.z)); + + if (std::abs(c.x) > v.x + h.x || std::abs(c.y) > v.y + h.y || std::abs(c.z) > v.z + h.z) + return disjoint; + + if (std::abs(c.y * w.z - c.z * w.y) > h.y * v.z + h.z * v.y || + std::abs(c.x * w.z - c.z * w.x) > h.x * v.z + h.z * v.x || + std::abs(c.x * w.y - c.y * w.x) > h.x * v.y + h.y * v.x) + return disjoint; + + return overlap; +} + +///////////////////////////////////////////////////////////////////////////// + +FrustumPlanes::FrustumPlanes() +{ +} + +FrustumPlanes::FrustumPlanes(const Mat4f &world_to_projection) +{ + planes[0] = near_frustum_plane(world_to_projection); + planes[1] = far_frustum_plane(world_to_projection); + planes[2] = left_frustum_plane(world_to_projection); + planes[3] = right_frustum_plane(world_to_projection); + planes[4] = top_frustum_plane(world_to_projection); + planes[5] = bottom_frustum_plane(world_to_projection); +} + +Vec4f FrustumPlanes::left_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] + m.matrix[0 + 0 * 4], + m.matrix[3 + 1 * 4] + m.matrix[0 + 1 * 4], + m.matrix[3 + 2 * 4] + m.matrix[0 + 2 * 4], + m.matrix[3 + 3 * 4] + m.matrix[0 + 3 * 4]); + plane /= plane.length3(); + return plane; +} + +Vec4f FrustumPlanes::right_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] - m.matrix[0 + 0 * 4], + m.matrix[3 + 1 * 4] - m.matrix[0 + 1 * 4], + m.matrix[3 + 2 * 4] - m.matrix[0 + 2 * 4], + m.matrix[3 + 3 * 4] - m.matrix[0 + 3 * 4]); + plane /= plane.length3(); + return plane; +} + +Vec4f FrustumPlanes::top_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] - m.matrix[1 + 0 * 4], + m.matrix[3 + 1 * 4] - m.matrix[1 + 1 * 4], + m.matrix[3 + 2 * 4] - m.matrix[1 + 2 * 4], + m.matrix[3 + 3 * 4] - m.matrix[1 + 3 * 4]); + plane /= plane.length3(); + return plane; +} + +Vec4f FrustumPlanes::bottom_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] + m.matrix[1 + 0 * 4], + m.matrix[3 + 1 * 4] + m.matrix[1 + 1 * 4], + m.matrix[3 + 2 * 4] + m.matrix[1 + 2 * 4], + m.matrix[3 + 3 * 4] + m.matrix[1 + 3 * 4]); + plane /= plane.length3(); + return plane; +} + +Vec4f FrustumPlanes::near_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] + m.matrix[2 + 0 * 4], + m.matrix[3 + 1 * 4] + m.matrix[2 + 1 * 4], + m.matrix[3 + 2 * 4] + m.matrix[2 + 2 * 4], + m.matrix[3 + 3 * 4] + m.matrix[2 + 3 * 4]); + plane /= plane.length3(); + return plane; +} + +Vec4f FrustumPlanes::far_frustum_plane(const Mat4f &m) +{ + Vec4f plane( + m.matrix[3 + 0 * 4] - m.matrix[2 + 0 * 4], + m.matrix[3 + 1 * 4] - m.matrix[2 + 1 * 4], + m.matrix[3 + 2 * 4] - m.matrix[2 + 2 * 4], + m.matrix[3 + 3 * 4] - m.matrix[2 + 3 * 4]); + plane /= plane.length3(); + return plane; +} diff --git a/src/r_poly_intersection.h b/src/r_poly_intersection.h new file mode 100644 index 0000000000..16f30201c2 --- /dev/null +++ b/src/r_poly_intersection.h @@ -0,0 +1,167 @@ +/* +** Various 3D intersection tests +** Copyright (c) 1997-2015 The UICore Team +** +** This software is provided 'as-is', without any express or implied +** warranty. In no event will the authors be held liable for any damages +** arising from the use of this software. +** +** Permission is granted to anyone to use this software for any purpose, +** including commercial applications, and to alter it and redistribute it +** freely, subject to the following restrictions: +** +** 1. The origin of this software must not be misrepresented; you must not +** claim that you wrote the original software. If you use this software +** in a product, an acknowledgment in the product documentation would be +** appreciated but is not required. +** 2. Altered source versions must be plainly marked as such, and must not be +** misrepresented as being the original software. +** 3. This notice may not be removed or altered from any source distribution. +** +*/ + +#pragma once + +#include "r_triangle.h" +#include + +class Vec4f +{ +public: + Vec4f() = default; + Vec4f(const Vec4f &) = default; + Vec4f(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) { } + Vec4f(float v) : x(v), y(v), z(v), w(v) { } + + static float dot(const Vec4f &a, const Vec4f &b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w; } + static float dot3(const Vec4f &a, const Vec4f &b) { return a.x * b.x + a.y * b.y + a.z * b.z; } + float length3() const { return std::sqrt(dot3(*this, *this)); } + float magnitude() const { return std::sqrt(dot(*this, *this)); } + + Vec4f &operator+=(const Vec4f &b) { *this = Vec4f(x + b.x, y + b.y, z + b.z, w + b.w); return *this; } + Vec4f &operator-=(const Vec4f &b) { *this = Vec4f(x - b.x, y - b.y, z - b.z, w - b.w); return *this; } + Vec4f &operator*=(const Vec4f &b) { *this = Vec4f(x * b.x, y * b.y, z * b.z, w * b.w); return *this; } + Vec4f &operator/=(const Vec4f &b) { *this = Vec4f(x / b.x, y / b.y, z / b.z, w / b.w); return *this; } + Vec4f &operator+=(float b) { *this = Vec4f(x + b, y + b, z + b, w + b); return *this; } + Vec4f &operator-=(float b) { *this = Vec4f(x - b, y - b, z - b, w - b); return *this; } + Vec4f &operator*=(float b) { *this = Vec4f(x * b, y * b, z * b, w * b); return *this; } + Vec4f &operator/=(float b) { *this = Vec4f(x / b, y / b, z / b, w / b); return *this; } + + float x, y, z, w; +}; + +class Vec3f +{ +public: + Vec3f() = default; + Vec3f(const Vec3f &) = default; + Vec3f(const Vec4f &v) : x(v.x), y(v.y), z(v.z) { } + Vec3f(float x, float y, float z) : x(x), y(y), z(z) { } + Vec3f(float v) : x(v), y(v), z(v) { } + + static float dot(const Vec3f &a, const Vec3f &b) { return a.x * b.x + a.y * b.y + a.z * b.z; } + float length() const { return std::sqrt(dot(*this, *this)); } + + Vec3f &operator+=(const Vec3f &b) { *this = Vec3f(x + b.x, y + b.y, z + b.z); return *this; } + Vec3f &operator-=(const Vec3f &b) { *this = Vec3f(x - b.x, y - b.y, z - b.z); return *this; } + Vec3f &operator*=(const Vec3f &b) { *this = Vec3f(x * b.x, y * b.y, z * b.z); return *this; } + Vec3f &operator/=(const Vec3f &b) { *this = Vec3f(x / b.x, y / b.y, z / b.z); return *this; } + Vec3f &operator+=(float b) { *this = Vec3f(x + b, y + b, z + b); return *this; } + Vec3f &operator-=(float b) { *this = Vec3f(x - b, y - b, z - b); return *this; } + Vec3f &operator*=(float b) { *this = Vec3f(x * b, y * b, z * b); return *this; } + Vec3f &operator/=(float b) { *this = Vec3f(x / b, y / b, z / b); return *this; } + + float x, y, z; +}; + +inline Vec3f operator+(const Vec3f &a, const Vec3f &b) { return Vec3f(a.x + b.x, a.y + b.y, a.z + b.z); } +inline Vec3f operator-(const Vec3f &a, const Vec3f &b) { return Vec3f(a.x - b.x, a.y - b.y, a.z - b.z); } +inline Vec3f operator*(const Vec3f &a, const Vec3f &b) { return Vec3f(a.x * b.x, a.y * b.y, a.z * b.z); } +inline Vec3f operator/(const Vec3f &a, const Vec3f &b) { return Vec3f(a.x / b.x, a.y / b.y, a.z / b.z); } + +inline Vec3f operator+(const Vec3f &a, float b) { return Vec3f(a.x + b, a.y + b, a.z + b); } +inline Vec3f operator-(const Vec3f &a, float b) { return Vec3f(a.x - b, a.y - b, a.z - b); } +inline Vec3f operator*(const Vec3f &a, float b) { return Vec3f(a.x * b, a.y * b, a.z * b); } +inline Vec3f operator/(const Vec3f &a, float b) { return Vec3f(a.x / b, a.y / b, a.z / b); } + +inline Vec3f operator+(float a, const Vec3f &b) { return Vec3f(a + b.x, a + b.y, a + b.z); } +inline Vec3f operator-(float a, const Vec3f &b) { return Vec3f(a - b.x, a - b.y, a - b.z); } +inline Vec3f operator*(float a, const Vec3f &b) { return Vec3f(a * b.x, a * b.y, a * b.z); } +inline Vec3f operator/(float a, const Vec3f &b) { return Vec3f(a / b.x, a / b.y, a / b.z); } + +typedef TriMatrix Mat4f; + +class AxisAlignedBoundingBox +{ +public: + AxisAlignedBoundingBox() : aabb_min(), aabb_max() {} + AxisAlignedBoundingBox(const Vec3f &aabb_min, const Vec3f &aabb_max) : aabb_min(aabb_min), aabb_max(aabb_max) { } + AxisAlignedBoundingBox(const AxisAlignedBoundingBox &aabb, const Vec3f &barycentric_min, const Vec3f &barycentric_max) + : aabb_min(mix(aabb.aabb_min, aabb.aabb_max, barycentric_min)), aabb_max(mix(aabb.aabb_min, aabb.aabb_max, barycentric_max)) { } + + Vec3f center() const { return (aabb_max + aabb_min) * 0.5f; } + Vec3f extents() const { return (aabb_max - aabb_min) * 0.5f; } + + Vec3f aabb_min; + Vec3f aabb_max; + +private: + template + inline A mix(A a, B b, C mix) + { + return a * (C(1) - mix) + b * mix; + } +}; + +class OrientedBoundingBox +{ +public: + Vec3f center; + Vec3f extents; + Vec3f axis_x; + Vec3f axis_y; + Vec3f axis_z; +}; + +class FrustumPlanes +{ +public: + FrustumPlanes(); + explicit FrustumPlanes(const Mat4f &world_to_projection); + + Vec4f planes[6]; + +private: + static Vec4f left_frustum_plane(const Mat4f &matrix); + static Vec4f right_frustum_plane(const Mat4f &matrix); + static Vec4f top_frustum_plane(const Mat4f &matrix); + static Vec4f bottom_frustum_plane(const Mat4f &matrix); + static Vec4f near_frustum_plane(const Mat4f &matrix); + static Vec4f far_frustum_plane(const Mat4f &matrix); +}; + +class IntersectionTest +{ +public: + enum Result + { + outside, + inside, + intersecting, + }; + + enum OverlapResult + { + disjoint, + overlap + }; + + static Result plane_aabb(const Vec4f &plane, const AxisAlignedBoundingBox &aabb); + static Result plane_obb(const Vec4f &plane, const OrientedBoundingBox &obb); + static OverlapResult sphere(const Vec3f ¢er1, float radius1, const Vec3f ¢er2, float radius2); + static OverlapResult sphere_aabb(const Vec3f ¢er, float radius, const AxisAlignedBoundingBox &aabb); + static OverlapResult aabb(const AxisAlignedBoundingBox &a, const AxisAlignedBoundingBox &b); + static Result frustum_aabb(const FrustumPlanes &frustum, const AxisAlignedBoundingBox &box); + static Result frustum_obb(const FrustumPlanes &frustum, const OrientedBoundingBox &box); + static OverlapResult ray_aabb(const Vec3f &ray_start, const Vec3f &ray_end, const AxisAlignedBoundingBox &box); +};