Hi, I’m trying to export a scene with GLTFExporter, but the exported file is not being downloaded. The idea is that you can drag&drop any bitmap onto a mesh, adapt the scaling and position, and finally download the result as a glb file. The code is available here: three.js webgl - materials - matcap
Chrome inspector is reporting this error:
ER_webgl_materials_matcap_RepeatGUI_10.html:52
Uncaught ReferenceError: gltfExporter is not defined at exportGLTF (ER_webgl_materials_matcap_RepeatGUI_10.html:52:13)
at HTMLButtonElement. (ER_webgl_materials_matcap_RepeatGUI_10.html:72:21)
Any help much appreciated
This the code based on the matcap example:
`
<body>
<div id="info">
<a href="https://everyrealm.com/" target="_blank" rel="noopener">EveryRealm</a> Personae Texture Tool<br />
Drag-and-drop JPG, PNG image files<br/>
<button id="export_scene">Export Scene1</button>
<label><input id="option_binary" name="visible" type="checkbox">Binary (<code>.glb</code>)</label>
</div>
<script type="module">
import * as THREE from '../build/three.module.js';
import { GUI } from './jsm/libs/lil-gui.module.min.js';
import { OrbitControls } from './jsm/controls/OrbitControls.js';
import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
import { GLTFExporter } from './jsm/exporters/GLTFExporter.js';
import { EXRLoader } from './jsm/loaders/EXRLoader.js';
let mesh, renderer, scene, camera;
const API = {
color: 0xffffff, // sRGB
exposure: 0.65, //brightness
offsetX: 0,
offsetY: 0,
repeatX: 1,
repeatY: 1,
rotation: Math.PI / 4, // positive is counter-clockwise
centerX: 0.5,
centerY: 0.5
};
function exportGLTF( input ) {
const exporter = new GLTFExporter();
const options = {
binary: true,
maxTextureSize: 4096,
//animations: [animationClip],
includeCustomExtensions: true
};
console.log(options);
gltfExporter.parse(input, function (result) {
if (result instanceof ArrayBuffer) {
saveArrayBuffer(result, 'scene.glb');
} else {
const output = JSON.stringify(result, null, 2);
console.log(output);
saveString(output, 'scene.gltf');
}
}, options);
}
document.getElementById( 'export_scene' ).addEventListener( 'click', function () {
exportGLTF( scene );
console.log( 'Button pressed' );
} );
const link = document.createElement( 'a' );
link.style.display = 'none';
document.body.appendChild( link ); // Firefox workaround, see #6594
function save( blob, filename ) {
link.href = URL.createObjectURL( blob );
link.download = filename;
link.click();
// URL.revokeObjectURL( url ); breaks Firefox...
}
function saveString( text, filename ) {
save( new Blob( [ text ], { type: 'text/plain' } ), filename );
}
function saveArrayBuffer( buffer, filename ) {
save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename );
}
//end ofExportCode
init();
function init() {
// renderer
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// tone mapping
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = API.exposure;
renderer.outputEncoding = THREE.sRGBEncoding;
// scene
scene = new THREE.Scene();
scene.name = 'scene';
// camera
camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 100 );
camera.position.set( 0, 0, 13 );
// controls
const controls = new OrbitControls( camera, renderer.domElement );
controls.addEventListener( 'change', render );
controls.enableZoom = true;
controls.enablePan = false;
// manager
const manager = new THREE.LoadingManager( render );
// matcap
const loaderEXR = new EXRLoader( manager );
const matcap = loaderEXR.load( 'textures/matcaps/040full.exr' );
// normalmap2
const loader = new THREE.TextureLoader( manager );
const normalmap2 = loader.load( 'models/gltf/LeePerrySmith/Suit_Normal_OpenGLFlipV.jpg' );
const normalmap = loader.load( 'models/gltf/LeePerrySmith/grid.png' );
normalmap.wrapS = normalmap.wrapT = THREE.RepeatWrapping;
// model
new GLTFLoader( manager ).load( 'models/gltf/LeePerrySmith/jacket4.glb', function ( gltf ) {
mesh = gltf.scene.children[ 0 ];
mesh.position.y = - 0.25;
mesh.name = 'myMesh'; // <- set a name
mesh.material = new THREE.MeshBasicMaterial( {
color: new THREE.Color().setHex( API.color ).convertSRGBToLinear(),
//matcap: matcap,
map: normalmap,
normalmap: normalmap2,
} );
scene.add( mesh );
} );
// gui
const gui = new GUI();
gui.addColor( API, 'color' )
.listen()
.onChange( function () {
mesh.material.color.set( API.color ).convertSRGBToLinear();
render();
} );
gui.add( API, 'exposure', 0, 2 )
.onChange( function () {
renderer.toneMappingExposure = API.exposure;
render();
} );
gui.add( API, 'offsetX', 0.0, 1.0 )
.name( 'offset.x' )
.onChange( updateUvTransform );
gui.add( API, 'offsetY', 0.0, 1.0 ).name( 'offset.y' ).onChange( updateUvTransform );
gui.add( API, 'repeatX', 0.25, 20.0 ).name( 'repeat.x' ).onChange( updateUvTransform );
gui.add( API, 'repeatY', 0.25, 20.0 ).name( 'repeat.y' ).onChange( updateUvTransform );
gui.add( API, 'rotation', - 3.5, 3.5 ).name( 'rotation' ).onChange( updateUvTransform );
gui.add( API, 'centerX', 0.0, 1.0 ).name( 'center.x' ).onChange( updateUvTransform );
gui.add( API, 'centerY', 0.0, 1.0 ).name( 'center.y' ).onChange( updateUvTransform );
gui.domElement.style.webkitUserSelect = 'none';
// drag 'n drop
initDragAndDrop();
//
updateUvTransform();
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
render();
}
function render() {
renderer.render( scene, camera );
}
//
function updateUvTransform() {
const texture = mesh.material.map;
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
if ( texture.matrixAutoUpdate === true ) {
texture.offset.set( API.offsetX, API.offsetY );
texture.repeat.set( API.repeatX, API.repeatY );
//texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
//texture.wrapS = THREE.RepeatWrapping;
//texture.wrapT = THREE.RepeatWrapping;
texture.center.set( API.centerX, API.centerY );
texture.rotation = API.rotation; // rotation is around [ 0.5, 0.5 ]
} else {
// one way...
//texture.matrix.setUvTransform( API.offsetX, API.offsetY, API.repeatX, API.repeatY, API.rotation, API.centerX, API.centerY );
// another way...
texture.matrix
.identity()
.translate( - API.centerX, - API.centerY )
.rotate( API.rotation ) // I don't understand how rotation can preceed scale, but it seems to be required...
.scale( API.repeatX, API.repeatY )
.translate( API.centerX, API.centerY )
.translate( API.offsetX, API.offsetY );
}
render();
}
// drag and drop anywhere in document
//
function updateBasicMaterial( texture ) {
if ( mesh.material.map ) {
mesh.material.map.dispose();
}
mesh.material.map = texture;
texture.needsUpdate = true;
mesh.material.needsUpdate = true; // because the encoding can change
render();
}
function handleJPG( event ) { // PNG, too
function imgCallback( event ) {
const texture = new THREE.Texture( event.target );
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.encoding = THREE.sRGBEncoding;
updateBasicMaterial( texture );
}
const img = new Image();
img.onload = imgCallback;
img.src = event.target.result;
}
function handleEXR( event ) {
const contents = event.target.result;
const loader = new EXRLoader();
loader.setDataType( THREE.HalfFloatType );
const texData = loader.parse( contents );
const texture = new THREE.DataTexture();
texture.image.width = texData.width;
texture.image.height = texData.height;
texture.image.data = texData.data;
texture.format = texData.format;
texture.type = texData.type;
texture.encoding = THREE.LinearEncoding;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.generateMipmaps = false;
texture.flipY = false;
updatemap( texture );
}
function loadFile( file ) {
const filename = file.name;
const extension = filename.split( '.' ).pop().toLowerCase();
if ( extension === 'exr' ) {
const reader = new FileReader();
reader.addEventListener( 'load', function ( event ) {
handleEXR( event );
} );
reader.readAsArrayBuffer( file );
} else { // 'jpg', 'png'
const reader = new FileReader();
reader.addEventListener( 'load', function ( event ) {
handleJPG( event );
} );
reader.readAsDataURL( file );
}
}
function initDragAndDrop() {
document.addEventListener( 'dragover', function ( event ) {
event.preventDefault();
event.dataTransfer.dropEffect = 'copy';
} );
document.addEventListener( 'drop', function ( event ) {
event.preventDefault();
loadFile( event.dataTransfer.files[ 0 ] );
} );
}
</script>
</body>
`