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 };
}
@getkey
Copy link

getkey commented Jan 25, 2022

The last Three.js for which this script works is r110. three/examples/js/loaders/deprecated/LegacyJSONLoader.js has been removed from the subsequent releases.

npm install three@0.110.0

@LinkunGao
Copy link

Hi Don,

Thanks for this useful script, which gives me the opportunity to try to convert the previous Geometry based JSON model file into a GLTF file. But when I run this script I meet some issues, could you help me?

I used your script to try to convert a Geometry format JSON model to GLTF, but I failed to convert it whenever I use the mac or Windows system. Below is my operations:

  1. create a test folder
  2. npm install canvas vblob three@0.110.0
  3. copy the legacythree2gltf.js into test folder
  4. copy my test.json file into test folder
  5. In the terminal run: node legacythree2gltf.js test.json --optimize
    Is this a correct workflow?
    I also test npm install canvas vblob three@0.110.0 -g, it also doesn’t work.
    Then I also use the JSON file that I export from blender to test whether it be converted successfully or not.
    However, it always failed. When I run the script there is no response, e.g. no warning, no error, no new file created in the folder.

Could you give me an example or some advice on this problem?

Thank you

@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