-
Notifications
You must be signed in to change notification settings - Fork 193
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
feat(gltf): extensions, encoding feature metadata #2972
Conversation
*/ | ||
export function encodeExtMeshFeatures( | ||
scenegraph: GLTFScenegraph, | ||
featureIdArray: number[] | TypedArray, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally we would create the extension with these fields, and then call encode()
on the extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
* @returns primitive updated | ||
* @see https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_mesh_features | ||
*/ | ||
export function encodeExtMeshFeatures( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The convention in the extension files is that each extension exports decode
and encode
functions. That way one can work with a list of extensions and call this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove tile-converter
changes. We will change the tile-converter
in separate RPs
} | ||
|
||
function convertBuffersToBase64(gltf, {firstBuffer = 0} = {}) { | ||
if (gltf.buffers && gltf.buffers.length > firstBuffer) { | ||
throw new Error('encodeGLTF: multiple buffers not yet implemented'); | ||
if (gltf.buffers && gltf.buffers.length > firstBuffer 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this logic has changed? Probably, breaking change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the function name is "convertBuffersToBase64"? It doesn't convert anything, just check number of buffers
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
/** | ||
* Decodes feature metadata from extension. | ||
* @param {GLTFScenegraph} scenegraph - Instance of the class for structured access to GLTF data. | ||
* @param {GLTFLoaderOptions} options - GLTFLoader options. | ||
*/ | ||
function decodeExtMeshFeatures(scenegraph: GLTFScenegraph, options: GLTFLoaderOptions): void { | ||
export function decodeExtMeshFeatures( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it exported? We don't need it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Encoding data | ||
*/ | ||
|
||
export function encodeExtMeshFeatures(scenegraph: GLTFScenegraph, options: GLTFWriterOptions) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't need to export it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const typedArray = new Uint32Array(featureIds); | ||
const tableIndex = 8; | ||
|
||
createExtMeshFeatures(scenegraph, primitive, typedArray, tableIndex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The extensions should have interfaces "decode" and "encode". createExtMeshFeature
is not necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed the removing this function results in the necessity to put all input data into GLTF which complicates the implementation.
|
||
test('gltf#EXT_structural_metadata - Should encode', async (t) => { | ||
const scenegraph = new GLTFScenegraph(); | ||
const tableIndex = createExtStructuralMetadata(scenegraph, ATTRIBUTES); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extensions should work through encode
and decode
. createExtStructuralMetadata
is not necessary and have to be included into encode
workflow.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed the removing this function results in the necessity to put all input data into GLTF which complicates the implementation.
@mspivak-actionengine i this ready for review? |
@ibgreen Please give me some time to fix the Victor's comments, which might change some logic. I'll let you know when ready. Sorry for that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest landing EXT_MESH_FEATURES
in this PR and separate EXT_STRUCTURAL_METADATA
in an another one.
/** | ||
* Creates ExtMeshFeatures, creates a featureId containing feature ids provided. | ||
* @param scenegraph - Instance of the class for structured access to GLTF data. | ||
* @primitive - target primitive instance that will contain the extension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* @primitive - target primitive instance that will contain the extension | |
* @param primitive - target primitive instance that will contain the extension |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -75,6 85,8 @@ function processMeshPrimitiveFeatures( | |||
// Process "Feature ID by Texture Coordinates" | |||
else if (typeof featureId.texture !== 'undefined' && options?.gltf?.loadImages) { | |||
featureIdData = getPrimitiveTextureData(scenegraph, featureId.texture, primitive); | |||
// Clean up input data | |||
featureId.texture = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this for encoding? I'd remove
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's for decoding. I have removed it and changed the encoding part to make sure that the featureId object doesn't contain anything that can come from the original data (round-trip scenario as an example).
primitive.extensions[EXT_MESH_FEATURES_NAME] = extension; | ||
} | ||
|
||
const featureIds: GLTF_EXT_mesh_features_featureId[] = extension.featureIds; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit
const featureIds: GLTF_EXT_mesh_features_featureId[] = extension.featureIds; | |
const { featureIds } = extension; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// Search for all "_FEATURE_ID_n" attribures in the primitive provided if any. | ||
// If there are some, e.g. "_FEATURE_ID_0", "_FEATURE_ID_1", | ||
// we will add a new one with the name "_FEATURE_ID_2" | ||
const attrs = Object.keys(attributes).filter((item) => item.indexOf(prefix) !== -1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: we need startsWith
logic that is more precise.
const attrs = Object.keys(attributes).filter((item) => item.indexOf(prefix) !== -1); | |
const attrs = Object.keys(attributes).filter((item) => item.indexOf(prefix) === 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
export function createExtMeshFeatures( | ||
scenegraph: GLTFScenegraph, | ||
primitive: GLTFMeshPrimitive, | ||
featureIdArray: TypedArray, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Output of decode
is NumericArray
. Lets consume similar data here. Can we narrow to this type in GLTF_EXT_mesh_features_featureId
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if (featureId.data) { | ||
const {accessorKey, index} = createAccessorKey(primitive.attributes); | ||
featureId.attribute = index; | ||
const typedArray = new Uint32Array(featureId.data as number[]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const typedArray = new Uint32Array(featureId.data as number[]); | |
const typedArray = new Uint32Array(featureId.data as NumericArray); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
*/ | ||
export function createExtStructuralMetadata( | ||
scenegraph: GLTFScenegraph, | ||
typedFeatureAttributes: TypedFeatureAttribute[], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The extension should work with encode
function (similar to EXT_MESH_FEATURES
).
We need th create...
function but locate it after encode
.
featureAttributes is an I3S entities. We need something more generic. Look at the propertyTable
which is an output of this extension decode
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will be addressed in a separate PR for the EXT_structural _metadata.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON.parse(JSON.stringify
is a hack. Try to avoid it in tests.
// Clean up featureId object. | ||
// Everything that could come from the original extension in case of round-trip decode/encode operations should be deleted. | ||
// We need make sure the featureId object is clean. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What this comment for? You say "clean up" and then add new objects to the gltf. Forgot a real clean up?
|
||
test('gltf#EXT_mesh_features - Should decode', async (t) => { | ||
const options = {gltf: {loadImages: true, loadBuffers: true}}; | ||
const gltf = JSON.parse(JSON.stringify(GLTF_WITH_EXTENSION)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To avoid affecting test data, the better solution is making a function wrapper:
const getGltfWithExtension = () => ({
buffers: [
{
arrayBuffer: [],
byteOffset: 0,
byteLength: 128
}
],
images: [
....
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks for the idea.
const gltf = JSON.parse(JSON.stringify(GLTF_WITH_EXTENSION)); | ||
gltf.buffers[0].arrayBuffer = new Uint8Array(binaryBufferDataAlligned).buffer; | ||
await decodeExtensions(gltf, options); | ||
// The clone of GLTF_WITH_EXTENSION has been modified |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment before an empty line is confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
encodeExtensions(scenegraph.gltf, {}); | ||
|
||
// The clone of GLTF_WITH_EXTENSION has been modified | ||
t.deepEqual(JSON.stringify(primitive), JSON.stringify(PRIMITIVE_EXPECTED)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deepEqual
should work without JSON.stringify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// The clone of GLTF_WITH_EXTENSION has been modified | ||
const gltfBin = encodeExtensions(gltf, options); | ||
|
||
const json = deleteUndefined(gltfBin.json); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add undefined
values to the expected object, probably
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@ibgreen Hi Ib. The PR is ready. Could you please review it? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Conditionally approved.
Required
- Update docs and release notes
Nice to have
- A bit more comments in the code to guide a user who is not familiar with the extension.
return; | ||
} | ||
const featureIds: GLTF_EXT_mesh_features_featureId[] = extension.featureIds; | ||
featureIds.forEach((featureId, ind) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Type out variable names
featureIds.forEach((featureId, ind) => { | |
featureIds.forEach((featureId, index) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if (!extension) { | ||
return; | ||
} | ||
const featureIds: GLTF_EXT_mesh_features_featureId[] = extension.featureIds; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible to clone the feature ids so that we don't mutate the existing data structure?
const featureIds: GLTF_EXT_mesh_features_featureId[] = extension.featureIds; | |
const featureIds: GLTF_EXT_mesh_features_featureId[] = {...extension.featureIds}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm afraid not. While encoding/decoding extensions we use temporal fields in the structure and change the data accordingly.
}); | ||
} | ||
|
||
function createAccessorKey(attributes: {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add documentation and types to every function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic of scenegraph was:
- Create scenegraph;
- Make changes;
- Call
createBinaryChunk
in the current scope in order to save buffer changes
The logic of sceengraph now is not obvious. You have to do
scenegraph.gltf.buffers.push({
arrayBuffer: typedArray.buffer,
byteOffset: typedArray.byteOffset,
byteLength: typedArray.byteLength
});
that is outside of scenegraph in the extension. Whithout this piece of code your approach won't work.
I'd preserve the logic from our previous inspection to save time. We can support deferred call of createBinaryChunk
in a separate PR because we should think a little bit more on it.
modules/gltf/src/gltf-writer.ts
Outdated
const scenegraph = new GLTFScenegraph(gltf); | ||
scenegraph.createBinaryChunk(); | ||
const gltfToEncode = scenegraph.gltf; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need createBinaryChunk
here. It's done in the extension already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
// export function encode(gltfData: {json: GLTF}, options: GLTFWriterOptions): void { | ||
// const scenegraph = new GLTFScenegraph(gltfData); | ||
// encodeExtMeshFeatures(scenegraph, options); | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
No description provided.