📄 ui.three.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 28 |
🧱 Classes | 6 |
📦 Imports | 9 |
📊 Variables & Constants | 53 |
⚡ Async/Await Patterns | 1 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 editor/js/libs/ui.three.js
📦 Imports¶
Name | Source |
---|---|
FullScreenQuad |
three/addons/postprocessing/Pass.js |
UISpan |
./ui.js |
UIDiv |
./ui.js |
UIRow |
./ui.js |
UIButton |
./ui.js |
UICheckbox |
./ui.js |
UIText |
./ui.js |
UINumber |
./ui.js |
MoveObjectCommand |
../commands/MoveObjectCommand.js |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
cache |
Map<any, any> |
let/var | new Map() |
✗ |
scope |
this |
let/var | this |
✗ |
reader |
FileReader |
let/var | new FileReader() |
✗ |
hash |
string |
let/var | ${file.lastModified}_${file.size}_${file.name} |
✗ |
loader |
any |
let/var | new RGBELoader() |
✗ |
loader |
any |
let/var | new TGALoader() |
✗ |
arrayBuffer |
string \| ArrayBuffer |
let/var | event.target.result |
✗ |
ktx2Loader |
any |
let/var | new KTX2Loader() |
✗ |
arrayBuffer |
string \| ArrayBuffer |
let/var | event.target.result |
✗ |
exrLoader |
any |
let/var | new EXRLoader() |
✗ |
texture |
any |
let/var | new THREE.Texture( this ) |
✗ |
canvas |
any |
let/var | this.dom.children[ 0 ] |
✗ |
image |
any |
let/var | texture.image |
✗ |
scale |
number |
let/var | canvas.width / image.width |
✗ |
scope |
this |
let/var | this |
✗ |
changeEvent |
Event |
let/var | new Event( 'change', { bubbles: true, cancelable: true } ) |
✗ |
scope |
this |
let/var | this |
✗ |
changeEvent |
Event |
let/var | new Event( 'change', { bubbles: true, cancelable: true } ) |
✗ |
currentDrag |
any |
let/var | *not shown* |
✗ |
area |
number |
let/var | event.offsetY / this.clientHeight |
✗ |
scene |
any |
let/var | scope.scene |
✗ |
area |
number |
let/var | event.offsetY / this.clientHeight |
✗ |
nextObject |
any |
let/var | *not shown* |
✗ |
parent |
any |
let/var | *not shown* |
✗ |
newParentIsChild |
boolean |
let/var | false |
✗ |
editor |
any |
let/var | scope.editor |
✗ |
changeEvent |
Event |
let/var | new Event( 'change', { bubbles: true, cancelable: true } ) |
✗ |
div |
any |
let/var | options[ i ] |
✗ |
element |
any |
let/var | this.options[ i ] |
✗ |
y |
number |
let/var | element.offsetTop - this.dom.offsetTop |
✗ |
bottomY |
any |
let/var | y + element.offsetHeight |
✗ |
minScroll |
number |
let/var | bottomY - this.dom.offsetHeight |
✗ |
row |
UIRow |
let/var | new UIRow() |
✗ |
addPointButton |
UIButton |
let/var | new UIButton( '+' ) |
✗ |
point |
any |
let/var | this.pointsUI[ this.pointsUI.length - 1 ] |
✗ |
points |
any[] |
let/var | [] |
✗ |
count |
number |
let/var | 0 |
✗ |
pointUI |
any |
let/var | this.pointsUI[ i ] |
✗ |
point |
any |
let/var | points[ i ] |
✗ |
pointRow |
UIDiv |
let/var | new UIDiv() |
✗ |
scope |
this |
let/var | this |
✗ |
row |
UIRow |
let/var | new UIRow() |
✗ |
addPointButton |
UIButton |
let/var | new UIButton( '+' ) |
✗ |
point |
any |
let/var | this.pointsUI[ this.pointsUI.length - 1 ] |
✗ |
points |
any[] |
let/var | [] |
✗ |
count |
number |
let/var | 0 |
✗ |
pointUI |
any |
let/var | this.pointsUI[ i ] |
✗ |
point |
any |
let/var | points[ i ] |
✗ |
pointRow |
UIDiv |
let/var | new UIDiv() |
✗ |
scope |
this |
let/var | this |
✗ |
renderer |
any |
let/var | *not shown* |
✗ |
fsQuad |
any |
let/var | *not shown* |
✗ |
image |
any |
let/var | texture.image |
✗ |
Async/Await Patterns¶
Type | Function | Await Expressions | Promise Chains |
---|---|---|---|
async-function | loadFile |
import( 'three/addons/loaders/RGBELoader.js' ), import( 'three/addons/loaders/TGALoader.js' ), import( 'three/addons/loaders/KTX2Loader.js' ), import( 'three/addons/loaders/EXRLoader.js' ) | none |
Functions¶
UITexture.getValue(): any
¶
Returns: any
UITexture.setValue(texture: any): void
¶
Parameters:
texture
any
Returns: void
Calls:
canvas.getContext
context.clearRect
renderToCanvas
context.drawImage
Internal Comments:
// Seems like context can be null if the canvas is not visible
// Always clear the context before set new texture, because new texture may has transparency (x4)
Code
setValue( texture ) {
const canvas = this.dom.children[ 0 ];
const context = canvas.getContext( '2d' );
// Seems like context can be null if the canvas is not visible
if ( context ) {
// Always clear the context before set new texture, because new texture may has transparency
context.clearRect( 0, 0, canvas.width, canvas.height );
}
if ( texture !== null ) {
const image = texture.image;
if ( image !== undefined && image !== null && image.width > 0 ) {
canvas.title = texture.sourceFile;
const scale = canvas.width / image.width;
if ( texture.isDataTexture || texture.isCompressedTexture ) {
const canvas2 = renderToCanvas( texture );
context.drawImage( canvas2, 0, 0, image.width * scale, image.height * scale );
} else {
context.drawImage( image, 0, 0, image.width * scale, image.height * scale );
}
} else {
canvas.title = texture.sourceFile + ' (error)';
}
} else {
canvas.title = 'empty';
}
this.texture = texture;
}
UITexture.setColorSpace(colorSpace: any): this
¶
Parameters:
colorSpace
any
Returns: this
Calls:
this.getValue
Code
UITexture.onChange(callback: any): this
¶
Parameters:
callback
any
Returns: this
loadFile(file: any): Promise<void>
¶
Parameters:
file
any
Returns: Promise<void>
Calls:
file.name.split( '.' ).pop().toLowerCase
cache.has
cache.get
scope.setValue
scope.onChangeCallback
reader.addEventListener
complex_call_1721
loader.load
cache.set
reader.readAsDataURL
complex_call_2260
complex_call_2841
URL.createObjectURL
ktx2Loader.setTranscoderPath
editor.signals.rendererDetectKTX2Support.dispatch
ktx2Loader.load
ktx2Loader.dispose
reader.readAsArrayBuffer
complex_call_3737
exrLoader.load
file.type.match
document.createElement
image.addEventListener
form.reset
Internal Comments:
Code
async function loadFile( file ) {
const extension = file.name.split( '.' ).pop().toLowerCase();
const reader = new FileReader();
const hash = `${file.lastModified}_${file.size}_${file.name}`;
if ( cache.has( hash ) ) {
const texture = cache.get( hash );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} else if ( extension === 'hdr' || extension === 'pic' ) {
reader.addEventListener( 'load', async function ( event ) {
// assuming RGBE/Radiance HDR image format
const { RGBELoader } = await import( 'three/addons/loaders/RGBELoader.js' );
const loader = new RGBELoader();
loader.load( event.target.result, function ( hdrTexture ) {
hdrTexture.sourceFile = file.name;
cache.set( hash, hdrTexture );
scope.setValue( hdrTexture );
if ( scope.onChangeCallback ) scope.onChangeCallback( hdrTexture );
} );
} );
reader.readAsDataURL( file );
} else if ( extension === 'tga' ) {
reader.addEventListener( 'load', async function ( event ) {
const { TGALoader } = await import( 'three/addons/loaders/TGALoader.js' );
const loader = new TGALoader();
loader.load( event.target.result, function ( texture ) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.sourceFile = file.name;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} );
}, false );
reader.readAsDataURL( file );
} else if ( extension === 'ktx2' ) {
reader.addEventListener( 'load', async function ( event ) {
const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' );
const arrayBuffer = event.target.result;
const blobURL = URL.createObjectURL( new Blob( [ arrayBuffer ] ) );
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( '../../examples/jsm/libs/basis/' );
editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader );
ktx2Loader.load( blobURL, function ( texture ) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
ktx2Loader.dispose();
} );
} );
reader.readAsArrayBuffer( file );
} else if ( extension === 'exr' ) {
reader.addEventListener( 'load', async function ( event ) {
const { EXRLoader } = await import( 'three/addons/loaders/EXRLoader.js' );
const arrayBuffer = event.target.result;
const blobURL = URL.createObjectURL( new Blob( [ arrayBuffer ] ) );
const exrLoader = new EXRLoader();
exrLoader.load( blobURL, function ( texture ) {
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} );
} );
reader.readAsArrayBuffer( file );
} else if ( file.type.match( 'image.*' ) ) {
reader.addEventListener( 'load', function ( event ) {
const image = document.createElement( 'img' );
image.addEventListener( 'load', function () {
const texture = new THREE.Texture( this );
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
}, false );
image.src = event.target.result;
}, false );
reader.readAsDataURL( file );
}
form.reset();
}
UIOutliner.selectIndex(index: any): void
¶
Parameters:
index
any
Returns: void
Calls:
this.setValue
this.dom.dispatchEvent
Code
UIOutliner.setOptions(options: any): this
¶
Parameters:
options
any
Returns: this
Calls:
scope.dom.removeChild
scope.setValue
scope.dom.dispatchEvent
event.dataTransfer.setData
scene.getObjectById
moveObject
object.traverse
editor.execute
scope.dom.appendChild
scope.options.push
div.addEventListener
Internal Comments:
Code
setOptions( options ) {
const scope = this;
while ( scope.dom.children.length > 0 ) {
scope.dom.removeChild( scope.dom.firstChild );
}
function onClick() {
scope.setValue( this.value );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
// Drag
let currentDrag;
function onDrag() {
currentDrag = this;
}
function onDragStart( event ) {
event.dataTransfer.setData( 'text', 'foo' );
}
function onDragOver( event ) {
if ( this === currentDrag ) return;
const 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 || currentDrag === undefined ) return;
this.className = 'option';
const scene = scope.scene;
const object = scene.getObjectById( currentDrag.value );
const area = event.offsetY / this.clientHeight;
if ( area < 0.25 ) {
const nextObject = scene.getObjectById( this.value );
moveObject( object, nextObject.parent, nextObject );
} else if ( area > 0.75 ) {
let nextObject, parent;
if ( this.nextSibling !== null ) {
nextObject = scene.getObjectById( this.nextSibling.value );
parent = nextObject.parent;
} else {
// end of list (no next object)
nextObject = null;
parent = scene.getObjectById( this.value ).parent;
}
moveObject( object, parent, nextObject );
} else {
const parentObject = scene.getObjectById( this.value );
moveObject( object, parentObject );
}
}
function moveObject( object, newParent, nextObject ) {
if ( nextObject === null ) nextObject = undefined;
let newParentIsChild = false;
object.traverse( function ( child ) {
if ( child === newParent ) newParentIsChild = true;
} );
if ( newParentIsChild ) return;
const editor = scope.editor;
editor.execute( new MoveObjectCommand( editor, object, newParent, nextObject ) );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
//
scope.options = [];
for ( let i = 0; i < options.length; i ++ ) {
const div = options[ i ];
div.className = 'option';
scope.dom.appendChild( div );
scope.options.push( div );
div.addEventListener( 'click', onClick );
if ( div.draggable === true ) {
div.addEventListener( 'drag', onDrag );
div.addEventListener( 'dragstart', onDragStart ); // Firefox needs this
div.addEventListener( 'dragover', onDragOver );
div.addEventListener( 'dragleave', onDragLeave );
div.addEventListener( 'drop', onDrop );
}
}
return scope;
}
UIOutliner.getValue(): any
¶
Returns: any
UIOutliner.setValue(value: any): this
¶
Parameters:
value
any
Returns: this
Calls:
element.classList.add
element.classList.remove
Internal Comments:
Code
setValue( value ) {
for ( let i = 0; i < this.options.length; i ++ ) {
const element = this.options[ i ];
if ( element.value === value ) {
element.classList.add( 'active' );
// scroll into view
const y = element.offsetTop - this.dom.offsetTop;
const bottomY = y + element.offsetHeight;
const 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;
}
onClick(): void
¶
Returns: void
Calls:
scope.setValue
scope.dom.dispatchEvent
Code
onDrag(): void
¶
Returns: void
onDragStart(event: any): void
¶
Parameters:
event
any
Returns: void
Calls:
event.dataTransfer.setData
onDragOver(event: any): void
¶
Parameters:
event
any
Returns: void
Code
onDragLeave(): void
¶
Returns: void
onDrop(event: any): void
¶
Parameters:
event
any
Returns: void
Calls:
scene.getObjectById
moveObject
Internal Comments:
Code
function onDrop( event ) {
if ( this === currentDrag || currentDrag === undefined ) return;
this.className = 'option';
const scene = scope.scene;
const object = scene.getObjectById( currentDrag.value );
const area = event.offsetY / this.clientHeight;
if ( area < 0.25 ) {
const nextObject = scene.getObjectById( this.value );
moveObject( object, nextObject.parent, nextObject );
} else if ( area > 0.75 ) {
let nextObject, parent;
if ( this.nextSibling !== null ) {
nextObject = scene.getObjectById( this.nextSibling.value );
parent = nextObject.parent;
} else {
// end of list (no next object)
nextObject = null;
parent = scene.getObjectById( this.value ).parent;
}
moveObject( object, parent, nextObject );
} else {
const parentObject = scene.getObjectById( this.value );
moveObject( object, parentObject );
}
}
moveObject(object: any, newParent: any, nextObject: any): void
¶
Parameters:
object
any
newParent
any
nextObject
any
Returns: void
Calls:
object.traverse
editor.execute
scope.dom.dispatchEvent
Code
function moveObject( object, newParent, nextObject ) {
if ( nextObject === null ) nextObject = undefined;
let newParentIsChild = false;
object.traverse( function ( child ) {
if ( child === newParent ) newParentIsChild = true;
} );
if ( newParentIsChild ) return;
const editor = scope.editor;
editor.execute( new MoveObjectCommand( editor, object, newParent, nextObject ) );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
UIPoints.onChange(callback: any): this
¶
Parameters:
callback
any
Returns: this
UIPoints.clear(): void
¶
Returns: void
Calls:
this.deletePointRow
Code
UIPoints.deletePointRow(idx: any, needsUpdate: boolean): void
¶
Parameters:
idx
any
needsUpdate
boolean
Returns: void
Calls:
this.pointsList.remove
this.pointsUI.splice
this.update
Code
UIPoints2.getValue(): any[]
¶
Returns: any[]
Calls:
points.push
pointUI.x.getValue
pointUI.y.getValue
pointUI.lbl.setValue
Code
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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.setValue(points: any, needsUpdate: boolean): this
¶
Parameters:
points
any
needsUpdate
boolean
Returns: this
Calls:
this.clear
this.pointsList.add
this.createPointRow
this.update
Code
UIPoints2.createPointRow(x: any, y: any): UIDiv
¶
Parameters:
x
any
y
any
Returns: UIDiv
Calls:
new UIText( this.lastPointIdx + 1 ).setWidth
new UINumber( x ).setWidth( '30px' ).onChange
new UINumber( y ).setWidth( '30px' ).onChange
new UIButton( '-' ).onClick
scope.pointsList.getIndexOfChild
scope.deletePointRow
this.pointsUI.push
pointRow.add
Code
createPointRow( x, y ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
scope.deletePointRow( idx );
} );
this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
++ this.lastPointIdx;
pointRow.add( lbl, txtX, txtY, btn );
return pointRow;
}
UIPoints3.getValue(): any[]
¶
Returns: any[]
Calls:
points.push
pointUI.x.getValue
pointUI.y.getValue
pointUI.z.getValue
pointUI.lbl.setValue
Code
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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.setValue(points: any, needsUpdate: boolean): this
¶
Parameters:
points
any
needsUpdate
boolean
Returns: this
Calls:
this.clear
this.pointsList.add
this.createPointRow
this.update
Code
UIPoints3.createPointRow(x: any, y: any, z: any): UIDiv
¶
Parameters:
x
any
y
any
z
any
Returns: UIDiv
Calls:
new UIText( this.lastPointIdx + 1 ).setWidth
new UINumber( x ).setWidth( '30px' ).onChange
new UINumber( y ).setWidth( '30px' ).onChange
new UINumber( z ).setWidth( '30px' ).onChange
new UIButton( '-' ).onClick
scope.pointsList.getIndexOfChild
scope.deletePointRow
this.pointsUI.push
pointRow.add
Code
createPointRow( x, y, z ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const txtZ = new UINumber( z ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
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;
}
UIBoolean.getValue(): any
¶
Returns: any
Calls:
this.checkbox.getValue
UIBoolean.setValue(value: any): UICheckbox
¶
Parameters:
value
any
Returns: UICheckbox
Calls:
this.checkbox.setValue
renderToCanvas(texture: any): any
¶
Parameters:
texture
any
Returns: any
Calls:
renderer.setSize
fsQuad.render
Code
function renderToCanvas( texture ) {
if ( renderer === undefined ) {
renderer = new THREE.WebGLRenderer();
}
if ( fsQuad === undefined ) {
fsQuad = new FullScreenQuad( new THREE.MeshBasicMaterial() );
}
const image = texture.image;
renderer.setSize( image.width, image.height, false );
fsQuad.material.map = texture;
fsQuad.render( renderer );
return renderer.domElement;
}
Classes¶
UITexture
¶
Class Code
class UITexture extends UISpan {
constructor( editor ) {
super();
const scope = this;
const form = document.createElement( 'form' );
const input = document.createElement( 'input' );
input.type = 'file';
input.addEventListener( 'change', function ( event ) {
loadFile( event.target.files[ 0 ] );
} );
form.appendChild( input );
const 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();
} );
canvas.addEventListener( 'drop', function ( event ) {
event.preventDefault();
event.stopPropagation();
loadFile( event.dataTransfer.files[ 0 ] );
} );
this.dom.appendChild( canvas );
async function loadFile( file ) {
const extension = file.name.split( '.' ).pop().toLowerCase();
const reader = new FileReader();
const hash = `${file.lastModified}_${file.size}_${file.name}`;
if ( cache.has( hash ) ) {
const texture = cache.get( hash );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} else if ( extension === 'hdr' || extension === 'pic' ) {
reader.addEventListener( 'load', async function ( event ) {
// assuming RGBE/Radiance HDR image format
const { RGBELoader } = await import( 'three/addons/loaders/RGBELoader.js' );
const loader = new RGBELoader();
loader.load( event.target.result, function ( hdrTexture ) {
hdrTexture.sourceFile = file.name;
cache.set( hash, hdrTexture );
scope.setValue( hdrTexture );
if ( scope.onChangeCallback ) scope.onChangeCallback( hdrTexture );
} );
} );
reader.readAsDataURL( file );
} else if ( extension === 'tga' ) {
reader.addEventListener( 'load', async function ( event ) {
const { TGALoader } = await import( 'three/addons/loaders/TGALoader.js' );
const loader = new TGALoader();
loader.load( event.target.result, function ( texture ) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.sourceFile = file.name;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} );
}, false );
reader.readAsDataURL( file );
} else if ( extension === 'ktx2' ) {
reader.addEventListener( 'load', async function ( event ) {
const { KTX2Loader } = await import( 'three/addons/loaders/KTX2Loader.js' );
const arrayBuffer = event.target.result;
const blobURL = URL.createObjectURL( new Blob( [ arrayBuffer ] ) );
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath( '../../examples/jsm/libs/basis/' );
editor.signals.rendererDetectKTX2Support.dispatch( ktx2Loader );
ktx2Loader.load( blobURL, function ( texture ) {
texture.colorSpace = THREE.SRGBColorSpace;
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
ktx2Loader.dispose();
} );
} );
reader.readAsArrayBuffer( file );
} else if ( extension === 'exr' ) {
reader.addEventListener( 'load', async function ( event ) {
const { EXRLoader } = await import( 'three/addons/loaders/EXRLoader.js' );
const arrayBuffer = event.target.result;
const blobURL = URL.createObjectURL( new Blob( [ arrayBuffer ] ) );
const exrLoader = new EXRLoader();
exrLoader.load( blobURL, function ( texture ) {
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
} );
} );
reader.readAsArrayBuffer( file );
} else if ( file.type.match( 'image.*' ) ) {
reader.addEventListener( 'load', function ( event ) {
const image = document.createElement( 'img' );
image.addEventListener( 'load', function () {
const texture = new THREE.Texture( this );
texture.sourceFile = file.name;
texture.needsUpdate = true;
cache.set( hash, texture );
scope.setValue( texture );
if ( scope.onChangeCallback ) scope.onChangeCallback( texture );
}, false );
image.src = event.target.result;
}, false );
reader.readAsDataURL( file );
}
form.reset();
}
this.texture = null;
this.onChangeCallback = null;
}
getValue() {
return this.texture;
}
setValue( texture ) {
const canvas = this.dom.children[ 0 ];
const context = canvas.getContext( '2d' );
// Seems like context can be null if the canvas is not visible
if ( context ) {
// Always clear the context before set new texture, because new texture may has transparency
context.clearRect( 0, 0, canvas.width, canvas.height );
}
if ( texture !== null ) {
const image = texture.image;
if ( image !== undefined && image !== null && image.width > 0 ) {
canvas.title = texture.sourceFile;
const scale = canvas.width / image.width;
if ( texture.isDataTexture || texture.isCompressedTexture ) {
const canvas2 = renderToCanvas( texture );
context.drawImage( canvas2, 0, 0, image.width * scale, image.height * scale );
} else {
context.drawImage( image, 0, 0, image.width * scale, image.height * scale );
}
} else {
canvas.title = texture.sourceFile + ' (error)';
}
} else {
canvas.title = 'empty';
}
this.texture = texture;
}
setColorSpace( colorSpace ) {
const texture = this.getValue();
if ( texture !== null ) {
texture.colorSpace = colorSpace;
}
return this;
}
onChange( callback ) {
this.onChangeCallback = callback;
return this;
}
}
Methods¶
getValue(): any
¶
setValue(texture: any): void
¶
Code
setValue( texture ) {
const canvas = this.dom.children[ 0 ];
const context = canvas.getContext( '2d' );
// Seems like context can be null if the canvas is not visible
if ( context ) {
// Always clear the context before set new texture, because new texture may has transparency
context.clearRect( 0, 0, canvas.width, canvas.height );
}
if ( texture !== null ) {
const image = texture.image;
if ( image !== undefined && image !== null && image.width > 0 ) {
canvas.title = texture.sourceFile;
const scale = canvas.width / image.width;
if ( texture.isDataTexture || texture.isCompressedTexture ) {
const canvas2 = renderToCanvas( texture );
context.drawImage( canvas2, 0, 0, image.width * scale, image.height * scale );
} else {
context.drawImage( image, 0, 0, image.width * scale, image.height * scale );
}
} else {
canvas.title = texture.sourceFile + ' (error)';
}
} else {
canvas.title = 'empty';
}
this.texture = texture;
}
setColorSpace(colorSpace: any): this
¶
Code
onChange(callback: any): this
¶
UIOutliner
¶
Class Code
class UIOutliner extends UIDiv {
constructor( editor ) {
super();
this.dom.className = 'Outliner';
this.dom.tabIndex = 0; // keyup event is ignored without setting tabIndex
const scope = this;
// hack
this.scene = editor.scene;
// Prevent native scroll behavior
this.dom.addEventListener( 'keydown', function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
case 'ArrowDown':
event.preventDefault();
event.stopPropagation();
break;
}
} );
// Keybindings to support arrow navigation
this.dom.addEventListener( 'keyup', function ( event ) {
switch ( event.code ) {
case 'ArrowUp':
scope.selectIndex( scope.selectedIndex - 1 );
break;
case 'ArrowDown':
scope.selectIndex( scope.selectedIndex + 1 );
break;
}
} );
this.editor = editor;
this.options = [];
this.selectedIndex = - 1;
this.selectedValue = null;
}
selectIndex( index ) {
if ( index >= 0 && index < this.options.length ) {
this.setValue( this.options[ index ].value );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
this.dom.dispatchEvent( changeEvent );
}
}
setOptions( options ) {
const scope = this;
while ( scope.dom.children.length > 0 ) {
scope.dom.removeChild( scope.dom.firstChild );
}
function onClick() {
scope.setValue( this.value );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
// Drag
let currentDrag;
function onDrag() {
currentDrag = this;
}
function onDragStart( event ) {
event.dataTransfer.setData( 'text', 'foo' );
}
function onDragOver( event ) {
if ( this === currentDrag ) return;
const 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 || currentDrag === undefined ) return;
this.className = 'option';
const scene = scope.scene;
const object = scene.getObjectById( currentDrag.value );
const area = event.offsetY / this.clientHeight;
if ( area < 0.25 ) {
const nextObject = scene.getObjectById( this.value );
moveObject( object, nextObject.parent, nextObject );
} else if ( area > 0.75 ) {
let nextObject, parent;
if ( this.nextSibling !== null ) {
nextObject = scene.getObjectById( this.nextSibling.value );
parent = nextObject.parent;
} else {
// end of list (no next object)
nextObject = null;
parent = scene.getObjectById( this.value ).parent;
}
moveObject( object, parent, nextObject );
} else {
const parentObject = scene.getObjectById( this.value );
moveObject( object, parentObject );
}
}
function moveObject( object, newParent, nextObject ) {
if ( nextObject === null ) nextObject = undefined;
let newParentIsChild = false;
object.traverse( function ( child ) {
if ( child === newParent ) newParentIsChild = true;
} );
if ( newParentIsChild ) return;
const editor = scope.editor;
editor.execute( new MoveObjectCommand( editor, object, newParent, nextObject ) );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
//
scope.options = [];
for ( let i = 0; i < options.length; i ++ ) {
const div = options[ i ];
div.className = 'option';
scope.dom.appendChild( div );
scope.options.push( div );
div.addEventListener( 'click', onClick );
if ( div.draggable === true ) {
div.addEventListener( 'drag', onDrag );
div.addEventListener( 'dragstart', onDragStart ); // Firefox needs this
div.addEventListener( 'dragover', onDragOver );
div.addEventListener( 'dragleave', onDragLeave );
div.addEventListener( 'drop', onDrop );
}
}
return scope;
}
getValue() {
return this.selectedValue;
}
setValue( value ) {
for ( let i = 0; i < this.options.length; i ++ ) {
const element = this.options[ i ];
if ( element.value === value ) {
element.classList.add( 'active' );
// scroll into view
const y = element.offsetTop - this.dom.offsetTop;
const bottomY = y + element.offsetHeight;
const 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;
}
}
Methods¶
selectIndex(index: any): void
¶
Code
setOptions(options: any): this
¶
Code
setOptions( options ) {
const scope = this;
while ( scope.dom.children.length > 0 ) {
scope.dom.removeChild( scope.dom.firstChild );
}
function onClick() {
scope.setValue( this.value );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
// Drag
let currentDrag;
function onDrag() {
currentDrag = this;
}
function onDragStart( event ) {
event.dataTransfer.setData( 'text', 'foo' );
}
function onDragOver( event ) {
if ( this === currentDrag ) return;
const 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 || currentDrag === undefined ) return;
this.className = 'option';
const scene = scope.scene;
const object = scene.getObjectById( currentDrag.value );
const area = event.offsetY / this.clientHeight;
if ( area < 0.25 ) {
const nextObject = scene.getObjectById( this.value );
moveObject( object, nextObject.parent, nextObject );
} else if ( area > 0.75 ) {
let nextObject, parent;
if ( this.nextSibling !== null ) {
nextObject = scene.getObjectById( this.nextSibling.value );
parent = nextObject.parent;
} else {
// end of list (no next object)
nextObject = null;
parent = scene.getObjectById( this.value ).parent;
}
moveObject( object, parent, nextObject );
} else {
const parentObject = scene.getObjectById( this.value );
moveObject( object, parentObject );
}
}
function moveObject( object, newParent, nextObject ) {
if ( nextObject === null ) nextObject = undefined;
let newParentIsChild = false;
object.traverse( function ( child ) {
if ( child === newParent ) newParentIsChild = true;
} );
if ( newParentIsChild ) return;
const editor = scope.editor;
editor.execute( new MoveObjectCommand( editor, object, newParent, nextObject ) );
const changeEvent = new Event( 'change', { bubbles: true, cancelable: true } );
scope.dom.dispatchEvent( changeEvent );
}
//
scope.options = [];
for ( let i = 0; i < options.length; i ++ ) {
const div = options[ i ];
div.className = 'option';
scope.dom.appendChild( div );
scope.options.push( div );
div.addEventListener( 'click', onClick );
if ( div.draggable === true ) {
div.addEventListener( 'drag', onDrag );
div.addEventListener( 'dragstart', onDragStart ); // Firefox needs this
div.addEventListener( 'dragover', onDragOver );
div.addEventListener( 'dragleave', onDragLeave );
div.addEventListener( 'drop', onDrop );
}
}
return scope;
}
getValue(): any
¶
setValue(value: any): this
¶
Code
setValue( value ) {
for ( let i = 0; i < this.options.length; i ++ ) {
const element = this.options[ i ];
if ( element.value === value ) {
element.classList.add( 'active' );
// scroll into view
const y = element.offsetTop - this.dom.offsetTop;
const bottomY = y + element.offsetHeight;
const 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;
}
UIPoints
¶
Class Code
class UIPoints extends UISpan {
constructor() {
super();
this.dom.style.display = 'inline-block';
this.pointsList = new UIDiv();
this.add( this.pointsList );
this.pointsUI = [];
this.lastPointIdx = 0;
this.onChangeCallback = null;
this.update = () => { // bind lexical this
if ( this.onChangeCallback !== null ) {
this.onChangeCallback();
}
};
}
onChange( callback ) {
this.onChangeCallback = callback;
return this;
}
clear() {
for ( let i = this.pointsUI.length - 1; i >= 0; -- i ) {
this.deletePointRow( i, false );
}
this.lastPointIdx = 0;
}
deletePointRow( idx, needsUpdate = true ) {
if ( ! this.pointsUI[ idx ] ) return;
this.pointsList.remove( this.pointsUI[ idx ].row );
this.pointsUI.splice( idx, 1 );
if ( needsUpdate === true ) {
this.update();
}
this.lastPointIdx --;
}
}
Methods¶
onChange(callback: any): this
¶
clear(): void
¶
Code
deletePointRow(idx: any, needsUpdate: boolean): void
¶
Code
UIPoints2
¶
Class Code
class UIPoints2 extends UIPoints {
constructor() {
super();
const row = new UIRow();
this.add( row );
const addPointButton = new UIButton( '+' );
addPointButton.onClick( () => {
if ( this.pointsUI.length === 0 ) {
this.pointsList.add( this.createPointRow( 0, 0 ) );
} else {
const point = this.pointsUI[ this.pointsUI.length - 1 ];
this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue() ) );
}
this.update();
} );
row.add( addPointButton );
}
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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;
}
setValue( points, needsUpdate = true ) {
this.clear();
for ( let i = 0; i < points.length; i ++ ) {
const point = points[ i ];
this.pointsList.add( this.createPointRow( point.x, point.y ) );
}
if ( needsUpdate === true ) this.update();
return this;
}
createPointRow( x, y ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
scope.deletePointRow( idx );
} );
this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
++ this.lastPointIdx;
pointRow.add( lbl, txtX, txtY, btn );
return pointRow;
}
}
Methods¶
getValue(): any[]
¶
Code
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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;
}
setValue(points: any, needsUpdate: boolean): this
¶
Code
createPointRow(x: any, y: any): UIDiv
¶
Code
createPointRow( x, y ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
scope.deletePointRow( idx );
} );
this.pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
++ this.lastPointIdx;
pointRow.add( lbl, txtX, txtY, btn );
return pointRow;
}
UIPoints3
¶
Class Code
class UIPoints3 extends UIPoints {
constructor() {
super();
const row = new UIRow();
this.add( row );
const addPointButton = new UIButton( '+' );
addPointButton.onClick( () => {
if ( this.pointsUI.length === 0 ) {
this.pointsList.add( this.createPointRow( 0, 0, 0 ) );
} else {
const point = this.pointsUI[ this.pointsUI.length - 1 ];
this.pointsList.add( this.createPointRow( point.x.getValue(), point.y.getValue(), point.z.getValue() ) );
}
this.update();
} );
row.add( addPointButton );
}
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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;
}
setValue( points, needsUpdate = true ) {
this.clear();
for ( let i = 0; i < points.length; i ++ ) {
const point = points[ i ];
this.pointsList.add( this.createPointRow( point.x, point.y, point.z ) );
}
if ( needsUpdate === true ) this.update();
return this;
}
createPointRow( x, y, z ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const txtZ = new UINumber( z ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
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;
}
}
Methods¶
getValue(): any[]
¶
Code
getValue() {
const points = [];
let count = 0;
for ( let i = 0; i < this.pointsUI.length; i ++ ) {
const 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;
}
setValue(points: any, needsUpdate: boolean): this
¶
Code
createPointRow(x: any, y: any, z: any): UIDiv
¶
Code
createPointRow( x, y, z ) {
const pointRow = new UIDiv();
const lbl = new UIText( this.lastPointIdx + 1 ).setWidth( '20px' );
const txtX = new UINumber( x ).setWidth( '30px' ).onChange( this.update );
const txtY = new UINumber( y ).setWidth( '30px' ).onChange( this.update );
const txtZ = new UINumber( z ).setWidth( '30px' ).onChange( this.update );
const scope = this;
const btn = new UIButton( '-' ).onClick( function () {
if ( scope.isEditing ) return;
const idx = scope.pointsList.getIndexOfChild( pointRow );
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;
}
UIBoolean
¶
Class Code
class UIBoolean extends UISpan {
constructor( boolean, text ) {
super();
this.setMarginRight( '4px' );
this.checkbox = new UICheckbox( boolean );
this.text = new UIText( text ).setMarginLeft( '3px' );
this.add( this.checkbox );
this.add( this.text );
}
getValue() {
return this.checkbox.getValue();
}
setValue( value ) {
return this.checkbox.setValue( value );
}
}