Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug Fix - Vector3 project on plane #12663

Merged
merged 5 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 33 additions & 13 deletions packages/dev/core/src/Maths/math.vector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1150,22 +1150,42 @@ 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;
RaananW marked this conversation as resolved.
Show resolved Hide resolved

// 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
* 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;
}

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 to 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);
}

/**
Expand Down
43 changes: 43 additions & 0 deletions packages/dev/core/test/unit/Math/babylon.math.vector.test.ts
Original file line number Diff line number Diff line change
@@ -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 plane 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)
})
})
})