Skip to content

Commit d51be1a

Browse files
mrdoobclaude
andcommitted
JSON v5: Centralize buffers and use UUID-keyed collections
- Bump serialization version to 5 - Extract buffer data to top-level `buffers` object to avoid duplication - Change collections (geometries, materials, textures, etc.) from arrays to UUID-keyed objects - Remove redundant uuid property from items (uuid is now the key) - Use explicit type strings instead of boolean flags: - `isInstancedBufferAttribute` → `type: 'InstancedBufferAttribute'` - `isInterleavedBufferAttribute` → `type: 'InterleavedBufferAttribute'` - `isInstancedInterleavedBuffer` → `type: 'InstancedInterleavedBuffer'` - Add `arrayType` property to preserve typed array constructor name - ObjectLoader: Add `_distributeBuffers()` to merge buffers into geometries - ObjectLoader: Add `_convertLegacyCollections()` for v4 compatibility - ObjectLoader: Parse methods now iterate over objects natively - BufferGeometryLoader: Support both v4 and v5 attribute formats Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent dcb73dd commit d51be1a

13 files changed

+1466
-78
lines changed

src/core/BufferGeometry.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1213,7 +1213,7 @@ class BufferGeometry extends EventDispatcher {
12131213

12141214
const data = {
12151215
metadata: {
1216-
version: 4.7,
1216+
version: 5,
12171217
type: 'BufferGeometry',
12181218
generator: 'BufferGeometry.toJSON'
12191219
}

src/core/InstancedBufferAttribute.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ class InstancedBufferAttribute extends BufferAttribute {
5555

5656
const data = super.toJSON();
5757

58+
data.type = 'InstancedBufferAttribute';
59+
data.arrayType = data.type;
5860
data.meshPerAttribute = this.meshPerAttribute;
5961

60-
data.isInstancedBufferAttribute = true;
61-
6262
return data;
6363

6464
}

src/core/InstancedInterleavedBuffer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class InstancedInterleavedBuffer extends InterleavedBuffer {
6262

6363
const json = super.toJSON( data );
6464

65-
json.isInstancedInterleavedBuffer = true;
65+
json.type = 'InstancedInterleavedBuffer';
6666
json.meshPerAttribute = this.meshPerAttribute;
6767

6868
return json;

src/core/InterleavedBuffer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,8 +279,9 @@ class InterleavedBuffer {
279279

280280
return {
281281
uuid: this.uuid,
282+
type: 'InterleavedBuffer',
283+
arrayType: this.array.constructor.name,
282284
buffer: this.array.buffer._uuid,
283-
type: this.array.constructor.name,
284285
stride: this.stride
285286
};
286287

src/core/InterleavedBufferAttribute.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ class InterleavedBufferAttribute {
532532
}
533533

534534
return {
535-
isInterleavedBufferAttribute: true,
535+
type: 'InterleavedBufferAttribute',
536536
itemSize: this.itemSize,
537537
data: this.data.uuid,
538538
offset: this.offset,

src/core/Object3D.js

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ class Object3D extends EventDispatcher {
12831283
};
12841284

12851285
output.metadata = {
1286-
version: 4.7,
1286+
version: 5,
12871287
type: 'Object',
12881288
generator: 'Object3D.toJSON'
12891289
};
@@ -1513,45 +1513,92 @@ class Object3D extends EventDispatcher {
15131513

15141514
if ( isRootObject ) {
15151515

1516-
const geometries = extractFromCache( meta.geometries );
1517-
const materials = extractFromCache( meta.materials );
1518-
const textures = extractFromCache( meta.textures );
1519-
const images = extractFromCache( meta.images );
1520-
const shapes = extractFromCache( meta.shapes );
1521-
const skeletons = extractFromCache( meta.skeletons );
1522-
const animations = extractFromCache( meta.animations );
1523-
const nodes = extractFromCache( meta.nodes );
1524-
1525-
if ( geometries.length > 0 ) output.geometries = geometries;
1526-
if ( materials.length > 0 ) output.materials = materials;
1527-
if ( textures.length > 0 ) output.textures = textures;
1528-
if ( images.length > 0 ) output.images = images;
1529-
if ( shapes.length > 0 ) output.shapes = shapes;
1530-
if ( skeletons.length > 0 ) output.skeletons = skeletons;
1531-
if ( animations.length > 0 ) output.animations = animations;
1532-
if ( nodes.length > 0 ) output.nodes = nodes;
1516+
// Remove metadata and uuid (redundant since uuid is the key) from each item
1517+
const collections = [ meta.geometries, meta.materials, meta.textures, meta.images, meta.shapes, meta.skeletons, meta.animations, meta.nodes ];
1518+
1519+
for ( const collection of collections ) {
1520+
1521+
for ( const key in collection ) {
1522+
1523+
delete collection[ key ].metadata;
1524+
delete collection[ key ].uuid;
1525+
1526+
}
1527+
1528+
}
1529+
1530+
// Extract buffers from geometries to top-level
1531+
const buffers = extractBuffers( meta.geometries );
1532+
if ( Object.keys( buffers ).length > 0 ) output.buffers = buffers;
1533+
1534+
// Output collections as UUID-keyed objects
1535+
if ( Object.keys( meta.geometries ).length > 0 ) output.geometries = meta.geometries;
1536+
if ( Object.keys( meta.materials ).length > 0 ) output.materials = meta.materials;
1537+
if ( Object.keys( meta.textures ).length > 0 ) output.textures = meta.textures;
1538+
if ( Object.keys( meta.images ).length > 0 ) output.images = meta.images;
1539+
if ( Object.keys( meta.shapes ).length > 0 ) output.shapes = meta.shapes;
1540+
if ( Object.keys( meta.skeletons ).length > 0 ) output.skeletons = meta.skeletons;
1541+
if ( Object.keys( meta.animations ).length > 0 ) output.animations = meta.animations;
1542+
if ( Object.keys( meta.nodes ).length > 0 ) output.nodes = meta.nodes;
15331543

15341544
}
15351545

15361546
output.object = object;
15371547

15381548
return output;
15391549

1540-
// extract data from the cache hash
1541-
// remove metadata on each item
1542-
// and return as array
1543-
function extractFromCache( cache ) {
1550+
// Extract buffer data from geometries to top-level buffers object
1551+
function extractBuffers( geometries ) {
1552+
1553+
const buffers = {};
1554+
1555+
for ( const uuid in geometries ) {
1556+
1557+
const geometry = geometries[ uuid ];
1558+
const data = geometry.data;
1559+
1560+
if ( data === undefined ) continue;
1561+
1562+
// Extract arrayBuffers as typed array entries
1563+
if ( data.arrayBuffers !== undefined ) {
1564+
1565+
for ( const bufferUuid in data.arrayBuffers ) {
1566+
1567+
if ( buffers[ bufferUuid ] === undefined ) {
15441568

1545-
const values = [];
1546-
for ( const key in cache ) {
1569+
buffers[ bufferUuid ] = {
1570+
type: 'Uint32Array',
1571+
array: data.arrayBuffers[ bufferUuid ]
1572+
};
15471573

1548-
const data = cache[ key ];
1549-
delete data.metadata;
1550-
values.push( data );
1574+
}
1575+
1576+
}
1577+
1578+
delete data.arrayBuffers;
1579+
1580+
}
1581+
1582+
// Extract interleavedBuffers
1583+
if ( data.interleavedBuffers !== undefined ) {
1584+
1585+
for ( const bufferUuid in data.interleavedBuffers ) {
1586+
1587+
if ( buffers[ bufferUuid ] === undefined ) {
1588+
1589+
buffers[ bufferUuid ] = data.interleavedBuffers[ bufferUuid ];
1590+
1591+
}
1592+
1593+
}
1594+
1595+
delete data.interleavedBuffers;
1596+
1597+
}
15511598

15521599
}
15531600

1554-
return values;
1601+
return buffers;
15551602

15561603
}
15571604

src/loaders/BufferGeometryLoader.js

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { InstancedBufferGeometry } from '../core/InstancedBufferGeometry.js';
77
import { InstancedBufferAttribute } from '../core/InstancedBufferAttribute.js';
88
import { InterleavedBufferAttribute } from '../core/InterleavedBufferAttribute.js';
99
import { InterleavedBuffer } from '../core/InterleavedBuffer.js';
10+
import { InstancedInterleavedBuffer } from '../core/InstancedInterleavedBuffer.js';
1011
import { getTypedArray, error } from '../utils.js';
1112

1213
/**
@@ -99,8 +100,27 @@ class BufferGeometryLoader extends Loader {
99100

100101
const buffer = getArrayBuffer( json, interleavedBuffer.buffer );
101102

102-
const array = getTypedArray( interleavedBuffer.type, buffer );
103-
const ib = new InterleavedBuffer( array, interleavedBuffer.stride );
103+
// Use arrayType for v5 format, fall back to type for v4
104+
const arrayType = interleavedBuffer.arrayType || interleavedBuffer.type;
105+
const array = getTypedArray( arrayType, buffer );
106+
107+
let ib;
108+
109+
if ( interleavedBuffer.type === 'InstancedInterleavedBuffer' ) {
110+
111+
ib = new InstancedInterleavedBuffer( array, interleavedBuffer.stride, interleavedBuffer.meshPerAttribute );
112+
113+
} else if ( interleavedBuffer.isInstancedInterleavedBuffer ) {
114+
115+
// v4 backwards compatibility
116+
ib = new InstancedInterleavedBuffer( array, interleavedBuffer.stride, interleavedBuffer.meshPerAttribute );
117+
118+
} else {
119+
120+
ib = new InterleavedBuffer( array, interleavedBuffer.stride );
121+
122+
}
123+
104124
ib.uuid = interleavedBuffer.uuid;
105125

106126
interleavedBufferMap[ uuid ] = ib;
@@ -142,16 +162,30 @@ class BufferGeometryLoader extends Loader {
142162
const attribute = attributes[ key ];
143163
let bufferAttribute;
144164

145-
if ( attribute.isInterleavedBufferAttribute ) {
165+
// Check for interleaved buffer attribute (support both v4 and v5 formats)
166+
const isInterleaved = attribute.isInterleavedBufferAttribute || attribute.type === 'InterleavedBufferAttribute';
167+
168+
if ( isInterleaved ) {
146169

147170
const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
148171
bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
149172

150173
} else {
151174

152-
const typedArray = getTypedArray( attribute.type, attribute.array );
153-
const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
154-
bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
175+
// Check for instanced buffer attribute (support both v4 and v5 formats)
176+
const isInstanced = attribute.isInstancedBufferAttribute || attribute.type === 'InstancedBufferAttribute';
177+
178+
const typedArray = getTypedArray( isInstanced ? attribute.arrayType || 'Float32Array' : attribute.type, attribute.array );
179+
180+
if ( isInstanced ) {
181+
182+
bufferAttribute = new InstancedBufferAttribute( typedArray, attribute.itemSize, attribute.normalized, attribute.meshPerAttribute );
183+
184+
} else {
185+
186+
bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized );
187+
188+
}
155189

156190
}
157191

@@ -177,7 +211,10 @@ class BufferGeometryLoader extends Loader {
177211
const attribute = attributeArray[ i ];
178212
let bufferAttribute;
179213

180-
if ( attribute.isInterleavedBufferAttribute ) {
214+
// Check for interleaved buffer attribute (support both v4 and v5 formats)
215+
const isInterleaved = attribute.isInterleavedBufferAttribute || attribute.type === 'InterleavedBufferAttribute';
216+
217+
if ( isInterleaved ) {
181218

182219
const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
183220
bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );

0 commit comments

Comments
 (0)