From a63330a0af4e8ad82c643a26635cd9dc734aa86a Mon Sep 17 00:00:00 2001 From: Tristan Shelton Date: Fri, 17 Jun 2022 02:38:54 -0700 Subject: [PATCH 1/5] Failing unit tests for projecting a vector onto a plane --- .../unit/Math/babylon.math.vector.test.ts | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packages/dev/core/test/unit/Math/babylon.math.vector.test.ts diff --git a/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts b/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts new file mode 100644 index 00000000000..c1a71d61c7b --- /dev/null +++ b/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts @@ -0,0 +1,43 @@ +import { Plane, Vector3 } from 'core/Maths' + +/** + * Describes the test suite. + */ +describe("Babylon Vectors", () => { + describe("#Vector3", () => { + it("can project a direction vector onto a plane", () => { + // A ground plan at origin + const simplePlane = Plane.FromPositionAndNormal( + Vector3.Zero(), + Vector3.Up(), + ); + + const rayOrigin = new Vector3(0, 10, 0); + const rayAngle = new Vector3(0.5, -0.5, 0); + + /* + * At 45 degrees this should form a perfect right triangle, + * so the result should be the same as the distance to the origin. + */ + const expected = new Vector3(10, 0, 0); + + expect(rayAngle.projectOnPlane(simplePlane, rayOrigin)).toEqual(expected) + }) + + it("can project a direction vector onto an offset plane", () => { + // A ground plane 10 units below origin + const simplePlane = Plane.FromPositionAndNormal( + new Vector3(0, -10, 0), + Vector3.Up(), + ); + + const rayOrigin = new Vector3(0, 10, 0); + const rayAngle = new Vector3(0.5, -0.5, 0); + + // This is also a right triangle, but the plane is offset so the hit point is offset and the distance increases + const expected = new Vector3(20, -10, 0); + + expect(rayAngle.projectOnPlane(simplePlane, rayOrigin)).toEqual(expected) + }) + }) +}) \ No newline at end of file From 753ebd25c19da21c99091ec76a4338f79c284307 Mon Sep 17 00:00:00 2001 From: Tristan Shelton Date: Fri, 17 Jun 2022 02:39:14 -0700 Subject: [PATCH 2/5] Improved vector/plane projection --- packages/dev/core/src/Maths/math.vector.ts | 47 ++++++++++++++++------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/dev/core/src/Maths/math.vector.ts b/packages/dev/core/src/Maths/math.vector.ts index 58b8aab7ef8..676ceda067e 100644 --- a/packages/dev/core/src/Maths/math.vector.ts +++ b/packages/dev/core/src/Maths/math.vector.ts @@ -1150,22 +1150,43 @@ export class Vector3 { * @param result defines the Vector3 where to store the result */ public projectOnPlaneToRef(plane: Plane, origin: Vector3, result: Vector3): void { - const n = plane.normal; - const d = plane.d; - - const V = MathTmp.Vector3[0]; - - // ray direction - this.subtractToRef(origin, V); + // Use the normal scaled to the plane offset as the origin for the formula + const planeOrigin = MathTmp.Vector3[0]; + plane.normal.scaleToRef(-plane.d, planeOrigin) + + // Since the normal in Babylon should point toward the viewer, invert it for the dot product + const inverseNormal = MathTmp.Vector3[1]; + plane.normal.negateToRef(inverseNormal); + + // This vector is the direction + const direction = this; + + // Set a margin to prevent issues with directions very close to perpendicular to the plane + const epsilon = 0.000001; + + // Calculate how close the direction is to the normal of the plane + const dotProduct = Vector3.Dot(inverseNormal, direction); + + // Early out in case the direction will never hit the plane + if (dotProduct <= epsilon) { + // No good option for setting the result vector here, so just take the origin of the ray + result.copyFrom(origin) + return + } - V.normalize(); + // Calculate the offset + const relativeOrigin = MathTmp.Vector3[2]; + planeOrigin.subtractToRef(origin, relativeOrigin) - const denom = Vector3.Dot(V, n); - const t = -(Vector3.Dot(origin, n) + d) / denom; + // Calculate the length along the direction vector of the hit point + const hitDistance = Vector3.Dot(relativeOrigin, inverseNormal) / dotProduct; - // P = P0 + t*V - const scaledV = V.scaleInPlace(t); - origin.addToRef(scaledV, result); + // Apply the hit point by adding the direction scaled by the distance to the origin + result.set( + origin.x + direction.x * hitDistance, + origin.y + direction.y * hitDistance, + origin.z + direction.z * hitDistance, + ) } /** From 977fa22bf118aa6e087c0b6386a8d7cf06717014 Mon Sep 17 00:00:00 2001 From: Tristan Shelton Date: Fri, 17 Jun 2022 12:08:28 -0700 Subject: [PATCH 3/5] Formatting and typos --- packages/dev/core/src/Maths/math.vector.ts | 20 ++++++++----------- .../unit/Math/babylon.math.vector.test.ts | 2 +- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/dev/core/src/Maths/math.vector.ts b/packages/dev/core/src/Maths/math.vector.ts index 676ceda067e..3bee9dd3e65 100644 --- a/packages/dev/core/src/Maths/math.vector.ts +++ b/packages/dev/core/src/Maths/math.vector.ts @@ -1152,12 +1152,12 @@ export class Vector3 { public projectOnPlaneToRef(plane: Plane, origin: Vector3, result: Vector3): void { // Use the normal scaled to the plane offset as the origin for the formula const planeOrigin = MathTmp.Vector3[0]; - plane.normal.scaleToRef(-plane.d, planeOrigin) + plane.normal.scaleToRef(-plane.d, planeOrigin); // Since the normal in Babylon should point toward the viewer, invert it for the dot product const inverseNormal = MathTmp.Vector3[1]; plane.normal.negateToRef(inverseNormal); - + // This vector is the direction const direction = this; @@ -1170,23 +1170,19 @@ export class Vector3 { // Early out in case the direction will never hit the plane if (dotProduct <= epsilon) { // No good option for setting the result vector here, so just take the origin of the ray - result.copyFrom(origin) - return + result.copyFrom(origin); + return; } // Calculate the offset const relativeOrigin = MathTmp.Vector3[2]; - planeOrigin.subtractToRef(origin, relativeOrigin) + planeOrigin.subtractToRef(origin, relativeOrigin); - // Calculate the length along the direction vector of the hit point - const hitDistance = Vector3.Dot(relativeOrigin, inverseNormal) / dotProduct; + // Calculate the length along the direction vector to the hit point + const hitDistance = Vector3.Dot(relativeOrigin, inverseNormal) / dotProduct; // Apply the hit point by adding the direction scaled by the distance to the origin - result.set( - origin.x + direction.x * hitDistance, - origin.y + direction.y * hitDistance, - origin.z + direction.z * hitDistance, - ) + result.set(origin.x + direction.x * hitDistance, origin.y + direction.y * hitDistance, origin.z + direction.z * hitDistance); } /** diff --git a/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts b/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts index c1a71d61c7b..ee2c45869db 100644 --- a/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts +++ b/packages/dev/core/test/unit/Math/babylon.math.vector.test.ts @@ -6,7 +6,7 @@ import { Plane, Vector3 } from 'core/Maths' describe("Babylon Vectors", () => { describe("#Vector3", () => { it("can project a direction vector onto a plane", () => { - // A ground plan at origin + // A ground plane at origin const simplePlane = Plane.FromPositionAndNormal( Vector3.Zero(), Vector3.Up(), From f3dafaa0c8e43f4fafa910756ef0db6bc588bc99 Mon Sep 17 00:00:00 2001 From: Tristan Shelton Date: Fri, 17 Jun 2022 12:14:51 -0700 Subject: [PATCH 4/5] Use existing constant --- packages/dev/core/src/Maths/math.vector.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/dev/core/src/Maths/math.vector.ts b/packages/dev/core/src/Maths/math.vector.ts index 3bee9dd3e65..b9754e7dfaf 100644 --- a/packages/dev/core/src/Maths/math.vector.ts +++ b/packages/dev/core/src/Maths/math.vector.ts @@ -1161,14 +1161,17 @@ export class Vector3 { // This vector is the direction const direction = this; - // Set a margin to prevent issues with directions very close to perpendicular to the plane - const epsilon = 0.000001; - // Calculate how close the direction is to the normal of the plane const dotProduct = Vector3.Dot(inverseNormal, direction); - // Early out in case the direction will never hit the plane - if (dotProduct <= epsilon) { + /* + * Early out in case the direction will never hit the plane. + * + * Epsilon is used to avoid issues with rays very near to parallel with the + * plane, and squared because as of writing the value is not sufficiently + * small for this use case. + */ + if (dotProduct <= Epsilon * Epsilon) { // No good option for setting the result vector here, so just take the origin of the ray result.copyFrom(origin); return; From 364257500f633a08d662de540832c24fffc41075 Mon Sep 17 00:00:00 2001 From: Tristan Shelton Date: Fri, 17 Jun 2022 14:49:55 -0700 Subject: [PATCH 5/5] Oof --- packages/dev/core/src/Maths/math.vector.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Maths/math.vector.ts b/packages/dev/core/src/Maths/math.vector.ts index b9754e7dfaf..3630388c90d 100644 --- a/packages/dev/core/src/Maths/math.vector.ts +++ b/packages/dev/core/src/Maths/math.vector.ts @@ -1164,7 +1164,7 @@ export class Vector3 { // Calculate how close the direction is to the normal of the plane const dotProduct = Vector3.Dot(inverseNormal, direction); - /* + /* * Early out in case the direction will never hit the plane. * * Epsilon is used to avoid issues with rays very near to parallel with the