From 5cad61bc6afc4bfeb7a6fa52912b9130bf1f960c Mon Sep 17 00:00:00 2001 From: Garux Date: Mon, 17 Nov 2025 18:42:36 +0500 Subject: [PATCH] * Q3MAP2_EXPERIMENTAL_OFFSET_WINDING_CREATION for higher windings precision --- tools/quake3/common/polylib.cpp | 28 ++++++++ tools/quake3/common/polylib.h | 1 + tools/quake3/common/qmath.h | 1 + tools/quake3/q3map2/brush.cpp | 111 ++++++++++++++++++++++++++++---- 4 files changed, 129 insertions(+), 12 deletions(-) diff --git a/tools/quake3/common/polylib.cpp b/tools/quake3/common/polylib.cpp index 7b8cc3de..706fe3e3 100644 --- a/tools/quake3/common/polylib.cpp +++ b/tools/quake3/common/polylib.cpp @@ -124,6 +124,34 @@ Vector3 WindingCenter( const winding_t& w ){ BaseWindingForPlaneAccu ================= */ +winding_accu_t BaseWindingForPlaneAccu( const Plane3& plane, const DoubleMinMax& minmax ){ + const DoubleVector3& n = plane.normal(); + const int z = vector3_max_abs_component_index( n ); + const int x = ( z == 2 )? 0 : z + 1; + const int y = ( x == 2 )? 0 : x + 1; + + if ( std::fabs( n[z] ) < 0.56 ) { + Error( "BaseWindingForPlaneAccu: no dominant axis found because normal is too short" ); + } + + // four points which are intersection of the plane with minmax edges + DoubleVector3 a, b, c, d; + a[x] = minmax.maxs[x]; + a[y] = minmax.maxs[y]; + a[z] = ( plane.dist() - a[x] * n[x] - a[y] * n[y] ) / n[z]; + b[x] = minmax.maxs[x]; + b[y] = minmax.mins[y]; + b[z] = ( plane.dist() - b[x] * n[x] - b[y] * n[y] ) / n[z]; + c[x] = minmax.mins[x]; + c[y] = minmax.mins[y]; + c[z] = ( plane.dist() - c[x] * n[x] - c[y] * n[y] ) / n[z]; + d[x] = minmax.mins[x]; + d[y] = minmax.maxs[y]; + d[z] = ( plane.dist() - d[x] * n[x] - d[y] * n[y] ) / n[z]; + + return ( plane.normal()[z] > 0 )? winding_accu_t{ a, b, c, d } : winding_accu_t{ d, c, b, a }; +} + winding_accu_t BaseWindingForPlaneAccu( const Plane3& plane ){ // The goal in this function is to replicate the behavior of the original BaseWindingForPlane() // function (see below) but at the same time increasing accuracy substantially. diff --git a/tools/quake3/common/polylib.h b/tools/quake3/common/polylib.h index 3e3b1abb..cebefcaa 100644 --- a/tools/quake3/common/polylib.h +++ b/tools/quake3/common/polylib.h @@ -107,6 +107,7 @@ bool windings_intersect_coplanar( const winding_t& w1, const winding_t& w2, cons using winding_accu_t = std::vector; +winding_accu_t BaseWindingForPlaneAccu( const Plane3& plane, const DoubleMinMax& minmax ); winding_accu_t BaseWindingForPlaneAccu( const Plane3& plane ); void ChopWindingInPlaceAccu( winding_accu_t& w, const Plane3& plane, float epsilon ); winding_t CopyWindingAccuToRegular( const winding_accu_t& w ); diff --git a/tools/quake3/common/qmath.h b/tools/quake3/common/qmath.h index c108fea7..2da22119 100644 --- a/tools/quake3/common/qmath.h +++ b/tools/quake3/common/qmath.h @@ -91,6 +91,7 @@ struct MinMax___ }; using MinMax = MinMax___; +using DoubleMinMax = MinMax___; diff --git a/tools/quake3/q3map2/brush.cpp b/tools/quake3/q3map2/brush.cpp index f5d6f9f9..6632dc99 100644 --- a/tools/quake3/q3map2/brush.cpp +++ b/tools/quake3/q3map2/brush.cpp @@ -217,11 +217,8 @@ static bool FixWinding( winding_t& w ){ static bool FixWindingAccu( winding_accu_t& w ){ bool altered = false; - while ( true ) + while ( w.size() > 1 ) // Don't remove the only remaining point. { - if ( w.size() < 2 ) { - break; // Don't remove the only remaining point. - } bool done = true; for ( winding_accu_t::iterator i = w.end() - 1, j = w.begin(); j != w.end(); i = j, ++j ) { @@ -252,7 +249,66 @@ static bool FixWindingAccu( winding_accu_t& w ){ return altered; } +// Solve: n1·x = d1, n2·x = d2, n3·x = d3 +// Returns true if unique solution +static bool solve3Planes( const Plane3 &p1, const Plane3 &p2, const Plane3 &p3, DoubleVector3 &out ){ + // Build matrix: rows = normals + const double m[3][3] = { { p1.normal().x(), p1.normal().y(), p1.normal().z() }, + { p2.normal().x(), p2.normal().y(), p2.normal().z() }, + { p3.normal().x(), p3.normal().y(), p3.normal().z() } }; + const double b[3] = { p1.dist(), p2.dist(), p3.dist() }; + // Cramer's rule + const double det = m[0][0] * ( m[1][1] * m[2][2] - m[2][1] * m[1][2] ) - + m[0][1] * ( m[1][0] * m[2][2] - m[2][0] * m[1][2] ) + + m[0][2] * ( m[1][0] * m[2][1] - m[1][1] * m[2][0] ); + + if( std::fabs( det ) < 1e-9 ) + return false; // parallel or degenerate + + // x = det(mx) / det + const double mx[3][3] = { { b[0], m[0][1], m[0][2] }, + { b[1], m[1][1], m[1][2] }, + { b[2], m[2][1], m[2][2] } }; + const double det_x = mx[0][0] * ( mx[1][1] * mx[2][2] - mx[2][1] * mx[1][2] ) - + mx[0][1] * ( mx[1][0] * mx[2][2] - mx[2][0] * mx[1][2] ) + + mx[0][2] * ( mx[1][0] * mx[2][1] - mx[1][1] * mx[2][0] ); + // y + const double my[3][3] = { { m[0][0], b[0], m[0][2] }, + { m[1][0], b[1], m[1][2] }, + { m[2][0], b[2], m[2][2] } }; + const double det_y = my[0][0] * ( my[1][1] * my[2][2] - my[2][1] * my[1][2] ) - + my[0][1] * ( my[1][0] * my[2][2] - my[2][0] * my[1][2] ) + + my[0][2] * ( my[1][0] * my[2][1] - my[1][1] * my[2][0] ); + // z + const double mz[3][3] = { { m[0][0], m[0][1], b[0] }, + { m[1][0], m[1][1], b[1] }, + { m[2][0], m[2][1], b[2] } }; + const double det_z = mz[0][0] * ( mz[1][1] * mz[2][2] - mz[2][1] * mz[1][2] ) - + mz[0][1] * ( mz[1][0] * mz[2][2] - mz[2][0] * mz[1][2] ) + + mz[0][2] * ( mz[1][0] * mz[2][1] - mz[1][1] * mz[2][0] ); + out.x() = det_x / det; + out.y() = det_y / det; + out.z() = det_z / det; + + return true; +} + +static DoubleMinMax brushMinMaxFromPlanes( const std::vector& planes ){ + DoubleMinMax minmax; + + for ( auto i = planes.cbegin(), end = planes.cend(); i != end; ++i ) + for ( auto j = i + 1; j != end; ++j ) + for ( auto k = j + 1; k != end; ++k ) + if( DoubleVector3 v; solve3Planes( *i, *j, *k, v ) ) + // Validate against ALL planes + if( std::ranges::all_of( planes, [&]( const Plane3& plane ){ return plane3_distance_to_point( plane, v ) < ON_EPSILON; } ) ) + minmax.extend( v ); + + return minmax; +} + +#define Q3MAP2_EXPERIMENTAL_OFFSET_WINDING_CREATION 1 /* CreateBrushWindings() makes basewindigs for sides and mins/maxs for the brush @@ -260,17 +316,43 @@ static bool FixWindingAccu( winding_accu_t& w ){ */ bool CreateBrushWindings( brush_t& brush ){ + std::vector planes; + planes.reserve( brush.sides.size() ); + + for( const side_t& side : brush.sides ){ + ENSURE( !side.bevel ); + planes.push_back( ( side.plane.normal() != g_vector3_identity )? side.plane : Plane3( mapplanes[ side.planenum ].plane ) ); + } +#if Q3MAP2_EXPERIMENTAL_OFFSET_WINDING_CREATION + DoubleMinMax minmax = brushMinMaxFromPlanes( planes ); + + if( !minmax.valid() ) + return false; + + const DoubleVector3 offset = minmax.origin(); + minmax.maxs -= offset; + minmax.mins -= offset; + for( Plane3& p : planes ) + p = plane3_translated( plane3_flipped( p ), -offset ); // flip for clipping +#else + for( Plane3& p : planes ) + p = plane3_flipped( p ); +#endif /* walk the list of brush sides */ for ( size_t i = 0; i < brush.sides.size(); ++i ) { /* get side and plane */ side_t& side = brush.sides[ i ]; - const plane_t& plane = mapplanes[ side.planenum ]; /* make huge winding */ #if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES - winding_accu_t w = BaseWindingForPlaneAccu( ( side.plane.normal() != g_vector3_identity )? side.plane : Plane3( plane.plane ) ); + #if Q3MAP2_EXPERIMENTAL_OFFSET_WINDING_CREATION + winding_accu_t w = BaseWindingForPlaneAccu( plane3_flipped( planes[ i ] ), minmax ); + #else + winding_accu_t w = BaseWindingForPlaneAccu( plane3_flipped( planes[ i ] ) ); + #endif #else + const plane_t& plane = mapplanes[ side.planenum ]; winding_t w = BaseWindingForPlane( plane.plane ); #endif @@ -278,15 +360,14 @@ bool CreateBrushWindings( brush_t& brush ){ for ( size_t j = 0; j < brush.sides.size() && !w.empty(); ++j ) { const side_t& cside = brush.sides[ j ]; - const plane_t& cplane = mapplanes[ cside.planenum ^ 1 ]; if ( i == j - || cside.planenum == ( side.planenum ^ 1 ) /* back side clipaway */ - || cside.bevel ) { + || cside.planenum == ( side.planenum ^ 1 ) ) { /* back side clipaway */ continue; } #if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES - ChopWindingInPlaceAccu( w, ( cside.plane.normal() != g_vector3_identity )? plane3_flipped( cside.plane ) : Plane3( cplane.plane ), 0 ); + ChopWindingInPlaceAccu( w, planes[ j ], 0 ); #else + const plane_t& cplane = mapplanes[ cside.planenum ^ 1 ]; ChopWindingInPlace( w, cplane.plane, 0 ); // CLIP_EPSILON ); #endif @@ -304,10 +385,16 @@ bool CreateBrushWindings( brush_t& brush ){ /* set side winding */ #if Q3MAP2_EXPERIMENTAL_HIGH_PRECISION_MATH_FIXES FixWindingAccu( w ); - if( w.size() >= 3 ) + if( w.size() >= 3 ){ + #if Q3MAP2_EXPERIMENTAL_OFFSET_WINDING_CREATION + for( DoubleVector3& v : w ) + v += offset; + #endif side.winding = CopyWindingAccuToRegular( w ); - else + } + else{ side.winding.clear(); + } #else side.winding.swap( w ); #endif