/* * stijn: I wrote this small test case while debugging the "invisible collision" problem in UT469. * When dodging into or sliding over a slope at high FPS, you often get stuck for no apparent * reason. There are two root causes for this problem: * * + The code that adjusts your movement vector when a collision is imminent is supposed to * calculate a vector that is parallel to the plane, but due to 32-bit float imprecision it * might still intersect the plane. * * + The code that detects collisions compares the distance between the current location and the * collision plane (D0), with the current location + adjusted movement vector and the plane (D1). * If we are far away from the world space origin, then D1 might be way off. */ #include #include #include #include #define DOUBLE_PRECISION 1 #if DOUBLE_PRECISION typedef double FLOAT; #else typedef float FLOAT; #endif typedef unsigned int DWORD; typedef int UBOOL; class FVector { public: FLOAT X, Y, Z; FVector(FLOAT InX, FLOAT InY, FLOAT InZ) : X(InX) , Y(InY) , Z(InZ) {} FVector(DWORD InXBits, DWORD InYBits, DWORD InZBits) : X(*reinterpret_cast(&InXBits)) , Y(*reinterpret_cast(&InYBits)) , Z(*reinterpret_cast(&InZBits)) {} inline FVector operator-(const FVector& V) const { return FVector(X - V.X, Y - V.Y, Z - V.Z); } inline FVector operator*(FLOAT Scale) const { return FVector(X * Scale, Y * Scale, Z * Scale); } inline FLOAT operator|(const FVector& V) const { return X * V.X + Y * V.Y + Z * V.Z; } inline FLOAT Size() const { return sqrtf(X * X + Y * Y + Z * Z); } inline FVector operator/(FLOAT Scale) const { FLOAT RScale = 1.f / Scale; return FVector(X * RScale, Y * RScale, Z * RScale); } friend FVector operator*(FLOAT Scale, const FVector& V); inline FVector operator+(const FVector& V) const { return FVector(X + V.X, Y + V.Y, Z + V.Z); } std::string String() { std::stringstream ss; ss.precision(10); ss << "(X=" << X << ", Y=" << Y << ", Z=" << Z << ")"; return ss.str(); } }; inline FVector operator*(FLOAT Scale, const FVector& V) { return FVector(V.X * Scale, V.Y * Scale, V.Z * Scale); } class FPlane { public: FLOAT X, Y, Z, W; FPlane(FLOAT InX, FLOAT InY, FLOAT InZ, FLOAT InW) : X(InX) , Y(InY) , Z(InZ) , W(InW) {} FPlane(DWORD InXBits, DWORD InYBits, DWORD InZBits, DWORD InWBits) : X(*reinterpret_cast(&InXBits)) , Y(*reinterpret_cast(&InYBits)) , Z(*reinterpret_cast(&InZBits)) , W(*reinterpret_cast(&InWBits)) {} FVector Normal() { return FVector(X, Y, Z); } #if !DOUBLE_PRECISION FLOAT PlaneDotSSE(const FVector& P) const; #endif FLOAT PlaneDot(const FVector& P) const; std::string String() { std::stringstream ss; ss.precision(10); ss << "(X=" << X << ", Y=" << Y << ", Z=" << Z << ", W=" << W << ")"; return ss.str(); } }; #if !DOUBLE_PRECISION inline __m128 _mm(const FPlane& P) { return _mm_loadu_ps(&P.X); } // Load FVector to XMM safely (we can specify whether we want to zero the unused float) template inline __m128 _mm(const FVector& V) { #if VECTOR_ALIGNMENT == 16 __m128 mm = _mm_loadu_ps(&V.X); //X,Y,Z,? if (bZeroW) mm = _mm_and_ps(mm, _mm_castsi128_ps(MM_3D_MASK)); //X,Y,Z,0 #else __m128 mm = _mm_setzero_ps(); //0,0,0,0 (some compilers already do this prior to _mm_load_ss) mm = _mm_load_ss(&V.Z); //Z,0,0,0 mm = _mm_movelh_ps(mm, mm); //Z,0,Z,0 mm = _mm_loadl_pi(mm, (const __m64*) & V.X); //X,Y,Z,0 (we load the low 8 bytes onto mm) #endif return mm; } inline __m128 _mm_coords_sum_ps(__m128 x) { __m128 w = _mm_shuffle_ps(x, x, 0b10110001); //x,y,z,w >> y,x,w,z // __m128 w = _mm_pshufd_ps( v, 0b10110001); /*SSE2 version*/ x = _mm_add_ps(x, w); // x+y,-,z+w,- w = _mm_movehl_ps(w, x); // >> z+w,-,-,- w = _mm_add_ss(w, x); // x+y+z+w,-,-,- return w; } inline FLOAT FPlane::PlaneDotSSE(const FVector& P) const { // Used to fill the W coordinate with -1 constexpr __m128 MM_PLANEDOT_W{ 0.f, 0.f, 0.f, -1.f }; FLOAT Result; __m128 mm_x_y_z_w = _mm_or_ps(_mm(P), MM_PLANEDOT_W); //PX,PY,PZ,-1 mm_x_y_z_w = _mm_mul_ps(mm_x_y_z_w, _mm(*this)); //X*PX, Y*PY, Z*PZ, -W (x,y,z,w) _mm_store_ss(&Result, _mm_coords_sum_ps(mm_x_y_z_w)); return Result; } #endif inline FLOAT FPlane::PlaneDot(const FVector& P) const { return X * P.X + Y * P.Y + Z * P.Z - W; } inline FLOAT FBoxPushOut(FVector Normal, FVector Size) { return abs(Normal.X * Size.X) + abs(Normal.Y * Size.Y) + abs(Normal.Z * Size.Z); } int main(int argc, char** argv) { FLOAT T0 = -1.f; FLOAT T1 = 1.f; FLOAT HitTime = 0.f; // UT: Start (X=-20777.466797,Y=-6414.467285,Z=-2672.729248) - (0xc6a252ef,0xc5c873bd,0xc5270bab) // Unit Test: Start = (X=-20777.4668, Y=-6414.467285, Z=-2672.729248) FVector Start(0xc6a252ef, 0xc5c873bd, 0xc5270bab); // Log: Trying Delta (X=-0.135223,Y=0.000000,Z=-0.162531) - (0xbe0a77c5,0x00000000,0xbe266e99) // Unit Test: Delta = (X=-0.1352225095, Y=0, Z=-0.1625312716) FVector Delta(0xbe0a77c5, 0x00000000, 0xbe266e99); // Log: Hit Collision Plane: (X=-0.761811,Y=-0.000000,Z=0.647799,W=14058.916016)- (0xbf430613,0xb40451c4,0x3f25d625,0x465babaa) // Unit Test: CollisionPlane = (X=-0.7618114352, Y=-1.232320415e-07, Z=0.6477988362, W=14058.91602) FPlane CollisionPlane(0xbf430613, 0xb40451c4, 0x3f25d625, 0x465babaa); // Log: Deflected Delta is (X=-0.136955,Y=-0.000000,Z=-0.161058) - (0xbe0c3dcd,0xaf9a0668,0xbe24ec84) (0.211415 UU) // Unit Test: DeflectedDelta (UT) = (X=-0.1369545013, Y=-2.801698873e-10, Z=-0.1610584855) FVector DeflectedDelta(0xbe0c3dcd, 0xaf9a0668, 0xbe24ec84); // FVector DeflectedDelta = (Delta - CollisionPlane.Normal * (Delta | CollisionPlane.Normal)) * (1.f - HitTime); FVector DeflectedDeltaRecalc = (Delta - CollisionPlane.Normal() * (Delta | CollisionPlane.Normal())) * (1.f - HitTime); // Log: TestDelta is (X=-1.432552,Y=-0.000000,Z=-1.684681) - (0xbfb75de0,0xb149638d,0xbfd7a3a4) (2.211415 UU) // Unit Test: TestDelta (UT) = (X=-1.432552338, Y=-2.930593768e-09, Z=-1.684681416) FVector TestDelta(0xbfb75de0, 0xb149638d, 0xbfd7a3a4); // FLOAT DeflectedDeltaSize = DeflectedDeltaRecalc.Size(); FVector DeflectedDeltaDir = DeflectedDeltaRecalc / DeflectedDeltaSize; FVector TestDeltaRecalc = DeflectedDeltaRecalc + 2.f * DeflectedDeltaDir; // Log: End is (X=-20778.898438,Y=-6414.467285,Z=-2674.413818) - (0xc6a255cc,0xc5c873bd,0xc527269f) FVector End(0xc6a255cc, 0xc5c873bd, 0xc527269f); // FVector EndRecalc(Start.X + TestDeltaRecalc.X, Start.Y + TestDeltaRecalc.Y, Start.Z + TestDeltaRecalc.Z); // Log: D0 is 38.206055 (0x4218d300) D1 is 38.205078 (0x4218d200) DWORD D0Raw = 0x4218d300; DWORD D1Raw = 0x4218d200; FLOAT D0 = *reinterpret_cast(&D0Raw); FLOAT D1 = *reinterpret_cast(&D1Raw); #if !DOUBLE_PRECISION FLOAT D0RecalcSSE = CollisionPlane.PlaneDotSSE(Start); FLOAT D1RecalcSSE = CollisionPlane.PlaneDotSSE(EndRecalc); #endif FLOAT D0Recalc = CollisionPlane.PlaneDot(Start); FLOAT D1Recalc = CollisionPlane.PlaneDot(EndRecalc); // Calculate time until hit FVector Extent(17.f, 17.f, 39.f); FLOAT PushOut = FBoxPushOut(CollisionPlane.Normal(), Extent); FLOAT AdjD0 = D0Recalc - PushOut; if (D0Recalc > D1Recalc && AdjD0 >= -PushOut && AdjD0 < 0) AdjD0 = 0.f; FLOAT T = (AdjD0) / (D0Recalc - D1Recalc); if (T > HitTime) HitTime = T; // Attenuate Movement FVector FinalDelta = DeflectedDeltaRecalc; if (HitTime < 1.f) { FLOAT FinalDeltaSize = (DeflectedDeltaSize + 2.f) * HitTime; if (FinalDeltaSize <= 2.f) { HitTime = 0; FinalDelta = FVector(0.f, 0.f, 0.f); } else { FinalDelta = TestDeltaRecalc * HitTime - 2.f * DeflectedDeltaDir; HitTime = (FinalDeltaSize - 2.f) / DeflectedDeltaSize; } } printf("########################################################################################################################\n"); printf("# Unreal Tournament v469 Movement Simulation #\n"); printf("########################################################################################################################\n"); printf("> Recalculation Precision: %s\n", #if DOUBLE_PRECISION "Double" #else "Single" #endif ); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> Start = %s\n", Start.String().c_str()); printf("> Delta = %s\n", Delta.String().c_str()); printf("> CollisionPlane = %s\n", CollisionPlane.String().c_str()); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> DeflectedDelta (UT) = %s\n", DeflectedDelta.String().c_str()); printf("> DeflectedDelta (Recalculated) = %s - Rounding Error = %f\n", DeflectedDeltaRecalc.String().c_str(), (DeflectedDeltaRecalc-DeflectedDelta).Size()); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> TestDelta (UT) = %s\n", TestDelta.String().c_str()); printf("> TestDelta (Recalculated) = %s - Rounding Error = %f\n", TestDeltaRecalc.String().c_str(), (TestDeltaRecalc-TestDelta).Size()); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> End (UT) = %s\n", End.String().c_str()); printf("> End (Recalculated) = %s - Rounding Error = %f\n", EndRecalc.String().c_str(), (EndRecalc - End).Size()); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> D0 (UT) = %f\n", D0); printf("> D1 (UT) = %f\n", D1); printf("> Collision? (UT) = %s\n", ((D0 - D1) > +0.00001f) ? "YES" : "NO"); printf("------------------------------------------------------------------------------------------------------------------------\n"); printf("> D0 (Recalculated on FPU) = %f\n", D0Recalc); printf("> D1 (Recalculated on FPU) = %f\n", D1Recalc); printf("> Collision? (Recalcalculated on FPU) = %s\n", ((D0Recalc - D1Recalc) > +0.00001f) ? "YES" : "NO"); printf("------------------------------------------------------------------------------------------------------------------------\n"); #if !DOUBLE_PRECISION printf("> D0 (Recalculated with SSE) = %f\n", D0RecalcSSE); printf("> D1 (Recalculated with SSE) = %f\n", D1RecalcSSE); printf("> Collision? (Recalculated with SSE) = %s\n", ((D0RecalcSSE - D1RecalcSSE) > +0.00001f) ? "YES" : "NO"); printf("------------------------------------------------------------------------------------------------------------------------\n"); #endif printf("> AdjD0 = %f\n", AdjD0); printf("> Final HitTime = %f\n", HitTime); printf("> Final Delta = %s\n", FinalDelta.String().c_str()); printf("------------------------------------------------------------------------------------------------------------------------\n"); return 0; }