three
Version:
JavaScript 3D library
282 lines (147 loc) • 4.86 kB
JavaScript
import { FileLoader, error } from 'three';
import * as THREE from 'three';
import * as TSL from 'three/tsl';
const _library = {
'three/tsl': { ...TSL }
};
const STORAGE_PREFIX = 'tsl-graph';
const STORAGE_CODE = 'tsl-graph-code';
function _storageKey( graphId ) {
return `${STORAGE_PREFIX}:${graphId}`;
}
class TSLGraphLoaderApplier {
constructor( tslGraphFns ) {
this.tslGraphFns = tslGraphFns;
}
apply( scene ) {
const tslGraphFns = this.tslGraphFns;
scene.traverse( ( object ) => {
if ( object.material && object.material.userData.graphId ) {
if ( tslGraphFns[ object.material.userData.graphId ] ) {
tslGraphFns[ object.material.userData.graphId ]( object.material );
object.material.needsUpdate = true;
}
}
} );
}
}
export class TSLGraphLoader extends FileLoader {
constructor( manager ) {
super( manager );
}
load( url, onLoad, onProgress, onError ) {
super.load( url, ( text ) => {
let json;
try {
json = JSON.parse( text );
} catch ( e ) {
if ( onError ) onError( e );
return;
}
const applier = this.parse( json );
if ( onLoad ) onLoad( applier );
}, onProgress, onError );
}
parseMaterial( json ) {
const baseFn = 'tslGraph';
const imports = {};
const materials = [ this._generateMaterialCode( json, baseFn, imports ) ];
const code = this._generateCode( materials, imports );
const tslFunction = new Function( code )()( THREE, imports );
return tslFunction;
}
parseMaterials( json ) {
const imports = {};
const materials = [];
for ( const [ name, material ] of Object.entries( json ) ) {
materials.push( this._generateMaterialCode( material, name, imports ) );
}
const code = this._generateCode( materials, imports );
const tslFunction = new Function( code )()( THREE, imports );
return tslFunction;
}
parse( json ) {
let result;
if ( json.material && json.material.code ) {
result = this.parseMaterial( json.material );
} else if ( json.materials ) {
result = this.parseMaterials( json.materials );
} else if ( json.codes && json.graphs ) {
result = this.parseMaterials( json.codes.materials );
TSLGraphLoader.setGraphs( json );
}
return new TSLGraphLoaderApplier( result );
}
_generateMaterialCode( json, name = 'tslGraph', imports = {} ) {
const code = json.code.replace( 'function tslGraph', `materials[ '${ name }' ] = function` ).replace( /\n|^/g, '\n\t' );
for ( const importData of json.imports ) {
if ( _library[ importData.from ] ) {
for ( const importName of importData.imports ) {
if ( _library[ importData.from ][ importName ] ) {
imports[ importName ] = _library[ importData.from ][ importName ];
} else {
error( `TSLGraph: Import ${ importName } not found in ${ importData.from }.` );
}
}
} else {
error( `TSLGraph: Library ${ importData.from } not found.` );
}
}
return code;
}
_generateCode( materials, imports ) {
const fnCode = `return ( THREE, { ${ Object.keys( imports ).join( ', ' ) } } ) => {\n\n\tconst materials = {};\n${ materials.join( '\n' ) }\n\n\treturn materials;\n\n}`;
return fnCode;
}
static get hasGraphs() {
return Object.keys( TSLGraphLoader.getCodes().materials ).length > 0;
}
static getCodes() {
const code = window.localStorage.getItem( STORAGE_CODE );
return code ? JSON.parse( code ) : { materials: {} };
}
static setCodes( codes ) {
window.localStorage.setItem( STORAGE_CODE, JSON.stringify( codes ) );
}
static setGraph( graphId, graphData ) {
window.localStorage.setItem( _storageKey( graphId ), JSON.stringify( graphData ) );
}
static getGraph( graphId ) {
const raw = window.localStorage.getItem( _storageKey( graphId ) );
if ( ! raw ) return null;
try {
return JSON.parse( raw );
} catch ( e ) {
error( 'TSLGraph: Invalid graph JSON in localStorage, ignoring.', e );
return null;
}
}
static deleteGraph( graphId ) {
window.localStorage.removeItem( _storageKey( graphId ) );
}
static setGraphs( json ) {
if ( ! json.codes || ! json.graphs ) {
throw new Error( 'TSLGraph: Invalid import file structure.' );
}
TSLGraphLoader.clearGraphs();
// Save imported graph visualizations
for ( const [ id, graphData ] of Object.entries( json.graphs ) ) {
TSLGraphLoader.setGraph( id, graphData );
}
// Fully overwrite codes
TSLGraphLoader.setCodes( json.codes );
return json;
}
static clearGraphs() {
const keysToRemove = [];
for ( let i = 0; i < window.localStorage.length; i ++ ) {
const key = window.localStorage.key( i );
if ( key.startsWith( STORAGE_PREFIX ) ) {
keysToRemove.push( key );
}
}
for ( const key of keysToRemove ) {
window.localStorage.removeItem( key );
}
}
}