-
-
Notifications
You must be signed in to change notification settings - Fork 35.6k
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
GLTFLoader: Support morph targets w/ interleaved attributes #12800
GLTFLoader: Support morph targets w/ interleaved attributes #12800
Conversation
I think that'd be a better solution yeah. |
Ok, done. 👍 |
|
||
} | ||
|
||
return new BufferAttribute( array, itemSize, this.normalized ); |
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 would expect an InterleavedBufferAttribute
to be returned from the clone()
method.
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.
That seems more intuitive, yes, but I can't think of a reason you'd want data the attribute is interleaved with to also be copied... do we return a new InterleavedBufferAttribute with stride === itemSize
? Or rename this as toBufferAttribute()
?
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.
Only guessing here, but maybe
BufferAttribute.fromInterleavedBufferAttribute()
is the method you need to create.
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.
Do you mean a static method, or instance like geometry.fromBufferGeometry(bufferGeometry)
? Feels non-ideal making an empty array just to instantiate an empty buffer attribute then overwrite it. What about .toNonInterleaved()
similar to .toNonIndexed()
?
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 not sure if the problem you are trying to solve warrants a special method... How often do uses want to de-interleave their geometry?
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.
How often do uses want to de-interleave their geometry?
Not sure, I only need this within GLTFLoader. But the idea that .clone()
should exist for what most users will assume is a subclass of BufferAttribute seems nice on least-surprise principle. The original version of this PR put the method inside of GLTFLoader, and I changed that based on #12800 (comment).
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 think your suggestion makes sense.
var bufferAttribute = interleavedBufferAttribute.toNonInterleaved();
var geometry = new THREE.BufferGeometry();
geometry.addAttribute( 'position', bufferAttribute );
I'm just not sure if such a method is needed outside your loader... I'll leave that up to you.
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.
@mrdoob with the context above, any preference on moving this back into a private cloneBufferAttribute(attr)
helper, vs renaming clone()
as toNonInterleaved()
here?
I don't expect users outside GLTFLoader to call .toNonInterleaved()
directly, but we have multiple open issues about .clone()
not working consistently, and this is one of those edge cases — having any interleaved buffer attributes will cause bufferGeometry.clone()
to throw a TypeError.
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.
Agreed. I think this is an accurate statement of the issues:
-
@donmccurdy wants to de-interleave his buffer geometry. I think that should be implemented via a private helper for now.
-
BufferGeometry.clone()
does not support interleaved attributes. That should be fixed.
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've looked into this and created a little code snippet that should enable the support of interleaved attributes in BufferGeometry.clone()
. The most important thing first: Because interleaved attributes of a geometry share a common instance of ArrayBuffer, it does not make sense to perform an isolated clone of a single interleaved attribute. A clone operation is only valid on geometry level in order to ensure continuous, interleaved memory. The following code performs this task. The assumption of the implementation is that all interleaved attributes of a geometry share the same array buffer (which might not be true in all cases).
// attributes
var attributes = source.attributes;
var arrayBuffer;
for ( name in attributes ) {
var attribute = attributes[ name ];
if ( attribute.isInterleavedBufferAttribute ) {
if ( arrayBuffer === undefined ) {
// clone the array buffer once and use it for subsequent typed arrays
arrayBuffer = attribute.array.buffer.slice( 0 );
}
// now perform the actual clone of the attribute and its internal data
var typedArray = new attribute.array.constructor( arrayBuffer );
var interleavedBuffer = new attribute.data.constructor( typedArray, attribute.data.stride );
var interleavedBufferAttribute = new attribute.constructor( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
this.addAttribute( name, interleavedBufferAttribute );
} else {
this.addAttribute( name, attribute.clone() );
}
}
Besides, it's maybe a good idea to merge InterleavedBuffer
with InterleavedBufferAttribute
. Properties like dynamic
and updateRange
should be part of the attribute. Are there any reasons for a separation of InterleavedBuffer
and InterleavedBufferAttribute
?
Ok, I've reverted the
The separation of InterleavedBuffer and InterleavedBufferAttribute is a bit confusing to me as well... |
I think this PR needs a new title, now... |
/ping @benaadams for opinion |
fc96e29
to
81f9082
Compare
@benaadams for me the confusing bit is why |
Correct. The only difference of var arrayBuffer = new ArrayBuffer( 72 ); // a generic buffer that holds 72 bytes of data
// share the buffer across multiple typed arrays (this makes the vertex information interleaved!)
var positions = new Float32Array( arrayBuffer );
var normals = new Float32Array( arrayBuffer );
// create attributes without InterleavedBuffer via new THREE.InterleavedBufferAttribute( array, itemSize, stride, offset, normalized );
var interleavedPositionAttribute = new THREE.InterleavedBufferAttribute( positions, 3, 6, 0, false );
var interleavedNormalAttribute = new THREE.InterleavedBufferAttribute( normals, 3, 6, 3, false );
// fill in some data with e.g. interleavedPositionAttribute.setXYZ(); We can't use
So i think we do not necessarily need |
Do you understand that interleaved attribute data can contain a mixture of types? For example: var geometry = new THREE.BufferGeometry();
var stride32 = 7; // x/y/z + nx/ny/nz + color = 7 4-byte floats
var stride8 = 28; // x/y/z + nx/ny/nz + color = 28 1-byte uints
var float32Array = new Float32Array( numTriangles * 3 * stride32 );
var uint8Array = new Uint8Array( float32Array.buffer ); // same buffer, interpreted differently
// populate data ...
var ib32 = new THREE.InterleavedBuffer( float32Array, stride32 );
var ib8 = new THREE.InterleavedBuffer( uint8Array, stride8 );
geometry.addAttribute( 'position', new THREE.InterleavedBufferAttribute( ib32, 3, 0, false ) );
geometry.addAttribute( 'normal', new THREE.InterleavedBufferAttribute( ib32, 3, 3, false ) );
geometry.addAttribute( 'color', new THREE.InterleavedBufferAttribute( ib8, 3, 24, true ) ); |
Sure, but conceptually I think of the typed arrays as having a shared interleaved (array)buffer, not being encapsulated by an interleaved buffer. So where we have this structure:
Why not have this:
This matches a normal var geometry = new THREE.BufferGeometry();
var stride32 = 7; // x/y/z + nx/ny/nz + color = 7 4-byte floats
var stride8 = 28; // x/y/z + nx/ny/nz + color = 28 1-byte uints
var float32Array = new Float32Array( numTriangles * 3 * stride32 );
var uint8Array = new Uint8Array( float32Array.buffer ); // same buffer, interpreted differently
// populate data ...
geometry.addAttribute( 'position', new THREE.InterleavedBufferAttribute( float32Array, stride32, 3, 0, false ) );
geometry.addAttribute( 'normal', new THREE.InterleavedBufferAttribute( float32Array, stride32, 3, 3, false ) );
geometry.addAttribute( 'color', new THREE.InterleavedBufferAttribute( uint8Array, stride8, 3, 24, true ) ); The constructor arguments are different from |
If we would remove |
When i think about it, it should be also possible to have a single buffer created via |
Another scenario; its not just distinct attributes that are interleaved; it can be the same attribute. var geometry = new THREE.BufferGeometry();
var stride = 3; // x/y/z 4-byte floats
var float32Array = new Float32Array( (numPoints + 4) * 3);
// populate position data ...
var ib = new THREE.InterleavedBuffer( float32Array, stride );
// Same position data, different offsets
geometry.addAttribute( 'last', new THREE.InterleavedBufferAttribute( ib, 3, 0, false ) );
geometry.addAttribute( 'current', new THREE.InterleavedBufferAttribute( ib, 3, 2 * stride, false ) );
geometry.addAttribute( 'next', new THREE.InterleavedBufferAttribute( ib, 3, 4 * stride, false ) );
var mesh = new THREE.Mesh( geometry, material );
mesh.drawMode = THREE.TriangleStripDrawMode; You want to call Removing As for items like Or you'd need an aside dict of buffers types with dirty flags keyed on the attribute; but the extra lookup wouldn't be good for perf. |
Um, I see what you mean. @benaadams Can you make a suggestions on how to support interleaved attributes in |
Ok, yes this makes sense and I agree that mutating the primitive array should be avoided. If the InterleavedBuffer's UUID is used to avoid calling var geometry = new THREE.BufferGeometry();
var stride32 = 7; // x/y/z + nx/ny/nz + color = 7 4-byte floats
var stride8 = 28; // x/y/z + nx/ny/nz + color = 28 1-byte uints
var float32Array = new Float32Array( numTriangles * 3 * stride32 );
var uint8Array = new Uint8Array( float32Array.buffer ); // same buffer, interpreted differently
// populate data ...
var ib32 = new THREE.InterleavedBuffer( float32Array, stride32 );
var ib8 = new THREE.InterleavedBuffer( uint8Array, stride8 );
geometry.addAttribute( 'position', new THREE.InterleavedBufferAttribute( ib32, 3, 0, false ) );
geometry.addAttribute( 'normal', new THREE.InterleavedBufferAttribute( ib32, 3, 3, false ) );
geometry.addAttribute( 'color', new THREE.InterleavedBufferAttribute( ib8, 3, 24, true ) ); Or does this result in multiple |
Aside, I think the PR itself is OK to merge at this point and orthogonal to discussion above. |
Thanks! |
array[ i ] = attribute.getX( i ); | ||
if ( itemSize >= 2 ) array[ i + 1 ] = attribute.getY( i ); | ||
if ( itemSize >= 3 ) array[ i + 2 ] = attribute.getZ( i ); | ||
if ( itemSize >= 4 ) array[ i + 3 ] = attribute.getW( i ); |
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.
@donmccurdy Um, I'm not sure this section is correct. If you use i
in order to refer to the current array position, you are going to overwrite values after array[ i ] = attribute.getX( i );
since you raise the variable in the for
loop. I think it should look like this:
var j = 0;
for ( var i = 0; i < count; ++ i ) {
array[ j ++ ] = attribute.getX( i );
if ( itemSize >= 2 ) array[ j ++ ] = attribute.getY( i );
if ( itemSize >= 3 ) array[ j ++ ] = attribute.getZ( i );
if ( itemSize >= 4 ) array[ j ++ ] = attribute.getW( i );
}
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.
Oops, yes. There's a sample file that uses interleaved morph targets in donmccurdy/three-gltf-viewer#56, but it looks like I never got an answer back from the OP about whether the result was what they expected.
Do you think we should fix this directly, or change something in a base class per #15395?
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 would change it directly. I've actually added this code section in #15413. I'm not sure how much effort it takes not enable copy/clone and serialization/deserialization of interleaved buffer attributes. For now, I vote to create "normal" buffer attributes if we have to clone or serialization them.
Fixes donmccurdy/three-gltf-viewer#56.
Alternatively, I'd be glad to add this implementation as
InterleavedBufferAttribute.prototype.clone()
, which currently does not exist./cc @takahirox