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

Test the PickingInfo class #13290

Merged
merged 7 commits into from
Nov 28, 2022
8 changes: 4 additions & 4 deletions packages/dev/core/src/Collisions/pickingInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class PickingInfo {
*/
public pickedPoint: Nullable<Vector3> = null;
/**
* The mesh corresponding the the pick collision
* The mesh corresponding the pick collision
*/
public pickedMesh: Nullable<AbstractMesh> = null;
/** (See getTextureCoordinates) The barycentric U coordinate that is used when calculating the texture coordinates of the collision.*/
Expand All @@ -36,7 +36,7 @@ export class PickingInfo {
public faceId = -1;
/** The index of the face on the subMesh that was picked, or the index of the Line if the picked Mesh is a LinesMesh */
public subMeshFaceId = -1;
/** Id of the the submesh that was picked */
/** Id of the submesh that was picked */
public subMeshId = 0;
/** If a sprite was picked, this will be the sprite the pick collided with */
public pickedSprite: Nullable<Sprite> = null;
Expand All @@ -63,7 +63,7 @@ export class PickingInfo {
/**
* Gets the normal corresponding to the face the pick collided with
* @param useWorldCoordinates If the resulting normal should be relative to the world (default: false)
* @param useVerticesNormals If the vertices normals should be used to calculate the normal instead of the normal map
* @param useVerticesNormals If the vertices normals should be used to calculate the normal instead of the normal map (default: true)
* @returns The normal corresponding to the face the pick collided with
* @remarks Note that the returned normal will always point towards the picking ray.
*/
Expand Down Expand Up @@ -146,7 +146,7 @@ export class PickingInfo {

/**
* Gets the texture coordinates of where the pick occurred
* @returns the vector containing the coordinates of the texture
* @returns The vector containing the coordinates of the texture
*/
public getTextureCoordinates(): Nullable<Vector2> {
if (!this.pickedMesh || !this.pickedMesh.isVerticesDataPresent(VertexBuffer.UVKind)) {
Expand Down
276 changes: 276 additions & 0 deletions packages/dev/core/test/unit/Collisions/babylon.pickingInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
import { PickingInfo } from "core/Collisions";
import { Ray } from "core/Culling";
import type { Engine } from "core/Engines";
import { NullEngine } from "core/Engines";
import { Vector2, Vector3 } from "core/Maths";
import type { Mesh } from "core/Meshes";
import { MeshBuilder } from "core/Meshes";
import { Scene } from "core/scene";

describe("PickingInfo", () => {
let subject: Engine;
let scene: Scene;
let box: Mesh;
let torusKnot: Mesh;

beforeEach(() => {
subject = new NullEngine({
renderHeight: 256,
renderWidth: 256,
textureSize: 256,
deterministicLockstep: false,
lockstepMaxSteps: 1,
});
scene = new Scene(subject);

torusKnot = MeshBuilder.CreateTorusKnot(
"Knot",
{
radius: 10,
tube: 3,
radialSegments: 32,
tubularSegments: 8,
p: 2,
q: 3,
},
scene
);

box = MeshBuilder.CreateBox("Box", { size: 1 }, scene);
});

describe("getNormal", () => {
it("should return null when no intersection", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = null;

expect(pickingInfo.getNormal()).toBeNull();
});

it('should return null when "useVerticesNormals" is true and no normals', () => {
const pickingInfo = new PickingInfo();

box.isVerticesDataPresent = () => false;
pickingInfo.pickedMesh = box;

expect(pickingInfo.getNormal(true)).toBeNull();
});

it("should return null when no indices", () => {
const pickingInfo = new PickingInfo();

box.getIndices = () => null;
pickingInfo.pickedMesh = box;

expect(pickingInfo.getNormal()).toBeNull();
});

it("should return normal when useVerticesNormals is true", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;

const normalBox = pickingInfo.getNormal(false, true);

expect(normalBox).toBeInstanceOf(Vector3);
expect(normalBox!.x).toBeCloseTo(0);
expect(normalBox!.y).toBeCloseTo(0);
expect(normalBox!.z).toBeCloseTo(1);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

const normal = pickingInfo.getNormal(false, true);

expect(normal).toBeInstanceOf(Vector3);
expect(normal!.x).toBeCloseTo(-0.84);
expect(normal!.y).toBeCloseTo(-0.24);
expect(normal!.z).toBeCloseTo(-0.48);
});

it("should return normal when useVerticesNormals is false", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;

const normalBox = pickingInfo.getNormal(false, false);

expect(normalBox).toBeInstanceOf(Vector3);
expect(normalBox!.x).toBeCloseTo(0);
expect(normalBox!.y).toBeCloseTo(0);
expect(normalBox!.z).toBeCloseTo(1);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

const normal = pickingInfo.getNormal(false, false);

expect(normal).toBeInstanceOf(Vector3);
expect(normal!.x).toBeCloseTo(-0.89);
expect(normal!.y).toBeCloseTo(-0.08);
expect(normal!.z).toBeCloseTo(-0.45);
});

it('should transform normal to world when "useWorldCoordinates" is true', () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;

box.scaling = new Vector3(1, 2, 3);
box.rotation = new Vector3(0, Math.PI / 2, 0);
box.computeWorldMatrix(true);

const normalBox = pickingInfo.getNormal(true, true);

expect(normalBox).toBeInstanceOf(Vector3);
expect(normalBox!.x).toBeCloseTo(1);
expect(normalBox!.y).toBeCloseTo(0);
expect(normalBox!.z).toBeCloseTo(0);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

torusKnot.scaling = new Vector3(1, 2, 3);
torusKnot.rotation = new Vector3(0, Math.PI / 2, 0);
torusKnot.computeWorldMatrix(true);

const normal = pickingInfo.getNormal(true, true);

expect(normal).toBeInstanceOf(Vector3);
expect(normal!.x).toBeCloseTo(-0.18);
expect(normal!.y).toBeCloseTo(-0.14);
expect(normal!.z).toBeCloseTo(0.97);
});

it("should use the ray when provided", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;
pickingInfo.ray = new Ray(new Vector3(0, 0, 0), new Vector3(0, 0, 1));

const normalBox = pickingInfo.getNormal(true, true);

expect(normalBox).toBeInstanceOf(Vector3);
expect(normalBox!.x).toBeCloseTo(0);
expect(normalBox!.y).toBeCloseTo(0);
expect(normalBox!.z).toBeCloseTo(-1);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

const normal = pickingInfo.getNormal(true, true);

expect(normal).toBeInstanceOf(Vector3);
expect(normal!.x).toBeCloseTo(-0.84);
expect(normal!.y).toBeCloseTo(-0.24);
expect(normal!.z).toBeCloseTo(-0.48);
});

it("should transform normal to world when 'useWorldCoordinates' is false and ray is provided", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;
pickingInfo.ray = new Ray(new Vector3(0, 0, 0), new Vector3(0, 0, 1));

box.scaling = new Vector3(1, 2, 3);
box.rotation = new Vector3(0, Math.PI / 4, 0);
box.computeWorldMatrix(true);

const normalBox = pickingInfo.getNormal(false, true);

expect(normalBox).toBeInstanceOf(Vector3);
expect(normalBox!.x).toBeCloseTo(0);
expect(normalBox!.y).toBeCloseTo(0);
expect(normalBox!.z).toBeCloseTo(-1);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

torusKnot.scaling = new Vector3(1, 2, 3);
torusKnot.rotation = new Vector3(0, Math.PI / 4, 0);
torusKnot.computeWorldMatrix(true);

const normal = pickingInfo.getNormal(false, true);

expect(normal).toBeInstanceOf(Vector3);
expect(normal!.x).toBeCloseTo(0.84);
expect(normal!.y).toBeCloseTo(0.24);
expect(normal!.z).toBeCloseTo(0.48);
});
});

describe("getTextureCoordinates", () => {
it("should return null when no pickedMesh", () => {
const pickingInfo = new PickingInfo();
expect(pickingInfo.getTextureCoordinates()).toBeNull();
});

it("should return null when pickedMesh has no UV", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;

box.isVerticesDataPresent = () => false;

expect(pickingInfo.getTextureCoordinates()).toBeNull();
});

it("should return null when indicies are not present", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;

box.isVerticesDataPresent = () => true;
box.getIndices = () => null;

expect(pickingInfo.getTextureCoordinates()).toBeNull();
});

it("should return null when uvs are not present", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;

box.isVerticesDataPresent = () => true;
box.getIndices = () => [];
box.getVerticesData = () => null;

expect(pickingInfo.getTextureCoordinates()).toBeNull();
});

it("should return vector2 with correct values", () => {
const pickingInfo = new PickingInfo();
pickingInfo.pickedMesh = box;
pickingInfo.faceId = 0;
pickingInfo.bu = 0.5;
pickingInfo.bv = 0.5;

const uv = pickingInfo.getTextureCoordinates();

expect(uv).toBeInstanceOf(Vector2);
expect(uv!.x).toBeCloseTo(0.5);
expect(uv!.y).toBeCloseTo(1);

// And test with the knot

pickingInfo.pickedMesh = torusKnot;

const uvKnot = pickingInfo.getTextureCoordinates();

expect(uvKnot!.x).toBeCloseTo(0.02);
expect(uvKnot!.y).toBeCloseTo(0.06);
});
});
});