From d0d52bd32d45cdaa357020913c64733dd7e198c4 Mon Sep 17 00:00:00 2001 From: Oleg Postoev Date: Mon, 19 Sep 2022 17:14:17 +0300 Subject: [PATCH] test(particles.cloudPoint): add tests for intersectsMesh function (#12992) --- packages/dev/core/src/Particles/cloudPoint.ts | 38 +-- .../core/src/Particles/pointsCloudSystem.ts | 46 +-- .../unit/Particles/babylon.cloudPoint.test.ts | 270 ++++++++++++++++++ 3 files changed, 317 insertions(+), 37 deletions(-) create mode 100644 packages/dev/core/test/unit/Particles/babylon.cloudPoint.test.ts diff --git a/packages/dev/core/src/Particles/cloudPoint.ts b/packages/dev/core/src/Particles/cloudPoint.ts index 8f509a63089..f0ec8856829 100644 --- a/packages/dev/core/src/Particles/cloudPoint.ts +++ b/packages/dev/core/src/Particles/cloudPoint.ts @@ -149,29 +149,29 @@ export class CloudPoint { if (!target.hasBoundingInfo) { return false; } - isSphere = isSphere ? isSphere : false; + + if (!this._pcs.mesh) { + throw new Error("Point Cloud System doesnt contain the Mesh"); + } if (isSphere) { return target.getBoundingInfo().boundingSphere.intersectsPoint(this.position.add(this._pcs.mesh.position)); - } else { - let maxX = 0; - let minX = 0; - let maxY = 0; - let minY = 0; - let maxZ = 0; - let minZ = 0; - maxX = target.getBoundingInfo().boundingBox.maximumWorld.x; - minX = target.getBoundingInfo().boundingBox.minimumWorld.x; - maxY = target.getBoundingInfo().boundingBox.maximumWorld.y; - minY = target.getBoundingInfo().boundingBox.minimumWorld.y; - maxZ = target.getBoundingInfo().boundingBox.maximumWorld.z; - minZ = target.getBoundingInfo().boundingBox.minimumWorld.z; - - const x = this.position.x + this._pcs.mesh.position.x; - const y = this.position.y + this._pcs.mesh.position.y; - const z = this.position.z + this._pcs.mesh.position.z; - return minX <= x && x <= maxX && minY <= y && y <= maxY && minZ <= z && z <= maxZ; } + + const bbox = target.getBoundingInfo().boundingBox; + + const maxX = bbox.maximumWorld.x; + const minX = bbox.minimumWorld.x; + const maxY = bbox.maximumWorld.y; + const minY = bbox.minimumWorld.y; + const maxZ = bbox.maximumWorld.z; + const minZ = bbox.minimumWorld.z; + + const x = this.position.x + this._pcs.mesh.position.x; + const y = this.position.y + this._pcs.mesh.position.y; + const z = this.position.z + this._pcs.mesh.position.z; + + return minX <= x && x <= maxX && minY <= y && y <= maxY && minZ <= z && z <= maxZ; } /** diff --git a/packages/dev/core/src/Particles/pointsCloudSystem.ts b/packages/dev/core/src/Particles/pointsCloudSystem.ts index 23534d23a0f..723f980afea 100644 --- a/packages/dev/core/src/Particles/pointsCloudSystem.ts +++ b/packages/dev/core/src/Particles/pointsCloudSystem.ts @@ -57,7 +57,7 @@ export class PointsCloudSystem implements IDisposable { /** * The PCS mesh. It's a standard BJS Mesh, so all the methods from the Mesh class are available. */ - public mesh: Mesh; + public mesh?: Mesh; /** * This empty object is intended to store some PCS specific or temporary values in order to lower the Garbage Collector activity. * Please read : @@ -765,7 +765,7 @@ export class PointsCloudSystem implements IDisposable { Matrix.IdentityToRef(rotMatrix); let idx = 0; // current index of the particle - if (this.mesh.isFacetDataEnabled) { + if (this.mesh?.isFacetDataEnabled) { this._computeBoundingBox = true; } @@ -773,7 +773,7 @@ export class PointsCloudSystem implements IDisposable { if (this._computeBoundingBox) { if (start != 0 || end != this.nbParticles - 1) { // only some particles are updated, then use the current existing BBox basis. Note : it can only increase. - const boundingInfo = this.mesh.getBoundingInfo(); + const boundingInfo = this.mesh?.getBoundingInfo(); if (boundingInfo) { minimum.copyFrom(boundingInfo.minimum); maximum.copyFrom(boundingInfo.maximum); @@ -907,21 +907,23 @@ export class PointsCloudSystem implements IDisposable { } // if the VBO must be updated - if (update) { - if (this._computeParticleColor) { - mesh.updateVerticesData(VertexBuffer.ColorKind, colors32, false, false); - } - if (this._computeParticleTexture) { - mesh.updateVerticesData(VertexBuffer.UVKind, uvs32, false, false); + if (mesh) { + if (update) { + if (this._computeParticleColor) { + mesh.updateVerticesData(VertexBuffer.ColorKind, colors32, false, false); + } + if (this._computeParticleTexture) { + mesh.updateVerticesData(VertexBuffer.UVKind, uvs32, false, false); + } + mesh.updateVerticesData(VertexBuffer.PositionKind, positions32, false, false); } - mesh.updateVerticesData(VertexBuffer.PositionKind, positions32, false, false); - } - if (this._computeBoundingBox) { - if (mesh.hasBoundingInfo) { - mesh.getBoundingInfo().reConstruct(minimum, maximum, mesh._worldMatrix); - } else { - mesh.buildBoundingInfo(minimum, maximum, mesh._worldMatrix); + if (this._computeBoundingBox) { + if (mesh.hasBoundingInfo) { + mesh.getBoundingInfo().reConstruct(minimum, maximum, mesh._worldMatrix); + } else { + mesh.buildBoundingInfo(minimum, maximum, mesh._worldMatrix); + } } } this.afterUpdateParticles(start, end, update); @@ -932,7 +934,7 @@ export class PointsCloudSystem implements IDisposable { * Disposes the PCS. */ public dispose(): void { - this.mesh.dispose(); + this.mesh?.dispose(); this.vars = null; // drop references to internal big arrays for the GC (this._positions) = null; @@ -953,7 +955,7 @@ export class PointsCloudSystem implements IDisposable { */ public refreshVisibleSize(): PointsCloudSystem { if (!this._isVisibilityBoxLocked) { - this.mesh.refreshBoundingInfo(); + this.mesh?.refreshBoundingInfo(); } return this; } @@ -965,6 +967,10 @@ export class PointsCloudSystem implements IDisposable { * doc : */ public setVisibilityBox(size: number): void { + if (!this.mesh) { + return; + } + const vis = size / 2; this.mesh.buildBoundingInfo(new Vector3(-vis, -vis, -vis), new Vector3(vis, vis, vis)); } @@ -982,6 +988,10 @@ export class PointsCloudSystem implements IDisposable { * doc : */ public set isAlwaysVisible(val: boolean) { + if (!this.mesh) { + return; + } + this._alwaysVisible = val; this.mesh.alwaysSelectAsActiveMesh = val; } diff --git a/packages/dev/core/test/unit/Particles/babylon.cloudPoint.test.ts b/packages/dev/core/test/unit/Particles/babylon.cloudPoint.test.ts new file mode 100644 index 00000000000..3713cebad01 --- /dev/null +++ b/packages/dev/core/test/unit/Particles/babylon.cloudPoint.test.ts @@ -0,0 +1,270 @@ +import { BoundingBox, BoundingInfo, BoundingSphere } from "core/Culling"; +import { Engine, NullEngine } from "core/Engines"; +import { Vector3 } from "core/Maths"; +import { Mesh } from "core/Meshes"; +import { CloudPoint, PointsCloudSystem, PointsGroup } from "core/Particles"; +import { Scene } from "core/scene"; +import { DeepImmutable } from "core/types"; + +describe("CloudPoint", () => { + describe("intersectsMesh", () => { + let subject: Engine; + + let pointsGroup: PointsGroup; + let scene: Scene; + + let pointsCloudSystemWithoutMesh: PointsCloudSystem; + let cloudPointWithoutMesh: CloudPoint; + + let pointsCloudSystemWithMesh: PointsCloudSystem; + let cloudPointWithMesh: CloudPoint; + + /** + * Create a new engine subject before each test. + */ + beforeEach(async () => { + subject = new NullEngine({ + renderHeight: 256, + renderWidth: 256, + textureSize: 256, + deterministicLockstep: false, + lockstepMaxSteps: 1, + }); + + pointsGroup = new PointsGroup(1, () => {}); + scene = new Scene(subject); + + pointsCloudSystemWithoutMesh = new PointsCloudSystem("cloud-system", 1, scene); + cloudPointWithoutMesh = new CloudPoint(1, pointsGroup, 1, 0, pointsCloudSystemWithoutMesh); + + pointsCloudSystemWithMesh = new PointsCloudSystem("cloud-system", 1, scene); + await pointsCloudSystemWithMesh.buildMeshAsync(); + cloudPointWithMesh = new CloudPoint(1, pointsGroup, 1, 0, pointsCloudSystemWithMesh); + }); + + // Check the hasBoundingInfo + it("should return False when target doesnt contain Bounding Info", () => { + const mesh = {} as Mesh; + const result = cloudPointWithoutMesh.intersectsMesh(mesh, false); + + expect(result).toBeFalsy(); + }); + + // Check throw Error + it("should throw an error if Point Cloud System doesnt have a Mesh", () => { + const isIntersects = true; + + const boundingInfo = { + boundingSphere: { + intersectsPoint: (_point: DeepImmutable) => isIntersects, + } as BoundingSphere, + } as BoundingInfo; + + const mesh = { + hasBoundingInfo: true, + getBoundingInfo: () => boundingInfo, + } as Mesh; + + expect(() => cloudPointWithoutMesh.intersectsMesh(mesh, true)).toThrowError(); + }); + + // Check intersect by sphere + it("should return True when target intersects with Point Cloud by sphere", () => { + const isIntersects = true; + + const boundingInfo = { + boundingSphere: { + intersectsPoint: (_point: DeepImmutable) => isIntersects, + } as BoundingSphere, + } as BoundingInfo; + + const mesh = { + hasBoundingInfo: true, + getBoundingInfo: () => boundingInfo, + } as Mesh; + + const result = cloudPointWithMesh.intersectsMesh(mesh, true); + + expect(result).toBeTruthy(); + }); + + it("should return False when target dont intersects with Point Cloud by sphere", () => { + const isIntersects = false; + + const boundingInfo = { + boundingSphere: { + intersectsPoint: (_point: DeepImmutable) => isIntersects, + } as BoundingSphere, + } as BoundingInfo; + + const mesh = { + hasBoundingInfo: true, + getBoundingInfo: () => boundingInfo, + } as Mesh; + + const result = cloudPointWithMesh.intersectsMesh(mesh, true); + + expect(result).toBeFalsy(); + }); + + // Check real intersects + describe("should check real intersects", () => { + const boundingBoxLimits = [ + { min: [-1, -1, -1], max: [-1, -1, -1], result: false }, + { min: [-1, -1, -1], max: [0, 0, -1], result: false }, + { min: [-1, -1, -1], max: [0, 0, 0], result: true }, + { min: [-1, -1, -1], max: [0, 0, 1], result: true }, + { min: [-1, -1, -1], max: [0, 1, -1], result: false }, + { min: [-1, -1, -1], max: [0, 1, 0], result: true }, + { min: [-1, -1, -1], max: [0, 1, 1], result: true }, + { min: [-1, -1, -1], max: [1, -1, -1], result: false }, + { min: [-1, -1, -1], max: [1, 0, -1], result: false }, + { min: [-1, -1, -1], max: [1, 0, 0], result: true }, + { min: [-1, -1, -1], max: [1, 0, 1], result: true }, + { min: [-1, -1, -1], max: [1, 1, -1], result: false }, + { min: [-1, -1, -1], max: [1, 1, 0], result: true }, + { min: [-1, -1, -1], max: [1, 1, 1], result: true }, + { min: [-1, -1, 0], max: [-1, -1, -1], result: false }, + { min: [-1, -1, 0], max: [0, 0, -1], result: false }, + { min: [-1, -1, 0], max: [0, 0, 0], result: true }, + { min: [-1, -1, 0], max: [0, 0, 1], result: true }, + { min: [-1, -1, 0], max: [0, 1, -1], result: false }, + { min: [-1, -1, 0], max: [0, 1, 0], result: true }, + { min: [-1, -1, 0], max: [0, 1, 1], result: true }, + { min: [-1, -1, 0], max: [1, -1, -1], result: false }, + { min: [-1, -1, 0], max: [1, 0, -1], result: false }, + { min: [-1, -1, 0], max: [1, 0, 0], result: true }, + { min: [-1, -1, 0], max: [1, 0, 1], result: true }, + { min: [-1, -1, 0], max: [1, 1, -1], result: false }, + { min: [-1, -1, 0], max: [1, 1, 0], result: true }, + { min: [-1, -1, 0], max: [1, 1, 1], result: true }, + { min: [-1, -1, 1], max: [-1, -1, -1], result: false }, + { min: [-1, 0, -1], max: [0, 0, -1], result: false }, + { min: [-1, 0, -1], max: [0, 0, 0], result: true }, + { min: [-1, 0, -1], max: [0, 0, 1], result: true }, + { min: [-1, 0, -1], max: [0, 1, -1], result: false }, + { min: [-1, 0, -1], max: [0, 1, 0], result: true }, + { min: [-1, 0, -1], max: [0, 1, 1], result: true }, + { min: [-1, 0, -1], max: [1, -1, -1], result: false }, + { min: [-1, 0, -1], max: [1, 0, -1], result: false }, + { min: [-1, 0, -1], max: [1, 0, 0], result: true }, + { min: [-1, 0, -1], max: [1, 0, 1], result: true }, + { min: [-1, 0, -1], max: [1, 1, -1], result: false }, + { min: [-1, 0, -1], max: [1, 1, 0], result: true }, + { min: [-1, 0, -1], max: [1, 1, 1], result: true }, + { min: [-1, 0, 0], max: [-1, -1, -1], result: false }, + { min: [-1, 0, 0], max: [0, 0, -1], result: false }, + { min: [-1, 0, 0], max: [0, 0, 0], result: true }, + { min: [-1, 0, 0], max: [0, 0, 1], result: true }, + { min: [-1, 0, 0], max: [0, 1, -1], result: false }, + { min: [-1, 0, 0], max: [0, 1, 0], result: true }, + { min: [-1, 0, 0], max: [0, 1, 1], result: true }, + { min: [-1, 0, 0], max: [1, -1, -1], result: false }, + { min: [-1, 0, 0], max: [1, 0, -1], result: false }, + { min: [-1, 0, 0], max: [1, 0, 0], result: true }, + { min: [-1, 0, 0], max: [1, 0, 1], result: true }, + { min: [-1, 0, 0], max: [1, 1, -1], result: false }, + { min: [-1, 0, 0], max: [1, 1, 0], result: true }, + { min: [-1, 0, 0], max: [1, 1, 1], result: true }, + { min: [-1, 0, 1], max: [-1, -1, -1], result: false }, + { min: [0, -1, -1], max: [0, 0, -1], result: false }, + { min: [0, -1, -1], max: [0, 0, 0], result: true }, + { min: [0, -1, -1], max: [0, 0, 1], result: true }, + { min: [0, -1, -1], max: [0, 1, -1], result: false }, + { min: [0, -1, -1], max: [0, 1, 0], result: true }, + { min: [0, -1, -1], max: [0, 1, 1], result: true }, + { min: [0, -1, -1], max: [1, -1, -1], result: false }, + { min: [0, -1, -1], max: [1, 0, -1], result: false }, + { min: [0, -1, -1], max: [1, 0, 0], result: true }, + { min: [0, -1, -1], max: [1, 0, 1], result: true }, + { min: [0, -1, -1], max: [1, 1, -1], result: false }, + { min: [0, -1, -1], max: [1, 1, 0], result: true }, + { min: [0, -1, -1], max: [1, 1, 1], result: true }, + { min: [0, -1, 0], max: [-1, -1, -1], result: false }, + { min: [0, -1, 0], max: [0, 0, -1], result: false }, + { min: [0, -1, 0], max: [0, 0, 0], result: true }, + { min: [0, -1, 0], max: [0, 0, 1], result: true }, + { min: [0, -1, 0], max: [0, 1, -1], result: false }, + { min: [0, -1, 0], max: [0, 1, 0], result: true }, + { min: [0, -1, 0], max: [0, 1, 1], result: true }, + { min: [0, -1, 0], max: [1, -1, -1], result: false }, + { min: [0, -1, 0], max: [1, 0, -1], result: false }, + { min: [0, -1, 0], max: [1, 0, 0], result: true }, + { min: [0, -1, 0], max: [1, 0, 1], result: true }, + { min: [0, -1, 0], max: [1, 1, -1], result: false }, + { min: [0, -1, 0], max: [1, 1, 0], result: true }, + { min: [0, -1, 0], max: [1, 1, 1], result: true }, + { min: [0, -1, 1], max: [-1, -1, -1], result: false }, + { min: [0, 0, -1], max: [0, 0, -1], result: false }, + { min: [0, 0, -1], max: [0, 0, 0], result: true }, + { min: [0, 0, -1], max: [0, 0, 1], result: true }, + { min: [0, 0, -1], max: [0, 1, -1], result: false }, + { min: [0, 0, -1], max: [0, 1, 0], result: true }, + { min: [0, 0, -1], max: [0, 1, 1], result: true }, + { min: [0, 0, -1], max: [1, -1, -1], result: false }, + { min: [0, 0, -1], max: [1, 0, -1], result: false }, + { min: [0, 0, -1], max: [1, 0, 0], result: true }, + { min: [0, 0, -1], max: [1, 0, 1], result: true }, + { min: [0, 0, -1], max: [1, 1, -1], result: false }, + { min: [0, 0, -1], max: [1, 1, 0], result: true }, + { min: [0, 0, -1], max: [1, 1, 1], result: true }, + { min: [0, 0, 0], max: [-1, -1, -1], result: false }, + { min: [0, 0, 0], max: [0, 0, -1], result: false }, + { min: [0, 0, 0], max: [0, 0, 0], result: true }, + { min: [0, 0, 0], max: [0, 0, 1], result: true }, + { min: [0, 0, 0], max: [0, 1, -1], result: false }, + { min: [0, 0, 0], max: [0, 1, 0], result: true }, + { min: [0, 0, 0], max: [0, 1, 1], result: true }, + { min: [0, 0, 0], max: [1, -1, -1], result: false }, + { min: [0, 0, 0], max: [1, 0, -1], result: false }, + { min: [0, 0, 0], max: [1, 0, 0], result: true }, + { min: [0, 0, 0], max: [1, 0, 1], result: true }, + { min: [0, 0, 0], max: [1, 1, -1], result: false }, + { min: [0, 0, 0], max: [1, 1, 0], result: true }, + { min: [0, 0, 0], max: [1, 1, 1], result: true }, + { min: [0, 0, 1], max: [-1, -1, -1], result: false }, + { min: [0, 1, -1], max: [-1, -1, -1], result: false }, + { min: [0, 1, 0], max: [-1, -1, -1], result: false }, + { min: [0, 1, 1], max: [-1, -1, -1], result: false }, + { min: [1, -1, -1], max: [-1, -1, -1], result: false }, + { min: [1, -1, 0], max: [-1, -1, -1], result: false }, + { min: [1, -1, 1], max: [-1, -1, -1], result: false }, + { min: [1, 0, -1], max: [-1, -1, -1], result: false }, + { min: [1, 0, 0], max: [-1, -1, -1], result: false }, + { min: [1, 0, 1], max: [-1, -1, -1], result: false }, + { min: [1, 1, -1], max: [-1, -1, -1], result: false }, + { min: [1, 1, 0], max: [-1, -1, -1], result: false }, + { min: [1, 1, 1], max: [-1, -1, -1], result: false }, + { min: [1, 1, 1], max: [1, 1, 1], result: false }, + ]; + + boundingBoxLimits.forEach((boundingBoxLimit, index) => { + it("should return correct value for test case " + index, () => { + const boundingInfo = { + boundingBox: { + maximumWorld: { + x: boundingBoxLimit.max[0], + y: boundingBoxLimit.max[1], + z: boundingBoxLimit.max[2], + }, + minimumWorld: { + x: boundingBoxLimit.min[0], + y: boundingBoxLimit.min[1], + z: boundingBoxLimit.min[2], + }, + } as BoundingBox, + } as BoundingInfo; + + const mesh = { + hasBoundingInfo: true, + getBoundingInfo: () => boundingInfo, + } as Mesh; + + const result = cloudPointWithMesh.intersectsMesh(mesh, false); + + expect(result).toEqual(boundingBoxLimit.result); + }); + }); + }); + }); +});