/** * @author mrdoob / http://mrdoob.com/ */ import * as THREE from '../../../build/three.module.js'; import { RGBELoader } from '../../../examples/jsm/loaders/RGBELoader.js'; import { TGALoader } from '../../../examples/jsm/loaders/TGALoader.js'; import { UIElement, UISpan, UIDiv, UIRow, UIButton, UICheckbox, UIText, UINumber } from './ui.js'; import { MoveObjectCommand } from '../commands/MoveObjectCommand.js'; /** * @author mrdoob / http://mrdoob.com/ */ var UITexture = function ( mapping ) { UIElement.call( this ); var scope = this; var dom = document.createElement( 'span' ); var form = document.createElement( 'form' ); var input = document.createElement( 'input' ); input.type = 'file'; input.addEventListener( 'change', function ( event ) { loadFile( event.target.files[ 0 ] ); } ); form.appendChild( input ); var canvas = document.createElement( 'canvas' ); canvas.width = 32; canvas.height = 16; canvas.style.cursor = 'pointer'; canvas.style.marginRight = '5px'; canvas.style.border = '1px solid #888'; canvas.addEventListener( 'click', function () { input.click(); }, false ); canvas.addEventListener( 'drop', function () { event.preventDefault(); event.stopPropagation(); loadFile( event.dataTransfer.files[ 0 ] ); }, false ); dom.appendChild( canvas ); function loadFile( file ) { if ( file.type.match( 'image.*' ) ) { var reader = new FileReader(); if ( file.type === 'image/targa' ) { reader.addEventListener( 'load', function ( event ) { var canvas = new TGALoader().parse( event.target.result ); var texture = new THREE.CanvasTexture( canvas, mapping ); texture.sourceFile = file.name; scope.setValue( texture ); if ( scope.onChangeCallback ) scope.onChangeCallback( texture ); }, false ); reader.readAsArrayBuffer( file ); } else { reader.addEventListener( 'load', function ( event ) { var image = document.createElement( 'img' ); image.addEventListener( 'load', function () { var texture = new THREE.Texture( this, mapping ); texture.sourceFile = file.name; texture.format = file.type === 'image/jpeg' ? THREE.RGBFormat : THREE.RGBAFormat; texture.needsUpdate = true; scope.setValue( texture ); if ( scope.onChangeCallback ) scope.onChangeCallback( texture ); }, false ); image.src = event.target.result; }, false ); reader.readAsDataURL( file ); } } else { var reader = new FileReader(); reader.addEventListener( 'load', function ( event ) { if ( file.name.split( '.' ).pop() === 'hdr' ) { // assuming RGBE/Radiance HDR iamge format var loader = new RGBELoader().setDataType( THREE.UnsignedByteType ); loader.load( event.target.result, function ( hdrTexture ) { hdrTexture.sourceFile = file.name; hdrTexture.isHDRTexture = true; scope.setValue( hdrTexture ); if ( scope.onChangeCallback ) scope.onChangeCallback( hdrTexture ); } ); } } ); reader.readAsDataURL( file ); } form.reset(); } this.dom = dom; this.texture = null; this.onChangeCallback = null; return this; }; UITexture.prototype = Object.create( UIElement.prototype ); UITexture.prototype.constructor = UITexture; UITexture.prototype.getValue = function () { return this.texture; }; UITexture.prototype.setValue = function ( texture ) { var canvas = this.dom.children[ 0 ]; var context = canvas.getContext( '2d' ); if ( texture !== null ) { var image = texture.image; if ( image !== undefined && image.width > 0 ) { canvas.title = texture.sourceFile; var scale = canvas.width / image.width; if ( image.data === undefined ) { context.drawImage( image, 0, 0, image.width * scale, image.height * scale ); } else { var canvas2 = renderToCanvas( texture ); context.drawImage( canvas2, 0, 0, image.width * scale, image.height * scale ); } } else { canvas.title = texture.sourceFile + ' (error)'; context.clearRect( 0, 0, canvas.width, canvas.height ); } } else { canvas.title = 'empty'; if ( context !== null ) { // Seems like context can be null if the canvas is not visible context.clearRect( 0, 0, canvas.width, canvas.height ); } } this.texture = texture; }; UITexture.prototype.setEncoding = function ( encoding ) { var texture = this.getValue(); if ( texture !== null ) { texture.encoding = encoding; } return this; }; UITexture.prototype.onChange = function ( callback ) { this.onChangeCallback = callback; return this; }; // UICubeTexture var UICubeTexture = function () { UIElement.call( this ); var container = new UIDiv(); this.cubeTexture = null; this.onChangeCallback = null; this.dom = container.dom; this.textures = []; var scope = this; var pRow = new UIRow(); var nRow = new UIRow(); pRow.add( new UIText( 'P:' ).setWidth( '35px' ) ); nRow.add( new UIText( 'N:' ).setWidth( '35px' ) ); var posXTexture = new UITexture().onChange( onTextureChanged ); var negXTexture = new UITexture().onChange( onTextureChanged ); var posYTexture = new UITexture().onChange( onTextureChanged ); var negYTexture = new UITexture().onChange( onTextureChanged ); var posZTexture = new UITexture().onChange( onTextureChanged ); var negZTexture = new UITexture().onChange( onTextureChanged ); this.textures.push( posXTexture, negXTexture, posYTexture, negYTexture, posZTexture, negZTexture ); pRow.add( posXTexture ); pRow.add( posYTexture ); pRow.add( posZTexture ); nRow.add( negXTexture ); nRow.add( negYTexture ); nRow.add( negZTexture ); container.add( pRow, nRow ); function onTextureChanged() { var images = []; for ( var i = 0; i < scope.textures.length; i ++ ) { var texture = scope.textures[ i ].getValue(); if ( texture !== null ) { images.push( texture.isHDRTexture ? texture : texture.image ); } } if ( images.length === 6 ) { var cubeTexture = new THREE.CubeTexture( images ); cubeTexture.needsUpdate = true; if ( images[ 0 ].isHDRTexture ) cubeTexture.isHDRTexture = true; scope.cubeTexture = cubeTexture; if ( scope.onChangeCallback ) scope.onChangeCallback( cubeTexture ); } } }; UICubeTexture.prototype = Object.create( UIElement.prototype ); UICubeTexture.prototype.constructor = UICubeTexture; UICubeTexture.prototype.setEncoding = function ( encoding ) { var cubeTexture = this.getValue(); if ( cubeTexture !== null ) { cubeTexture.encoding = encoding; } return this; }; UICubeTexture.prototype.getValue = function () { return this.cubeTexture; }; UICubeTexture.prototype.setValue = function ( cubeTexture ) { this.cubeTexture = cubeTexture; if ( cubeTexture !== null ) { var images = cubeTexture.image; if ( Array.isArray( images ) === true && images.length === 6 ) { for ( var i = 0; i < images.length; i ++ ) { var image = images[ i ]; var texture = new THREE.Texture( image ); this.textures[ i ].setValue( texture ); } } } else { var textures = this.textures; for ( var i = 0; i < textures.length; i ++ ) { textures[ i ].setValue( null ); } } return this; }; UICubeTexture.prototype.onChange = function ( callback ) { this.onChangeCallback = callback; return this; }; // UIOutliner var UIOutliner = function ( editor ) { UIElement.call( this ); var scope = this; var dom = document.createElement( 'div' ); dom.className = 'Outliner'; dom.tabIndex = 0; // keyup event is ignored without setting tabIndex // hack this.scene = editor.scene; // Prevent native scroll behavior dom.addEventListener( 'keydown', function ( event ) { switch ( event.keyCode ) { case 38: // up case 40: // down event.preventDefault(); event.stopPropagation(); break; } }, false ); // Keybindings to support arrow navigation dom.addEventListener( 'keyup', function ( event ) { switch ( event.keyCode ) { case 38: // up scope.selectIndex( scope.selectedIndex - 1 ); break; case 40: // down scope.selectIndex( scope.selectedIndex + 1 ); break; } }, false ); this.dom = dom; this.editor = editor; this.options = []; this.selectedIndex = - 1; this.selectedValue = null; return this; }; UIOutliner.prototype = Object.create( UIElement.prototype ); UIOutliner.prototype.constructor = UIOutliner; UIOutliner.prototype.selectIndex = function ( index ) { if ( index >= 0 && index < this.options.length ) { this.setValue( this.options[ index ].value ); var changeEvent = document.createEvent( 'HTMLEvents' ); changeEvent.initEvent( 'change', true, true ); this.dom.dispatchEvent( changeEvent ); } }; UIOutliner.prototype.setOptions = function ( options ) { var scope = this; while ( scope.dom.children.length > 0 ) { scope.dom.removeChild( scope.dom.firstChild ); } function onClick() { scope.setValue( this.value ); var changeEvent = document.createEvent( 'HTMLEvents' ); changeEvent.initEvent( 'change', true, true ); scope.dom.dispatchEvent( changeEvent ); } // Drag var currentDrag; function onDrag() { currentDrag = this; } function onDragStart( event ) { event.dataTransfer.setData( 'text', 'foo' ); } function onDragOver( event ) { if ( this === currentDrag ) return; var area = event.offsetY / this.clientHeight; if ( area < 0.25 ) { this.className = 'option dragTop'; } else if ( area > 0.75 ) { this.className = 'option dragBottom'; } else { this.className = 'option drag'; } } function onDragLeave() { if ( this === currentDrag ) return; this.className = 'option'; } function onDrop( event ) { if ( this === currentDrag ) return; this.className = 'option'; var scene = scope.scene; var object = scene.getObjectById( currentDrag.value ); var area = event.offsetY / this.clientHeight; if ( area < 0.25 ) { var nextObject = scene.getObjectById( this.value ); moveObject( object, nextObject.parent, nextObject ); } else if ( area > 0.75 ) { var nextObject = scene.getObjectById( this.nextSibling.value ); moveObject( object, nextObject.parent, nextObject ); } else { var parentObject = scene.getObjectById( this.value ); moveObject( object, parentObject ); } } function moveObject( object, newParent, nextObject ) { if ( nextObject === null ) nextObject = undefined; var newParentIsChild = false; object.traverse( function ( child ) { if ( child === newParent ) newParentIsChild = true; } ); if ( newParentIsChild ) return; var editor = scope.editor; editor.execute( new MoveObjectCommand( editor, object, newParent, nextObject ) ); var changeEvent = document.createEvent( 'HTMLEvents' ); changeEvent.initEvent( 'change', true, true ); scope.dom.dispatchEvent( changeEvent ); } // scope.options = []; for ( var i = 0; i < options.length; i ++ ) { var div = options[ i ]; div.className = 'option'; scope.dom.appendChild( div ); scope.options.push( div ); div.addEventListener( 'click', onClick, false ); if ( div.draggable === true ) { div.addEventListener( 'drag', onDrag, false ); div.addEventListener( 'dragstart', onDragStart, false ); // Firefox needs this div.addEventListener( 'dragover', onDragOver, false ); div.addEventListener( 'dragleave', onDragLeave, false ); div.addEventListener( 'drop', onDrop, false ); } } return scope; }; UIOutliner.prototype.getValue = function () { return this.selectedValue; }; UIOutliner.prototype.setValue = function ( value ) { for ( var i = 0; i < this.options.length; i ++ ) { var element = this.options[ i ]; if ( element.value === value ) { element.classList.add( 'active' ); // scroll into view var y = element.offsetTop - this.dom.offsetTop; var bottomY = y + element.offsetHeight; var minScroll = bottomY - this.dom.offsetHeight; if ( this.dom.scrollTop > y ) { this.dom.scrollTop = y; } else if ( this.dom.scrollTop < minScroll ) { this.dom.scrollTop = minScroll; } this.selectedIndex = i; } else { element.classList.remove( 'active' ); } } this.selectedValue = value; return this; }; var UIPoints = function ( onAddClicked ) { UIElement.call( this ); var span = new UISpan().setDisplay( 'inline-block' ); this.pointsList = new UIDiv(); span.add( this.pointsList ); var row = new UIRow(); span.add( row ); var addPointButton = new UIButton( '+' ).onClick( onAddClicked ); row.add( addPointButton ); this.update = function () { if ( this.onChangeCallback !== null ) { this.onChangeCallback(); } }.bind( this ); this.dom = span.dom; this.pointsUI = []; this.lastPointIdx = 0; this.onChangeCallback = null; return this; }; UIPoints.prototype = Object.create( UIElement.prototype ); UIPoints.prototype.constructor = UIPoints; UIPoints.prototype.onChange = function ( callback ) { this.onChangeCallback = callback; return this; }; UIPoints.prototype.clear = function () { for ( var i = 0; i < this.pointsUI.length; ++ i ) { if ( this.pointsUI[ i ] ) { this.deletePointRow( i, true ); } } this.lastPointIdx = 0; }; UIPoints.prototype.deletePointRow = function ( idx, dontUpdate ) { if ( ! this.pointsUI[ idx ] ) return; this.pointsList.remove( this.pointsUI[ idx ].row ); this.pointsUI[ idx ] = null; if ( dontUpdate !== true ) { this.update(); } }; var UIPoints2 = function () { UIPoints.call( this, UIPoints2.addRow.bind( this ) ); return this; }; UIPoints2.prototype = Object.create( UIPoints.prototype ); UIPoints2.prototype.constructor = UIPoints2; UIPoints2.addRow = function () { if ( this.pointsUI.length === 0 ) { this.pointsList.add( this.createPointRow( 0, 0 ) ); } else { var point = this.pointsUI[ this.pointsUI.length - 1 ]; this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue() ) ); } this.update(); }; UIPoints2.prototype.getValue = function () { var points = []; var count = 0; for ( var i = 0; i < this.pointsUI.length; i ++ ) { var pointUI = this.pointsUI[ i ]; if ( ! pointUI ) continue; points.push( new THREE.Vector2( pointUI.x.getValue(), pointUI.y.getValue() ) ); ++ count; pointUI.lbl.setValue( count ); } return points; }; UIPoints2.prototype.setValue = function ( points ) { this.clear(); for ( var i = 0; i < points.length; i ++ ) { var point = points[ i ]; this.pointsList.add( this.createPointRow( point.x, point.y ) ); } this.update(); return this; }; UIPoints2.prototype.createPointRow = function ( x, y ) { var pointRow = new UIDiv(); var lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' ); var txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update ); var txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update ); var idx = this.lastPointIdx; var scope = this; var btn = new UIButton( '-' ).onClick( function () { if ( scope.isEditing ) return; scope.deletePointRow( idx ); } ); this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } ); ++ this.lastPointIdx; pointRow.add( lbl, txtX, txtY, btn ); return pointRow; }; var UIPoints3 = function () { UIPoints.call( this, UIPoints3.addRow.bind( this ) ); return this; }; UIPoints3.prototype = Object.create( UIPoints.prototype ); UIPoints3.prototype.constructor = UIPoints3; UIPoints3.addRow = function () { if ( this.pointsUI.length === 0 ) { this.pointsList.add( this.createPointRow( 0, 0, 0 ) ); } else { var point = this.pointsUI[ this.pointsUI.length - 1 ]; this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue(), point.z.getValue() ) ); } this.update(); }; UIPoints3.prototype.getValue = function () { var points = []; var count = 0; for ( var i = 0; i < this.pointsUI.length; i ++ ) { var pointUI = this.pointsUI[ i ]; if ( ! pointUI ) continue; points.push( new THREE.Vector3( pointUI.x.getValue(), pointUI.y.getValue(), pointUI.z.getValue() ) ); ++ count; pointUI.lbl.setValue( count ); } return points; }; UIPoints3.prototype.setValue = function ( points ) { this.clear(); for ( var i = 0; i < points.length; i ++ ) { var point = points[ i ]; this.pointsList.add( this.createPointRow( point.x, point.y, point.z ) ); } this.update(); return this; }; UIPoints3.prototype.createPointRow = function ( x, y, z ) { var pointRow = new UIDiv(); var lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' ); var txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update ); var txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update ); var txtZ = new UINumber( z ).setWidth( '30px' ).onChange( this.update ); var idx = this.lastPointIdx; var scope = this; var btn = new UIButton( '-' ).onClick( function () { if ( scope.isEditing ) return; scope.deletePointRow( idx ); } ); this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY, z: txtZ } ); ++ this.lastPointIdx; pointRow.add( lbl, txtX, txtY, txtZ, btn ); return pointRow; }; var UIBoolean = function ( boolean, text ) { UISpan.call( this ); this.setMarginRight( '10px' ); this.checkbox = new UICheckbox( boolean ); this.text = new UIText( text ).setMarginLeft( '3px' ); this.add( this.checkbox ); this.add( this.text ); }; UIBoolean.prototype = Object.create( UISpan.prototype ); UIBoolean.prototype.constructor = UIBoolean; UIBoolean.prototype.getValue = function () { return this.checkbox.getValue(); }; UIBoolean.prototype.setValue = function ( value ) { return this.checkbox.setValue( value ); }; var renderer; function renderToCanvas( texture ) { if ( renderer === undefined ) { renderer = new THREE.WebGLRenderer( { canvas: new OffscreenCanvas( 1, 1 ) } ); } var image = texture.image; renderer.setSize( image.width, image.height, false ); renderer.toneMapping = THREE.ReinhardToneMapping; renderer.toneMappingExposure = 2; renderer.outputEncoding = THREE.sRGBEncoding; var scene = new THREE.Scene(); var camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); var material = new THREE.MeshBasicMaterial( { map: texture } ); var quad = new THREE.PlaneBufferGeometry( 2, 2 ); var mesh = new THREE.Mesh( quad, material ); scene.add( mesh ); renderer.render( scene, camera ); return renderer.domElement; } export { UITexture, UICubeTexture, UIOutliner, UIPoints, UIPoints2, UIPoints3, UIBoolean };