Skip to content
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

legacythree2gltf: Add legacy JSON to glTF converter #15552

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions utils/converters/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,33 @@ Utilities for converting model files to the Three.js JSON format.
Usage:

```
node obj2three.js model.obj
./obj2three.js model.obj
```

## fbx2three.js

Usage:

```
node fbx2three.js model.fbx
./fbx2three.js model.fbx
```

## legacythree2gltf.js

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

Installation:

```
npm install canvas vblob
```

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 (https://github.com/mrdoob/three.js/pull/15011)
2 changes: 2 additions & 0 deletions utils/converters/fbx2three.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env node

var fs = require( 'fs' );
var path = require( 'path' );

Expand Down
155 changes: 155 additions & 0 deletions utils/converters/legacythree2gltf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
#!/usr/bin/env node

const fs = require( 'fs' );
const path = require( 'path' );
const Canvas = require( 'canvas' );
const { Blob, FileReader } = require( 'vblob' );
THREE = require( '../../build/three.js' );
require( '../../examples/js/loaders/deprecated/LegacyJSONLoader.js' );
require( '../../examples/js/exporters/GLTFExporter.js' );
require( '../../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 );
Mugen87 marked this conversation as resolved.
Show resolved Hide resolved

}

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 };

}
2 changes: 2 additions & 0 deletions utils/converters/obj2three.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#!/usr/bin/env node

var fs = require( 'fs' );
var path = require( 'path' );

Expand Down