Skip to content

Commit df46000

Browse files
committed
Added AABBUtils and Vector3Utils.ceil
1 parent dbbe537 commit df46000

File tree

11 files changed

+539
-3
lines changed

11 files changed

+539
-3
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import type { Vector3 } from '@minecraft/server';
5+
6+
export class BlockVolume {
7+
constructor(from: Vector3, to: Vector3) {
8+
this.from = from;
9+
this.to = to;
10+
11+
this.from.x = Math.floor(this.from.x);
12+
this.from.y = Math.floor(this.from.y);
13+
this.from.z = Math.floor(this.from.z);
14+
15+
this.to.x = Math.floor(this.to.x);
16+
this.to.y = Math.floor(this.to.y);
17+
this.to.z = Math.floor(this.to.z);
18+
}
19+
20+
from: Vector3;
21+
to: Vector3;
22+
}
23+
24+
export const createMockServerBindings = () => {
25+
return { BlockVolume };
26+
};

libraries/math/api-extractor.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
*/
44
{
55
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
6-
"extends": "@minecraft/api-extractor-base/api-extractor-base.json"
6+
"extends": "@minecraft/api-extractor-base/api-extractor-base.json",
7+
"mainEntryPointFilePath": "<projectFolder>/temp/types/src/index.d.ts"
78
}

libraries/math/api-report/math.api.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,35 @@
44
55
```ts
66

7+
import { BlockVolume } from '@minecraft/server';
78
import type { Vector2 } from '@minecraft/server';
89
import type { Vector3 } from '@minecraft/server';
910

11+
// @public
12+
export interface AABB {
13+
// (undocumented)
14+
center: Vector3;
15+
// (undocumented)
16+
extent: Vector3;
17+
}
18+
19+
// @public
20+
export class AABBUtils {
21+
static createFromCornerPoints(pointA: Vector3, pointB: Vector3): AABB;
22+
static dilate(aabb: AABB, size: Vector3): AABB;
23+
static equals(aabb: AABB, other: AABB): boolean;
24+
static expand(aabb: AABB, other: AABB): AABB;
25+
static getBlockVolume(aabb: AABB): BlockVolume;
26+
static getIntersection(aabb: AABB, other: AABB): AABB | undefined;
27+
static getMax(aabb: AABB): Vector3;
28+
static getMin(aabb: AABB): Vector3;
29+
static getSpan(aabb: AABB): Vector3;
30+
static intersects(aabb: AABB, other: AABB): boolean;
31+
static isInside(aabb: AABB, pos: Vector3): boolean;
32+
static isValid(box: AABB): boolean;
33+
static translate(aabb: AABB, delta: Vector3): AABB;
34+
}
35+
1036
// @public
1137
export function clampNumber(val: number, min: number, max: number): number;
1238

@@ -121,6 +147,7 @@ export class Vector3Builder implements Vector3 {
121147
// @public
122148
export class Vector3Utils {
123149
static add(v1: Vector3, v2: Partial<Vector3>): Vector3;
150+
static ceil(v: Vector3): Vector3;
124151
static clamp(v: Vector3, limits?: {
125152
min?: Partial<Vector3>;
126153
max?: Partial<Vector3>;

libraries/math/just.config.cts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ task('typescript', tscTask());
2424
task('api-extractor-local', apiExtractorTask('./api-extractor.json', isOnlyBuild /* localBuild */));
2525
task('bundle', () => {
2626
execSync(
27-
'npx esbuild ./lib/index.js --bundle --outfile=dist/minecraft-math.js --format=esm --sourcemap --external:@minecraft/server'
27+
'npx esbuild ./lib/src/index.js --bundle --outfile=dist/minecraft-math.js --format=esm --sourcemap --external:@minecraft/server',
2828
);
2929
// Copy over type definitions and rename
3030
const officialTypes = JSON.parse(readFileSync('./package.json', 'utf-8'))['types'];
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import { describe, expect, it, vi } from 'vitest';
5+
import { createMockServerBindings } from '../../__mocks__/minecraft-server.js';
6+
vi.mock('@minecraft/server', () => createMockServerBindings());
7+
8+
import type { Vector3 } from '@minecraft/server';
9+
import { VECTOR3_FORWARD, VECTOR3_ONE, VECTOR3_ZERO, Vector3Utils } from '../vector3/coreHelpers.js';
10+
import { AABB, AABBUtils } from './coreHelpers.js';
11+
12+
describe('AABB factories', () => {
13+
it('successfully reports invalid AABB when created from identical corner points', () => {
14+
const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ONE, VECTOR3_ONE);
15+
expect(AABBUtils.isValid(aabb)).toBe(false);
16+
});
17+
18+
it('successfully reports expected AABB when corner point A is less than B', () => {
19+
const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ZERO, VECTOR3_ONE);
20+
const expectedCenter = { x: 0.5, y: 0.5, z: 0.5 };
21+
const expectedextent = { x: 0.5, y: 0.5, z: 0.5 };
22+
expect(AABBUtils.isValid(aabb)).toBe(true);
23+
expect(Vector3Utils.equals(aabb.center, expectedCenter)).toBe(true);
24+
expect(Vector3Utils.equals(aabb.extent, expectedextent)).toBe(true);
25+
});
26+
27+
it('successfully reports expected AABB when corner point B is less than A', () => {
28+
const aabb = AABBUtils.createFromCornerPoints(VECTOR3_ONE, VECTOR3_ZERO);
29+
const expectedCenter = { x: 0.5, y: 0.5, z: 0.5 };
30+
const expectedextent = { x: 0.5, y: 0.5, z: 0.5 };
31+
expect(AABBUtils.isValid(aabb)).toBe(true);
32+
expect(Vector3Utils.equals(aabb.center, expectedCenter)).toBe(true);
33+
expect(Vector3Utils.equals(aabb.extent, expectedextent)).toBe(true);
34+
});
35+
});
36+
37+
describe('AABB operations', () => {
38+
const validAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
39+
const invalidAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ZERO };
40+
41+
it('successfully reports zero extent AABB as invalid', () => {
42+
expect(AABBUtils.isValid(invalidAABB)).toBe(false);
43+
});
44+
45+
it('successfully reports non-zero extent AABB as valid', () => {
46+
expect(AABBUtils.isValid(validAABB)).toBe(true);
47+
});
48+
49+
it('successfully compares AABBs with different centers as not equal', () => {
50+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
51+
const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE };
52+
expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false);
53+
});
54+
55+
it('successfully compares AABBs with different extent as not equal', () => {
56+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
57+
const secondAABB: AABB = { center: VECTOR3_ZERO, extent: { x: 2.0, y: 2.0, z: 2.0 } };
58+
expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false);
59+
});
60+
61+
it('successfully compares AABBs with different center and extent as not equal', () => {
62+
const firstAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE };
63+
const secondAABB: AABB = { center: VECTOR3_ZERO, extent: { x: 2.0, y: 2.0, z: 2.0 } };
64+
expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(false);
65+
});
66+
67+
it('successfully compares AABBs with same center and extent as equal', () => {
68+
const firstAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE };
69+
const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE };
70+
expect(AABBUtils.equals(firstAABB, secondAABB)).toBe(true);
71+
});
72+
73+
it('successfully returns expected min Vector3', () => {
74+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
75+
const min = AABBUtils.getMin(aabb);
76+
expect(Vector3Utils.equals(min, { x: -1.0, y: -1.0, z: -1.0 })).toBe(true);
77+
});
78+
79+
it('successfully returns expected max Vector3', () => {
80+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
81+
const max = AABBUtils.getMax(aabb);
82+
expect(Vector3Utils.equals(max, { x: 1.0, y: 1.0, z: 1.0 })).toBe(true);
83+
});
84+
85+
it('successfully returns expected span Vector3', () => {
86+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
87+
const span = AABBUtils.getSpan(aabb);
88+
expect(Vector3Utils.equals(span, { x: 2.0, y: 2.0, z: 2.0 })).toBe(true);
89+
});
90+
91+
it('successfully translates AABB center not changing extent', () => {
92+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
93+
const translatedAABB = AABBUtils.translate(aabb, VECTOR3_FORWARD);
94+
expect(Vector3Utils.equals(translatedAABB.center, { x: 0.0, y: 0.0, z: 1.0 })).toBe(true);
95+
expect(Vector3Utils.equals(translatedAABB.extent, VECTOR3_ONE)).toBe(true);
96+
});
97+
98+
it('successfully dilates AABB extent not changing center', () => {
99+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
100+
const dilatedAABB = AABBUtils.dilate(aabb, VECTOR3_ONE);
101+
expect(Vector3Utils.equals(dilatedAABB.center, VECTOR3_ZERO)).toBe(true);
102+
expect(Vector3Utils.equals(dilatedAABB.extent, { x: 2.0, y: 2.0, z: 2.0 })).toBe(true);
103+
});
104+
105+
// TODO: This may need a matrix of tests for different situations
106+
it('successfully expands AABB with other AABB', () => {
107+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
108+
const secondAABB: AABB = { center: VECTOR3_ONE, extent: VECTOR3_ONE };
109+
const expandedAABB = AABBUtils.expand(firstAABB, secondAABB);
110+
expect(Vector3Utils.equals(expandedAABB.center, { x: 0.5, y: 0.5, z: 0.5 })).toBe(true);
111+
expect(Vector3Utils.equals(expandedAABB.extent, { x: 1.5, y: 1.5, z: 1.5 })).toBe(true);
112+
});
113+
114+
it('successfully reports non-overlapping AABBs as not intersecting', () => {
115+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
116+
const secondAABB: AABB = { center: { x: 2.0, y: 2.0, z: 2.0 }, extent: { x: 0.5, y: 0.5, z: 0.5 } };
117+
expect(AABBUtils.intersects(firstAABB, secondAABB)).toBe(false);
118+
});
119+
120+
it('successfully reports overlapping AABBs as intersecting', () => {
121+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
122+
const secondAABB: AABB = { center: VECTOR3_ONE, extent: { x: 0.5, y: 0.5, z: 0.5 } };
123+
expect(AABBUtils.intersects(firstAABB, secondAABB)).toBe(true);
124+
});
125+
126+
it('successfully reports Vector3 outside AABB as not inside', () => {
127+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
128+
const location: Vector3 = { x: 1.1, y: 1.0, z: 1.0 };
129+
expect(AABBUtils.isInside(aabb, location)).toBe(false);
130+
});
131+
132+
it('successfully reports Vector3 inside of AABB as inside', () => {
133+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
134+
const location: Vector3 = { x: 1.0, y: 1.0, z: 1.0 };
135+
expect(AABBUtils.isInside(aabb, location)).toBe(true);
136+
});
137+
138+
it('successfully reports correct intersecting AABB for overlapping AABBs', () => {
139+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
140+
const secondAABB: AABB = { center: VECTOR3_ONE, extent: { x: 0.5, y: 0.5, z: 0.5 } };
141+
const intersection = AABBUtils.getIntersection(firstAABB, secondAABB);
142+
expect(intersection).toBeDefined();
143+
if (intersection !== undefined) {
144+
expect(Vector3Utils.equals(intersection.center, { x: 0.75, y: 0.75, z: 0.75 })).toBe(true);
145+
expect(Vector3Utils.equals(intersection.extent, { x: 0.25, y: 0.25, z: 0.25 })).toBe(true);
146+
}
147+
});
148+
149+
it('successfully reports undefined AABB for non-overlapping AABBs', () => {
150+
const firstAABB: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
151+
const secondAABB: AABB = { center: { x: 2.0, y: 2.0, z: 2.0 }, extent: { x: 0.5, y: 0.5, z: 0.5 } };
152+
const intersection = AABBUtils.getIntersection(firstAABB, secondAABB);
153+
expect(intersection).toBeUndefined();
154+
});
155+
});
156+
157+
describe('AABB BlockVolume operations', () => {
158+
it('successfully creates a BlockVolume when AABB extent are VECTOR3_ONE', () => {
159+
const aabb: AABB = { center: VECTOR3_ZERO, extent: VECTOR3_ONE };
160+
const blockVolume = AABBUtils.getBlockVolume(aabb);
161+
expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 });
162+
expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 });
163+
});
164+
165+
it('successfully creates a BlockVolume when AABB extent coords are 0.5', () => {
166+
const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.5, y: 0.5, z: 0.5 } };
167+
const blockVolume = AABBUtils.getBlockVolume(aabb);
168+
expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 });
169+
expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 });
170+
});
171+
172+
it('successfully creates a BlockVolume when AABB center and extent coords are 0.5', () => {
173+
const aabb: AABB = { center: { x: 0.5, y: 0.5, z: 0.5 }, extent: { x: 0.5, y: 0.5, z: 0.5 } };
174+
const blockVolume = AABBUtils.getBlockVolume(aabb);
175+
expect(blockVolume.from).toEqual({ x: 0.0, y: 0.0, z: 0.0 });
176+
expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 });
177+
});
178+
179+
it('successfully creates a BlockVolume when AABB center coords are -0.5 and extent coords are 0.5', () => {
180+
const aabb: AABB = { center: { x: -0.5, y: -0.5, z: -0.5 }, extent: { x: 0.5, y: 0.5, z: 0.5 } };
181+
const blockVolume = AABBUtils.getBlockVolume(aabb);
182+
expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 });
183+
expect(blockVolume.to).toEqual({ x: -0.0, y: -0.0, z: -0.0 });
184+
});
185+
186+
it('successfully creates a BlockVolume when AABB extent are greater than VECTOR3_ZERO within epsilon', () => {
187+
const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.00001, y: 0.00001, z: 0.00001 } };
188+
const blockVolume = AABBUtils.getBlockVolume(aabb);
189+
expect(blockVolume.from).toEqual({ x: 0.0, y: 0.0, z: 0.0 });
190+
expect(blockVolume.to).toEqual({ x: 0.0, y: 0.0, z: 0.0 });
191+
});
192+
193+
it('successfully creates a BlockVolume when AABB extent are greater than VECTOR3_ZERO exceeding epsilon', () => {
194+
const aabb: AABB = { center: VECTOR3_ZERO, extent: { x: 0.00002, y: 0.00002, z: 0.00002 } };
195+
const blockVolume = AABBUtils.getBlockVolume(aabb);
196+
expect(blockVolume.from).toEqual({ x: -1.0, y: -1.0, z: -1.0 });
197+
expect(blockVolume.to).toEqual({ x: 1.0, y: 1.0, z: 1.0 });
198+
});
199+
});

0 commit comments

Comments
 (0)