From d44d57baffbb721c01a6e67db891d98540ce7747 Mon Sep 17 00:00:00 2001 From: Marco Hutter Date: Thu, 29 Aug 2024 13:28:10 +0200 Subject: [PATCH] Added fromMinMax and transform for OBB --- .../engine/Source/Core/OrientedBoundingBox.js | 90 +++++++++++++ .../Specs/Core/OrientedBoundingBoxSpec.js | 125 ++++++++++++++++++ 2 files changed, 215 insertions(+) diff --git a/packages/engine/Source/Core/OrientedBoundingBox.js b/packages/engine/Source/Core/OrientedBoundingBox.js index c94354adfc3d..73b394193d90 100644 --- a/packages/engine/Source/Core/OrientedBoundingBox.js +++ b/packages/engine/Source/Core/OrientedBoundingBox.js @@ -240,6 +240,96 @@ OrientedBoundingBox.fromPoints = function (positions, result) { return result; }; +// A Cartesian3 that will store the scale factors for computing +// an oriented bounding box in fromMinMax +const scratchScaleFromMinMax = new Cartesian3(); + +/** + * Creates an oriented bounding box from the given minimum- and maximum + * point, stores it in the given result, and returns it. + * + * If the given result is `undefined`, then a new oriented bounding box + * will be created, filled, and returned. + * + * @param {Cartesian3} min The minimum point + * @param {Cartesian3} max The maximum point + * @param {OrientedBoundingBox} [result] The result + * @returns The result + */ +OrientedBoundingBox.fromMinMax = function (min, max, result) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("min", min); + Check.typeOf.object("max", max); + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new OrientedBoundingBox(); + } + Cartesian3.midpoint(min, max, result.center); + Cartesian3.subtract(max, min, scratchScaleFromMinMax); + Cartesian3.multiplyByScalar( + scratchScaleFromMinMax, + 0.5, + scratchScaleFromMinMax + ); + Matrix3.fromScale(scratchScaleFromMinMax, result.halfAxes); + return result; +}; + +// A Matrix3 that will store the rotation and scale components +// of a transform matrix in transform +const scratchRotationScaleTransform = new Matrix3(); + +/** + * Transforms the given oriented bounding box with the given matrix, + * stores the result in the given result parameter, and returns it. + * + * If the given result is `undefined`, then a new oriented bounding box + * will be created, filled, and returned. + * + * @param {OrientedBoundingBox} orientedBoundingBox The oriented bounding box + * @param {Matrix4} transform The transform matrix + * @param {OrientedBoundingBox} [result] The result + * @returns The result + */ +OrientedBoundingBox.transform = function ( + orientedBoundingBox, + transform, + result +) { + //>>includeStart('debug', pragmas.debug); + Check.typeOf.object("orientedBoundingBox", orientedBoundingBox); + Check.typeOf.object("transform", transform); + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new OrientedBoundingBox(); + } + Matrix4.multiplyByPoint(transform, orientedBoundingBox.center, result.center); + Matrix4.getMatrix3(transform, scratchRotationScaleTransform); + Matrix3.multiply( + scratchRotationScaleTransform, + orientedBoundingBox.halfAxes, + result.halfAxes + ); + return result; +}; + +/** + * Transforms this oriented bounding box with the given matrix, + * stores the result in the given result parameter, and returns it. + * + * If the given result is `undefined`, then a new oriented bounding box + * will be created, filled, and returned. + * + * @param {Matrix4} transform The transform matrix + * @param {OrientedBoundingBox} [result] The result + * @returns The result + */ +OrientedBoundingBox.prototype.transform = function (transform, result) { + return OrientedBoundingBox.transform(this, transform, result); +}; + const scratchOffset = new Cartesian3(); const scratchScale = new Cartesian3(); function fromPlaneExtents( diff --git a/packages/engine/Specs/Core/OrientedBoundingBoxSpec.js b/packages/engine/Specs/Core/OrientedBoundingBoxSpec.js index a424c6561c35..875981d49356 100644 --- a/packages/engine/Specs/Core/OrientedBoundingBoxSpec.js +++ b/packages/engine/Specs/Core/OrientedBoundingBoxSpec.js @@ -1,3 +1,4 @@ +/* eslint-disable no-restricted-globals */ import { BoundingSphere, Cartesian3, @@ -184,6 +185,130 @@ describe("Core/OrientedBoundingBox", function () { expect(box.center).toEqualEpsilon(translation, CesiumMath.EPSILON15); }); + fit("fromMinMax creates the right bounding box", function () { + const min = new Cartesian3(-2.0, -3.0, -4.0); + const max = new Cartesian3(2.0, 3.0, 4.0); + const box = OrientedBoundingBox.fromMinMax(min, max); + + expect(box.center).toEqualEpsilon( + new Cartesian3(0.0, 0.0, 0.0), + CesiumMath.EPSILON15 + ); + + const halfAxes = new Matrix3(2, 0, 0, 0, 3, 0, 0, 0, 4); + expect(box.halfAxes).toEqualEpsilon(halfAxes, CesiumMath.EPSILON15); + }); + + fit("fromMinMax throws without min/max", function () { + expect(function () { + OrientedBoundingBox.fromMinMax(undefined, undefined); + }).toThrowDeveloperError(); + }); + + fit("fromMinMax creates the right bounding box with a result parameter", function () { + const min = new Cartesian3(-2.0, -3.0, -4.0); + const max = new Cartesian3(2.0, 3.0, 4.0); + const result = new OrientedBoundingBox(); + const box = OrientedBoundingBox.fromMinMax(min, max, result); + + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon( + new Cartesian3(0.0, 0.0, 0.0), + CesiumMath.EPSILON15 + ); + + const halfAxes = new Matrix3(2, 0, 0, 0, 3, 0, 0, 0, 4); + expect(box.halfAxes).toEqualEpsilon(halfAxes, CesiumMath.EPSILON15); + }); + + fit("fromMinMax creates the right bounding box with a result parameter", function () { + const min = new Cartesian3(-2.0, -3.0, -4.0); + const max = new Cartesian3(2.0, 3.0, 4.0); + const result = new OrientedBoundingBox(); + const box = OrientedBoundingBox.fromMinMax(min, max, result); + + expect(box).toBe(result); + + expect(box.center).toEqualEpsilon( + new Cartesian3(0.0, 0.0, 0.0), + CesiumMath.EPSILON15 + ); + + const halfAxes = new Matrix3(2, 0, 0, 0, 3, 0, 0, 0, 4); + expect(box.halfAxes).toEqualEpsilon(halfAxes, CesiumMath.EPSILON15); + }); + + fit("transform throws without transform", function () { + expect(function () { + const box = new OrientedBoundingBox(); + OrientedBoundingBox.transform(box, undefined); + }).toThrowDeveloperError(); + }); + + fit("transform transforms the bounding box with transform", function () { + const center = new Cartesian3(1.0, 2.0, 3.0); + const halfAxes = new Matrix3(2, 0, 0, 0, 3, 0, 0, 0, 4); + const box = new OrientedBoundingBox(center, halfAxes); + + const rotation = Matrix3.fromRotationX(CesiumMath.PI / 2.0); + const translation = new Cartesian3(2.0, 3.0, 4.0); + const transform = Matrix4.fromRotationTranslation(rotation, translation); + + // The rotation transforms the center into (x, -z, y) = (1, -3, 2) + // Adding the translation of (2, 3, 4) results in (3, 0, 6) + const expectedCenter = new Cartesian3(3.0, 0.0, 6.0); + const expectedHalfAxes = new Matrix3(2, 0, 0, 0, 0, -4, 0, 3, 0); + const expectedBox = new OrientedBoundingBox( + expectedCenter, + expectedHalfAxes + ); + + const actualBox = OrientedBoundingBox.transform(box, transform); + + expect(actualBox.center).toEqualEpsilon( + expectedBox.center, + CesiumMath.EPSILON15 + ); + expect(actualBox.halfAxes).toEqualEpsilon( + expectedBox.halfAxes, + CesiumMath.EPSILON15 + ); + }); + + fit("transform transforms the bounding box with transform with a result parameter", function () { + const center = new Cartesian3(1.0, 2.0, 3.0); + const halfAxes = new Matrix3(2, 0, 0, 0, 3, 0, 0, 0, 4); + const box = new OrientedBoundingBox(center, halfAxes); + + const rotation = Matrix3.fromRotationX(CesiumMath.PI / 2.0); + const translation = new Cartesian3(2.0, 3.0, 4.0); + const transform = Matrix4.fromRotationTranslation(rotation, translation); + + // The rotation transforms the center into (x, -z, y) = (1, -3, 2) + // Adding the translation of (2, 3, 4) results in (3, 0, 6) + const expectedCenter = new Cartesian3(3.0, 0.0, 6.0); + const expectedHalfAxes = new Matrix3(2, 0, 0, 0, 0, -4, 0, 3, 0); + const expectedBox = new OrientedBoundingBox( + expectedCenter, + expectedHalfAxes + ); + + const result = new OrientedBoundingBox(); + const actualBox = OrientedBoundingBox.transform(box, transform, result); + + expect(actualBox).toBe(result); + + expect(actualBox.center).toEqualEpsilon( + expectedBox.center, + CesiumMath.EPSILON15 + ); + expect(actualBox.halfAxes).toEqualEpsilon( + expectedBox.halfAxes, + CesiumMath.EPSILON15 + ); + }); + it("fromRectangle sets correct default ellipsoid", function () { const rectangle = new Rectangle(-0.9, -1.2, 0.5, 0.7); const box1 = OrientedBoundingBox.fromRectangle(rectangle, 0.0, 0.0);