Skip to content

Instantly share code, notes, and snippets.

@fijisoo
Forked from donmccurdy/README.md
Created August 27, 2021 22:07
Show Gist options
  • Save fijisoo/6cf0ea51314f73c2cbcc0df9d4f0a848 to your computer and use it in GitHub Desktop.
Save fijisoo/6cf0ea51314f73c2cbcc0df9d4f0a848 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 };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment