UNPKG

three.fbo-helper

Version:

FrameBuffer Object inspector for three.js

475 lines (365 loc) 12 kB
;(function() { "use strict"; var root = this var has_require = typeof require !== 'undefined' var THREE = (root && root.THREE) || has_require && require('three') if( !THREE ) throw new Error( 'FBOHelper requires three.js' ) "use strict"; var layerCSS = ` #fboh-fbos-list{ all: unset; position: fixed; left: 0; top: 0; z-index: 1000000; width: 150px; } #fboh-fbos-list, #fboh-fbos-list *, #fboh-hotspot, #fboh-label, #fboh-info{ box-sizing: border-box; font-family: 'Roboto Mono', 'courier new', courier, monospace; font-size: 11px; line-height: 1.4em; } #fboh-fbos-list li{ cursor: pointer; color: white; width: 100%; padding: 4px 0; border-top: 1px solid #888; border-bottom: 1px solid black; background-color: #444; text-align: center; text-shadow: 0 -1px black; } #fboh-fbos-list li:hover{ background-color: rgba( 158, 253, 56, .5 ); } #fboh-fbos-list li.active{ background-color: rgba( 158, 253, 56, .5 ); color: white; text-shadow: 0 1px black; } #fboh-hotspot{ position: absolute; left: 0; top: 0; background-color: rgba( 158, 253, 56,.5); pointer-events: none; } #fboh-label{ position: absolute; left: 0; bottom: 0; transform-origin: bottom left; pointer-events: none; } #fboh-info{ display: none; position: absolute; left: 160px; top: 10px; pointer-events: none; } .fboh-card{ display: block; white-space: nowrap; color: black; padding: 10px; background-color: white; border: 1px solid black; } `; let formats = {} formats[ THREE.AlphaFormat ] = 'THREE.AlphaFormat'; formats[ THREE.RGBFormat ] = 'THREE.RGBFormat'; formats[ THREE.RGBAFormat ] = 'THREE.RGBAFormat'; formats[ THREE.LuminanceFormat ] = 'THREE.LuminanceFormat'; formats[ THREE.LuminanceAlphaFormat ] = 'THREE.LuminanceAlphaFormat'; //formats[ THREE.RGBEFormat ] = 'THREE.RGBEFormat'; let types = {} types[ THREE.UnsignedByteType ] = 'THREE.UnsignedByteType'; types[ THREE.ByteType ] = 'THREE.ByteType'; types[ THREE.ShortType ] = 'THREE.ShortType'; types[ THREE.UnsignedShortType ] = 'THREE.UnsignedShortType'; types[ THREE.IntType ] = 'THREE.IntType'; types[ THREE.UnsignedIntType ] = 'THREE.UnsignedIntType'; types[ THREE.FloatType ] = 'THREE.FloatType'; types[ THREE.HalfFloatType ] = 'THREE.HalfFloatType'; types[ THREE.UnsignedShort4444Type ] = 'THREE.UnsignedShort4444Type'; types[ THREE.UnsignedShort5551Type ] = 'THREE.UnsignedShort5551Type'; types[ THREE.UnsignedShort565Type ] = 'THREE.UnsignedShort565Type'; class FBOHelper { constructor( renderer ) { this.renderer = renderer; this.autoUpdate = false; this.fbos = [] this.list = document.createElement( 'ul' ); this.list.setAttribute( 'id', 'fboh-fbos-list' ); document.body.appendChild( this.list ); this.scene = new THREE.Scene(); this.camera = new THREE.OrthographicCamera( -1, 1, 1, -1, .000001, 1000 ); this.raycaster = new THREE.Raycaster(); this.mouse = new THREE.Vector2(); this.grid = document.createElement( 'div' ); this.grid.setAttribute( 'style', 'position: fixed; left: 50%; top: 50%; border: 1px solid #000000; transform: translate3d(-50%, -50%, 0 ); box-shadow: 0 0 50px black; display: none' ); this.grid.setAttribute( 'id', 'bfoh-grid' ); document.body.appendChild( this.grid ); this.hotspot = document.createElement( 'div' ); this.hotspot.setAttribute( 'id', 'fboh-hotspot' ); this.grid.appendChild( this.hotspot ); this.label = document.createElement( 'div' ); this.label.setAttribute( 'id', 'fboh-label' ); this.label.className = 'fboh-card'; this.hotspot.appendChild( this.label ); this.info = document.createElement( 'div' ); this.info.setAttribute( 'id', 'fboh-info' ); this.info.className = 'fboh-card'; document.body.appendChild( this.info ); this.currentObj = null; this.currentU = 0; this.currentV = 0; this.fboMap = new Map(); this.offsetX = 0; this.offsetY = 0; this.grid.appendChild( this.hotspot ); const head = window.document.head || window.document.getElementsByTagName('head')[0]; const style = window.document.createElement('style'); style.type = 'text/css'; if (style.styleSheet){ style.styleSheet.cssText = layerCSS; } else { style.appendChild(document.createTextNode(layerCSS)); } head.appendChild(style); const ss = document.createElement( 'link' ); ss.type = 'text/css'; ss.rel = 'stylesheet'; ss.href = 'https://fonts.googleapis.com/css?family=Roboto+Mono'; head.appendChild( ss ); this.grid.addEventListener( 'wheel', e => { var direction = ( e.deltaY < 0 ) ? 1 : -1; this.camera.zoom += direction / 50; this.camera.updateProjectionMatrix(); this.grid.style.transform = `translate3d(-50%, -50%, 0 ) scale(${this.camera.zoom},${this.camera.zoom}) translate3d(${this.offsetX}px,${this.offsetY}px,0) `; this.label.style.transform = `scale(${1/this.camera.zoom},${1/this.camera.zoom})`; this.hotspot.style.transform = `scale(${1/this.camera.zoom},${1/this.camera.zoom})`; this.hotspot.style.borderWidth = `${1/this.camera.zoom}px`; this.readPixel( this.currentObj, this.currentU, this.currentV ); } ); let dragging = false; let mouseStart = { x: 0, y: 0 }; let offsetStart = { x: 0, y: 0 }; this.grid.addEventListener( 'mousedown', e => { dragging = true; mouseStart.x = e.clientX; mouseStart.y = e.clientY; offsetStart.x = this.offsetX; offsetStart.y = this.offsetY; } ); this.grid.addEventListener( 'mouseup', e => { dragging = false; } ); this.grid.addEventListener( 'mouseout', e => { this.label.style.display = 'none'; dragging = false; } ); this.grid.addEventListener( 'mouseover', e => { this.label.style.display = 'block'; } ); this.grid.addEventListener( 'mousemove', e => { if( dragging ) { this.offsetX = offsetStart.x + ( e.clientX - mouseStart.x ) / this.camera.zoom; this.offsetY = offsetStart.y + ( e.clientY - mouseStart.y ) / this.camera.zoom; this.camera.position.x = -this.offsetX; this.camera.position.y = this.offsetY; this.grid.style.transform = `translate3d(-50%, -50%, 0 ) scale(${this.camera.zoom},${this.camera.zoom}) translate3d(${this.offsetX}px,${this.offsetY}px,0)`; } else { this.mouse.x = ( e.clientX / renderer.domElement.clientWidth ) * 2 - 1; this.mouse.y = - ( e.clientY / renderer.domElement.clientHeight ) * 2 + 1; this.raycaster.setFromCamera( this.mouse, this.camera ); const intersects = this.raycaster.intersectObject( this.currentObj.quad, true ); if ( intersects.length > 0 ) { this.readPixel( this.fboMap.get( intersects[ 0 ].object ), intersects[ 0 ].uv.x, intersects[ 0 ].uv.y ); this.label.style.display = 'block'; } else { this.label.style.display = 'none'; } } } ); window.addEventListener( 'keydown', e => { if( e.keyCode === 27 ) { this.hide(); } } ); this.grid.addEventListener( 'keydown', e => { if( e.keyCode === 27 ) { this.hide(); } } ); } hide() { this.hideAll(); this.info.style.display = 'none'; this.grid.style.display = 'none'; this.currentObj = null; } attach( fbo, name, formatter ) { var li = document.createElement( 'li' ); li.textContent = name; if( fbo.image ) { fbo.width = fbo.image.width; fbo.height = fbo.image.height; } const width = 600; const height = fbo.height * width / fbo.width; const material = new THREE.MeshBasicMaterial( { map: fbo, side: THREE.DoubleSide } ); const quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 1, 1 ), material ); if( !fbo.flipY ) quad.rotation.x = Math.PI; quad.visible = false; quad.width = width; quad.height = height; quad.scale.set( width, height, 1. ); this.scene.add( quad ); var fboData = { width: width, height: height, name: name, fbo: fbo, flipY: fbo.flipY, li: li, visible: false, quad: quad, material: material, formatter: formatter }; this.fbos.push( fboData ); this.fboMap.set( quad, fboData ); li.addEventListener( 'click', e => { quad.visible = !quad.visible; if( quad.visible ) { this.hideAll(); quad.visible = true; li.classList.add( 'active' ); this.info.style.display = 'block'; this.grid.style.display = 'block'; this.grid.style.width = ( fboData.width + 2 ) + 'px'; this.grid.style.height = ( fboData.height + 2 ) + 'px'; this.currentObj = fboData; this.info.innerHTML = `Width: ${fbo.width} Height: ${fbo.height}<br/>Format: ${formats[fbo.texture?fbo.texture.format:fbo.format]} Type: ${types[fbo.texture?fbo.texture.type:fbo.type]}`; } else { this.info.style.display = 'none'; li.classList.remove( 'active' ); this.grid.style.display = 'none'; this.currentObj = null; } } ); this.buildList(); } detach( f ) { var p = 0; for( var fbo of this.fbos ) { if( fbo.fbo === f ) { this.fbos.splice( p, 1 ) } p++; } this.buildList(); } refreshFBO( f ) { for( var fbo of this.fbos ) { if( fbo.fbo === f ) { const width = 600; const height = f.height * width / f.width; fbo.width = width; fbo.height = height; fbo.quad.width = width; fbo.quad.height = height; fbo.quad.scale.set( width, height, 1. ); } } } hideAll() { this.fbos.forEach( fbo => { fbo.quad.visible = false; fbo.li.classList.remove( 'active' ); } ); } buildList() { while( this.list.firstChild ) this.list.removeChild( this.list.firstChild ); for( var fbo of this.fbos ) { this.list.appendChild( fbo.li ); } } setSize( w, h ) { this.camera.left = w / - 2; this.camera.right = w / 2; this.camera.top = h / 2; this.camera.bottom = h / - 2; this.camera.updateProjectionMatrix(); } readPixel( obj, u, v ) { this.currentU = u; this.currentV = v; if( this.currentObj === null ) return; const fbo = obj.fbo; const x = ~~( fbo.width * u ); const y = ~~( fbo.height * v ); let types = {} types[ THREE.UnsignedByteType ] = Uint8Array; types[ THREE.ByteType ] = Int8Array; types[ THREE.ShortType ] = Int16Array; types[ THREE.UnsignedShortType ] = Uint16Array; types[ THREE.IntType ] = Int32Array; types[ THREE.UnsignedIntType ] = Uint32Array; types[ THREE.FloatType ] = Float32Array; types[ THREE.HalfFloatType ] = null; types[ THREE.UnsignedShort4444Type ] = Uint16Array; types[ THREE.UnsignedShort5551Type ] = Uint16Array; types[ THREE.UnsignedShort565Type ] = Uint16Array; var type = types[ fbo.texture ? fbo.texture.type : fbo.type ]; if( type === null ) { console.warning( fbo.texture ? fbo.texture.type : fbo.type + ' not supported' ); return; } const pixelBuffer = new ( type )( 4 ); this.renderer.readRenderTargetPixels( fbo, x, y, 1, 1, pixelBuffer ); const posTxt = `X : ${x} Y: ${y} u: ${u} v: ${v}`; const dataTxt = obj.formatter ? obj.formatter( { x: x, y: y, u: u, v: v, r: pixelBuffer[ 0 ], g: pixelBuffer[ 1 ], b: pixelBuffer[ 2 ], a: pixelBuffer[ 3 ] } ) : `R: ${pixelBuffer[ 0 ]} G: ${pixelBuffer[ 1 ]} B: ${pixelBuffer[ 2 ]} A: ${pixelBuffer[ 3 ]}`; this.label.innerHTML = `${posTxt}<br/>${dataTxt}`; const ox = ~~( u * fbo.width ) * obj.quad.width / fbo.width; const oy = ~~( obj.flipY ? ( 1 - v ) * fbo.height : v * fbo.height ) * obj.quad.height / fbo.height; this.hotspot.style.width = `${obj.quad.width / fbo.width}px`; this.hotspot.style.height = `${obj.quad.height / fbo.height}px`; this.hotspot.style.transform = `translate3d(${ox}px,${oy}px,0)`; this.label.style.bottom = ( obj.quad.height / fbo.height ) + 'px'; } update() { this.renderer.autoClear = false; this.renderer.render( this.scene, this.camera ); this.renderer.autoClear = true; if( this.autoUpdate ) this.readPixel( this.currentObj, this.currentU, this.currentV ); } } if( typeof exports !== 'undefined' ) { if( typeof module !== 'undefined' && module.exports ) { exports = module.exports = FBOHelper } exports.FBOHelper = FBOHelper } else { root.FBOHelper = FBOHelper } }).call(this);