@math.gl/geometry 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 +140 -0
- package/README.md +15 -0
- package/dist/geometries/box-geometry.d.ts +17 -0
- package/dist/geometries/box-geometry.d.ts.map +1 -0
- package/dist/geometries/box-geometry.js +60 -0
- package/dist/geometries/box-geometry.js.map +1 -0
- package/dist/geometries/capsule-geometry.d.ts +15 -0
- package/dist/geometries/capsule-geometry.d.ts.map +1 -0
- package/dist/geometries/capsule-geometry.js +92 -0
- package/dist/geometries/capsule-geometry.js.map +1 -0
- package/dist/geometries/cylinder-geometry.d.ts +17 -0
- package/dist/geometries/cylinder-geometry.d.ts.map +1 -0
- package/dist/geometries/cylinder-geometry.js +27 -0
- package/dist/geometries/cylinder-geometry.js.map +1 -0
- package/dist/geometries/geometry-helpers.d.ts +16 -0
- package/dist/geometries/geometry-helpers.d.ts.map +1 -0
- package/dist/geometries/geometry-helpers.js +19 -0
- package/dist/geometries/geometry-helpers.js.map +1 -0
- package/dist/geometries/ico-sphere-geometry.d.ts +11 -0
- package/dist/geometries/ico-sphere-geometry.d.ts.map +1 -0
- package/dist/geometries/ico-sphere-geometry.js +87 -0
- package/dist/geometries/ico-sphere-geometry.js.map +1 -0
- package/dist/geometries/plane-geometry.d.ts +13 -0
- package/dist/geometries/plane-geometry.d.ts.map +1 -0
- package/dist/geometries/plane-geometry.js +51 -0
- package/dist/geometries/plane-geometry.js.map +1 -0
- package/dist/geometries/sphere-geometry.d.ts +12 -0
- package/dist/geometries/sphere-geometry.d.ts.map +1 -0
- package/dist/geometries/sphere-geometry.js +35 -0
- package/dist/geometries/sphere-geometry.js.map +1 -0
- package/dist/geometries/surface-of-revolution.d.ts +11 -0
- package/dist/geometries/surface-of-revolution.d.ts.map +1 -0
- package/dist/geometries/surface-of-revolution.js +37 -0
- package/dist/geometries/surface-of-revolution.js.map +1 -0
- package/dist/geometries/truncated-cone-geometry.d.ts +17 -0
- package/dist/geometries/truncated-cone-geometry.d.ts.map +1 -0
- package/dist/geometries/truncated-cone-geometry.js +92 -0
- package/dist/geometries/truncated-cone-geometry.js.map +1 -0
- package/dist/index.cjs +601 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/geometry-utils.d.ts +9 -0
- package/dist/lib/geometry-utils.d.ts.map +1 -0
- package/dist/lib/geometry-utils.js +29 -0
- package/dist/lib/geometry-utils.js.map +1 -0
- package/dist/lib/geometry.d.ts +36 -0
- package/dist/lib/geometry.d.ts.map +1 -0
- package/dist/lib/geometry.js +89 -0
- package/dist/lib/geometry.js.map +1 -0
- package/package.json +40 -0
- package/src/geometries/box-geometry.ts +70 -0
- package/src/geometries/capsule-geometry.ts +120 -0
- package/src/geometries/cylinder-geometry.ts +41 -0
- package/src/geometries/geometry-helpers.ts +34 -0
- package/src/geometries/ico-sphere-geometry.ts +96 -0
- package/src/geometries/plane-geometry.ts +64 -0
- package/src/geometries/sphere-geometry.ts +42 -0
- package/src/geometries/surface-of-revolution.ts +43 -0
- package/src/geometries/truncated-cone-geometry.ts +129 -0
- package/src/index.ts +28 -0
- package/src/lib/geometry-utils.ts +35 -0
- package/src/lib/geometry.ts +140 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Geometry} from '../lib/geometry';
|
|
6
|
+
import {assertPositive, assertSegments, type PrimitiveGeometryProps} from './geometry-helpers';
|
|
7
|
+
import {makeSurfaceOfRevolution, type ProfileRing} from './surface-of-revolution';
|
|
8
|
+
|
|
9
|
+
export type SphereGeometryProps = PrimitiveGeometryProps & {
|
|
10
|
+
radius?: number;
|
|
11
|
+
nlat?: number;
|
|
12
|
+
nlong?: number;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** Tessellates the glTF sphere shape. */
|
|
16
|
+
export class SphereGeometry extends Geometry {
|
|
17
|
+
constructor(props: SphereGeometryProps = {}) {
|
|
18
|
+
const {radius = 0.5, nlat = 10, nlong = 10} = props;
|
|
19
|
+
assertPositive(radius, 'radius');
|
|
20
|
+
assertSegments(nlat, 'nlat', 2);
|
|
21
|
+
assertSegments(nlong, 'nlong', 3);
|
|
22
|
+
const rings: ProfileRing[] = [];
|
|
23
|
+
for (let latitude = 0; latitude <= nlat; latitude++) {
|
|
24
|
+
const angle = -Math.PI / 2 + (latitude / nlat) * Math.PI;
|
|
25
|
+
const normalRadius = Math.cos(angle);
|
|
26
|
+
const normalY = Math.sin(angle);
|
|
27
|
+
rings.push({radius: radius * normalRadius, y: radius * normalY, normalRadius, normalY});
|
|
28
|
+
}
|
|
29
|
+
const mesh = makeSurfaceOfRevolution(rings, nlong);
|
|
30
|
+
super({
|
|
31
|
+
id: props.id,
|
|
32
|
+
topology: 'triangle-list',
|
|
33
|
+
indices: mesh.indexArray,
|
|
34
|
+
attributes: {
|
|
35
|
+
POSITION: {size: 3, value: new Float32Array(mesh.positions)},
|
|
36
|
+
NORMAL: {size: 3, value: new Float32Array(mesh.normals)},
|
|
37
|
+
TEXCOORD_0: {size: 2, value: new Float32Array(mesh.texCoords)},
|
|
38
|
+
...props.attributes
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {makeIndices, type MeshData} from './geometry-helpers';
|
|
6
|
+
|
|
7
|
+
export type ProfileRing = {radius: number; y: number; normalRadius: number; normalY: number};
|
|
8
|
+
|
|
9
|
+
export function makeSurfaceOfRevolution(
|
|
10
|
+
rings: ProfileRing[],
|
|
11
|
+
nradial: number
|
|
12
|
+
): MeshData & {indexArray: Uint16Array | Uint32Array} {
|
|
13
|
+
const positions: number[] = [];
|
|
14
|
+
const normals: number[] = [];
|
|
15
|
+
const texCoords: number[] = [];
|
|
16
|
+
const indices: number[] = [];
|
|
17
|
+
const stride = nradial + 1;
|
|
18
|
+
for (let ringIndex = 0; ringIndex < rings.length; ringIndex++) {
|
|
19
|
+
const ring = rings[ringIndex];
|
|
20
|
+
for (let radial = 0; radial <= nradial; radial++) {
|
|
21
|
+
const u = radial / nradial;
|
|
22
|
+
const angle = u * Math.PI * 2;
|
|
23
|
+
const sin = Math.sin(angle);
|
|
24
|
+
const cos = Math.cos(angle);
|
|
25
|
+
positions.push(sin * ring.radius, ring.y, cos * ring.radius);
|
|
26
|
+
normals.push(sin * ring.normalRadius, ring.normalY, cos * ring.normalRadius);
|
|
27
|
+
texCoords.push(u, ringIndex / (rings.length - 1));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
for (let ring = 0; ring < rings.length - 1; ring++) {
|
|
31
|
+
for (let radial = 0; radial < nradial; radial++) {
|
|
32
|
+
const a = ring * stride + radial;
|
|
33
|
+
const b = a + 1;
|
|
34
|
+
const c = a + stride;
|
|
35
|
+
const d = c + 1;
|
|
36
|
+
indices.push(a, b, d, a, d, c);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
...{positions, normals, texCoords, indices},
|
|
41
|
+
indexArray: makeIndices(positions.length / 3, indices)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {Geometry} from '../lib/geometry';
|
|
6
|
+
import {
|
|
7
|
+
assertNonNegative,
|
|
8
|
+
assertPositive,
|
|
9
|
+
assertSegments,
|
|
10
|
+
makeIndices,
|
|
11
|
+
type PrimitiveGeometryProps
|
|
12
|
+
} from './geometry-helpers';
|
|
13
|
+
|
|
14
|
+
export type TruncatedConeGeometryProps = PrimitiveGeometryProps & {
|
|
15
|
+
topRadius?: number;
|
|
16
|
+
bottomRadius?: number;
|
|
17
|
+
topCap?: boolean;
|
|
18
|
+
bottomCap?: boolean;
|
|
19
|
+
height?: number;
|
|
20
|
+
nradial?: number;
|
|
21
|
+
nvertical?: number;
|
|
22
|
+
verticalAxis?: 'x' | 'y' | 'z';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** General truncated cone primitive, adapted from luma.gl's CPU tessellator. */
|
|
26
|
+
export class TruncatedConeGeometry extends Geometry {
|
|
27
|
+
constructor(props: TruncatedConeGeometryProps = {}) {
|
|
28
|
+
const {
|
|
29
|
+
topRadius = 0,
|
|
30
|
+
bottomRadius = 0,
|
|
31
|
+
topCap = false,
|
|
32
|
+
bottomCap = false,
|
|
33
|
+
height = 1,
|
|
34
|
+
nradial = 10,
|
|
35
|
+
nvertical = 1,
|
|
36
|
+
verticalAxis = 'y'
|
|
37
|
+
} = props;
|
|
38
|
+
assertNonNegative(topRadius, 'topRadius');
|
|
39
|
+
assertNonNegative(bottomRadius, 'bottomRadius');
|
|
40
|
+
if (topRadius === 0 && bottomRadius === 0)
|
|
41
|
+
throw new Error('At least one radius must be greater than zero');
|
|
42
|
+
assertPositive(height, 'height');
|
|
43
|
+
assertSegments(nradial, 'nradial', 3);
|
|
44
|
+
assertSegments(nvertical, 'nvertical');
|
|
45
|
+
|
|
46
|
+
const positions: number[] = [];
|
|
47
|
+
const normals: number[] = [];
|
|
48
|
+
const texCoords: number[] = [];
|
|
49
|
+
const indices: number[] = [];
|
|
50
|
+
const stride = nradial + 1;
|
|
51
|
+
const slope = (bottomRadius - topRadius) / height;
|
|
52
|
+
const normalScale = 1 / Math.hypot(1, slope);
|
|
53
|
+
|
|
54
|
+
const addRing = (
|
|
55
|
+
radius: number,
|
|
56
|
+
y: number,
|
|
57
|
+
normalRadius: number,
|
|
58
|
+
normalY: number,
|
|
59
|
+
v: number
|
|
60
|
+
): number => {
|
|
61
|
+
const ring = positions.length / 3 / stride;
|
|
62
|
+
for (let radial = 0; radial <= nradial; radial++) {
|
|
63
|
+
const u = radial / nradial;
|
|
64
|
+
const angle = u * Math.PI * 2;
|
|
65
|
+
const sin = Math.sin(angle);
|
|
66
|
+
const cos = Math.cos(angle);
|
|
67
|
+
const point = orient([sin * radius, y, cos * radius], verticalAxis);
|
|
68
|
+
const normal = orient([sin * normalRadius, normalY, cos * normalRadius], verticalAxis);
|
|
69
|
+
positions.push(...point);
|
|
70
|
+
normals.push(...normal);
|
|
71
|
+
texCoords.push(u, v);
|
|
72
|
+
}
|
|
73
|
+
return ring;
|
|
74
|
+
};
|
|
75
|
+
const connect = (lower: number, upper: number): void => {
|
|
76
|
+
for (let radial = 0; radial < nradial; radial++) {
|
|
77
|
+
const a = lower * stride + radial;
|
|
78
|
+
const b = a + 1;
|
|
79
|
+
const c = upper * stride + radial;
|
|
80
|
+
const d = c + 1;
|
|
81
|
+
indices.push(a, b, d, a, d, c);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
let previous: number | undefined;
|
|
86
|
+
if (bottomCap) {
|
|
87
|
+
const center = addRing(0, -height / 2, 0, -1, 0);
|
|
88
|
+
const edge = addRing(bottomRadius, -height / 2, 0, -1, 1);
|
|
89
|
+
connect(center, edge);
|
|
90
|
+
previous = addRing(bottomRadius, -height / 2, normalScale, slope * normalScale, 0);
|
|
91
|
+
}
|
|
92
|
+
for (let vertical = 0; vertical <= nvertical; vertical++) {
|
|
93
|
+
const v = vertical / nvertical;
|
|
94
|
+
const radius = bottomRadius + (topRadius - bottomRadius) * v;
|
|
95
|
+
const ring = addRing(radius, (v - 0.5) * height, normalScale, slope * normalScale, v);
|
|
96
|
+
if (previous !== undefined && !(bottomCap && vertical === 0)) connect(previous, ring);
|
|
97
|
+
previous = ring;
|
|
98
|
+
}
|
|
99
|
+
if (topCap && previous !== undefined) {
|
|
100
|
+
const edge = addRing(topRadius, height / 2, 0, 1, 1);
|
|
101
|
+
const center = addRing(0, height / 2, 0, 1, 0);
|
|
102
|
+
connect(edge, center);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const vertexCount = positions.length / 3;
|
|
106
|
+
super({
|
|
107
|
+
id: props.id,
|
|
108
|
+
topology: 'triangle-list',
|
|
109
|
+
indices: makeIndices(vertexCount, indices),
|
|
110
|
+
attributes: {
|
|
111
|
+
POSITION: {size: 3, value: new Float32Array(positions)},
|
|
112
|
+
NORMAL: {size: 3, value: new Float32Array(normals)},
|
|
113
|
+
TEXCOORD_0: {size: 2, value: new Float32Array(texCoords)},
|
|
114
|
+
...props.attributes
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function orient(vector: readonly number[], axis: 'x' | 'y' | 'z'): number[] {
|
|
121
|
+
switch (axis) {
|
|
122
|
+
case 'x':
|
|
123
|
+
return [vector[1], vector[2], vector[0]];
|
|
124
|
+
case 'z':
|
|
125
|
+
return [vector[2], vector[0], vector[1]];
|
|
126
|
+
default:
|
|
127
|
+
return [vector[0], vector[1], vector[2]];
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
export {Geometry} from './lib/geometry';
|
|
6
|
+
export type {
|
|
7
|
+
GeometryProps,
|
|
8
|
+
GeometryAttribute,
|
|
9
|
+
GeometryAttributeInput,
|
|
10
|
+
GeometryAttributes,
|
|
11
|
+
PrimitiveTopology
|
|
12
|
+
} from './lib/geometry';
|
|
13
|
+
export {unpackIndexedGeometry} from './lib/geometry-utils';
|
|
14
|
+
|
|
15
|
+
export {BoxGeometry, CubeGeometry} from './geometries/box-geometry';
|
|
16
|
+
export type {BoxGeometryProps, CubeGeometryProps} from './geometries/box-geometry';
|
|
17
|
+
export {CapsuleGeometry} from './geometries/capsule-geometry';
|
|
18
|
+
export type {CapsuleGeometryProps} from './geometries/capsule-geometry';
|
|
19
|
+
export {CylinderGeometry, ConeGeometry} from './geometries/cylinder-geometry';
|
|
20
|
+
export type {CylinderGeometryProps, ConeGeometryProps} from './geometries/cylinder-geometry';
|
|
21
|
+
export {PlaneGeometry} from './geometries/plane-geometry';
|
|
22
|
+
export type {PlaneGeometryProps} from './geometries/plane-geometry';
|
|
23
|
+
export {SphereGeometry} from './geometries/sphere-geometry';
|
|
24
|
+
export type {SphereGeometryProps} from './geometries/sphere-geometry';
|
|
25
|
+
export {TruncatedConeGeometry} from './geometries/truncated-cone-geometry';
|
|
26
|
+
export type {TruncatedConeGeometryProps} from './geometries/truncated-cone-geometry';
|
|
27
|
+
export {IcoSphereGeometry} from './geometries/ico-sphere-geometry';
|
|
28
|
+
export type {IcoSphereGeometryProps} from './geometries/ico-sphere-geometry';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {TypedArray, TypedArrayConstructor} from '@math.gl/types';
|
|
6
|
+
import type {GeometryAttribute} from './geometry';
|
|
7
|
+
|
|
8
|
+
type GeometryLike = {
|
|
9
|
+
indices?: GeometryAttribute;
|
|
10
|
+
attributes: Record<string, GeometryAttribute | undefined>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** Expands indexed attributes into a non-indexed geometry-like object. */
|
|
14
|
+
export function unpackIndexedGeometry<T extends GeometryLike>(geometry: T): GeometryLike {
|
|
15
|
+
if (!geometry.indices) return geometry;
|
|
16
|
+
const indices = geometry.indices.value;
|
|
17
|
+
const attributes: Record<string, GeometryAttribute> = {};
|
|
18
|
+
for (const [name, attribute] of Object.entries(geometry.attributes)) {
|
|
19
|
+
if (!attribute) continue;
|
|
20
|
+
if (attribute.constant || !attribute.size) {
|
|
21
|
+
attributes[name] = attribute;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
const ArrayType = attribute.value.constructor as TypedArrayConstructor;
|
|
25
|
+
const value = new ArrayType(indices.length * attribute.size) as TypedArray;
|
|
26
|
+
for (let i = 0; i < indices.length; i++) {
|
|
27
|
+
for (let component = 0; component < attribute.size; component++) {
|
|
28
|
+
value[i * attribute.size + component] =
|
|
29
|
+
attribute.value[Number(indices[i]) * attribute.size + component];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
attributes[name] = {...attribute, value};
|
|
33
|
+
}
|
|
34
|
+
return {attributes};
|
|
35
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import type {TypedArray} from '@math.gl/types';
|
|
6
|
+
|
|
7
|
+
export type PrimitiveTopology =
|
|
8
|
+
| 'point-list'
|
|
9
|
+
| 'line-list'
|
|
10
|
+
| 'line-strip'
|
|
11
|
+
| 'triangle-list'
|
|
12
|
+
| 'triangle-strip';
|
|
13
|
+
|
|
14
|
+
export type GeometryAttribute = {
|
|
15
|
+
size?: number;
|
|
16
|
+
value: TypedArray;
|
|
17
|
+
constant?: boolean;
|
|
18
|
+
normalized?: boolean;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type GeometryAttributeInput = GeometryAttribute | TypedArray;
|
|
23
|
+
export type GeometryAttributes = Record<string, GeometryAttribute | undefined> & {
|
|
24
|
+
indices?: GeometryAttribute & {size: 1; value: Uint16Array | Uint32Array};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type GeometryProps = {
|
|
28
|
+
id?: string;
|
|
29
|
+
topology: PrimitiveTopology;
|
|
30
|
+
vertexCount?: number;
|
|
31
|
+
attributes: Record<string, GeometryAttributeInput>;
|
|
32
|
+
indices?: GeometryAttributeInput;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
let geometryId = 0;
|
|
36
|
+
|
|
37
|
+
/** A renderer-independent, typed-array-backed geometry container. */
|
|
38
|
+
export class Geometry {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly topology: PrimitiveTopology;
|
|
41
|
+
readonly vertexCount: number;
|
|
42
|
+
readonly indices?: GeometryAttribute;
|
|
43
|
+
readonly attributes: Record<string, GeometryAttribute>;
|
|
44
|
+
userData: Record<string, unknown> = {};
|
|
45
|
+
|
|
46
|
+
constructor(props: GeometryProps) {
|
|
47
|
+
this.id = props.id || `geometry-${++geometryId}`;
|
|
48
|
+
this.topology = props.topology;
|
|
49
|
+
this.attributes = {};
|
|
50
|
+
|
|
51
|
+
let indices = props.indices ? normalizeAttribute(props.indices) : undefined;
|
|
52
|
+
for (const [name, input] of Object.entries(props.attributes || {})) {
|
|
53
|
+
const attribute = normalizeAttribute(input);
|
|
54
|
+
if (name === 'POSITION' || name === 'positions') {
|
|
55
|
+
attribute.size ||= 3;
|
|
56
|
+
}
|
|
57
|
+
if (name === 'indices') {
|
|
58
|
+
if (indices) throw new Error('Geometry has multiple index attributes');
|
|
59
|
+
indices = attribute;
|
|
60
|
+
} else {
|
|
61
|
+
this.attributes[name] = attribute;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (indices) {
|
|
66
|
+
if (!(indices.value instanceof Uint16Array || indices.value instanceof Uint32Array)) {
|
|
67
|
+
throw new Error('Geometry indices must be a Uint16Array or Uint32Array');
|
|
68
|
+
}
|
|
69
|
+
indices.size = 1;
|
|
70
|
+
this.indices = indices;
|
|
71
|
+
validateIndices(indices, this.attributes);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.vertexCount = props.vertexCount ?? calculateVertexCount(this.attributes, this.indices);
|
|
75
|
+
if (!Number.isInteger(this.vertexCount) || this.vertexCount < 0) {
|
|
76
|
+
throw new Error('Geometry vertexCount must be a non-negative integer');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getVertexCount(): number {
|
|
81
|
+
return this.vertexCount;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getAttributes(): GeometryAttributes {
|
|
85
|
+
return (
|
|
86
|
+
this.indices ? {indices: this.indices, ...this.attributes} : this.attributes
|
|
87
|
+
) as GeometryAttributes;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeAttribute(input: GeometryAttributeInput): GeometryAttribute {
|
|
92
|
+
const attribute = isTypedArray(input) ? {value: input} : {...input};
|
|
93
|
+
if (!isTypedArray(attribute.value)) {
|
|
94
|
+
throw new Error('Geometry attributes must contain a typed-array value');
|
|
95
|
+
}
|
|
96
|
+
if (attribute.size !== undefined && (!Number.isInteger(attribute.size) || attribute.size < 1)) {
|
|
97
|
+
throw new Error('Geometry attribute size must be a positive integer');
|
|
98
|
+
}
|
|
99
|
+
if (attribute.size && attribute.value.length % attribute.size !== 0) {
|
|
100
|
+
throw new Error('Geometry attribute length must be divisible by its size');
|
|
101
|
+
}
|
|
102
|
+
return attribute;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function isTypedArray(value: unknown): value is TypedArray {
|
|
106
|
+
return ArrayBuffer.isView(value) && !(value instanceof DataView);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function validateIndices(
|
|
110
|
+
indices: GeometryAttribute,
|
|
111
|
+
attributes: Record<string, GeometryAttribute>
|
|
112
|
+
): void {
|
|
113
|
+
let maximumIndex = -1;
|
|
114
|
+
for (const index of indices.value) maximumIndex = Math.max(maximumIndex, Number(index));
|
|
115
|
+
for (const [name, attribute] of Object.entries(attributes)) {
|
|
116
|
+
if (!attribute.constant && attribute.size) {
|
|
117
|
+
const attributeVertexCount = attribute.value.length / attribute.size;
|
|
118
|
+
if (maximumIndex >= attributeVertexCount) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Geometry index ${maximumIndex} exceeds ${name} vertex count ${attributeVertexCount}`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function calculateVertexCount(
|
|
128
|
+
attributes: Record<string, GeometryAttribute>,
|
|
129
|
+
indices?: GeometryAttribute
|
|
130
|
+
): number {
|
|
131
|
+
if (indices) return indices.value.length;
|
|
132
|
+
let count = Infinity;
|
|
133
|
+
for (const attribute of Object.values(attributes)) {
|
|
134
|
+
if (!attribute.constant && attribute.size) {
|
|
135
|
+
count = Math.min(count, attribute.value.length / attribute.size);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!Number.isFinite(count)) throw new Error('Geometry has no countable attributes');
|
|
139
|
+
return count;
|
|
140
|
+
}
|