Skip to content

Instantly share code, notes, and snippets.

@donmccurdy
Last active September 25, 2024 15:50
Show Gist options
  • Save donmccurdy/c090dc53c7bfb704ef9de654ecc07632 to your computer and use it in GitHub Desktop.
Save donmccurdy/c090dc53c7bfb704ef9de654ecc07632 to your computer and use it in GitHub Desktop.
Convert legacy three.js (JSON) files to glTF 2.0

legacythree2gltf.js

Converts legacy JSON models (created by the three.js Blender exporter, for THREE.JSONLoader) to glTF 2.0. When original .blend files are available, prefer direct export from Blender 2.80+.

NOTE: JSON files created with .toJSON() use a newer JSON format, which isn't deprecated, and these are still supported by THREE.ObjectLoader. This converter does not support that newer type of JSON file.

Installation:

npm install canvas vblob three

Usage:

./legacythree2gltf.js model.json --optimize

Known issues:

  • Creates .gltf files with embedded Data URIs. Optimize to .glb using glTF-Pipeline to reduce file size.
  • Limited support for morph targets (mrdoob/three.js#15011)
#!/usr/bin/env node
const fs = require( 'fs' );
const path = require( 'path' );
const Canvas = require( 'canvas' );
const { Blob, FileReader } = require( 'vblob' );
THREE = global.THREE = require( 'three' );
require( 'three/examples/js/loaders/deprecated/LegacyJSONLoader.js' );
require( 'three/examples/js/exporters/GLTFExporter.js' );
require( 'three/examples/js/utils/BufferGeometryUtils.js' );
if ( process.argv.length <= 2 ) {
console.log( `Usage: ${path.basename( __filename )} model.json [ --optimize ]` );
process.exit( - 1 );
}
var file = process.argv[ 2 ];
var optimize = process.argv.indexOf( '--optimize' ) > 0;
var resourceDirectory = THREE.LoaderUtils.extractUrlBase( file );
//
// Patch global scope to imitate browser environment.
global.window = global;
global.Blob = Blob;
global.FileReader = FileReader;
global.THREE = THREE;
global.document = {
createElement: ( nodeName ) => {
if ( nodeName !== 'canvas' ) throw new Error( `Cannot create node ${nodeName}` );
const canvas = new Canvas( 256, 256 );
// This isn't working — currently need to avoid toBlob(), so export to embedded .gltf not .glb.
// canvas.toBlob = function () {
// return new Blob([this.toBuffer()]);
// };
return canvas;
}
};
//
// Load legacy JSON file and construct a mesh.
var jsonContent = fs.readFileSync( file, 'utf8' );
var loader = new THREE.LegacyJSONLoader();
var { geometry, materials } = loader.parse( JSON.parse( jsonContent ), resourceDirectory );
var mesh;
var boneDefs = geometry.bones || [];
var animations = geometry.animations || [];
var hasVertexColors = geometry.colors.length > 0;
geometry = new THREE.BufferGeometry().fromGeometry( geometry );
// Remove unnecessary vertex colors and groups added during BufferGeometry conversion.
if ( ! hasVertexColors ) geometry.removeAttribute( 'color' );
if ( geometry.groups.length === 1 ) geometry.clearGroups();
if ( ! materials ) {
materials = new THREE.MeshStandardMaterial( { color: 0x888888, roughness: 1, metalness: 0 } );
}
if ( optimize ) {
geometry = THREE.BufferGeometryUtils.mergeVertices( geometry );
}
if ( boneDefs.length ) {
var { roots, bones } = initBones( boneDefs );
mesh = new THREE.SkinnedMesh( geometry, materials );
roots.forEach( ( bone ) => mesh.add( bone ) );
mesh.updateMatrixWorld( true );
mesh.bind( new THREE.Skeleton( bones ), mesh.matrixWorld );
mesh.normalizeSkinWeights();
} else {
mesh = new THREE.Mesh( geometry, materials );
}
//
// Export to glTF.
var exporter = new THREE.GLTFExporter();
exporter.parse( mesh, ( json ) => {
var content = JSON.stringify( json );
fs.writeFileSync( path.basename( file, '.json' ) + '.gltf', content, 'utf8' );
}, { binary: false, animations } );
//
/** Previously SkinnedMesh.initBones(). */
function initBones( boneDefs ) {
var bones = [], bone, gbone;
var i, il;
// first, create array of 'Bone' objects from geometry data
for ( i = 0, il = boneDefs.length; i < il; i ++ ) {
gbone = boneDefs[ i ];
// create new 'Bone' object
bone = new THREE.Bone();
bones.push( bone );
// apply values
bone.name = gbone.name;
bone.position.fromArray( gbone.pos );
bone.quaternion.fromArray( gbone.rotq );
if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl );
}
// second, create bone hierarchy
var roots = [];
for ( i = 0, il = boneDefs.length; i < il; i ++ ) {
gbone = boneDefs[ i ];
if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) {
// subsequent bones in the hierarchy
bones[ gbone.parent ].add( bones[ i ] );
} else {
// topmost bone, immediate child of the skinned mesh
roots.push( bones[ i ] );
}
}
return { roots, bones };
}
@rahulkhandepts
Copy link

Hi @donmccurdy

I'm trying to export gltf from node server. This sample helped me with Three js version 110 but will newest version, I get error as:
code:
'ERR_PACKAGE_PATH_NOT_EXPORTED'
message:
'Package subpath './examples/js/exporters/GLTFExporter.js' is not defined by "exports" in d:\three-js-legachy\node_modules\three\package.json'
stack:
'Error [ERR_PACKAGE_PATH_NOT_EXPORTED]: Package subpath './examples/js/exporters/GLTFExporter.js' is not defined by "exports" in d:\three-js-legachy\node

Can you please advice. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment