Skip to content

Allow typed arrays in geometry and mesh data APIs - #9121

Merged
mvaligursky merged 3 commits into
mainfrom
mv-geometry-array-types
Jul 27, 2026
Merged

Allow typed arrays in geometry and mesh data APIs#9121
mvaligursky merged 3 commits into
mainfrom
mv-geometry-array-types

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

Fixes #9120. calculateNormals and calculateTangents have always worked with typed arrays - the engine's own glb parser and several examples pass them - but were documented as accepting number[] only, so TypeScript users (and two of our examples, via @ts-ignore) could not call them with a Float32Array. This widens the JSDoc across the whole chain those functions sit in, so the types are consistent from Geometry through Mesh to BoundingBox.

Read-only parameters use ArrayLike<number>, which accepts both number[] and any typed array. Parameters that are written into (the Mesh getters) use a new NumericArray typedef instead, since ArrayLike is read-only.

Changes:

  • calculateNormals / calculateTangents: all parameters accept ArrayLike<number>; return values are unchanged (number[])
  • Geometry: data fields accept ArrayLike<number>; indices accepts number[]|Uint8Array|Uint16Array|Uint32Array to match Mesh#setIndices
  • Mesh: setVertexStream / setPositions / setNormals / setUvs / setColors / setColors32 accept ArrayLike<number>; getVertexStream / getPositions / getNormals / getUvs / getColors accept NumericArray. setIndices / getIndices are unchanged.
  • MorphTarget: options.deltaPositions and options.deltaNormals were documented as ArrayBuffer, which was simply wrong - they are numeric arrays
  • BoundingBox#compute / BoundingBox.computeMinMax: vertices accepts ArrayLike<number>
  • Fixed Mesh#getVertexStream populating an array destination incorrectly when the stream had not been applied yet: it pushed the source array as a single element, so mesh.getPositions([]) before mesh.update() returned [[x, y, z, ...]] instead of a flat array (and aliased the caller's own data). It now copies element-wise, matching the typed-array branch, getIndices and VertexIterator#readData.
  • Removed three stale @ts-ignore engine-tsd comments that existed only because of the above typings

These changes also resolve four pre-existing type errors in the repository: the calculateNormals call in glb-parser, two in Mesh (ArrayBufferView has neither length nor set), and one in MorphTarget.

API Changes:

  • No signatures change at runtime; the accepted types widen only. The one behavioural change is Mesh#getVertexStream (and getPositions / getNormals / getUvs / getColors) returning flat data for array destinations where it previously returned a single nested element - code that worked around the old result by reading out[0] would need updating.

Examples:

  • graphics/mesh-generation and graphics/mesh-morph: removed now-unnecessary @ts-ignore comments

Tests:

  • New test/scene/mesh.test.mjs covering vertex stream readback into arrays and typed arrays, before and after update(), plus indices
  • New test/scene/geometry/geometry-utils.test.mjs covering normals and tangents from both arrays and typed arrays

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Public API report

This PR changes the public API surface (+57 / −56), per the docs' rules (@ignore / @Private / undocumented are excluded).

Show API diff
-BoundingBox.compute(vertices: number[] | Float32Array<ArrayBufferLike>, numVerts?: number): void
+BoundingBox.compute(vertices: ArrayLike<number>, numVerts?: number): void
-BoundingBox.static computeMinMax(vertices: number[] | Float32Array<ArrayBufferLike>, min: Vec3, max: Vec3, numVerts?: number): void
+BoundingBox.static computeMinMax(vertices: ArrayLike<number>, min: Vec3, max: Vec3, numVerts?: number): void
-BoxGeometry.blendIndices: number[] | undefined
-BoxGeometry.blendWeights: number[] | undefined
+BoxGeometry.blendIndices: ArrayLike<number> | undefined
+BoxGeometry.blendWeights: ArrayLike<number> | undefined
-BoxGeometry.colors: number[] | undefined
+BoxGeometry.colors: ArrayLike<number> | undefined
-CapsuleGeometry.blendIndices: number[] | undefined
-CapsuleGeometry.blendWeights: number[] | undefined
+CapsuleGeometry.blendIndices: ArrayLike<number> | undefined
+CapsuleGeometry.blendWeights: ArrayLike<number> | undefined
-CapsuleGeometry.colors: number[] | undefined
+CapsuleGeometry.colors: ArrayLike<number> | undefined
-CircleGeometry.blendIndices: number[] | undefined
-CircleGeometry.blendWeights: number[] | undefined
+CircleGeometry.blendIndices: ArrayLike<number> | undefined
+CircleGeometry.blendWeights: ArrayLike<number> | undefined
-CircleGeometry.colors: number[] | undefined
+CircleGeometry.colors: ArrayLike<number> | undefined
-ConeBaseGeometry.blendIndices: number[] | undefined
-ConeBaseGeometry.blendWeights: number[] | undefined
+ConeBaseGeometry.blendIndices: ArrayLike<number> | undefined
+ConeBaseGeometry.blendWeights: ArrayLike<number> | undefined
-ConeBaseGeometry.colors: number[] | undefined
-ConeBaseGeometry.tangents: number[] | undefined
-ConeGeometry.blendIndices: number[] | undefined
-ConeGeometry.blendWeights: number[] | undefined
+ConeBaseGeometry.colors: ArrayLike<number> | undefined
+ConeBaseGeometry.tangents: ArrayLike<number> | undefined
+ConeGeometry.blendIndices: ArrayLike<number> | undefined
+ConeGeometry.blendWeights: ArrayLike<number> | undefined
-ConeGeometry.colors: number[] | undefined
+ConeGeometry.colors: ArrayLike<number> | undefined
-CylinderGeometry.blendIndices: number[] | undefined
-CylinderGeometry.blendWeights: number[] | undefined
+CylinderGeometry.blendIndices: ArrayLike<number> | undefined
+CylinderGeometry.blendWeights: ArrayLike<number> | undefined
-CylinderGeometry.colors: number[] | undefined
+CylinderGeometry.colors: ArrayLike<number> | undefined
-DomeGeometry.blendIndices: number[] | undefined
-DomeGeometry.blendWeights: number[] | undefined
+DomeGeometry.blendIndices: ArrayLike<number> | undefined
+DomeGeometry.blendWeights: ArrayLike<number> | undefined
-DomeGeometry.colors: number[] | undefined
+DomeGeometry.colors: ArrayLike<number> | undefined
-Geometry.blendIndices: number[] | undefined
-Geometry.blendWeights: number[] | undefined
+Geometry.blendIndices: ArrayLike<number> | undefined
+Geometry.blendWeights: ArrayLike<number> | undefined
-Geometry.colors: number[] | undefined
-Geometry.indices: number[] | undefined
-Geometry.normals: number[] | undefined
-Geometry.positions: number[] | undefined
-Geometry.tangents: number[] | undefined
-Geometry.uvs1: number[] | undefined
-Geometry.uvs: number[] | undefined
+Geometry.colors: ArrayLike<number> | undefined
+Geometry.indices: number[] | Uint8Array<ArrayBufferLike> | Uint32Array<ArrayBufferLike> | Uint16Array<ArrayBufferLike> | undefined
+Geometry.normals: ArrayLike<number> | undefined
+Geometry.positions: ArrayLike<number> | undefined
+Geometry.tangents: ArrayLike<number> | undefined
+Geometry.uvs1: ArrayLike<number> | undefined
+Geometry.uvs: ArrayLike<number> | undefined
-Mesh.getColors(colors: number[] | ArrayBufferView<ArrayBufferLike>): number
+Mesh.getColors(colors: NumericArray): number
-Mesh.getNormals(normals: number[] | ArrayBufferView<ArrayBufferLike>): number
-Mesh.getPositions(positions: number[] | ArrayBufferView<ArrayBufferLike>): number
-Mesh.getUvs(channel: number, uvs: number[] | ArrayBufferView<ArrayBufferLike>): number
-Mesh.getVertexStream(semantic: string, data: number[] | ArrayBufferView<ArrayBufferLike>): number
+Mesh.getNormals(normals: NumericArray): number
+Mesh.getPositions(positions: NumericArray): number
+Mesh.getUvs(channel: number, uvs: NumericArray): number
+Mesh.getVertexStream(semantic: string, data: NumericArray): number
-Mesh.setColors(colors: number[] | ArrayBufferView<ArrayBufferLike>, componentCount?: number, numVertices?: number): void
-Mesh.setColors32(colors: number[] | ArrayBufferView<ArrayBufferLike>, numVertices?: number): void
+Mesh.setColors(colors: ArrayLike<number>, componentCount?: number, numVertices?: number): void
+Mesh.setColors32(colors: ArrayLike<number>, numVertices?: number): void
-Mesh.setNormals(normals: number[] | ArrayBufferView<ArrayBufferLike>, componentCount?: number, numVertices?: number): void
-Mesh.setPositions(positions: number[] | ArrayBufferView<ArrayBufferLike>, componentCount?: number, numVertices?: number): void
-Mesh.setUvs(channel: number, uvs: number[] | ArrayBufferView<ArrayBufferLike>, componentCount?: number, numVertices?: number): void
-Mesh.setVertexStream(semantic: string, data: number[] | ArrayBufferView<ArrayBufferLike>, componentCount: number, numVertices?: number, dataType?: number, dataTypeNormalize?: boolean, asInt?: boolean): void
+Mesh.setNormals(normals: ArrayLike<number>, componentCount?: number, numVertices?: number): void
+Mesh.setPositions(positions: ArrayLike<number>, componentCount?: number, numVertices?: number): void
+Mesh.setUvs(channel: number, uvs: ArrayLike<number>, componentCount?: number, numVertices?: number): void
+Mesh.setVertexStream(semantic: string, data: ArrayLike<number>, componentCount: number, numVertices?: number, dataType?: number, dataTypeNormalize?: boolean, asInt?: boolean): void
-MorphTarget.constructor(options: { aabb: BoundingBox; defaultWeight: number; deltaNormals: ArrayBuffer; deltaPositions: ArrayBuffer; name: string; preserveData: boolean }, ...args: any[])
+MorphTarget.constructor(options: { aabb: BoundingBox; defaultWeight: number; deltaNormals: ArrayLike<number>; deltaPositions: ArrayLike<number>; name: string; preserveData: boolean }, ...args: any[])
-PlaneGeometry.blendIndices: number[] | undefined
-PlaneGeometry.blendWeights: number[] | undefined
+PlaneGeometry.blendIndices: ArrayLike<number> | undefined
+PlaneGeometry.blendWeights: ArrayLike<number> | undefined
-PlaneGeometry.colors: number[] | undefined
+PlaneGeometry.colors: ArrayLike<number> | undefined
-SphereGeometry.blendIndices: number[] | undefined
-SphereGeometry.blendWeights: number[] | undefined
+SphereGeometry.blendIndices: ArrayLike<number> | undefined
+SphereGeometry.blendWeights: ArrayLike<number> | undefined
-SphereGeometry.colors: number[] | undefined
+SphereGeometry.colors: ArrayLike<number> | undefined
-TorusGeometry.blendIndices: number[] | undefined
-TorusGeometry.blendWeights: number[] | undefined
+TorusGeometry.blendIndices: ArrayLike<number> | undefined
+TorusGeometry.blendWeights: ArrayLike<number> | undefined
-TorusGeometry.colors: number[] | undefined
+TorusGeometry.colors: ArrayLike<number> | undefined
-function calculateNormals(positions: number[], indices: number[]): number[]
-function calculateTangents(positions: number[], normals: number[], uvs: number[], indices: number[]): number[]
+function calculateNormals(positions: ArrayLike<number>, indices: ArrayLike<number>): number[]
+function calculateTangents(positions: ArrayLike<number>, normals: ArrayLike<number>, uvs: ArrayLike<number>, indices: ArrayLike<number>): number[]
+type NumericArray = number[] | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array

Informational only — this never fails the build.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2326.8 KB (+0.3 KB, +0.01%) 598.6 KB (+0.2 KB, +0.03%) 465.4 KB (−0.1 KB, −0.02%)
playcanvas.min.mjs 2324.2 KB (+0.3 KB, +0.01%) 597.5 KB (+0.1 KB, +0.03%) 464.7 KB (+0.4 KB, +0.08%)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR resolves TypeScript/JSDoc typing mismatches around geometry/mesh data APIs by widening read-only inputs to ArrayLike<number> (supporting both number[] and typed arrays), introducing a writable NumericArray type for getter destinations, and fixing Mesh#getVertexStream array-population behavior when streams haven’t been applied yet.

Changes:

  • Widened JSDoc types across GeometryMeshBoundingBox (and geometry-utils) to accept typed arrays via ArrayLike<number>.
  • Fixed Mesh#getVertexStream when writing into JS arrays for unapplied streams (now copies element-wise instead of pushing a nested array).
  • Added focused tests covering typed-array + array behavior for geometry utils and mesh stream readback, and removed stale @ts-ignore from examples.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/scene/mesh.test.mjs Adds regression tests for Mesh#getPositions / getIndices readback before/after update().
test/scene/geometry/geometry-utils.test.mjs Adds tests ensuring normals/tangents behave identically for arrays vs typed arrays.
src/scene/morph-target.js Corrects morph target delta docs to numeric arrays (ArrayLike<number>).
src/scene/mesh.js Introduces NumericArray typedef, widens setter params to ArrayLike<number>, and fixes array-destination stream copying.
src/scene/geometry/geometry.js Widens stored geometry data fields and indices typing to match mesh APIs.
src/scene/geometry/geometry-utils.js Widens calculateNormals / calculateTangents params to ArrayLike<number>.
src/core/shape/bounding-box.js Widens bounding-box vertex inputs to ArrayLike<number>.
examples/src/examples/graphics/mesh-morph.example.mjs Removes unnecessary @ts-ignore after typing fixes.
examples/src/examples/graphics/mesh-generation.example.mjs Removes unnecessary @ts-ignore after typing fixes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/scene/mesh.js
Comment thread src/scene/mesh.js

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/scene/mesh.js:861

  • Similar to getVertexStream(), this typed-array destination branch always uses a JS loop. If the staged index data is a typed array, using TypedArray#set with a subarray of the used range is faster and consistent with IndexBuffer#readData.
                // destination data is typed array, copy as much of the data as it can hold
                Debug.assert(indices.length >= count, 'Destination array is too small to receive all index data.');
                const numValues = Math.min(indices.length, count);
                for (let i = 0; i < numValues; i++) {
                    indices[i] = streamIndices[i];
                }

Comment thread src/scene/mesh.js Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated no new comments.

@mvaligursky
mvaligursky merged commit 9560471 into main Jul 27, 2026
11 checks passed
@mvaligursky
mvaligursky deleted the mv-geometry-array-types branch July 27, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: graphics Graphics related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Doc issue

2 participants