@math.gl/culling 4.2.0-alpha.3 → 4.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +51 -0
- package/README.md +8 -1
- package/dist/index.cjs +618 -89
- package/dist/index.cjs.map +3 -4
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/plane.d.ts +2 -2
- package/dist/lib/plane.d.ts.map +1 -1
- package/dist/lib/plane.js +3 -6
- package/dist/lib/plane.js.map +1 -1
- package/dist/lib/shapes/box-shape.d.ts +19 -0
- package/dist/lib/shapes/box-shape.d.ts.map +1 -0
- package/dist/lib/shapes/box-shape.js +69 -0
- package/dist/lib/shapes/box-shape.js.map +1 -0
- package/dist/lib/shapes/capsule-shape.d.ts +23 -0
- package/dist/lib/shapes/capsule-shape.d.ts.map +1 -0
- package/dist/lib/shapes/capsule-shape.js +151 -0
- package/dist/lib/shapes/capsule-shape.js.map +1 -0
- package/dist/lib/shapes/cylinder-shape.d.ts +22 -0
- package/dist/lib/shapes/cylinder-shape.d.ts.map +1 -0
- package/dist/lib/shapes/cylinder-shape.js +99 -0
- package/dist/lib/shapes/cylinder-shape.js.map +1 -0
- package/dist/lib/shapes/implicit-shape.d.ts +55 -0
- package/dist/lib/shapes/implicit-shape.d.ts.map +1 -0
- package/dist/lib/shapes/implicit-shape.js +143 -0
- package/dist/lib/shapes/implicit-shape.js.map +1 -0
- package/dist/lib/shapes/plane-shape.d.ts +26 -0
- package/dist/lib/shapes/plane-shape.d.ts.map +1 -0
- package/dist/lib/shapes/plane-shape.js +72 -0
- package/dist/lib/shapes/plane-shape.js.map +1 -0
- package/dist/lib/shapes/sphere-shape.d.ts +18 -0
- package/dist/lib/shapes/sphere-shape.d.ts.map +1 -0
- package/dist/lib/shapes/sphere-shape.js +43 -0
- package/dist/lib/shapes/sphere-shape.js.map +1 -0
- package/package.json +4 -4
- package/src/index.ts +12 -0
- package/src/lib/plane.ts +3 -6
- package/src/lib/shapes/box-shape.ts +78 -0
- package/src/lib/shapes/capsule-shape.ts +232 -0
- package/src/lib/shapes/cylinder-shape.ts +133 -0
- package/src/lib/shapes/implicit-shape.ts +209 -0
- package/src/lib/shapes/plane-shape.ts +78 -0
- package/src/lib/shapes/sphere-shape.ts +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plane-shape.js","sourceRoot":"","sources":["../../../src/lib/shapes/plane-shape.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ImplicitShapeBase, type ImplicitShape, type LocalRayIntersection } from "./implicit-shape.js";
|
|
2
|
+
export type SphereShapeProps = {
|
|
3
|
+
radius?: number;
|
|
4
|
+
matrix?: readonly number[];
|
|
5
|
+
};
|
|
6
|
+
/** Analytic glTF sphere shape. */
|
|
7
|
+
export declare class SphereShape extends ImplicitShapeBase {
|
|
8
|
+
readonly type: "sphere";
|
|
9
|
+
readonly radius: number;
|
|
10
|
+
constructor(props?: SphereShapeProps);
|
|
11
|
+
clone(): SphereShape;
|
|
12
|
+
equals(shape: ImplicitShape): boolean;
|
|
13
|
+
containsLocalPoint(point: readonly number[]): boolean;
|
|
14
|
+
distanceToLocalPoint(point: readonly number[]): number;
|
|
15
|
+
supportLocal(direction: readonly number[]): readonly number[];
|
|
16
|
+
intersectLocalRay(origin: readonly number[], direction: readonly number[]): LocalRayIntersection | undefined;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=sphere-shape.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sphere-shape.d.ts","sourceRoot":"","sources":["../../../src/lib/shapes/sphere-shape.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,iBAAiB,EAEjB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EAC1B,4BAAyB;AAI1B,MAAM,MAAM,gBAAgB,GAAG;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAC,CAAC;AAE7E,kCAAkC;AAClC,qBAAa,WAAY,SAAQ,iBAAiB;IAChD,QAAQ,CAAC,IAAI,EAAG,QAAQ,CAAU;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,KAAK,GAAE,gBAAqB;IAMxC,KAAK,IAAI,WAAW;IAGX,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO;IAG9C,kBAAkB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO;IAGrD,oBAAoB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM;IAGtD,YAAY,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE;IAI7D,iBAAiB,CACf,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,oBAAoB,GAAG,SAAS;CAWpC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
import { ImplicitShapeBase, solveQuadratic } from "./implicit-shape.js";
|
|
5
|
+
const EPSILON = 1e-6;
|
|
6
|
+
/** Analytic glTF sphere shape. */
|
|
7
|
+
export class SphereShape extends ImplicitShapeBase {
|
|
8
|
+
constructor(props = {}) {
|
|
9
|
+
super(props.matrix);
|
|
10
|
+
this.type = 'sphere';
|
|
11
|
+
this.radius = props.radius ?? 0.5;
|
|
12
|
+
if (!Number.isFinite(this.radius) || this.radius <= 0)
|
|
13
|
+
throw new Error('Sphere radius must be greater than zero');
|
|
14
|
+
}
|
|
15
|
+
clone() {
|
|
16
|
+
return new SphereShape({ radius: this.radius, matrix: this.matrix });
|
|
17
|
+
}
|
|
18
|
+
equals(shape) {
|
|
19
|
+
return shape instanceof SphereShape && super.equals(shape) && this.radius === shape.radius;
|
|
20
|
+
}
|
|
21
|
+
containsLocalPoint(point) {
|
|
22
|
+
return Math.hypot(...point) <= this.radius + EPSILON;
|
|
23
|
+
}
|
|
24
|
+
distanceToLocalPoint(point) {
|
|
25
|
+
return Math.max(0, Math.hypot(...point) - this.radius);
|
|
26
|
+
}
|
|
27
|
+
supportLocal(direction) {
|
|
28
|
+
const length = Math.hypot(...direction);
|
|
29
|
+
return length ? direction.map(value => (value / length) * this.radius) : [this.radius, 0, 0];
|
|
30
|
+
}
|
|
31
|
+
intersectLocalRay(origin, direction) {
|
|
32
|
+
const roots = solveQuadratic(dot(direction, direction), 2 * dot(origin, direction), dot(origin, origin) - this.radius * this.radius);
|
|
33
|
+
const distance = roots.find(value => value >= 0);
|
|
34
|
+
if (distance === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
const point = origin.map((value, i) => value + distance * direction[i]);
|
|
37
|
+
return { distance, normal: point.map(value => value / this.radius) };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function dot(a, b) {
|
|
41
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=sphere-shape.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sphere-shape.js","sourceRoot":"","sources":["../../../src/lib/shapes/sphere-shape.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
9
|
-
"version": "4.2.0-alpha.
|
|
9
|
+
"version": "4.2.0-alpha.5",
|
|
10
10
|
"keywords": [
|
|
11
11
|
"webgl",
|
|
12
12
|
"javascript",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"src"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@math.gl/core": "4.2.0-alpha.
|
|
46
|
-
"@math.gl/types": "4.2.0-alpha.
|
|
45
|
+
"@math.gl/core": "4.2.0-alpha.5",
|
|
46
|
+
"@math.gl/types": "4.2.0-alpha.5"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "9c13785761867748e2edb167b5f2749dce803039"
|
|
49
49
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,18 @@ export {CullingVolume} from './lib/culling-volume';
|
|
|
11
11
|
export {Plane} from './lib/plane';
|
|
12
12
|
export {Ray} from './lib/ray';
|
|
13
13
|
|
|
14
|
+
export type {ImplicitShape, RayIntersection} from './lib/shapes/implicit-shape';
|
|
15
|
+
export {BoxShape} from './lib/shapes/box-shape';
|
|
16
|
+
export type {BoxShapeProps} from './lib/shapes/box-shape';
|
|
17
|
+
export {CapsuleShape} from './lib/shapes/capsule-shape';
|
|
18
|
+
export type {CapsuleShapeProps} from './lib/shapes/capsule-shape';
|
|
19
|
+
export {CylinderShape} from './lib/shapes/cylinder-shape';
|
|
20
|
+
export type {CylinderShapeProps} from './lib/shapes/cylinder-shape';
|
|
21
|
+
export {PlaneShape} from './lib/shapes/plane-shape';
|
|
22
|
+
export type {PlaneShapeProps} from './lib/shapes/plane-shape';
|
|
23
|
+
export {SphereShape} from './lib/shapes/sphere-shape';
|
|
24
|
+
export type {SphereShapeProps} from './lib/shapes/sphere-shape';
|
|
25
|
+
|
|
14
26
|
export {PerspectiveOffCenterFrustum as _PerspectiveOffCenterFrustum} from './lib/perspective-off-center-frustum';
|
|
15
27
|
export {PerspectiveFrustum as _PerspectiveFrustum} from './lib/perspective-frustum';
|
|
16
28
|
|
package/src/lib/plane.ts
CHANGED
|
@@ -93,11 +93,9 @@ export class Plane {
|
|
|
93
93
|
*
|
|
94
94
|
* @param {Ray} ray The ray.
|
|
95
95
|
* @param {Vector3} [result] The object onto which to store the result.
|
|
96
|
-
* @returns {Vector3} The intersection point or undefined if there is no
|
|
96
|
+
* @returns {Vector3} The intersection point or undefined if there is no intersection.
|
|
97
97
|
*/
|
|
98
|
-
intersectWithRay(ray: Ray, result
|
|
99
|
-
if (!result) result = new Vector3();
|
|
100
|
-
|
|
98
|
+
intersectWithRay(ray: Ray, result: Vector3 = new Vector3()): Vector3 | undefined {
|
|
101
99
|
const origin = ray.origin;
|
|
102
100
|
const direction = ray.direction;
|
|
103
101
|
const normal = this.normal;
|
|
@@ -113,7 +111,6 @@ export class Plane {
|
|
|
113
111
|
return undefined;
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
|
|
117
|
-
return origin.add(result);
|
|
114
|
+
return result.copy(direction).multiplyByScalar(t).add(origin);
|
|
118
115
|
}
|
|
119
116
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {ImplicitShapeBase, type ImplicitShape, type LocalRayIntersection} from './implicit-shape';
|
|
6
|
+
|
|
7
|
+
const EPSILON = 1e-6;
|
|
8
|
+
|
|
9
|
+
export type BoxShapeProps = {size?: readonly [number, number, number]; matrix?: readonly number[]};
|
|
10
|
+
|
|
11
|
+
/** Analytic glTF box shape. */
|
|
12
|
+
export class BoxShape extends ImplicitShapeBase {
|
|
13
|
+
readonly type = 'box' as const;
|
|
14
|
+
readonly size: readonly [number, number, number];
|
|
15
|
+
private readonly halfSize: readonly [number, number, number];
|
|
16
|
+
|
|
17
|
+
constructor(props: BoxShapeProps = {}) {
|
|
18
|
+
super(props.matrix);
|
|
19
|
+
this.size = props.size ? [...props.size] : [1, 1, 1];
|
|
20
|
+
if (this.size.some(value => !Number.isFinite(value) || value <= 0))
|
|
21
|
+
throw new Error('Box size values must be greater than zero');
|
|
22
|
+
this.halfSize = [this.size[0] / 2, this.size[1] / 2, this.size[2] / 2];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
clone(): BoxShape {
|
|
26
|
+
return new BoxShape({size: this.size, matrix: this.matrix});
|
|
27
|
+
}
|
|
28
|
+
override equals(shape: ImplicitShape): boolean {
|
|
29
|
+
return (
|
|
30
|
+
shape instanceof BoxShape &&
|
|
31
|
+
super.equals(shape) &&
|
|
32
|
+
this.size.every((value, i) => value === shape.size[i])
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
containsLocalPoint(point: readonly number[]): boolean {
|
|
36
|
+
return point.every((value, i) => Math.abs(value) <= this.halfSize[i] + EPSILON);
|
|
37
|
+
}
|
|
38
|
+
distanceToLocalPoint(point: readonly number[]): number {
|
|
39
|
+
return Math.hypot(...point.map((value, i) => Math.max(0, Math.abs(value) - this.halfSize[i])));
|
|
40
|
+
}
|
|
41
|
+
supportLocal(direction: readonly number[]): readonly number[] {
|
|
42
|
+
return direction.map((value, i) => (value < 0 ? -this.halfSize[i] : this.halfSize[i]));
|
|
43
|
+
}
|
|
44
|
+
intersectLocalRay(
|
|
45
|
+
origin: readonly number[],
|
|
46
|
+
direction: readonly number[]
|
|
47
|
+
): LocalRayIntersection | undefined {
|
|
48
|
+
let near = -Infinity;
|
|
49
|
+
let far = Infinity;
|
|
50
|
+
let nearAxis = 0;
|
|
51
|
+
let farAxis = 0;
|
|
52
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
53
|
+
if (Math.abs(direction[axis]) < 1e-15) {
|
|
54
|
+
if (Math.abs(origin[axis]) > this.halfSize[axis]) return undefined;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
let t1 = (-this.halfSize[axis] - origin[axis]) / direction[axis];
|
|
58
|
+
let t2 = (this.halfSize[axis] - origin[axis]) / direction[axis];
|
|
59
|
+
if (t1 > t2) [t1, t2] = [t2, t1];
|
|
60
|
+
if (t1 > near) {
|
|
61
|
+
near = t1;
|
|
62
|
+
nearAxis = axis;
|
|
63
|
+
}
|
|
64
|
+
if (t2 < far) {
|
|
65
|
+
far = t2;
|
|
66
|
+
farAxis = axis;
|
|
67
|
+
}
|
|
68
|
+
if (near > far) return undefined;
|
|
69
|
+
}
|
|
70
|
+
const distance = near >= 0 ? near : far;
|
|
71
|
+
if (distance < 0 || !Number.isFinite(distance)) return undefined;
|
|
72
|
+
const axis = near >= 0 ? nearAxis : farAxis;
|
|
73
|
+
const pointCoordinate = origin[axis] + distance * direction[axis];
|
|
74
|
+
const normal = [0, 0, 0];
|
|
75
|
+
normal[axis] = pointCoordinate < 0 ? -1 : 1;
|
|
76
|
+
return {distance, normal};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
closestDistanceToCircleArc2D,
|
|
7
|
+
closestDistanceToSegment2D,
|
|
8
|
+
ImplicitShapeBase,
|
|
9
|
+
solveQuadratic,
|
|
10
|
+
type ImplicitShape,
|
|
11
|
+
type LocalRayIntersection
|
|
12
|
+
} from './implicit-shape';
|
|
13
|
+
|
|
14
|
+
const EPSILON = 1e-6;
|
|
15
|
+
|
|
16
|
+
export type CapsuleShapeProps = {
|
|
17
|
+
height?: number;
|
|
18
|
+
radiusBottom?: number;
|
|
19
|
+
radiusTop?: number;
|
|
20
|
+
matrix?: readonly number[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Analytic glTF capsule: the convex hull of two spheres on the Y axis. */
|
|
24
|
+
export class CapsuleShape extends ImplicitShapeBase {
|
|
25
|
+
readonly type = 'capsule' as const;
|
|
26
|
+
readonly height: number;
|
|
27
|
+
readonly radiusBottom: number;
|
|
28
|
+
readonly radiusTop: number;
|
|
29
|
+
|
|
30
|
+
constructor(props: CapsuleShapeProps = {}) {
|
|
31
|
+
super(props.matrix);
|
|
32
|
+
this.height = props.height ?? 1;
|
|
33
|
+
this.radiusBottom = props.radiusBottom ?? 0.5;
|
|
34
|
+
this.radiusTop = props.radiusTop ?? 0.5;
|
|
35
|
+
if (!Number.isFinite(this.height) || this.height <= 0)
|
|
36
|
+
throw new Error('Capsule height must be greater than zero');
|
|
37
|
+
if (![this.radiusBottom, this.radiusTop].every(value => Number.isFinite(value) && value >= 0))
|
|
38
|
+
throw new Error('Capsule radii must be non-negative');
|
|
39
|
+
if (this.radiusBottom === 0 && this.radiusTop === 0)
|
|
40
|
+
throw new Error('At least one capsule radius must be greater than zero');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
clone(): CapsuleShape {
|
|
44
|
+
return new CapsuleShape({
|
|
45
|
+
height: this.height,
|
|
46
|
+
radiusBottom: this.radiusBottom,
|
|
47
|
+
radiusTop: this.radiusTop,
|
|
48
|
+
matrix: this.matrix
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
override equals(shape: ImplicitShape): boolean {
|
|
52
|
+
return (
|
|
53
|
+
shape instanceof CapsuleShape &&
|
|
54
|
+
super.equals(shape) &&
|
|
55
|
+
this.height === shape.height &&
|
|
56
|
+
this.radiusBottom === shape.radiusBottom &&
|
|
57
|
+
this.radiusTop === shape.radiusTop
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
containsLocalPoint(point: readonly number[]): boolean {
|
|
61
|
+
const profile = this.getProfile();
|
|
62
|
+
const radial = Math.hypot(point[0], point[2]);
|
|
63
|
+
if (profile.sphere)
|
|
64
|
+
return (
|
|
65
|
+
Math.hypot(radial, point[1] - profile.sphere.center) <= profile.sphere.radius + EPSILON
|
|
66
|
+
);
|
|
67
|
+
if (point[1] < profile.bottom.y)
|
|
68
|
+
return Math.hypot(radial, point[1] + this.height / 2) <= this.radiusBottom + EPSILON;
|
|
69
|
+
if (point[1] > profile.top.y)
|
|
70
|
+
return Math.hypot(radial, point[1] - this.height / 2) <= this.radiusTop + EPSILON;
|
|
71
|
+
const t = (point[1] - profile.bottom.y) / (profile.top.y - profile.bottom.y);
|
|
72
|
+
return (
|
|
73
|
+
radial <= profile.bottom.radius + (profile.top.radius - profile.bottom.radius) * t + EPSILON
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
distanceToLocalPoint(point: readonly number[]): number {
|
|
77
|
+
if (this.containsLocalPoint(point)) return 0;
|
|
78
|
+
const profile = this.getProfile();
|
|
79
|
+
const radial = Math.hypot(point[0], point[2]);
|
|
80
|
+
if (profile.sphere)
|
|
81
|
+
return Math.max(
|
|
82
|
+
0,
|
|
83
|
+
Math.hypot(radial, point[1] - profile.sphere.center) - profile.sphere.radius
|
|
84
|
+
);
|
|
85
|
+
const delta = this.radiusTop - this.radiusBottom;
|
|
86
|
+
const sideNormalY = -delta / this.height;
|
|
87
|
+
const bottomArc = closestDistanceToCircleArc2D(
|
|
88
|
+
radial,
|
|
89
|
+
point[1],
|
|
90
|
+
-this.height / 2,
|
|
91
|
+
this.radiusBottom,
|
|
92
|
+
-1,
|
|
93
|
+
sideNormalY
|
|
94
|
+
);
|
|
95
|
+
const topArc = closestDistanceToCircleArc2D(
|
|
96
|
+
radial,
|
|
97
|
+
point[1],
|
|
98
|
+
this.height / 2,
|
|
99
|
+
this.radiusTop,
|
|
100
|
+
sideNormalY,
|
|
101
|
+
1
|
|
102
|
+
);
|
|
103
|
+
const side = closestDistanceToSegment2D(
|
|
104
|
+
radial,
|
|
105
|
+
point[1],
|
|
106
|
+
profile.bottom.radius,
|
|
107
|
+
profile.bottom.y,
|
|
108
|
+
profile.top.radius,
|
|
109
|
+
profile.top.y
|
|
110
|
+
);
|
|
111
|
+
return Math.min(bottomArc, topArc, side);
|
|
112
|
+
}
|
|
113
|
+
supportLocal(direction: readonly number[]): readonly number[] {
|
|
114
|
+
const length = Math.hypot(...direction);
|
|
115
|
+
const unit = length ? direction.map(value => value / length) : [1, 0, 0];
|
|
116
|
+
const bottom = [
|
|
117
|
+
unit[0] * this.radiusBottom,
|
|
118
|
+
-this.height / 2 + unit[1] * this.radiusBottom,
|
|
119
|
+
unit[2] * this.radiusBottom
|
|
120
|
+
];
|
|
121
|
+
const top = [
|
|
122
|
+
unit[0] * this.radiusTop,
|
|
123
|
+
this.height / 2 + unit[1] * this.radiusTop,
|
|
124
|
+
unit[2] * this.radiusTop
|
|
125
|
+
];
|
|
126
|
+
return dot(top, direction) >= dot(bottom, direction) ? top : bottom;
|
|
127
|
+
}
|
|
128
|
+
intersectLocalRay(
|
|
129
|
+
origin: readonly number[],
|
|
130
|
+
direction: readonly number[]
|
|
131
|
+
): LocalRayIntersection | undefined {
|
|
132
|
+
const profile = this.getProfile();
|
|
133
|
+
if (profile.sphere)
|
|
134
|
+
return intersectSphere(origin, direction, profile.sphere.center, profile.sphere.radius);
|
|
135
|
+
const candidates: LocalRayIntersection[] = [];
|
|
136
|
+
const slope = (profile.top.radius - profile.bottom.radius) / (profile.top.y - profile.bottom.y);
|
|
137
|
+
const intercept = profile.bottom.radius - slope * profile.bottom.y;
|
|
138
|
+
const radialAtOrigin = intercept + slope * origin[1];
|
|
139
|
+
const radialAlongRay = slope * direction[1];
|
|
140
|
+
for (const distance of solveQuadratic(
|
|
141
|
+
direction[0] ** 2 + direction[2] ** 2 - radialAlongRay ** 2,
|
|
142
|
+
2 * (origin[0] * direction[0] + origin[2] * direction[2] - radialAtOrigin * radialAlongRay),
|
|
143
|
+
origin[0] ** 2 + origin[2] ** 2 - radialAtOrigin ** 2
|
|
144
|
+
)) {
|
|
145
|
+
const y = origin[1] + distance * direction[1];
|
|
146
|
+
if (distance >= 0 && y >= profile.bottom.y - EPSILON && y <= profile.top.y + EPSILON) {
|
|
147
|
+
const x = origin[0] + distance * direction[0];
|
|
148
|
+
const z = origin[2] + distance * direction[2];
|
|
149
|
+
const radial = Math.hypot(x, z) || 1;
|
|
150
|
+
const normal = [x / radial, -slope, z / radial];
|
|
151
|
+
const length = Math.hypot(...normal);
|
|
152
|
+
candidates.push({distance, normal: normal.map(value => value / length)});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
for (const bottomHit of intersectSphereRoots(
|
|
156
|
+
origin,
|
|
157
|
+
direction,
|
|
158
|
+
-this.height / 2,
|
|
159
|
+
this.radiusBottom
|
|
160
|
+
)) {
|
|
161
|
+
const y = origin[1] + bottomHit.distance * direction[1];
|
|
162
|
+
if (y <= profile.bottom.y + EPSILON) candidates.push(bottomHit);
|
|
163
|
+
}
|
|
164
|
+
for (const topHit of intersectSphereRoots(origin, direction, this.height / 2, this.radiusTop)) {
|
|
165
|
+
const y = origin[1] + topHit.distance * direction[1];
|
|
166
|
+
if (y >= profile.top.y - EPSILON) candidates.push(topHit);
|
|
167
|
+
}
|
|
168
|
+
return candidates.sort((a, b) => a.distance - b.distance)[0];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getProfile(): {
|
|
172
|
+
sphere?: {center: number; radius: number};
|
|
173
|
+
bottom: {radius: number; y: number};
|
|
174
|
+
top: {radius: number; y: number};
|
|
175
|
+
} {
|
|
176
|
+
const delta = this.radiusTop - this.radiusBottom;
|
|
177
|
+
if (Math.abs(delta) >= this.height) {
|
|
178
|
+
const topWins = this.radiusTop >= this.radiusBottom;
|
|
179
|
+
return {
|
|
180
|
+
sphere: {
|
|
181
|
+
center: topWins ? this.height / 2 : -this.height / 2,
|
|
182
|
+
radius: topWins ? this.radiusTop : this.radiusBottom
|
|
183
|
+
},
|
|
184
|
+
bottom: {radius: 0, y: 0},
|
|
185
|
+
top: {radius: 0, y: 0}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
const sinSlope = delta / this.height;
|
|
189
|
+
const radialNormal = Math.sqrt(1 - sinSlope * sinSlope);
|
|
190
|
+
const normalY = -sinSlope;
|
|
191
|
+
return {
|
|
192
|
+
bottom: {
|
|
193
|
+
radius: this.radiusBottom * radialNormal,
|
|
194
|
+
y: -this.height / 2 + this.radiusBottom * normalY
|
|
195
|
+
},
|
|
196
|
+
top: {radius: this.radiusTop * radialNormal, y: this.height / 2 + this.radiusTop * normalY}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function intersectSphere(
|
|
202
|
+
origin: readonly number[],
|
|
203
|
+
direction: readonly number[],
|
|
204
|
+
centerY: number,
|
|
205
|
+
radius: number
|
|
206
|
+
): LocalRayIntersection | undefined {
|
|
207
|
+
return intersectSphereRoots(origin, direction, centerY, radius)[0];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function intersectSphereRoots(
|
|
211
|
+
origin: readonly number[],
|
|
212
|
+
direction: readonly number[],
|
|
213
|
+
centerY: number,
|
|
214
|
+
radius: number
|
|
215
|
+
): LocalRayIntersection[] {
|
|
216
|
+
if (radius === 0) return [];
|
|
217
|
+
const relative = [origin[0], origin[1] - centerY, origin[2]];
|
|
218
|
+
return solveQuadratic(
|
|
219
|
+
dot(direction, direction),
|
|
220
|
+
2 * dot(relative, direction),
|
|
221
|
+
dot(relative, relative) - radius * radius
|
|
222
|
+
)
|
|
223
|
+
.filter(distance => distance >= 0)
|
|
224
|
+
.map(distance => ({
|
|
225
|
+
distance,
|
|
226
|
+
normal: relative.map((value, i) => (value + distance * direction[i]) / radius)
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function dot(a: readonly number[], b: readonly number[]): number {
|
|
231
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
232
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
closestDistanceToSegment2D,
|
|
7
|
+
ImplicitShapeBase,
|
|
8
|
+
solveQuadratic,
|
|
9
|
+
type ImplicitShape,
|
|
10
|
+
type LocalRayIntersection
|
|
11
|
+
} from './implicit-shape';
|
|
12
|
+
|
|
13
|
+
const EPSILON = 1e-6;
|
|
14
|
+
|
|
15
|
+
export type CylinderShapeProps = {
|
|
16
|
+
height?: number;
|
|
17
|
+
radiusBottom?: number;
|
|
18
|
+
radiusTop?: number;
|
|
19
|
+
matrix?: readonly number[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** Analytic closed, Y-aligned glTF cylinder shape, including tapered cylinders. */
|
|
23
|
+
export class CylinderShape extends ImplicitShapeBase {
|
|
24
|
+
readonly type = 'cylinder' as const;
|
|
25
|
+
readonly height: number;
|
|
26
|
+
readonly radiusBottom: number;
|
|
27
|
+
readonly radiusTop: number;
|
|
28
|
+
|
|
29
|
+
constructor(props: CylinderShapeProps = {}) {
|
|
30
|
+
super(props.matrix);
|
|
31
|
+
this.height = props.height ?? 2;
|
|
32
|
+
this.radiusBottom = props.radiusBottom ?? 0.5;
|
|
33
|
+
this.radiusTop = props.radiusTop ?? 0.5;
|
|
34
|
+
if (!Number.isFinite(this.height) || this.height <= 0)
|
|
35
|
+
throw new Error('Cylinder height must be greater than zero');
|
|
36
|
+
if (![this.radiusBottom, this.radiusTop].every(value => Number.isFinite(value) && value >= 0))
|
|
37
|
+
throw new Error('Cylinder radii must be non-negative');
|
|
38
|
+
if (this.radiusBottom === 0 && this.radiusTop === 0)
|
|
39
|
+
throw new Error('At least one cylinder radius must be greater than zero');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clone(): CylinderShape {
|
|
43
|
+
return new CylinderShape({
|
|
44
|
+
height: this.height,
|
|
45
|
+
radiusBottom: this.radiusBottom,
|
|
46
|
+
radiusTop: this.radiusTop,
|
|
47
|
+
matrix: this.matrix
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
override equals(shape: ImplicitShape): boolean {
|
|
51
|
+
return (
|
|
52
|
+
shape instanceof CylinderShape &&
|
|
53
|
+
super.equals(shape) &&
|
|
54
|
+
this.height === shape.height &&
|
|
55
|
+
this.radiusBottom === shape.radiusBottom &&
|
|
56
|
+
this.radiusTop === shape.radiusTop
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
containsLocalPoint(point: readonly number[]): boolean {
|
|
60
|
+
const half = this.height / 2;
|
|
61
|
+
if (point[1] < -half - EPSILON || point[1] > half + EPSILON) return false;
|
|
62
|
+
const t = (point[1] + half) / this.height;
|
|
63
|
+
const radius = this.radiusBottom + (this.radiusTop - this.radiusBottom) * t;
|
|
64
|
+
return Math.hypot(point[0], point[2]) <= radius + EPSILON;
|
|
65
|
+
}
|
|
66
|
+
distanceToLocalPoint(point: readonly number[]): number {
|
|
67
|
+
if (this.containsLocalPoint(point)) return 0;
|
|
68
|
+
const radial = Math.hypot(point[0], point[2]);
|
|
69
|
+
const half = this.height / 2;
|
|
70
|
+
const side = closestDistanceToSegment2D(
|
|
71
|
+
radial,
|
|
72
|
+
point[1],
|
|
73
|
+
this.radiusBottom,
|
|
74
|
+
-half,
|
|
75
|
+
this.radiusTop,
|
|
76
|
+
half
|
|
77
|
+
);
|
|
78
|
+
const bottom = closestDistanceToSegment2D(radial, point[1], 0, -half, this.radiusBottom, -half);
|
|
79
|
+
const top = closestDistanceToSegment2D(radial, point[1], 0, half, this.radiusTop, half);
|
|
80
|
+
return Math.min(side, bottom, top);
|
|
81
|
+
}
|
|
82
|
+
supportLocal(direction: readonly number[]): readonly number[] {
|
|
83
|
+
const radialLength = Math.hypot(direction[0], direction[2]);
|
|
84
|
+
const x = radialLength ? direction[0] / radialLength : 1;
|
|
85
|
+
const z = radialLength ? direction[2] / radialLength : 0;
|
|
86
|
+
const half = this.height / 2;
|
|
87
|
+
const bottomValue = radialLength * this.radiusBottom - direction[1] * half;
|
|
88
|
+
const topValue = radialLength * this.radiusTop + direction[1] * half;
|
|
89
|
+
return topValue >= bottomValue
|
|
90
|
+
? [x * this.radiusTop, half, z * this.radiusTop]
|
|
91
|
+
: [x * this.radiusBottom, -half, z * this.radiusBottom];
|
|
92
|
+
}
|
|
93
|
+
intersectLocalRay(
|
|
94
|
+
origin: readonly number[],
|
|
95
|
+
direction: readonly number[]
|
|
96
|
+
): LocalRayIntersection | undefined {
|
|
97
|
+
const half = this.height / 2;
|
|
98
|
+
const slope = (this.radiusTop - this.radiusBottom) / this.height;
|
|
99
|
+
const intercept = (this.radiusTop + this.radiusBottom) / 2;
|
|
100
|
+
const radialAtOrigin = intercept + slope * origin[1];
|
|
101
|
+
const radialAlongRay = slope * direction[1];
|
|
102
|
+
const candidates: LocalRayIntersection[] = [];
|
|
103
|
+
const roots = solveQuadratic(
|
|
104
|
+
direction[0] ** 2 + direction[2] ** 2 - radialAlongRay ** 2,
|
|
105
|
+
2 * (origin[0] * direction[0] + origin[2] * direction[2] - radialAtOrigin * radialAlongRay),
|
|
106
|
+
origin[0] ** 2 + origin[2] ** 2 - radialAtOrigin ** 2
|
|
107
|
+
);
|
|
108
|
+
for (const distance of roots) {
|
|
109
|
+
const y = origin[1] + distance * direction[1];
|
|
110
|
+
if (distance >= 0 && y >= -half - EPSILON && y <= half + EPSILON) {
|
|
111
|
+
const x = origin[0] + distance * direction[0];
|
|
112
|
+
const z = origin[2] + distance * direction[2];
|
|
113
|
+
const radial = Math.hypot(x, z) || 1;
|
|
114
|
+
const normal = [x / radial, -slope, z / radial];
|
|
115
|
+
const length = Math.hypot(...normal);
|
|
116
|
+
candidates.push({distance, normal: normal.map(value => value / length)});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (Math.abs(direction[1]) > 1e-15) {
|
|
120
|
+
for (const [y, radius, normalY] of [
|
|
121
|
+
[-half, this.radiusBottom, -1],
|
|
122
|
+
[half, this.radiusTop, 1]
|
|
123
|
+
]) {
|
|
124
|
+
const distance = (y - origin[1]) / direction[1];
|
|
125
|
+
const x = origin[0] + distance * direction[0];
|
|
126
|
+
const z = origin[2] + distance * direction[2];
|
|
127
|
+
if (distance >= 0 && Math.hypot(x, z) <= radius + EPSILON)
|
|
128
|
+
candidates.push({distance, normal: [0, normalY, 0]});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return candidates.sort((a, b) => a.distance - b.distance)[0];
|
|
132
|
+
}
|
|
133
|
+
}
|