Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(gltf): getting typed arrays for accessor #2691

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions modules/gltf/src/lib/extensions/EXT_mesh_features.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// GLTF EXTENSION: EXT_mesh_features
// https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features
/* eslint-disable camelcase */
import type {NumericArray} from '@loaders.gl/loader-utils';
import type {GLTF, GLTFMeshPrimitive} from '../types/gltf-json-schema';
import {GLTFLoaderOptions} from '../../gltf-loader';
import type {
Expand All @@ -10,6 +11,7 @@ import type {

import {GLTFScenegraph} from '../api/gltf-scenegraph';
import {getPrimitiveTextureData} from './utils/3d-tiles-utils';
import {getTypedArrayForAccessor} from '../gltf-utils/get-typed-array';

const EXT_MESH_FEATURES_NAME = 'EXT_mesh_features';
export const name = EXT_MESH_FEATURES_NAME;
Expand Down Expand Up @@ -63,12 +65,16 @@ function processMeshPrimitiveFeatures(
}

for (const featureId of featureIds) {
let featureIdData: number[] | null = null;
let featureIdData: NumericArray | null = null;
// Process "Feature ID by Vertex"
if (typeof featureId.attribute !== 'undefined') {
const accessorKey = `_FEATURE_ID_${featureId.attribute}`;
const accessorIndex = primitive.attributes[accessorKey];
featureIdData = scenegraph.getTypedArrayForAccessor(accessorIndex) as number[];
featureIdData = getTypedArrayForAccessor(
scenegraph.gltf.json,
scenegraph.gltf.buffers,
accessorIndex
);
}

// Process "Feature ID by Texture Coordinates"
Expand Down
28 changes: 18 additions & 10 deletions modules/gltf/src/lib/extensions/utils/3d-tiles-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import type {BigTypedArray, TypedArray} from '@loaders.gl/schema';
import type {ImageType} from '@loaders.gl/images';

import {GLTFScenegraph} from '../../api/gltf-scenegraph';
import {getComponentTypeFromArray, getFloat32ArrayForAccessor} from '../../gltf-utils/gltf-utils';
import {getComponentTypeFromArray} from '../../gltf-utils/gltf-utils';
import {getTypedArrayForAccessor} from '../../gltf-utils/get-typed-array';
import {getImageData} from '@loaders.gl/images';
import {emod} from '@loaders.gl/math';

Expand Down Expand Up @@ -160,24 +161,27 @@ export function getPrimitiveTextureData(
"source": 0
}
"sampler" is an index for the "samplers" array
"source" is an index for the "images" array that contains data. These data are stored in rgba channels of the image.
"source" is an index for the "images" array that contains data stored in rgba channels of the image.

texture.texCoord is a number-suffix (like 1) for an attribute like "TEXCOORD_1" in meshes.primitives
The value of "TEXCOORD_1" is an accessor that is used to get coordinates. These coordinates are being used to get data from the image.
The value of "TEXCOORD_1" is an accessor that is used to get coordinates.
These coordinates are being used to get data from the image.

Default for texture.texCoord is 0
@see https://github.com/CesiumGS/glTF/blob/3d-tiles-next/specification/2.0/schema/textureInfo.schema.json
*/
const json = scenegraph.gltf.json;

const texCoordAccessorKey = `TEXCOORD_${textureInfo.texCoord || 0}`;
const texCoordAccessorIndex = primitive.attributes[texCoordAccessorKey];

const textureCoordinates: Float32Array | null = getFloat32ArrayForAccessor(
scenegraph.gltf,
const textureCoordinates: TypedArray | null = getTypedArrayForAccessor(
scenegraph.gltf.json,
scenegraph.gltf.buffers,
texCoordAccessorIndex
);
if (!textureCoordinates) {
return null;
}

const json = scenegraph.gltf.json;
const textureIndex: number = textureInfo.index;
const imageIndex = json.textures?.[textureIndex]?.source;
if (typeof imageIndex !== 'undefined') {
Expand Down Expand Up @@ -260,13 +264,17 @@ export function primitivePropertyDataToAttributes(
* @param mimeType - MIME type.
* @param textureCoordinates - uv coordinates to access data in the image.
* @param index - Index of uv coordinates in the array textureCoordinates.
* @param channels - Image channels where data are stored. Channels of an RGBA texture are numbered 0..3 respectively.
* @param channels - Image channels where data are stored.
* Channels of an RGBA texture are numbered 0..3 respectively.
* For Ext_mesh_features and EXT_strucural_metadata the channels default is [0]
* @see https://github.com/CesiumGS/glTF/blob/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features/schema/featureIdTexture.schema.json
* @see https://github.com/CesiumGS/glTF/blob/3d-tiles-next/extensions/2.0/Vendor/EXT_structural_metadata/schema/propertyTexture.property.schema.json
* @returns Value taken from the image.
*/
function getImageValueByCoordinates(
parsedImage: ImageType,
mimeType: string | undefined,
textureCoordinates: Float32Array,
textureCoordinates: TypedArray,
index: number,
channels: number[] | string = [0]
) {
Expand Down
67 changes: 53 additions & 14 deletions modules/gltf/src/lib/gltf-utils/get-typed-array.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// TODO - GLTFScenegraph should use these
import {assert} from '../utils/assert';
import type {TypedArray} from '@loaders.gl/schema';
import type {GLTF, GLTFExternalBuffer, GLTFAccessor} from '../types/gltf-types';
import {getAccessorArrayTypeAndLength} from './gltf-utils';

// accepts buffer view index or buffer view object
// returns a `Uint8Array`
Expand All @@ -24,18 +27,54 @@ export function getTypedArrayForImageData(json, buffers, imageIndex) {
return getTypedArrayForBufferView(json, buffers, bufferViewIndex);
}

/*
// accepts accessor index or accessor object
// returns a typed array with type that matches the types
export function getTypedArrayForAccessor(accessor) {
accessor = this.getAccessor(accessor);
const bufferView = this.getBufferView(accessor.bufferView);
const buffer = this.getBuffer(bufferView.buffer);
const arrayBuffer = buffer.data;

// Create a new typed array as a view into the combined buffer
const {ArrayType, length} = getAccessorArrayTypeAndLength(accessor, bufferView);
const byteOffset = bufferView.byteOffset + accessor.byteOffset;
return new ArrayType(arrayBuffer, byteOffset, length);
/**
* Gets data pointed by the accessor.
* @param json - json part of gltf content of a GLTF tile.
* @param buffers - Array containing buffers of data.
* @param accessor - accepts accessor index or accessor object.
* @returns {TypedArray} Typed array with type matching the type of data poited by the accessor.
*/
// eslint-disable-next-line complexity
export function getTypedArrayForAccessor(
Copy link
Collaborator

Choose a reason for hiding this comment

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

A function like this should really have a couple of test cases. It can be very painful to debug code when the error happens in a low-level utility like this.

json: GLTF,
buffers: GLTFExternalBuffer[],
accessor: GLTFAccessor | number
): TypedArray {
const gltfAccessor = typeof accessor === 'number' ? json.accessors?.[accessor] : accessor;
if (!gltfAccessor) {
throw new Error(`No Accessor ${accessor}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can be very hard for end users to understand such an error message. Maybe mentioning glTF could give them a hint?

Suggested change
throw new Error(`No Accessor ${accessor}`);
throw new Error(`No glTF accessor ${accessor}`);

}
const bufferView = json.bufferViews?.[gltfAccessor.bufferView || 0];
if (!bufferView) {
throw new Error(`No Buffer View for Accessor ${bufferView}`);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
throw new Error(`No Buffer View for Accessor ${bufferView}`);
throw new Error(`No glTF buffer view for accessor ${bufferView}`);

}
// Get `arrayBuffer` the `bufferView` looks at
const {arrayBuffer, byteOffset: bufferByteOffset} = buffers[bufferView.buffer];
// Resulting byteOffset is sum of the buffer, accessor and bufferView byte offsets
const byteOffset =
(bufferByteOffset || 0) + (gltfAccessor.byteOffset || 0) + (bufferView.byteOffset || 0);
// Deduce TypedArray type and its length from `accessor` and `bufferView` data
const {ArrayType, length, componentByteSize, numberOfComponentsInElement} =
getAccessorArrayTypeAndLength(gltfAccessor, bufferView);
// 'length' is a whole number of components of all elements in the buffer pointed by the accessor
// Multiplier to calculate the address of the element in the arrayBuffer
const elementByteSize = componentByteSize * numberOfComponentsInElement;
const elementAddressScale = bufferView.byteStride || elementByteSize;
// Creare an array of component's type where all components (not just elements) will reside
if (typeof bufferView.byteStride === 'undefined' || bufferView.byteStride === elementByteSize) {
// No iterleaving
const result: TypedArray = new ArrayType(arrayBuffer, byteOffset, length);
return result;
}
// Iterleaving
const result: TypedArray = new ArrayType(length);
for (let i = 0; i < gltfAccessor.count; i++) {
const values = new ArrayType(
arrayBuffer,
byteOffset + i * elementAddressScale,
numberOfComponentsInElement
);
result.set(values, i * numberOfComponentsInElement);
}
return result;
}
*/
40 changes: 3 additions & 37 deletions modules/gltf/src/lib/gltf-utils/gltf-utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {assert} from '../utils/assert';

import type {GLTFWithBuffers} from '../types/gltf-types';
import type {GLTFPostprocessed} from '../types/gltf-postprocessed-schema';
import {BYTES, COMPONENTS} from '../gltf-utils/gltf-constants';

Expand Down Expand Up @@ -85,42 +84,9 @@ export function getAccessorArrayTypeAndLength(accessor, bufferView) {
const length = accessor.count * components;
const byteLength = accessor.count * components * bytesPerComponent;
assert(byteLength >= 0 && byteLength <= bufferView.byteLength);
return {ArrayType, length, byteLength};
}

export function getFloat32ArrayForAccessor(
gltfData: GLTFWithBuffers,
texCoordAccessor: number
): Float32Array | null {
const accessor = gltfData.json.accessors?.[texCoordAccessor];
if (accessor && typeof accessor.bufferView !== 'undefined') {
// Get `bufferView` of the `accessor`
const bufferView = gltfData.json.bufferViews?.[accessor.bufferView];
if (bufferView) {
// Get `arrayBuffer` the `bufferView` look at
const {arrayBuffer, byteOffset: bufferByteOffset} = gltfData.buffers[bufferView.buffer];
// Resulting byteOffset is sum of the buffer, accessor and bufferView byte offsets
const byteOffset =
(bufferByteOffset || 0) + (accessor.byteOffset || 0) + (bufferView.byteOffset || 0);
// Deduce TypedArray type and its length from `accessor` and `bufferView` data
const {ArrayType, length} = getAccessorArrayTypeAndLength(accessor, bufferView);
// Number of bytes each component occupies
const bytes = BYTES[accessor.componentType];
// Number of components. For the `TEXCOORD_0` with `VEC2` type, it must return 2
const components = COMPONENTS[accessor.type];
// Multiplier to calculate the address of the `TEXCOORD_0` element in the arrayBuffer
const elementAddressScale = bufferView.byteStride || bytes * components;
// Data transform to Float32Array
const result = new Float32Array(length);
for (let i = 0; i < accessor.count; i++) {
// Take [u, v] couple from the arrayBuffer
const uv = new ArrayType(arrayBuffer, byteOffset + i * elementAddressScale, 2);
result.set(uv, i * components);
}
return result;
}
}
return null;
const componentByteSize = BYTES[accessor.componentType];
const numberOfComponentsInElement = COMPONENTS[accessor.type];
return {ArrayType, length, byteLength, componentByteSize, numberOfComponentsInElement};
}

/**
Expand Down
2 changes: 1 addition & 1 deletion modules/gltf/src/lib/types/gltf-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type GLTFWithBuffers = {
images?: GLTFExternalImage[];
};

type GLTFExternalBuffer = {
export type GLTFExternalBuffer = {
arrayBuffer: ArrayBuffer;
byteOffset: number;
byteLength: number;
Expand Down
Loading