diff --git a/change/@minecraft-math-13da4e91-9583-4450-b4b9-a00da5a6a7c9.json b/change/@minecraft-math-13da4e91-9583-4450-b4b9-a00da5a6a7c9.json new file mode 100644 index 0000000..afc7248 --- /dev/null +++ b/change/@minecraft-math-13da4e91-9583-4450-b4b9-a00da5a6a7c9.json @@ -0,0 +1,7 @@ +{ + "type": "none", + "comment": "Added AABBUtils for performing functional operations on AABB objects, added Vector3Utils.ceil", + "packageName": "@minecraft/math", + "email": "niamh.cuileann@skyboxlabs.com", + "dependentChangeType": "none" +} diff --git a/libraries/math/__mocks__/minecraft-server.ts b/libraries/math/__mocks__/minecraft-server.ts new file mode 100644 index 0000000..ed8e82a --- /dev/null +++ b/libraries/math/__mocks__/minecraft-server.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import type { Vector3 } from '@minecraft/server'; + +export class BlockVolume { + constructor(from: Vector3, to: Vector3) { + this.from = from; + this.to = to; + + this.from.x = Math.floor(this.from.x); + this.from.y = Math.floor(this.from.y); + this.from.z = Math.floor(this.from.z); + + this.to.x = Math.floor(this.to.x); + this.to.y = Math.floor(this.to.y); + this.to.z = Math.floor(this.to.z); + } + + from: Vector3; + to: Vector3; +} + +export const createMockServerBindings = () => { + return { BlockVolume }; +}; diff --git a/libraries/math/api-extractor.json b/libraries/math/api-extractor.json index 6ca38f6..a7deb6c 100644 --- a/libraries/math/api-extractor.json +++ b/libraries/math/api-extractor.json @@ -3,5 +3,6 @@ */ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "extends": "@minecraft/api-extractor-base/api-extractor-base.json" + "extends": "@minecraft/api-extractor-base/api-extractor-base.json", + "mainEntryPointFilePath": "/temp/types/src/index.d.ts" } diff --git a/libraries/math/api-report/math.api.md b/libraries/math/api-report/math.api.md index 927e951..1ef731e 100644 --- a/libraries/math/api-report/math.api.md +++ b/libraries/math/api-report/math.api.md @@ -4,9 +4,35 @@ ```ts +import { BlockVolume } from '@minecraft/server'; import type { Vector2 } from '@minecraft/server'; import type { Vector3 } from '@minecraft/server'; +// @public +export interface AABB { + // (undocumented) + center: Vector3; + // (undocumented) + extent: Vector3; +} + +// @public +export class AABBUtils { + static createFromCornerPoints(pointA: Vector3, pointB: Vector3): AABB; + static dilate(aabb: AABB, size: Vector3): AABB; + static equals(aabb: AABB, other: AABB): boolean; + static expand(aabb: AABB, other: AABB): AABB; + static getBlockVolume(aabb: AABB): BlockVolume; + static getIntersection(aabb: AABB, other: AABB): AABB | undefined; + static getMax(aabb: AABB): Vector3; + static getMin(aabb: AABB): Vector3; + static getSpan(aabb: AABB): Vector3; + static intersects(aabb: AABB, other: AABB): boolean; + static isInside(aabb: AABB, pos: Vector3): boolean; + static isValid(box: AABB): boolean; + static translate(aabb: AABB, delta: Vector3): AABB; +} + // @public export function clampNumber(val: number, min: number, max: number): number; @@ -121,6 +147,7 @@ export class Vector3Builder implements Vector3 { // @public export class Vector3Utils { static add(v1: Vector3, v2: Partial): Vector3; + static ceil(v: Vector3): Vector3; static clamp(v: Vector3, limits?: { min?: Partial; max?: Partial; diff --git a/libraries/math/just.config.cts b/libraries/math/just.config.cts index 85da464..4fb25aa 100644 --- a/libraries/math/just.config.cts +++ b/libraries/math/just.config.cts @@ -24,7 +24,7 @@ task('typescript', tscTask()); task('api-extractor-local', apiExtractorTask('./api-extractor.json', isOnlyBuild /* localBuild */)); task('bundle', () => { execSync( - 'npx esbuild ./lib/index.js --bundle --outfile=dist/minecraft-math.js --format=esm --sourcemap --external:@minecraft/server' + 'npx esbuild ./lib/src/index.js --bundle --outfile=dist/minecraft-math.js --format=esm --sourcemap --external:@minecraft/server', ); // Copy over type definitions and rename const officialTypes = JSON.parse(readFileSync('./package.json', 'utf-8'))['types']; diff --git a/libraries/math/src/aabb/coreHelpers.test.ts b/libraries/math/src/aabb/coreHelpers.test.ts new file mode 100644 index 0000000..c0f860c --- /dev/null +++ b/libraries/math/src/aabb/coreHelpers.test.ts @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { describe, expect, it, vi } from 'vitest'; +import { createMockServerBindings } from '../../__mocks__/minecraft-server.js'; +vi.mock('@minecraft/server', () => createMockServerBindings()); + +import type { Vector3 } from '@minecraft/server'; +import { VECTOR3_FORWARD, VECTOR3_ONE, VECTOR3_ZERO, Vector3Utils } from '../vector3/coreHelpers.js'; +import { AABB, AABBUtils } from './coreHelpers.js'; + +describe('AABB factories', () => { + it('successfully reports invalid AABB when created from identical corner points', () => { + const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ONE, VECTOR3_ONE); + expect(AABBUtils.isValid(aabb)).toBe(false); + }); + + it('successfully reports expected AABB when corner point A is less than B', () => { + const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ZERO, VECTOR3_ONE); + const expectedCenter = { x: 0.5, y: 0.5, z: 0.5 }; + const expectedextent = { x: 0.5, y: 0.5, z: 0.5 }; + expect(AABBUtils.isValid(aabb)).toBe(true); + expect(Vector3Utils.equals(aabb.center, expectedCenter)).toBe(true); + expect(Vector3Utils.equals(aabb.extent, expectedextent)).toBe(true); + }); + + it('successfully reports expected AABB when corner point B is less than A', () => { + const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ONE, VECTOR3_ZERO); + const expectedCenter = { x: 0.5, y: 0.5, z: 0.5 }; + const expectedextent = { x: 0.5, y: 0.5, z: 0.5 }; + expect(AABBUtils.isValid(aabb)).toBe(true); + expect(Vector3Utils.equals(aabb.center, expectedCenter)).toBe(true); + expect(Vector3Utils.equals(aabb.extent, expectedextent)).toBe(true); + }); +}); + +describe('AABB operations', () => { + const validAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const invalidAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ZERO }; + + it('successfully reports zero extent AABB as invalid', () => { + expect(AABBUtils.isValid(invalidAABB)).toBe(false); + }); + + it('successfully reports non-zero extent AABB as valid', () => { + expect(AABBUtils.isValid(validAABB)).toBe(true); + }); + + it('successfully compares AABBs with different centers as not equal', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE }; + expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false); + }); + + it('successfully compares AABBs with different extent as not equal', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ZERO, extent: { x: 2.0, y: 2.0, z: 2.0 } }; + expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false); + }); + + it('successfully compares AABBs with different center and extent as not equal', () => { + const firstAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ZERO, extent: { x: 2.0, y: 2.0, z: 2.0 } }; + expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false); + }); + + it('successfully compares AABBs with same center and extent as equal', () => { + const firstAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE }; + expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(true); + }); + + it('successfully returns expected min Vector3', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const min = AABBUtils.getMin(aabb); + expect(Vector3Utils.equals(min, { x: -1.0, y: -1.0, z: -1.0 })).toBe(true); + }); + + it('successfully returns expected max Vector3', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const max = AABBUtils.getMax(aabb); + expect(Vector3Utils.equals(max, { x: 1.0, y: 1.0, z: 1.0 })).toBe(true); + }); + + it('successfully returns expected span Vector3', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const span = AABBUtils.getSpan(aabb); + expect(Vector3Utils.equals(span, { x: 2.0, y: 2.0, z: 2.0 })).toBe(true); + }); + + it('successfully translates AABB center not changing extent', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const translatedAABB = AABBUtils.translate(aabb, VECTOR3_FORWARD); + expect(Vector3Utils.equals(translatedAABB.center, { x: 0.0, y: 0.0, z: 1.0 })).toBe(true); + expect(Vector3Utils.equals(translatedAABB.extent, VECTOR3_ONE)).toBe(true); + }); + + it('successfully dilates AABB extent not changing center', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const dilatedAABB = AABBUtils.dilate(aabb, VECTOR3_ONE); + expect(Vector3Utils.equals(dilatedAABB.center, VECTOR3_ZERO)).toBe(true); + expect(Vector3Utils.equals(dilatedAABB.extent, { x: 2.0, y: 2.0, z: 2.0 })).toBe(true); + }); + + // TODO: This may need a matrix of tests for different situations + it('successfully expands AABB with other AABB', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE }; + const expandedAABB = AABBUtils.expand(firstAABB, secondAABB); + expect(Vector3Utils.equals(expandedAABB.center, { x: 0.5, y: 0.5, z: 0.5 })).toBe(true); + expect(Vector3Utils.equals(expandedAABB.extent, { x: 1.5, y: 1.5, z: 1.5 })).toBe(true); + }); + + it('successfully reports non-overlapping AABBs as not intersecting', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: { x: 2.0, y: 2.0, z: 2.0 }, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + expect(AABBUtils.intersects(firstAABB, secondAABB)).toBe(false); + }); + + it('successfully reports overlapping AABBs as intersecting', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ONE, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + expect(AABBUtils.intersects(firstAABB, secondAABB)).toBe(true); + }); + + it('successfully reports Vector3 outside AABB as not inside', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const location: Vector3 = { x: 1.1, y: 1.0, z: 1.0 }; + expect(AABBUtils.isInside(aabb, location)).toBe(false); + }); + + it('successfully reports Vector3 inside of AABB as inside', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const location: Vector3 = { x: 1.0, y: 1.0, z: 1.0 }; + expect(AABBUtils.isInside(aabb, location)).toBe(true); + }); + + it('successfully reports correct intersecting AABB for overlapping AABBs', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: VECTOR3_ONE, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + const intersection = AABBUtils.getIntersection(firstAABB, secondAABB); + expect(intersection).toBeDefined(); + if (intersection !== undefined) { + expect(Vector3Utils.equals(intersection.center, { x: 0.75, y: 0.75, z: 0.75 })).toBe(true); + expect(Vector3Utils.equals(intersection.extent, { x: 0.25, y: 0.25, z: 0.25 })).toBe(true); + } + }); + + it('successfully reports undefined AABB for non-overlapping AABBs', () => { + const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const secondAABB: AABB = { center: { x: 2.0, y: 2.0, z: 2.0 }, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + const intersection = AABBUtils.getIntersection(firstAABB, secondAABB); + expect(intersection).toBeUndefined(); + }); +}); + +describe('AABB BlockVolume operations', () => { + it('successfully creates a BlockVolume when AABB extent are VECTOR3_ONE', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 }); + expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 }); + }); + + it('successfully creates a BlockVolume when AABB extent coords are 0.5', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 }); + expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 }); + }); + + it('successfully creates a BlockVolume when AABB center and extent coords are 0.5', () => { + const aabb: AABB = { center: { x: 0.5, y: 0.5, z: 0.5 }, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: 0.0, y: 0.0, z: 0.0 }); + expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 }); + }); + + it('successfully creates a BlockVolume when AABB center coords are -0.5 and extent coords are 0.5', () => { + const aabb: AABB = { center: { x: -0.5, y: -0.5, z: -0.5 }, extent: { x: 0.5, y: 0.5, z: 0.5 } }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 }); + expect(blockVolume.to).toEqual({ x: -0.0, y: -0.0, z: -0.0 }); + }); + + it('successfully creates a BlockVolume when AABB extent are greater than VECTOR3_ZERO within epsilon', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.00001, y: 0.00001, z: 0.00001 } }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: 0.0, y: 0.0, z: 0.0 }); + expect(blockVolume.to).toEqual({ x: 0.0, y: 0.0, z: 0.0 }); + }); + + it('successfully creates a BlockVolume when AABB extent are greater than VECTOR3_ZERO exceeding epsilon', () => { + const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.00002, y: 0.00002, z: 0.00002 } }; + const blockVolume = AABBUtils.getBlockVolume(aabb); + expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 }); + expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 }); + }); +}); diff --git a/libraries/math/src/aabb/coreHelpers.ts b/libraries/math/src/aabb/coreHelpers.ts new file mode 100644 index 0000000..dfd6864 --- /dev/null +++ b/libraries/math/src/aabb/coreHelpers.ts @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import type { Vector3 } from '@minecraft/server'; +import { BlockVolume } from '@minecraft/server'; +import { Vector3Utils } from '../vector3/coreHelpers.js'; + +/** + * TEMP: Interface until AABB is available + * + * @public + */ +export interface AABB { + center: Vector3; + extent: Vector3; +} + +/** + * Utilities operating on AABB objects. All methods are static and do not modify the input objects. + * + * @public + */ +export class AABBUtils { + private constructor() {} + + /** + * createFromCornerPoints + * + * Gets an AABB from points defining it's corners, the order doesn't matter. + * @param pointA - The first corner point. + * @param pointB - The second corner point. + * @returns - The resulting AABB. + */ + static createFromCornerPoints(pointA: Vector3, pointB: Vector3): AABB { + const min: Vector3 = { + x: Math.min(pointA.x, pointB.x), + y: Math.min(pointA.y, pointB.y), + z: Math.min(pointA.z, pointB.z), + }; + const max: Vector3 = { + x: Math.max(pointA.x, pointB.x), + y: Math.max(pointA.y, pointB.y), + z: Math.max(pointA.z, pointB.z), + }; + + const extent = Vector3Utils.multiply(Vector3Utils.subtract(max, min), { x: 0.5, y: 0.5, z: 0.5 }); + const aabb: AABB = { center: Vector3Utils.add(min, extent), extent: extent }; + return aabb; + } + + /** + * isValid + * + * Determines if the AABB has non-zero extent on all axes. + * @param box - The AABB to test for validity. + * @returns - True if all extent axes are non-zero, otherwise false. + */ + static isValid(box: AABB): boolean { + return box.extent.x > 0.0 && box.extent.y > 0.0 && box.extent.z > 0.0; + } + + /** + * equals + * + * Compares the equality of two AABBs. + * @param aabb - The first AABB in the comparison. + * @param other - The second AABB in the comparison. + * @returns - True if the center and extent of both AABBs are equal. + */ + static equals(aabb: AABB, other: AABB): boolean { + return Vector3Utils.equals(aabb.center, other.center) && Vector3Utils.equals(aabb.extent, other.extent); + } + + /** + * getMin + * + * Gets the minimum corner of an AABB. + * @param aabb - The AABB to retrieve the minimum corner of. + * @returns - The minimum corner of the AABB. + */ + static getMin(aabb: AABB): Vector3 { + return Vector3Utils.subtract(aabb.center, aabb.extent); + } + + /** + * getMax + * + * Gets the maximum corner of an AABB. + * @param aabb - The AABB to retrieve the maximum corner of. + * @returns - The maximum corner of the AABB. + */ + static getMax(aabb: AABB): Vector3 { + return Vector3Utils.add(aabb.center, aabb.extent); + } + + /** + * getSpan + * + * Gets the span of an AABB. + * @param aabb - The AABB to retrieve the span of. + * @returns - The span of the AABB. + */ + static getSpan(aabb: AABB): Vector3 { + return Vector3Utils.multiply(aabb.extent, { x: 2.0, y: 2.0, z: 2.0 }); + } + + /** + * Creates the smallest BlockVolume that includes all of a source AABB. + * + * @param aabb - The source AABB. + * @returns - The BlockVolume containing the source AABB. + */ + static getBlockVolume(aabb: AABB): BlockVolume { + const epsilon = 0.00001; + const epsilonVec: Vector3 = { x: epsilon, y: epsilon, z: epsilon }; + const from = Vector3Utils.floor(Vector3Utils.add(this.getMin(aabb), epsilonVec)); + const to = Vector3Utils.ceil(Vector3Utils.subtract(this.getMax(aabb), epsilonVec)); + return new BlockVolume(from, to); + } + + /** + * translate + * + * Creates a translated AABB given a source AABB and translation vector. + * @param aabb - The source AABB. + * @param delta - The translation vector to add to the AABBs center. + * @returns - The resulting translated AABB. + */ + static translate(aabb: AABB, delta: Vector3): AABB { + return { center: Vector3Utils.add(aabb.center, delta), extent: aabb.extent }; + } + + /** + * dilate + * + * Creates a dilated AABB given a source AABB and dilation vector. + * @param aabb - The source AABB. + * @param size - The dilation vector to add to the AABBs extent. + * @returns - The resulting dilated AABB. + */ + static dilate(aabb: AABB, size: Vector3): AABB { + return { center: aabb.center, extent: Vector3Utils.add(aabb.extent, size) }; + } + + /** + * expand + * + * Creates an expanded AABB given two source AABBs. + * @param aabb - The first source AABB. + * @param other - The second source AABB + * @returns - The resulting expanded AABB. + */ + static expand(aabb: AABB, other: AABB): AABB { + const aabbMin = this.getMin(aabb); + const otherMin = this.getMin(other); + const min: Vector3 = { + x: Math.min(aabbMin.x, otherMin.x), + y: Math.min(aabbMin.y, otherMin.y), + z: Math.min(aabbMin.z, otherMin.z), + }; + const aabbMax = this.getMax(aabb); + const otherMax = this.getMax(other); + const max: Vector3 = { + x: Math.max(aabbMax.x, otherMax.x), + y: Math.max(aabbMax.y, otherMax.y), + z: Math.max(aabbMax.z, otherMax.z), + }; + return this.createFromCornerPoints(min, max); + } + + /** + * getIntersection + * + * Creates an AABB of the intersecting area of two source AABBs. + * @param aabb - The first source AABB. + * @param other - The second source AABB, + * @returns - The resulting intersecting AABB if they intersect, otherwise returns undefined. + */ + static getIntersection(aabb: AABB, other: AABB): AABB | undefined { + if (!this.intersects(aabb, other)) { + return undefined; + } + + const aabbMin = this.getMin(aabb); + const otherMin = this.getMin(other); + const min: Vector3 = { + x: Math.max(aabbMin.x, otherMin.x), + y: Math.max(aabbMin.y, otherMin.y), + z: Math.max(aabbMin.z, otherMin.z), + }; + + const aabbMax = this.getMax(aabb); + const otherMax = this.getMax(other); + const max: Vector3 = { + x: Math.min(aabbMax.x, otherMax.x), + y: Math.min(aabbMax.y, otherMax.y), + z: Math.min(aabbMax.z, otherMax.z), + }; + return this.createFromCornerPoints(min, max); + } + + /** + * intersects + * + * Calculates if two AABBs are intersecting. + * @param aabb - The first AABB. + * @param aabb - The second AABB. + * @returns - True if the AABBs are intersecting, otherwise false. + */ + static intersects(aabb: AABB, other: AABB): boolean { + if (!this.isValid(aabb) || !this.isValid(other)) { + return false; + } + + const aabbMin = this.getMin(aabb); + const aabbMax = this.getMax(aabb); + const otherMin = this.getMin(other); + const otherMax = this.getMax(other); + + if (otherMax.x < aabbMin.x || otherMin.x > aabbMax.x) { + return false; + } + if (otherMax.y < aabbMin.y || otherMin.y > aabbMax.y) { + return false; + } + if (otherMax.z < aabbMin.z || otherMin.z > aabbMax.z) { + return false; + } + return true; + } + + /** + * isInside + * + * Calculates if a position is inside of an AABB. + * @param aabb - The AABB to test against. + * @param pos - The position to test. + * @returns True if the position is inside of the AABB, otherwise returns false. + */ + static isInside(aabb: AABB, pos: Vector3): boolean { + const min = this.getMin(aabb); + if (pos.x < min.x || pos.y < min.y || pos.z < min.z) { + return false; + } + + const max = this.getMax(aabb); + if (pos.x > max.x || pos.y > max.y || pos.z > max.z) { + return false; + } + + return true; + } +} diff --git a/libraries/math/src/aabb/index.ts b/libraries/math/src/aabb/index.ts new file mode 100644 index 0000000..03db660 --- /dev/null +++ b/libraries/math/src/aabb/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export * from './coreHelpers.js'; diff --git a/libraries/math/src/index.ts b/libraries/math/src/index.ts index da3c68e..dfbd896 100644 --- a/libraries/math/src/index.ts +++ b/libraries/math/src/index.ts @@ -3,3 +3,4 @@ export * from './vector3/index.js'; export * from './general/index.js'; +export * from './aabb/index.js'; diff --git a/libraries/math/src/vector3/coreHelpers.test.ts b/libraries/math/src/vector3/coreHelpers.test.ts index c0c9dc0..ec633b0 100644 --- a/libraries/math/src/vector3/coreHelpers.test.ts +++ b/libraries/math/src/vector3/coreHelpers.test.ts @@ -88,6 +88,18 @@ describe('Vector3 operations', () => { expect(Vector3Utils.floor(input)).toEqual(expected); }); + it('computes the ceil of the vector', () => { + const input: Vector3 = { x: 0.33, y: 1.14, z: 2.55 }; + const expected: Vector3 = { x: 1, y: 2, z: 3 }; + expect(Vector3Utils.ceil(input)).toEqual(expected); + }); + + it('computes the ceil of negative vectors', () => { + const input: Vector3 = { x: -0.33, y: -1.14, z: -2.55 }; + const expected: Vector3 = { x: -0, y: -1, z: -2 }; + expect(Vector3Utils.ceil(input)).toEqual(expected); + }); + it('normalizes the vector', () => { const result: Vector3 = Vector3Utils.normalize(v1); expect(result.x).toBeCloseTo(0.27, 2); diff --git a/libraries/math/src/vector3/coreHelpers.ts b/libraries/math/src/vector3/coreHelpers.ts index 491a480..39ca1cf 100644 --- a/libraries/math/src/vector3/coreHelpers.ts +++ b/libraries/math/src/vector3/coreHelpers.ts @@ -100,6 +100,15 @@ export class Vector3Utils { return { x: Math.floor(v.x), y: Math.floor(v.y), z: Math.floor(v.z) }; } + /** + * ceil + * + * Ceil the components of a vector to produce a new vector + */ + static ceil(v: Vector3): Vector3 { + return { x: Math.ceil(v.x), y: Math.ceil(v.y), z: Math.ceil(v.z) }; + } + /** * toString * diff --git a/libraries/math/vite.config.mts b/libraries/math/vite.config.mts index c72faff..5ddb8a7 100644 --- a/libraries/math/vite.config.mts +++ b/libraries/math/vite.config.mts @@ -5,5 +5,9 @@ import { configDefaults, defineConfig } from 'vitest/config'; export default defineConfig({ - test: { exclude: [...configDefaults.exclude, '**/build/**', '**/lib/**', '**/lib-commonjs/**'], watch: false }, + test: { + exclude: [...configDefaults.exclude, '**/build/**', '**/lib/**', '**/lib-commonjs/**'], + watch: false, + alias: { '@minecraft/server': './__mocks__/minecraft-server.ts' }, + }, });