commit 713d8be98c3edee2f6ca040d0a3fadd207a9d716 Author: ink-soul Date: Fri Jun 16 09:32:15 2023 +0800 init diff --git a/hw0/assets/mary/MC003_Kozakura_Mari.png b/hw0/assets/mary/MC003_Kozakura_Mari.png new file mode 100644 index 0000000..b4d4f62 Binary files /dev/null and b/hw0/assets/mary/MC003_Kozakura_Mari.png differ diff --git a/hw0/assets/mary/Marry.mtl b/hw0/assets/mary/Marry.mtl new file mode 100644 index 0000000..7d5a5f4 --- /dev/null +++ b/hw0/assets/mary/Marry.mtl @@ -0,0 +1,23 @@ +# Blender MTL File: 'Marry.blend' +# Material Count: 2 + +newmtl MC003_Kozakura_Mari +Ns 900.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 1 +map_Kd MC003_Kozakura_Mari.png + +newmtl 材质 +Ns 900.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.270588 0.552941 0.874510 +Ks 0.000000 0.000000 0.000000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 1 diff --git a/hw0/assets/mary/Marry.obj b/hw0/assets/mary/Marry.obj new file mode 100644 index 0000000..6bb2fee Binary files /dev/null and b/hw0/assets/mary/Marry.obj differ diff --git a/hw0/assignment.pdf b/hw0/assignment.pdf new file mode 100644 index 0000000..9be202e Binary files /dev/null and b/hw0/assignment.pdf differ diff --git a/hw0/index.html b/hw0/index.html new file mode 100644 index 0000000..759473c --- /dev/null +++ b/hw0/index.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw0/lib/MTLLoader.js b/hw0/lib/MTLLoader.js new file mode 100644 index 0000000..ed5a835 --- /dev/null +++ b/hw0/lib/MTLLoader.js @@ -0,0 +1,549 @@ +/** + * Loads a Wavefront .mtl file specifying materials + */ + + THREE.MTLLoader = function ( manager ) { + + THREE.Loader.call( this, manager ); + +}; + +THREE.MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { + + constructor: THREE.MTLLoader, + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var path = ( this.path === '' ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; + + var loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + }, + + setMaterialOptions: function ( value ) { + + this.materialOptions = value; + return this; + + }, + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {THREE.MTLLoader.MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse: function ( text, path ) { + + var lines = text.split( '\n' ); + var info = {}; + var delimiter_pattern = /\s+/; + var materialsInfo = {}; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + var pos = line.indexOf( ' ' ); + + var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { + + var ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +} ); + +/** + * Create a new THREE.MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) { + + this.baseUrl = baseUrl || ''; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; + this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; + +}; + +THREE.MTLLoader.MaterialCreator.prototype = { + + constructor: THREE.MTLLoader.MaterialCreator, + + crossOrigin: 'anonymous', + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setManager: function ( value ) { + + this.manager = value; + + }, + + setMaterials: function ( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + }, + + convert: function ( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + var converted = {}; + + for ( var mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + var mat = materialsInfo[ mn ]; + + var covmat = {}; + + converted[ mn ] = covmat; + + for ( var prop in mat ) { + + var save = true; + var value = mat[ prop ]; + var lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + }, + + preload: function () { + + for ( var mn in this.materialsInfo ) { + + this.create( mn ); + + } + + }, + + getIndex: function ( materialName ) { + + return this.nameLookup[ materialName ]; + + }, + + getAsArray: function () { + + var index = 0; + + for ( var mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + }, + + create: function ( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + }, + + createMaterial_: function ( materialName ) { + + // Create material + + var scope = this; + var mat = this.materialsInfo[ materialName ]; + var params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + var texParams = scope.getTextureParams( value, params ); + var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + params[ mapType ] = map; + + } + + for ( var prop in mat ) { + + var value = mat[ prop ]; + var n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new THREE.Color().fromArray( value ); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new THREE.Color().fromArray( value ); + + break; + + case 'ke': + + // Emissive using RGB values + params.emissive = new THREE.Color().fromArray( value ); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( 'map', value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( 'specularMap', value ); + + break; + + case 'map_ke': + + // Emissive map + + setMapForType( 'emissiveMap', value ); + + break; + + case 'norm': + + setMapForType( 'normalMap', value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( 'bumpMap', value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( 'alphaMap', value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + }, + + getTextureParams: function ( value, matParams ) { + + var texParams = { + + scale: new THREE.Vector2( 1, 1 ), + offset: new THREE.Vector2( 0, 0 ) + + }; + + var items = value.split( /\s+/ ); + var pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + }, + + loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + + var texture; + var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; + var loader = manager.getHandler( url ); + + if ( loader === null ) { + + loader = new THREE.TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +}; \ No newline at end of file diff --git a/hw0/lib/OBJLoader.js b/hw0/lib/OBJLoader.js new file mode 100644 index 0000000..72fd0c9 --- /dev/null +++ b/hw0/lib/OBJLoader.js @@ -0,0 +1,900 @@ +THREE.OBJLoader = ( function () { + + // o object_name | g group_name + var object_pattern = /^[og]\s*(.+)?/; + // mtllib file_reference + var material_library_pattern = /^mtllib /; + // usemtl material_name + var material_use_pattern = /^usemtl /; + // usemap map_name + var map_use_pattern = /^usemap /; + + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); + + var ab = new THREE.Vector3(); + var cb = new THREE.Vector3(); + + function ParserState() { + + var state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materials: {}, + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + var previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + var material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + var cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + var lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + var declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + var src = this.normals; + var dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addFaceNormal: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.normals; + + vA.fromArray( src, a ); + vB.fromArray( src, b ); + vC.fromArray( src, c ); + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + dst.push( cb.x, cb.y, cb.z ); + dst.push( cb.x, cb.y, cb.z ); + dst.push( cb.x, cb.y, cb.z ); + + }, + + addColor: function ( a, b, c ) { + + var src = this.colors; + var dst = this.object.geometry.colors; + + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addDefaultUV: function () { + + var dst = this.object.geometry.uvs; + + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); + + }, + + addUVLine: function ( a ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + var vLen = this.vertices.length; + + var ia = this.parseVertexIndex( a, vLen ); + var ib = this.parseVertexIndex( b, vLen ); + var ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); + + // normals + + if ( na !== undefined && na !== '' ) { + + var nLen = this.normals.length; + + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } else { + + this.addFaceNormal( ia, ib, ic ); + + } + + // uvs + + if ( ua !== undefined && ua !== '' ) { + + var uvLen = this.uvs.length; + + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + + this.addUV( ia, ib, ic ); + + this.object.geometry.hasUVIndices = true; + + } else { + + // add placeholder values (for inconsistent face definitions) + + this.addDefaultUV(); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + var vLen = this.vertices.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + var index = this.parseVertexIndex( vertices[ vi ], vLen ); + + this.addVertexPoint( index ); + this.addColor( index ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + var vLen = this.vertices.length; + var uvLen = this.uvs.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + + } + + // + + function OBJLoader( manager ) { + + THREE.Loader.call( this, manager ); + + this.materials = null; + + } + + OBJLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { + + constructor: OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + }, + + setMaterials: function ( materials ) { + + this.materials = materials; + + return this; + + }, + + parse: function ( text ) { + + var state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + var lines = text.split( '\n' ); + var line = '', lineFirstChar = ''; + var lineLength = 0; + var result = []; + + // Faster to just trim left side of the line. Use if available. + var trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( var i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + var data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length >= 7 ) { + + state.colors.push( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + + ); + + } else { + + // if no colors are defined, add placeholders so color and vertex indices match + + state.colors.push( undefined, undefined, undefined ); + + } + + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + var lineData = line.substr( 1 ).trim(); + var vertexData = lineData.split( /\s+/ ); + var faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + + var vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + var vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + var v1 = faceVertices[ 0 ]; + + for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + var v2 = faceVertices[ j ]; + var v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + var lineParts = line.substring( 1 ).trim().split( ' ' ); + var lineVertices = [], lineUVs = []; + + if ( line.indexOf( '/' ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + + var parts = lineParts[ li ].split( '/' ); + + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + + } + + } + + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + var lineData = line.substr( 1 ).trim(); + var pointData = lineData.split( ' ' ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // var name = result[ 0 ].substr( 1 ).trim(); + var name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + + state.startObject( name ); + + } else if ( material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( map_use_pattern.test( line ) ) { + + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * or + * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + var value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + + var material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + var container = new THREE.Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + var hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + + if ( hasPrimitives === true ) { + + for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + + var object = state.objects[ i ]; + var geometry = object.geometry; + var materials = object.materials; + var isLine = ( geometry.type === 'Line' ); + var isPoints = ( geometry.type === 'Points' ); + var hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.hasUVIndices === true ) { + + buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + var createdMaterials = []; + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + var materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + var material = state.materials[ materialHash ]; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + + var materialLine = new THREE.LineBasicMaterial(); + THREE.Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { + + var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); + THREE.Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; + + } + + } + + if ( material === undefined ) { + + if ( isLine ) { + + material = new THREE.LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new THREE.MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + + state.materials[ materialHash ] = material; + + } + + createdMaterials.push( material ); + + } + + // Create mesh + + var mesh; + + if ( createdMaterials.length > 1 ) { + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + } else { + + // if there is only the default parser state object with no geometry data, interpret data as point cloud + + if ( state.vertices.length > 0 ) { + + var material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( state.vertices, 3 ) ); + + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; + + } + + var points = new THREE.Points( buffergeometry, material ); + container.add( points ); + + } + + } + + return container; + + } + + } ); + + return OBJLoader; + +} )(); \ No newline at end of file diff --git a/hw0/lib/OrbitControls.js b/hw0/lib/OrbitControls.js new file mode 100644 index 0000000..963b13d --- /dev/null +++ b/hw0/lib/OrbitControls.js @@ -0,0 +1,1215 @@ +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +THREE.OrbitControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // so camera.up is the orbit axis + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().invert(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + var twoPI = 2 * Math.PI; + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + var min = scope.minAzimuthAngle; + var max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove ); + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseDown( event ); + break; + + // TODO touch + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseMove( event ); + break; + + // TODO touch + + } + + } + + function onPointerUp( event ) { + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseUp( event ); + break; + + // TODO touch + + } + + } + + function onMouseDown( event ) { + + // Prevent the browser from scrolling. + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case THREE.MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case THREE.MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case THREE.MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case THREE.TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case THREE.TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case THREE.TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case THREE.TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'wheel', onMouseWheel ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart ); + scope.domElement.addEventListener( 'touchend', onTouchEnd ); + scope.domElement.addEventListener( 'touchmove', onTouchMove ); + + // force an update at start + + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +THREE.MapControls = function ( object, domElement ) { + + THREE.OrbitControls.call( this, object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = THREE.MOUSE.PAN; + this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; + + this.touches.ONE = THREE.TOUCH.PAN; + this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; + +}; + +THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.MapControls.prototype.constructor = THREE.MapControls; diff --git a/hw0/lib/dat.gui.js b/hw0/lib/dat.gui.js new file mode 100644 index 0000000..dec7478 --- /dev/null +++ b/hw0/lib/dat.gui.js @@ -0,0 +1,2538 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.dat = {}))); +}(this, (function (exports) { 'use strict'; + +function ___$insertStyle(css) { + if (!css) { + return; + } + if (typeof window === 'undefined') { + return; + } + + var style = document.createElement('style'); + + style.setAttribute('type', 'text/css'); + style.innerHTML = css; + document.head.appendChild(style); + + return css; +} + +function colorToString (color, forceCSSHex) { + var colorFormat = color.__state.conversionName.toString(); + var r = Math.round(color.r); + var g = Math.round(color.g); + var b = Math.round(color.b); + var a = color.a; + var h = Math.round(color.h); + var s = color.s.toFixed(1); + var v = color.v.toFixed(1); + if (forceCSSHex || colorFormat === 'THREE_CHAR_HEX' || colorFormat === 'SIX_CHAR_HEX') { + var str = color.hex.toString(16); + while (str.length < 6) { + str = '0' + str; + } + return '#' + str; + } else if (colorFormat === 'CSS_RGB') { + return 'rgb(' + r + ',' + g + ',' + b + ')'; + } else if (colorFormat === 'CSS_RGBA') { + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; + } else if (colorFormat === 'HEX') { + return '0x' + color.hex.toString(16); + } else if (colorFormat === 'RGB_ARRAY') { + return '[' + r + ',' + g + ',' + b + ']'; + } else if (colorFormat === 'RGBA_ARRAY') { + return '[' + r + ',' + g + ',' + b + ',' + a + ']'; + } else if (colorFormat === 'RGB_OBJ') { + return '{r:' + r + ',g:' + g + ',b:' + b + '}'; + } else if (colorFormat === 'RGBA_OBJ') { + return '{r:' + r + ',g:' + g + ',b:' + b + ',a:' + a + '}'; + } else if (colorFormat === 'HSV_OBJ') { + return '{h:' + h + ',s:' + s + ',v:' + v + '}'; + } else if (colorFormat === 'HSVA_OBJ') { + return '{h:' + h + ',s:' + s + ',v:' + v + ',a:' + a + '}'; + } + return 'unknown format'; +} + +var ARR_EACH = Array.prototype.forEach; +var ARR_SLICE = Array.prototype.slice; +var Common = { + BREAK: {}, + extend: function extend(target) { + this.each(ARR_SLICE.call(arguments, 1), function (obj) { + var keys = this.isObject(obj) ? Object.keys(obj) : []; + keys.forEach(function (key) { + if (!this.isUndefined(obj[key])) { + target[key] = obj[key]; + } + }.bind(this)); + }, this); + return target; + }, + defaults: function defaults(target) { + this.each(ARR_SLICE.call(arguments, 1), function (obj) { + var keys = this.isObject(obj) ? Object.keys(obj) : []; + keys.forEach(function (key) { + if (this.isUndefined(target[key])) { + target[key] = obj[key]; + } + }.bind(this)); + }, this); + return target; + }, + compose: function compose() { + var toCall = ARR_SLICE.call(arguments); + return function () { + var args = ARR_SLICE.call(arguments); + for (var i = toCall.length - 1; i >= 0; i--) { + args = [toCall[i].apply(this, args)]; + } + return args[0]; + }; + }, + each: function each(obj, itr, scope) { + if (!obj) { + return; + } + if (ARR_EACH && obj.forEach && obj.forEach === ARR_EACH) { + obj.forEach(itr, scope); + } else if (obj.length === obj.length + 0) { + var key = void 0; + var l = void 0; + for (key = 0, l = obj.length; key < l; key++) { + if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) { + return; + } + } + } else { + for (var _key in obj) { + if (itr.call(scope, obj[_key], _key) === this.BREAK) { + return; + } + } + } + }, + defer: function defer(fnc) { + setTimeout(fnc, 0); + }, + debounce: function debounce(func, threshold, callImmediately) { + var timeout = void 0; + return function () { + var obj = this; + var args = arguments; + function delayed() { + timeout = null; + if (!callImmediately) func.apply(obj, args); + } + var callNow = callImmediately || !timeout; + clearTimeout(timeout); + timeout = setTimeout(delayed, threshold); + if (callNow) { + func.apply(obj, args); + } + }; + }, + toArray: function toArray(obj) { + if (obj.toArray) return obj.toArray(); + return ARR_SLICE.call(obj); + }, + isUndefined: function isUndefined(obj) { + return obj === undefined; + }, + isNull: function isNull(obj) { + return obj === null; + }, + isNaN: function (_isNaN) { + function isNaN(_x) { + return _isNaN.apply(this, arguments); + } + isNaN.toString = function () { + return _isNaN.toString(); + }; + return isNaN; + }(function (obj) { + return isNaN(obj); + }), + isArray: Array.isArray || function (obj) { + return obj.constructor === Array; + }, + isObject: function isObject(obj) { + return obj === Object(obj); + }, + isNumber: function isNumber(obj) { + return obj === obj + 0; + }, + isString: function isString(obj) { + return obj === obj + ''; + }, + isBoolean: function isBoolean(obj) { + return obj === false || obj === true; + }, + isFunction: function isFunction(obj) { + return obj instanceof Function; + } +}; + +var INTERPRETATIONS = [ +{ + litmus: Common.isString, + conversions: { + THREE_CHAR_HEX: { + read: function read(original) { + var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); + if (test === null) { + return false; + } + return { + space: 'HEX', + hex: parseInt('0x' + test[1].toString() + test[1].toString() + test[2].toString() + test[2].toString() + test[3].toString() + test[3].toString(), 0) + }; + }, + write: colorToString + }, + SIX_CHAR_HEX: { + read: function read(original) { + var test = original.match(/^#([A-F0-9]{6})$/i); + if (test === null) { + return false; + } + return { + space: 'HEX', + hex: parseInt('0x' + test[1].toString(), 0) + }; + }, + write: colorToString + }, + CSS_RGB: { + read: function read(original) { + var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); + if (test === null) { + return false; + } + return { + space: 'RGB', + r: parseFloat(test[1]), + g: parseFloat(test[2]), + b: parseFloat(test[3]) + }; + }, + write: colorToString + }, + CSS_RGBA: { + read: function read(original) { + var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); + if (test === null) { + return false; + } + return { + space: 'RGB', + r: parseFloat(test[1]), + g: parseFloat(test[2]), + b: parseFloat(test[3]), + a: parseFloat(test[4]) + }; + }, + write: colorToString + } + } +}, +{ + litmus: Common.isNumber, + conversions: { + HEX: { + read: function read(original) { + return { + space: 'HEX', + hex: original, + conversionName: 'HEX' + }; + }, + write: function write(color) { + return color.hex; + } + } + } +}, +{ + litmus: Common.isArray, + conversions: { + RGB_ARRAY: { + read: function read(original) { + if (original.length !== 3) { + return false; + } + return { + space: 'RGB', + r: original[0], + g: original[1], + b: original[2] + }; + }, + write: function write(color) { + return [color.r, color.g, color.b]; + } + }, + RGBA_ARRAY: { + read: function read(original) { + if (original.length !== 4) return false; + return { + space: 'RGB', + r: original[0], + g: original[1], + b: original[2], + a: original[3] + }; + }, + write: function write(color) { + return [color.r, color.g, color.b, color.a]; + } + } + } +}, +{ + litmus: Common.isObject, + conversions: { + RGBA_OBJ: { + read: function read(original) { + if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b) && Common.isNumber(original.a)) { + return { + space: 'RGB', + r: original.r, + g: original.g, + b: original.b, + a: original.a + }; + } + return false; + }, + write: function write(color) { + return { + r: color.r, + g: color.g, + b: color.b, + a: color.a + }; + } + }, + RGB_OBJ: { + read: function read(original) { + if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b)) { + return { + space: 'RGB', + r: original.r, + g: original.g, + b: original.b + }; + } + return false; + }, + write: function write(color) { + return { + r: color.r, + g: color.g, + b: color.b + }; + } + }, + HSVA_OBJ: { + read: function read(original) { + if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v) && Common.isNumber(original.a)) { + return { + space: 'HSV', + h: original.h, + s: original.s, + v: original.v, + a: original.a + }; + } + return false; + }, + write: function write(color) { + return { + h: color.h, + s: color.s, + v: color.v, + a: color.a + }; + } + }, + HSV_OBJ: { + read: function read(original) { + if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v)) { + return { + space: 'HSV', + h: original.h, + s: original.s, + v: original.v + }; + } + return false; + }, + write: function write(color) { + return { + h: color.h, + s: color.s, + v: color.v + }; + } + } + } +}]; +var result = void 0; +var toReturn = void 0; +var interpret = function interpret() { + toReturn = false; + var original = arguments.length > 1 ? Common.toArray(arguments) : arguments[0]; + Common.each(INTERPRETATIONS, function (family) { + if (family.litmus(original)) { + Common.each(family.conversions, function (conversion, conversionName) { + result = conversion.read(original); + if (toReturn === false && result !== false) { + toReturn = result; + result.conversionName = conversionName; + result.conversion = conversion; + return Common.BREAK; + } + }); + return Common.BREAK; + } + }); + return toReturn; +}; + +var tmpComponent = void 0; +var ColorMath = { + hsv_to_rgb: function hsv_to_rgb(h, s, v) { + var hi = Math.floor(h / 60) % 6; + var f = h / 60 - Math.floor(h / 60); + var p = v * (1.0 - s); + var q = v * (1.0 - f * s); + var t = v * (1.0 - (1.0 - f) * s); + var c = [[v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q]][hi]; + return { + r: c[0] * 255, + g: c[1] * 255, + b: c[2] * 255 + }; + }, + rgb_to_hsv: function rgb_to_hsv(r, g, b) { + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h = void 0; + var s = void 0; + if (max !== 0) { + s = delta / max; + } else { + return { + h: NaN, + s: 0, + v: 0 + }; + } + if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else { + h = 4 + (r - g) / delta; + } + h /= 6; + if (h < 0) { + h += 1; + } + return { + h: h * 360, + s: s, + v: max / 255 + }; + }, + rgb_to_hex: function rgb_to_hex(r, g, b) { + var hex = this.hex_with_component(0, 2, r); + hex = this.hex_with_component(hex, 1, g); + hex = this.hex_with_component(hex, 0, b); + return hex; + }, + component_from_hex: function component_from_hex(hex, componentIndex) { + return hex >> componentIndex * 8 & 0xFF; + }, + hex_with_component: function hex_with_component(hex, componentIndex, value) { + return value << (tmpComponent = componentIndex * 8) | hex & ~(0xFF << tmpComponent); + } +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + + + + + + + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + + + + + + + +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + + + + + + + + + + + +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; + +var Color = function () { + function Color() { + classCallCheck(this, Color); + this.__state = interpret.apply(this, arguments); + if (this.__state === false) { + throw new Error('Failed to interpret color arguments'); + } + this.__state.a = this.__state.a || 1; + } + createClass(Color, [{ + key: 'toString', + value: function toString() { + return colorToString(this); + } + }, { + key: 'toHexString', + value: function toHexString() { + return colorToString(this, true); + } + }, { + key: 'toOriginal', + value: function toOriginal() { + return this.__state.conversion.write(this); + } + }]); + return Color; +}(); +function defineRGBComponent(target, component, componentHexIndex) { + Object.defineProperty(target, component, { + get: function get$$1() { + if (this.__state.space === 'RGB') { + return this.__state[component]; + } + Color.recalculateRGB(this, component, componentHexIndex); + return this.__state[component]; + }, + set: function set$$1(v) { + if (this.__state.space !== 'RGB') { + Color.recalculateRGB(this, component, componentHexIndex); + this.__state.space = 'RGB'; + } + this.__state[component] = v; + } + }); +} +function defineHSVComponent(target, component) { + Object.defineProperty(target, component, { + get: function get$$1() { + if (this.__state.space === 'HSV') { + return this.__state[component]; + } + Color.recalculateHSV(this); + return this.__state[component]; + }, + set: function set$$1(v) { + if (this.__state.space !== 'HSV') { + Color.recalculateHSV(this); + this.__state.space = 'HSV'; + } + this.__state[component] = v; + } + }); +} +Color.recalculateRGB = function (color, component, componentHexIndex) { + if (color.__state.space === 'HEX') { + color.__state[component] = ColorMath.component_from_hex(color.__state.hex, componentHexIndex); + } else if (color.__state.space === 'HSV') { + Common.extend(color.__state, ColorMath.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); + } else { + throw new Error('Corrupted color state'); + } +}; +Color.recalculateHSV = function (color) { + var result = ColorMath.rgb_to_hsv(color.r, color.g, color.b); + Common.extend(color.__state, { + s: result.s, + v: result.v + }); + if (!Common.isNaN(result.h)) { + color.__state.h = result.h; + } else if (Common.isUndefined(color.__state.h)) { + color.__state.h = 0; + } +}; +Color.COMPONENTS = ['r', 'g', 'b', 'h', 's', 'v', 'hex', 'a']; +defineRGBComponent(Color.prototype, 'r', 2); +defineRGBComponent(Color.prototype, 'g', 1); +defineRGBComponent(Color.prototype, 'b', 0); +defineHSVComponent(Color.prototype, 'h'); +defineHSVComponent(Color.prototype, 's'); +defineHSVComponent(Color.prototype, 'v'); +Object.defineProperty(Color.prototype, 'a', { + get: function get$$1() { + return this.__state.a; + }, + set: function set$$1(v) { + this.__state.a = v; + } +}); +Object.defineProperty(Color.prototype, 'hex', { + get: function get$$1() { + if (this.__state.space !== 'HEX') { + this.__state.hex = ColorMath.rgb_to_hex(this.r, this.g, this.b); + this.__state.space = 'HEX'; + } + return this.__state.hex; + }, + set: function set$$1(v) { + this.__state.space = 'HEX'; + this.__state.hex = v; + } +}); + +var Controller = function () { + function Controller(object, property) { + classCallCheck(this, Controller); + this.initialValue = object[property]; + this.domElement = document.createElement('div'); + this.object = object; + this.property = property; + this.__onChange = undefined; + this.__onFinishChange = undefined; + } + createClass(Controller, [{ + key: 'onChange', + value: function onChange(fnc) { + this.__onChange = fnc; + return this; + } + }, { + key: 'onFinishChange', + value: function onFinishChange(fnc) { + this.__onFinishChange = fnc; + return this; + } + }, { + key: 'setValue', + value: function setValue(newValue) { + this.object[this.property] = newValue; + if (this.__onChange) { + this.__onChange.call(this, newValue); + } + this.updateDisplay(); + return this; + } + }, { + key: 'getValue', + value: function getValue() { + return this.object[this.property]; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + return this; + } + }, { + key: 'isModified', + value: function isModified() { + return this.initialValue !== this.getValue(); + } + }]); + return Controller; +}(); + +var EVENT_MAP = { + HTMLEvents: ['change'], + MouseEvents: ['click', 'mousemove', 'mousedown', 'mouseup', 'mouseover'], + KeyboardEvents: ['keydown'] +}; +var EVENT_MAP_INV = {}; +Common.each(EVENT_MAP, function (v, k) { + Common.each(v, function (e) { + EVENT_MAP_INV[e] = k; + }); +}); +var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/; +function cssValueToPixels(val) { + if (val === '0' || Common.isUndefined(val)) { + return 0; + } + var match = val.match(CSS_VALUE_PIXELS); + if (!Common.isNull(match)) { + return parseFloat(match[1]); + } + return 0; +} +var dom = { + makeSelectable: function makeSelectable(elem, selectable) { + if (elem === undefined || elem.style === undefined) return; + elem.onselectstart = selectable ? function () { + return false; + } : function () {}; + elem.style.MozUserSelect = selectable ? 'auto' : 'none'; + elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none'; + elem.unselectable = selectable ? 'on' : 'off'; + }, + makeFullscreen: function makeFullscreen(elem, hor, vert) { + var vertical = vert; + var horizontal = hor; + if (Common.isUndefined(horizontal)) { + horizontal = true; + } + if (Common.isUndefined(vertical)) { + vertical = true; + } + elem.style.position = 'absolute'; + if (horizontal) { + elem.style.left = 0; + elem.style.right = 0; + } + if (vertical) { + elem.style.top = 0; + elem.style.bottom = 0; + } + }, + fakeEvent: function fakeEvent(elem, eventType, pars, aux) { + var params = pars || {}; + var className = EVENT_MAP_INV[eventType]; + if (!className) { + throw new Error('Event type ' + eventType + ' not supported.'); + } + var evt = document.createEvent(className); + switch (className) { + case 'MouseEvents': + { + var clientX = params.x || params.clientX || 0; + var clientY = params.y || params.clientY || 0; + evt.initMouseEvent(eventType, params.bubbles || false, params.cancelable || true, window, params.clickCount || 1, 0, + 0, + clientX, + clientY, + false, false, false, false, 0, null); + break; + } + case 'KeyboardEvents': + { + var init = evt.initKeyboardEvent || evt.initKeyEvent; + Common.defaults(params, { + cancelable: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: undefined, + charCode: undefined + }); + init(eventType, params.bubbles || false, params.cancelable, window, params.ctrlKey, params.altKey, params.shiftKey, params.metaKey, params.keyCode, params.charCode); + break; + } + default: + { + evt.initEvent(eventType, params.bubbles || false, params.cancelable || true); + break; + } + } + Common.defaults(evt, aux); + elem.dispatchEvent(evt); + }, + bind: function bind(elem, event, func, newBool) { + var bool = newBool || false; + if (elem.addEventListener) { + elem.addEventListener(event, func, bool); + } else if (elem.attachEvent) { + elem.attachEvent('on' + event, func); + } + return dom; + }, + unbind: function unbind(elem, event, func, newBool) { + var bool = newBool || false; + if (elem.removeEventListener) { + elem.removeEventListener(event, func, bool); + } else if (elem.detachEvent) { + elem.detachEvent('on' + event, func); + } + return dom; + }, + addClass: function addClass(elem, className) { + if (elem.className === undefined) { + elem.className = className; + } else if (elem.className !== className) { + var classes = elem.className.split(/ +/); + if (classes.indexOf(className) === -1) { + classes.push(className); + elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, ''); + } + } + return dom; + }, + removeClass: function removeClass(elem, className) { + if (className) { + if (elem.className === className) { + elem.removeAttribute('class'); + } else { + var classes = elem.className.split(/ +/); + var index = classes.indexOf(className); + if (index !== -1) { + classes.splice(index, 1); + elem.className = classes.join(' '); + } + } + } else { + elem.className = undefined; + } + return dom; + }, + hasClass: function hasClass(elem, className) { + return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false; + }, + getWidth: function getWidth(elem) { + var style = getComputedStyle(elem); + return cssValueToPixels(style['border-left-width']) + cssValueToPixels(style['border-right-width']) + cssValueToPixels(style['padding-left']) + cssValueToPixels(style['padding-right']) + cssValueToPixels(style.width); + }, + getHeight: function getHeight(elem) { + var style = getComputedStyle(elem); + return cssValueToPixels(style['border-top-width']) + cssValueToPixels(style['border-bottom-width']) + cssValueToPixels(style['padding-top']) + cssValueToPixels(style['padding-bottom']) + cssValueToPixels(style.height); + }, + getOffset: function getOffset(el) { + var elem = el; + var offset = { left: 0, top: 0 }; + if (elem.offsetParent) { + do { + offset.left += elem.offsetLeft; + offset.top += elem.offsetTop; + elem = elem.offsetParent; + } while (elem); + } + return offset; + }, + isActive: function isActive(elem) { + return elem === document.activeElement && (elem.type || elem.href); + } +}; + +var BooleanController = function (_Controller) { + inherits(BooleanController, _Controller); + function BooleanController(object, property) { + classCallCheck(this, BooleanController); + var _this2 = possibleConstructorReturn(this, (BooleanController.__proto__ || Object.getPrototypeOf(BooleanController)).call(this, object, property)); + var _this = _this2; + _this2.__prev = _this2.getValue(); + _this2.__checkbox = document.createElement('input'); + _this2.__checkbox.setAttribute('type', 'checkbox'); + function onChange() { + _this.setValue(!_this.__prev); + } + dom.bind(_this2.__checkbox, 'change', onChange, false); + _this2.domElement.appendChild(_this2.__checkbox); + _this2.updateDisplay(); + return _this2; + } + createClass(BooleanController, [{ + key: 'setValue', + value: function setValue(v) { + var toReturn = get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'setValue', this).call(this, v); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + this.__prev = this.getValue(); + return toReturn; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + if (this.getValue() === true) { + this.__checkbox.setAttribute('checked', 'checked'); + this.__checkbox.checked = true; + this.__prev = true; + } else { + this.__checkbox.checked = false; + this.__prev = false; + } + return get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'updateDisplay', this).call(this); + } + }]); + return BooleanController; +}(Controller); + +var OptionController = function (_Controller) { + inherits(OptionController, _Controller); + function OptionController(object, property, opts) { + classCallCheck(this, OptionController); + var _this2 = possibleConstructorReturn(this, (OptionController.__proto__ || Object.getPrototypeOf(OptionController)).call(this, object, property)); + var options = opts; + var _this = _this2; + _this2.__select = document.createElement('select'); + if (Common.isArray(options)) { + var map = {}; + Common.each(options, function (element) { + map[element] = element; + }); + options = map; + } + Common.each(options, function (value, key) { + var opt = document.createElement('option'); + opt.innerHTML = key; + opt.setAttribute('value', value); + _this.__select.appendChild(opt); + }); + _this2.updateDisplay(); + dom.bind(_this2.__select, 'change', function () { + var desiredValue = this.options[this.selectedIndex].value; + _this.setValue(desiredValue); + }); + _this2.domElement.appendChild(_this2.__select); + return _this2; + } + createClass(OptionController, [{ + key: 'setValue', + value: function setValue(v) { + var toReturn = get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'setValue', this).call(this, v); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + return toReturn; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + if (dom.isActive(this.__select)) return this; + this.__select.value = this.getValue(); + return get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'updateDisplay', this).call(this); + } + }]); + return OptionController; +}(Controller); + +var StringController = function (_Controller) { + inherits(StringController, _Controller); + function StringController(object, property) { + classCallCheck(this, StringController); + var _this2 = possibleConstructorReturn(this, (StringController.__proto__ || Object.getPrototypeOf(StringController)).call(this, object, property)); + var _this = _this2; + function onChange() { + _this.setValue(_this.__input.value); + } + function onBlur() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + _this2.__input = document.createElement('input'); + _this2.__input.setAttribute('type', 'text'); + dom.bind(_this2.__input, 'keyup', onChange); + dom.bind(_this2.__input, 'change', onChange); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + this.blur(); + } + }); + _this2.updateDisplay(); + _this2.domElement.appendChild(_this2.__input); + return _this2; + } + createClass(StringController, [{ + key: 'updateDisplay', + value: function updateDisplay() { + if (!dom.isActive(this.__input)) { + this.__input.value = this.getValue(); + } + return get(StringController.prototype.__proto__ || Object.getPrototypeOf(StringController.prototype), 'updateDisplay', this).call(this); + } + }]); + return StringController; +}(Controller); + +function numDecimals(x) { + var _x = x.toString(); + if (_x.indexOf('.') > -1) { + return _x.length - _x.indexOf('.') - 1; + } + return 0; +} +var NumberController = function (_Controller) { + inherits(NumberController, _Controller); + function NumberController(object, property, params) { + classCallCheck(this, NumberController); + var _this = possibleConstructorReturn(this, (NumberController.__proto__ || Object.getPrototypeOf(NumberController)).call(this, object, property)); + var _params = params || {}; + _this.__min = _params.min; + _this.__max = _params.max; + _this.__step = _params.step; + if (Common.isUndefined(_this.__step)) { + if (_this.initialValue === 0) { + _this.__impliedStep = 1; + } else { + _this.__impliedStep = Math.pow(10, Math.floor(Math.log(Math.abs(_this.initialValue)) / Math.LN10)) / 10; + } + } else { + _this.__impliedStep = _this.__step; + } + _this.__precision = numDecimals(_this.__impliedStep); + return _this; + } + createClass(NumberController, [{ + key: 'setValue', + value: function setValue(v) { + var _v = v; + if (this.__min !== undefined && _v < this.__min) { + _v = this.__min; + } else if (this.__max !== undefined && _v > this.__max) { + _v = this.__max; + } + if (this.__step !== undefined && _v % this.__step !== 0) { + _v = Math.round(_v / this.__step) * this.__step; + } + return get(NumberController.prototype.__proto__ || Object.getPrototypeOf(NumberController.prototype), 'setValue', this).call(this, _v); + } + }, { + key: 'min', + value: function min(minValue) { + this.__min = minValue; + return this; + } + }, { + key: 'max', + value: function max(maxValue) { + this.__max = maxValue; + return this; + } + }, { + key: 'step', + value: function step(stepValue) { + this.__step = stepValue; + this.__impliedStep = stepValue; + this.__precision = numDecimals(stepValue); + return this; + } + }]); + return NumberController; +}(Controller); + +function roundToDecimal(value, decimals) { + var tenTo = Math.pow(10, decimals); + return Math.round(value * tenTo) / tenTo; +} +var NumberControllerBox = function (_NumberController) { + inherits(NumberControllerBox, _NumberController); + function NumberControllerBox(object, property, params) { + classCallCheck(this, NumberControllerBox); + var _this2 = possibleConstructorReturn(this, (NumberControllerBox.__proto__ || Object.getPrototypeOf(NumberControllerBox)).call(this, object, property, params)); + _this2.__truncationSuspended = false; + var _this = _this2; + var prevY = void 0; + function onChange() { + var attempted = parseFloat(_this.__input.value); + if (!Common.isNaN(attempted)) { + _this.setValue(attempted); + } + } + function onFinish() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + function onBlur() { + onFinish(); + } + function onMouseDrag(e) { + var diff = prevY - e.clientY; + _this.setValue(_this.getValue() + diff * _this.__impliedStep); + prevY = e.clientY; + } + function onMouseUp() { + dom.unbind(window, 'mousemove', onMouseDrag); + dom.unbind(window, 'mouseup', onMouseUp); + onFinish(); + } + function onMouseDown(e) { + dom.bind(window, 'mousemove', onMouseDrag); + dom.bind(window, 'mouseup', onMouseUp); + prevY = e.clientY; + } + _this2.__input = document.createElement('input'); + _this2.__input.setAttribute('type', 'text'); + dom.bind(_this2.__input, 'change', onChange); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__input, 'mousedown', onMouseDown); + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + _this.__truncationSuspended = true; + this.blur(); + _this.__truncationSuspended = false; + onFinish(); + } + }); + _this2.updateDisplay(); + _this2.domElement.appendChild(_this2.__input); + return _this2; + } + createClass(NumberControllerBox, [{ + key: 'updateDisplay', + value: function updateDisplay() { + this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision); + return get(NumberControllerBox.prototype.__proto__ || Object.getPrototypeOf(NumberControllerBox.prototype), 'updateDisplay', this).call(this); + } + }]); + return NumberControllerBox; +}(NumberController); + +function map(v, i1, i2, o1, o2) { + return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); +} +var NumberControllerSlider = function (_NumberController) { + inherits(NumberControllerSlider, _NumberController); + function NumberControllerSlider(object, property, min, max, step) { + classCallCheck(this, NumberControllerSlider); + var _this2 = possibleConstructorReturn(this, (NumberControllerSlider.__proto__ || Object.getPrototypeOf(NumberControllerSlider)).call(this, object, property, { min: min, max: max, step: step })); + var _this = _this2; + _this2.__background = document.createElement('div'); + _this2.__foreground = document.createElement('div'); + dom.bind(_this2.__background, 'mousedown', onMouseDown); + dom.bind(_this2.__background, 'touchstart', onTouchStart); + dom.addClass(_this2.__background, 'slider'); + dom.addClass(_this2.__foreground, 'slider-fg'); + function onMouseDown(e) { + document.activeElement.blur(); + dom.bind(window, 'mousemove', onMouseDrag); + dom.bind(window, 'mouseup', onMouseUp); + onMouseDrag(e); + } + function onMouseDrag(e) { + e.preventDefault(); + var bgRect = _this.__background.getBoundingClientRect(); + _this.setValue(map(e.clientX, bgRect.left, bgRect.right, _this.__min, _this.__max)); + return false; + } + function onMouseUp() { + dom.unbind(window, 'mousemove', onMouseDrag); + dom.unbind(window, 'mouseup', onMouseUp); + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + function onTouchStart(e) { + if (e.touches.length !== 1) { + return; + } + dom.bind(window, 'touchmove', onTouchMove); + dom.bind(window, 'touchend', onTouchEnd); + onTouchMove(e); + } + function onTouchMove(e) { + var clientX = e.touches[0].clientX; + var bgRect = _this.__background.getBoundingClientRect(); + _this.setValue(map(clientX, bgRect.left, bgRect.right, _this.__min, _this.__max)); + } + function onTouchEnd() { + dom.unbind(window, 'touchmove', onTouchMove); + dom.unbind(window, 'touchend', onTouchEnd); + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + _this2.updateDisplay(); + _this2.__background.appendChild(_this2.__foreground); + _this2.domElement.appendChild(_this2.__background); + return _this2; + } + createClass(NumberControllerSlider, [{ + key: 'updateDisplay', + value: function updateDisplay() { + var pct = (this.getValue() - this.__min) / (this.__max - this.__min); + this.__foreground.style.width = pct * 100 + '%'; + return get(NumberControllerSlider.prototype.__proto__ || Object.getPrototypeOf(NumberControllerSlider.prototype), 'updateDisplay', this).call(this); + } + }]); + return NumberControllerSlider; +}(NumberController); + +var FunctionController = function (_Controller) { + inherits(FunctionController, _Controller); + function FunctionController(object, property, text) { + classCallCheck(this, FunctionController); + var _this2 = possibleConstructorReturn(this, (FunctionController.__proto__ || Object.getPrototypeOf(FunctionController)).call(this, object, property)); + var _this = _this2; + _this2.__button = document.createElement('div'); + _this2.__button.innerHTML = text === undefined ? 'Fire' : text; + dom.bind(_this2.__button, 'click', function (e) { + e.preventDefault(); + _this.fire(); + return false; + }); + dom.addClass(_this2.__button, 'button'); + _this2.domElement.appendChild(_this2.__button); + return _this2; + } + createClass(FunctionController, [{ + key: 'fire', + value: function fire() { + if (this.__onChange) { + this.__onChange.call(this); + } + this.getValue().call(this.object); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + } + }]); + return FunctionController; +}(Controller); + +var ColorController = function (_Controller) { + inherits(ColorController, _Controller); + function ColorController(object, property) { + classCallCheck(this, ColorController); + var _this2 = possibleConstructorReturn(this, (ColorController.__proto__ || Object.getPrototypeOf(ColorController)).call(this, object, property)); + _this2.__color = new Color(_this2.getValue()); + _this2.__temp = new Color(0); + var _this = _this2; + _this2.domElement = document.createElement('div'); + dom.makeSelectable(_this2.domElement, false); + _this2.__selector = document.createElement('div'); + _this2.__selector.className = 'selector'; + _this2.__saturation_field = document.createElement('div'); + _this2.__saturation_field.className = 'saturation-field'; + _this2.__field_knob = document.createElement('div'); + _this2.__field_knob.className = 'field-knob'; + _this2.__field_knob_border = '2px solid '; + _this2.__hue_knob = document.createElement('div'); + _this2.__hue_knob.className = 'hue-knob'; + _this2.__hue_field = document.createElement('div'); + _this2.__hue_field.className = 'hue-field'; + _this2.__input = document.createElement('input'); + _this2.__input.type = 'text'; + _this2.__input_textShadow = '0 1px 1px '; + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + onBlur.call(this); + } + }); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__selector, 'mousedown', function () { + dom.addClass(this, 'drag').bind(window, 'mouseup', function () { + dom.removeClass(_this.__selector, 'drag'); + }); + }); + dom.bind(_this2.__selector, 'touchstart', function () { + dom.addClass(this, 'drag').bind(window, 'touchend', function () { + dom.removeClass(_this.__selector, 'drag'); + }); + }); + var valueField = document.createElement('div'); + Common.extend(_this2.__selector.style, { + width: '122px', + height: '102px', + padding: '3px', + backgroundColor: '#222', + boxShadow: '0px 1px 3px rgba(0,0,0,0.3)' + }); + Common.extend(_this2.__field_knob.style, { + position: 'absolute', + width: '12px', + height: '12px', + border: _this2.__field_knob_border + (_this2.__color.v < 0.5 ? '#fff' : '#000'), + boxShadow: '0px 1px 3px rgba(0,0,0,0.5)', + borderRadius: '12px', + zIndex: 1 + }); + Common.extend(_this2.__hue_knob.style, { + position: 'absolute', + width: '15px', + height: '2px', + borderRight: '4px solid #fff', + zIndex: 1 + }); + Common.extend(_this2.__saturation_field.style, { + width: '100px', + height: '100px', + border: '1px solid #555', + marginRight: '3px', + display: 'inline-block', + cursor: 'pointer' + }); + Common.extend(valueField.style, { + width: '100%', + height: '100%', + background: 'none' + }); + linearGradient(valueField, 'top', 'rgba(0,0,0,0)', '#000'); + Common.extend(_this2.__hue_field.style, { + width: '15px', + height: '100px', + border: '1px solid #555', + cursor: 'ns-resize', + position: 'absolute', + top: '3px', + right: '3px' + }); + hueGradient(_this2.__hue_field); + Common.extend(_this2.__input.style, { + outline: 'none', + textAlign: 'center', + color: '#fff', + border: 0, + fontWeight: 'bold', + textShadow: _this2.__input_textShadow + 'rgba(0,0,0,0.7)' + }); + dom.bind(_this2.__saturation_field, 'mousedown', fieldDown); + dom.bind(_this2.__saturation_field, 'touchstart', fieldDown); + dom.bind(_this2.__field_knob, 'mousedown', fieldDown); + dom.bind(_this2.__field_knob, 'touchstart', fieldDown); + dom.bind(_this2.__hue_field, 'mousedown', fieldDownH); + dom.bind(_this2.__hue_field, 'touchstart', fieldDownH); + function fieldDown(e) { + setSV(e); + dom.bind(window, 'mousemove', setSV); + dom.bind(window, 'touchmove', setSV); + dom.bind(window, 'mouseup', fieldUpSV); + dom.bind(window, 'touchend', fieldUpSV); + } + function fieldDownH(e) { + setH(e); + dom.bind(window, 'mousemove', setH); + dom.bind(window, 'touchmove', setH); + dom.bind(window, 'mouseup', fieldUpH); + dom.bind(window, 'touchend', fieldUpH); + } + function fieldUpSV() { + dom.unbind(window, 'mousemove', setSV); + dom.unbind(window, 'touchmove', setSV); + dom.unbind(window, 'mouseup', fieldUpSV); + dom.unbind(window, 'touchend', fieldUpSV); + onFinish(); + } + function fieldUpH() { + dom.unbind(window, 'mousemove', setH); + dom.unbind(window, 'touchmove', setH); + dom.unbind(window, 'mouseup', fieldUpH); + dom.unbind(window, 'touchend', fieldUpH); + onFinish(); + } + function onBlur() { + var i = interpret(this.value); + if (i !== false) { + _this.__color.__state = i; + _this.setValue(_this.__color.toOriginal()); + } else { + this.value = _this.__color.toString(); + } + } + function onFinish() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.__color.toOriginal()); + } + } + _this2.__saturation_field.appendChild(valueField); + _this2.__selector.appendChild(_this2.__field_knob); + _this2.__selector.appendChild(_this2.__saturation_field); + _this2.__selector.appendChild(_this2.__hue_field); + _this2.__hue_field.appendChild(_this2.__hue_knob); + _this2.domElement.appendChild(_this2.__input); + _this2.domElement.appendChild(_this2.__selector); + _this2.updateDisplay(); + function setSV(e) { + if (e.type.indexOf('touch') === -1) { + e.preventDefault(); + } + var fieldRect = _this.__saturation_field.getBoundingClientRect(); + var _ref = e.touches && e.touches[0] || e, + clientX = _ref.clientX, + clientY = _ref.clientY; + var s = (clientX - fieldRect.left) / (fieldRect.right - fieldRect.left); + var v = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top); + if (v > 1) { + v = 1; + } else if (v < 0) { + v = 0; + } + if (s > 1) { + s = 1; + } else if (s < 0) { + s = 0; + } + _this.__color.v = v; + _this.__color.s = s; + _this.setValue(_this.__color.toOriginal()); + return false; + } + function setH(e) { + if (e.type.indexOf('touch') === -1) { + e.preventDefault(); + } + var fieldRect = _this.__hue_field.getBoundingClientRect(); + var _ref2 = e.touches && e.touches[0] || e, + clientY = _ref2.clientY; + var h = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top); + if (h > 1) { + h = 1; + } else if (h < 0) { + h = 0; + } + _this.__color.h = h * 360; + _this.setValue(_this.__color.toOriginal()); + return false; + } + return _this2; + } + createClass(ColorController, [{ + key: 'updateDisplay', + value: function updateDisplay() { + var i = interpret(this.getValue()); + if (i !== false) { + var mismatch = false; + Common.each(Color.COMPONENTS, function (component) { + if (!Common.isUndefined(i[component]) && !Common.isUndefined(this.__color.__state[component]) && i[component] !== this.__color.__state[component]) { + mismatch = true; + return {}; + } + }, this); + if (mismatch) { + Common.extend(this.__color.__state, i); + } + } + Common.extend(this.__temp.__state, this.__color.__state); + this.__temp.a = 1; + var flip = this.__color.v < 0.5 || this.__color.s > 0.5 ? 255 : 0; + var _flip = 255 - flip; + Common.extend(this.__field_knob.style, { + marginLeft: 100 * this.__color.s - 7 + 'px', + marginTop: 100 * (1 - this.__color.v) - 7 + 'px', + backgroundColor: this.__temp.toHexString(), + border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip + ')' + }); + this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px'; + this.__temp.s = 1; + this.__temp.v = 1; + linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toHexString()); + this.__input.value = this.__color.toString(); + Common.extend(this.__input.style, { + backgroundColor: this.__color.toHexString(), + color: 'rgb(' + flip + ',' + flip + ',' + flip + ')', + textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip + ',.7)' + }); + } + }]); + return ColorController; +}(Controller); +var vendors = ['-moz-', '-o-', '-webkit-', '-ms-', '']; +function linearGradient(elem, x, a, b) { + elem.style.background = ''; + Common.each(vendors, function (vendor) { + elem.style.cssText += 'background: ' + vendor + 'linear-gradient(' + x + ', ' + a + ' 0%, ' + b + ' 100%); '; + }); +} +function hueGradient(elem) { + elem.style.background = ''; + elem.style.cssText += 'background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);'; + elem.style.cssText += 'background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; +} + +var css = { + load: function load(url, indoc) { + var doc = indoc || document; + var link = doc.createElement('link'); + link.type = 'text/css'; + link.rel = 'stylesheet'; + link.href = url; + doc.getElementsByTagName('head')[0].appendChild(link); + }, + inject: function inject(cssContent, indoc) { + var doc = indoc || document; + var injected = document.createElement('style'); + injected.type = 'text/css'; + injected.innerHTML = cssContent; + var head = doc.getElementsByTagName('head')[0]; + try { + head.appendChild(injected); + } catch (e) { + } + } +}; + +var saveDialogContents = "
\n\n Here's the new load parameter for your GUI's constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI's constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n\n
\n\n
\n\n
"; + +var ControllerFactory = function ControllerFactory(object, property) { + var initialValue = object[property]; + if (Common.isArray(arguments[2]) || Common.isObject(arguments[2])) { + return new OptionController(object, property, arguments[2]); + } + if (Common.isNumber(initialValue)) { + if (Common.isNumber(arguments[2]) && Common.isNumber(arguments[3])) { + if (Common.isNumber(arguments[4])) { + return new NumberControllerSlider(object, property, arguments[2], arguments[3], arguments[4]); + } + return new NumberControllerSlider(object, property, arguments[2], arguments[3]); + } + if (Common.isNumber(arguments[4])) { + return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3], step: arguments[4] }); + } + return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] }); + } + if (Common.isString(initialValue)) { + return new StringController(object, property); + } + if (Common.isFunction(initialValue)) { + return new FunctionController(object, property, ''); + } + if (Common.isBoolean(initialValue)) { + return new BooleanController(object, property); + } + return null; +}; + +function requestAnimationFrame(callback) { + setTimeout(callback, 1000 / 60); +} +var requestAnimationFrame$1 = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || requestAnimationFrame; + +var CenteredDiv = function () { + function CenteredDiv() { + classCallCheck(this, CenteredDiv); + this.backgroundElement = document.createElement('div'); + Common.extend(this.backgroundElement.style, { + backgroundColor: 'rgba(0,0,0,0.8)', + top: 0, + left: 0, + display: 'none', + zIndex: '1000', + opacity: 0, + WebkitTransition: 'opacity 0.2s linear', + transition: 'opacity 0.2s linear' + }); + dom.makeFullscreen(this.backgroundElement); + this.backgroundElement.style.position = 'fixed'; + this.domElement = document.createElement('div'); + Common.extend(this.domElement.style, { + position: 'fixed', + display: 'none', + zIndex: '1001', + opacity: 0, + WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear', + transition: 'transform 0.2s ease-out, opacity 0.2s linear' + }); + document.body.appendChild(this.backgroundElement); + document.body.appendChild(this.domElement); + var _this = this; + dom.bind(this.backgroundElement, 'click', function () { + _this.hide(); + }); + } + createClass(CenteredDiv, [{ + key: 'show', + value: function show() { + var _this = this; + this.backgroundElement.style.display = 'block'; + this.domElement.style.display = 'block'; + this.domElement.style.opacity = 0; + this.domElement.style.webkitTransform = 'scale(1.1)'; + this.layout(); + Common.defer(function () { + _this.backgroundElement.style.opacity = 1; + _this.domElement.style.opacity = 1; + _this.domElement.style.webkitTransform = 'scale(1)'; + }); + } + }, { + key: 'hide', + value: function hide() { + var _this = this; + var hide = function hide() { + _this.domElement.style.display = 'none'; + _this.backgroundElement.style.display = 'none'; + dom.unbind(_this.domElement, 'webkitTransitionEnd', hide); + dom.unbind(_this.domElement, 'transitionend', hide); + dom.unbind(_this.domElement, 'oTransitionEnd', hide); + }; + dom.bind(this.domElement, 'webkitTransitionEnd', hide); + dom.bind(this.domElement, 'transitionend', hide); + dom.bind(this.domElement, 'oTransitionEnd', hide); + this.backgroundElement.style.opacity = 0; + this.domElement.style.opacity = 0; + this.domElement.style.webkitTransform = 'scale(1.1)'; + } + }, { + key: 'layout', + value: function layout() { + this.domElement.style.left = window.innerWidth / 2 - dom.getWidth(this.domElement) / 2 + 'px'; + this.domElement.style.top = window.innerHeight / 2 - dom.getHeight(this.domElement) / 2 + 'px'; + } + }]); + return CenteredDiv; +}(); + +var styleSheet = ___$insertStyle(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n"); + +css.inject(styleSheet); +var CSS_NAMESPACE = 'dg'; +var HIDE_KEY_CODE = 72; +var CLOSE_BUTTON_HEIGHT = 20; +var DEFAULT_DEFAULT_PRESET_NAME = 'Default'; +var SUPPORTS_LOCAL_STORAGE = function () { + try { + return !!window.localStorage; + } catch (e) { + return false; + } +}(); +var SAVE_DIALOGUE = void 0; +var autoPlaceVirgin = true; +var autoPlaceContainer = void 0; +var hide = false; +var hideableGuis = []; +var GUI = function GUI(pars) { + var _this = this; + var params = pars || {}; + this.domElement = document.createElement('div'); + this.__ul = document.createElement('ul'); + this.domElement.appendChild(this.__ul); + dom.addClass(this.domElement, CSS_NAMESPACE); + this.__folders = {}; + this.__controllers = []; + this.__rememberedObjects = []; + this.__rememberedObjectIndecesToControllers = []; + this.__listening = []; + params = Common.defaults(params, { + closeOnTop: false, + autoPlace: true, + width: GUI.DEFAULT_WIDTH + }); + params = Common.defaults(params, { + resizable: params.autoPlace, + hideable: params.autoPlace + }); + if (!Common.isUndefined(params.load)) { + if (params.preset) { + params.load.preset = params.preset; + } + } else { + params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME }; + } + if (Common.isUndefined(params.parent) && params.hideable) { + hideableGuis.push(this); + } + params.resizable = Common.isUndefined(params.parent) && params.resizable; + if (params.autoPlace && Common.isUndefined(params.scrollable)) { + params.scrollable = true; + } + var useLocalStorage = SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true'; + var saveToLocalStorage = void 0; + var titleRow = void 0; + Object.defineProperties(this, + { + parent: { + get: function get$$1() { + return params.parent; + } + }, + scrollable: { + get: function get$$1() { + return params.scrollable; + } + }, + autoPlace: { + get: function get$$1() { + return params.autoPlace; + } + }, + closeOnTop: { + get: function get$$1() { + return params.closeOnTop; + } + }, + preset: { + get: function get$$1() { + if (_this.parent) { + return _this.getRoot().preset; + } + return params.load.preset; + }, + set: function set$$1(v) { + if (_this.parent) { + _this.getRoot().preset = v; + } else { + params.load.preset = v; + } + setPresetSelectIndex(this); + _this.revert(); + } + }, + width: { + get: function get$$1() { + return params.width; + }, + set: function set$$1(v) { + params.width = v; + setWidth(_this, v); + } + }, + name: { + get: function get$$1() { + return params.name; + }, + set: function set$$1(v) { + params.name = v; + if (titleRow) { + titleRow.innerHTML = params.name; + } + } + }, + closed: { + get: function get$$1() { + return params.closed; + }, + set: function set$$1(v) { + params.closed = v; + if (params.closed) { + dom.addClass(_this.__ul, GUI.CLASS_CLOSED); + } else { + dom.removeClass(_this.__ul, GUI.CLASS_CLOSED); + } + this.onResize(); + if (_this.__closeButton) { + _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED; + } + } + }, + load: { + get: function get$$1() { + return params.load; + } + }, + useLocalStorage: { + get: function get$$1() { + return useLocalStorage; + }, + set: function set$$1(bool) { + if (SUPPORTS_LOCAL_STORAGE) { + useLocalStorage = bool; + if (bool) { + dom.bind(window, 'unload', saveToLocalStorage); + } else { + dom.unbind(window, 'unload', saveToLocalStorage); + } + localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool); + } + } + } + }); + if (Common.isUndefined(params.parent)) { + this.closed = params.closed || false; + dom.addClass(this.domElement, GUI.CLASS_MAIN); + dom.makeSelectable(this.domElement, false); + if (SUPPORTS_LOCAL_STORAGE) { + if (useLocalStorage) { + _this.useLocalStorage = true; + var savedGui = localStorage.getItem(getLocalStorageHash(this, 'gui')); + if (savedGui) { + params.load = JSON.parse(savedGui); + } + } + } + this.__closeButton = document.createElement('div'); + this.__closeButton.innerHTML = GUI.TEXT_CLOSED; + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON); + if (params.closeOnTop) { + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_TOP); + this.domElement.insertBefore(this.__closeButton, this.domElement.childNodes[0]); + } else { + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BOTTOM); + this.domElement.appendChild(this.__closeButton); + } + dom.bind(this.__closeButton, 'click', function () { + _this.closed = !_this.closed; + }); + } else { + if (params.closed === undefined) { + params.closed = true; + } + var titleRowName = document.createTextNode(params.name); + dom.addClass(titleRowName, 'controller-name'); + titleRow = addRow(_this, titleRowName); + var onClickTitle = function onClickTitle(e) { + e.preventDefault(); + _this.closed = !_this.closed; + return false; + }; + dom.addClass(this.__ul, GUI.CLASS_CLOSED); + dom.addClass(titleRow, 'title'); + dom.bind(titleRow, 'click', onClickTitle); + if (!params.closed) { + this.closed = false; + } + } + if (params.autoPlace) { + if (Common.isUndefined(params.parent)) { + if (autoPlaceVirgin) { + autoPlaceContainer = document.createElement('div'); + dom.addClass(autoPlaceContainer, CSS_NAMESPACE); + dom.addClass(autoPlaceContainer, GUI.CLASS_AUTO_PLACE_CONTAINER); + document.body.appendChild(autoPlaceContainer); + autoPlaceVirgin = false; + } + autoPlaceContainer.appendChild(this.domElement); + dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE); + } + if (!this.parent) { + setWidth(_this, params.width); + } + } + this.__resizeHandler = function () { + _this.onResizeDebounced(); + }; + dom.bind(window, 'resize', this.__resizeHandler); + dom.bind(this.__ul, 'webkitTransitionEnd', this.__resizeHandler); + dom.bind(this.__ul, 'transitionend', this.__resizeHandler); + dom.bind(this.__ul, 'oTransitionEnd', this.__resizeHandler); + this.onResize(); + if (params.resizable) { + addResizeHandle(this); + } + saveToLocalStorage = function saveToLocalStorage() { + if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') { + localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject())); + } + }; + this.saveToLocalStorageIfPossible = saveToLocalStorage; + function resetWidth() { + var root = _this.getRoot(); + root.width += 1; + Common.defer(function () { + root.width -= 1; + }); + } + if (!params.parent) { + resetWidth(); + } +}; +GUI.toggleHide = function () { + hide = !hide; + Common.each(hideableGuis, function (gui) { + gui.domElement.style.display = hide ? 'none' : ''; + }); +}; +GUI.CLASS_AUTO_PLACE = 'a'; +GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac'; +GUI.CLASS_MAIN = 'main'; +GUI.CLASS_CONTROLLER_ROW = 'cr'; +GUI.CLASS_TOO_TALL = 'taller-than-window'; +GUI.CLASS_CLOSED = 'closed'; +GUI.CLASS_CLOSE_BUTTON = 'close-button'; +GUI.CLASS_CLOSE_TOP = 'close-top'; +GUI.CLASS_CLOSE_BOTTOM = 'close-bottom'; +GUI.CLASS_DRAG = 'drag'; +GUI.DEFAULT_WIDTH = 245; +GUI.TEXT_CLOSED = 'Close Controls'; +GUI.TEXT_OPEN = 'Open Controls'; +GUI._keydownHandler = function (e) { + if (document.activeElement.type !== 'text' && (e.which === HIDE_KEY_CODE || e.keyCode === HIDE_KEY_CODE)) { + GUI.toggleHide(); + } +}; +dom.bind(window, 'keydown', GUI._keydownHandler, false); +Common.extend(GUI.prototype, +{ + add: function add(object, property) { + return _add(this, object, property, { + factoryArgs: Array.prototype.slice.call(arguments, 2) + }); + }, + addColor: function addColor(object, property) { + return _add(this, object, property, { + color: true + }); + }, + remove: function remove(controller) { + this.__ul.removeChild(controller.__li); + this.__controllers.splice(this.__controllers.indexOf(controller), 1); + var _this = this; + Common.defer(function () { + _this.onResize(); + }); + }, + destroy: function destroy() { + if (this.parent) { + throw new Error('Only the root GUI should be removed with .destroy(). ' + 'For subfolders, use gui.removeFolder(folder) instead.'); + } + if (this.autoPlace) { + autoPlaceContainer.removeChild(this.domElement); + } + var _this = this; + Common.each(this.__folders, function (subfolder) { + _this.removeFolder(subfolder); + }); + dom.unbind(window, 'keydown', GUI._keydownHandler, false); + removeListeners(this); + }, + addFolder: function addFolder(name) { + if (this.__folders[name] !== undefined) { + throw new Error('You already have a folder in this GUI by the' + ' name "' + name + '"'); + } + var newGuiParams = { name: name, parent: this }; + newGuiParams.autoPlace = this.autoPlace; + if (this.load && + this.load.folders && + this.load.folders[name]) { + newGuiParams.closed = this.load.folders[name].closed; + newGuiParams.load = this.load.folders[name]; + } + var gui = new GUI(newGuiParams); + this.__folders[name] = gui; + var li = addRow(this, gui.domElement); + dom.addClass(li, 'folder'); + return gui; + }, + removeFolder: function removeFolder(folder) { + this.__ul.removeChild(folder.domElement.parentElement); + delete this.__folders[folder.name]; + if (this.load && + this.load.folders && + this.load.folders[folder.name]) { + delete this.load.folders[folder.name]; + } + removeListeners(folder); + var _this = this; + Common.each(folder.__folders, function (subfolder) { + folder.removeFolder(subfolder); + }); + Common.defer(function () { + _this.onResize(); + }); + }, + open: function open() { + this.closed = false; + }, + close: function close() { + this.closed = true; + }, + hide: function hide() { + this.domElement.style.display = 'none'; + }, + show: function show() { + this.domElement.style.display = ''; + }, + onResize: function onResize() { + var root = this.getRoot(); + if (root.scrollable) { + var top = dom.getOffset(root.__ul).top; + var h = 0; + Common.each(root.__ul.childNodes, function (node) { + if (!(root.autoPlace && node === root.__save_row)) { + h += dom.getHeight(node); + } + }); + if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) { + dom.addClass(root.domElement, GUI.CLASS_TOO_TALL); + root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px'; + } else { + dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL); + root.__ul.style.height = 'auto'; + } + } + if (root.__resize_handle) { + Common.defer(function () { + root.__resize_handle.style.height = root.__ul.offsetHeight + 'px'; + }); + } + if (root.__closeButton) { + root.__closeButton.style.width = root.width + 'px'; + } + }, + onResizeDebounced: Common.debounce(function () { + this.onResize(); + }, 50), + remember: function remember() { + if (Common.isUndefined(SAVE_DIALOGUE)) { + SAVE_DIALOGUE = new CenteredDiv(); + SAVE_DIALOGUE.domElement.innerHTML = saveDialogContents; + } + if (this.parent) { + throw new Error('You can only call remember on a top level GUI.'); + } + var _this = this; + Common.each(Array.prototype.slice.call(arguments), function (object) { + if (_this.__rememberedObjects.length === 0) { + addSaveMenu(_this); + } + if (_this.__rememberedObjects.indexOf(object) === -1) { + _this.__rememberedObjects.push(object); + } + }); + if (this.autoPlace) { + setWidth(this, this.width); + } + }, + getRoot: function getRoot() { + var gui = this; + while (gui.parent) { + gui = gui.parent; + } + return gui; + }, + getSaveObject: function getSaveObject() { + var toReturn = this.load; + toReturn.closed = this.closed; + if (this.__rememberedObjects.length > 0) { + toReturn.preset = this.preset; + if (!toReturn.remembered) { + toReturn.remembered = {}; + } + toReturn.remembered[this.preset] = getCurrentPreset(this); + } + toReturn.folders = {}; + Common.each(this.__folders, function (element, key) { + toReturn.folders[key] = element.getSaveObject(); + }); + return toReturn; + }, + save: function save() { + if (!this.load.remembered) { + this.load.remembered = {}; + } + this.load.remembered[this.preset] = getCurrentPreset(this); + markPresetModified(this, false); + this.saveToLocalStorageIfPossible(); + }, + saveAs: function saveAs(presetName) { + if (!this.load.remembered) { + this.load.remembered = {}; + this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true); + } + this.load.remembered[presetName] = getCurrentPreset(this); + this.preset = presetName; + addPresetOption(this, presetName, true); + this.saveToLocalStorageIfPossible(); + }, + revert: function revert(gui) { + Common.each(this.__controllers, function (controller) { + if (!this.getRoot().load.remembered) { + controller.setValue(controller.initialValue); + } else { + recallSavedValue(gui || this.getRoot(), controller); + } + if (controller.__onFinishChange) { + controller.__onFinishChange.call(controller, controller.getValue()); + } + }, this); + Common.each(this.__folders, function (folder) { + folder.revert(folder); + }); + if (!gui) { + markPresetModified(this.getRoot(), false); + } + }, + listen: function listen(controller) { + var init = this.__listening.length === 0; + this.__listening.push(controller); + if (init) { + updateDisplays(this.__listening); + } + }, + updateDisplay: function updateDisplay() { + Common.each(this.__controllers, function (controller) { + controller.updateDisplay(); + }); + Common.each(this.__folders, function (folder) { + folder.updateDisplay(); + }); + } +}); +function addRow(gui, newDom, liBefore) { + var li = document.createElement('li'); + if (newDom) { + li.appendChild(newDom); + } + if (liBefore) { + gui.__ul.insertBefore(li, liBefore); + } else { + gui.__ul.appendChild(li); + } + gui.onResize(); + return li; +} +function removeListeners(gui) { + dom.unbind(window, 'resize', gui.__resizeHandler); + if (gui.saveToLocalStorageIfPossible) { + dom.unbind(window, 'unload', gui.saveToLocalStorageIfPossible); + } +} +function markPresetModified(gui, modified) { + var opt = gui.__preset_select[gui.__preset_select.selectedIndex]; + if (modified) { + opt.innerHTML = opt.value + '*'; + } else { + opt.innerHTML = opt.value; + } +} +function augmentController(gui, li, controller) { + controller.__li = li; + controller.__gui = gui; + Common.extend(controller, { + options: function options(_options) { + if (arguments.length > 1) { + var nextSibling = controller.__li.nextElementSibling; + controller.remove(); + return _add(gui, controller.object, controller.property, { + before: nextSibling, + factoryArgs: [Common.toArray(arguments)] + }); + } + if (Common.isArray(_options) || Common.isObject(_options)) { + var _nextSibling = controller.__li.nextElementSibling; + controller.remove(); + return _add(gui, controller.object, controller.property, { + before: _nextSibling, + factoryArgs: [_options] + }); + } + }, + name: function name(_name) { + controller.__li.firstElementChild.firstElementChild.innerHTML = _name; + return controller; + }, + listen: function listen() { + controller.__gui.listen(controller); + return controller; + }, + remove: function remove() { + controller.__gui.remove(controller); + return controller; + } + }); + if (controller instanceof NumberControllerSlider) { + var box = new NumberControllerBox(controller.object, controller.property, { min: controller.__min, max: controller.__max, step: controller.__step }); + Common.each(['updateDisplay', 'onChange', 'onFinishChange', 'step', 'min', 'max'], function (method) { + var pc = controller[method]; + var pb = box[method]; + controller[method] = box[method] = function () { + var args = Array.prototype.slice.call(arguments); + pb.apply(box, args); + return pc.apply(controller, args); + }; + }); + dom.addClass(li, 'has-slider'); + controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild); + } else if (controller instanceof NumberControllerBox) { + var r = function r(returned) { + if (Common.isNumber(controller.__min) && Common.isNumber(controller.__max)) { + var oldName = controller.__li.firstElementChild.firstElementChild.innerHTML; + var wasListening = controller.__gui.__listening.indexOf(controller) > -1; + controller.remove(); + var newController = _add(gui, controller.object, controller.property, { + before: controller.__li.nextElementSibling, + factoryArgs: [controller.__min, controller.__max, controller.__step] + }); + newController.name(oldName); + if (wasListening) newController.listen(); + return newController; + } + return returned; + }; + controller.min = Common.compose(r, controller.min); + controller.max = Common.compose(r, controller.max); + } else if (controller instanceof BooleanController) { + dom.bind(li, 'click', function () { + dom.fakeEvent(controller.__checkbox, 'click'); + }); + dom.bind(controller.__checkbox, 'click', function (e) { + e.stopPropagation(); + }); + } else if (controller instanceof FunctionController) { + dom.bind(li, 'click', function () { + dom.fakeEvent(controller.__button, 'click'); + }); + dom.bind(li, 'mouseover', function () { + dom.addClass(controller.__button, 'hover'); + }); + dom.bind(li, 'mouseout', function () { + dom.removeClass(controller.__button, 'hover'); + }); + } else if (controller instanceof ColorController) { + dom.addClass(li, 'color'); + controller.updateDisplay = Common.compose(function (val) { + li.style.borderLeftColor = controller.__color.toString(); + return val; + }, controller.updateDisplay); + controller.updateDisplay(); + } + controller.setValue = Common.compose(function (val) { + if (gui.getRoot().__preset_select && controller.isModified()) { + markPresetModified(gui.getRoot(), true); + } + return val; + }, controller.setValue); +} +function recallSavedValue(gui, controller) { + var root = gui.getRoot(); + var matchedIndex = root.__rememberedObjects.indexOf(controller.object); + if (matchedIndex !== -1) { + var controllerMap = root.__rememberedObjectIndecesToControllers[matchedIndex]; + if (controllerMap === undefined) { + controllerMap = {}; + root.__rememberedObjectIndecesToControllers[matchedIndex] = controllerMap; + } + controllerMap[controller.property] = controller; + if (root.load && root.load.remembered) { + var presetMap = root.load.remembered; + var preset = void 0; + if (presetMap[gui.preset]) { + preset = presetMap[gui.preset]; + } else if (presetMap[DEFAULT_DEFAULT_PRESET_NAME]) { + preset = presetMap[DEFAULT_DEFAULT_PRESET_NAME]; + } else { + return; + } + if (preset[matchedIndex] && preset[matchedIndex][controller.property] !== undefined) { + var value = preset[matchedIndex][controller.property]; + controller.initialValue = value; + controller.setValue(value); + } + } + } +} +function _add(gui, object, property, params) { + if (object[property] === undefined) { + throw new Error('Object "' + object + '" has no property "' + property + '"'); + } + var controller = void 0; + if (params.color) { + controller = new ColorController(object, property); + } else { + var factoryArgs = [object, property].concat(params.factoryArgs); + controller = ControllerFactory.apply(gui, factoryArgs); + } + if (params.before instanceof Controller) { + params.before = params.before.__li; + } + recallSavedValue(gui, controller); + dom.addClass(controller.domElement, 'c'); + var name = document.createElement('span'); + dom.addClass(name, 'property-name'); + name.innerHTML = controller.property; + var container = document.createElement('div'); + container.appendChild(name); + container.appendChild(controller.domElement); + var li = addRow(gui, container, params.before); + dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); + if (controller instanceof ColorController) { + dom.addClass(li, 'color'); + } else { + dom.addClass(li, _typeof(controller.getValue())); + } + augmentController(gui, li, controller); + gui.__controllers.push(controller); + return controller; +} +function getLocalStorageHash(gui, key) { + return document.location.href + '.' + key; +} +function addPresetOption(gui, name, setSelected) { + var opt = document.createElement('option'); + opt.innerHTML = name; + opt.value = name; + gui.__preset_select.appendChild(opt); + if (setSelected) { + gui.__preset_select.selectedIndex = gui.__preset_select.length - 1; + } +} +function showHideExplain(gui, explain) { + explain.style.display = gui.useLocalStorage ? 'block' : 'none'; +} +function addSaveMenu(gui) { + var div = gui.__save_row = document.createElement('li'); + dom.addClass(gui.domElement, 'has-save'); + gui.__ul.insertBefore(div, gui.__ul.firstChild); + dom.addClass(div, 'save-row'); + var gears = document.createElement('span'); + gears.innerHTML = ' '; + dom.addClass(gears, 'button gears'); + var button = document.createElement('span'); + button.innerHTML = 'Save'; + dom.addClass(button, 'button'); + dom.addClass(button, 'save'); + var button2 = document.createElement('span'); + button2.innerHTML = 'New'; + dom.addClass(button2, 'button'); + dom.addClass(button2, 'save-as'); + var button3 = document.createElement('span'); + button3.innerHTML = 'Revert'; + dom.addClass(button3, 'button'); + dom.addClass(button3, 'revert'); + var select = gui.__preset_select = document.createElement('select'); + if (gui.load && gui.load.remembered) { + Common.each(gui.load.remembered, function (value, key) { + addPresetOption(gui, key, key === gui.preset); + }); + } else { + addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false); + } + dom.bind(select, 'change', function () { + for (var index = 0; index < gui.__preset_select.length; index++) { + gui.__preset_select[index].innerHTML = gui.__preset_select[index].value; + } + gui.preset = this.value; + }); + div.appendChild(select); + div.appendChild(gears); + div.appendChild(button); + div.appendChild(button2); + div.appendChild(button3); + if (SUPPORTS_LOCAL_STORAGE) { + var explain = document.getElementById('dg-local-explain'); + var localStorageCheckBox = document.getElementById('dg-local-storage'); + var saveLocally = document.getElementById('dg-save-locally'); + saveLocally.style.display = 'block'; + if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') { + localStorageCheckBox.setAttribute('checked', 'checked'); + } + showHideExplain(gui, explain); + dom.bind(localStorageCheckBox, 'change', function () { + gui.useLocalStorage = !gui.useLocalStorage; + showHideExplain(gui, explain); + }); + } + var newConstructorTextArea = document.getElementById('dg-new-constructor'); + dom.bind(newConstructorTextArea, 'keydown', function (e) { + if (e.metaKey && (e.which === 67 || e.keyCode === 67)) { + SAVE_DIALOGUE.hide(); + } + }); + dom.bind(gears, 'click', function () { + newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2); + SAVE_DIALOGUE.show(); + newConstructorTextArea.focus(); + newConstructorTextArea.select(); + }); + dom.bind(button, 'click', function () { + gui.save(); + }); + dom.bind(button2, 'click', function () { + var presetName = prompt('Enter a new preset name.'); + if (presetName) { + gui.saveAs(presetName); + } + }); + dom.bind(button3, 'click', function () { + gui.revert(); + }); +} +function addResizeHandle(gui) { + var pmouseX = void 0; + gui.__resize_handle = document.createElement('div'); + Common.extend(gui.__resize_handle.style, { + width: '6px', + marginLeft: '-3px', + height: '200px', + cursor: 'ew-resize', + position: 'absolute' + }); + function drag(e) { + e.preventDefault(); + gui.width += pmouseX - e.clientX; + gui.onResize(); + pmouseX = e.clientX; + return false; + } + function dragStop() { + dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG); + dom.unbind(window, 'mousemove', drag); + dom.unbind(window, 'mouseup', dragStop); + } + function dragStart(e) { + e.preventDefault(); + pmouseX = e.clientX; + dom.addClass(gui.__closeButton, GUI.CLASS_DRAG); + dom.bind(window, 'mousemove', drag); + dom.bind(window, 'mouseup', dragStop); + return false; + } + dom.bind(gui.__resize_handle, 'mousedown', dragStart); + dom.bind(gui.__closeButton, 'mousedown', dragStart); + gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild); +} +function setWidth(gui, w) { + gui.domElement.style.width = w + 'px'; + if (gui.__save_row && gui.autoPlace) { + gui.__save_row.style.width = w + 'px'; + } + if (gui.__closeButton) { + gui.__closeButton.style.width = w + 'px'; + } +} +function getCurrentPreset(gui, useInitialValues) { + var toReturn = {}; + Common.each(gui.__rememberedObjects, function (val, index) { + var savedValues = {}; + var controllerMap = gui.__rememberedObjectIndecesToControllers[index]; + Common.each(controllerMap, function (controller, property) { + savedValues[property] = useInitialValues ? controller.initialValue : controller.getValue(); + }); + toReturn[index] = savedValues; + }); + return toReturn; +} +function setPresetSelectIndex(gui) { + for (var index = 0; index < gui.__preset_select.length; index++) { + if (gui.__preset_select[index].value === gui.preset) { + gui.__preset_select.selectedIndex = index; + } + } +} +function updateDisplays(controllerArray) { + if (controllerArray.length !== 0) { + requestAnimationFrame$1.call(window, function () { + updateDisplays(controllerArray); + }); + } + Common.each(controllerArray, function (c) { + c.updateDisplay(); + }); +} + +var color = { + Color: Color, + math: ColorMath, + interpret: interpret +}; +var controllers = { + Controller: Controller, + BooleanController: BooleanController, + OptionController: OptionController, + StringController: StringController, + NumberController: NumberController, + NumberControllerBox: NumberControllerBox, + NumberControllerSlider: NumberControllerSlider, + FunctionController: FunctionController, + ColorController: ColorController +}; +var dom$1 = { dom: dom }; +var gui = { GUI: GUI }; +var GUI$1 = GUI; +var index = { + color: color, + controllers: controllers, + dom: dom$1, + gui: gui, + GUI: GUI$1 +}; + +exports.color = color; +exports.controllers = controllers; +exports.dom = dom$1; +exports.gui = gui; +exports.GUI = GUI$1; +exports['default'] = index; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); +//# sourceMappingURL=dat.gui.js.map \ No newline at end of file diff --git a/hw0/lib/imgui.umd.js b/hw0/lib/imgui.umd.js new file mode 100644 index 0000000..dbd71a1 Binary files /dev/null and b/hw0/lib/imgui.umd.js differ diff --git a/hw0/lib/imgui_impl.umd.js b/hw0/lib/imgui_impl.umd.js new file mode 100644 index 0000000..983618b --- /dev/null +++ b/hw0/lib/imgui_impl.umd.js @@ -0,0 +1,816 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('imgui-js')) : + typeof define === 'function' && define.amd ? define(['exports', 'imgui-js'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ImGui_Impl = {}, global.ImGui)); +}(this, (function (exports, ImGui) { 'use strict'; + + let clipboard_text = ""; + let canvas = null; + exports.gl = null; + let g_ShaderHandle = null; + let g_VertHandle = null; + let g_FragHandle = null; + let g_AttribLocationTex = null; + let g_AttribLocationProjMtx = null; + let g_AttribLocationPosition = -1; + let g_AttribLocationUV = -1; + let g_AttribLocationColor = -1; + let g_VboHandle = null; + let g_ElementsHandle = null; + let g_FontTexture = null; + exports.ctx = null; + let prev_time = 0; + function document_on_copy(event) { + if (event.clipboardData) { + event.clipboardData.setData("text/plain", clipboard_text); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function document_on_cut(event) { + if (event.clipboardData) { + event.clipboardData.setData("text/plain", clipboard_text); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function document_on_paste(event) { + if (event.clipboardData) { + clipboard_text = event.clipboardData.getData("text/plain"); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function window_on_resize() { + if (canvas !== null) { + const devicePixelRatio = window.devicePixelRatio || 1; + canvas.width = Math.floor(canvas.scrollWidth * devicePixelRatio); + canvas.height = Math.floor(canvas.scrollHeight * devicePixelRatio); + } + } + function window_on_gamepadconnected(event /* GamepadEvent */) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", event.gamepad.index, event.gamepad.id, event.gamepad.buttons.length, event.gamepad.axes.length); + } + function window_on_gamepaddisconnected(event /* GamepadEvent */) { + console.log("Gamepad disconnected at index %d: %s.", event.gamepad.index, event.gamepad.id); + } + function canvas_on_blur(event) { + const io = ImGui.GetIO(); + io.KeyCtrl = false; + io.KeyShift = false; + io.KeyAlt = false; + io.KeySuper = false; + for (let i = 0; i < io.KeysDown.length; ++i) { + io.KeysDown[i] = false; + } + for (let i = 0; i < io.MouseDown.length; ++i) { + io.MouseDown[i] = false; + } + } + const key_code_to_index = { + "NumpadEnter": 176, + }; + function canvas_on_keydown(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.KeyCtrl = event.ctrlKey; + io.KeyShift = event.shiftKey; + io.KeyAlt = event.altKey; + io.KeySuper = event.metaKey; + const key_index = key_code_to_index[event.code] || event.keyCode; + ImGui.ASSERT(key_index >= 0 && key_index < ImGui.ARRAYSIZE(io.KeysDown)); + io.KeysDown[key_index] = true; + // forward to the keypress event + if ( /*io.WantCaptureKeyboard ||*/event.key === "Tab") { + event.preventDefault(); + } + } + function canvas_on_keyup(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.KeyCtrl = event.ctrlKey; + io.KeyShift = event.shiftKey; + io.KeyAlt = event.altKey; + io.KeySuper = event.metaKey; + const key_index = key_code_to_index[event.code] || event.keyCode; + ImGui.ASSERT(key_index >= 0 && key_index < ImGui.ARRAYSIZE(io.KeysDown)); + io.KeysDown[key_index] = false; + if (io.WantCaptureKeyboard) { + event.preventDefault(); + } + } + function canvas_on_keypress(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.AddInputCharacter(event.charCode); + if (io.WantCaptureKeyboard) { + event.preventDefault(); + } + } + function canvas_on_pointermove(event) { + const io = ImGui.GetIO(); + io.MousePos.x = event.offsetX; + io.MousePos.y = event.offsetY; + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + // MouseEvent.button + // A number representing a given button: + // 0: Main button pressed, usually the left button or the un-initialized state + // 1: Auxiliary button pressed, usually the wheel button or the middle button (if present) + // 2: Secondary button pressed, usually the right button + // 3: Fourth button, typically the Browser Back button + // 4: Fifth button, typically the Browser Forward button + const mouse_button_map = [0, 2, 1, 3, 4]; + function canvas_on_pointerdown(event) { + const io = ImGui.GetIO(); + io.MousePos.x = event.offsetX; + io.MousePos.y = event.offsetY; + io.MouseDown[mouse_button_map[event.button]] = true; + // if (io.WantCaptureMouse) { + // event.preventDefault(); + // } + } + function canvas_on_contextmenu(event) { + const io = ImGui.GetIO(); + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function canvas_on_pointerup(event) { + const io = ImGui.GetIO(); + io.MouseDown[mouse_button_map[event.button]] = false; + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function canvas_on_wheel(event) { + const io = ImGui.GetIO(); + let scale = 1.0; + switch (event.deltaMode) { + case event.DOM_DELTA_PIXEL: + scale = 0.01; + break; + case event.DOM_DELTA_LINE: + scale = 0.2; + break; + case event.DOM_DELTA_PAGE: + scale = 1.0; + break; + } + io.MouseWheelH = event.deltaX * scale; + io.MouseWheel = -event.deltaY * scale; // Mouse wheel: 1 unit scrolls about 5 lines text. + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function Init(value) { + const io = ImGui.GetIO(); + if (typeof (window) !== "undefined") { + io.BackendPlatformName = "imgui_impl_browser"; + ImGui.LoadIniSettingsFromMemory(window.localStorage.getItem("imgui.ini") || ""); + } + else { + io.BackendPlatformName = "imgui_impl_console"; + } + if (typeof (navigator) !== "undefined") { + io.ConfigMacOSXBehaviors = navigator.platform.match(/Mac/) !== null; + } + if (typeof (document) !== "undefined") { + document.body.addEventListener("copy", document_on_copy); + document.body.addEventListener("cut", document_on_cut); + document.body.addEventListener("paste", document_on_paste); + } + io.SetClipboardTextFn = (user_data, text) => { + clipboard_text = text; + // console.log(`set clipboard_text: "${clipboard_text}"`); + if (typeof navigator !== "undefined" && typeof navigator.clipboard !== "undefined") { + // console.log(`clipboard.writeText: "${clipboard_text}"`); + navigator.clipboard.writeText(clipboard_text).then(() => { + // console.log(`clipboard.writeText: "${clipboard_text}" done.`); + }); + } + }; + io.GetClipboardTextFn = (user_data) => { + // if (typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined") { + // console.log(`clipboard.readText: "${clipboard_text}"`); + // (navigator as any).clipboard.readText().then((text: string): void => { + // clipboard_text = text; + // console.log(`clipboard.readText: "${clipboard_text}" done.`); + // }); + // } + // console.log(`get clipboard_text: "${clipboard_text}"`); + return clipboard_text; + }; + io.ClipboardUserData = null; + if (typeof (window) !== "undefined") { + window.addEventListener("resize", window_on_resize); + window.addEventListener("gamepadconnected", window_on_gamepadconnected); + window.addEventListener("gamepaddisconnected", window_on_gamepaddisconnected); + } + if (typeof (window) !== "undefined") { + if (value instanceof (HTMLCanvasElement)) { + canvas = value; + value = canvas.getContext("webgl2", { alpha: false }) || canvas.getContext("webgl", { alpha: false }) || canvas.getContext("2d"); + } + if (typeof WebGL2RenderingContext !== "undefined" && value instanceof (WebGL2RenderingContext)) { + io.BackendRendererName = "imgui_impl_webgl2"; + canvas = canvas || value.canvas; + exports.gl = value; + } + else if (typeof WebGLRenderingContext !== "undefined" && value instanceof (WebGLRenderingContext)) { + io.BackendRendererName = "imgui_impl_webgl"; + canvas = canvas || value.canvas; + exports.gl = value; + } + else if (typeof CanvasRenderingContext2D !== "undefined" && value instanceof (CanvasRenderingContext2D)) { + io.BackendRendererName = "imgui_impl_2d"; + canvas = canvas || value.canvas; + exports.ctx = value; + } + } + if (canvas !== null) { + window_on_resize(); + canvas.style.touchAction = "none"; // Disable browser handling of all panning and zooming gestures. + canvas.addEventListener("blur", canvas_on_blur); + canvas.addEventListener("keydown", canvas_on_keydown); + canvas.addEventListener("keyup", canvas_on_keyup); + canvas.addEventListener("keypress", canvas_on_keypress); + canvas.addEventListener("pointermove", canvas_on_pointermove); + canvas.addEventListener("pointerdown", canvas_on_pointerdown); + canvas.addEventListener("contextmenu", canvas_on_contextmenu); + canvas.addEventListener("pointerup", canvas_on_pointerup); + canvas.addEventListener("wheel", canvas_on_wheel); + } + // Setup back-end capabilities flags + io.BackendFlags |= ImGui.BackendFlags.HasMouseCursors; // We can honor GetMouseCursor() values (optional) + // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGui.Key.Tab] = 9; + io.KeyMap[ImGui.Key.LeftArrow] = 37; + io.KeyMap[ImGui.Key.RightArrow] = 39; + io.KeyMap[ImGui.Key.UpArrow] = 38; + io.KeyMap[ImGui.Key.DownArrow] = 40; + io.KeyMap[ImGui.Key.PageUp] = 33; + io.KeyMap[ImGui.Key.PageDown] = 34; + io.KeyMap[ImGui.Key.Home] = 36; + io.KeyMap[ImGui.Key.End] = 35; + io.KeyMap[ImGui.Key.Insert] = 45; + io.KeyMap[ImGui.Key.Delete] = 46; + io.KeyMap[ImGui.Key.Backspace] = 8; + io.KeyMap[ImGui.Key.Space] = 32; + io.KeyMap[ImGui.Key.Enter] = 13; + io.KeyMap[ImGui.Key.Escape] = 27; + io.KeyMap[ImGui.Key.KeyPadEnter] = key_code_to_index["NumpadEnter"]; + io.KeyMap[ImGui.Key.A] = 65; + io.KeyMap[ImGui.Key.C] = 67; + io.KeyMap[ImGui.Key.V] = 86; + io.KeyMap[ImGui.Key.X] = 88; + io.KeyMap[ImGui.Key.Y] = 89; + io.KeyMap[ImGui.Key.Z] = 90; + CreateDeviceObjects(); + } + function Shutdown() { + DestroyDeviceObjects(); + if (canvas !== null) { + canvas.removeEventListener("blur", canvas_on_blur); + canvas.removeEventListener("keydown", canvas_on_keydown); + canvas.removeEventListener("keyup", canvas_on_keyup); + canvas.removeEventListener("keypress", canvas_on_keypress); + canvas.removeEventListener("pointermove", canvas_on_pointermove); + canvas.removeEventListener("pointerdown", canvas_on_pointerdown); + canvas.removeEventListener("contextmenu", canvas_on_contextmenu); + canvas.removeEventListener("pointerup", canvas_on_pointerup); + canvas.removeEventListener("wheel", canvas_on_wheel); + } + exports.gl = null; + exports.ctx = null; + canvas = null; + if (typeof (window) !== "undefined") { + window.removeEventListener("resize", window_on_resize); + window.removeEventListener("gamepadconnected", window_on_gamepadconnected); + window.removeEventListener("gamepaddisconnected", window_on_gamepaddisconnected); + } + if (typeof (document) !== "undefined") { + document.body.removeEventListener("copy", document_on_copy); + document.body.removeEventListener("cut", document_on_cut); + document.body.removeEventListener("paste", document_on_paste); + } + } + function NewFrame(time) { + const io = ImGui.GetIO(); + if (io.WantSaveIniSettings) { + io.WantSaveIniSettings = false; + if (typeof (window) !== "undefined") { + window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); + } + } + const w = canvas && canvas.scrollWidth || 640; + const h = canvas && canvas.scrollHeight || 480; + const display_w = exports.gl && exports.gl.drawingBufferWidth || w; + const display_h = exports.gl && exports.gl.drawingBufferHeight || h; + io.DisplaySize.x = w; + io.DisplaySize.y = h; + io.DisplayFramebufferScale.x = w > 0 ? (display_w / w) : 0; + io.DisplayFramebufferScale.y = h > 0 ? (display_h / h) : 0; + const dt = time - prev_time; + prev_time = time; + io.DeltaTime = dt / 1000; + if (io.WantSetMousePos) { + console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); + } + if (typeof (document) !== "undefined") { + if (io.MouseDrawCursor) { + document.body.style.cursor = "none"; + } + else { + switch (ImGui.GetMouseCursor()) { + case ImGui.MouseCursor.None: + document.body.style.cursor = "none"; + break; + default: + case ImGui.MouseCursor.Arrow: + document.body.style.cursor = "default"; + break; + case ImGui.MouseCursor.TextInput: + document.body.style.cursor = "text"; + break; // When hovering over InputText, etc. + case ImGui.MouseCursor.ResizeAll: + document.body.style.cursor = "all-scroll"; + break; // Unused + case ImGui.MouseCursor.ResizeNS: + document.body.style.cursor = "ns-resize"; + break; // When hovering over an horizontal border + case ImGui.MouseCursor.ResizeEW: + document.body.style.cursor = "ew-resize"; + break; // When hovering over a vertical border or a column + case ImGui.MouseCursor.ResizeNESW: + document.body.style.cursor = "nesw-resize"; + break; // When hovering over the bottom-left corner of a window + case ImGui.MouseCursor.ResizeNWSE: + document.body.style.cursor = "nwse-resize"; + break; // When hovering over the bottom-right corner of a window + case ImGui.MouseCursor.Hand: + document.body.style.cursor = "move"; + break; + case ImGui.MouseCursor.NotAllowed: + document.body.style.cursor = "not-allowed"; + break; + } + } + } + // Gamepad navigation mapping [BETA] + for (let i = 0; i < io.NavInputs.length; ++i) { + // TODO: This is currently causing an issue and I have no gamepad to test with. + // The error is: ''set' on proxy: trap returned falsish for property '21' + // I think that the NavInputs are zeroed out by ImGui at the start of each frame anyway + // so I am not sure if the following is even necessary. + //io.NavInputs[i] = 0.0; + } + if (io.ConfigFlags & ImGui.ConfigFlags.NavEnableGamepad) { + // Update gamepad inputs + const gamepads = (typeof (navigator) !== "undefined" && typeof (navigator.getGamepads) === "function") ? navigator.getGamepads() : []; + for (let i = 0; i < gamepads.length; ++i) { + const gamepad = gamepads[i]; + if (!gamepad) { + continue; + } + io.BackendFlags |= ImGui.BackendFlags.HasGamepad; + const buttons_count = gamepad.buttons.length; + const axes_count = gamepad.axes.length; + function MAP_BUTTON(NAV_NO, BUTTON_NO) { + if (!gamepad) { + return; + } + if (buttons_count > BUTTON_NO && gamepad.buttons[BUTTON_NO].pressed) + io.NavInputs[NAV_NO] = 1.0; + } + function MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { + if (!gamepad) { + return; + } + let v = (axes_count > AXIS_NO) ? gamepad.axes[AXIS_NO] : V0; + v = (v - V0) / (V1 - V0); + if (v > 1.0) + v = 1.0; + if (io.NavInputs[NAV_NO] < v) + io.NavInputs[NAV_NO] = v; + } + // TODO: map input based on vendor and product id + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id + const match = gamepad.id.match(/^([0-9a-f]{4})-([0-9a-f]{4})-.*$/); + const match_chrome = gamepad.id.match(/^.*\(.*Vendor: ([0-9a-f]{4}) Product: ([0-9a-f]{4})\).*$/); + const vendor = (match && match[1]) || (match_chrome && match_chrome[1]) || "0000"; + const product = (match && match[2]) || (match_chrome && match_chrome[2]) || "0000"; + switch (vendor + product) { + case "046dc216": // Logitech Logitech Dual Action (Vendor: 046d Product: c216) + MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 2); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 0); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_ANALOG(ImGui.NavInput.DpadLeft, 4, -0.3, -0.9); // D-Pad Left + MAP_ANALOG(ImGui.NavInput.DpadRight, 4, +0.3, +0.9); // D-Pad Right + MAP_ANALOG(ImGui.NavInput.DpadUp, 5, -0.3, -0.9); // D-Pad Up + MAP_ANALOG(ImGui.NavInput.DpadDown, 5, +0.3, +0.9); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + case "046dc21d": // Logitech Gamepad F310 (STANDARD GAMEPAD Vendor: 046d Product: c21d) + MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left + MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right + MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up + MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_ANALOG(ImGui.NavInput.TweakSlow, 6, +0.3, +0.9); // L2 / LT + MAP_ANALOG(ImGui.NavInput.TweakFast, 7, +0.3, +0.9); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + case "2dc86001": // 8Bitdo SN30 Pro 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6001) + case "2dc86101": // 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6101) + MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 0); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 4); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_ANALOG(ImGui.NavInput.DpadLeft, 6, -0.3, -0.9); // D-Pad Left + MAP_ANALOG(ImGui.NavInput.DpadRight, 6, +0.3, +0.9); // D-Pad Right + MAP_ANALOG(ImGui.NavInput.DpadUp, 7, -0.3, -0.9); // D-Pad Up + MAP_ANALOG(ImGui.NavInput.DpadDown, 7, +0.3, +0.9); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 6); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 7); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 8); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 9); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + default: // standard gamepad: https://w3c.github.io/gamepad/#remapping + MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left + MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right + MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up + MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + } + } + } + } + function RenderDrawData(draw_data = ImGui.GetDrawData()) { + const io = ImGui.GetIO(); + if (draw_data === null) { + throw new Error(); + } + exports.gl || exports.ctx || console.log(draw_data); + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + const fb_width = io.DisplaySize.x * io.DisplayFramebufferScale.x; + const fb_height = io.DisplaySize.y * io.DisplayFramebufferScale.y; + if (fb_width === 0 || fb_height === 0) { + return; + } + draw_data.ScaleClipRects(io.DisplayFramebufferScale); + const gl2 = typeof WebGL2RenderingContext !== "undefined" && exports.gl instanceof WebGL2RenderingContext && exports.gl || null; + const gl_vao = exports.gl && exports.gl.getExtension("OES_vertex_array_object") || null; + // Backup GL state + const last_active_texture = exports.gl && exports.gl.getParameter(exports.gl.ACTIVE_TEXTURE) || null; + const last_program = exports.gl && exports.gl.getParameter(exports.gl.CURRENT_PROGRAM) || null; + const last_texture = exports.gl && exports.gl.getParameter(exports.gl.TEXTURE_BINDING_2D) || null; + const last_array_buffer = exports.gl && exports.gl.getParameter(exports.gl.ARRAY_BUFFER_BINDING) || null; + const last_element_array_buffer = exports.gl && exports.gl.getParameter(exports.gl.ELEMENT_ARRAY_BUFFER_BINDING) || null; + const last_vertex_array_object = gl2 && gl2.getParameter(gl2.VERTEX_ARRAY_BINDING) || exports.gl && gl_vao && exports.gl.getParameter(gl_vao.VERTEX_ARRAY_BINDING_OES) || null; + // GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + const last_viewport = exports.gl && exports.gl.getParameter(exports.gl.VIEWPORT) || null; + const last_scissor_box = exports.gl && exports.gl.getParameter(exports.gl.SCISSOR_BOX) || null; + const last_blend_src_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_SRC_RGB) || null; + const last_blend_dst_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_DST_RGB) || null; + const last_blend_src_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_SRC_ALPHA) || null; + const last_blend_dst_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_DST_ALPHA) || null; + const last_blend_equation_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_EQUATION_RGB) || null; + const last_blend_equation_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_EQUATION_ALPHA) || null; + const last_enable_blend = exports.gl && exports.gl.getParameter(exports.gl.BLEND) || null; + const last_enable_cull_face = exports.gl && exports.gl.getParameter(exports.gl.CULL_FACE) || null; + const last_enable_depth_test = exports.gl && exports.gl.getParameter(exports.gl.DEPTH_TEST) || null; + const last_enable_scissor_test = exports.gl && exports.gl.getParameter(exports.gl.SCISSOR_TEST) || null; + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + const vertex_array_object = gl2 && gl2.createVertexArray() || gl_vao && gl_vao.createVertexArrayOES(); + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + exports.gl && exports.gl.enable(exports.gl.BLEND); + exports.gl && exports.gl.blendEquation(exports.gl.FUNC_ADD); + exports.gl && exports.gl.blendFunc(exports.gl.SRC_ALPHA, exports.gl.ONE_MINUS_SRC_ALPHA); + exports.gl && exports.gl.disable(exports.gl.CULL_FACE); + exports.gl && exports.gl.disable(exports.gl.DEPTH_TEST); + exports.gl && exports.gl.enable(exports.gl.SCISSOR_TEST); + // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + exports.gl && exports.gl.viewport(0, 0, fb_width, fb_height); + const L = draw_data.DisplayPos.x; + const R = draw_data.DisplayPos.x + draw_data.DisplaySize.x; + const T = draw_data.DisplayPos.y; + const B = draw_data.DisplayPos.y + draw_data.DisplaySize.y; + const ortho_projection = new Float32Array([ + 2.0 / (R - L), 0.0, 0.0, 0.0, + 0.0, 2.0 / (T - B), 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + (R + L) / (L - R), (T + B) / (B - T), 0.0, 1.0, + ]); + exports.gl && exports.gl.useProgram(g_ShaderHandle); + exports.gl && exports.gl.uniform1i(g_AttribLocationTex, 0); + exports.gl && g_AttribLocationProjMtx && exports.gl.uniformMatrix4fv(g_AttribLocationProjMtx, false, ortho_projection); + gl2 && gl2.bindVertexArray(vertex_array_object) || gl_vao && gl_vao.bindVertexArrayOES(vertex_array_object); + // Render command lists + exports.gl && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, g_VboHandle); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationPosition); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationUV); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationColor); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationPosition, 2, exports.gl.FLOAT, false, ImGui.DrawVertSize, ImGui.DrawVertPosOffset); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationUV, 2, exports.gl.FLOAT, false, ImGui.DrawVertSize, ImGui.DrawVertUVOffset); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationColor, 4, exports.gl.UNSIGNED_BYTE, true, ImGui.DrawVertSize, ImGui.DrawVertColOffset); + // Draw + const pos = draw_data.DisplayPos; + const idx_buffer_type = exports.gl && ((ImGui.DrawIdxSize === 4) ? exports.gl.UNSIGNED_INT : exports.gl.UNSIGNED_SHORT) || 0; + draw_data.IterateDrawLists((draw_list) => { + exports.gl || exports.ctx || console.log(draw_list); + exports.gl || exports.ctx || console.log("VtxBuffer.length", draw_list.VtxBuffer.length); + exports.gl || exports.ctx || console.log("IdxBuffer.length", draw_list.IdxBuffer.length); + let idx_buffer_offset = 0; + exports.gl && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, g_VboHandle); + exports.gl && exports.gl.bufferData(exports.gl.ARRAY_BUFFER, draw_list.VtxBuffer, exports.gl.STREAM_DRAW); + exports.gl && exports.gl.bindBuffer(exports.gl.ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + exports.gl && exports.gl.bufferData(exports.gl.ELEMENT_ARRAY_BUFFER, draw_list.IdxBuffer, exports.gl.STREAM_DRAW); + draw_list.IterateDrawCmds((draw_cmd) => { + exports.gl || exports.ctx || console.log(draw_cmd); + exports.gl || exports.ctx || console.log("ElemCount", draw_cmd.ElemCount); + exports.gl || exports.ctx || console.log("ClipRect", draw_cmd.ClipRect.x, fb_height - draw_cmd.ClipRect.w, draw_cmd.ClipRect.z - draw_cmd.ClipRect.x, draw_cmd.ClipRect.w - draw_cmd.ClipRect.y); + exports.gl || exports.ctx || console.log("TextureId", draw_cmd.TextureId); + if (!exports.gl && !exports.ctx) { + console.log("i: pos.x pos.y uv.x uv.y col"); + for (let i = 0; i < Math.min(3, draw_cmd.ElemCount); ++i) { + const view = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i * ImGui.DrawVertSize); + console.log(`${i}: ${view.pos[0].toFixed(2)} ${view.pos[1].toFixed(2)} ${view.uv[0].toFixed(5)} ${view.uv[1].toFixed(5)} ${("00000000" + view.col[0].toString(16)).substr(-8)}`); + } + } + if (draw_cmd.UserCallback !== null) { + // User callback (registered via ImDrawList::AddCallback) + draw_cmd.UserCallback(draw_list, draw_cmd); + } + else { + const clip_rect = new ImGui.Vec4(draw_cmd.ClipRect.x - pos.x, draw_cmd.ClipRect.y - pos.y, draw_cmd.ClipRect.z - pos.x, draw_cmd.ClipRect.w - pos.y); + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0 && clip_rect.w >= 0.0) { + // Apply scissor/clipping rectangle + exports.gl && exports.gl.scissor(clip_rect.x, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + // Bind texture, Draw + exports.gl && exports.gl.activeTexture(exports.gl.TEXTURE0); + exports.gl && exports.gl.bindTexture(exports.gl.TEXTURE_2D, draw_cmd.TextureId); + exports.gl && exports.gl.drawElements(exports.gl.TRIANGLES, draw_cmd.ElemCount, idx_buffer_type, idx_buffer_offset); + if (exports.ctx) { + exports.ctx.save(); + exports.ctx.beginPath(); + exports.ctx.rect(clip_rect.x, clip_rect.y, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + exports.ctx.clip(); + const idx = ImGui.DrawIdxSize === 4 ? + new Uint32Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset) : + new Uint16Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset); + for (let i = 0; i < draw_cmd.ElemCount; i += 3) { + const i0 = idx[i + 0]; + const i1 = idx[i + 1]; + const i2 = idx[i + 2]; + const v0 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i0 * ImGui.DrawVertSize); + const v1 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i1 * ImGui.DrawVertSize); + const v2 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i2 * ImGui.DrawVertSize); + const i3 = idx[i + 3]; + const i4 = idx[i + 4]; + const i5 = idx[i + 5]; + const v3 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i3 * ImGui.DrawVertSize); + const v4 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i4 * ImGui.DrawVertSize); + const v5 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i5 * ImGui.DrawVertSize); + let quad = true; + let minmin = v0; + let minmax = v0; + let maxmin = v0; + let maxmax = v0; + for (const v of [v1, v2, v3, v4, v5]) { + let found = false; + if (v.pos[0] <= minmin.pos[0] && v.pos[1] <= minmin.pos[1]) { + minmin = v; + found = true; + } + if (v.pos[0] <= minmax.pos[0] && v.pos[1] >= minmax.pos[1]) { + minmax = v; + found = true; + } + if (v.pos[0] >= maxmin.pos[0] && v.pos[1] <= maxmin.pos[1]) { + maxmin = v; + found = true; + } + if (v.pos[0] >= maxmax.pos[0] && v.pos[1] >= maxmax.pos[1]) { + maxmax = v; + found = true; + } + if (!found) { + quad = false; + } + } + quad = quad && (minmin.pos[0] === minmax.pos[0]); + quad = quad && (maxmin.pos[0] === maxmax.pos[0]); + quad = quad && (minmin.pos[1] === maxmin.pos[1]); + quad = quad && (minmax.pos[1] === maxmax.pos[1]); + if (quad) { + if (minmin.uv[0] === maxmax.uv[0] || minmin.uv[1] === maxmax.uv[1]) { + // one vertex color + exports.ctx.beginPath(); + exports.ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + exports.ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; + exports.ctx.fill(); + } + else { + // no vertex color + const image = draw_cmd.TextureId; // HACK + const width = image instanceof HTMLVideoElement ? image.videoWidth : image.width; + const height = image instanceof HTMLVideoElement ? image.videoHeight : image.height; + image && exports.ctx.drawImage(image, minmin.uv[0] * width, minmin.uv[1] * height, (maxmax.uv[0] - minmin.uv[0]) * width, (maxmax.uv[1] - minmin.uv[1]) * height, minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + // ctx.beginPath(); + // ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + // ctx.strokeStyle = "yellow"; + // ctx.stroke(); + } + i += 3; + } + else { + // one vertex color, no texture + exports.ctx.beginPath(); + exports.ctx.moveTo(v0.pos[0], v0.pos[1]); + exports.ctx.lineTo(v1.pos[0], v1.pos[1]); + exports.ctx.lineTo(v2.pos[0], v2.pos[1]); + exports.ctx.closePath(); + exports.ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; + exports.ctx.fill(); + } + } + exports.ctx.restore(); + } + } + } + idx_buffer_offset += draw_cmd.ElemCount * ImGui.DrawIdxSize; + }); + }); + // Destroy the temporary VAO + gl2 && gl2.deleteVertexArray(vertex_array_object) || gl_vao && gl_vao.deleteVertexArrayOES(vertex_array_object); + // Restore modified GL state + exports.gl && (last_program !== null) && exports.gl.useProgram(last_program); + exports.gl && (last_texture !== null) && exports.gl.bindTexture(exports.gl.TEXTURE_2D, last_texture); + exports.gl && (last_active_texture !== null) && exports.gl.activeTexture(last_active_texture); + gl2 && gl2.bindVertexArray(last_vertex_array_object) || gl_vao && gl_vao.bindVertexArrayOES(last_vertex_array_object); + exports.gl && (last_array_buffer !== null) && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, last_array_buffer); + exports.gl && (last_element_array_buffer !== null) && exports.gl.bindBuffer(exports.gl.ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + exports.gl && (last_blend_equation_rgb !== null && last_blend_equation_alpha !== null) && exports.gl.blendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + exports.gl && (last_blend_src_rgb !== null && last_blend_src_alpha !== null && last_blend_dst_rgb !== null && last_blend_dst_alpha !== null) && exports.gl.blendFuncSeparate(last_blend_src_rgb, last_blend_src_alpha, last_blend_dst_rgb, last_blend_dst_alpha); + exports.gl && (last_enable_blend ? exports.gl.enable(exports.gl.BLEND) : exports.gl.disable(exports.gl.BLEND)); + exports.gl && (last_enable_cull_face ? exports.gl.enable(exports.gl.CULL_FACE) : exports.gl.disable(exports.gl.CULL_FACE)); + exports.gl && (last_enable_depth_test ? exports.gl.enable(exports.gl.DEPTH_TEST) : exports.gl.disable(exports.gl.DEPTH_TEST)); + exports.gl && (last_enable_scissor_test ? exports.gl.enable(exports.gl.SCISSOR_TEST) : exports.gl.disable(exports.gl.SCISSOR_TEST)); + // glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + exports.gl && (last_viewport !== null) && exports.gl.viewport(last_viewport[0], last_viewport[1], last_viewport[2], last_viewport[3]); + exports.gl && (last_scissor_box !== null) && exports.gl.scissor(last_scissor_box[0], last_scissor_box[1], last_scissor_box[2], last_scissor_box[3]); + } + function CreateFontsTexture() { + const io = ImGui.GetIO(); + // Backup GL state + const last_texture = exports.gl && exports.gl.getParameter(exports.gl.TEXTURE_BINDING_2D); + // Build texture atlas + // const width: number = 256; + // const height: number = 256; + // const pixels: Uint8Array = new Uint8Array(4 * width * height).fill(0xff); + const { width, height, pixels } = io.Fonts.GetTexDataAsRGBA32(); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + // console.log(`font texture ${width} x ${height} @ ${pixels.length}`); + // Upload texture to graphics system + g_FontTexture = exports.gl && exports.gl.createTexture(); + exports.gl && exports.gl.bindTexture(exports.gl.TEXTURE_2D, g_FontTexture); + exports.gl && exports.gl.texParameteri(exports.gl.TEXTURE_2D, exports.gl.TEXTURE_MIN_FILTER, exports.gl.LINEAR); + exports.gl && exports.gl.texParameteri(exports.gl.TEXTURE_2D, exports.gl.TEXTURE_MAG_FILTER, exports.gl.LINEAR); + // gl && gl.pixelStorei(gl.UNPACK_ROW_LENGTH); // WebGL2 + exports.gl && exports.gl.texImage2D(exports.gl.TEXTURE_2D, 0, exports.gl.RGBA, width, height, 0, exports.gl.RGBA, exports.gl.UNSIGNED_BYTE, pixels); + // Store our identifier + io.Fonts.TexID = g_FontTexture || { foo: "bar" }; + // console.log("font texture id", g_FontTexture); + if (exports.ctx) { + const image_canvas = document.createElement("canvas"); + image_canvas.width = width; + image_canvas.height = height; + const image_ctx = image_canvas.getContext("2d"); + if (image_ctx === null) { + throw new Error(); + } + const image_data = image_ctx.getImageData(0, 0, width, height); + image_data.data.set(pixels); + image_ctx.putImageData(image_data, 0, 0); + io.Fonts.TexID = image_canvas; + } + // Restore modified GL state + exports.gl && last_texture && exports.gl.bindTexture(exports.gl.TEXTURE_2D, last_texture); + } + function DestroyFontsTexture() { + const io = ImGui.GetIO(); + io.Fonts.TexID = null; + exports.gl && exports.gl.deleteTexture(g_FontTexture); + g_FontTexture = null; + } + function CreateDeviceObjects() { + const vertex_shader = [ + "uniform mat4 ProjMtx;", + "attribute vec2 Position;", + "attribute vec2 UV;", + "attribute vec4 Color;", + "varying vec2 Frag_UV;", + "varying vec4 Frag_Color;", + "void main() {", + " Frag_UV = UV;", + " Frag_Color = Color;", + " gl_Position = ProjMtx * vec4(Position.xy,0,1);", + "}", + ]; + const fragment_shader = [ + "precision mediump float;", + "uniform sampler2D Texture;", + "varying vec2 Frag_UV;", + "varying vec4 Frag_Color;", + "void main() {", + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV);", + "}", + ]; + g_ShaderHandle = exports.gl && exports.gl.createProgram(); + g_VertHandle = exports.gl && exports.gl.createShader(exports.gl.VERTEX_SHADER); + g_FragHandle = exports.gl && exports.gl.createShader(exports.gl.FRAGMENT_SHADER); + exports.gl && exports.gl.shaderSource(g_VertHandle, vertex_shader.join("\n")); + exports.gl && exports.gl.shaderSource(g_FragHandle, fragment_shader.join("\n")); + exports.gl && exports.gl.compileShader(g_VertHandle); + exports.gl && exports.gl.compileShader(g_FragHandle); + exports.gl && exports.gl.attachShader(g_ShaderHandle, g_VertHandle); + exports.gl && exports.gl.attachShader(g_ShaderHandle, g_FragHandle); + exports.gl && exports.gl.linkProgram(g_ShaderHandle); + g_AttribLocationTex = exports.gl && exports.gl.getUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = exports.gl && exports.gl.getUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "Position") || 0; + g_AttribLocationUV = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "UV") || 0; + g_AttribLocationColor = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "Color") || 0; + g_VboHandle = exports.gl && exports.gl.createBuffer(); + g_ElementsHandle = exports.gl && exports.gl.createBuffer(); + CreateFontsTexture(); + } + function DestroyDeviceObjects() { + DestroyFontsTexture(); + exports.gl && exports.gl.deleteBuffer(g_VboHandle); + g_VboHandle = null; + exports.gl && exports.gl.deleteBuffer(g_ElementsHandle); + g_ElementsHandle = null; + g_AttribLocationTex = null; + g_AttribLocationProjMtx = null; + g_AttribLocationPosition = -1; + g_AttribLocationUV = -1; + g_AttribLocationColor = -1; + exports.gl && exports.gl.deleteProgram(g_ShaderHandle); + g_ShaderHandle = null; + exports.gl && exports.gl.deleteShader(g_VertHandle); + g_VertHandle = null; + exports.gl && exports.gl.deleteShader(g_FragHandle); + g_FragHandle = null; + } + + exports.CreateDeviceObjects = CreateDeviceObjects; + exports.CreateFontsTexture = CreateFontsTexture; + exports.DestroyDeviceObjects = DestroyDeviceObjects; + exports.DestroyFontsTexture = DestroyFontsTexture; + exports.Init = Init; + exports.NewFrame = NewFrame; + exports.RenderDrawData = RenderDrawData; + exports.Shutdown = Shutdown; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/hw0/lib/three.js b/hw0/lib/three.js new file mode 100644 index 0000000..cea6fdc Binary files /dev/null and b/hw0/lib/three.js differ diff --git a/hw0/src/engine.js b/hw0/src/engine.js new file mode 100644 index 0000000..e443cf3 --- /dev/null +++ b/hw0/src/engine.js @@ -0,0 +1,73 @@ +var cameraPosition = [-20, 180, 250]; + +GAMES202Main(); + +function GAMES202Main() { + const canvas = document.querySelector('#glcanvas'); + canvas.width = window.screen.width; + canvas.height = window.screen.height; + const gl = canvas.getContext('webgl'); + if (!gl) { + alert('Unable to initialize WebGL. Your browser or machine may not support it.'); + return; + } + + const camera = new THREE.PerspectiveCamera(75, gl.canvas.clientWidth / gl.canvas.clientHeight, 0.1, 1000); + const cameraControls = new THREE.OrbitControls(camera, canvas); + cameraControls.enableZoom = true; + cameraControls.enableRotate = true; + cameraControls.enablePan = true; + cameraControls.rotateSpeed = 0.3; + cameraControls.zoomSpeed = 1.0; + cameraControls.panSpeed = 2.0; + + function setSize(width, height) { + camera.aspect = width / height; + camera.updateProjectionMatrix(); + } + setSize(canvas.clientWidth, canvas.clientHeight); + window.addEventListener('resize', () => setSize(canvas.clientWidth, canvas.clientHeight)); + + camera.position.set(cameraPosition[0], cameraPosition[1], cameraPosition[2]); + cameraControls.target.set(0, 1, 0); + + const pointLight = new PointLight(250, [1, 1, 1]); + + const renderer = new WebGLRenderer(gl, camera); + renderer.addLight(pointLight); + loadOBJ(renderer, 'assets/mary/', 'Marry'); + + var guiParams = { + modelTransX: 0, + modelTransY: 0, + modelTransZ: 0, + modelScaleX: 52, + modelScaleY: 52, + modelScaleZ: 52, + } + function createGUI() { + const gui = new dat.gui.GUI(); + const panelModel = gui.addFolder('Model properties'); + const panelModelTrans = panelModel.addFolder('Translation'); + const panelModelScale = panelModel.addFolder('Scale'); + panelModelTrans.add(guiParams, 'modelTransX').name('X'); + panelModelTrans.add(guiParams, 'modelTransY').name('Y'); + panelModelTrans.add(guiParams, 'modelTransZ').name('Z'); + panelModelScale.add(guiParams, 'modelScaleX').name('X'); + panelModelScale.add(guiParams, 'modelScaleY').name('Y'); + panelModelScale.add(guiParams, 'modelScaleZ').name('Z'); + panelModel.open(); + panelModelTrans.open(); + panelModelScale.open(); + } + + createGUI(); + + function mainLoop(now) { + cameraControls.update(); + + renderer.render(guiParams); + requestAnimationFrame(mainLoop); + } + requestAnimationFrame(mainLoop); +} diff --git a/hw0/src/lights/Light.js b/hw0/src/lights/Light.js new file mode 100644 index 0000000..ae41167 --- /dev/null +++ b/hw0/src/lights/Light.js @@ -0,0 +1,12 @@ +class EmissiveMaterial extends Material { + + constructor(lightIntensity, lightColor) { + super({ + 'uLigIntensity': { type: '1f', value: lightIntensity }, + 'uLightColor': { type: '3fv', value: lightColor } + }, [], LightCubeVertexShader, LightCubeFragmentShader); + + this.intensity = lightIntensity; + this.color = lightColor; + } +} diff --git a/hw0/src/lights/PointLight.js b/hw0/src/lights/PointLight.js new file mode 100644 index 0000000..2edae2b --- /dev/null +++ b/hw0/src/lights/PointLight.js @@ -0,0 +1,13 @@ + +class PointLight { + /** + * Creates an instance of PointLight. + * @param {float} lightIntensity The intensity of the PointLight. + * @param {vec3f} lightColor The color of the PointLight. + * @memberof PointLight + */ + constructor(lightIntensity, lightColor) { + this.mesh = Mesh.cube(); + this.mat = new EmissiveMaterial(lightIntensity, lightColor); + } +} \ No newline at end of file diff --git a/hw0/src/loads/loadOBJ.js b/hw0/src/loads/loadOBJ.js new file mode 100644 index 0000000..7dfeefb --- /dev/null +++ b/hw0/src/loads/loadOBJ.js @@ -0,0 +1,64 @@ + +function loadOBJ(renderer, path, name) { + + const manager = new THREE.LoadingManager(); + manager.onProgress = function (item, loaded, total) { + console.log(item, loaded, total); + }; + + function onProgress(xhr) { + if (xhr.lengthComputable) { + const percentComplete = xhr.loaded / xhr.total * 100; + console.log('model ' + Math.round(percentComplete, 2) + '% downloaded'); + } + } + function onError() { } + + new THREE.MTLLoader(manager) + .setPath(path) + .load(name + '.mtl', function (materials) { + materials.preload(); + new THREE.OBJLoader(manager) + .setMaterials(materials) + .setPath(path) + .load(name + '.obj', function (object) { + object.traverse(function (child) { + if (child.isMesh) { + let geo = child.geometry; + let mat; + if (Array.isArray(child.material)) mat = child.material[0]; + else mat = child.material; + + var indices = Array.from({ length: geo.attributes.position.count }, (v, k) => k); + let mesh = new Mesh({ name: 'aVertexPosition', array: geo.attributes.position.array }, + { name: 'aNormalPosition', array: geo.attributes.normal.array }, + { name: 'aTextureCoord', array: geo.attributes.uv.array }, + indices); + + let colorMap = null; + if (mat.map != null) colorMap = new Texture(renderer.gl, mat.map.image); + // MARK: You can change the myMaterial object to your own Material instance + + let textureSample = 0; + let myMaterial; + if (colorMap != null) { + textureSample = 1; + myMaterial = new Material({ + 'uSampler': { type: 'texture', value: colorMap }, + 'uTextureSample': { type: '1i', value: textureSample }, + 'uKd': { type: '3fv', value: mat.color.toArray() } + },[],VertexShader, FragmentShader); + }else{ + myMaterial = new Material({ + 'uTextureSample': { type: '1i', value: textureSample }, + 'uKd': { type: '3fv', value: mat.color.toArray() } + },[],VertexShader, FragmentShader); + } + + let meshRender = new MeshRender(renderer.gl, mesh, myMaterial); + renderer.addMesh(meshRender); + } + }); + }, onProgress, onError); + }); +} diff --git a/hw0/src/loads/loadShader.js b/hw0/src/loads/loadShader.js new file mode 100644 index 0000000..e1c6eeb --- /dev/null +++ b/hw0/src/loads/loadShader.js @@ -0,0 +1,11 @@ + +function loadShaderFile(filename) { + + return new Promise((resolve, reject) => { + const loader = new THREE.FileLoader(); + loader.load(filename, (data) => { + resolve(data); + //console.log(data); + }); + }); +} diff --git a/hw0/src/materials/Material.js b/hw0/src/materials/Material.js new file mode 100644 index 0000000..1deaa67 --- /dev/null +++ b/hw0/src/materials/Material.js @@ -0,0 +1,33 @@ +class Material { + #flatten_uniforms; + #flatten_attribs; + #vsSrc; + #fsSrc; + // Uniforms is a map, attribs is a Array + constructor(uniforms, attribs, vsSrc, fsSrc) { + this.uniforms = uniforms; + this.attribs = attribs; + this.#vsSrc = vsSrc; + this.#fsSrc = fsSrc; + + this.#flatten_uniforms = ['uModelViewMatrix', 'uProjectionMatrix', 'uCameraPos', 'uLightPos']; + for (let k in uniforms) { + this.#flatten_uniforms.push(k); + } + this.#flatten_attribs = attribs; + } + + setMeshAttribs(extraAttribs) { + for (let i = 0; i < extraAttribs.length; i++) { + this.#flatten_attribs.push(extraAttribs[i]); + } + } + + compile(gl) { + return new Shader(gl, this.#vsSrc, this.#fsSrc, + { + uniforms: this.#flatten_uniforms, + attribs: this.#flatten_attribs + }); + } +} \ No newline at end of file diff --git a/hw0/src/objects/Mesh.js b/hw0/src/objects/Mesh.js new file mode 100644 index 0000000..2830c04 --- /dev/null +++ b/hw0/src/objects/Mesh.js @@ -0,0 +1,75 @@ +class Mesh { + constructor(verticesAttrib, normalsAttrib, texcoordsAttrib, indices) { + this.indices = indices; + this.count = indices.length; + this.hasVertices = false; + this.hasNormals = false; + this.hasTexcoords = false; + let extraAttribs = []; + + if (verticesAttrib != null) { + this.hasVertices = true; + this.vertices = verticesAttrib.array; + this.verticesName = verticesAttrib.name; + } + if (normalsAttrib != null) { + this.hasNormals = true; + this.normals = normalsAttrib.array; + this.normalsName = normalsAttrib.name; + } + if (texcoordsAttrib != null) { + this.hasTexcoords = true; + this.texcoords = texcoordsAttrib.array; + this.texcoordsName = texcoordsAttrib.name; + } + } + + static cube() { + const positions = [ + // Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0, + ]; + const indices = [ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23, // left + ]; + return new Mesh({ name: 'aVertexPosition', array: new Float32Array(positions) }, null, null, indices); + } +} \ No newline at end of file diff --git a/hw0/src/renderers/MeshRender.js b/hw0/src/renderers/MeshRender.js new file mode 100644 index 0000000..3e6f3e1 --- /dev/null +++ b/hw0/src/renderers/MeshRender.js @@ -0,0 +1,165 @@ + +class MeshRender { + + #vertexBuffer; + #normalBuffer; + #texcoordBuffer; + #indicesBuffer; + + constructor(gl, mesh, material) { + this.gl = gl; + this.mesh = mesh; + this.material = material; + + this.#vertexBuffer = gl.createBuffer(); + this.#normalBuffer = gl.createBuffer(); + this.#texcoordBuffer = gl.createBuffer(); + this.#indicesBuffer = gl.createBuffer(); + + let extraAttribs = [] + if (mesh.hasVertices) { + extraAttribs.push(mesh.verticesName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.vertices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + if (mesh.hasNormals) { + extraAttribs.push(mesh.normalsName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#normalBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.normals, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + if (mesh.hasTexcoords) { + extraAttribs.push(mesh.texcoordsName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#texcoordBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.texcoords, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#indicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(mesh.indices), gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + + this.material.setMeshAttribs(extraAttribs); + this.shader = this.material.compile(gl); + } + + draw(camera, transform) { + const gl = this.gl; + + let modelViewMatrix = mat4.create(); + let projectionMatrix = mat4.create(); + + camera.updateMatrixWorld(); + mat4.invert(modelViewMatrix, camera.matrixWorld.elements); + mat4.translate(modelViewMatrix, modelViewMatrix, transform.translate); + mat4.scale(modelViewMatrix, modelViewMatrix, transform.scale); + mat4.copy(projectionMatrix, camera.projectionMatrix.elements); + + if (this.mesh.hasVertices) { + const numComponents = 3; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#vertexBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.verticesName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.verticesName]); + } + + if (this.mesh.hasNormals) { + const numComponents = 3; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#normalBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.normalsName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.normalsName]); + } + + if (this.mesh.hasTexcoords) { + const numComponents = 2; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#texcoordBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.texcoordsName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.texcoordsName]); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#indicesBuffer); + + gl.useProgram(this.shader.program.glShaderProgram); + + gl.uniformMatrix4fv( + this.shader.program.uniforms.uProjectionMatrix, + false, + projectionMatrix); + gl.uniformMatrix4fv( + this.shader.program.uniforms.uModelViewMatrix, + false, + modelViewMatrix); + + // Specific the camera uniforms + gl.uniform3fv( + this.shader.program.uniforms.uCameraPos, + [camera.position.x, camera.position.y, camera.position.z]); + + for (let k in this.material.uniforms) { + if (this.material.uniforms[k].type == 'matrix4fv') { + gl.uniformMatrix4fv( + this.shader.program.uniforms[k], + false, + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '3fv') { + gl.uniform3fv( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '1f') { + gl.uniform1f( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '1i') { + gl.uniform1i( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == 'texture') { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, this.material.uniforms[k].value.texture); + gl.uniform1i(this.shader.program.uniforms[k], 0); + } + } + + { + const vertexCount = this.mesh.count; + const type = gl.UNSIGNED_SHORT; + const offset = 0; + gl.drawElements(gl.TRIANGLES, vertexCount, type, offset); + } + } +} \ No newline at end of file diff --git a/hw0/src/renderers/WebGLRenderer.js b/hw0/src/renderers/WebGLRenderer.js new file mode 100644 index 0000000..2f0b0d5 --- /dev/null +++ b/hw0/src/renderers/WebGLRenderer.js @@ -0,0 +1,64 @@ +// Remain rotatation +class TRSTransform { + constructor(translate = [0, 0, 0], scale = [1, 1, 1]) { + this.translate = translate; + this.scale = scale; + } +} + +class WebGLRenderer { + meshes = []; + lights = []; + + constructor(gl, camera) { + this.gl = gl; + this.camera = camera; + } + + addLight(light) { this.lights.push({ entity: light, meshRender: new MeshRender(this.gl, light.mesh, light.mat) }); } + + addMesh(mesh) { this.meshes.push(mesh); } + + render(guiParams) { + const gl = this.gl; + + gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque + gl.clearDepth(1.0); // Clear everything + gl.enable(gl.DEPTH_TEST); // Enable depth testing + gl.depthFunc(gl.LEQUAL); // Near things obscure far things + + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + // Handle light + const timer = Date.now() * 0.00025; + let lightPos = [ Math.sin(timer * 6) * 100, + Math.cos(timer * 4) * 150, + Math.cos(timer * 2) * 100 ]; + + if (this.lights.length != 0) { + for (let l = 0; l < this.lights.length; l++) { + let trans = new TRSTransform(lightPos); + this.lights[l].meshRender.draw(this.camera, trans); + + for (let i = 0; i < this.meshes.length; i++) { + const mesh = this.meshes[i]; + + const modelTranslation = [guiParams.modelTransX, guiParams.modelTransY, guiParams.modelTransZ]; + const modelScale = [guiParams.modelScaleX, guiParams.modelScaleY, guiParams.modelScaleZ]; + let meshTrans = new TRSTransform(modelTranslation, modelScale); + + this.gl.useProgram(mesh.shader.program.glShaderProgram); + this.gl.uniform3fv(mesh.shader.program.uniforms.uLightPos, lightPos); + mesh.draw(this.camera, meshTrans); + } + } + } else { + // Handle mesh(no light) + for (let i = 0; i < this.meshes.length; i++) { + const mesh = this.meshes[i]; + let trans = new TRSTransform(); + mesh.draw(this.camera, trans); + } + } + } +} \ No newline at end of file diff --git a/hw0/src/shaders/InternalShader.js b/hw0/src/shaders/InternalShader.js new file mode 100644 index 0000000..2c41de5 --- /dev/null +++ b/hw0/src/shaders/InternalShader.js @@ -0,0 +1,77 @@ +const LightCubeVertexShader = ` +attribute vec3 aVertexPosition; + +uniform mat4 uModelViewMatrix; +uniform mat4 uProjectionMatrix; + + +void main(void) { + + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); + +} +`; + +const LightCubeFragmentShader = ` +#ifdef GL_ES +precision mediump float; +#endif + +uniform float uLigIntensity; +uniform vec3 uLightColor; + +void main(void) { + + //gl_FragColor = vec4(1,1,1, 1.0); + gl_FragColor = vec4(uLightColor, 1.0); +} +`; +const VertexShader = ` +attribute vec3 aVertexPosition; +attribute vec3 aNormalPosition; +attribute vec2 aTextureCoord; + +uniform mat4 uModelViewMatrix; +uniform mat4 uProjectionMatrix; + +varying highp vec3 vFragPos; +varying highp vec3 vNormal; +varying highp vec2 vTextureCoord; + +void main(void) { + + vFragPos = aVertexPosition; + vNormal = aNormalPosition; + + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); + + vTextureCoord = aTextureCoord; + +} +`; + +const FragmentShader = ` +#ifdef GL_ES +precision mediump float; +#endif + +uniform int uTextureSample; +uniform vec3 uKd; +uniform sampler2D uSampler; +uniform vec3 uLightPos; +uniform vec3 uCameraPos; + +varying highp vec3 vFragPos; +varying highp vec3 vNormal; +varying highp vec2 vTextureCoord; + +void main(void) { + + if (uTextureSample == 1) { + gl_FragColor = texture2D(uSampler, vTextureCoord); + } else { + gl_FragColor = vec4(uKd,1); + } + +} +`; \ No newline at end of file diff --git a/hw0/src/shaders/Shader.js b/hw0/src/shaders/Shader.js new file mode 100644 index 0000000..c9e03aa --- /dev/null +++ b/hw0/src/shaders/Shader.js @@ -0,0 +1,63 @@ +class Shader { + + constructor(gl, vsSrc, fsSrc, shaderLocations) { + this.gl = gl; + const vs = this.compileShader(vsSrc, gl.VERTEX_SHADER); + const fs = this.compileShader(fsSrc, gl.FRAGMENT_SHADER); + + this.program = this.addShaderLocations({ + glShaderProgram: this.linkShader(vs, fs), + }, shaderLocations); + } + + compileShader(shaderSource, shaderType) { + const gl = this.gl; + var shader = gl.createShader(shaderType); + gl.shaderSource(shader, shaderSource); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error(shaderSource); + console.error(('shader compiler error:\n' + gl.getShaderInfoLog(shader))); + } + + return shader; + }; + + linkShader(vs, fs) { + const gl = this.gl; + var prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + abort('shader linker error:\n' + gl.getProgramInfoLog(prog)); + } + return prog; + }; + + addShaderLocations(result, shaderLocations) { + const gl = this.gl; + result.uniforms = {}; + result.attribs = {}; + + if (shaderLocations && shaderLocations.uniforms && shaderLocations.uniforms.length) { + for (let i = 0; i < shaderLocations.uniforms.length; ++i) { + result.uniforms = Object.assign(result.uniforms, { + [shaderLocations.uniforms[i]]: gl.getUniformLocation(result.glShaderProgram, shaderLocations.uniforms[i]), + }); + //console.log(gl.getUniformLocation(result.glShaderProgram, 'uKd')); + } + } + if (shaderLocations && shaderLocations.attribs && shaderLocations.attribs.length) { + for (let i = 0; i < shaderLocations.attribs.length; ++i) { + result.attribs = Object.assign(result.attribs, { + [shaderLocations.attribs[i]]: gl.getAttribLocation(result.glShaderProgram, shaderLocations.attribs[i]), + }); + } + } + + return result; + } +} diff --git a/hw0/src/shaders/lightShader/fragment.glsl b/hw0/src/shaders/lightShader/fragment.glsl new file mode 100644 index 0000000..0a42225 --- /dev/null +++ b/hw0/src/shaders/lightShader/fragment.glsl @@ -0,0 +1,12 @@ +#ifdef GL_ES +precision mediump float; +#endif + +uniform float uLigIntensity; +uniform vec3 uLightColor; + +void main(void) { + + //gl_FragColor = vec4(1,1,1, 1.0); + gl_FragColor = vec4(uLightColor, 1.0); +} \ No newline at end of file diff --git a/hw0/src/shaders/lightShader/vertex.glsl b/hw0/src/shaders/lightShader/vertex.glsl new file mode 100644 index 0000000..75756e0 --- /dev/null +++ b/hw0/src/shaders/lightShader/vertex.glsl @@ -0,0 +1,11 @@ +attribute vec3 aVertexPosition; + +uniform mat4 uModelViewMatrix; +uniform mat4 uProjectionMatrix; + + +void main(void) { + + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); + +} \ No newline at end of file diff --git a/hw0/src/shaders/phongShader/fragment.glsl b/hw0/src/shaders/phongShader/fragment.glsl new file mode 100644 index 0000000..a6ba4b9 --- /dev/null +++ b/hw0/src/shaders/phongShader/fragment.glsl @@ -0,0 +1,40 @@ +#ifdef GL_ES +precision mediump float; +#endif +uniform sampler2D uSampler; +uniform vec3 uKd; +uniform vec3 uKs; +uniform vec3 uLightPos; +uniform vec3 uCameraPos; +uniform float uLightIntensity; +uniform int uTextureSample; + +varying highp vec2 vTextureCoord; +varying highp vec3 vFragPos; +varying highp vec3 vNormal; + +void main(void) { + vec3 color; + if (uTextureSample == 1) { + color = pow(texture2D(uSampler, vTextureCoord).rgb, vec3(2.2)); + } else { + color = uKd; + } + + vec3 ambient = 0.05 * color; + + vec3 lightDir = normalize(uLightPos - vFragPos); + vec3 normal = normalize(vNormal); + float diff = max(dot(lightDir, normal), 0.0); + float light_atten_coff = uLightIntensity / length(uLightPos - vFragPos); + vec3 diffuse = diff * light_atten_coff * color; + + vec3 viewDir = normalize(uCameraPos - vFragPos); + float spec = 0.0; + vec3 reflectDir = reflect(-lightDir, normal); + spec = pow (max(dot(viewDir, reflectDir), 0.0), 35.0); + vec3 specular = uKs * light_atten_coff * spec; + + gl_FragColor = vec4(pow((ambient + diffuse + specular), vec3(1.0/2.2)), 1.0); + +} \ No newline at end of file diff --git a/hw0/src/shaders/phongShader/vertex.glsl b/hw0/src/shaders/phongShader/vertex.glsl new file mode 100644 index 0000000..516f6b9 --- /dev/null +++ b/hw0/src/shaders/phongShader/vertex.glsl @@ -0,0 +1,22 @@ +attribute vec3 aVertexPosition; +attribute vec3 aNormalPosition; +attribute vec2 aTextureCoord; + +uniform mat4 uModelViewMatrix; +uniform mat4 uProjectionMatrix; + +varying highp vec2 vTextureCoord; +varying highp vec3 vFragPos; +varying highp vec3 vNormal; + + +void main(void) { + + vFragPos = aVertexPosition; + vNormal = aNormalPosition; + + gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0); + + vTextureCoord = aTextureCoord; + +} \ No newline at end of file diff --git a/hw0/src/shaders/test.txt b/hw0/src/shaders/test.txt new file mode 100644 index 0000000..ed1bf69 --- /dev/null +++ b/hw0/src/shaders/test.txt @@ -0,0 +1 @@ +jainzhou \ No newline at end of file diff --git a/hw0/src/textures/Texture.js b/hw0/src/textures/Texture.js new file mode 100644 index 0000000..b1cacae --- /dev/null +++ b/hw0/src/textures/Texture.js @@ -0,0 +1,50 @@ +class Texture { + constructor(gl, img) { + this.texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + // Because images have to be download over the internet + // they might take a moment until they are ready. + // Until then put a single pixel in the texture so we can + // use it immediately. When the image has finished downloading + // we'll update the texture with the contents of the image. + const level = 0; + const internalFormat = gl.RGBA; + const width = 1; + const height = 1; + const border = 0; + const srcFormat = gl.RGBA; + const srcType = gl.UNSIGNED_BYTE; + const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + width, height, border, srcFormat, srcType, + pixel); + + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + srcFormat, srcType, img); + + // WebGL1 has different requirements for power of 2 images + // vs non power of 2 images so check if the image is a + // power of 2 in both dimensions. + if (isPowerOf2(img.width) && isPowerOf2(img.height)) { + // Yes, it's a power of 2. Generate mips. + gl.generateMipmap(gl.TEXTURE_2D); + } else { + // No, it's not a power of 2. Turn of mips and set + // wrapping to clamp to edge + //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEATE); + //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEATE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + } + gl.bindTexture(gl.TEXTURE_2D, null); + } +} + +function isPowerOf2(value) { + return (value & (value - 1)) == 0; +} diff --git a/hw1/LICENSE b/hw1/LICENSE new file mode 100644 index 0000000..f6dd8ab --- /dev/null +++ b/hw1/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2021 Lingqi Yan , All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/hw1/README.md b/hw1/README.md new file mode 100644 index 0000000..abfe74e --- /dev/null +++ b/hw1/README.md @@ -0,0 +1,22 @@ +# GAMES202 homework0 + +## Usage + +### For Visual Studio Code +Install plugin `Live Sever` and run with `index.html` directly + +### For Node.js users +To install: +``` +npm install http-server -g +``` +To run(from `index.html` directory): +``` +http-server . -p 8000 +``` + +## In-web operation +- Hold right mouse to rotate the camera +- Scroll mouse wheel to zoom in/out +- Hold left mouse to move the camera +- Hold left mouse only to drag the GUI \ No newline at end of file diff --git a/hw1/assets/floor/floor.mtl b/hw1/assets/floor/floor.mtl new file mode 100644 index 0000000..f231bdf --- /dev/null +++ b/hw1/assets/floor/floor.mtl @@ -0,0 +1,10 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl None +Ns 500 +Ka 0.8 0.8 0.8 +Kd 0.8 0.8 0.8 +Ks 0.8 0.8 0.8 +d 1 +illum 2 diff --git a/hw1/assets/floor/floor.obj b/hw1/assets/floor/floor.obj new file mode 100644 index 0000000..b9eb545 --- /dev/null +++ b/hw1/assets/floor/floor.obj @@ -0,0 +1,16 @@ +# Blender v2.91.0 OBJ File: '' +# www.blender.org +mtllib floor.mtl +o Plane +v -27.924055 0.000000 27.924055 +v 27.924055 0.000000 27.924055 +v -27.924055 0.000000 -27.924055 +v 27.924055 0.000000 -27.924055 +vt 0.000000 0.000000 +vt 1.000000 0.000000 +vt 1.000000 1.000000 +vt 0.000000 1.000000 +vn 0.0000 1.0000 0.0000 +usemtl None +s off +f 1/1/1 2/2/1 4/3/1 3/4/1 diff --git a/hw1/assets/mary/MC003_Kozakura_Mari.png b/hw1/assets/mary/MC003_Kozakura_Mari.png new file mode 100644 index 0000000..b4d4f62 Binary files /dev/null and b/hw1/assets/mary/MC003_Kozakura_Mari.png differ diff --git a/hw1/assets/mary/Marry.mtl b/hw1/assets/mary/Marry.mtl new file mode 100644 index 0000000..7d5a5f4 --- /dev/null +++ b/hw1/assets/mary/Marry.mtl @@ -0,0 +1,23 @@ +# Blender MTL File: 'Marry.blend' +# Material Count: 2 + +newmtl MC003_Kozakura_Mari +Ns 900.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.000000 0.000000 0.000000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 1 +map_Kd MC003_Kozakura_Mari.png + +newmtl 材质 +Ns 900.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.270588 0.552941 0.874510 +Ks 0.000000 0.000000 0.000000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 1 diff --git a/hw1/assets/mary/Marry.obj b/hw1/assets/mary/Marry.obj new file mode 100644 index 0000000..6bb2fee Binary files /dev/null and b/hw1/assets/mary/Marry.obj differ diff --git a/hw1/assets/testObj/testObj.mtl b/hw1/assets/testObj/testObj.mtl new file mode 100644 index 0000000..d842ea8 --- /dev/null +++ b/hw1/assets/testObj/testObj.mtl @@ -0,0 +1,10 @@ +# Blender MTL File: 'None' +# Material Count: 1 + +newmtl None +Ns 500 +Ka 0.8 0.8 0.8 +Kd 0.0 1.0 0.0 +Ks 0.8 0.8 0.8 +d 1 +illum 2 diff --git a/hw1/assets/testObj/testObj.obj b/hw1/assets/testObj/testObj.obj new file mode 100644 index 0000000..3a4eec2 --- /dev/null +++ b/hw1/assets/testObj/testObj.obj @@ -0,0 +1,64 @@ +# Blender v2.91.0 OBJ File: '' +# www.blender.org +mtllib untitled.mtl +o Cube +v -1.000000 -1.000000 1.000000 +v -0.676804 1.000000 0.676804 +v -1.000000 -1.000000 -1.000000 +v -0.676804 1.000000 -0.676804 +v 1.000000 -1.000000 1.000000 +v 0.676804 1.000000 0.676804 +v 1.000000 -1.000000 -1.000000 +v 0.676804 1.000000 -0.676804 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v 1.000000 1.000000 -1.000000 +v 1.000000 1.000000 1.000000 +v -0.676804 3.528965 0.676804 +v -0.676804 3.528965 -0.676804 +v 0.676804 3.528965 -0.676804 +v 0.676804 3.528965 0.676804 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.125000 0.500000 +vt 0.125000 0.750000 +vt 0.834601 0.709601 +vt 0.665400 0.709601 +vt 0.665400 0.709601 +vt 0.834601 0.709601 +vt 0.834601 0.540400 +vt 0.875000 0.500000 +vt 0.875000 0.750000 +vt 0.665400 0.540400 +vt 0.665400 0.540400 +vt 0.834601 0.540400 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn 0.0000 1.0000 0.0000 +usemtl None +s off +f 1/1/1 9/2/1 10/3/1 3/4/1 +f 3/4/2 10/3/2 11/5/2 7/6/2 +f 7/6/3 11/5/3 12/7/3 5/8/3 +f 5/8/4 12/7/4 9/9/4 1/10/4 +f 3/11/5 7/6/5 5/8/5 1/12/5 +f 2/13/4 6/14/4 16/15/4 13/16/4 +f 2/13/6 4/17/6 10/18/6 9/19/6 +f 4/17/6 8/20/6 11/5/6 10/18/6 +f 8/20/6 6/14/6 12/7/6 11/5/6 +f 6/14/6 2/13/6 9/19/6 12/7/6 +f 15/21/6 14/22/6 13/16/6 16/15/6 +f 8/20/2 4/17/2 14/22/2 15/21/2 +f 6/14/3 8/20/3 15/21/3 16/15/3 +f 4/17/1 2/13/1 13/16/1 14/22/1 diff --git a/hw1/assignment1.pdf b/hw1/assignment1.pdf new file mode 100644 index 0000000..7aba18f Binary files /dev/null and b/hw1/assignment1.pdf differ diff --git a/hw1/index.html b/hw1/index.html new file mode 100644 index 0000000..92f4ea4 --- /dev/null +++ b/hw1/index.html @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/hw1/lib/MTLLoader.js b/hw1/lib/MTLLoader.js new file mode 100644 index 0000000..ed5a835 --- /dev/null +++ b/hw1/lib/MTLLoader.js @@ -0,0 +1,549 @@ +/** + * Loads a Wavefront .mtl file specifying materials + */ + + THREE.MTLLoader = function ( manager ) { + + THREE.Loader.call( this, manager ); + +}; + +THREE.MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { + + constructor: THREE.MTLLoader, + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var path = ( this.path === '' ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path; + + var loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + }, + + setMaterialOptions: function ( value ) { + + this.materialOptions = value; + return this; + + }, + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {THREE.MTLLoader.MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse: function ( text, path ) { + + var lines = text.split( '\n' ); + var info = {}; + var delimiter_pattern = /\s+/; + var materialsInfo = {}; + + for ( var i = 0; i < lines.length; i ++ ) { + + var line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + var pos = line.indexOf( ' ' ); + + var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { + + var ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +} ); + +/** + * Create a new THREE.MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) { + + this.baseUrl = baseUrl || ''; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; + this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; + +}; + +THREE.MTLLoader.MaterialCreator.prototype = { + + constructor: THREE.MTLLoader.MaterialCreator, + + crossOrigin: 'anonymous', + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setManager: function ( value ) { + + this.manager = value; + + }, + + setMaterials: function ( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + }, + + convert: function ( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + var converted = {}; + + for ( var mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + var mat = materialsInfo[ mn ]; + + var covmat = {}; + + converted[ mn ] = covmat; + + for ( var prop in mat ) { + + var save = true; + var value = mat[ prop ]; + var lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + }, + + preload: function () { + + for ( var mn in this.materialsInfo ) { + + this.create( mn ); + + } + + }, + + getIndex: function ( materialName ) { + + return this.nameLookup[ materialName ]; + + }, + + getAsArray: function () { + + var index = 0; + + for ( var mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + }, + + create: function ( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + }, + + createMaterial_: function ( materialName ) { + + // Create material + + var scope = this; + var mat = this.materialsInfo[ materialName ]; + var params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + var texParams = scope.getTextureParams( value, params ); + var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + params[ mapType ] = map; + + } + + for ( var prop in mat ) { + + var value = mat[ prop ]; + var n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new THREE.Color().fromArray( value ); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new THREE.Color().fromArray( value ); + + break; + + case 'ke': + + // Emissive using RGB values + params.emissive = new THREE.Color().fromArray( value ); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( 'map', value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( 'specularMap', value ); + + break; + + case 'map_ke': + + // Emissive map + + setMapForType( 'emissiveMap', value ); + + break; + + case 'norm': + + setMapForType( 'normalMap', value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( 'bumpMap', value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( 'alphaMap', value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + }, + + getTextureParams: function ( value, matParams ) { + + var texParams = { + + scale: new THREE.Vector2( 1, 1 ), + offset: new THREE.Vector2( 0, 0 ) + + }; + + var items = value.split( /\s+/ ); + var pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + }, + + loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { + + var texture; + var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; + var loader = manager.getHandler( url ); + + if ( loader === null ) { + + loader = new THREE.TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +}; \ No newline at end of file diff --git a/hw1/lib/OBJLoader.js b/hw1/lib/OBJLoader.js new file mode 100644 index 0000000..72fd0c9 --- /dev/null +++ b/hw1/lib/OBJLoader.js @@ -0,0 +1,900 @@ +THREE.OBJLoader = ( function () { + + // o object_name | g group_name + var object_pattern = /^[og]\s*(.+)?/; + // mtllib file_reference + var material_library_pattern = /^mtllib /; + // usemtl material_name + var material_use_pattern = /^usemtl /; + // usemap map_name + var map_use_pattern = /^usemap /; + + var vA = new THREE.Vector3(); + var vB = new THREE.Vector3(); + var vC = new THREE.Vector3(); + + var ab = new THREE.Vector3(); + var cb = new THREE.Vector3(); + + function ParserState() { + + var state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materials: {}, + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + var previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + var material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + var cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + var lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + var declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + var src = this.normals; + var dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addFaceNormal: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.normals; + + vA.fromArray( src, a ); + vB.fromArray( src, b ); + vC.fromArray( src, c ); + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + dst.push( cb.x, cb.y, cb.z ); + dst.push( cb.x, cb.y, cb.z ); + dst.push( cb.x, cb.y, cb.z ); + + }, + + addColor: function ( a, b, c ) { + + var src = this.colors; + var dst = this.object.geometry.colors; + + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addDefaultUV: function () { + + var dst = this.object.geometry.uvs; + + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); + + }, + + addUVLine: function ( a ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + var vLen = this.vertices.length; + + var ia = this.parseVertexIndex( a, vLen ); + var ib = this.parseVertexIndex( b, vLen ); + var ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); + + // normals + + if ( na !== undefined && na !== '' ) { + + var nLen = this.normals.length; + + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } else { + + this.addFaceNormal( ia, ib, ic ); + + } + + // uvs + + if ( ua !== undefined && ua !== '' ) { + + var uvLen = this.uvs.length; + + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + + this.addUV( ia, ib, ic ); + + this.object.geometry.hasUVIndices = true; + + } else { + + // add placeholder values (for inconsistent face definitions) + + this.addDefaultUV(); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + var vLen = this.vertices.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + var index = this.parseVertexIndex( vertices[ vi ], vLen ); + + this.addVertexPoint( index ); + this.addColor( index ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + var vLen = this.vertices.length; + var uvLen = this.uvs.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + + } + + // + + function OBJLoader( manager ) { + + THREE.Loader.call( this, manager ); + + this.materials = null; + + } + + OBJLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { + + constructor: OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + }, + + setMaterials: function ( materials ) { + + this.materials = materials; + + return this; + + }, + + parse: function ( text ) { + + var state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + var lines = text.split( '\n' ); + var line = '', lineFirstChar = ''; + var lineLength = 0; + var result = []; + + // Faster to just trim left side of the line. Use if available. + var trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( var i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + var data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length >= 7 ) { + + state.colors.push( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + + ); + + } else { + + // if no colors are defined, add placeholders so color and vertex indices match + + state.colors.push( undefined, undefined, undefined ); + + } + + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + var lineData = line.substr( 1 ).trim(); + var vertexData = lineData.split( /\s+/ ); + var faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + + var vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + var vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + var v1 = faceVertices[ 0 ]; + + for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + var v2 = faceVertices[ j ]; + var v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + var lineParts = line.substring( 1 ).trim().split( ' ' ); + var lineVertices = [], lineUVs = []; + + if ( line.indexOf( '/' ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + + var parts = lineParts[ li ].split( '/' ); + + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + + } + + } + + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + var lineData = line.substr( 1 ).trim(); + var pointData = lineData.split( ' ' ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // var name = result[ 0 ].substr( 1 ).trim(); + var name = ( ' ' + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + + state.startObject( name ); + + } else if ( material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( map_use_pattern.test( line ) ) { + + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * or + * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + var value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + + var material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + var container = new THREE.Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + var hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + + if ( hasPrimitives === true ) { + + for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + + var object = state.objects[ i ]; + var geometry = object.geometry; + var materials = object.materials; + var isLine = ( geometry.type === 'Line' ); + var isPoints = ( geometry.type === 'Points' ); + var hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.hasUVIndices === true ) { + + buffergeometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + var createdMaterials = []; + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + var materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + var material = state.materials[ materialHash ]; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + + var materialLine = new THREE.LineBasicMaterial(); + THREE.Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { + + var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); + THREE.Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; + + } + + } + + if ( material === undefined ) { + + if ( isLine ) { + + material = new THREE.LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new THREE.MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + + state.materials[ materialHash ] = material; + + } + + createdMaterials.push( material ); + + } + + // Create mesh + + var mesh; + + if ( createdMaterials.length > 1 ) { + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + } else { + + // if there is only the default parser state object with no geometry data, interpret data as point cloud + + if ( state.vertices.length > 0 ) { + + var material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( state.vertices, 3 ) ); + + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + + buffergeometry.setAttribute( 'color', new THREE.Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; + + } + + var points = new THREE.Points( buffergeometry, material ); + container.add( points ); + + } + + } + + return container; + + } + + } ); + + return OBJLoader; + +} )(); \ No newline at end of file diff --git a/hw1/lib/OrbitControls.js b/hw1/lib/OrbitControls.js new file mode 100644 index 0000000..963b13d --- /dev/null +++ b/hw1/lib/OrbitControls.js @@ -0,0 +1,1215 @@ +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +THREE.OrbitControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // so camera.up is the orbit axis + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().invert(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + var twoPI = 2 * Math.PI; + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + var min = scope.minAzimuthAngle; + var max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove ); + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseDown( event ); + break; + + // TODO touch + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseMove( event ); + break; + + // TODO touch + + } + + } + + function onPointerUp( event ) { + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + onMouseUp( event ); + break; + + // TODO touch + + } + + } + + function onMouseDown( event ) { + + // Prevent the browser from scrolling. + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case THREE.MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case THREE.MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case THREE.MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp ); + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case THREE.TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case THREE.TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case THREE.TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case THREE.TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); // prevent scrolling + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'wheel', onMouseWheel ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart ); + scope.domElement.addEventListener( 'touchend', onTouchEnd ); + scope.domElement.addEventListener( 'touchmove', onTouchMove ); + + // force an update at start + + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +THREE.MapControls = function ( object, domElement ) { + + THREE.OrbitControls.call( this, object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = THREE.MOUSE.PAN; + this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE; + + this.touches.ONE = THREE.TOUCH.PAN; + this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE; + +}; + +THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.MapControls.prototype.constructor = THREE.MapControls; diff --git a/hw1/lib/dat.gui.js b/hw1/lib/dat.gui.js new file mode 100644 index 0000000..dec7478 --- /dev/null +++ b/hw1/lib/dat.gui.js @@ -0,0 +1,2538 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ + + (function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.dat = {}))); +}(this, (function (exports) { 'use strict'; + +function ___$insertStyle(css) { + if (!css) { + return; + } + if (typeof window === 'undefined') { + return; + } + + var style = document.createElement('style'); + + style.setAttribute('type', 'text/css'); + style.innerHTML = css; + document.head.appendChild(style); + + return css; +} + +function colorToString (color, forceCSSHex) { + var colorFormat = color.__state.conversionName.toString(); + var r = Math.round(color.r); + var g = Math.round(color.g); + var b = Math.round(color.b); + var a = color.a; + var h = Math.round(color.h); + var s = color.s.toFixed(1); + var v = color.v.toFixed(1); + if (forceCSSHex || colorFormat === 'THREE_CHAR_HEX' || colorFormat === 'SIX_CHAR_HEX') { + var str = color.hex.toString(16); + while (str.length < 6) { + str = '0' + str; + } + return '#' + str; + } else if (colorFormat === 'CSS_RGB') { + return 'rgb(' + r + ',' + g + ',' + b + ')'; + } else if (colorFormat === 'CSS_RGBA') { + return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')'; + } else if (colorFormat === 'HEX') { + return '0x' + color.hex.toString(16); + } else if (colorFormat === 'RGB_ARRAY') { + return '[' + r + ',' + g + ',' + b + ']'; + } else if (colorFormat === 'RGBA_ARRAY') { + return '[' + r + ',' + g + ',' + b + ',' + a + ']'; + } else if (colorFormat === 'RGB_OBJ') { + return '{r:' + r + ',g:' + g + ',b:' + b + '}'; + } else if (colorFormat === 'RGBA_OBJ') { + return '{r:' + r + ',g:' + g + ',b:' + b + ',a:' + a + '}'; + } else if (colorFormat === 'HSV_OBJ') { + return '{h:' + h + ',s:' + s + ',v:' + v + '}'; + } else if (colorFormat === 'HSVA_OBJ') { + return '{h:' + h + ',s:' + s + ',v:' + v + ',a:' + a + '}'; + } + return 'unknown format'; +} + +var ARR_EACH = Array.prototype.forEach; +var ARR_SLICE = Array.prototype.slice; +var Common = { + BREAK: {}, + extend: function extend(target) { + this.each(ARR_SLICE.call(arguments, 1), function (obj) { + var keys = this.isObject(obj) ? Object.keys(obj) : []; + keys.forEach(function (key) { + if (!this.isUndefined(obj[key])) { + target[key] = obj[key]; + } + }.bind(this)); + }, this); + return target; + }, + defaults: function defaults(target) { + this.each(ARR_SLICE.call(arguments, 1), function (obj) { + var keys = this.isObject(obj) ? Object.keys(obj) : []; + keys.forEach(function (key) { + if (this.isUndefined(target[key])) { + target[key] = obj[key]; + } + }.bind(this)); + }, this); + return target; + }, + compose: function compose() { + var toCall = ARR_SLICE.call(arguments); + return function () { + var args = ARR_SLICE.call(arguments); + for (var i = toCall.length - 1; i >= 0; i--) { + args = [toCall[i].apply(this, args)]; + } + return args[0]; + }; + }, + each: function each(obj, itr, scope) { + if (!obj) { + return; + } + if (ARR_EACH && obj.forEach && obj.forEach === ARR_EACH) { + obj.forEach(itr, scope); + } else if (obj.length === obj.length + 0) { + var key = void 0; + var l = void 0; + for (key = 0, l = obj.length; key < l; key++) { + if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) { + return; + } + } + } else { + for (var _key in obj) { + if (itr.call(scope, obj[_key], _key) === this.BREAK) { + return; + } + } + } + }, + defer: function defer(fnc) { + setTimeout(fnc, 0); + }, + debounce: function debounce(func, threshold, callImmediately) { + var timeout = void 0; + return function () { + var obj = this; + var args = arguments; + function delayed() { + timeout = null; + if (!callImmediately) func.apply(obj, args); + } + var callNow = callImmediately || !timeout; + clearTimeout(timeout); + timeout = setTimeout(delayed, threshold); + if (callNow) { + func.apply(obj, args); + } + }; + }, + toArray: function toArray(obj) { + if (obj.toArray) return obj.toArray(); + return ARR_SLICE.call(obj); + }, + isUndefined: function isUndefined(obj) { + return obj === undefined; + }, + isNull: function isNull(obj) { + return obj === null; + }, + isNaN: function (_isNaN) { + function isNaN(_x) { + return _isNaN.apply(this, arguments); + } + isNaN.toString = function () { + return _isNaN.toString(); + }; + return isNaN; + }(function (obj) { + return isNaN(obj); + }), + isArray: Array.isArray || function (obj) { + return obj.constructor === Array; + }, + isObject: function isObject(obj) { + return obj === Object(obj); + }, + isNumber: function isNumber(obj) { + return obj === obj + 0; + }, + isString: function isString(obj) { + return obj === obj + ''; + }, + isBoolean: function isBoolean(obj) { + return obj === false || obj === true; + }, + isFunction: function isFunction(obj) { + return obj instanceof Function; + } +}; + +var INTERPRETATIONS = [ +{ + litmus: Common.isString, + conversions: { + THREE_CHAR_HEX: { + read: function read(original) { + var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); + if (test === null) { + return false; + } + return { + space: 'HEX', + hex: parseInt('0x' + test[1].toString() + test[1].toString() + test[2].toString() + test[2].toString() + test[3].toString() + test[3].toString(), 0) + }; + }, + write: colorToString + }, + SIX_CHAR_HEX: { + read: function read(original) { + var test = original.match(/^#([A-F0-9]{6})$/i); + if (test === null) { + return false; + } + return { + space: 'HEX', + hex: parseInt('0x' + test[1].toString(), 0) + }; + }, + write: colorToString + }, + CSS_RGB: { + read: function read(original) { + var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); + if (test === null) { + return false; + } + return { + space: 'RGB', + r: parseFloat(test[1]), + g: parseFloat(test[2]), + b: parseFloat(test[3]) + }; + }, + write: colorToString + }, + CSS_RGBA: { + read: function read(original) { + var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); + if (test === null) { + return false; + } + return { + space: 'RGB', + r: parseFloat(test[1]), + g: parseFloat(test[2]), + b: parseFloat(test[3]), + a: parseFloat(test[4]) + }; + }, + write: colorToString + } + } +}, +{ + litmus: Common.isNumber, + conversions: { + HEX: { + read: function read(original) { + return { + space: 'HEX', + hex: original, + conversionName: 'HEX' + }; + }, + write: function write(color) { + return color.hex; + } + } + } +}, +{ + litmus: Common.isArray, + conversions: { + RGB_ARRAY: { + read: function read(original) { + if (original.length !== 3) { + return false; + } + return { + space: 'RGB', + r: original[0], + g: original[1], + b: original[2] + }; + }, + write: function write(color) { + return [color.r, color.g, color.b]; + } + }, + RGBA_ARRAY: { + read: function read(original) { + if (original.length !== 4) return false; + return { + space: 'RGB', + r: original[0], + g: original[1], + b: original[2], + a: original[3] + }; + }, + write: function write(color) { + return [color.r, color.g, color.b, color.a]; + } + } + } +}, +{ + litmus: Common.isObject, + conversions: { + RGBA_OBJ: { + read: function read(original) { + if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b) && Common.isNumber(original.a)) { + return { + space: 'RGB', + r: original.r, + g: original.g, + b: original.b, + a: original.a + }; + } + return false; + }, + write: function write(color) { + return { + r: color.r, + g: color.g, + b: color.b, + a: color.a + }; + } + }, + RGB_OBJ: { + read: function read(original) { + if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b)) { + return { + space: 'RGB', + r: original.r, + g: original.g, + b: original.b + }; + } + return false; + }, + write: function write(color) { + return { + r: color.r, + g: color.g, + b: color.b + }; + } + }, + HSVA_OBJ: { + read: function read(original) { + if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v) && Common.isNumber(original.a)) { + return { + space: 'HSV', + h: original.h, + s: original.s, + v: original.v, + a: original.a + }; + } + return false; + }, + write: function write(color) { + return { + h: color.h, + s: color.s, + v: color.v, + a: color.a + }; + } + }, + HSV_OBJ: { + read: function read(original) { + if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v)) { + return { + space: 'HSV', + h: original.h, + s: original.s, + v: original.v + }; + } + return false; + }, + write: function write(color) { + return { + h: color.h, + s: color.s, + v: color.v + }; + } + } + } +}]; +var result = void 0; +var toReturn = void 0; +var interpret = function interpret() { + toReturn = false; + var original = arguments.length > 1 ? Common.toArray(arguments) : arguments[0]; + Common.each(INTERPRETATIONS, function (family) { + if (family.litmus(original)) { + Common.each(family.conversions, function (conversion, conversionName) { + result = conversion.read(original); + if (toReturn === false && result !== false) { + toReturn = result; + result.conversionName = conversionName; + result.conversion = conversion; + return Common.BREAK; + } + }); + return Common.BREAK; + } + }); + return toReturn; +}; + +var tmpComponent = void 0; +var ColorMath = { + hsv_to_rgb: function hsv_to_rgb(h, s, v) { + var hi = Math.floor(h / 60) % 6; + var f = h / 60 - Math.floor(h / 60); + var p = v * (1.0 - s); + var q = v * (1.0 - f * s); + var t = v * (1.0 - (1.0 - f) * s); + var c = [[v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q]][hi]; + return { + r: c[0] * 255, + g: c[1] * 255, + b: c[2] * 255 + }; + }, + rgb_to_hsv: function rgb_to_hsv(r, g, b) { + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h = void 0; + var s = void 0; + if (max !== 0) { + s = delta / max; + } else { + return { + h: NaN, + s: 0, + v: 0 + }; + } + if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else { + h = 4 + (r - g) / delta; + } + h /= 6; + if (h < 0) { + h += 1; + } + return { + h: h * 360, + s: s, + v: max / 255 + }; + }, + rgb_to_hex: function rgb_to_hex(r, g, b) { + var hex = this.hex_with_component(0, 2, r); + hex = this.hex_with_component(hex, 1, g); + hex = this.hex_with_component(hex, 0, b); + return hex; + }, + component_from_hex: function component_from_hex(hex, componentIndex) { + return hex >> componentIndex * 8 & 0xFF; + }, + hex_with_component: function hex_with_component(hex, componentIndex, value) { + return value << (tmpComponent = componentIndex * 8) | hex & ~(0xFF << tmpComponent); + } +}; + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + + + + + + + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + + + + + + + +var get = function get(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + + + + + + + + + + + +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; + +var Color = function () { + function Color() { + classCallCheck(this, Color); + this.__state = interpret.apply(this, arguments); + if (this.__state === false) { + throw new Error('Failed to interpret color arguments'); + } + this.__state.a = this.__state.a || 1; + } + createClass(Color, [{ + key: 'toString', + value: function toString() { + return colorToString(this); + } + }, { + key: 'toHexString', + value: function toHexString() { + return colorToString(this, true); + } + }, { + key: 'toOriginal', + value: function toOriginal() { + return this.__state.conversion.write(this); + } + }]); + return Color; +}(); +function defineRGBComponent(target, component, componentHexIndex) { + Object.defineProperty(target, component, { + get: function get$$1() { + if (this.__state.space === 'RGB') { + return this.__state[component]; + } + Color.recalculateRGB(this, component, componentHexIndex); + return this.__state[component]; + }, + set: function set$$1(v) { + if (this.__state.space !== 'RGB') { + Color.recalculateRGB(this, component, componentHexIndex); + this.__state.space = 'RGB'; + } + this.__state[component] = v; + } + }); +} +function defineHSVComponent(target, component) { + Object.defineProperty(target, component, { + get: function get$$1() { + if (this.__state.space === 'HSV') { + return this.__state[component]; + } + Color.recalculateHSV(this); + return this.__state[component]; + }, + set: function set$$1(v) { + if (this.__state.space !== 'HSV') { + Color.recalculateHSV(this); + this.__state.space = 'HSV'; + } + this.__state[component] = v; + } + }); +} +Color.recalculateRGB = function (color, component, componentHexIndex) { + if (color.__state.space === 'HEX') { + color.__state[component] = ColorMath.component_from_hex(color.__state.hex, componentHexIndex); + } else if (color.__state.space === 'HSV') { + Common.extend(color.__state, ColorMath.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); + } else { + throw new Error('Corrupted color state'); + } +}; +Color.recalculateHSV = function (color) { + var result = ColorMath.rgb_to_hsv(color.r, color.g, color.b); + Common.extend(color.__state, { + s: result.s, + v: result.v + }); + if (!Common.isNaN(result.h)) { + color.__state.h = result.h; + } else if (Common.isUndefined(color.__state.h)) { + color.__state.h = 0; + } +}; +Color.COMPONENTS = ['r', 'g', 'b', 'h', 's', 'v', 'hex', 'a']; +defineRGBComponent(Color.prototype, 'r', 2); +defineRGBComponent(Color.prototype, 'g', 1); +defineRGBComponent(Color.prototype, 'b', 0); +defineHSVComponent(Color.prototype, 'h'); +defineHSVComponent(Color.prototype, 's'); +defineHSVComponent(Color.prototype, 'v'); +Object.defineProperty(Color.prototype, 'a', { + get: function get$$1() { + return this.__state.a; + }, + set: function set$$1(v) { + this.__state.a = v; + } +}); +Object.defineProperty(Color.prototype, 'hex', { + get: function get$$1() { + if (this.__state.space !== 'HEX') { + this.__state.hex = ColorMath.rgb_to_hex(this.r, this.g, this.b); + this.__state.space = 'HEX'; + } + return this.__state.hex; + }, + set: function set$$1(v) { + this.__state.space = 'HEX'; + this.__state.hex = v; + } +}); + +var Controller = function () { + function Controller(object, property) { + classCallCheck(this, Controller); + this.initialValue = object[property]; + this.domElement = document.createElement('div'); + this.object = object; + this.property = property; + this.__onChange = undefined; + this.__onFinishChange = undefined; + } + createClass(Controller, [{ + key: 'onChange', + value: function onChange(fnc) { + this.__onChange = fnc; + return this; + } + }, { + key: 'onFinishChange', + value: function onFinishChange(fnc) { + this.__onFinishChange = fnc; + return this; + } + }, { + key: 'setValue', + value: function setValue(newValue) { + this.object[this.property] = newValue; + if (this.__onChange) { + this.__onChange.call(this, newValue); + } + this.updateDisplay(); + return this; + } + }, { + key: 'getValue', + value: function getValue() { + return this.object[this.property]; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + return this; + } + }, { + key: 'isModified', + value: function isModified() { + return this.initialValue !== this.getValue(); + } + }]); + return Controller; +}(); + +var EVENT_MAP = { + HTMLEvents: ['change'], + MouseEvents: ['click', 'mousemove', 'mousedown', 'mouseup', 'mouseover'], + KeyboardEvents: ['keydown'] +}; +var EVENT_MAP_INV = {}; +Common.each(EVENT_MAP, function (v, k) { + Common.each(v, function (e) { + EVENT_MAP_INV[e] = k; + }); +}); +var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/; +function cssValueToPixels(val) { + if (val === '0' || Common.isUndefined(val)) { + return 0; + } + var match = val.match(CSS_VALUE_PIXELS); + if (!Common.isNull(match)) { + return parseFloat(match[1]); + } + return 0; +} +var dom = { + makeSelectable: function makeSelectable(elem, selectable) { + if (elem === undefined || elem.style === undefined) return; + elem.onselectstart = selectable ? function () { + return false; + } : function () {}; + elem.style.MozUserSelect = selectable ? 'auto' : 'none'; + elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none'; + elem.unselectable = selectable ? 'on' : 'off'; + }, + makeFullscreen: function makeFullscreen(elem, hor, vert) { + var vertical = vert; + var horizontal = hor; + if (Common.isUndefined(horizontal)) { + horizontal = true; + } + if (Common.isUndefined(vertical)) { + vertical = true; + } + elem.style.position = 'absolute'; + if (horizontal) { + elem.style.left = 0; + elem.style.right = 0; + } + if (vertical) { + elem.style.top = 0; + elem.style.bottom = 0; + } + }, + fakeEvent: function fakeEvent(elem, eventType, pars, aux) { + var params = pars || {}; + var className = EVENT_MAP_INV[eventType]; + if (!className) { + throw new Error('Event type ' + eventType + ' not supported.'); + } + var evt = document.createEvent(className); + switch (className) { + case 'MouseEvents': + { + var clientX = params.x || params.clientX || 0; + var clientY = params.y || params.clientY || 0; + evt.initMouseEvent(eventType, params.bubbles || false, params.cancelable || true, window, params.clickCount || 1, 0, + 0, + clientX, + clientY, + false, false, false, false, 0, null); + break; + } + case 'KeyboardEvents': + { + var init = evt.initKeyboardEvent || evt.initKeyEvent; + Common.defaults(params, { + cancelable: true, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: undefined, + charCode: undefined + }); + init(eventType, params.bubbles || false, params.cancelable, window, params.ctrlKey, params.altKey, params.shiftKey, params.metaKey, params.keyCode, params.charCode); + break; + } + default: + { + evt.initEvent(eventType, params.bubbles || false, params.cancelable || true); + break; + } + } + Common.defaults(evt, aux); + elem.dispatchEvent(evt); + }, + bind: function bind(elem, event, func, newBool) { + var bool = newBool || false; + if (elem.addEventListener) { + elem.addEventListener(event, func, bool); + } else if (elem.attachEvent) { + elem.attachEvent('on' + event, func); + } + return dom; + }, + unbind: function unbind(elem, event, func, newBool) { + var bool = newBool || false; + if (elem.removeEventListener) { + elem.removeEventListener(event, func, bool); + } else if (elem.detachEvent) { + elem.detachEvent('on' + event, func); + } + return dom; + }, + addClass: function addClass(elem, className) { + if (elem.className === undefined) { + elem.className = className; + } else if (elem.className !== className) { + var classes = elem.className.split(/ +/); + if (classes.indexOf(className) === -1) { + classes.push(className); + elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, ''); + } + } + return dom; + }, + removeClass: function removeClass(elem, className) { + if (className) { + if (elem.className === className) { + elem.removeAttribute('class'); + } else { + var classes = elem.className.split(/ +/); + var index = classes.indexOf(className); + if (index !== -1) { + classes.splice(index, 1); + elem.className = classes.join(' '); + } + } + } else { + elem.className = undefined; + } + return dom; + }, + hasClass: function hasClass(elem, className) { + return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false; + }, + getWidth: function getWidth(elem) { + var style = getComputedStyle(elem); + return cssValueToPixels(style['border-left-width']) + cssValueToPixels(style['border-right-width']) + cssValueToPixels(style['padding-left']) + cssValueToPixels(style['padding-right']) + cssValueToPixels(style.width); + }, + getHeight: function getHeight(elem) { + var style = getComputedStyle(elem); + return cssValueToPixels(style['border-top-width']) + cssValueToPixels(style['border-bottom-width']) + cssValueToPixels(style['padding-top']) + cssValueToPixels(style['padding-bottom']) + cssValueToPixels(style.height); + }, + getOffset: function getOffset(el) { + var elem = el; + var offset = { left: 0, top: 0 }; + if (elem.offsetParent) { + do { + offset.left += elem.offsetLeft; + offset.top += elem.offsetTop; + elem = elem.offsetParent; + } while (elem); + } + return offset; + }, + isActive: function isActive(elem) { + return elem === document.activeElement && (elem.type || elem.href); + } +}; + +var BooleanController = function (_Controller) { + inherits(BooleanController, _Controller); + function BooleanController(object, property) { + classCallCheck(this, BooleanController); + var _this2 = possibleConstructorReturn(this, (BooleanController.__proto__ || Object.getPrototypeOf(BooleanController)).call(this, object, property)); + var _this = _this2; + _this2.__prev = _this2.getValue(); + _this2.__checkbox = document.createElement('input'); + _this2.__checkbox.setAttribute('type', 'checkbox'); + function onChange() { + _this.setValue(!_this.__prev); + } + dom.bind(_this2.__checkbox, 'change', onChange, false); + _this2.domElement.appendChild(_this2.__checkbox); + _this2.updateDisplay(); + return _this2; + } + createClass(BooleanController, [{ + key: 'setValue', + value: function setValue(v) { + var toReturn = get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'setValue', this).call(this, v); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + this.__prev = this.getValue(); + return toReturn; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + if (this.getValue() === true) { + this.__checkbox.setAttribute('checked', 'checked'); + this.__checkbox.checked = true; + this.__prev = true; + } else { + this.__checkbox.checked = false; + this.__prev = false; + } + return get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'updateDisplay', this).call(this); + } + }]); + return BooleanController; +}(Controller); + +var OptionController = function (_Controller) { + inherits(OptionController, _Controller); + function OptionController(object, property, opts) { + classCallCheck(this, OptionController); + var _this2 = possibleConstructorReturn(this, (OptionController.__proto__ || Object.getPrototypeOf(OptionController)).call(this, object, property)); + var options = opts; + var _this = _this2; + _this2.__select = document.createElement('select'); + if (Common.isArray(options)) { + var map = {}; + Common.each(options, function (element) { + map[element] = element; + }); + options = map; + } + Common.each(options, function (value, key) { + var opt = document.createElement('option'); + opt.innerHTML = key; + opt.setAttribute('value', value); + _this.__select.appendChild(opt); + }); + _this2.updateDisplay(); + dom.bind(_this2.__select, 'change', function () { + var desiredValue = this.options[this.selectedIndex].value; + _this.setValue(desiredValue); + }); + _this2.domElement.appendChild(_this2.__select); + return _this2; + } + createClass(OptionController, [{ + key: 'setValue', + value: function setValue(v) { + var toReturn = get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'setValue', this).call(this, v); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + return toReturn; + } + }, { + key: 'updateDisplay', + value: function updateDisplay() { + if (dom.isActive(this.__select)) return this; + this.__select.value = this.getValue(); + return get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'updateDisplay', this).call(this); + } + }]); + return OptionController; +}(Controller); + +var StringController = function (_Controller) { + inherits(StringController, _Controller); + function StringController(object, property) { + classCallCheck(this, StringController); + var _this2 = possibleConstructorReturn(this, (StringController.__proto__ || Object.getPrototypeOf(StringController)).call(this, object, property)); + var _this = _this2; + function onChange() { + _this.setValue(_this.__input.value); + } + function onBlur() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + _this2.__input = document.createElement('input'); + _this2.__input.setAttribute('type', 'text'); + dom.bind(_this2.__input, 'keyup', onChange); + dom.bind(_this2.__input, 'change', onChange); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + this.blur(); + } + }); + _this2.updateDisplay(); + _this2.domElement.appendChild(_this2.__input); + return _this2; + } + createClass(StringController, [{ + key: 'updateDisplay', + value: function updateDisplay() { + if (!dom.isActive(this.__input)) { + this.__input.value = this.getValue(); + } + return get(StringController.prototype.__proto__ || Object.getPrototypeOf(StringController.prototype), 'updateDisplay', this).call(this); + } + }]); + return StringController; +}(Controller); + +function numDecimals(x) { + var _x = x.toString(); + if (_x.indexOf('.') > -1) { + return _x.length - _x.indexOf('.') - 1; + } + return 0; +} +var NumberController = function (_Controller) { + inherits(NumberController, _Controller); + function NumberController(object, property, params) { + classCallCheck(this, NumberController); + var _this = possibleConstructorReturn(this, (NumberController.__proto__ || Object.getPrototypeOf(NumberController)).call(this, object, property)); + var _params = params || {}; + _this.__min = _params.min; + _this.__max = _params.max; + _this.__step = _params.step; + if (Common.isUndefined(_this.__step)) { + if (_this.initialValue === 0) { + _this.__impliedStep = 1; + } else { + _this.__impliedStep = Math.pow(10, Math.floor(Math.log(Math.abs(_this.initialValue)) / Math.LN10)) / 10; + } + } else { + _this.__impliedStep = _this.__step; + } + _this.__precision = numDecimals(_this.__impliedStep); + return _this; + } + createClass(NumberController, [{ + key: 'setValue', + value: function setValue(v) { + var _v = v; + if (this.__min !== undefined && _v < this.__min) { + _v = this.__min; + } else if (this.__max !== undefined && _v > this.__max) { + _v = this.__max; + } + if (this.__step !== undefined && _v % this.__step !== 0) { + _v = Math.round(_v / this.__step) * this.__step; + } + return get(NumberController.prototype.__proto__ || Object.getPrototypeOf(NumberController.prototype), 'setValue', this).call(this, _v); + } + }, { + key: 'min', + value: function min(minValue) { + this.__min = minValue; + return this; + } + }, { + key: 'max', + value: function max(maxValue) { + this.__max = maxValue; + return this; + } + }, { + key: 'step', + value: function step(stepValue) { + this.__step = stepValue; + this.__impliedStep = stepValue; + this.__precision = numDecimals(stepValue); + return this; + } + }]); + return NumberController; +}(Controller); + +function roundToDecimal(value, decimals) { + var tenTo = Math.pow(10, decimals); + return Math.round(value * tenTo) / tenTo; +} +var NumberControllerBox = function (_NumberController) { + inherits(NumberControllerBox, _NumberController); + function NumberControllerBox(object, property, params) { + classCallCheck(this, NumberControllerBox); + var _this2 = possibleConstructorReturn(this, (NumberControllerBox.__proto__ || Object.getPrototypeOf(NumberControllerBox)).call(this, object, property, params)); + _this2.__truncationSuspended = false; + var _this = _this2; + var prevY = void 0; + function onChange() { + var attempted = parseFloat(_this.__input.value); + if (!Common.isNaN(attempted)) { + _this.setValue(attempted); + } + } + function onFinish() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + function onBlur() { + onFinish(); + } + function onMouseDrag(e) { + var diff = prevY - e.clientY; + _this.setValue(_this.getValue() + diff * _this.__impliedStep); + prevY = e.clientY; + } + function onMouseUp() { + dom.unbind(window, 'mousemove', onMouseDrag); + dom.unbind(window, 'mouseup', onMouseUp); + onFinish(); + } + function onMouseDown(e) { + dom.bind(window, 'mousemove', onMouseDrag); + dom.bind(window, 'mouseup', onMouseUp); + prevY = e.clientY; + } + _this2.__input = document.createElement('input'); + _this2.__input.setAttribute('type', 'text'); + dom.bind(_this2.__input, 'change', onChange); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__input, 'mousedown', onMouseDown); + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + _this.__truncationSuspended = true; + this.blur(); + _this.__truncationSuspended = false; + onFinish(); + } + }); + _this2.updateDisplay(); + _this2.domElement.appendChild(_this2.__input); + return _this2; + } + createClass(NumberControllerBox, [{ + key: 'updateDisplay', + value: function updateDisplay() { + this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision); + return get(NumberControllerBox.prototype.__proto__ || Object.getPrototypeOf(NumberControllerBox.prototype), 'updateDisplay', this).call(this); + } + }]); + return NumberControllerBox; +}(NumberController); + +function map(v, i1, i2, o1, o2) { + return o1 + (o2 - o1) * ((v - i1) / (i2 - i1)); +} +var NumberControllerSlider = function (_NumberController) { + inherits(NumberControllerSlider, _NumberController); + function NumberControllerSlider(object, property, min, max, step) { + classCallCheck(this, NumberControllerSlider); + var _this2 = possibleConstructorReturn(this, (NumberControllerSlider.__proto__ || Object.getPrototypeOf(NumberControllerSlider)).call(this, object, property, { min: min, max: max, step: step })); + var _this = _this2; + _this2.__background = document.createElement('div'); + _this2.__foreground = document.createElement('div'); + dom.bind(_this2.__background, 'mousedown', onMouseDown); + dom.bind(_this2.__background, 'touchstart', onTouchStart); + dom.addClass(_this2.__background, 'slider'); + dom.addClass(_this2.__foreground, 'slider-fg'); + function onMouseDown(e) { + document.activeElement.blur(); + dom.bind(window, 'mousemove', onMouseDrag); + dom.bind(window, 'mouseup', onMouseUp); + onMouseDrag(e); + } + function onMouseDrag(e) { + e.preventDefault(); + var bgRect = _this.__background.getBoundingClientRect(); + _this.setValue(map(e.clientX, bgRect.left, bgRect.right, _this.__min, _this.__max)); + return false; + } + function onMouseUp() { + dom.unbind(window, 'mousemove', onMouseDrag); + dom.unbind(window, 'mouseup', onMouseUp); + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + function onTouchStart(e) { + if (e.touches.length !== 1) { + return; + } + dom.bind(window, 'touchmove', onTouchMove); + dom.bind(window, 'touchend', onTouchEnd); + onTouchMove(e); + } + function onTouchMove(e) { + var clientX = e.touches[0].clientX; + var bgRect = _this.__background.getBoundingClientRect(); + _this.setValue(map(clientX, bgRect.left, bgRect.right, _this.__min, _this.__max)); + } + function onTouchEnd() { + dom.unbind(window, 'touchmove', onTouchMove); + dom.unbind(window, 'touchend', onTouchEnd); + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.getValue()); + } + } + _this2.updateDisplay(); + _this2.__background.appendChild(_this2.__foreground); + _this2.domElement.appendChild(_this2.__background); + return _this2; + } + createClass(NumberControllerSlider, [{ + key: 'updateDisplay', + value: function updateDisplay() { + var pct = (this.getValue() - this.__min) / (this.__max - this.__min); + this.__foreground.style.width = pct * 100 + '%'; + return get(NumberControllerSlider.prototype.__proto__ || Object.getPrototypeOf(NumberControllerSlider.prototype), 'updateDisplay', this).call(this); + } + }]); + return NumberControllerSlider; +}(NumberController); + +var FunctionController = function (_Controller) { + inherits(FunctionController, _Controller); + function FunctionController(object, property, text) { + classCallCheck(this, FunctionController); + var _this2 = possibleConstructorReturn(this, (FunctionController.__proto__ || Object.getPrototypeOf(FunctionController)).call(this, object, property)); + var _this = _this2; + _this2.__button = document.createElement('div'); + _this2.__button.innerHTML = text === undefined ? 'Fire' : text; + dom.bind(_this2.__button, 'click', function (e) { + e.preventDefault(); + _this.fire(); + return false; + }); + dom.addClass(_this2.__button, 'button'); + _this2.domElement.appendChild(_this2.__button); + return _this2; + } + createClass(FunctionController, [{ + key: 'fire', + value: function fire() { + if (this.__onChange) { + this.__onChange.call(this); + } + this.getValue().call(this.object); + if (this.__onFinishChange) { + this.__onFinishChange.call(this, this.getValue()); + } + } + }]); + return FunctionController; +}(Controller); + +var ColorController = function (_Controller) { + inherits(ColorController, _Controller); + function ColorController(object, property) { + classCallCheck(this, ColorController); + var _this2 = possibleConstructorReturn(this, (ColorController.__proto__ || Object.getPrototypeOf(ColorController)).call(this, object, property)); + _this2.__color = new Color(_this2.getValue()); + _this2.__temp = new Color(0); + var _this = _this2; + _this2.domElement = document.createElement('div'); + dom.makeSelectable(_this2.domElement, false); + _this2.__selector = document.createElement('div'); + _this2.__selector.className = 'selector'; + _this2.__saturation_field = document.createElement('div'); + _this2.__saturation_field.className = 'saturation-field'; + _this2.__field_knob = document.createElement('div'); + _this2.__field_knob.className = 'field-knob'; + _this2.__field_knob_border = '2px solid '; + _this2.__hue_knob = document.createElement('div'); + _this2.__hue_knob.className = 'hue-knob'; + _this2.__hue_field = document.createElement('div'); + _this2.__hue_field.className = 'hue-field'; + _this2.__input = document.createElement('input'); + _this2.__input.type = 'text'; + _this2.__input_textShadow = '0 1px 1px '; + dom.bind(_this2.__input, 'keydown', function (e) { + if (e.keyCode === 13) { + onBlur.call(this); + } + }); + dom.bind(_this2.__input, 'blur', onBlur); + dom.bind(_this2.__selector, 'mousedown', function () { + dom.addClass(this, 'drag').bind(window, 'mouseup', function () { + dom.removeClass(_this.__selector, 'drag'); + }); + }); + dom.bind(_this2.__selector, 'touchstart', function () { + dom.addClass(this, 'drag').bind(window, 'touchend', function () { + dom.removeClass(_this.__selector, 'drag'); + }); + }); + var valueField = document.createElement('div'); + Common.extend(_this2.__selector.style, { + width: '122px', + height: '102px', + padding: '3px', + backgroundColor: '#222', + boxShadow: '0px 1px 3px rgba(0,0,0,0.3)' + }); + Common.extend(_this2.__field_knob.style, { + position: 'absolute', + width: '12px', + height: '12px', + border: _this2.__field_knob_border + (_this2.__color.v < 0.5 ? '#fff' : '#000'), + boxShadow: '0px 1px 3px rgba(0,0,0,0.5)', + borderRadius: '12px', + zIndex: 1 + }); + Common.extend(_this2.__hue_knob.style, { + position: 'absolute', + width: '15px', + height: '2px', + borderRight: '4px solid #fff', + zIndex: 1 + }); + Common.extend(_this2.__saturation_field.style, { + width: '100px', + height: '100px', + border: '1px solid #555', + marginRight: '3px', + display: 'inline-block', + cursor: 'pointer' + }); + Common.extend(valueField.style, { + width: '100%', + height: '100%', + background: 'none' + }); + linearGradient(valueField, 'top', 'rgba(0,0,0,0)', '#000'); + Common.extend(_this2.__hue_field.style, { + width: '15px', + height: '100px', + border: '1px solid #555', + cursor: 'ns-resize', + position: 'absolute', + top: '3px', + right: '3px' + }); + hueGradient(_this2.__hue_field); + Common.extend(_this2.__input.style, { + outline: 'none', + textAlign: 'center', + color: '#fff', + border: 0, + fontWeight: 'bold', + textShadow: _this2.__input_textShadow + 'rgba(0,0,0,0.7)' + }); + dom.bind(_this2.__saturation_field, 'mousedown', fieldDown); + dom.bind(_this2.__saturation_field, 'touchstart', fieldDown); + dom.bind(_this2.__field_knob, 'mousedown', fieldDown); + dom.bind(_this2.__field_knob, 'touchstart', fieldDown); + dom.bind(_this2.__hue_field, 'mousedown', fieldDownH); + dom.bind(_this2.__hue_field, 'touchstart', fieldDownH); + function fieldDown(e) { + setSV(e); + dom.bind(window, 'mousemove', setSV); + dom.bind(window, 'touchmove', setSV); + dom.bind(window, 'mouseup', fieldUpSV); + dom.bind(window, 'touchend', fieldUpSV); + } + function fieldDownH(e) { + setH(e); + dom.bind(window, 'mousemove', setH); + dom.bind(window, 'touchmove', setH); + dom.bind(window, 'mouseup', fieldUpH); + dom.bind(window, 'touchend', fieldUpH); + } + function fieldUpSV() { + dom.unbind(window, 'mousemove', setSV); + dom.unbind(window, 'touchmove', setSV); + dom.unbind(window, 'mouseup', fieldUpSV); + dom.unbind(window, 'touchend', fieldUpSV); + onFinish(); + } + function fieldUpH() { + dom.unbind(window, 'mousemove', setH); + dom.unbind(window, 'touchmove', setH); + dom.unbind(window, 'mouseup', fieldUpH); + dom.unbind(window, 'touchend', fieldUpH); + onFinish(); + } + function onBlur() { + var i = interpret(this.value); + if (i !== false) { + _this.__color.__state = i; + _this.setValue(_this.__color.toOriginal()); + } else { + this.value = _this.__color.toString(); + } + } + function onFinish() { + if (_this.__onFinishChange) { + _this.__onFinishChange.call(_this, _this.__color.toOriginal()); + } + } + _this2.__saturation_field.appendChild(valueField); + _this2.__selector.appendChild(_this2.__field_knob); + _this2.__selector.appendChild(_this2.__saturation_field); + _this2.__selector.appendChild(_this2.__hue_field); + _this2.__hue_field.appendChild(_this2.__hue_knob); + _this2.domElement.appendChild(_this2.__input); + _this2.domElement.appendChild(_this2.__selector); + _this2.updateDisplay(); + function setSV(e) { + if (e.type.indexOf('touch') === -1) { + e.preventDefault(); + } + var fieldRect = _this.__saturation_field.getBoundingClientRect(); + var _ref = e.touches && e.touches[0] || e, + clientX = _ref.clientX, + clientY = _ref.clientY; + var s = (clientX - fieldRect.left) / (fieldRect.right - fieldRect.left); + var v = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top); + if (v > 1) { + v = 1; + } else if (v < 0) { + v = 0; + } + if (s > 1) { + s = 1; + } else if (s < 0) { + s = 0; + } + _this.__color.v = v; + _this.__color.s = s; + _this.setValue(_this.__color.toOriginal()); + return false; + } + function setH(e) { + if (e.type.indexOf('touch') === -1) { + e.preventDefault(); + } + var fieldRect = _this.__hue_field.getBoundingClientRect(); + var _ref2 = e.touches && e.touches[0] || e, + clientY = _ref2.clientY; + var h = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top); + if (h > 1) { + h = 1; + } else if (h < 0) { + h = 0; + } + _this.__color.h = h * 360; + _this.setValue(_this.__color.toOriginal()); + return false; + } + return _this2; + } + createClass(ColorController, [{ + key: 'updateDisplay', + value: function updateDisplay() { + var i = interpret(this.getValue()); + if (i !== false) { + var mismatch = false; + Common.each(Color.COMPONENTS, function (component) { + if (!Common.isUndefined(i[component]) && !Common.isUndefined(this.__color.__state[component]) && i[component] !== this.__color.__state[component]) { + mismatch = true; + return {}; + } + }, this); + if (mismatch) { + Common.extend(this.__color.__state, i); + } + } + Common.extend(this.__temp.__state, this.__color.__state); + this.__temp.a = 1; + var flip = this.__color.v < 0.5 || this.__color.s > 0.5 ? 255 : 0; + var _flip = 255 - flip; + Common.extend(this.__field_knob.style, { + marginLeft: 100 * this.__color.s - 7 + 'px', + marginTop: 100 * (1 - this.__color.v) - 7 + 'px', + backgroundColor: this.__temp.toHexString(), + border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip + ')' + }); + this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px'; + this.__temp.s = 1; + this.__temp.v = 1; + linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toHexString()); + this.__input.value = this.__color.toString(); + Common.extend(this.__input.style, { + backgroundColor: this.__color.toHexString(), + color: 'rgb(' + flip + ',' + flip + ',' + flip + ')', + textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip + ',.7)' + }); + } + }]); + return ColorController; +}(Controller); +var vendors = ['-moz-', '-o-', '-webkit-', '-ms-', '']; +function linearGradient(elem, x, a, b) { + elem.style.background = ''; + Common.each(vendors, function (vendor) { + elem.style.cssText += 'background: ' + vendor + 'linear-gradient(' + x + ', ' + a + ' 0%, ' + b + ' 100%); '; + }); +} +function hueGradient(elem) { + elem.style.background = ''; + elem.style.cssText += 'background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);'; + elem.style.cssText += 'background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; + elem.style.cssText += 'background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);'; +} + +var css = { + load: function load(url, indoc) { + var doc = indoc || document; + var link = doc.createElement('link'); + link.type = 'text/css'; + link.rel = 'stylesheet'; + link.href = url; + doc.getElementsByTagName('head')[0].appendChild(link); + }, + inject: function inject(cssContent, indoc) { + var doc = indoc || document; + var injected = document.createElement('style'); + injected.type = 'text/css'; + injected.innerHTML = cssContent; + var head = doc.getElementsByTagName('head')[0]; + try { + head.appendChild(injected); + } catch (e) { + } + } +}; + +var saveDialogContents = "
\n\n Here's the new load parameter for your GUI's constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI's constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n\n
\n\n
\n\n
"; + +var ControllerFactory = function ControllerFactory(object, property) { + var initialValue = object[property]; + if (Common.isArray(arguments[2]) || Common.isObject(arguments[2])) { + return new OptionController(object, property, arguments[2]); + } + if (Common.isNumber(initialValue)) { + if (Common.isNumber(arguments[2]) && Common.isNumber(arguments[3])) { + if (Common.isNumber(arguments[4])) { + return new NumberControllerSlider(object, property, arguments[2], arguments[3], arguments[4]); + } + return new NumberControllerSlider(object, property, arguments[2], arguments[3]); + } + if (Common.isNumber(arguments[4])) { + return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3], step: arguments[4] }); + } + return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] }); + } + if (Common.isString(initialValue)) { + return new StringController(object, property); + } + if (Common.isFunction(initialValue)) { + return new FunctionController(object, property, ''); + } + if (Common.isBoolean(initialValue)) { + return new BooleanController(object, property); + } + return null; +}; + +function requestAnimationFrame(callback) { + setTimeout(callback, 1000 / 60); +} +var requestAnimationFrame$1 = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || requestAnimationFrame; + +var CenteredDiv = function () { + function CenteredDiv() { + classCallCheck(this, CenteredDiv); + this.backgroundElement = document.createElement('div'); + Common.extend(this.backgroundElement.style, { + backgroundColor: 'rgba(0,0,0,0.8)', + top: 0, + left: 0, + display: 'none', + zIndex: '1000', + opacity: 0, + WebkitTransition: 'opacity 0.2s linear', + transition: 'opacity 0.2s linear' + }); + dom.makeFullscreen(this.backgroundElement); + this.backgroundElement.style.position = 'fixed'; + this.domElement = document.createElement('div'); + Common.extend(this.domElement.style, { + position: 'fixed', + display: 'none', + zIndex: '1001', + opacity: 0, + WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear', + transition: 'transform 0.2s ease-out, opacity 0.2s linear' + }); + document.body.appendChild(this.backgroundElement); + document.body.appendChild(this.domElement); + var _this = this; + dom.bind(this.backgroundElement, 'click', function () { + _this.hide(); + }); + } + createClass(CenteredDiv, [{ + key: 'show', + value: function show() { + var _this = this; + this.backgroundElement.style.display = 'block'; + this.domElement.style.display = 'block'; + this.domElement.style.opacity = 0; + this.domElement.style.webkitTransform = 'scale(1.1)'; + this.layout(); + Common.defer(function () { + _this.backgroundElement.style.opacity = 1; + _this.domElement.style.opacity = 1; + _this.domElement.style.webkitTransform = 'scale(1)'; + }); + } + }, { + key: 'hide', + value: function hide() { + var _this = this; + var hide = function hide() { + _this.domElement.style.display = 'none'; + _this.backgroundElement.style.display = 'none'; + dom.unbind(_this.domElement, 'webkitTransitionEnd', hide); + dom.unbind(_this.domElement, 'transitionend', hide); + dom.unbind(_this.domElement, 'oTransitionEnd', hide); + }; + dom.bind(this.domElement, 'webkitTransitionEnd', hide); + dom.bind(this.domElement, 'transitionend', hide); + dom.bind(this.domElement, 'oTransitionEnd', hide); + this.backgroundElement.style.opacity = 0; + this.domElement.style.opacity = 0; + this.domElement.style.webkitTransform = 'scale(1.1)'; + } + }, { + key: 'layout', + value: function layout() { + this.domElement.style.left = window.innerWidth / 2 - dom.getWidth(this.domElement) / 2 + 'px'; + this.domElement.style.top = window.innerHeight / 2 - dom.getHeight(this.domElement) / 2 + 'px'; + } + }]); + return CenteredDiv; +}(); + +var styleSheet = ___$insertStyle(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n"); + +css.inject(styleSheet); +var CSS_NAMESPACE = 'dg'; +var HIDE_KEY_CODE = 72; +var CLOSE_BUTTON_HEIGHT = 20; +var DEFAULT_DEFAULT_PRESET_NAME = 'Default'; +var SUPPORTS_LOCAL_STORAGE = function () { + try { + return !!window.localStorage; + } catch (e) { + return false; + } +}(); +var SAVE_DIALOGUE = void 0; +var autoPlaceVirgin = true; +var autoPlaceContainer = void 0; +var hide = false; +var hideableGuis = []; +var GUI = function GUI(pars) { + var _this = this; + var params = pars || {}; + this.domElement = document.createElement('div'); + this.__ul = document.createElement('ul'); + this.domElement.appendChild(this.__ul); + dom.addClass(this.domElement, CSS_NAMESPACE); + this.__folders = {}; + this.__controllers = []; + this.__rememberedObjects = []; + this.__rememberedObjectIndecesToControllers = []; + this.__listening = []; + params = Common.defaults(params, { + closeOnTop: false, + autoPlace: true, + width: GUI.DEFAULT_WIDTH + }); + params = Common.defaults(params, { + resizable: params.autoPlace, + hideable: params.autoPlace + }); + if (!Common.isUndefined(params.load)) { + if (params.preset) { + params.load.preset = params.preset; + } + } else { + params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME }; + } + if (Common.isUndefined(params.parent) && params.hideable) { + hideableGuis.push(this); + } + params.resizable = Common.isUndefined(params.parent) && params.resizable; + if (params.autoPlace && Common.isUndefined(params.scrollable)) { + params.scrollable = true; + } + var useLocalStorage = SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true'; + var saveToLocalStorage = void 0; + var titleRow = void 0; + Object.defineProperties(this, + { + parent: { + get: function get$$1() { + return params.parent; + } + }, + scrollable: { + get: function get$$1() { + return params.scrollable; + } + }, + autoPlace: { + get: function get$$1() { + return params.autoPlace; + } + }, + closeOnTop: { + get: function get$$1() { + return params.closeOnTop; + } + }, + preset: { + get: function get$$1() { + if (_this.parent) { + return _this.getRoot().preset; + } + return params.load.preset; + }, + set: function set$$1(v) { + if (_this.parent) { + _this.getRoot().preset = v; + } else { + params.load.preset = v; + } + setPresetSelectIndex(this); + _this.revert(); + } + }, + width: { + get: function get$$1() { + return params.width; + }, + set: function set$$1(v) { + params.width = v; + setWidth(_this, v); + } + }, + name: { + get: function get$$1() { + return params.name; + }, + set: function set$$1(v) { + params.name = v; + if (titleRow) { + titleRow.innerHTML = params.name; + } + } + }, + closed: { + get: function get$$1() { + return params.closed; + }, + set: function set$$1(v) { + params.closed = v; + if (params.closed) { + dom.addClass(_this.__ul, GUI.CLASS_CLOSED); + } else { + dom.removeClass(_this.__ul, GUI.CLASS_CLOSED); + } + this.onResize(); + if (_this.__closeButton) { + _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED; + } + } + }, + load: { + get: function get$$1() { + return params.load; + } + }, + useLocalStorage: { + get: function get$$1() { + return useLocalStorage; + }, + set: function set$$1(bool) { + if (SUPPORTS_LOCAL_STORAGE) { + useLocalStorage = bool; + if (bool) { + dom.bind(window, 'unload', saveToLocalStorage); + } else { + dom.unbind(window, 'unload', saveToLocalStorage); + } + localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool); + } + } + } + }); + if (Common.isUndefined(params.parent)) { + this.closed = params.closed || false; + dom.addClass(this.domElement, GUI.CLASS_MAIN); + dom.makeSelectable(this.domElement, false); + if (SUPPORTS_LOCAL_STORAGE) { + if (useLocalStorage) { + _this.useLocalStorage = true; + var savedGui = localStorage.getItem(getLocalStorageHash(this, 'gui')); + if (savedGui) { + params.load = JSON.parse(savedGui); + } + } + } + this.__closeButton = document.createElement('div'); + this.__closeButton.innerHTML = GUI.TEXT_CLOSED; + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON); + if (params.closeOnTop) { + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_TOP); + this.domElement.insertBefore(this.__closeButton, this.domElement.childNodes[0]); + } else { + dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BOTTOM); + this.domElement.appendChild(this.__closeButton); + } + dom.bind(this.__closeButton, 'click', function () { + _this.closed = !_this.closed; + }); + } else { + if (params.closed === undefined) { + params.closed = true; + } + var titleRowName = document.createTextNode(params.name); + dom.addClass(titleRowName, 'controller-name'); + titleRow = addRow(_this, titleRowName); + var onClickTitle = function onClickTitle(e) { + e.preventDefault(); + _this.closed = !_this.closed; + return false; + }; + dom.addClass(this.__ul, GUI.CLASS_CLOSED); + dom.addClass(titleRow, 'title'); + dom.bind(titleRow, 'click', onClickTitle); + if (!params.closed) { + this.closed = false; + } + } + if (params.autoPlace) { + if (Common.isUndefined(params.parent)) { + if (autoPlaceVirgin) { + autoPlaceContainer = document.createElement('div'); + dom.addClass(autoPlaceContainer, CSS_NAMESPACE); + dom.addClass(autoPlaceContainer, GUI.CLASS_AUTO_PLACE_CONTAINER); + document.body.appendChild(autoPlaceContainer); + autoPlaceVirgin = false; + } + autoPlaceContainer.appendChild(this.domElement); + dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE); + } + if (!this.parent) { + setWidth(_this, params.width); + } + } + this.__resizeHandler = function () { + _this.onResizeDebounced(); + }; + dom.bind(window, 'resize', this.__resizeHandler); + dom.bind(this.__ul, 'webkitTransitionEnd', this.__resizeHandler); + dom.bind(this.__ul, 'transitionend', this.__resizeHandler); + dom.bind(this.__ul, 'oTransitionEnd', this.__resizeHandler); + this.onResize(); + if (params.resizable) { + addResizeHandle(this); + } + saveToLocalStorage = function saveToLocalStorage() { + if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') { + localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject())); + } + }; + this.saveToLocalStorageIfPossible = saveToLocalStorage; + function resetWidth() { + var root = _this.getRoot(); + root.width += 1; + Common.defer(function () { + root.width -= 1; + }); + } + if (!params.parent) { + resetWidth(); + } +}; +GUI.toggleHide = function () { + hide = !hide; + Common.each(hideableGuis, function (gui) { + gui.domElement.style.display = hide ? 'none' : ''; + }); +}; +GUI.CLASS_AUTO_PLACE = 'a'; +GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac'; +GUI.CLASS_MAIN = 'main'; +GUI.CLASS_CONTROLLER_ROW = 'cr'; +GUI.CLASS_TOO_TALL = 'taller-than-window'; +GUI.CLASS_CLOSED = 'closed'; +GUI.CLASS_CLOSE_BUTTON = 'close-button'; +GUI.CLASS_CLOSE_TOP = 'close-top'; +GUI.CLASS_CLOSE_BOTTOM = 'close-bottom'; +GUI.CLASS_DRAG = 'drag'; +GUI.DEFAULT_WIDTH = 245; +GUI.TEXT_CLOSED = 'Close Controls'; +GUI.TEXT_OPEN = 'Open Controls'; +GUI._keydownHandler = function (e) { + if (document.activeElement.type !== 'text' && (e.which === HIDE_KEY_CODE || e.keyCode === HIDE_KEY_CODE)) { + GUI.toggleHide(); + } +}; +dom.bind(window, 'keydown', GUI._keydownHandler, false); +Common.extend(GUI.prototype, +{ + add: function add(object, property) { + return _add(this, object, property, { + factoryArgs: Array.prototype.slice.call(arguments, 2) + }); + }, + addColor: function addColor(object, property) { + return _add(this, object, property, { + color: true + }); + }, + remove: function remove(controller) { + this.__ul.removeChild(controller.__li); + this.__controllers.splice(this.__controllers.indexOf(controller), 1); + var _this = this; + Common.defer(function () { + _this.onResize(); + }); + }, + destroy: function destroy() { + if (this.parent) { + throw new Error('Only the root GUI should be removed with .destroy(). ' + 'For subfolders, use gui.removeFolder(folder) instead.'); + } + if (this.autoPlace) { + autoPlaceContainer.removeChild(this.domElement); + } + var _this = this; + Common.each(this.__folders, function (subfolder) { + _this.removeFolder(subfolder); + }); + dom.unbind(window, 'keydown', GUI._keydownHandler, false); + removeListeners(this); + }, + addFolder: function addFolder(name) { + if (this.__folders[name] !== undefined) { + throw new Error('You already have a folder in this GUI by the' + ' name "' + name + '"'); + } + var newGuiParams = { name: name, parent: this }; + newGuiParams.autoPlace = this.autoPlace; + if (this.load && + this.load.folders && + this.load.folders[name]) { + newGuiParams.closed = this.load.folders[name].closed; + newGuiParams.load = this.load.folders[name]; + } + var gui = new GUI(newGuiParams); + this.__folders[name] = gui; + var li = addRow(this, gui.domElement); + dom.addClass(li, 'folder'); + return gui; + }, + removeFolder: function removeFolder(folder) { + this.__ul.removeChild(folder.domElement.parentElement); + delete this.__folders[folder.name]; + if (this.load && + this.load.folders && + this.load.folders[folder.name]) { + delete this.load.folders[folder.name]; + } + removeListeners(folder); + var _this = this; + Common.each(folder.__folders, function (subfolder) { + folder.removeFolder(subfolder); + }); + Common.defer(function () { + _this.onResize(); + }); + }, + open: function open() { + this.closed = false; + }, + close: function close() { + this.closed = true; + }, + hide: function hide() { + this.domElement.style.display = 'none'; + }, + show: function show() { + this.domElement.style.display = ''; + }, + onResize: function onResize() { + var root = this.getRoot(); + if (root.scrollable) { + var top = dom.getOffset(root.__ul).top; + var h = 0; + Common.each(root.__ul.childNodes, function (node) { + if (!(root.autoPlace && node === root.__save_row)) { + h += dom.getHeight(node); + } + }); + if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) { + dom.addClass(root.domElement, GUI.CLASS_TOO_TALL); + root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px'; + } else { + dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL); + root.__ul.style.height = 'auto'; + } + } + if (root.__resize_handle) { + Common.defer(function () { + root.__resize_handle.style.height = root.__ul.offsetHeight + 'px'; + }); + } + if (root.__closeButton) { + root.__closeButton.style.width = root.width + 'px'; + } + }, + onResizeDebounced: Common.debounce(function () { + this.onResize(); + }, 50), + remember: function remember() { + if (Common.isUndefined(SAVE_DIALOGUE)) { + SAVE_DIALOGUE = new CenteredDiv(); + SAVE_DIALOGUE.domElement.innerHTML = saveDialogContents; + } + if (this.parent) { + throw new Error('You can only call remember on a top level GUI.'); + } + var _this = this; + Common.each(Array.prototype.slice.call(arguments), function (object) { + if (_this.__rememberedObjects.length === 0) { + addSaveMenu(_this); + } + if (_this.__rememberedObjects.indexOf(object) === -1) { + _this.__rememberedObjects.push(object); + } + }); + if (this.autoPlace) { + setWidth(this, this.width); + } + }, + getRoot: function getRoot() { + var gui = this; + while (gui.parent) { + gui = gui.parent; + } + return gui; + }, + getSaveObject: function getSaveObject() { + var toReturn = this.load; + toReturn.closed = this.closed; + if (this.__rememberedObjects.length > 0) { + toReturn.preset = this.preset; + if (!toReturn.remembered) { + toReturn.remembered = {}; + } + toReturn.remembered[this.preset] = getCurrentPreset(this); + } + toReturn.folders = {}; + Common.each(this.__folders, function (element, key) { + toReturn.folders[key] = element.getSaveObject(); + }); + return toReturn; + }, + save: function save() { + if (!this.load.remembered) { + this.load.remembered = {}; + } + this.load.remembered[this.preset] = getCurrentPreset(this); + markPresetModified(this, false); + this.saveToLocalStorageIfPossible(); + }, + saveAs: function saveAs(presetName) { + if (!this.load.remembered) { + this.load.remembered = {}; + this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true); + } + this.load.remembered[presetName] = getCurrentPreset(this); + this.preset = presetName; + addPresetOption(this, presetName, true); + this.saveToLocalStorageIfPossible(); + }, + revert: function revert(gui) { + Common.each(this.__controllers, function (controller) { + if (!this.getRoot().load.remembered) { + controller.setValue(controller.initialValue); + } else { + recallSavedValue(gui || this.getRoot(), controller); + } + if (controller.__onFinishChange) { + controller.__onFinishChange.call(controller, controller.getValue()); + } + }, this); + Common.each(this.__folders, function (folder) { + folder.revert(folder); + }); + if (!gui) { + markPresetModified(this.getRoot(), false); + } + }, + listen: function listen(controller) { + var init = this.__listening.length === 0; + this.__listening.push(controller); + if (init) { + updateDisplays(this.__listening); + } + }, + updateDisplay: function updateDisplay() { + Common.each(this.__controllers, function (controller) { + controller.updateDisplay(); + }); + Common.each(this.__folders, function (folder) { + folder.updateDisplay(); + }); + } +}); +function addRow(gui, newDom, liBefore) { + var li = document.createElement('li'); + if (newDom) { + li.appendChild(newDom); + } + if (liBefore) { + gui.__ul.insertBefore(li, liBefore); + } else { + gui.__ul.appendChild(li); + } + gui.onResize(); + return li; +} +function removeListeners(gui) { + dom.unbind(window, 'resize', gui.__resizeHandler); + if (gui.saveToLocalStorageIfPossible) { + dom.unbind(window, 'unload', gui.saveToLocalStorageIfPossible); + } +} +function markPresetModified(gui, modified) { + var opt = gui.__preset_select[gui.__preset_select.selectedIndex]; + if (modified) { + opt.innerHTML = opt.value + '*'; + } else { + opt.innerHTML = opt.value; + } +} +function augmentController(gui, li, controller) { + controller.__li = li; + controller.__gui = gui; + Common.extend(controller, { + options: function options(_options) { + if (arguments.length > 1) { + var nextSibling = controller.__li.nextElementSibling; + controller.remove(); + return _add(gui, controller.object, controller.property, { + before: nextSibling, + factoryArgs: [Common.toArray(arguments)] + }); + } + if (Common.isArray(_options) || Common.isObject(_options)) { + var _nextSibling = controller.__li.nextElementSibling; + controller.remove(); + return _add(gui, controller.object, controller.property, { + before: _nextSibling, + factoryArgs: [_options] + }); + } + }, + name: function name(_name) { + controller.__li.firstElementChild.firstElementChild.innerHTML = _name; + return controller; + }, + listen: function listen() { + controller.__gui.listen(controller); + return controller; + }, + remove: function remove() { + controller.__gui.remove(controller); + return controller; + } + }); + if (controller instanceof NumberControllerSlider) { + var box = new NumberControllerBox(controller.object, controller.property, { min: controller.__min, max: controller.__max, step: controller.__step }); + Common.each(['updateDisplay', 'onChange', 'onFinishChange', 'step', 'min', 'max'], function (method) { + var pc = controller[method]; + var pb = box[method]; + controller[method] = box[method] = function () { + var args = Array.prototype.slice.call(arguments); + pb.apply(box, args); + return pc.apply(controller, args); + }; + }); + dom.addClass(li, 'has-slider'); + controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild); + } else if (controller instanceof NumberControllerBox) { + var r = function r(returned) { + if (Common.isNumber(controller.__min) && Common.isNumber(controller.__max)) { + var oldName = controller.__li.firstElementChild.firstElementChild.innerHTML; + var wasListening = controller.__gui.__listening.indexOf(controller) > -1; + controller.remove(); + var newController = _add(gui, controller.object, controller.property, { + before: controller.__li.nextElementSibling, + factoryArgs: [controller.__min, controller.__max, controller.__step] + }); + newController.name(oldName); + if (wasListening) newController.listen(); + return newController; + } + return returned; + }; + controller.min = Common.compose(r, controller.min); + controller.max = Common.compose(r, controller.max); + } else if (controller instanceof BooleanController) { + dom.bind(li, 'click', function () { + dom.fakeEvent(controller.__checkbox, 'click'); + }); + dom.bind(controller.__checkbox, 'click', function (e) { + e.stopPropagation(); + }); + } else if (controller instanceof FunctionController) { + dom.bind(li, 'click', function () { + dom.fakeEvent(controller.__button, 'click'); + }); + dom.bind(li, 'mouseover', function () { + dom.addClass(controller.__button, 'hover'); + }); + dom.bind(li, 'mouseout', function () { + dom.removeClass(controller.__button, 'hover'); + }); + } else if (controller instanceof ColorController) { + dom.addClass(li, 'color'); + controller.updateDisplay = Common.compose(function (val) { + li.style.borderLeftColor = controller.__color.toString(); + return val; + }, controller.updateDisplay); + controller.updateDisplay(); + } + controller.setValue = Common.compose(function (val) { + if (gui.getRoot().__preset_select && controller.isModified()) { + markPresetModified(gui.getRoot(), true); + } + return val; + }, controller.setValue); +} +function recallSavedValue(gui, controller) { + var root = gui.getRoot(); + var matchedIndex = root.__rememberedObjects.indexOf(controller.object); + if (matchedIndex !== -1) { + var controllerMap = root.__rememberedObjectIndecesToControllers[matchedIndex]; + if (controllerMap === undefined) { + controllerMap = {}; + root.__rememberedObjectIndecesToControllers[matchedIndex] = controllerMap; + } + controllerMap[controller.property] = controller; + if (root.load && root.load.remembered) { + var presetMap = root.load.remembered; + var preset = void 0; + if (presetMap[gui.preset]) { + preset = presetMap[gui.preset]; + } else if (presetMap[DEFAULT_DEFAULT_PRESET_NAME]) { + preset = presetMap[DEFAULT_DEFAULT_PRESET_NAME]; + } else { + return; + } + if (preset[matchedIndex] && preset[matchedIndex][controller.property] !== undefined) { + var value = preset[matchedIndex][controller.property]; + controller.initialValue = value; + controller.setValue(value); + } + } + } +} +function _add(gui, object, property, params) { + if (object[property] === undefined) { + throw new Error('Object "' + object + '" has no property "' + property + '"'); + } + var controller = void 0; + if (params.color) { + controller = new ColorController(object, property); + } else { + var factoryArgs = [object, property].concat(params.factoryArgs); + controller = ControllerFactory.apply(gui, factoryArgs); + } + if (params.before instanceof Controller) { + params.before = params.before.__li; + } + recallSavedValue(gui, controller); + dom.addClass(controller.domElement, 'c'); + var name = document.createElement('span'); + dom.addClass(name, 'property-name'); + name.innerHTML = controller.property; + var container = document.createElement('div'); + container.appendChild(name); + container.appendChild(controller.domElement); + var li = addRow(gui, container, params.before); + dom.addClass(li, GUI.CLASS_CONTROLLER_ROW); + if (controller instanceof ColorController) { + dom.addClass(li, 'color'); + } else { + dom.addClass(li, _typeof(controller.getValue())); + } + augmentController(gui, li, controller); + gui.__controllers.push(controller); + return controller; +} +function getLocalStorageHash(gui, key) { + return document.location.href + '.' + key; +} +function addPresetOption(gui, name, setSelected) { + var opt = document.createElement('option'); + opt.innerHTML = name; + opt.value = name; + gui.__preset_select.appendChild(opt); + if (setSelected) { + gui.__preset_select.selectedIndex = gui.__preset_select.length - 1; + } +} +function showHideExplain(gui, explain) { + explain.style.display = gui.useLocalStorage ? 'block' : 'none'; +} +function addSaveMenu(gui) { + var div = gui.__save_row = document.createElement('li'); + dom.addClass(gui.domElement, 'has-save'); + gui.__ul.insertBefore(div, gui.__ul.firstChild); + dom.addClass(div, 'save-row'); + var gears = document.createElement('span'); + gears.innerHTML = ' '; + dom.addClass(gears, 'button gears'); + var button = document.createElement('span'); + button.innerHTML = 'Save'; + dom.addClass(button, 'button'); + dom.addClass(button, 'save'); + var button2 = document.createElement('span'); + button2.innerHTML = 'New'; + dom.addClass(button2, 'button'); + dom.addClass(button2, 'save-as'); + var button3 = document.createElement('span'); + button3.innerHTML = 'Revert'; + dom.addClass(button3, 'button'); + dom.addClass(button3, 'revert'); + var select = gui.__preset_select = document.createElement('select'); + if (gui.load && gui.load.remembered) { + Common.each(gui.load.remembered, function (value, key) { + addPresetOption(gui, key, key === gui.preset); + }); + } else { + addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false); + } + dom.bind(select, 'change', function () { + for (var index = 0; index < gui.__preset_select.length; index++) { + gui.__preset_select[index].innerHTML = gui.__preset_select[index].value; + } + gui.preset = this.value; + }); + div.appendChild(select); + div.appendChild(gears); + div.appendChild(button); + div.appendChild(button2); + div.appendChild(button3); + if (SUPPORTS_LOCAL_STORAGE) { + var explain = document.getElementById('dg-local-explain'); + var localStorageCheckBox = document.getElementById('dg-local-storage'); + var saveLocally = document.getElementById('dg-save-locally'); + saveLocally.style.display = 'block'; + if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') { + localStorageCheckBox.setAttribute('checked', 'checked'); + } + showHideExplain(gui, explain); + dom.bind(localStorageCheckBox, 'change', function () { + gui.useLocalStorage = !gui.useLocalStorage; + showHideExplain(gui, explain); + }); + } + var newConstructorTextArea = document.getElementById('dg-new-constructor'); + dom.bind(newConstructorTextArea, 'keydown', function (e) { + if (e.metaKey && (e.which === 67 || e.keyCode === 67)) { + SAVE_DIALOGUE.hide(); + } + }); + dom.bind(gears, 'click', function () { + newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2); + SAVE_DIALOGUE.show(); + newConstructorTextArea.focus(); + newConstructorTextArea.select(); + }); + dom.bind(button, 'click', function () { + gui.save(); + }); + dom.bind(button2, 'click', function () { + var presetName = prompt('Enter a new preset name.'); + if (presetName) { + gui.saveAs(presetName); + } + }); + dom.bind(button3, 'click', function () { + gui.revert(); + }); +} +function addResizeHandle(gui) { + var pmouseX = void 0; + gui.__resize_handle = document.createElement('div'); + Common.extend(gui.__resize_handle.style, { + width: '6px', + marginLeft: '-3px', + height: '200px', + cursor: 'ew-resize', + position: 'absolute' + }); + function drag(e) { + e.preventDefault(); + gui.width += pmouseX - e.clientX; + gui.onResize(); + pmouseX = e.clientX; + return false; + } + function dragStop() { + dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG); + dom.unbind(window, 'mousemove', drag); + dom.unbind(window, 'mouseup', dragStop); + } + function dragStart(e) { + e.preventDefault(); + pmouseX = e.clientX; + dom.addClass(gui.__closeButton, GUI.CLASS_DRAG); + dom.bind(window, 'mousemove', drag); + dom.bind(window, 'mouseup', dragStop); + return false; + } + dom.bind(gui.__resize_handle, 'mousedown', dragStart); + dom.bind(gui.__closeButton, 'mousedown', dragStart); + gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild); +} +function setWidth(gui, w) { + gui.domElement.style.width = w + 'px'; + if (gui.__save_row && gui.autoPlace) { + gui.__save_row.style.width = w + 'px'; + } + if (gui.__closeButton) { + gui.__closeButton.style.width = w + 'px'; + } +} +function getCurrentPreset(gui, useInitialValues) { + var toReturn = {}; + Common.each(gui.__rememberedObjects, function (val, index) { + var savedValues = {}; + var controllerMap = gui.__rememberedObjectIndecesToControllers[index]; + Common.each(controllerMap, function (controller, property) { + savedValues[property] = useInitialValues ? controller.initialValue : controller.getValue(); + }); + toReturn[index] = savedValues; + }); + return toReturn; +} +function setPresetSelectIndex(gui) { + for (var index = 0; index < gui.__preset_select.length; index++) { + if (gui.__preset_select[index].value === gui.preset) { + gui.__preset_select.selectedIndex = index; + } + } +} +function updateDisplays(controllerArray) { + if (controllerArray.length !== 0) { + requestAnimationFrame$1.call(window, function () { + updateDisplays(controllerArray); + }); + } + Common.each(controllerArray, function (c) { + c.updateDisplay(); + }); +} + +var color = { + Color: Color, + math: ColorMath, + interpret: interpret +}; +var controllers = { + Controller: Controller, + BooleanController: BooleanController, + OptionController: OptionController, + StringController: StringController, + NumberController: NumberController, + NumberControllerBox: NumberControllerBox, + NumberControllerSlider: NumberControllerSlider, + FunctionController: FunctionController, + ColorController: ColorController +}; +var dom$1 = { dom: dom }; +var gui = { GUI: GUI }; +var GUI$1 = GUI; +var index = { + color: color, + controllers: controllers, + dom: dom$1, + gui: gui, + GUI: GUI$1 +}; + +exports.color = color; +exports.controllers = controllers; +exports.dom = dom$1; +exports.gui = gui; +exports.GUI = GUI$1; +exports['default'] = index; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); +//# sourceMappingURL=dat.gui.js.map \ No newline at end of file diff --git a/hw1/lib/gl-matrix-min.js b/hw1/lib/gl-matrix-min.js new file mode 100644 index 0000000..747b3a7 --- /dev/null +++ b/hw1/lib/gl-matrix-min.js @@ -0,0 +1,28 @@ +/*! +@fileoverview gl-matrix - High performance matrix and vector operations +@author Brandon Jones +@author Colin MacKenzie IV +@version 2.7.0 + +Copyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ +!function(t,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var r=n();for(var a in r)("object"==typeof exports?exports:t)[a]=r[a]}}("undefined"!=typeof self?self:this,function(){return function(t){var n={};function r(a){if(n[a])return n[a].exports;var e=n[a]={i:a,l:!1,exports:{}};return t[a].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=t,r.c=n,r.d=function(t,n,a){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:a})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(r.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var e in t)r.d(a,e,function(n){return t[n]}.bind(null,e));return a},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=10)}([function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.setMatrixArrayType=function(t){n.ARRAY_TYPE=t},n.toRadian=function(t){return t*e},n.equals=function(t,n){return Math.abs(t-n)<=a*Math.max(1,Math.abs(t),Math.abs(n))};var a=n.EPSILON=1e-6;n.ARRAY_TYPE="undefined"!=typeof Float32Array?Float32Array:Array,n.RANDOM=Math.random;var e=Math.PI/180},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t[2]=Math.ceil(n[2]),t[3]=Math.ceil(n[3]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t[2]=Math.floor(n[2]),t[3]=Math.floor(n[3]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t[3]=Math.min(n[3],r[3]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t[3]=Math.max(n[3],r[3]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t[2]=Math.round(n[2]),t[3]=Math.round(n[3]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=-n[3],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t[3]=1/n[3],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u;o>0&&(o=1/Math.sqrt(o),t[0]=r*o,t[1]=a*o,t[2]=e*o,t[3]=u*o);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]+t[3]*n[3]},n.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t[3]=i+a*(r[3]-i),t},n.random=function(t,n){var r,e,u,o,i,s;n=n||1;do{r=2*a.RANDOM()-1,e=2*a.RANDOM()-1,i=r*r+e*e}while(i>=1);do{u=2*a.RANDOM()-1,o=2*a.RANDOM()-1,s=u*u+o*o}while(s>=1);var c=Math.sqrt((1-i)/s);return t[0]=n*r,t[1]=n*e,t[2]=n*u*c,t[3]=n*o*c,t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3];return t[0]=r[0]*a+r[4]*e+r[8]*u+r[12]*o,t[1]=r[1]*a+r[5]*e+r[9]*u+r[13]*o,t[2]=r[2]*a+r[6]*e+r[10]*u+r[14]*o,t[3]=r[3]*a+r[7]*e+r[11]*u+r[15]*o,t},n.transformQuat=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2],c=r[3],f=c*a+i*u-s*e,M=c*e+s*a-o*u,h=c*u+o*e-i*a,l=-o*a-i*e-s*u;return t[0]=f*c+l*-o+M*-s-h*-i,t[1]=M*c+l*-i+h*-o-f*-s,t[2]=h*c+l*-s+f*-i-M*-o,t[3]=n[3],t},n.str=function(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0,t[3]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t[3]=n[3]*r[3],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t[3]=n[3]/r[3],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return Math.sqrt(r*r+a*a+e*e+u*u)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return r*r+a*a+e*e+u*u}function f(t){var n=t[0],r=t[1],a=t[2],e=t[3];return Math.sqrt(n*n+r*r+a*a+e*e)}function M(t){var n=t[0],r=t[1],a=t[2],e=t[3];return n*n+r*r+a*a+e*e}n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.len=f,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=4),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i1?0:e<-1?Math.PI:Math.acos(e)},n.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=n[0],i=n[1],s=n[2];return Math.abs(r-o)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(o))&&Math.abs(e-i)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(i))&&Math.abs(u-s)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(s))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(3);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function u(t){var n=t[0],r=t[1],a=t[2];return Math.sqrt(n*n+r*r+a*a)}function o(t,n,r){var e=new a.ARRAY_TYPE(3);return e[0]=t,e[1]=n,e[2]=r,e}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t}function s(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t}function c(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t}function f(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return Math.sqrt(r*r+a*a+e*e)}function M(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return r*r+a*a+e*e}function h(t){var n=t[0],r=t[1],a=t[2];return n*n+r*r+a*a}function l(t,n){var r=n[0],a=n[1],e=n[2],u=r*r+a*a+e*e;return u>0&&(u=1/Math.sqrt(u),t[0]=n[0]*u,t[1]=n[1]*u,t[2]=n[2]*u),t}function v(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}n.sub=i,n.mul=s,n.div=c,n.dist=f,n.sqrDist=M,n.len=u,n.sqrLen=h,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=3),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;ia.EPSILON?(t[0]=n[0]/e,t[1]=n[1]/e,t[2]=n[2]/e):(t[0]=1,t[1]=0,t[2]=0);return r},n.multiply=f,n.rotateX=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+o*i,t[1]=e*s+u*i,t[2]=u*s-e*i,t[3]=o*s-a*i,t},n.rotateY=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s-u*i,t[1]=e*s+o*i,t[2]=u*s+a*i,t[3]=o*s-e*i,t},n.rotateZ=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+e*i,t[1]=e*s-a*i,t[2]=u*s+o*i,t[3]=o*s-u*i,t},n.calculateW=function(t,n){var r=n[0],a=n[1],e=n[2];return t[0]=r,t[1]=a,t[2]=e,t[3]=Math.sqrt(Math.abs(1-r*r-a*a-e*e)),t},n.slerp=M,n.random=function(t){var n=a.RANDOM(),r=a.RANDOM(),e=a.RANDOM(),u=Math.sqrt(1-n),o=Math.sqrt(n);return t[0]=u*Math.sin(2*Math.PI*r),t[1]=u*Math.cos(2*Math.PI*r),t[2]=o*Math.sin(2*Math.PI*e),t[3]=o*Math.cos(2*Math.PI*e),t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u,i=o?1/o:0;return t[0]=-r*i,t[1]=-a*i,t[2]=-e*i,t[3]=u*i,t},n.conjugate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t},n.fromMat3=h,n.fromEuler=function(t,n,r,a){var e=.5*Math.PI/180;n*=e,r*=e,a*=e;var u=Math.sin(n),o=Math.cos(n),i=Math.sin(r),s=Math.cos(r),c=Math.sin(a),f=Math.cos(a);return t[0]=u*s*f-o*i*c,t[1]=o*i*f+u*s*c,t[2]=o*s*c-u*i*f,t[3]=o*s*f+u*i*c,t},n.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"};var a=i(r(0)),e=i(r(5)),u=i(r(2)),o=i(r(1));function i(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function s(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t[3]=1,t}function c(t,n,r){r*=.5;var a=Math.sin(r);return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=Math.cos(r),t}function f(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*f+o*i+e*c-u*s,t[1]=e*f+o*s+u*i-a*c,t[2]=u*f+o*c+a*s-e*i,t[3]=o*f-a*i-e*s-u*c,t}function M(t,n,r,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=r[0],f=r[1],M=r[2],h=r[3],l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;return(v=u*c+o*f+i*M+s*h)<0&&(v=-v,c=-c,f=-f,M=-M,h=-h),1-v>a.EPSILON?(l=Math.acos(v),d=Math.sin(l),b=Math.sin((1-e)*l)/d,m=Math.sin(e*l)/d):(b=1-e,m=e),t[0]=b*u+m*c,t[1]=b*o+m*f,t[2]=b*i+m*M,t[3]=b*s+m*h,t}function h(t,n){var r=n[0]+n[4]+n[8],a=void 0;if(r>0)a=Math.sqrt(r+1),t[3]=.5*a,a=.5/a,t[0]=(n[5]-n[7])*a,t[1]=(n[6]-n[2])*a,t[2]=(n[1]-n[3])*a;else{var e=0;n[4]>n[0]&&(e=1),n[8]>n[3*e+e]&&(e=2);var u=(e+1)%3,o=(e+2)%3;a=Math.sqrt(n[3*e+e]-n[3*u+u]-n[3*o+o]+1),t[e]=.5*a,a=.5/a,t[3]=(n[3*u+o]-n[3*o+u])*a,t[u]=(n[3*u+e]+n[3*e+u])*a,t[o]=(n[3*o+e]+n[3*e+o])*a}return t}n.clone=o.clone,n.fromValues=o.fromValues,n.copy=o.copy,n.set=o.set,n.add=o.add,n.mul=f,n.scale=o.scale,n.dot=o.dot,n.lerp=o.lerp;var l=n.length=o.length,v=(n.len=l,n.squaredLength=o.squaredLength),d=(n.sqrLen=v,n.normalize=o.normalize);n.exactEquals=o.exactEquals,n.equals=o.equals,n.rotationTo=function(){var t=u.create(),n=u.fromValues(1,0,0),r=u.fromValues(0,1,0);return function(a,e,o){var i=u.dot(e,o);return i<-.999999?(u.cross(t,n,e),u.len(t)<1e-6&&u.cross(t,r,e),u.normalize(t,t),c(a,t,Math.PI),a):i>.999999?(a[0]=0,a[1]=0,a[2]=0,a[3]=1,a):(u.cross(t,e,o),a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=1+i,d(a,a))}}(),n.sqlerp=function(){var t=s(),n=s();return function(r,a,e,u,o,i){return M(t,a,o,i),M(n,e,u,i),M(r,t,n,2*i*(1-i)),r}}(),n.setAxes=function(){var t=e.create();return function(n,r,a,e){return t[0]=a[0],t[3]=a[1],t[6]=a[2],t[1]=e[0],t[4]=e[1],t[7]=e[2],t[2]=-r[0],t[5]=-r[1],t[8]=-r[2],d(n,h(n,t))}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(16);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=0,t[12]=0,t[13]=0,t[14]=0);return t[0]=1,t[5]=1,t[10]=1,t[15]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(16);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n[9]=t[9],n[10]=t[10],n[11]=t[11],n[12]=t[12],n[13]=t[13],n[14]=t[14],n[15]=t[15],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.fromValues=function(t,n,r,e,u,o,i,s,c,f,M,h,l,v,d,b){var m=new a.ARRAY_TYPE(16);return m[0]=t,m[1]=n,m[2]=r,m[3]=e,m[4]=u,m[5]=o,m[6]=i,m[7]=s,m[8]=c,m[9]=f,m[10]=M,m[11]=h,m[12]=l,m[13]=v,m[14]=d,m[15]=b,m},n.set=function(t,n,r,a,e,u,o,i,s,c,f,M,h,l,v,d,b){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t[9]=f,t[10]=M,t[11]=h,t[12]=l,t[13]=v,t[14]=d,t[15]=b,t},n.identity=e,n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[3],u=n[6],o=n[7],i=n[11];t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=r,t[6]=n[9],t[7]=n[13],t[8]=a,t[9]=u,t[11]=n[14],t[12]=e,t[13]=o,t[14]=i}else t[0]=n[0],t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=n[1],t[5]=n[5],t[6]=n[9],t[7]=n[13],t[8]=n[2],t[9]=n[6],t[10]=n[10],t[11]=n[14],t[12]=n[3],t[13]=n[7],t[14]=n[11],t[15]=n[15];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(e*Y-a*L-u*_)*S,t[2]=(d*R-b*O+m*E)*S,t[3]=(h*O-M*R-l*E)*S,t[4]=(s*x-o*L-c*q)*S,t[5]=(r*L-e*x+u*q)*S,t[6]=(b*A-v*R-m*P)*S,t[7]=(f*R-h*A+l*P)*S,t[8]=(o*Y-i*x+c*y)*S,t[9]=(a*x-r*Y-u*y)*S,t[10]=(v*O-d*A+m*p)*S,t[11]=(M*A-f*O-l*p)*S,t[12]=(i*q-o*_-s*y)*S,t[13]=(r*_-a*q+e*y)*S,t[14]=(d*P-v*E-b*p)*S,t[15]=(f*E-M*P+h*p)*S,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15];return t[0]=i*(h*m-l*b)-M*(s*m-c*b)+d*(s*l-c*h),t[1]=-(a*(h*m-l*b)-M*(e*m-u*b)+d*(e*l-u*h)),t[2]=a*(s*m-c*b)-i*(e*m-u*b)+d*(e*c-u*s),t[3]=-(a*(s*l-c*h)-i*(e*l-u*h)+M*(e*c-u*s)),t[4]=-(o*(h*m-l*b)-f*(s*m-c*b)+v*(s*l-c*h)),t[5]=r*(h*m-l*b)-f*(e*m-u*b)+v*(e*l-u*h),t[6]=-(r*(s*m-c*b)-o*(e*m-u*b)+v*(e*c-u*s)),t[7]=r*(s*l-c*h)-o*(e*l-u*h)+f*(e*c-u*s),t[8]=o*(M*m-l*d)-f*(i*m-c*d)+v*(i*l-c*M),t[9]=-(r*(M*m-l*d)-f*(a*m-u*d)+v*(a*l-u*M)),t[10]=r*(i*m-c*d)-o*(a*m-u*d)+v*(a*c-u*i),t[11]=-(r*(i*l-c*M)-o*(a*l-u*M)+f*(a*c-u*i)),t[12]=-(o*(M*b-h*d)-f*(i*b-s*d)+v*(i*h-s*M)),t[13]=r*(M*b-h*d)-f*(a*b-e*d)+v*(a*h-e*M),t[14]=-(r*(i*b-s*d)-o*(a*b-e*d)+v*(a*s-e*i)),t[15]=r*(i*h-s*M)-o*(a*h-e*M)+f*(a*s-e*i),t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8],f=t[9],M=t[10],h=t[11],l=t[12],v=t[13],d=t[14],b=t[15];return(n*o-r*u)*(M*b-h*d)-(n*i-a*u)*(f*b-h*v)+(n*s-e*u)*(f*d-M*v)+(r*i-a*o)*(c*b-h*l)-(r*s-e*o)*(c*d-M*l)+(a*s-e*i)*(c*v-f*l)},n.multiply=u,n.translate=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;n===t?(t[12]=n[0]*a+n[4]*e+n[8]*u+n[12],t[13]=n[1]*a+n[5]*e+n[9]*u+n[13],t[14]=n[2]*a+n[6]*e+n[10]*u+n[14],t[15]=n[3]*a+n[7]*e+n[11]*u+n[15]):(o=n[0],i=n[1],s=n[2],c=n[3],f=n[4],M=n[5],h=n[6],l=n[7],v=n[8],d=n[9],b=n[10],m=n[11],t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=f,t[5]=M,t[6]=h,t[7]=l,t[8]=v,t[9]=d,t[10]=b,t[11]=m,t[12]=o*a+f*e+v*u+n[12],t[13]=i*a+M*e+d*u+n[13],t[14]=s*a+h*e+b*u+n[14],t[15]=c*a+l*e+m*u+n[15]);return t},n.scale=function(t,n,r){var a=r[0],e=r[1],u=r[2];return t[0]=n[0]*a,t[1]=n[1]*a,t[2]=n[2]*a,t[3]=n[3]*a,t[4]=n[4]*e,t[5]=n[5]*e,t[6]=n[6]*e,t[7]=n[7]*e,t[8]=n[8]*u,t[9]=n[9]*u,t[10]=n[10]*u,t[11]=n[11]*u,t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.rotate=function(t,n,r,e){var u=e[0],o=e[1],i=e[2],s=Math.sqrt(u*u+o*o+i*i),c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0,p=void 0,P=void 0,A=void 0,E=void 0,O=void 0,R=void 0,y=void 0,q=void 0,x=void 0,_=void 0,Y=void 0,L=void 0,S=void 0,w=void 0,I=void 0;if(s0?(r[0]=2*(c*s+h*e+f*i-M*u)/l,r[1]=2*(f*s+h*u+M*e-c*i)/l,r[2]=2*(M*s+h*i+c*u-f*e)/l):(r[0]=2*(c*s+h*e+f*i-M*u),r[1]=2*(f*s+h*u+M*e-c*i),r[2]=2*(M*s+h*i+c*u-f*e));return o(t,n,r),t},n.getTranslation=function(t,n){return t[0]=n[12],t[1]=n[13],t[2]=n[14],t},n.getScaling=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[4],o=n[5],i=n[6],s=n[8],c=n[9],f=n[10];return t[0]=Math.sqrt(r*r+a*a+e*e),t[1]=Math.sqrt(u*u+o*o+i*i),t[2]=Math.sqrt(s*s+c*c+f*f),t},n.getRotation=function(t,n){var r=n[0]+n[5]+n[10],a=0;r>0?(a=2*Math.sqrt(r+1),t[3]=.25*a,t[0]=(n[6]-n[9])/a,t[1]=(n[8]-n[2])/a,t[2]=(n[1]-n[4])/a):n[0]>n[5]&&n[0]>n[10]?(a=2*Math.sqrt(1+n[0]-n[5]-n[10]),t[3]=(n[6]-n[9])/a,t[0]=.25*a,t[1]=(n[1]+n[4])/a,t[2]=(n[8]+n[2])/a):n[5]>n[10]?(a=2*Math.sqrt(1+n[5]-n[0]-n[10]),t[3]=(n[8]-n[2])/a,t[0]=(n[1]+n[4])/a,t[1]=.25*a,t[2]=(n[6]+n[9])/a):(a=2*Math.sqrt(1+n[10]-n[0]-n[5]),t[3]=(n[1]-n[4])/a,t[0]=(n[8]+n[2])/a,t[1]=(n[6]+n[9])/a,t[2]=.25*a);return t},n.fromRotationTranslationScale=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3],s=e+e,c=u+u,f=o+o,M=e*s,h=e*c,l=e*f,v=u*c,d=u*f,b=o*f,m=i*s,p=i*c,P=i*f,A=a[0],E=a[1],O=a[2];return t[0]=(1-(v+b))*A,t[1]=(h+P)*A,t[2]=(l-p)*A,t[3]=0,t[4]=(h-P)*E,t[5]=(1-(M+b))*E,t[6]=(d+m)*E,t[7]=0,t[8]=(l+p)*O,t[9]=(d-m)*O,t[10]=(1-(M+v))*O,t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t},n.fromRotationTranslationScaleOrigin=function(t,n,r,a,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=u+u,f=o+o,M=i+i,h=u*c,l=u*f,v=u*M,d=o*f,b=o*M,m=i*M,p=s*c,P=s*f,A=s*M,E=a[0],O=a[1],R=a[2],y=e[0],q=e[1],x=e[2],_=(1-(d+m))*E,Y=(l+A)*E,L=(v-P)*E,S=(l-A)*O,w=(1-(h+m))*O,I=(b+p)*O,N=(v+P)*R,g=(b-p)*R,T=(1-(h+d))*R;return t[0]=_,t[1]=Y,t[2]=L,t[3]=0,t[4]=S,t[5]=w,t[6]=I,t[7]=0,t[8]=N,t[9]=g,t[10]=T,t[11]=0,t[12]=r[0]+y-(_*y+S*q+N*x),t[13]=r[1]+q-(Y*y+w*q+g*x),t[14]=r[2]+x-(L*y+I*q+T*x),t[15]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[1]=f+m,t[2]=h-b,t[3]=0,t[4]=f-m,t[5]=1-c-v,t[6]=l+d,t[7]=0,t[8]=h+b,t[9]=l-d,t[10]=1-c-M,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.frustum=function(t,n,r,a,e,u,o){var i=1/(r-n),s=1/(e-a),c=1/(u-o);return t[0]=2*u*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=2*u*s,t[6]=0,t[7]=0,t[8]=(r+n)*i,t[9]=(e+a)*s,t[10]=(o+u)*c,t[11]=-1,t[12]=0,t[13]=0,t[14]=o*u*2*c,t[15]=0,t},n.perspective=function(t,n,r,a,e){var u=1/Math.tan(n/2),o=void 0;t[0]=u/r,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=u,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=-1,t[12]=0,t[13]=0,t[15]=0,null!=e&&e!==1/0?(o=1/(a-e),t[10]=(e+a)*o,t[14]=2*e*a*o):(t[10]=-1,t[14]=-2*a);return t},n.perspectiveFromFieldOfView=function(t,n,r,a){var e=Math.tan(n.upDegrees*Math.PI/180),u=Math.tan(n.downDegrees*Math.PI/180),o=Math.tan(n.leftDegrees*Math.PI/180),i=Math.tan(n.rightDegrees*Math.PI/180),s=2/(o+i),c=2/(e+u);return t[0]=s,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=c,t[6]=0,t[7]=0,t[8]=-(o-i)*s*.5,t[9]=(e-u)*c*.5,t[10]=a/(r-a),t[11]=-1,t[12]=0,t[13]=0,t[14]=a*r/(r-a),t[15]=0,t},n.ortho=function(t,n,r,a,e,u,o){var i=1/(n-r),s=1/(a-e),c=1/(u-o);return t[0]=-2*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=-2*s,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=2*c,t[11]=0,t[12]=(n+r)*i,t[13]=(e+a)*s,t[14]=(o+u)*c,t[15]=1,t},n.lookAt=function(t,n,r,u){var o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=n[0],m=n[1],p=n[2],P=u[0],A=u[1],E=u[2],O=r[0],R=r[1],y=r[2];if(Math.abs(b-O)0&&(l=1/Math.sqrt(l),f*=l,M*=l,h*=l);var v=s*h-c*M,d=c*f-i*h,b=i*M-s*f;(l=v*v+d*d+b*b)>0&&(l=1/Math.sqrt(l),v*=l,d*=l,b*=l);return t[0]=v,t[1]=d,t[2]=b,t[3]=0,t[4]=M*b-h*d,t[5]=h*v-f*b,t[6]=f*d-M*v,t[7]=0,t[8]=f,t[9]=M,t[10]=h,t[11]=0,t[12]=e,t[13]=u,t[14]=o,t[15]=1,t},n.str=function(t){return"mat4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+", "+t[9]+", "+t[10]+", "+t[11]+", "+t[12]+", "+t[13]+", "+t[14]+", "+t[15]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2)+Math.pow(t[9],2)+Math.pow(t[10],2)+Math.pow(t[11],2)+Math.pow(t[12],2)+Math.pow(t[13],2)+Math.pow(t[14],2)+Math.pow(t[15],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t[9]=n[9]+r[9],t[10]=n[10]+r[10],t[11]=n[11]+r[11],t[12]=n[12]+r[12],t[13]=n[13]+r[13],t[14]=n[14]+r[14],t[15]=n[15]+r[15],t},n.subtract=i,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t[9]=n[9]*r,t[10]=n[10]*r,t[11]=n[11]*r,t[12]=n[12]*r,t[13]=n[13]*r,t[14]=n[14]*r,t[15]=n[15]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t[9]=n[9]+r[9]*a,t[10]=n[10]+r[10]*a,t[11]=n[11]+r[11]*a,t[12]=n[12]+r[12]*a,t[13]=n[13]+r[13]*a,t[14]=n[14]+r[14]*a,t[15]=n[15]+r[15]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]&&t[9]===n[9]&&t[10]===n[10]&&t[11]===n[11]&&t[12]===n[12]&&t[13]===n[13]&&t[14]===n[14]&&t[15]===n[15]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=t[9],l=t[10],v=t[11],d=t[12],b=t[13],m=t[14],p=t[15],P=n[0],A=n[1],E=n[2],O=n[3],R=n[4],y=n[5],q=n[6],x=n[7],_=n[8],Y=n[9],L=n[10],S=n[11],w=n[12],I=n[13],N=n[14],g=n[15];return Math.abs(r-P)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(P))&&Math.abs(e-A)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(A))&&Math.abs(u-E)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(E))&&Math.abs(o-O)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(O))&&Math.abs(i-R)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(R))&&Math.abs(s-y)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(y))&&Math.abs(c-q)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(q))&&Math.abs(f-x)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(x))&&Math.abs(M-_)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(_))&&Math.abs(h-Y)<=a.EPSILON*Math.max(1,Math.abs(h),Math.abs(Y))&&Math.abs(l-L)<=a.EPSILON*Math.max(1,Math.abs(l),Math.abs(L))&&Math.abs(v-S)<=a.EPSILON*Math.max(1,Math.abs(v),Math.abs(S))&&Math.abs(d-w)<=a.EPSILON*Math.max(1,Math.abs(d),Math.abs(w))&&Math.abs(b-I)<=a.EPSILON*Math.max(1,Math.abs(b),Math.abs(I))&&Math.abs(m-N)<=a.EPSILON*Math.max(1,Math.abs(m),Math.abs(N))&&Math.abs(p-g)<=a.EPSILON*Math.max(1,Math.abs(p),Math.abs(g))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function u(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=n[9],l=n[10],v=n[11],d=n[12],b=n[13],m=n[14],p=n[15],P=r[0],A=r[1],E=r[2],O=r[3];return t[0]=P*a+A*i+E*M+O*d,t[1]=P*e+A*s+E*h+O*b,t[2]=P*u+A*c+E*l+O*m,t[3]=P*o+A*f+E*v+O*p,P=r[4],A=r[5],E=r[6],O=r[7],t[4]=P*a+A*i+E*M+O*d,t[5]=P*e+A*s+E*h+O*b,t[6]=P*u+A*c+E*l+O*m,t[7]=P*o+A*f+E*v+O*p,P=r[8],A=r[9],E=r[10],O=r[11],t[8]=P*a+A*i+E*M+O*d,t[9]=P*e+A*s+E*h+O*b,t[10]=P*u+A*c+E*l+O*m,t[11]=P*o+A*f+E*v+O*p,P=r[12],A=r[13],E=r[14],O=r[15],t[12]=P*a+A*i+E*M+O*d,t[13]=P*e+A*s+E*h+O*b,t[14]=P*u+A*c+E*l+O*m,t[15]=P*o+A*f+E*v+O*p,t}function o(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=a+a,s=e+e,c=u+u,f=a*i,M=a*s,h=a*c,l=e*s,v=e*c,d=u*c,b=o*i,m=o*s,p=o*c;return t[0]=1-(l+d),t[1]=M+p,t[2]=h-m,t[3]=0,t[4]=M-p,t[5]=1-(f+d),t[6]=v+b,t[7]=0,t[8]=h+m,t[9]=v-b,t[10]=1-(f+l),t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t[9]=n[9]-r[9],t[10]=n[10]-r[10],t[11]=n[11]-r[11],t[12]=n[12]-r[12],t[13]=n[13]-r[13],t[14]=n[14]-r[14],t[15]=n[15]-r[15],t}n.mul=u,n.sub=i},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(9);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0);return t[0]=1,t[4]=1,t[8]=1,t},n.fromMat4=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[4],t[4]=n[5],t[5]=n[6],t[6]=n[8],t[7]=n[9],t[8]=n[10],t},n.clone=function(t){var n=new a.ARRAY_TYPE(9);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromValues=function(t,n,r,e,u,o,i,s,c){var f=new a.ARRAY_TYPE(9);return f[0]=t,f[1]=n,f[2]=r,f[3]=e,f[4]=u,f[5]=o,f[6]=i,f[7]=s,f[8]=c,f},n.set=function(t,n,r,a,e,u,o,i,s,c){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[5];t[1]=n[3],t[2]=n[6],t[3]=r,t[5]=n[7],t[6]=a,t[7]=e}else t[0]=n[0],t[1]=n[3],t[2]=n[6],t[3]=n[1],t[4]=n[4],t[5]=n[7],t[6]=n[2],t[7]=n[5],t[8]=n[8];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=f*o-i*c,h=-f*u+i*s,l=c*u-o*s,v=r*M+a*h+e*l;if(!v)return null;return v=1/v,t[0]=M*v,t[1]=(-f*a+e*c)*v,t[2]=(i*a-e*o)*v,t[3]=h*v,t[4]=(f*r-e*s)*v,t[5]=(-i*r+e*u)*v,t[6]=l*v,t[7]=(-c*r+a*s)*v,t[8]=(o*r-a*u)*v,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8];return t[0]=o*f-i*c,t[1]=e*c-a*f,t[2]=a*i-e*o,t[3]=i*s-u*f,t[4]=r*f-e*s,t[5]=e*u-r*i,t[6]=u*c-o*s,t[7]=a*s-r*c,t[8]=r*o-a*u,t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8];return n*(c*u-o*s)+r*(-c*e+o*i)+a*(s*e-u*i)},n.multiply=e,n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=i,t[5]=s,t[6]=h*a+l*o+c,t[7]=h*e+l*i+f,t[8]=h*u+l*s+M,t},n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=Math.sin(r),l=Math.cos(r);return t[0]=l*a+h*o,t[1]=l*e+h*i,t[2]=l*u+h*s,t[3]=l*o-h*a,t[4]=l*i-h*e,t[5]=l*s-h*u,t[6]=c,t[7]=f,t[8]=M,t},n.scale=function(t,n,r){var a=r[0],e=r[1];return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=e*n[3],t[4]=e*n[4],t[5]=e*n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=n[0],t[7]=n[1],t[8]=1,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=-r,t[4]=a,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=n[1],t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromMat2d=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=0,t[3]=n[2],t[4]=n[3],t[5]=0,t[6]=n[4],t[7]=n[5],t[8]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[3]=f-m,t[6]=h+b,t[1]=f+m,t[4]=1-c-v,t[7]=l-d,t[2]=h-b,t[5]=l+d,t[8]=1-c-M,t},n.normalFromMat4=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(s*x-o*L-c*q)*S,t[2]=(o*Y-i*x+c*y)*S,t[3]=(e*Y-a*L-u*_)*S,t[4]=(r*L-e*x+u*q)*S,t[5]=(a*x-r*Y-u*y)*S,t[6]=(d*R-b*O+m*E)*S,t[7]=(b*A-v*R-m*P)*S,t[8]=(v*O-d*A+m*p)*S,t},n.projection=function(t,n,r){return t[0]=2/n,t[1]=0,t[2]=0,t[3]=0,t[4]=-2/r,t[5]=0,t[6]=-1,t[7]=1,t[8]=1,t},n.str=function(t){return"mat3("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=n[0],l=n[1],v=n[2],d=n[3],b=n[4],m=n[5],p=n[6],P=n[7],A=n[8];return Math.abs(r-h)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(h))&&Math.abs(e-l)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(l))&&Math.abs(u-v)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(v))&&Math.abs(o-d)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(d))&&Math.abs(i-b)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(b))&&Math.abs(s-m)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(m))&&Math.abs(c-p)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(p))&&Math.abs(f-P)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(P))&&Math.abs(M-A)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(A))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1],v=r[2],d=r[3],b=r[4],m=r[5],p=r[6],P=r[7],A=r[8];return t[0]=h*a+l*o+v*c,t[1]=h*e+l*i+v*f,t[2]=h*u+l*s+v*M,t[3]=d*a+b*o+m*c,t[4]=d*e+b*i+m*f,t[5]=d*u+b*s+m*M,t[6]=p*a+P*o+A*c,t[7]=p*e+P*i+A*f,t[8]=p*u+P*s+A*M,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.sqrDist=n.dist=n.div=n.mul=n.sub=n.len=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(2);return n[0]=t[0],n[1]=t[1],n},n.fromValues=function(t,n){var r=new a.ARRAY_TYPE(2);return r[0]=t,r[1]=n,r},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t},n.set=function(t,n,r){return t[0]=n,t[1]=r,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=r*r+a*a;e>0&&(e=1/Math.sqrt(e),t[0]=n[0]*e,t[1]=n[1]*e);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]},n.cross=function(t,n,r){var a=n[0]*r[1]-n[1]*r[0];return t[0]=t[1]=0,t[2]=a,t},n.lerp=function(t,n,r,a){var e=n[0],u=n[1];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t},n.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI;return t[0]=Math.cos(r)*n,t[1]=Math.sin(r)*n,t},n.transformMat2=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e,t[1]=r[1]*a+r[3]*e,t},n.transformMat2d=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e+r[4],t[1]=r[1]*a+r[3]*e+r[5],t},n.transformMat3=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[3]*e+r[6],t[1]=r[1]*a+r[4]*e+r[7],t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[4]*e+r[12],t[1]=r[1]*a+r[5]*e+r[13],t},n.rotate=function(t,n,r,a){var e=n[0]-r[0],u=n[1]-r[1],o=Math.sin(a),i=Math.cos(a);return t[0]=e*i-u*o+r[0],t[1]=e*o+u*i+r[1],t},n.angle=function(t,n){var r=t[0],a=t[1],e=n[0],u=n[1],o=r*r+a*a;o>0&&(o=1/Math.sqrt(o));var i=e*e+u*u;i>0&&(i=1/Math.sqrt(i));var s=(r*e+a*u)*o*i;return s>1?0:s<-1?Math.PI:Math.acos(s)},n.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]},n.equals=function(t,n){var r=t[0],e=t[1],u=n[0],o=n[1];return Math.abs(r-u)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(u))&&Math.abs(e-o)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(o))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(2);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1];return Math.sqrt(r*r+a*a)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1];return r*r+a*a}function f(t){var n=t[0],r=t[1];return Math.sqrt(n*n+r*r)}function M(t){var n=t[0],r=t[1];return n*n+r*r}n.len=f,n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=2),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i0){r=Math.sqrt(r);var a=n[0]/r,e=n[1]/r,u=n[2]/r,o=n[3]/r,i=n[4],s=n[5],c=n[6],f=n[7],M=a*i+e*s+u*c+o*f;t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=(i-a*M)/r,t[5]=(s-e*M)/r,t[6]=(c-u*M)/r,t[7]=(f-o*M)/r}return t},n.str=function(t){return"quat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=n[0],h=n[1],l=n[2],v=n[3],d=n[4],b=n[5],m=n[6],p=n[7];return Math.abs(r-M)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(M))&&Math.abs(e-h)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(h))&&Math.abs(u-l)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(l))&&Math.abs(o-v)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(v))&&Math.abs(i-d)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(d))&&Math.abs(s-b)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(b))&&Math.abs(c-m)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(m))&&Math.abs(f-p)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(p))};var a=o(r(0)),e=o(r(3)),u=o(r(4));function o(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function i(t,n,r){var a=.5*r[0],e=.5*r[1],u=.5*r[2],o=n[0],i=n[1],s=n[2],c=n[3];return t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=a*c+e*s-u*i,t[5]=e*c+u*o-a*s,t[6]=u*c+a*i-e*o,t[7]=-a*o-e*i-u*s,t}function s(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t}n.getReal=e.copy;n.setReal=e.copy;function c(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[4],s=r[5],c=r[6],f=r[7],M=n[4],h=n[5],l=n[6],v=n[7],d=r[0],b=r[1],m=r[2],p=r[3];return t[0]=a*p+o*d+e*m-u*b,t[1]=e*p+o*b+u*d-a*m,t[2]=u*p+o*m+a*b-e*d,t[3]=o*p-a*d-e*b-u*m,t[4]=a*f+o*i+e*c-u*s+M*p+v*d+h*m-l*b,t[5]=e*f+o*s+u*i-a*c+h*p+v*b+l*d-M*m,t[6]=u*f+o*c+a*s-e*i+l*p+v*m+M*b-h*d,t[7]=o*f-a*i-e*s-u*c+v*p-M*d-h*b-l*m,t}n.mul=c;var f=n.dot=e.dot;var M=n.length=e.length,h=(n.len=M,n.squaredLength=e.squaredLength);n.sqrLen=h},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(6);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[4]=0,t[5]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(6);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},n.fromValues=function(t,n,r,e,u,o){var i=new a.ARRAY_TYPE(6);return i[0]=t,i[1]=n,i[2]=r,i[3]=e,i[4]=u,i[5]=o,i},n.set=function(t,n,r,a,e,u,o){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=r*u-a*e;if(!s)return null;return s=1/s,t[0]=u*s,t[1]=-a*s,t[2]=-e*s,t[3]=r*s,t[4]=(e*i-u*o)*s,t[5]=(a*o-r*i)*s,t},n.determinant=function(t){return t[0]*t[3]-t[1]*t[2]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=Math.sin(r),f=Math.cos(r);return t[0]=a*f+u*c,t[1]=e*f+o*c,t[2]=a*-c+u*f,t[3]=e*-c+o*f,t[4]=i,t[5]=s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a*c,t[1]=e*c,t[2]=u*f,t[3]=o*f,t[4]=i,t[5]=s,t},n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=a*c+u*f+i,t[5]=e*c+o*f+s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t[4]=0,t[5]=0,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t[4]=0,t[5]=0,t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=n[0],t[5]=n[1],t},n.str=function(t){return"mat2d("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+1)},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=n[0],f=n[1],M=n[2],h=n[3],l=n[4],v=n[5];return Math.abs(r-c)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(c))&&Math.abs(e-f)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(f))&&Math.abs(u-M)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(M))&&Math.abs(o-h)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(h))&&Math.abs(i-l)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(l))&&Math.abs(s-v)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(v))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1],M=r[2],h=r[3],l=r[4],v=r[5];return t[0]=a*c+u*f,t[1]=e*c+o*f,t[2]=a*M+u*h,t[3]=e*M+o*h,t[4]=a*l+u*v+i,t[5]=e*l+o*v+s,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(4);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.transpose=function(t,n){if(t===n){var r=n[1];t[1]=n[2],t[2]=r}else t[0]=n[0],t[1]=n[2],t[2]=n[1],t[3]=n[3];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*u-e*a;if(!o)return null;return o=1/o,t[0]=u*o,t[1]=-a*o,t[2]=-e*o,t[3]=r*o,t},n.adjoint=function(t,n){var r=n[0];return t[0]=n[3],t[1]=-n[1],t[2]=-n[2],t[3]=r,t},n.determinant=function(t){return t[0]*t[3]-t[2]*t[1]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+u*i,t[1]=e*s+o*i,t[2]=a*-i+u*s,t[3]=e*-i+o*s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1];return t[0]=a*i,t[1]=e*i,t[2]=u*s,t[3]=o*s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t},n.str=function(t){return"mat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2))},n.LDU=function(t,n,r,a){return t[2]=a[2]/a[0],r[0]=a[0],r[1]=a[1],r[3]=a[3]-t[2]*r[1],[t,n,r]},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))},n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*i+u*s,t[1]=e*i+o*s,t[2]=a*c+u*f,t[3]=e*c+o*f,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.vec4=n.vec3=n.vec2=n.quat2=n.quat=n.mat4=n.mat3=n.mat2d=n.mat2=n.glMatrix=void 0;var a=l(r(0)),e=l(r(9)),u=l(r(8)),o=l(r(5)),i=l(r(4)),s=l(r(3)),c=l(r(7)),f=l(r(6)),M=l(r(2)),h=l(r(1));function l(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}n.glMatrix=a,n.mat2=e,n.mat2d=u,n.mat3=o,n.mat4=i,n.quat=s,n.quat2=c,n.vec2=f,n.vec3=M,n.vec4=h}])}); \ No newline at end of file diff --git a/hw1/lib/imgui.umd.js b/hw1/lib/imgui.umd.js new file mode 100644 index 0000000..dbd71a1 Binary files /dev/null and b/hw1/lib/imgui.umd.js differ diff --git a/hw1/lib/imgui_impl.umd.js b/hw1/lib/imgui_impl.umd.js new file mode 100644 index 0000000..983618b --- /dev/null +++ b/hw1/lib/imgui_impl.umd.js @@ -0,0 +1,816 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('imgui-js')) : + typeof define === 'function' && define.amd ? define(['exports', 'imgui-js'], factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ImGui_Impl = {}, global.ImGui)); +}(this, (function (exports, ImGui) { 'use strict'; + + let clipboard_text = ""; + let canvas = null; + exports.gl = null; + let g_ShaderHandle = null; + let g_VertHandle = null; + let g_FragHandle = null; + let g_AttribLocationTex = null; + let g_AttribLocationProjMtx = null; + let g_AttribLocationPosition = -1; + let g_AttribLocationUV = -1; + let g_AttribLocationColor = -1; + let g_VboHandle = null; + let g_ElementsHandle = null; + let g_FontTexture = null; + exports.ctx = null; + let prev_time = 0; + function document_on_copy(event) { + if (event.clipboardData) { + event.clipboardData.setData("text/plain", clipboard_text); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function document_on_cut(event) { + if (event.clipboardData) { + event.clipboardData.setData("text/plain", clipboard_text); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function document_on_paste(event) { + if (event.clipboardData) { + clipboard_text = event.clipboardData.getData("text/plain"); + } + // console.log(`${event.type}: "${clipboard_text}"`); + event.preventDefault(); + } + function window_on_resize() { + if (canvas !== null) { + const devicePixelRatio = window.devicePixelRatio || 1; + canvas.width = Math.floor(canvas.scrollWidth * devicePixelRatio); + canvas.height = Math.floor(canvas.scrollHeight * devicePixelRatio); + } + } + function window_on_gamepadconnected(event /* GamepadEvent */) { + console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.", event.gamepad.index, event.gamepad.id, event.gamepad.buttons.length, event.gamepad.axes.length); + } + function window_on_gamepaddisconnected(event /* GamepadEvent */) { + console.log("Gamepad disconnected at index %d: %s.", event.gamepad.index, event.gamepad.id); + } + function canvas_on_blur(event) { + const io = ImGui.GetIO(); + io.KeyCtrl = false; + io.KeyShift = false; + io.KeyAlt = false; + io.KeySuper = false; + for (let i = 0; i < io.KeysDown.length; ++i) { + io.KeysDown[i] = false; + } + for (let i = 0; i < io.MouseDown.length; ++i) { + io.MouseDown[i] = false; + } + } + const key_code_to_index = { + "NumpadEnter": 176, + }; + function canvas_on_keydown(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.KeyCtrl = event.ctrlKey; + io.KeyShift = event.shiftKey; + io.KeyAlt = event.altKey; + io.KeySuper = event.metaKey; + const key_index = key_code_to_index[event.code] || event.keyCode; + ImGui.ASSERT(key_index >= 0 && key_index < ImGui.ARRAYSIZE(io.KeysDown)); + io.KeysDown[key_index] = true; + // forward to the keypress event + if ( /*io.WantCaptureKeyboard ||*/event.key === "Tab") { + event.preventDefault(); + } + } + function canvas_on_keyup(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.KeyCtrl = event.ctrlKey; + io.KeyShift = event.shiftKey; + io.KeyAlt = event.altKey; + io.KeySuper = event.metaKey; + const key_index = key_code_to_index[event.code] || event.keyCode; + ImGui.ASSERT(key_index >= 0 && key_index < ImGui.ARRAYSIZE(io.KeysDown)); + io.KeysDown[key_index] = false; + if (io.WantCaptureKeyboard) { + event.preventDefault(); + } + } + function canvas_on_keypress(event) { + // console.log(event.type, event.key, event.code, event.keyCode); + const io = ImGui.GetIO(); + io.AddInputCharacter(event.charCode); + if (io.WantCaptureKeyboard) { + event.preventDefault(); + } + } + function canvas_on_pointermove(event) { + const io = ImGui.GetIO(); + io.MousePos.x = event.offsetX; + io.MousePos.y = event.offsetY; + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + // MouseEvent.button + // A number representing a given button: + // 0: Main button pressed, usually the left button or the un-initialized state + // 1: Auxiliary button pressed, usually the wheel button or the middle button (if present) + // 2: Secondary button pressed, usually the right button + // 3: Fourth button, typically the Browser Back button + // 4: Fifth button, typically the Browser Forward button + const mouse_button_map = [0, 2, 1, 3, 4]; + function canvas_on_pointerdown(event) { + const io = ImGui.GetIO(); + io.MousePos.x = event.offsetX; + io.MousePos.y = event.offsetY; + io.MouseDown[mouse_button_map[event.button]] = true; + // if (io.WantCaptureMouse) { + // event.preventDefault(); + // } + } + function canvas_on_contextmenu(event) { + const io = ImGui.GetIO(); + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function canvas_on_pointerup(event) { + const io = ImGui.GetIO(); + io.MouseDown[mouse_button_map[event.button]] = false; + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function canvas_on_wheel(event) { + const io = ImGui.GetIO(); + let scale = 1.0; + switch (event.deltaMode) { + case event.DOM_DELTA_PIXEL: + scale = 0.01; + break; + case event.DOM_DELTA_LINE: + scale = 0.2; + break; + case event.DOM_DELTA_PAGE: + scale = 1.0; + break; + } + io.MouseWheelH = event.deltaX * scale; + io.MouseWheel = -event.deltaY * scale; // Mouse wheel: 1 unit scrolls about 5 lines text. + if (io.WantCaptureMouse) { + event.preventDefault(); + } + } + function Init(value) { + const io = ImGui.GetIO(); + if (typeof (window) !== "undefined") { + io.BackendPlatformName = "imgui_impl_browser"; + ImGui.LoadIniSettingsFromMemory(window.localStorage.getItem("imgui.ini") || ""); + } + else { + io.BackendPlatformName = "imgui_impl_console"; + } + if (typeof (navigator) !== "undefined") { + io.ConfigMacOSXBehaviors = navigator.platform.match(/Mac/) !== null; + } + if (typeof (document) !== "undefined") { + document.body.addEventListener("copy", document_on_copy); + document.body.addEventListener("cut", document_on_cut); + document.body.addEventListener("paste", document_on_paste); + } + io.SetClipboardTextFn = (user_data, text) => { + clipboard_text = text; + // console.log(`set clipboard_text: "${clipboard_text}"`); + if (typeof navigator !== "undefined" && typeof navigator.clipboard !== "undefined") { + // console.log(`clipboard.writeText: "${clipboard_text}"`); + navigator.clipboard.writeText(clipboard_text).then(() => { + // console.log(`clipboard.writeText: "${clipboard_text}" done.`); + }); + } + }; + io.GetClipboardTextFn = (user_data) => { + // if (typeof navigator !== "undefined" && typeof (navigator as any).clipboard !== "undefined") { + // console.log(`clipboard.readText: "${clipboard_text}"`); + // (navigator as any).clipboard.readText().then((text: string): void => { + // clipboard_text = text; + // console.log(`clipboard.readText: "${clipboard_text}" done.`); + // }); + // } + // console.log(`get clipboard_text: "${clipboard_text}"`); + return clipboard_text; + }; + io.ClipboardUserData = null; + if (typeof (window) !== "undefined") { + window.addEventListener("resize", window_on_resize); + window.addEventListener("gamepadconnected", window_on_gamepadconnected); + window.addEventListener("gamepaddisconnected", window_on_gamepaddisconnected); + } + if (typeof (window) !== "undefined") { + if (value instanceof (HTMLCanvasElement)) { + canvas = value; + value = canvas.getContext("webgl2", { alpha: false }) || canvas.getContext("webgl", { alpha: false }) || canvas.getContext("2d"); + } + if (typeof WebGL2RenderingContext !== "undefined" && value instanceof (WebGL2RenderingContext)) { + io.BackendRendererName = "imgui_impl_webgl2"; + canvas = canvas || value.canvas; + exports.gl = value; + } + else if (typeof WebGLRenderingContext !== "undefined" && value instanceof (WebGLRenderingContext)) { + io.BackendRendererName = "imgui_impl_webgl"; + canvas = canvas || value.canvas; + exports.gl = value; + } + else if (typeof CanvasRenderingContext2D !== "undefined" && value instanceof (CanvasRenderingContext2D)) { + io.BackendRendererName = "imgui_impl_2d"; + canvas = canvas || value.canvas; + exports.ctx = value; + } + } + if (canvas !== null) { + window_on_resize(); + canvas.style.touchAction = "none"; // Disable browser handling of all panning and zooming gestures. + canvas.addEventListener("blur", canvas_on_blur); + canvas.addEventListener("keydown", canvas_on_keydown); + canvas.addEventListener("keyup", canvas_on_keyup); + canvas.addEventListener("keypress", canvas_on_keypress); + canvas.addEventListener("pointermove", canvas_on_pointermove); + canvas.addEventListener("pointerdown", canvas_on_pointerdown); + canvas.addEventListener("contextmenu", canvas_on_contextmenu); + canvas.addEventListener("pointerup", canvas_on_pointerup); + canvas.addEventListener("wheel", canvas_on_wheel); + } + // Setup back-end capabilities flags + io.BackendFlags |= ImGui.BackendFlags.HasMouseCursors; // We can honor GetMouseCursor() values (optional) + // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGui.Key.Tab] = 9; + io.KeyMap[ImGui.Key.LeftArrow] = 37; + io.KeyMap[ImGui.Key.RightArrow] = 39; + io.KeyMap[ImGui.Key.UpArrow] = 38; + io.KeyMap[ImGui.Key.DownArrow] = 40; + io.KeyMap[ImGui.Key.PageUp] = 33; + io.KeyMap[ImGui.Key.PageDown] = 34; + io.KeyMap[ImGui.Key.Home] = 36; + io.KeyMap[ImGui.Key.End] = 35; + io.KeyMap[ImGui.Key.Insert] = 45; + io.KeyMap[ImGui.Key.Delete] = 46; + io.KeyMap[ImGui.Key.Backspace] = 8; + io.KeyMap[ImGui.Key.Space] = 32; + io.KeyMap[ImGui.Key.Enter] = 13; + io.KeyMap[ImGui.Key.Escape] = 27; + io.KeyMap[ImGui.Key.KeyPadEnter] = key_code_to_index["NumpadEnter"]; + io.KeyMap[ImGui.Key.A] = 65; + io.KeyMap[ImGui.Key.C] = 67; + io.KeyMap[ImGui.Key.V] = 86; + io.KeyMap[ImGui.Key.X] = 88; + io.KeyMap[ImGui.Key.Y] = 89; + io.KeyMap[ImGui.Key.Z] = 90; + CreateDeviceObjects(); + } + function Shutdown() { + DestroyDeviceObjects(); + if (canvas !== null) { + canvas.removeEventListener("blur", canvas_on_blur); + canvas.removeEventListener("keydown", canvas_on_keydown); + canvas.removeEventListener("keyup", canvas_on_keyup); + canvas.removeEventListener("keypress", canvas_on_keypress); + canvas.removeEventListener("pointermove", canvas_on_pointermove); + canvas.removeEventListener("pointerdown", canvas_on_pointerdown); + canvas.removeEventListener("contextmenu", canvas_on_contextmenu); + canvas.removeEventListener("pointerup", canvas_on_pointerup); + canvas.removeEventListener("wheel", canvas_on_wheel); + } + exports.gl = null; + exports.ctx = null; + canvas = null; + if (typeof (window) !== "undefined") { + window.removeEventListener("resize", window_on_resize); + window.removeEventListener("gamepadconnected", window_on_gamepadconnected); + window.removeEventListener("gamepaddisconnected", window_on_gamepaddisconnected); + } + if (typeof (document) !== "undefined") { + document.body.removeEventListener("copy", document_on_copy); + document.body.removeEventListener("cut", document_on_cut); + document.body.removeEventListener("paste", document_on_paste); + } + } + function NewFrame(time) { + const io = ImGui.GetIO(); + if (io.WantSaveIniSettings) { + io.WantSaveIniSettings = false; + if (typeof (window) !== "undefined") { + window.localStorage.setItem("imgui.ini", ImGui.SaveIniSettingsToMemory()); + } + } + const w = canvas && canvas.scrollWidth || 640; + const h = canvas && canvas.scrollHeight || 480; + const display_w = exports.gl && exports.gl.drawingBufferWidth || w; + const display_h = exports.gl && exports.gl.drawingBufferHeight || h; + io.DisplaySize.x = w; + io.DisplaySize.y = h; + io.DisplayFramebufferScale.x = w > 0 ? (display_w / w) : 0; + io.DisplayFramebufferScale.y = h > 0 ? (display_h / h) : 0; + const dt = time - prev_time; + prev_time = time; + io.DeltaTime = dt / 1000; + if (io.WantSetMousePos) { + console.log("TODO: MousePos", io.MousePos.x, io.MousePos.y); + } + if (typeof (document) !== "undefined") { + if (io.MouseDrawCursor) { + document.body.style.cursor = "none"; + } + else { + switch (ImGui.GetMouseCursor()) { + case ImGui.MouseCursor.None: + document.body.style.cursor = "none"; + break; + default: + case ImGui.MouseCursor.Arrow: + document.body.style.cursor = "default"; + break; + case ImGui.MouseCursor.TextInput: + document.body.style.cursor = "text"; + break; // When hovering over InputText, etc. + case ImGui.MouseCursor.ResizeAll: + document.body.style.cursor = "all-scroll"; + break; // Unused + case ImGui.MouseCursor.ResizeNS: + document.body.style.cursor = "ns-resize"; + break; // When hovering over an horizontal border + case ImGui.MouseCursor.ResizeEW: + document.body.style.cursor = "ew-resize"; + break; // When hovering over a vertical border or a column + case ImGui.MouseCursor.ResizeNESW: + document.body.style.cursor = "nesw-resize"; + break; // When hovering over the bottom-left corner of a window + case ImGui.MouseCursor.ResizeNWSE: + document.body.style.cursor = "nwse-resize"; + break; // When hovering over the bottom-right corner of a window + case ImGui.MouseCursor.Hand: + document.body.style.cursor = "move"; + break; + case ImGui.MouseCursor.NotAllowed: + document.body.style.cursor = "not-allowed"; + break; + } + } + } + // Gamepad navigation mapping [BETA] + for (let i = 0; i < io.NavInputs.length; ++i) { + // TODO: This is currently causing an issue and I have no gamepad to test with. + // The error is: ''set' on proxy: trap returned falsish for property '21' + // I think that the NavInputs are zeroed out by ImGui at the start of each frame anyway + // so I am not sure if the following is even necessary. + //io.NavInputs[i] = 0.0; + } + if (io.ConfigFlags & ImGui.ConfigFlags.NavEnableGamepad) { + // Update gamepad inputs + const gamepads = (typeof (navigator) !== "undefined" && typeof (navigator.getGamepads) === "function") ? navigator.getGamepads() : []; + for (let i = 0; i < gamepads.length; ++i) { + const gamepad = gamepads[i]; + if (!gamepad) { + continue; + } + io.BackendFlags |= ImGui.BackendFlags.HasGamepad; + const buttons_count = gamepad.buttons.length; + const axes_count = gamepad.axes.length; + function MAP_BUTTON(NAV_NO, BUTTON_NO) { + if (!gamepad) { + return; + } + if (buttons_count > BUTTON_NO && gamepad.buttons[BUTTON_NO].pressed) + io.NavInputs[NAV_NO] = 1.0; + } + function MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { + if (!gamepad) { + return; + } + let v = (axes_count > AXIS_NO) ? gamepad.axes[AXIS_NO] : V0; + v = (v - V0) / (V1 - V0); + if (v > 1.0) + v = 1.0; + if (io.NavInputs[NAV_NO] < v) + io.NavInputs[NAV_NO] = v; + } + // TODO: map input based on vendor and product id + // https://developer.mozilla.org/en-US/docs/Web/API/Gamepad/id + const match = gamepad.id.match(/^([0-9a-f]{4})-([0-9a-f]{4})-.*$/); + const match_chrome = gamepad.id.match(/^.*\(.*Vendor: ([0-9a-f]{4}) Product: ([0-9a-f]{4})\).*$/); + const vendor = (match && match[1]) || (match_chrome && match_chrome[1]) || "0000"; + const product = (match && match[2]) || (match_chrome && match_chrome[2]) || "0000"; + switch (vendor + product) { + case "046dc216": // Logitech Logitech Dual Action (Vendor: 046d Product: c216) + MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 2); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 0); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_ANALOG(ImGui.NavInput.DpadLeft, 4, -0.3, -0.9); // D-Pad Left + MAP_ANALOG(ImGui.NavInput.DpadRight, 4, +0.3, +0.9); // D-Pad Right + MAP_ANALOG(ImGui.NavInput.DpadUp, 5, -0.3, -0.9); // D-Pad Up + MAP_ANALOG(ImGui.NavInput.DpadDown, 5, +0.3, +0.9); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + case "046dc21d": // Logitech Gamepad F310 (STANDARD GAMEPAD Vendor: 046d Product: c21d) + MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left + MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right + MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up + MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_ANALOG(ImGui.NavInput.TweakSlow, 6, +0.3, +0.9); // L2 / LT + MAP_ANALOG(ImGui.NavInput.TweakFast, 7, +0.3, +0.9); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + case "2dc86001": // 8Bitdo SN30 Pro 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6001) + case "2dc86101": // 8Bitdo SN30 Pro (Vendor: 2dc8 Product: 6101) + MAP_BUTTON(ImGui.NavInput.Activate, 1); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 0); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 4); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_ANALOG(ImGui.NavInput.DpadLeft, 6, -0.3, -0.9); // D-Pad Left + MAP_ANALOG(ImGui.NavInput.DpadRight, 6, +0.3, +0.9); // D-Pad Right + MAP_ANALOG(ImGui.NavInput.DpadUp, 7, -0.3, -0.9); // D-Pad Up + MAP_ANALOG(ImGui.NavInput.DpadDown, 7, +0.3, +0.9); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 6); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 7); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 8); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 9); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + default: // standard gamepad: https://w3c.github.io/gamepad/#remapping + MAP_BUTTON(ImGui.NavInput.Activate, 0); // Cross / A + MAP_BUTTON(ImGui.NavInput.Cancel, 1); // Circle / B + MAP_BUTTON(ImGui.NavInput.Menu, 2); // Square / X + MAP_BUTTON(ImGui.NavInput.Input, 3); // Triangle / Y + MAP_BUTTON(ImGui.NavInput.DpadLeft, 14); // D-Pad Left + MAP_BUTTON(ImGui.NavInput.DpadRight, 15); // D-Pad Right + MAP_BUTTON(ImGui.NavInput.DpadUp, 12); // D-Pad Up + MAP_BUTTON(ImGui.NavInput.DpadDown, 13); // D-Pad Down + MAP_BUTTON(ImGui.NavInput.FocusPrev, 4); // L1 / LB + MAP_BUTTON(ImGui.NavInput.FocusNext, 5); // R1 / RB + MAP_BUTTON(ImGui.NavInput.TweakSlow, 6); // L2 / LT + MAP_BUTTON(ImGui.NavInput.TweakFast, 7); // R2 / RT + MAP_ANALOG(ImGui.NavInput.LStickLeft, 0, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickRight, 0, +0.3, +0.9); + MAP_ANALOG(ImGui.NavInput.LStickUp, 1, -0.3, -0.9); + MAP_ANALOG(ImGui.NavInput.LStickDown, 1, +0.3, +0.9); + break; + } + } + } + } + function RenderDrawData(draw_data = ImGui.GetDrawData()) { + const io = ImGui.GetIO(); + if (draw_data === null) { + throw new Error(); + } + exports.gl || exports.ctx || console.log(draw_data); + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + const fb_width = io.DisplaySize.x * io.DisplayFramebufferScale.x; + const fb_height = io.DisplaySize.y * io.DisplayFramebufferScale.y; + if (fb_width === 0 || fb_height === 0) { + return; + } + draw_data.ScaleClipRects(io.DisplayFramebufferScale); + const gl2 = typeof WebGL2RenderingContext !== "undefined" && exports.gl instanceof WebGL2RenderingContext && exports.gl || null; + const gl_vao = exports.gl && exports.gl.getExtension("OES_vertex_array_object") || null; + // Backup GL state + const last_active_texture = exports.gl && exports.gl.getParameter(exports.gl.ACTIVE_TEXTURE) || null; + const last_program = exports.gl && exports.gl.getParameter(exports.gl.CURRENT_PROGRAM) || null; + const last_texture = exports.gl && exports.gl.getParameter(exports.gl.TEXTURE_BINDING_2D) || null; + const last_array_buffer = exports.gl && exports.gl.getParameter(exports.gl.ARRAY_BUFFER_BINDING) || null; + const last_element_array_buffer = exports.gl && exports.gl.getParameter(exports.gl.ELEMENT_ARRAY_BUFFER_BINDING) || null; + const last_vertex_array_object = gl2 && gl2.getParameter(gl2.VERTEX_ARRAY_BINDING) || exports.gl && gl_vao && exports.gl.getParameter(gl_vao.VERTEX_ARRAY_BINDING_OES) || null; + // GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); + const last_viewport = exports.gl && exports.gl.getParameter(exports.gl.VIEWPORT) || null; + const last_scissor_box = exports.gl && exports.gl.getParameter(exports.gl.SCISSOR_BOX) || null; + const last_blend_src_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_SRC_RGB) || null; + const last_blend_dst_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_DST_RGB) || null; + const last_blend_src_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_SRC_ALPHA) || null; + const last_blend_dst_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_DST_ALPHA) || null; + const last_blend_equation_rgb = exports.gl && exports.gl.getParameter(exports.gl.BLEND_EQUATION_RGB) || null; + const last_blend_equation_alpha = exports.gl && exports.gl.getParameter(exports.gl.BLEND_EQUATION_ALPHA) || null; + const last_enable_blend = exports.gl && exports.gl.getParameter(exports.gl.BLEND) || null; + const last_enable_cull_face = exports.gl && exports.gl.getParameter(exports.gl.CULL_FACE) || null; + const last_enable_depth_test = exports.gl && exports.gl.getParameter(exports.gl.DEPTH_TEST) || null; + const last_enable_scissor_test = exports.gl && exports.gl.getParameter(exports.gl.SCISSOR_TEST) || null; + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + const vertex_array_object = gl2 && gl2.createVertexArray() || gl_vao && gl_vao.createVertexArrayOES(); + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + exports.gl && exports.gl.enable(exports.gl.BLEND); + exports.gl && exports.gl.blendEquation(exports.gl.FUNC_ADD); + exports.gl && exports.gl.blendFunc(exports.gl.SRC_ALPHA, exports.gl.ONE_MINUS_SRC_ALPHA); + exports.gl && exports.gl.disable(exports.gl.CULL_FACE); + exports.gl && exports.gl.disable(exports.gl.DEPTH_TEST); + exports.gl && exports.gl.enable(exports.gl.SCISSOR_TEST); + // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + // Setup viewport, orthographic projection matrix + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayMin is typically (0,0) for single viewport apps. + exports.gl && exports.gl.viewport(0, 0, fb_width, fb_height); + const L = draw_data.DisplayPos.x; + const R = draw_data.DisplayPos.x + draw_data.DisplaySize.x; + const T = draw_data.DisplayPos.y; + const B = draw_data.DisplayPos.y + draw_data.DisplaySize.y; + const ortho_projection = new Float32Array([ + 2.0 / (R - L), 0.0, 0.0, 0.0, + 0.0, 2.0 / (T - B), 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + (R + L) / (L - R), (T + B) / (B - T), 0.0, 1.0, + ]); + exports.gl && exports.gl.useProgram(g_ShaderHandle); + exports.gl && exports.gl.uniform1i(g_AttribLocationTex, 0); + exports.gl && g_AttribLocationProjMtx && exports.gl.uniformMatrix4fv(g_AttribLocationProjMtx, false, ortho_projection); + gl2 && gl2.bindVertexArray(vertex_array_object) || gl_vao && gl_vao.bindVertexArrayOES(vertex_array_object); + // Render command lists + exports.gl && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, g_VboHandle); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationPosition); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationUV); + exports.gl && exports.gl.enableVertexAttribArray(g_AttribLocationColor); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationPosition, 2, exports.gl.FLOAT, false, ImGui.DrawVertSize, ImGui.DrawVertPosOffset); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationUV, 2, exports.gl.FLOAT, false, ImGui.DrawVertSize, ImGui.DrawVertUVOffset); + exports.gl && exports.gl.vertexAttribPointer(g_AttribLocationColor, 4, exports.gl.UNSIGNED_BYTE, true, ImGui.DrawVertSize, ImGui.DrawVertColOffset); + // Draw + const pos = draw_data.DisplayPos; + const idx_buffer_type = exports.gl && ((ImGui.DrawIdxSize === 4) ? exports.gl.UNSIGNED_INT : exports.gl.UNSIGNED_SHORT) || 0; + draw_data.IterateDrawLists((draw_list) => { + exports.gl || exports.ctx || console.log(draw_list); + exports.gl || exports.ctx || console.log("VtxBuffer.length", draw_list.VtxBuffer.length); + exports.gl || exports.ctx || console.log("IdxBuffer.length", draw_list.IdxBuffer.length); + let idx_buffer_offset = 0; + exports.gl && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, g_VboHandle); + exports.gl && exports.gl.bufferData(exports.gl.ARRAY_BUFFER, draw_list.VtxBuffer, exports.gl.STREAM_DRAW); + exports.gl && exports.gl.bindBuffer(exports.gl.ELEMENT_ARRAY_BUFFER, g_ElementsHandle); + exports.gl && exports.gl.bufferData(exports.gl.ELEMENT_ARRAY_BUFFER, draw_list.IdxBuffer, exports.gl.STREAM_DRAW); + draw_list.IterateDrawCmds((draw_cmd) => { + exports.gl || exports.ctx || console.log(draw_cmd); + exports.gl || exports.ctx || console.log("ElemCount", draw_cmd.ElemCount); + exports.gl || exports.ctx || console.log("ClipRect", draw_cmd.ClipRect.x, fb_height - draw_cmd.ClipRect.w, draw_cmd.ClipRect.z - draw_cmd.ClipRect.x, draw_cmd.ClipRect.w - draw_cmd.ClipRect.y); + exports.gl || exports.ctx || console.log("TextureId", draw_cmd.TextureId); + if (!exports.gl && !exports.ctx) { + console.log("i: pos.x pos.y uv.x uv.y col"); + for (let i = 0; i < Math.min(3, draw_cmd.ElemCount); ++i) { + const view = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i * ImGui.DrawVertSize); + console.log(`${i}: ${view.pos[0].toFixed(2)} ${view.pos[1].toFixed(2)} ${view.uv[0].toFixed(5)} ${view.uv[1].toFixed(5)} ${("00000000" + view.col[0].toString(16)).substr(-8)}`); + } + } + if (draw_cmd.UserCallback !== null) { + // User callback (registered via ImDrawList::AddCallback) + draw_cmd.UserCallback(draw_list, draw_cmd); + } + else { + const clip_rect = new ImGui.Vec4(draw_cmd.ClipRect.x - pos.x, draw_cmd.ClipRect.y - pos.y, draw_cmd.ClipRect.z - pos.x, draw_cmd.ClipRect.w - pos.y); + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0 && clip_rect.w >= 0.0) { + // Apply scissor/clipping rectangle + exports.gl && exports.gl.scissor(clip_rect.x, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + // Bind texture, Draw + exports.gl && exports.gl.activeTexture(exports.gl.TEXTURE0); + exports.gl && exports.gl.bindTexture(exports.gl.TEXTURE_2D, draw_cmd.TextureId); + exports.gl && exports.gl.drawElements(exports.gl.TRIANGLES, draw_cmd.ElemCount, idx_buffer_type, idx_buffer_offset); + if (exports.ctx) { + exports.ctx.save(); + exports.ctx.beginPath(); + exports.ctx.rect(clip_rect.x, clip_rect.y, clip_rect.z - clip_rect.x, clip_rect.w - clip_rect.y); + exports.ctx.clip(); + const idx = ImGui.DrawIdxSize === 4 ? + new Uint32Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset) : + new Uint16Array(draw_list.IdxBuffer.buffer, draw_list.IdxBuffer.byteOffset + idx_buffer_offset); + for (let i = 0; i < draw_cmd.ElemCount; i += 3) { + const i0 = idx[i + 0]; + const i1 = idx[i + 1]; + const i2 = idx[i + 2]; + const v0 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i0 * ImGui.DrawVertSize); + const v1 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i1 * ImGui.DrawVertSize); + const v2 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i2 * ImGui.DrawVertSize); + const i3 = idx[i + 3]; + const i4 = idx[i + 4]; + const i5 = idx[i + 5]; + const v3 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i3 * ImGui.DrawVertSize); + const v4 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i4 * ImGui.DrawVertSize); + const v5 = new ImGui.DrawVert(draw_list.VtxBuffer.buffer, draw_list.VtxBuffer.byteOffset + i5 * ImGui.DrawVertSize); + let quad = true; + let minmin = v0; + let minmax = v0; + let maxmin = v0; + let maxmax = v0; + for (const v of [v1, v2, v3, v4, v5]) { + let found = false; + if (v.pos[0] <= minmin.pos[0] && v.pos[1] <= minmin.pos[1]) { + minmin = v; + found = true; + } + if (v.pos[0] <= minmax.pos[0] && v.pos[1] >= minmax.pos[1]) { + minmax = v; + found = true; + } + if (v.pos[0] >= maxmin.pos[0] && v.pos[1] <= maxmin.pos[1]) { + maxmin = v; + found = true; + } + if (v.pos[0] >= maxmax.pos[0] && v.pos[1] >= maxmax.pos[1]) { + maxmax = v; + found = true; + } + if (!found) { + quad = false; + } + } + quad = quad && (minmin.pos[0] === minmax.pos[0]); + quad = quad && (maxmin.pos[0] === maxmax.pos[0]); + quad = quad && (minmin.pos[1] === maxmin.pos[1]); + quad = quad && (minmax.pos[1] === maxmax.pos[1]); + if (quad) { + if (minmin.uv[0] === maxmax.uv[0] || minmin.uv[1] === maxmax.uv[1]) { + // one vertex color + exports.ctx.beginPath(); + exports.ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + exports.ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; + exports.ctx.fill(); + } + else { + // no vertex color + const image = draw_cmd.TextureId; // HACK + const width = image instanceof HTMLVideoElement ? image.videoWidth : image.width; + const height = image instanceof HTMLVideoElement ? image.videoHeight : image.height; + image && exports.ctx.drawImage(image, minmin.uv[0] * width, minmin.uv[1] * height, (maxmax.uv[0] - minmin.uv[0]) * width, (maxmax.uv[1] - minmin.uv[1]) * height, minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + // ctx.beginPath(); + // ctx.rect(minmin.pos[0], minmin.pos[1], maxmax.pos[0] - minmin.pos[0], maxmax.pos[1] - minmin.pos[1]); + // ctx.strokeStyle = "yellow"; + // ctx.stroke(); + } + i += 3; + } + else { + // one vertex color, no texture + exports.ctx.beginPath(); + exports.ctx.moveTo(v0.pos[0], v0.pos[1]); + exports.ctx.lineTo(v1.pos[0], v1.pos[1]); + exports.ctx.lineTo(v2.pos[0], v2.pos[1]); + exports.ctx.closePath(); + exports.ctx.fillStyle = `rgba(${v0.col[0] >> 0 & 0xff}, ${v0.col[0] >> 8 & 0xff}, ${v0.col[0] >> 16 & 0xff}, ${(v0.col[0] >> 24 & 0xff) / 0xff})`; + exports.ctx.fill(); + } + } + exports.ctx.restore(); + } + } + } + idx_buffer_offset += draw_cmd.ElemCount * ImGui.DrawIdxSize; + }); + }); + // Destroy the temporary VAO + gl2 && gl2.deleteVertexArray(vertex_array_object) || gl_vao && gl_vao.deleteVertexArrayOES(vertex_array_object); + // Restore modified GL state + exports.gl && (last_program !== null) && exports.gl.useProgram(last_program); + exports.gl && (last_texture !== null) && exports.gl.bindTexture(exports.gl.TEXTURE_2D, last_texture); + exports.gl && (last_active_texture !== null) && exports.gl.activeTexture(last_active_texture); + gl2 && gl2.bindVertexArray(last_vertex_array_object) || gl_vao && gl_vao.bindVertexArrayOES(last_vertex_array_object); + exports.gl && (last_array_buffer !== null) && exports.gl.bindBuffer(exports.gl.ARRAY_BUFFER, last_array_buffer); + exports.gl && (last_element_array_buffer !== null) && exports.gl.bindBuffer(exports.gl.ELEMENT_ARRAY_BUFFER, last_element_array_buffer); + exports.gl && (last_blend_equation_rgb !== null && last_blend_equation_alpha !== null) && exports.gl.blendEquationSeparate(last_blend_equation_rgb, last_blend_equation_alpha); + exports.gl && (last_blend_src_rgb !== null && last_blend_src_alpha !== null && last_blend_dst_rgb !== null && last_blend_dst_alpha !== null) && exports.gl.blendFuncSeparate(last_blend_src_rgb, last_blend_src_alpha, last_blend_dst_rgb, last_blend_dst_alpha); + exports.gl && (last_enable_blend ? exports.gl.enable(exports.gl.BLEND) : exports.gl.disable(exports.gl.BLEND)); + exports.gl && (last_enable_cull_face ? exports.gl.enable(exports.gl.CULL_FACE) : exports.gl.disable(exports.gl.CULL_FACE)); + exports.gl && (last_enable_depth_test ? exports.gl.enable(exports.gl.DEPTH_TEST) : exports.gl.disable(exports.gl.DEPTH_TEST)); + exports.gl && (last_enable_scissor_test ? exports.gl.enable(exports.gl.SCISSOR_TEST) : exports.gl.disable(exports.gl.SCISSOR_TEST)); + // glPolygonMode(GL_FRONT_AND_BACK, (GLenum)last_polygon_mode[0]); + exports.gl && (last_viewport !== null) && exports.gl.viewport(last_viewport[0], last_viewport[1], last_viewport[2], last_viewport[3]); + exports.gl && (last_scissor_box !== null) && exports.gl.scissor(last_scissor_box[0], last_scissor_box[1], last_scissor_box[2], last_scissor_box[3]); + } + function CreateFontsTexture() { + const io = ImGui.GetIO(); + // Backup GL state + const last_texture = exports.gl && exports.gl.getParameter(exports.gl.TEXTURE_BINDING_2D); + // Build texture atlas + // const width: number = 256; + // const height: number = 256; + // const pixels: Uint8Array = new Uint8Array(4 * width * height).fill(0xff); + const { width, height, pixels } = io.Fonts.GetTexDataAsRGBA32(); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + // console.log(`font texture ${width} x ${height} @ ${pixels.length}`); + // Upload texture to graphics system + g_FontTexture = exports.gl && exports.gl.createTexture(); + exports.gl && exports.gl.bindTexture(exports.gl.TEXTURE_2D, g_FontTexture); + exports.gl && exports.gl.texParameteri(exports.gl.TEXTURE_2D, exports.gl.TEXTURE_MIN_FILTER, exports.gl.LINEAR); + exports.gl && exports.gl.texParameteri(exports.gl.TEXTURE_2D, exports.gl.TEXTURE_MAG_FILTER, exports.gl.LINEAR); + // gl && gl.pixelStorei(gl.UNPACK_ROW_LENGTH); // WebGL2 + exports.gl && exports.gl.texImage2D(exports.gl.TEXTURE_2D, 0, exports.gl.RGBA, width, height, 0, exports.gl.RGBA, exports.gl.UNSIGNED_BYTE, pixels); + // Store our identifier + io.Fonts.TexID = g_FontTexture || { foo: "bar" }; + // console.log("font texture id", g_FontTexture); + if (exports.ctx) { + const image_canvas = document.createElement("canvas"); + image_canvas.width = width; + image_canvas.height = height; + const image_ctx = image_canvas.getContext("2d"); + if (image_ctx === null) { + throw new Error(); + } + const image_data = image_ctx.getImageData(0, 0, width, height); + image_data.data.set(pixels); + image_ctx.putImageData(image_data, 0, 0); + io.Fonts.TexID = image_canvas; + } + // Restore modified GL state + exports.gl && last_texture && exports.gl.bindTexture(exports.gl.TEXTURE_2D, last_texture); + } + function DestroyFontsTexture() { + const io = ImGui.GetIO(); + io.Fonts.TexID = null; + exports.gl && exports.gl.deleteTexture(g_FontTexture); + g_FontTexture = null; + } + function CreateDeviceObjects() { + const vertex_shader = [ + "uniform mat4 ProjMtx;", + "attribute vec2 Position;", + "attribute vec2 UV;", + "attribute vec4 Color;", + "varying vec2 Frag_UV;", + "varying vec4 Frag_Color;", + "void main() {", + " Frag_UV = UV;", + " Frag_Color = Color;", + " gl_Position = ProjMtx * vec4(Position.xy,0,1);", + "}", + ]; + const fragment_shader = [ + "precision mediump float;", + "uniform sampler2D Texture;", + "varying vec2 Frag_UV;", + "varying vec4 Frag_Color;", + "void main() {", + " gl_FragColor = Frag_Color * texture2D(Texture, Frag_UV);", + "}", + ]; + g_ShaderHandle = exports.gl && exports.gl.createProgram(); + g_VertHandle = exports.gl && exports.gl.createShader(exports.gl.VERTEX_SHADER); + g_FragHandle = exports.gl && exports.gl.createShader(exports.gl.FRAGMENT_SHADER); + exports.gl && exports.gl.shaderSource(g_VertHandle, vertex_shader.join("\n")); + exports.gl && exports.gl.shaderSource(g_FragHandle, fragment_shader.join("\n")); + exports.gl && exports.gl.compileShader(g_VertHandle); + exports.gl && exports.gl.compileShader(g_FragHandle); + exports.gl && exports.gl.attachShader(g_ShaderHandle, g_VertHandle); + exports.gl && exports.gl.attachShader(g_ShaderHandle, g_FragHandle); + exports.gl && exports.gl.linkProgram(g_ShaderHandle); + g_AttribLocationTex = exports.gl && exports.gl.getUniformLocation(g_ShaderHandle, "Texture"); + g_AttribLocationProjMtx = exports.gl && exports.gl.getUniformLocation(g_ShaderHandle, "ProjMtx"); + g_AttribLocationPosition = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "Position") || 0; + g_AttribLocationUV = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "UV") || 0; + g_AttribLocationColor = exports.gl && exports.gl.getAttribLocation(g_ShaderHandle, "Color") || 0; + g_VboHandle = exports.gl && exports.gl.createBuffer(); + g_ElementsHandle = exports.gl && exports.gl.createBuffer(); + CreateFontsTexture(); + } + function DestroyDeviceObjects() { + DestroyFontsTexture(); + exports.gl && exports.gl.deleteBuffer(g_VboHandle); + g_VboHandle = null; + exports.gl && exports.gl.deleteBuffer(g_ElementsHandle); + g_ElementsHandle = null; + g_AttribLocationTex = null; + g_AttribLocationProjMtx = null; + g_AttribLocationPosition = -1; + g_AttribLocationUV = -1; + g_AttribLocationColor = -1; + exports.gl && exports.gl.deleteProgram(g_ShaderHandle); + g_ShaderHandle = null; + exports.gl && exports.gl.deleteShader(g_VertHandle); + g_VertHandle = null; + exports.gl && exports.gl.deleteShader(g_FragHandle); + g_FragHandle = null; + } + + exports.CreateDeviceObjects = CreateDeviceObjects; + exports.CreateFontsTexture = CreateFontsTexture; + exports.DestroyDeviceObjects = DestroyDeviceObjects; + exports.DestroyFontsTexture = DestroyFontsTexture; + exports.Init = Init; + exports.NewFrame = NewFrame; + exports.RenderDrawData = RenderDrawData; + exports.Shutdown = Shutdown; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/hw1/lib/three.js b/hw1/lib/three.js new file mode 100644 index 0000000..cea6fdc Binary files /dev/null and b/hw1/lib/three.js differ diff --git a/hw1/src/engine.js b/hw1/src/engine.js new file mode 100644 index 0000000..a31e181 --- /dev/null +++ b/hw1/src/engine.js @@ -0,0 +1,99 @@ +var cameraPosition = [30, 30, 30] + +//生成的纹理的分辨率,纹理必须是标准的尺寸 256*256 1024*1024 2048*2048 +var resolution = 2048; +var fbo; + +GAMES202Main(); + +function GAMES202Main() { + // Init canvas and gl + const canvas = document.querySelector('#glcanvas'); + canvas.width = window.screen.width; + canvas.height = window.screen.height; + const gl = canvas.getContext('webgl'); + if (!gl) { + alert('Unable to initialize WebGL. Your browser or machine may not support it.'); + return; + } + + // Add camera + const camera = new THREE.PerspectiveCamera(75, gl.canvas.clientWidth / gl.canvas.clientHeight, 1e-2, 1000); + camera.position.set(cameraPosition[0], cameraPosition[1], cameraPosition[2]); + + // Add resize listener + function setSize(width, height) { + camera.aspect = width / height; + camera.updateProjectionMatrix(); + } + setSize(canvas.clientWidth, canvas.clientHeight); + window.addEventListener('resize', () => setSize(canvas.clientWidth, canvas.clientHeight)); + + // Add camera control + const cameraControls = new THREE.OrbitControls(camera, canvas); + cameraControls.enableZoom = true; + cameraControls.enableRotate = true; + cameraControls.enablePan = true; + cameraControls.rotateSpeed = 0.3; + cameraControls.zoomSpeed = 1.0; + cameraControls.panSpeed = 0.8; + cameraControls.target.set(0, 0, 0); + + // Add renderer + const renderer = new WebGLRenderer(gl, camera); + + // Add lights + // light - is open shadow map == true + let lightPos = [0, 80, 80]; + let focalPoint = [0, 0, 0]; + let lightUp = [0, 1, 0] + const directionLight = new DirectionalLight(5000, [1, 1, 1], lightPos, focalPoint, lightUp, true, renderer.gl); + renderer.addLight(directionLight); + + // Add shapes + + let floorTransform = setTransform(0, 0, -30, 4, 4, 4); + let obj1Transform = setTransform(0, 0, 0, 20, 20, 20); + let obj2Transform = setTransform(40, 0, -40, 10, 10, 10); + + loadOBJ(renderer, 'assets/mary/', 'Marry', 'PhongMaterial', obj1Transform); + loadOBJ(renderer, 'assets/mary/', 'Marry', 'PhongMaterial', obj2Transform); + loadOBJ(renderer, 'assets/floor/', 'floor', 'PhongMaterial', floorTransform); + + + // let floorTransform = setTransform(0, 0, 0, 100, 100, 100); + // let cubeTransform = setTransform(0, 50, 0, 10, 50, 10); + // let sphereTransform = setTransform(30, 10, 0, 10, 10, 10); + + //loadOBJ(renderer, 'assets/basic/', 'cube', 'PhongMaterial', cubeTransform); + // loadOBJ(renderer, 'assets/basic/', 'sphere', 'PhongMaterial', sphereTransform); + //loadOBJ(renderer, 'assets/basic/', 'plane', 'PhongMaterial', floorTransform); + + + function createGUI() { + const gui = new dat.gui.GUI(); + // const panelModel = gui.addFolder('Model properties'); + // panelModelTrans.add(GUIParams, 'x').name('X'); + // panelModel.open(); + } + createGUI(); + + function mainLoop(now) { + cameraControls.update(); + + renderer.render(); + requestAnimationFrame(mainLoop); + } + requestAnimationFrame(mainLoop); +} + +function setTransform(t_x, t_y, t_z, s_x, s_y, s_z) { + return { + modelTransX: t_x, + modelTransY: t_y, + modelTransZ: t_z, + modelScaleX: s_x, + modelScaleY: s_y, + modelScaleZ: s_z, + }; +} diff --git a/hw1/src/lights/DirectionalLight.js b/hw1/src/lights/DirectionalLight.js new file mode 100644 index 0000000..100eb9e --- /dev/null +++ b/hw1/src/lights/DirectionalLight.js @@ -0,0 +1,35 @@ +class DirectionalLight { + + constructor(lightIntensity, lightColor, lightPos, focalPoint, lightUp, hasShadowMap, gl) { + this.mesh = Mesh.cube(setTransform(0, 0, 0, 0.2, 0.2, 0.2, 0)); + this.mat = new EmissiveMaterial(lightIntensity, lightColor); + this.lightPos = lightPos; + this.focalPoint = focalPoint; + this.lightUp = lightUp + + this.hasShadowMap = hasShadowMap; + this.fbo = new FBO(gl); + if (!this.fbo) { + console.log("无法设置帧缓冲区对象"); + return; + } + } + + CalcLightMVP(translate, scale) { + let lightMVP = mat4.create(); + let modelMatrix = mat4.create(); + let viewMatrix = mat4.create(); + let projectionMatrix = mat4.create(); + + // Model transform + + // View transform + + // Projection transform + + mat4.multiply(lightMVP, projectionMatrix, viewMatrix); + mat4.multiply(lightMVP, lightMVP, modelMatrix); + + return lightMVP; + } +} diff --git a/hw1/src/lights/Light.js b/hw1/src/lights/Light.js new file mode 100644 index 0000000..416e9f9 --- /dev/null +++ b/hw1/src/lights/Light.js @@ -0,0 +1,16 @@ +class EmissiveMaterial extends Material { + + constructor(lightIntensity, lightColor) { + super({ + 'uLigIntensity': { type: '1f', value: lightIntensity }, + 'uLightColor': { type: '3fv', value: lightColor } + }, [], LightCubeVertexShader, LightCubeFragmentShader); + + this.intensity = lightIntensity; + this.color = lightColor; + } + + GetIntensity() { + return [this.intensity * this.color[0], this.intensity * this.color[1], this.intensity * this.color[2]] + } +} diff --git a/hw1/src/lights/PointLight.js b/hw1/src/lights/PointLight.js new file mode 100644 index 0000000..ff10d9f --- /dev/null +++ b/hw1/src/lights/PointLight.js @@ -0,0 +1,19 @@ +class PointLight { + /** + * Creates an instance of PointLight. + * @param {float} lightIntensity The intensity of the PointLight. + * @param {vec3f} lightColor The color of the PointLight. + * @memberof PointLight + */ + constructor(lightIntensity, lightColor, hasShadowMap, gl) { + this.mesh = Mesh.cube(setTransform(0, 0, 0, 0.2, 0.2, 0.2, 0)); + this.mat = new EmissiveMaterial(lightIntensity, lightColor); + + this.hasShadowMap = hasShadowMap; + this.fbo = new FBO(gl); + if (!this.fbo) { + console.log("无法设置帧缓冲区对象"); + return; + } + } +} \ No newline at end of file diff --git a/hw1/src/loads/loadOBJ.js b/hw1/src/loads/loadOBJ.js new file mode 100644 index 0000000..7148733 --- /dev/null +++ b/hw1/src/loads/loadOBJ.js @@ -0,0 +1,69 @@ +function loadOBJ(renderer, path, name, objMaterial, transform) { + + const manager = new THREE.LoadingManager(); + manager.onProgress = function (item, loaded, total) { + console.log(item, loaded, total); + }; + + function onProgress(xhr) { + if (xhr.lengthComputable) { + const percentComplete = xhr.loaded / xhr.total * 100; + console.log('model ' + Math.round(percentComplete, 2) + '% downloaded'); + } + } + function onError() { } + + new THREE.MTLLoader(manager) + .setPath(path) + .load(name + '.mtl', function (materials) { + materials.preload(); + new THREE.OBJLoader(manager) + .setMaterials(materials) + .setPath(path) + .load(name + '.obj', function (object) { + object.traverse(function (child) { + if (child.isMesh) { + let geo = child.geometry; + let mat; + if (Array.isArray(child.material)) mat = child.material[0]; + else mat = child.material; + + var indices = Array.from({ length: geo.attributes.position.count }, (v, k) => k); + let mesh = new Mesh({ name: 'aVertexPosition', array: geo.attributes.position.array }, + { name: 'aNormalPosition', array: geo.attributes.normal.array }, + { name: 'aTextureCoord', array: geo.attributes.uv.array }, + indices, transform); + + let colorMap = new Texture(); + if (mat.map != null) { + colorMap.CreateImageTexture(renderer.gl, mat.map.image); + } + else { + colorMap.CreateConstantTexture(renderer.gl, mat.color.toArray()); + } + + let material, shadowMaterial; + let Translation = [transform.modelTransX, transform.modelTransY, transform.modelTransZ]; + let Scale = [transform.modelScaleX, transform.modelScaleY, transform.modelScaleZ]; + + let light = renderer.lights[0].entity; + switch (objMaterial) { + case 'PhongMaterial': + material = buildPhongMaterial(colorMap, mat.specular.toArray(), light, Translation, Scale, "./src/shaders/phongShader/phongVertex.glsl", "./src/shaders/phongShader/phongFragment.glsl"); + shadowMaterial = buildShadowMaterial(light, Translation, Scale, "./src/shaders/shadowShader/shadowVertex.glsl", "./src/shaders/shadowShader/shadowFragment.glsl"); + break; + } + + material.then((data) => { + let meshRender = new MeshRender(renderer.gl, mesh, data); + renderer.addMeshRender(meshRender); + }); + shadowMaterial.then((data) => { + let shadowMeshRender = new MeshRender(renderer.gl, mesh, data); + renderer.addShadowMeshRender(shadowMeshRender); + }); + } + }); + }, onProgress, onError); + }); +} diff --git a/hw1/src/loads/loadShader.js b/hw1/src/loads/loadShader.js new file mode 100644 index 0000000..fb82c99 --- /dev/null +++ b/hw1/src/loads/loadShader.js @@ -0,0 +1,21 @@ +async function loadShaderFile(filename) { + + return new Promise((resolve, reject) => { + const loader = new THREE.FileLoader(); + + loader.load(filename, (data) => { + resolve(data); + //console.log(data); + }); + }); +} + +async function getShaderString(filename) { + + let val = '' + await this.loadShaderFile(filename).then(result => { + val = result; + }); + //console.log(val); + return val; +} diff --git a/hw1/src/materials/Material.js b/hw1/src/materials/Material.js new file mode 100644 index 0000000..d706983 --- /dev/null +++ b/hw1/src/materials/Material.js @@ -0,0 +1,35 @@ +class Material { + #flatten_uniforms; + #flatten_attribs; + #vsSrc; + #fsSrc; + // Uniforms is a map, attribs is a Array + constructor(uniforms, attribs, vsSrc, fsSrc, frameBuffer) { + this.uniforms = uniforms; + this.attribs = attribs; + this.#vsSrc = vsSrc; + this.#fsSrc = fsSrc; + + this.#flatten_uniforms = ['uViewMatrix','uModelMatrix', 'uProjectionMatrix', 'uCameraPos', 'uLightPos']; + for (let k in uniforms) { + this.#flatten_uniforms.push(k); + } + this.#flatten_attribs = attribs; + + this.frameBuffer = frameBuffer; + } + + setMeshAttribs(extraAttribs) { + for (let i = 0; i < extraAttribs.length; i++) { + this.#flatten_attribs.push(extraAttribs[i]); + } + } + + compile(gl) { + return new Shader(gl, this.#vsSrc, this.#fsSrc, + { + uniforms: this.#flatten_uniforms, + attribs: this.#flatten_attribs + }); + } +} \ No newline at end of file diff --git a/hw1/src/materials/PhongMaterial.js b/hw1/src/materials/PhongMaterial.js new file mode 100644 index 0000000..5bb0784 --- /dev/null +++ b/hw1/src/materials/PhongMaterial.js @@ -0,0 +1,28 @@ +class PhongMaterial extends Material { + + constructor(color, specular, light, translate, scale, vertexShader, fragmentShader) { + let lightMVP = light.CalcLightMVP(translate, scale); + let lightIntensity = light.mat.GetIntensity(); + + super({ + // Phong + 'uSampler': { type: 'texture', value: color }, + 'uKs': { type: '3fv', value: specular }, + 'uLightIntensity': { type: '3fv', value: lightIntensity }, + // Shadow + 'uShadowMap': { type: 'texture', value: light.fbo }, + 'uLightMVP': { type: 'matrix4fv', value: lightMVP }, + + }, [], vertexShader, fragmentShader); + } +} + +async function buildPhongMaterial(color, specular, light, translate, scale, vertexPath, fragmentPath) { + + + let vertexShader = await getShaderString(vertexPath); + let fragmentShader = await getShaderString(fragmentPath); + + return new PhongMaterial(color, specular, light, translate, scale, vertexShader, fragmentShader); + +} \ No newline at end of file diff --git a/hw1/src/materials/ShadowMaterial.js b/hw1/src/materials/ShadowMaterial.js new file mode 100644 index 0000000..794aac2 --- /dev/null +++ b/hw1/src/materials/ShadowMaterial.js @@ -0,0 +1,20 @@ +class ShadowMaterial extends Material { + + constructor(light, translate, scale, vertexShader, fragmentShader) { + let lightMVP = light.CalcLightMVP(translate, scale); + + super({ + 'uLightMVP': { type: 'matrix4fv', value: lightMVP } + }, [], vertexShader, fragmentShader, light.fbo); + } +} + +async function buildShadowMaterial(light, translate, scale, vertexPath, fragmentPath) { + + + let vertexShader = await getShaderString(vertexPath); + let fragmentShader = await getShaderString(fragmentPath); + + return new ShadowMaterial(light, translate, scale, vertexShader, fragmentShader); + +} \ No newline at end of file diff --git a/hw1/src/objects/Mesh.js b/hw1/src/objects/Mesh.js new file mode 100644 index 0000000..8a96cde --- /dev/null +++ b/hw1/src/objects/Mesh.js @@ -0,0 +1,88 @@ +class TRSTransform { + constructor(translate = [0, 0, 0], scale = [1, 1, 1]) { + this.translate = translate; + this.scale = scale; + } +} +class Mesh { + constructor(verticesAttrib, normalsAttrib, texcoordsAttrib, indices, transform) { + this.indices = indices; + this.count = indices.length; + this.hasVertices = false; + this.hasNormals = false; + this.hasTexcoords = false; + + const modelTranslation = [transform.modelTransX, transform.modelTransY, transform.modelTransZ]; + const modelScale = [transform.modelScaleX, transform.modelScaleY, transform.modelScaleZ]; + let meshTrans = new TRSTransform(modelTranslation, modelScale); + this.transform = meshTrans; + + let extraAttribs = []; + + if (verticesAttrib != null) { + this.hasVertices = true; + this.vertices = verticesAttrib.array; + this.verticesName = verticesAttrib.name; + } + if (normalsAttrib != null) { + this.hasNormals = true; + this.normals = normalsAttrib.array; + this.normalsName = normalsAttrib.name; + } + if (texcoordsAttrib != null) { + this.hasTexcoords = true; + this.texcoords = texcoordsAttrib.array; + this.texcoordsName = texcoordsAttrib.name; + } + } + + static cube(transform) { + const positions = [ + // Front face + -1.0, -1.0, 1.0, + 1.0, -1.0, 1.0, + 1.0, 1.0, 1.0, + -1.0, 1.0, 1.0, + + // Back face + -1.0, -1.0, -1.0, + -1.0, 1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, -1.0, -1.0, + + // Top face + -1.0, 1.0, -1.0, + -1.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, + + // Bottom face + -1.0, -1.0, -1.0, + 1.0, -1.0, -1.0, + 1.0, -1.0, 1.0, + -1.0, -1.0, 1.0, + + // Right face + 1.0, -1.0, -1.0, + 1.0, 1.0, -1.0, + 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, + + // Left face + -1.0, -1.0, -1.0, + -1.0, -1.0, 1.0, + -1.0, 1.0, 1.0, + -1.0, 1.0, -1.0, + ]; + const indices = [ + 0, 1, 2, 0, 2, 3, // front + 4, 5, 6, 4, 6, 7, // back + 8, 9, 10, 8, 10, 11, // top + 12, 13, 14, 12, 14, 15, // bottom + 16, 17, 18, 16, 18, 19, // right + 20, 21, 22, 20, 22, 23, // left + ]; + + return new Mesh({ name: 'aVertexPosition', array: new Float32Array(positions) }, null, null, indices, transform); + } +} \ No newline at end of file diff --git a/hw1/src/renderers/MeshRender.js b/hw1/src/renderers/MeshRender.js new file mode 100644 index 0000000..424ba53 --- /dev/null +++ b/hw1/src/renderers/MeshRender.js @@ -0,0 +1,206 @@ + +class MeshRender { + + #vertexBuffer; + #normalBuffer; + #texcoordBuffer; + #indicesBuffer; + + constructor(gl, mesh, material) { + + this.gl = gl; + this.mesh = mesh; + this.material = material; + + this.#vertexBuffer = gl.createBuffer(); + this.#normalBuffer = gl.createBuffer(); + this.#texcoordBuffer = gl.createBuffer(); + this.#indicesBuffer = gl.createBuffer(); + + let extraAttribs = [] + if (mesh.hasVertices) { + extraAttribs.push(mesh.verticesName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#vertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.vertices, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + if (mesh.hasNormals) { + extraAttribs.push(mesh.normalsName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#normalBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.normals, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + if (mesh.hasTexcoords) { + extraAttribs.push(mesh.texcoordsName); + gl.bindBuffer(gl.ARRAY_BUFFER, this.#texcoordBuffer); + gl.bufferData(gl.ARRAY_BUFFER, mesh.texcoords, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#indicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(mesh.indices), gl.STATIC_DRAW); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); + + this.material.setMeshAttribs(extraAttribs); + this.shader = this.material.compile(gl); + } + + bindGeometryInfo() { + const gl = this.gl; + + if (this.mesh.hasVertices) { + const numComponents = 3; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#vertexBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.verticesName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.verticesName]); + } + + if (this.mesh.hasNormals) { + const numComponents = 3; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#normalBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.normalsName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.normalsName]); + } + + if (this.mesh.hasTexcoords) { + const numComponents = 2; + const type = gl.FLOAT; + const normalize = false; + const stride = 0; + const offset = 0; + gl.bindBuffer(gl.ARRAY_BUFFER, this.#texcoordBuffer); + gl.vertexAttribPointer( + this.shader.program.attribs[this.mesh.texcoordsName], + numComponents, + type, + normalize, + stride, + offset); + gl.enableVertexAttribArray( + this.shader.program.attribs[this.mesh.texcoordsName]); + } + + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#indicesBuffer); + } + + bindCameraParameters(camera) { + const gl = this.gl; + + let modelMatrix = mat4.create(); + let viewMatrix = mat4.create(); + let projectionMatrix = mat4.create(); + // Model transform + mat4.identity(modelMatrix); + mat4.translate(modelMatrix, modelMatrix, this.mesh.transform.translate); + mat4.scale(modelMatrix, modelMatrix, this.mesh.transform.scale); + // View transform + camera.updateMatrixWorld(); + mat4.invert(viewMatrix, camera.matrixWorld.elements); + // mat4.lookAt(viewMatrix, cameraPosition, [0,0,0], [0,1,0]); + // Projection transform + mat4.copy(projectionMatrix, camera.projectionMatrix.elements); + + gl.uniformMatrix4fv( + this.shader.program.uniforms.uProjectionMatrix, + false, + projectionMatrix); + gl.uniformMatrix4fv( + this.shader.program.uniforms.uModelMatrix, + false, + modelMatrix); + gl.uniformMatrix4fv( + this.shader.program.uniforms.uViewMatrix, + false, + viewMatrix); + gl.uniform3fv( + this.shader.program.uniforms.uCameraPos, + [camera.position.x, camera.position.y, camera.position.z]); + } + + bindMaterialParameters() { + const gl = this.gl; + + let textureNum = 0; + for (let k in this.material.uniforms) { + + if (this.material.uniforms[k].type == 'matrix4fv') { + gl.uniformMatrix4fv( + this.shader.program.uniforms[k], + false, + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '3fv') { + gl.uniform3fv( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '1f') { + gl.uniform1f( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == '1i') { + gl.uniform1i( + this.shader.program.uniforms[k], + this.material.uniforms[k].value); + } else if (this.material.uniforms[k].type == 'texture') { + gl.activeTexture(gl.TEXTURE0 + textureNum); + gl.bindTexture(gl.TEXTURE_2D, this.material.uniforms[k].value.texture); + gl.uniform1i(this.shader.program.uniforms[k], textureNum); + textureNum += 1; + } + } + } + + draw(camera) { + const gl = this.gl; + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.material.frameBuffer); + if (this.material.frameBuffer != null) { + // Shadow map + gl.viewport(0.0, 0.0, resolution, resolution); + } else { + gl.viewport(0.0, 0.0, window.screen.width, window.screen.height); + } + + gl.useProgram(this.shader.program.glShaderProgram); + + // Bind geometry information + this.bindGeometryInfo(); + + // Bind Camera parameters + this.bindCameraParameters(camera); + + // Bind material parameters + this.bindMaterialParameters(); + + // Draw + { + const vertexCount = this.mesh.count; + const type = gl.UNSIGNED_SHORT; + const offset = 0; + gl.drawElements(gl.TRIANGLES, vertexCount, type, offset); + } + } +} \ No newline at end of file diff --git a/hw1/src/renderers/WebGLRenderer.js b/hw1/src/renderers/WebGLRenderer.js new file mode 100644 index 0000000..e74fcc6 --- /dev/null +++ b/hw1/src/renderers/WebGLRenderer.js @@ -0,0 +1,52 @@ +class WebGLRenderer { + meshes = []; + shadowMeshes = []; + lights = []; + + constructor(gl, camera) { + this.gl = gl; + this.camera = camera; + } + + addLight(light) { + this.lights.push({ + entity: light, + meshRender: new MeshRender(this.gl, light.mesh, light.mat) + }); + } + addMeshRender(mesh) { this.meshes.push(mesh); } + addShadowMeshRender(mesh) { this.shadowMeshes.push(mesh); } + + render() { + const gl = this.gl; + + gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque + gl.clearDepth(1.0); // Clear everything + gl.enable(gl.DEPTH_TEST); // Enable depth testing + gl.depthFunc(gl.LEQUAL); // Near things obscure far things + + console.assert(this.lights.length != 0, "No light"); + console.assert(this.lights.length == 1, "Multiple lights"); + + for (let l = 0; l < this.lights.length; l++) { + // Draw light + // TODO: Support all kinds of transform + this.lights[l].meshRender.mesh.transform.translate = this.lights[l].entity.lightPos; + this.lights[l].meshRender.draw(this.camera); + + // Shadow pass + if (this.lights[l].entity.hasShadowMap == true) { + for (let i = 0; i < this.shadowMeshes.length; i++) { + this.shadowMeshes[i].draw(this.camera); + } + } + + // Camera pass + for (let i = 0; i < this.meshes.length; i++) { + this.gl.useProgram(this.meshes[i].shader.program.glShaderProgram); + this.gl.uniform3fv(this.meshes[i].shader.program.uniforms.uLightPos, this.lights[l].entity.lightPos); + this.meshes[i].draw(this.camera); + } + } + } +} \ No newline at end of file diff --git a/hw1/src/shaders/InternalShader.js b/hw1/src/shaders/InternalShader.js new file mode 100644 index 0000000..fb3b5d4 --- /dev/null +++ b/hw1/src/shaders/InternalShader.js @@ -0,0 +1,28 @@ +const LightCubeVertexShader = ` +attribute vec3 aVertexPosition; + +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjectionMatrix; + + +void main(void) { + + gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aVertexPosition, 1.0); + +} +`; + +const LightCubeFragmentShader = ` +#ifdef GL_ES +precision mediump float; +#endif + +uniform float uLigIntensity; +uniform vec3 uLightColor; + +void main(void) { + + gl_FragColor = vec4(uLightColor, 1.0); +} +`; \ No newline at end of file diff --git a/hw1/src/shaders/Shader.js b/hw1/src/shaders/Shader.js new file mode 100644 index 0000000..27f5cfb --- /dev/null +++ b/hw1/src/shaders/Shader.js @@ -0,0 +1,62 @@ +class Shader { + + constructor(gl, vsSrc, fsSrc, shaderLocations) { + this.gl = gl; + const vs = this.compileShader(vsSrc, gl.VERTEX_SHADER); + const fs = this.compileShader(fsSrc, gl.FRAGMENT_SHADER); + + this.program = this.addShaderLocations({ + glShaderProgram: this.linkShader(vs, fs), + }, shaderLocations); + } + + compileShader(shaderSource, shaderType) { + const gl = this.gl; + var shader = gl.createShader(shaderType); + gl.shaderSource(shader, shaderSource); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + console.error(shaderSource); + console.error('shader compiler error:\n' + gl.getShaderInfoLog(shader)); + } + + return shader; + }; + + linkShader(vs, fs) { + const gl = this.gl; + var prog = gl.createProgram(); + gl.attachShader(prog, vs); + gl.attachShader(prog, fs); + gl.linkProgram(prog); + + if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { + abort('shader linker error:\n' + gl.getProgramInfoLog(prog)); + } + return prog; + }; + + addShaderLocations(result, shaderLocations) { + const gl = this.gl; + result.uniforms = {}; + result.attribs = {}; + + if (shaderLocations && shaderLocations.uniforms && shaderLocations.uniforms.length) { + for (let i = 0; i < shaderLocations.uniforms.length; ++i) { + result.uniforms = Object.assign(result.uniforms, { + [shaderLocations.uniforms[i]]: gl.getUniformLocation(result.glShaderProgram, shaderLocations.uniforms[i]), + }); + } + } + if (shaderLocations && shaderLocations.attribs && shaderLocations.attribs.length) { + for (let i = 0; i < shaderLocations.attribs.length; ++i) { + result.attribs = Object.assign(result.attribs, { + [shaderLocations.attribs[i]]: gl.getAttribLocation(result.glShaderProgram, shaderLocations.attribs[i]), + }); + } + } + + return result; + } +} diff --git a/hw1/src/shaders/lightShader/lightCubeFragment.glsl b/hw1/src/shaders/lightShader/lightCubeFragment.glsl new file mode 100644 index 0000000..b61a8d4 --- /dev/null +++ b/hw1/src/shaders/lightShader/lightCubeFragment.glsl @@ -0,0 +1,8 @@ +#ifdef GL_ES +precision mediump float; +#endif + +uniform float uLigIntensity; +uniform vec3 uLightColor; + +void main(void) { gl_FragColor = vec4(uLightColor, 1.0); } \ No newline at end of file diff --git a/hw1/src/shaders/lightShader/lightCubeVertexShader.glsl b/hw1/src/shaders/lightShader/lightCubeVertexShader.glsl new file mode 100644 index 0000000..d576053 --- /dev/null +++ b/hw1/src/shaders/lightShader/lightCubeVertexShader.glsl @@ -0,0 +1,11 @@ +attribute vec3 aVertexPosition; + +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjectionMatrix; + +void main(void) { + + gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * + vec4(aVertexPosition, 1.0); +} \ No newline at end of file diff --git a/hw1/src/shaders/phongShader/phongFragment.glsl b/hw1/src/shaders/phongShader/phongFragment.glsl new file mode 100644 index 0000000..85335ad --- /dev/null +++ b/hw1/src/shaders/phongShader/phongFragment.glsl @@ -0,0 +1,145 @@ +#ifdef GL_ES +precision mediump float; +#endif + +// Phong related variables +uniform sampler2D uSampler; +uniform vec3 uKd; +uniform vec3 uKs; +uniform vec3 uLightPos; +uniform vec3 uCameraPos; +uniform vec3 uLightIntensity; + +varying highp vec2 vTextureCoord; +varying highp vec3 vFragPos; +varying highp vec3 vNormal; + +// Shadow map related variables +#define NUM_SAMPLES 20 +#define BLOCKER_SEARCH_NUM_SAMPLES NUM_SAMPLES +#define PCF_NUM_SAMPLES NUM_SAMPLES +#define NUM_RINGS 10 + +#define EPS 1e-3 +#define PI 3.141592653589793 +#define PI2 6.283185307179586 + +uniform sampler2D uShadowMap; + +varying vec4 vPositionFromLight; + +highp float rand_1to1(highp float x ) { + // -1 -1 + return fract(sin(x)*10000.0); +} + +highp float rand_2to1(vec2 uv ) { + // 0 - 1 + const highp float a = 12.9898, b = 78.233, c = 43758.5453; + highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); + return fract(sin(sn) * c); +} + +float unpack(vec4 rgbaDepth) { + const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0*256.0), 1.0/(256.0*256.0*256.0)); + return dot(rgbaDepth, bitShift); +} + +vec2 poissonDisk[NUM_SAMPLES]; + +void poissonDiskSamples( const in vec2 randomSeed ) { + + float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES ); + float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES ); + + float angle = rand_2to1( randomSeed ) * PI2; + float radius = INV_NUM_SAMPLES; + float radiusStep = radius; + + for( int i = 0; i < NUM_SAMPLES; i ++ ) { + poissonDisk[i] = vec2( cos( angle ), sin( angle ) ) * pow( radius, 0.75 ); + radius += radiusStep; + angle += ANGLE_STEP; + } +} + +void uniformDiskSamples( const in vec2 randomSeed ) { + + float randNum = rand_2to1(randomSeed); + float sampleX = rand_1to1( randNum ) ; + float sampleY = rand_1to1( sampleX ) ; + + float angle = sampleX * PI2; + float radius = sqrt(sampleY); + + for( int i = 0; i < NUM_SAMPLES; i ++ ) { + poissonDisk[i] = vec2( radius * cos(angle) , radius * sin(angle) ); + + sampleX = rand_1to1( sampleY ) ; + sampleY = rand_1to1( sampleX ) ; + + angle = sampleX * PI2; + radius = sqrt(sampleY); + } +} + +float findBlocker( sampler2D shadowMap, vec2 uv, float zReceiver ) { + return 1.0; +} + +float PCF(sampler2D shadowMap, vec4 coords) { + return 1.0; +} + +float PCSS(sampler2D shadowMap, vec4 coords){ + + // STEP 1: avgblocker depth + + // STEP 2: penumbra size + + // STEP 3: filtering + + return 1.0; + +} + + +float useShadowMap(sampler2D shadowMap, vec4 shadowCoord){ + return 1.0; +} + +vec3 blinnPhong() { + vec3 color = texture2D(uSampler, vTextureCoord).rgb; + color = pow(color, vec3(2.2)); + + vec3 ambient = 0.05 * color; + + vec3 lightDir = normalize(uLightPos); + vec3 normal = normalize(vNormal); + float diff = max(dot(lightDir, normal), 0.0); + vec3 light_atten_coff = + uLightIntensity / pow(length(uLightPos - vFragPos), 2.0); + vec3 diffuse = diff * light_atten_coff * color; + + vec3 viewDir = normalize(uCameraPos - vFragPos); + vec3 halfDir = normalize((lightDir + viewDir)); + float spec = pow(max(dot(halfDir, normal), 0.0), 32.0); + vec3 specular = uKs * light_atten_coff * spec; + + vec3 radiance = (ambient + diffuse + specular); + vec3 phongColor = pow(radiance, vec3(1.0 / 2.2)); + return phongColor; +} + +void main(void) { + + float visibility; + //visibility = useShadowMap(uShadowMap, vec4(shadowCoord, 1.0)); + //visibility = PCF(uShadowMap, vec4(shadowCoord, 1.0)); + //visibility = PCSS(uShadowMap, vec4(shadowCoord, 1.0)); + + vec3 phongColor = blinnPhong(); + + //gl_FragColor = vec4(phongColor * visibility, 1.0); + gl_FragColor = vec4(phongColor, 1.0); +} \ No newline at end of file diff --git a/hw1/src/shaders/phongShader/phongVertex.glsl b/hw1/src/shaders/phongShader/phongVertex.glsl new file mode 100644 index 0000000..995772f --- /dev/null +++ b/hw1/src/shaders/phongShader/phongVertex.glsl @@ -0,0 +1,25 @@ +attribute vec3 aVertexPosition; +attribute vec3 aNormalPosition; +attribute vec2 aTextureCoord; + +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjectionMatrix; +uniform mat4 uLightMVP; + +varying highp vec2 vTextureCoord; +varying highp vec3 vFragPos; +varying highp vec3 vNormal; +varying highp vec4 vPositionFromLight; + +void main(void) { + + vFragPos = (uModelMatrix * vec4(aVertexPosition, 1.0)).xyz; + vNormal = (uModelMatrix * vec4(aNormalPosition, 0.0)).xyz; + + gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * + vec4(aVertexPosition, 1.0); + + vTextureCoord = aTextureCoord; + vPositionFromLight = uLightMVP * vec4(aVertexPosition, 1.0); +} \ No newline at end of file diff --git a/hw1/src/shaders/shadowShader/shadowFragment.glsl b/hw1/src/shaders/shadowShader/shadowFragment.glsl new file mode 100644 index 0000000..96dfc8a --- /dev/null +++ b/hw1/src/shaders/shadowShader/shadowFragment.glsl @@ -0,0 +1,25 @@ +#ifdef GL_ES +precision mediump float; +#endif + +uniform vec3 uLightPos; +uniform vec3 uCameraPos; + +varying highp vec3 vNormal; +varying highp vec2 vTextureCoord; + +vec4 pack (float depth) { + // 使用rgba 4字节共32位来存储z值,1个字节精度为1/256 + const vec4 bitShift = vec4(1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0); + const vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0); + // gl_FragCoord:片元的坐标,fract():返回数值的小数部分 + vec4 rgbaDepth = fract(depth * bitShift); //计算每个点的z值 + rgbaDepth -= rgbaDepth.gbaa * bitMask; // Cut off the value which do not fit in 8 bits + return rgbaDepth; +} + +void main(){ + + //gl_FragColor = vec4( 1.0, 0.0, 0.0, gl_FragCoord.z); + gl_FragColor = pack(gl_FragCoord.z); +} \ No newline at end of file diff --git a/hw1/src/shaders/shadowShader/shadowVertex.glsl b/hw1/src/shaders/shadowShader/shadowVertex.glsl new file mode 100644 index 0000000..1f95626 --- /dev/null +++ b/hw1/src/shaders/shadowShader/shadowVertex.glsl @@ -0,0 +1,19 @@ +attribute vec3 aVertexPosition; +attribute vec3 aNormalPosition; +attribute vec2 aTextureCoord; + +uniform mat4 uModelMatrix; +uniform mat4 uViewMatrix; +uniform mat4 uProjectionMatrix; +uniform mat4 uLightMVP; + +varying highp vec3 vNormal; +varying highp vec2 vTextureCoord; + +void main(void) { + + vNormal = aNormalPosition; + vTextureCoord = aTextureCoord; + + gl_Position = uLightMVP * vec4(aVertexPosition, 1.0); +} \ No newline at end of file diff --git a/hw1/src/textures/FBO.js b/hw1/src/textures/FBO.js new file mode 100644 index 0000000..8ca2a20 --- /dev/null +++ b/hw1/src/textures/FBO.js @@ -0,0 +1,63 @@ + class FBO{ + constructor(gl){ + var framebuffer, texture, depthBuffer; + + //定义错误函数 + function error() { + if(framebuffer) gl.deleteFramebuffer(framebuffer); + if(texture) gl.deleteFramebuffer(texture); + if(depthBuffer) gl.deleteFramebuffer(depthBuffer); + return null; + } + + //创建帧缓冲区对象 + framebuffer = gl.createFramebuffer(); + if(!framebuffer){ + console.log("无法创建帧缓冲区对象"); + return error(); + } + + //创建纹理对象并设置其尺寸和参数 + texture = gl.createTexture(); + if(!texture){ + console.log("无法创建纹理对象"); + return error(); + } + + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, resolution, resolution, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + framebuffer.texture = texture;//将纹理对象存入framebuffer + + //创建渲染缓冲区对象并设置其尺寸和参数 + depthBuffer = gl.createRenderbuffer(); + if(!depthBuffer){ + console.log("无法创建渲染缓冲区对象"); + return error(); + } + + gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, resolution, resolution); + + //将纹理和渲染缓冲区对象关联到帧缓冲区对象上 + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER,depthBuffer); + + //检查帧缓冲区对象是否被正确设置 + var e = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if(gl.FRAMEBUFFER_COMPLETE !== e){ + console.log("渲染缓冲区设置错误"+e.toString()); + return error(); + } + + //取消当前的focus对象 + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + + return framebuffer; + } + } \ No newline at end of file diff --git a/hw1/src/textures/Texture.js b/hw1/src/textures/Texture.js new file mode 100644 index 0000000..0a8aca5 --- /dev/null +++ b/hw1/src/textures/Texture.js @@ -0,0 +1,85 @@ +class Texture { + constructor() {} + CreateImageTexture(gl, image) { + this.texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + // Because images have to be download over the internet + // they might take a moment until they are ready. + // Until then put a single pixel in the texture so we can + // use it immediately. When the image has finished downloading + // we'll update the texture with the contents of the image. + const level = 0; + const internalFormat = gl.RGBA; + const width = 1; + const height = 1; + const border = 0; + const srcFormat = gl.RGBA; + const srcType = gl.UNSIGNED_BYTE; + const pixel = new Uint8Array([0, 0, 255, 255]); // opaque blue + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + width, height, border, srcFormat, srcType, + pixel); + + gl.bindTexture(gl.TEXTURE_2D, this.texture); + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + srcFormat, srcType, image); + + gl.bindTexture(gl.TEXTURE_2D, null); + + this.CreateMipmap(gl, image.width, image.height); + } + + CreateConstantTexture(gl, buffer) { + this.texture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + // Because images have to be download over the internet + // they might take a moment until they are ready. + // Until then put a single pixel in the texture so we can + // use it immediately. When the image has finished downloading + // we'll update the texture with the contents of the image. + const level = 0; + const internalFormat = gl.RGB; + const width = 1; + const height = 1; + const border = 0; + const srcFormat = gl.RGB; + const srcType = gl.UNSIGNED_BYTE; + const pixel = new Uint8Array([buffer[0] * 255, buffer[1] * 255, buffer[2] * 255, 255]); // opaque blue + gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, + width, height, border, srcFormat, srcType, + pixel); + + gl.bindTexture(gl.TEXTURE_2D, null); + + this.CreateMipmap(gl, width, height); + } + + CreateMipmap(gl, width, height) { + gl.bindTexture(gl.TEXTURE_2D, this.texture); + + // WebGL1 has different requirements for power of 2 images + // vs non power of 2 images so check if the image is a + // power of 2 in both dimensions. + if (isPowerOf2(width) && isPowerOf2(height)) { + // Yes, it's a power of 2. Generate mips. + gl.generateMipmap(gl.TEXTURE_2D); + } else { + // No, it's not a power of 2. Turn of mips and set + // wrapping to clamp to edge + //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEATE); + //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEATE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + } + gl.bindTexture(gl.TEXTURE_2D, null); + } +} + +function isPowerOf2(value) { + return (value & (value - 1)) == 0; +} diff --git a/hw5/CMakeLists.txt b/hw5/CMakeLists.txt new file mode 100644 index 0000000..f546b68 --- /dev/null +++ b/hw5/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required (VERSION 3.2) +project (Denoise) + +set (CMAKE_CXX_STANDARD 17) + +######################################## + +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/ext +) + +file(GLOB SOURCE_FILE + ${CMAKE_SOURCE_DIR}/src/*.h + ${CMAKE_SOURCE_DIR}/src/*.cpp + ${CMAKE_SOURCE_DIR}/src/util/*.h + ${CMAKE_SOURCE_DIR}/src/util/*.cpp + ${CMAKE_SOURCE_DIR}/src/ext/*/*.h +) + +# Sort the file into different folders +foreach(_source_file IN ITEMS ${SOURCE_FILE}) + get_filename_component(_source_path "${_source_file}" PATH) + string(REPLACE "${CMAKE_SOURCE_DIR}" "" _group_path "${_source_path}") + string(REPLACE "/" "\\" _group_path "${_group_path}") + source_group("${_group_path}" FILES "${_source_file}") +endforeach() + +######################################## + +# OpenMP +FIND_PACKAGE(OpenMP) +if(OPENMP_FOUND) + message("OPENMP FOUND") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") +endif() + +######################################## + +add_executable(Denoise ${SOURCE_FILE}) \ No newline at end of file diff --git a/hw5/LICENSE b/hw5/LICENSE new file mode 100644 index 0000000..129842c --- /dev/null +++ b/hw5/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 GAMES202 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/hw5/README.md b/hw5/README.md new file mode 100644 index 0000000..d995632 --- /dev/null +++ b/hw5/README.md @@ -0,0 +1 @@ +# Denoise \ No newline at end of file diff --git a/hw5/assignment.pdf b/hw5/assignment.pdf new file mode 100644 index 0000000..18bc734 Binary files /dev/null and b/hw5/assignment.pdf differ diff --git a/hw5/build.bat b/hw5/build.bat new file mode 100644 index 0000000..018dc44 --- /dev/null +++ b/hw5/build.bat @@ -0,0 +1,5 @@ +rd /s /q build +mkdir build +cd build +cmake .. +cd .. \ No newline at end of file diff --git a/hw5/build.sh b/hw5/build.sh new file mode 100644 index 0000000..9a6e400 --- /dev/null +++ b/hw5/build.sh @@ -0,0 +1,6 @@ +rm -rf build +mkdir build +cd build +cmake .. +make -j8 +cd .. \ No newline at end of file diff --git a/hw5/image2video.bat b/hw5/image2video.bat new file mode 100644 index 0000000..b287093 --- /dev/null +++ b/hw5/image2video.bat @@ -0,0 +1,4 @@ +ffmpeg -y -gamma 2.2 -r 20 -i examples\pink-room\input\beauty_%%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 pinkroom-input.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i examples\pink-room\output\result_%%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 pinkroom-result.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i examples\box\input\beauty_%%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 box-input.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i examples\box\output\result_%%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 box-result.mp4 diff --git a/hw5/image2video.sh b/hw5/image2video.sh new file mode 100644 index 0000000..177c64c --- /dev/null +++ b/hw5/image2video.sh @@ -0,0 +1,4 @@ +ffmpeg -y -gamma 2.2 -r 20 -i ./examples/pink-room/input/beauty_%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 pinkroom-input.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i ./examples/pink-room/output/result_%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 pinkroom-result.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i ./examples/box/input/beauty_%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 box-input.mp4 +ffmpeg -y -gamma 2.2 -r 20 -i ./examples/box/output/result_%d.exr -vcodec libx264 -pix_fmt yuv420p -preset slow -crf 18 box-result.mp4 diff --git a/hw5/src/.clang-format b/hw5/src/.clang-format new file mode 100644 index 0000000..9ded813 --- /dev/null +++ b/hw5/src/.clang-format @@ -0,0 +1,8 @@ +BasedOnStyle: LLVM +AccessModifierOffset: -2 +IndentCaseLabels: false +PointerBindsToType: false +IndentWidth: 4 +AlwaysBreakTemplateDeclarations: true +ColumnLimit: 90 + diff --git a/hw5/src/denoiser.cpp b/hw5/src/denoiser.cpp new file mode 100644 index 0000000..d918a6f --- /dev/null +++ b/hw5/src/denoiser.cpp @@ -0,0 +1,84 @@ +#include "denoiser.h" + +Denoiser::Denoiser() : m_useTemportal(false) {} + +void Denoiser::Reprojection(const FrameInfo &frameInfo) { + int height = m_accColor.m_height; + int width = m_accColor.m_width; + Matrix4x4 preWorldToScreen = + m_preFrameInfo.m_matrix[m_preFrameInfo.m_matrix.size() - 1]; + Matrix4x4 preWorldToCamera = + m_preFrameInfo.m_matrix[m_preFrameInfo.m_matrix.size() - 2]; +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // TODO: Reproject + m_valid(x, y) = false; + m_misc(x, y) = Float3(0.f); + } + } + std::swap(m_misc, m_accColor); +} + +void Denoiser::TemporalAccumulation(const Buffer2D &curFilteredColor) { + int height = m_accColor.m_height; + int width = m_accColor.m_width; + int kernelRadius = 3; +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // TODO: Temporal clamp + Float3 color = m_accColor(x, y); + // TODO: Exponential moving average + float alpha = 1.0f; + m_misc(x, y) = Lerp(color, curFilteredColor(x, y), alpha); + } + } + std::swap(m_misc, m_accColor); +} + +Buffer2D Denoiser::Filter(const FrameInfo &frameInfo) { + int height = frameInfo.m_beauty.m_height; + int width = frameInfo.m_beauty.m_width; + Buffer2D filteredImage = CreateBuffer2D(width, height); + int kernelRadius = 16; +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + // TODO: Joint bilateral filter + filteredImage(x, y) = frameInfo.m_beauty(x, y); + } + } + return filteredImage; +} + +void Denoiser::Init(const FrameInfo &frameInfo, const Buffer2D &filteredColor) { + m_accColor.Copy(filteredColor); + int height = m_accColor.m_height; + int width = m_accColor.m_width; + m_misc = CreateBuffer2D(width, height); + m_valid = CreateBuffer2D(width, height); +} + +void Denoiser::Maintain(const FrameInfo &frameInfo) { m_preFrameInfo = frameInfo; } + +Buffer2D Denoiser::ProcessFrame(const FrameInfo &frameInfo) { + // Filter current frame + Buffer2D filteredColor; + filteredColor = Filter(frameInfo); + + // Reproject previous frame color to current + if (m_useTemportal) { + Reprojection(frameInfo); + TemporalAccumulation(filteredColor); + } else { + Init(frameInfo, filteredColor); + } + + // Maintain + Maintain(frameInfo); + if (!m_useTemportal) { + m_useTemportal = true; + } + return m_accColor; +} diff --git a/hw5/src/denoiser.h b/hw5/src/denoiser.h new file mode 100644 index 0000000..1e36cfb --- /dev/null +++ b/hw5/src/denoiser.h @@ -0,0 +1,47 @@ +#pragma once + +#define NOMINMAX +#include + +#include "filesystem/path.h" + +#include "util/image.h" +#include "util/mathutil.h" + +struct FrameInfo { + public: + Buffer2D m_beauty; + Buffer2D m_depth; + Buffer2D m_normal; + Buffer2D m_position; + Buffer2D m_id; + std::vector m_matrix; +}; + +class Denoiser { + public: + Denoiser(); + + void Init(const FrameInfo &frameInfo, const Buffer2D &filteredColor); + void Maintain(const FrameInfo &frameInfo); + + void Reprojection(const FrameInfo &frameInfo); + void TemporalAccumulation(const Buffer2D &curFilteredColor); + Buffer2D Filter(const FrameInfo &frameInfo); + + Buffer2D ProcessFrame(const FrameInfo &frameInfo); + + public: + FrameInfo m_preFrameInfo; + Buffer2D m_accColor; + Buffer2D m_misc; + Buffer2D m_valid; + bool m_useTemportal; + + float m_alpha = 0.2f; + float m_sigmaPlane = 0.1f; + float m_sigmaColor = 0.6f; + float m_sigmaNormal = 0.1f; + float m_sigmaCoord = 32.0f; + float m_colorBoxK = 1.0f; +}; \ No newline at end of file diff --git a/hw5/src/ext/filesystem/fwd.h b/hw5/src/ext/filesystem/fwd.h new file mode 100644 index 0000000..3552199 --- /dev/null +++ b/hw5/src/ext/filesystem/fwd.h @@ -0,0 +1,24 @@ +/* + fwd.h -- Forward declarations for path.h and resolver.h + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#if !defined(NAMESPACE_BEGIN) +#define NAMESPACE_BEGIN(name) namespace name { +#endif +#if !defined(NAMESPACE_END) +#define NAMESPACE_END(name) } +#endif + +NAMESPACE_BEGIN(filesystem) + +class path; +class resolver; + +NAMESPACE_END(filesystem) diff --git a/hw5/src/ext/filesystem/path.h b/hw5/src/ext/filesystem/path.h new file mode 100644 index 0000000..14c9b55 --- /dev/null +++ b/hw5/src/ext/filesystem/path.h @@ -0,0 +1,438 @@ +/* + path.h -- A simple class for manipulating paths on Linux/Windows/Mac OS + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "fwd.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +# include +# include +#else +# include +#endif +#include + +#if defined(__linux) +# include +#endif + +#if defined(__APPLE__) +# include +#endif + +NAMESPACE_BEGIN(filesystem) + +/** + * \brief Simple class for manipulating paths on Linux/Windows/Mac OS + * + * This class is just a temporary workaround to avoid the heavy boost + * dependency until boost::filesystem is integrated into the standard template + * library at some point in the future. + */ +class path { +public: + enum path_type { + windows_path = 0, + posix_path = 1, +#if defined(_WIN32) + native_path = windows_path +#else + native_path = posix_path +#endif + }; + + path() : m_type(native_path), m_absolute(false), m_smb(false) { } + + path(const path &path) + : m_type(path.m_type), m_path(path.m_path), m_absolute(path.m_absolute), m_smb(path.m_smb) {} + + path(path &&path) + : m_type(path.m_type), m_path(std::move(path.m_path)), + m_absolute(path.m_absolute), m_smb(path.m_smb) {} + + path(const char *string) { set(string); } + + path(const std::string &string) { set(string); } + +#if defined(_WIN32) + path(const std::wstring &wstring) { set(wstring); } + path(const wchar_t *wstring) { set(wstring); } +#endif + + size_t length() const { return m_path.size(); } + + bool empty() const { return m_path.empty(); } + + bool is_absolute() const { return m_absolute; } + + path make_absolute() const { +#if !defined(_WIN32) + char temp[PATH_MAX]; + if (realpath(str().c_str(), temp) == NULL) + throw std::runtime_error("Internal error in realpath(): " + std::string(strerror(errno))); + return path(temp); +#else + std::wstring value = wstr(), out(MAX_PATH_WINDOWS, '\0'); + DWORD length = GetFullPathNameW(value.c_str(), MAX_PATH_WINDOWS, &out[0], NULL); + if (length == 0) + throw std::runtime_error("Internal error in realpath(): " + std::to_string(GetLastError())); + return path(out.substr(0, length)); +#endif + } + + bool exists() const { +#if defined(_WIN32) + return GetFileAttributesW(wstr().c_str()) != INVALID_FILE_ATTRIBUTES; +#else + struct stat sb; + return stat(str().c_str(), &sb) == 0; +#endif + } + + size_t file_size() const { +#if defined(_WIN32) + struct _stati64 sb; + if (_wstati64(wstr().c_str(), &sb) != 0) + throw std::runtime_error("path::file_size(): cannot stat file \"" + str() + "\"!"); +#else + struct stat sb; + if (stat(str().c_str(), &sb) != 0) + throw std::runtime_error("path::file_size(): cannot stat file \"" + str() + "\"!"); +#endif + return (size_t) sb.st_size; + } + + bool is_directory() const { +#if defined(_WIN32) + DWORD result = GetFileAttributesW(wstr().c_str()); + if (result == INVALID_FILE_ATTRIBUTES) + return false; + return (result & FILE_ATTRIBUTE_DIRECTORY) != 0; +#else + struct stat sb; + if (stat(str().c_str(), &sb)) + return false; + return S_ISDIR(sb.st_mode); +#endif + } + + bool is_file() const { +#if defined(_WIN32) + DWORD attr = GetFileAttributesW(wstr().c_str()); + return (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0); +#else + struct stat sb; + if (stat(str().c_str(), &sb)) + return false; + return S_ISREG(sb.st_mode); +#endif + } + + std::string extension() const { + const std::string &name = filename(); + size_t pos = name.find_last_of("."); + if (pos == std::string::npos) + return ""; + return name.substr(pos+1); + } + + std::string filename() const { + if (empty()) + return ""; + const std::string &last = m_path[m_path.size()-1]; + return last; + } + + path parent_path() const { + path result; + result.m_absolute = m_absolute; + result.m_smb = m_smb; + + if (m_path.empty()) { + if (!m_absolute) + result.m_path.push_back(".."); + } else { + size_t until = m_path.size() - 1; + for (size_t i = 0; i < until; ++i) + result.m_path.push_back(m_path[i]); + } + return result; + } + + path operator/(const path &other) const { + if (other.m_absolute) + throw std::runtime_error("path::operator/(): expected a relative path!"); + if (m_type != other.m_type) + throw std::runtime_error("path::operator/(): expected a path of the same type!"); + + path result(*this); + + for (size_t i=0; iMAX_PATH are + // not supported at all in Windows. + if (length > MAX_PATH_WINDOWS_LEGACY) { + if (m_smb) + oss << "\\\\?\\UNC\\"; + else + oss << "\\\\?\\"; + } else if (m_smb) + oss << "\\\\"; + } + } + + for (size_t i=0; iMAX_PATH characters long, so we remove it + // for convenience and add it back (if necessary) in str()/wstr(). + static const std::string LONG_PATH_PREFIX = "\\\\?\\"; + if (tmp.length() >= LONG_PATH_PREFIX.length() + && std::mismatch(std::begin(LONG_PATH_PREFIX), std::end(LONG_PATH_PREFIX), std::begin(tmp)).first == std::end(LONG_PATH_PREFIX)) { + tmp.erase(0, LONG_PATH_PREFIX.length()); + } + + // Special-case handling of absolute SMB paths, which start with the prefix "\\". + if (tmp.length() >= 2 && tmp[0] == '\\' && tmp[1] == '\\') { + m_path = {}; + tmp.erase(0, 2); + + // Interestingly, there is a special-special case where relative paths may be specified as beginning with a "\\" + // when a non-SMB file with a more-than-260-characters-long absolute _local_ path is double-clicked. This seems to + // only happen with single-segment relative paths, so we can check for this condition by making sure no further + // path separators are present. + if (tmp.find_first_of("/\\") != std::string::npos) + m_absolute = m_smb = true; + else + m_absolute = m_smb = false; + + // Special-case handling of absolute SMB paths, which start with the prefix "UNC\" + } else if (tmp.length() >= 4 && tmp[0] == 'U' && tmp[1] == 'N' && tmp[2] == 'C' && tmp[3] == '\\') { + m_path = {}; + tmp.erase(0, 4); + m_absolute = true; + m_smb = true; + // Special-case handling of absolute local paths, which start with the drive letter and a colon "X:\" + } else if (tmp.length() >= 3 && std::isalpha(tmp[0]) && tmp[1] == ':' && (tmp[2] == '\\' || tmp[2] == '/')) { + m_path = {tmp.substr(0, 2)}; + tmp.erase(0, 3); + m_absolute = true; + m_smb = false; + // Relative path + } else { + m_path = {}; + m_absolute = false; + m_smb = false; + } + + std::vector tokenized = tokenize(tmp, "/\\"); + m_path.insert(std::end(m_path), std::begin(tokenized), std::end(tokenized)); + } else { + m_path = tokenize(str, "/"); + m_absolute = !str.empty() && str[0] == '/'; + } + } + + path &operator=(const path &path) { + m_type = path.m_type; + m_path = path.m_path; + m_absolute = path.m_absolute; + m_smb = path.m_smb; + return *this; + } + + path &operator=(path &&path) { + if (this != &path) { + m_type = path.m_type; + m_path = std::move(path.m_path); + m_absolute = path.m_absolute; + m_smb = path.m_smb; + } + return *this; + } + + friend std::ostream &operator<<(std::ostream &os, const path &path) { + os << path.str(); + return os; + } + + bool remove_file() { +#if !defined(_WIN32) + return std::remove(str().c_str()) == 0; +#else + return DeleteFileW(wstr().c_str()) != 0; +#endif + } + + bool resize_file(size_t target_length) { +#if !defined(_WIN32) + return ::truncate(str().c_str(), (off_t) target_length) == 0; +#else + HANDLE handle = CreateFileW(wstr().c_str(), GENERIC_WRITE, 0, nullptr, 0, FILE_ATTRIBUTE_NORMAL, nullptr); + if (handle == INVALID_HANDLE_VALUE) + return false; + LARGE_INTEGER size; + size.QuadPart = (LONGLONG) target_length; + if (SetFilePointerEx(handle, size, NULL, FILE_BEGIN) == 0) { + CloseHandle(handle); + return false; + } + if (SetEndOfFile(handle) == 0) { + CloseHandle(handle); + return false; + } + CloseHandle(handle); + return true; +#endif + } + + static path getcwd() { +#if !defined(_WIN32) + char temp[PATH_MAX]; + if (::getcwd(temp, PATH_MAX) == NULL) + throw std::runtime_error("Internal error in getcwd(): " + std::string(strerror(errno))); + return path(temp); +#else + std::wstring temp(MAX_PATH_WINDOWS, '\0'); + if (!_wgetcwd(&temp[0], MAX_PATH_WINDOWS)) + throw std::runtime_error("Internal error in getcwd(): " + std::to_string(GetLastError())); + return path(temp.c_str()); +#endif + } + +#if defined(_WIN32) + std::wstring wstr(path_type type = native_path) const { + std::string temp = str(type); + int size = MultiByteToWideChar(CP_UTF8, 0, &temp[0], (int)temp.size(), NULL, 0); + std::wstring result(size, 0); + MultiByteToWideChar(CP_UTF8, 0, &temp[0], (int)temp.size(), &result[0], size); + return result; + } + + + void set(const std::wstring &wstring, path_type type = native_path) { + std::string string; + if (!wstring.empty()) { + int size = WideCharToMultiByte(CP_UTF8, 0, &wstring[0], (int)wstring.size(), + NULL, 0, NULL, NULL); + string.resize(size, 0); + WideCharToMultiByte(CP_UTF8, 0, &wstring[0], (int)wstring.size(), + &string[0], size, NULL, NULL); + } + set(string, type); + } + + path &operator=(const std::wstring &str) { set(str); return *this; } +#endif + + bool operator==(const path &p) const { return p.m_path == m_path; } + bool operator!=(const path &p) const { return p.m_path != m_path; } + +protected: + static std::vector tokenize(const std::string &string, const std::string &delim) { + std::string::size_type lastPos = 0, pos = string.find_first_of(delim, lastPos); + std::vector tokens; + + while (lastPos != std::string::npos) { + if (pos != lastPos) + tokens.push_back(string.substr(lastPos, pos - lastPos)); + lastPos = pos; + if (lastPos == std::string::npos || lastPos + 1 == string.length()) + break; + pos = string.find_first_of(delim, ++lastPos); + } + + return tokens; + } + +protected: +#if defined(_WIN32) + static const size_t MAX_PATH_WINDOWS = 32767; +#endif + static const size_t MAX_PATH_WINDOWS_LEGACY = 260; + path_type m_type; + std::vector m_path; + bool m_absolute; + bool m_smb; // Unused, except for on Windows +}; + +inline bool create_directory(const path& p) { +#if defined(_WIN32) + return CreateDirectoryW(p.wstr().c_str(), NULL) != 0; +#else + return mkdir(p.str().c_str(), S_IRWXU) == 0; +#endif +} + +inline bool create_directories(const path& p) { +#if defined(_WIN32) + return SHCreateDirectory(nullptr, p.make_absolute().wstr().c_str()) == ERROR_SUCCESS; +#else + if (create_directory(p.str().c_str())) + return true; + + if (p.empty()) + return false; + + if (errno == ENOENT) { + if (create_directory(p.parent_path())) + return mkdir(p.str().c_str(), S_IRWXU) == 0; + else + return false; + } + return false; +#endif +} + +NAMESPACE_END(filesystem) diff --git a/hw5/src/ext/filesystem/resolver.h b/hw5/src/ext/filesystem/resolver.h new file mode 100644 index 0000000..0c75768 --- /dev/null +++ b/hw5/src/ext/filesystem/resolver.h @@ -0,0 +1,72 @@ +/* + resolver.h -- A simple class for cross-platform path resolution + + Copyright (c) 2015 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "path.h" + +NAMESPACE_BEGIN(filesystem) + +/** + * \brief Simple class for resolving paths on Linux/Windows/Mac OS + * + * This convenience class looks for a file or directory given its name + * and a set of search paths. The implementation walks through the + * search paths in order and stops once the file is found. + */ +class resolver { +public: + typedef std::vector::iterator iterator; + typedef std::vector::const_iterator const_iterator; + + resolver() { + m_paths.push_back(path::getcwd()); + } + + size_t size() const { return m_paths.size(); } + + iterator begin() { return m_paths.begin(); } + iterator end() { return m_paths.end(); } + + const_iterator begin() const { return m_paths.begin(); } + const_iterator end() const { return m_paths.end(); } + + void erase(iterator it) { m_paths.erase(it); } + + void prepend(const path &path) { m_paths.insert(m_paths.begin(), path); } + void append(const path &path) { m_paths.push_back(path); } + const path &operator[](size_t index) const { return m_paths[index]; } + path &operator[](size_t index) { return m_paths[index]; } + + path resolve(const path &value) const { + for (const_iterator it = m_paths.begin(); it != m_paths.end(); ++it) { + path combined = *it / value; + if (combined.exists()) + return combined; + } + return value; + } + + friend std::ostream &operator<<(std::ostream &os, const resolver &r) { + os << "resolver[" << std::endl; + for (size_t i = 0; i < r.m_paths.size(); ++i) { + os << " \"" << r.m_paths[i] << "\""; + if (i + 1 < r.m_paths.size()) + os << ","; + os << std::endl; + } + os << "]"; + return os; + } + +private: + std::vector m_paths; +}; + +NAMESPACE_END(filesystem) diff --git a/hw5/src/ext/tinyexr/tinyexr.h b/hw5/src/ext/tinyexr/tinyexr.h new file mode 100644 index 0000000..7b79981 Binary files /dev/null and b/hw5/src/ext/tinyexr/tinyexr.h differ diff --git a/hw5/src/main.cpp b/hw5/src/main.cpp new file mode 100644 index 0000000..2cf6704 --- /dev/null +++ b/hw5/src/main.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include "denoiser.h" +#include "util/image.h" +#include "util/mathutil.h" + +std::vector ReadMatrix(const std::string &filename) { + std::ifstream is; + is.open(filename, std::ios::binary); + CHECK(is.is_open()); + int shapeNum; + is.read(reinterpret_cast(&shapeNum), sizeof(int)); + std::vector matrix(shapeNum + 2); + for (int i = 0; i < shapeNum + 2; i++) { + is.read(reinterpret_cast(&matrix[i]), sizeof(Matrix4x4)); + } + is.close(); + return matrix; +} + +FrameInfo LoadFrameInfo(const filesystem::path &inputDir, const int &idx) { + Buffer2D beauty = + ReadFloat3Image((inputDir / ("beauty_" + std::to_string(idx) + ".exr")).str()); + Buffer2D normal = + ReadFloat3Image((inputDir / ("normal_" + std::to_string(idx) + ".exr")).str()); + Buffer2D position = + ReadFloat3Image((inputDir / ("position_" + std::to_string(idx) + ".exr")).str()); + Buffer2D depth = + ReadFloatImage((inputDir / ("depth_" + std::to_string(idx) + ".exr")).str()); + Buffer2D id = + ReadFloatImage((inputDir / ("ID_" + std::to_string(idx) + ".exr")).str()); + std::vector matrix = + ReadMatrix((inputDir / ("matrix_" + std::to_string(idx) + ".mat")).str()); + + FrameInfo frameInfo = {beauty, depth, normal, position, id, matrix}; + return frameInfo; +} + +void Denoise(const filesystem::path &inputDir, const filesystem::path &outputDir, + const int &frameNum) { + Denoiser denoiser; + for (int i = 0; i < frameNum; i++) { + std::cout << "Frame: " << i << std::endl; + FrameInfo frameInfo = LoadFrameInfo(inputDir, i); + Buffer2D image = denoiser.ProcessFrame(frameInfo); + std::string filename = + (outputDir / ("result_" + std::to_string(i) + ".exr")).str(); + WriteFloat3Image(image, filename); + } +} + +int main() { + // Box + filesystem::path inputDir("examples/box/input"); + filesystem::path outputDir("examples/box/output"); + int frameNum = 20; + + /* + // Pink room + filesystem::path inputDir("examples/pink-room/input"); + filesystem::path outputDir("examples/pink-room/output"); + int frameNum = 80; + */ + + Denoise(inputDir, outputDir, frameNum); + return 0; +} \ No newline at end of file diff --git a/hw5/src/util/buffer.h b/hw5/src/util/buffer.h new file mode 100644 index 0000000..a0199b7 --- /dev/null +++ b/hw5/src/util/buffer.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#include "common.h" + +template +class Buffer { + public: + Buffer(T *buffer, const int &size); + virtual void Copy(const Buffer &buffer); + + int m_size; + std::shared_ptr m_buffer = nullptr; +}; + +template +inline Buffer::Buffer(T *buffer, const int &size) : m_buffer(buffer), m_size(size) {} + +template +inline void Buffer::Copy(const Buffer &buffer) { + if (m_buffer == buffer.m_buffer) { + return; + } + m_size = buffer.m_size; + m_buffer = std::shared_ptr(new T[m_size]); + std::memcpy(m_buffer.get(), buffer.m_buffer.get(), sizeof(T) * m_size); +} + +template +class Buffer2D : public Buffer { + public: + Buffer2D(); + Buffer2D(T *buffer, const int &width, const int &height); + + void Copy(const Buffer2D &buffer); + + T operator()(const int &x, const int &y) const; + T &operator()(const int &x, const int &y); + + int m_width, m_height; +}; + +template +inline Buffer2D::Buffer2D() : Buffer(nullptr, 0), m_width(0), m_height(0) {} + +template +inline Buffer2D::Buffer2D(T *buffer, const int &width, const int &height) + : Buffer(buffer, width * height), m_width(width), m_height(height) {} + +template +inline void Buffer2D::Copy(const Buffer2D &buffer) { + Buffer::Copy(buffer); + m_width = buffer.m_width; + m_height = buffer.m_height; +} + +template +inline T &Buffer2D::operator()(const int &x, const int &y) { + CHECK(0 <= x && x < m_width && 0 <= y && y < m_height); + return this->m_buffer[y * m_width + x]; +} + +template +inline T Buffer2D::operator()(const int &x, const int &y) const { + if (0 <= x && x < m_width && 0 <= y && y < m_height) { + return this->m_buffer[y * m_width + x]; + } else { + return T(0.0); + } +} + +template +inline Buffer2D CreateBuffer2D(const int &width, const int &height) { + T *buffer = new T[width * height]; + return Buffer2D(buffer, width, height); +} \ No newline at end of file diff --git a/hw5/src/util/common.h b/hw5/src/util/common.h new file mode 100644 index 0000000..5abafa7 --- /dev/null +++ b/hw5/src/util/common.h @@ -0,0 +1,17 @@ +#pragma once + +#define NOMINMAX + +#include + +#define LOG(msg) \ + std::cout << "[" << __FILE__ << ", " << __FUNCTION__ << ", " << __LINE__ \ + << "]: " << msg << std::endl; + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + LOG("Runtime Error."); \ + exit(-1); \ + } \ + } while (false) diff --git a/hw5/src/util/image.cpp b/hw5/src/util/image.cpp new file mode 100644 index 0000000..a1743b9 --- /dev/null +++ b/hw5/src/util/image.cpp @@ -0,0 +1,51 @@ +#include "image.h" + +Buffer2D ReadFloatImage(const std::string &filename) { + int width, height; + float *buffer = ReadImage(filename, width, height, 1); + CHECK(buffer != nullptr); + return Buffer2D(buffer, width, height); +} + +Buffer2D ReadFloatImageLayer(const std::string &filename, + const std::string &layername) { + int width, height; + float *buffer = ReadImageLayer(filename, layername, width, height, 1); + CHECK(buffer != nullptr); + return Buffer2D(buffer, width, height); +} + +Buffer2D ReadFloat3Image(const std::string &filename) { + int width, height; + float *_buffer = ReadImage(filename, width, height, 3); + CHECK(_buffer != nullptr); + Float3 *buffer = new Float3[width * height]; + for (int i = 0; i < width * height; i++) { + buffer[i] = Float3(_buffer[i * 3 + 0], _buffer[i * 3 + 1], _buffer[i * 3 + 2]); + } + delete[] _buffer; + return Buffer2D(buffer, width, height); +} + +Buffer2D ReadFloat3ImageLayer(const std::string &filename, + const std::string &layername) { + int width, height; + float *_buffer = ReadImageLayer(filename, layername, width, height, 3); + CHECK(_buffer != nullptr); + Float3 *buffer = new Float3[width * height]; + for (int i = 0; i < width * height; i++) { + buffer[i] = Float3(_buffer[i * 3 + 0], _buffer[i * 3 + 1], _buffer[i * 3 + 2]); + } + delete[] _buffer; + return Buffer2D(buffer, width, height); +} + +void WriteFloatImage(const Buffer2D &imageBuffer, const std::string &filename) { + WriteImage(filename, imageBuffer.m_width, imageBuffer.m_height, 1, + (float *)imageBuffer.m_buffer.get()); +} + +void WriteFloat3Image(const Buffer2D &imageBuffer, const std::string &filename) { + WriteImage(filename, imageBuffer.m_width, imageBuffer.m_height, 3, + (float *)imageBuffer.m_buffer.get()); +} diff --git a/hw5/src/util/image.h b/hw5/src/util/image.h new file mode 100644 index 0000000..722bc4f --- /dev/null +++ b/hw5/src/util/image.h @@ -0,0 +1,14 @@ +#pragma once + +#include "buffer.h" +#include "imageutil.h" +#include "mathutil.h" + +Buffer2D ReadFloatImage(const std::string &filename); +Buffer2D ReadFloatImageLayer(const std::string &filename, + const std::string &layername); +Buffer2D ReadFloat3Image(const std::string &filename); +Buffer2D ReadFloat3ImageLayer(const std::string &filename, + const std::string &layername); +void WriteFloatImage(const Buffer2D &imageBuffer, const std::string &filename); +void WriteFloat3Image(const Buffer2D &imageBuffer, const std::string &filename); \ No newline at end of file diff --git a/hw5/src/util/imageutil.cpp b/hw5/src/util/imageutil.cpp new file mode 100644 index 0000000..409af85 --- /dev/null +++ b/hw5/src/util/imageutil.cpp @@ -0,0 +1,148 @@ +#include "imageutil.h" +#include "common.h" + +#define TINYEXR_IMPLEMENTATION +#include "tinyexr/tinyexr.h" + +std::string GetExtension(const std::string &s) { + size_t pos = s.rfind('.'); + CHECK(pos != std::string::npos); + std::string ext = s.substr(pos + 1); + return ext; +} + +float *ReadImage(const std::string &filename, int &width, int &height, + const int &channel) { + CHECK(channel == 1 || channel == 3); + std::string ext = GetExtension(filename); + CHECK(ext == "exr"); + + float *out; // width * height * RGBA + const char *err = nullptr; + int ret = LoadEXR(&out, &width, &height, filename.c_str(), &err); + float *buffer = new float[width * height * channel]; + + if (ret != TINYEXR_SUCCESS) { + if (err) { + fprintf(stderr, "ERR : %s\n", err); + FreeEXRErrorMessage(err); // release memory of error message. + delete[] buffer; + buffer = nullptr; + } + } else { + for (int i = 0; i < width * height; i++) { + for (int j = 0; j < channel; j++) { + buffer[i * channel + j] = out[i * 4 + j]; + } + } + delete[] out; // release memory of image data + } + return buffer; +} + +float *ReadImageLayer(const std::string &filename, const std::string &layername, + int &width, int &height, const int &channel) { + CHECK(channel == 1 || channel == 3); + std::string ext = GetExtension(filename); + CHECK(ext == "exr"); + + float *out; // width * height * RGBA + const char *err = nullptr; + int ret = LoadEXRWithLayer(&out, &width, &height, filename.c_str(), layername.c_str(), + &err); + float *buffer = new float[width * height * channel]; + + if (ret != TINYEXR_SUCCESS) { + if (err) { + fprintf(stderr, "ERR : %s\n", err); + FreeEXRErrorMessage(err); // release memory of error message. + delete[] buffer; + buffer = nullptr; + } + } else { + for (int i = 0; i < width * height; i++) { + for (int j = 0; j < channel; j++) { + buffer[i * channel + j] = out[i * 4 + j]; + } + } + delete[] out; // release memory of image data + } + return buffer; +} + +bool WriteImage(const std::string &filename, const int &width, const int &height, + const int &channel, const float *buffer) { + CHECK(channel == 1 || channel == 3); + EXRHeader header; + InitEXRHeader(&header); + + EXRImage image; + InitEXRImage(&image); + + image.num_channels = channel; + std::vector images[3]; + for (int i = 0; i < channel; i++) { + images[i].resize(width * height); + } + + // Split RGBRGBRGB... into R, G and B layer + for (int i = 0; i < width * height; i++) { + for (int j = 0; j < channel; j++) { + images[j][i] = buffer[channel * i + j]; + } + } + + float *image_ptr[3]; + if (channel == 3) { + image_ptr[0] = &(images[2].at(0)); // B + image_ptr[1] = &(images[1].at(0)); // G + image_ptr[2] = &(images[0].at(0)); // R + } else if (channel == 1) { + image_ptr[0] = &(images[0].at(0)); // Y + image_ptr[1] = nullptr; + image_ptr[2] = nullptr; + } + + image.images = (unsigned char **)image_ptr; + image.width = width; + image.height = height; + + header.num_channels = channel; + header.channels = + (EXRChannelInfo *)malloc(sizeof(EXRChannelInfo) * header.num_channels); + if (header.num_channels == 3) { + // Must be (A)BGR order, since most of EXR viewers expect this + // channel order. + strncpy(header.channels[0].name, "B", 255); + header.channels[0].name[strlen("B")] = '\0'; + strncpy(header.channels[1].name, "G", 255); + header.channels[1].name[strlen("G")] = '\0'; + strncpy(header.channels[2].name, "R", 255); + header.channels[2].name[strlen("R")] = '\0'; + } else if (header.num_channels == 1) { + strncpy(header.channels[0].name, "Y", 255); + header.channels[0].name[strlen("Y")] = '\0'; + } + + header.pixel_types = (int *)malloc(sizeof(int) * header.num_channels); + header.requested_pixel_types = (int *)malloc(sizeof(int) * header.num_channels); + for (int i = 0; i < header.num_channels; i++) { + header.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; // pixel type of input image + header.requested_pixel_types[i] = + TINYEXR_PIXELTYPE_HALF; // pixel type of output image to be + // stored in .EXR + } + + const char *err = nullptr; + int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err); + if (ret != TINYEXR_SUCCESS) { + fprintf(stderr, "Save EXR err: %s\n", err); + FreeEXRErrorMessage(err); // free's buffer for an error message + return ret; + } + + free(header.channels); + free(header.pixel_types); + free(header.requested_pixel_types); + return true; +} diff --git a/hw5/src/util/imageutil.h b/hw5/src/util/imageutil.h new file mode 100644 index 0000000..e8cea8e --- /dev/null +++ b/hw5/src/util/imageutil.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +float *ReadImage(const std::string &filename, int &width, int &height, + const int &channel); + +float *ReadImageLayer(const std::string &filename, const std::string &layername, + int &width, int &height, const int &channel); + +bool WriteImage(const std::string &filename, const int &width, const int &height, + const int &channel, const float *buffer); \ No newline at end of file diff --git a/hw5/src/util/mathutil.cpp b/hw5/src/util/mathutil.cpp new file mode 100644 index 0000000..15c1faf --- /dev/null +++ b/hw5/src/util/mathutil.cpp @@ -0,0 +1,133 @@ +#include "mathutil.h" + +Matrix4x4 Inverse(const Matrix4x4 &mat) { + float inv[4][4]; + inv[0][0] = +mat.m[1][1] * mat.m[2][2] * mat.m[3][3] + + mat.m[1][2] * mat.m[2][3] * mat.m[3][1] + + mat.m[1][3] * mat.m[2][1] * mat.m[3][2] - + mat.m[1][1] * mat.m[2][3] * mat.m[3][2] - + mat.m[1][2] * mat.m[2][1] * mat.m[3][3] - + mat.m[1][3] * mat.m[2][2] * mat.m[3][1]; + inv[1][0] = -mat.m[1][0] * mat.m[2][2] * mat.m[3][3] - + mat.m[1][2] * mat.m[2][3] * mat.m[3][0] - + mat.m[1][3] * mat.m[2][0] * mat.m[3][2] + + mat.m[1][0] * mat.m[2][3] * mat.m[3][2] + + mat.m[1][2] * mat.m[2][0] * mat.m[3][3] + + mat.m[1][3] * mat.m[2][2] * mat.m[3][0]; + inv[2][0] = +mat.m[1][0] * mat.m[2][1] * mat.m[3][3] + + mat.m[1][1] * mat.m[2][3] * mat.m[3][0] + + mat.m[1][3] * mat.m[2][0] * mat.m[3][1] - + mat.m[1][0] * mat.m[2][3] * mat.m[3][1] - + mat.m[1][1] * mat.m[2][0] * mat.m[3][3] - + mat.m[1][3] * mat.m[2][1] * mat.m[3][0]; + inv[3][0] = -mat.m[1][0] * mat.m[2][1] * mat.m[3][2] - + mat.m[1][1] * mat.m[2][2] * mat.m[3][0] - + mat.m[1][2] * mat.m[2][0] * mat.m[3][1] + + mat.m[1][0] * mat.m[2][2] * mat.m[3][1] + + mat.m[1][1] * mat.m[2][0] * mat.m[3][2] + + mat.m[1][2] * mat.m[2][1] * mat.m[3][0]; + inv[0][1] = -mat.m[0][1] * mat.m[2][2] * mat.m[3][3] - + mat.m[0][2] * mat.m[2][3] * mat.m[3][1] - + mat.m[0][3] * mat.m[2][1] * mat.m[3][2] + + mat.m[0][1] * mat.m[2][3] * mat.m[3][2] + + mat.m[0][2] * mat.m[2][1] * mat.m[3][3] + + mat.m[0][3] * mat.m[2][2] * mat.m[3][1]; + inv[1][1] = +mat.m[0][0] * mat.m[2][2] * mat.m[3][3] + + mat.m[0][2] * mat.m[2][3] * mat.m[3][0] + + mat.m[0][3] * mat.m[2][0] * mat.m[3][2] - + mat.m[0][0] * mat.m[2][3] * mat.m[3][2] - + mat.m[0][2] * mat.m[2][0] * mat.m[3][3] - + mat.m[0][3] * mat.m[2][2] * mat.m[3][0]; + inv[2][1] = -mat.m[0][0] * mat.m[2][1] * mat.m[3][3] - + mat.m[0][1] * mat.m[2][3] * mat.m[3][0] - + mat.m[0][3] * mat.m[2][0] * mat.m[3][1] + + mat.m[0][0] * mat.m[2][3] * mat.m[3][1] + + mat.m[0][1] * mat.m[2][0] * mat.m[3][3] + + mat.m[0][3] * mat.m[2][1] * mat.m[3][0]; + inv[3][1] = +mat.m[0][0] * mat.m[2][1] * mat.m[3][2] + + mat.m[0][1] * mat.m[2][2] * mat.m[3][0] + + mat.m[0][2] * mat.m[2][0] * mat.m[3][1] - + mat.m[0][0] * mat.m[2][2] * mat.m[3][1] - + mat.m[0][1] * mat.m[2][0] * mat.m[3][2] - + mat.m[0][2] * mat.m[2][1] * mat.m[3][0]; + inv[0][2] = +mat.m[0][1] * mat.m[1][2] * mat.m[3][3] + + mat.m[0][2] * mat.m[1][3] * mat.m[3][1] + + mat.m[0][3] * mat.m[1][1] * mat.m[3][2] - + mat.m[0][1] * mat.m[1][3] * mat.m[3][2] - + mat.m[0][2] * mat.m[1][1] * mat.m[3][3] - + mat.m[0][3] * mat.m[1][2] * mat.m[3][1]; + inv[1][2] = -mat.m[0][0] * mat.m[1][2] * mat.m[3][3] - + mat.m[0][2] * mat.m[1][3] * mat.m[3][0] - + mat.m[0][3] * mat.m[1][0] * mat.m[3][2] + + mat.m[0][0] * mat.m[1][3] * mat.m[3][2] + + mat.m[0][2] * mat.m[1][0] * mat.m[3][3] + + mat.m[0][3] * mat.m[1][2] * mat.m[3][0]; + inv[2][2] = +mat.m[0][0] * mat.m[1][1] * mat.m[3][3] + + mat.m[0][1] * mat.m[1][3] * mat.m[3][0] + + mat.m[0][3] * mat.m[1][0] * mat.m[3][1] - + mat.m[0][0] * mat.m[1][3] * mat.m[3][1] - + mat.m[0][1] * mat.m[1][0] * mat.m[3][3] - + mat.m[0][3] * mat.m[1][1] * mat.m[3][0]; + inv[3][2] = -mat.m[0][0] * mat.m[1][1] * mat.m[3][2] - + mat.m[0][1] * mat.m[1][2] * mat.m[3][0] - + mat.m[0][2] * mat.m[1][0] * mat.m[3][1] + + mat.m[0][0] * mat.m[1][2] * mat.m[3][1] + + mat.m[0][1] * mat.m[1][0] * mat.m[3][2] + + mat.m[0][2] * mat.m[1][1] * mat.m[3][0]; + inv[0][3] = -mat.m[0][1] * mat.m[1][2] * mat.m[2][3] - + mat.m[0][2] * mat.m[1][3] * mat.m[2][1] - + mat.m[0][3] * mat.m[1][1] * mat.m[2][2] + + mat.m[0][1] * mat.m[1][3] * mat.m[2][2] + + mat.m[0][2] * mat.m[1][1] * mat.m[2][3] + + mat.m[0][3] * mat.m[1][2] * mat.m[2][1]; + inv[1][3] = +mat.m[0][0] * mat.m[1][2] * mat.m[2][3] + + mat.m[0][2] * mat.m[1][3] * mat.m[2][0] + + mat.m[0][3] * mat.m[1][0] * mat.m[2][2] - + mat.m[0][0] * mat.m[1][3] * mat.m[2][2] - + mat.m[0][2] * mat.m[1][0] * mat.m[2][3] - + mat.m[0][3] * mat.m[1][2] * mat.m[2][0]; + inv[2][3] = -mat.m[0][0] * mat.m[1][1] * mat.m[2][3] - + mat.m[0][1] * mat.m[1][3] * mat.m[2][0] - + mat.m[0][3] * mat.m[1][0] * mat.m[2][1] + + mat.m[0][0] * mat.m[1][3] * mat.m[2][1] + + mat.m[0][1] * mat.m[1][0] * mat.m[2][3] + + mat.m[0][3] * mat.m[1][1] * mat.m[2][0]; + inv[3][3] = +mat.m[0][0] * mat.m[1][1] * mat.m[2][2] + + mat.m[0][1] * mat.m[1][2] * mat.m[2][0] + + mat.m[0][2] * mat.m[1][0] * mat.m[2][1] - + mat.m[0][0] * mat.m[1][2] * mat.m[2][1] - + mat.m[0][1] * mat.m[1][0] * mat.m[2][2] - + mat.m[0][2] * mat.m[1][1] * mat.m[2][0]; + float det = mat.m[0][0] * inv[0][0] + mat.m[0][1] * inv[1][0] + + mat.m[0][2] * inv[2][0] + mat.m[0][3] * inv[3][0]; + return Matrix4x4(inv) / det; +} + +Matrix4x4 Transpose(const Matrix4x4 &mat) { + float m[4][4]; + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + m[i][j] = mat.m[j][i]; + } + } + return Matrix4x4(m); +} + +Float3 Matrix4x4::operator()(const Float3 &p, const Float3::EType &type) const { + Float3 ret; + if (type == Float3::Point) { + float x = m[0][0] * p.x + m[0][1] * p.y + m[0][2] * p.z + m[0][3]; + float y = m[1][0] * p.x + m[1][1] * p.y + m[1][2] * p.z + m[1][3]; + float z = m[2][0] * p.x + m[2][1] * p.y + m[2][2] * p.z + m[2][3]; + float w = m[3][0] * p.x + m[3][1] * p.y + m[3][2] * p.z + m[3][3]; + ret = Float3(x, y, z) / w; + } else if (type == Float3::Vector) { + float x = m[0][0] * p.x + m[0][1] * p.y + m[0][2] * p.z; + float y = m[1][0] * p.x + m[1][1] * p.y + m[1][2] * p.z; + float z = m[2][0] * p.x + m[2][1] * p.y + m[2][2] * p.z; + ret = Float3(x, y, z); + } else { + CHECK(false); + } + return ret; +} diff --git a/hw5/src/util/mathutil.h b/hw5/src/util/mathutil.h new file mode 100644 index 0000000..ac2cdb3 --- /dev/null +++ b/hw5/src/util/mathutil.h @@ -0,0 +1,170 @@ +#pragma once + +#include +#include + +#include "util/common.h" + +inline float Sqr(const float &v) { return v * v; } +inline float SafeSqrt(const float &v) { return std::sqrt(std::max(v, 0.f)); } +inline float SafeAcos(const float &v) { + return std::acos(std::min(std::max(v, 0.f), 1.f)); +} + +class Float3 { + public: + enum EType { Vector, Point }; + Float3(const float &v = 0) : x(v), y(v), z(v) {} + Float3(const float &_x, const float &_y, const float &_z) : x(_x), y(_y), z(_z) {} + Float3 operator+(const Float3 &v) const { return Float3(x + v.x, y + v.y, z + v.z); } + Float3 operator-(const Float3 &v) const { return Float3(x - v.x, y - v.y, z - v.z); } + Float3 &operator+=(const Float3 &v) { + x += v.x; + y += v.y; + z += v.z; + return *this; + } + Float3 operator*(const float &v) const { return Float3(x * v, y * v, z * v); } + Float3 operator*(const Float3 &v) const { return Float3(x * v.x, y * v.y, z * v.z); } + Float3 operator/(const float &v) const { + CHECK(v != 0.0); + float inv = 1.0f / v; + return Float3(x * inv, y * inv, z * inv); + } + Float3 operator/(const Float3 &v) const { + CHECK(v.x != 0.0); + CHECK(v.y != 0.0); + CHECK(v.z != 0.0); + float invX = 1.0f / v.x; + float invY = 1.0f / v.y; + float invZ = 1.0f / v.z; + return Float3(x * invX, y * invY, z * invZ); + } + Float3 &operator/=(const float &v) { + CHECK(v != 0.0); + float inv = 1.0f / v; + x *= inv; + y *= inv; + z *= inv; + return *this; + } + + float x, y, z; +}; + +inline Float3 Min(const Float3 &a, const Float3 &b) { + return Float3(std::min(a.x, b.x), std::min(a.y, b.y), std::min(a.z, b.z)); +} + +inline Float3 Max(const Float3 &a, const Float3 &b) { + return Float3(std::max(a.x, b.x), std::max(a.y, b.y), std::max(a.z, b.z)); +} + +inline float Dot(const Float3 &a, const Float3 &b) { + return a.x * b.x + a.y * b.y + a.z * b.z; +} + +inline float AbsSum(const Float3 &a, const Float3 &b) { + return std::fabs(a.x - b.x) + std::fabs(a.y - b.y) + std::fabs(a.z - b.z); +} + +inline Float3 Abs(const Float3 &a) { + return Float3(std::fabs(a.x), std::fabs(a.y), std::fabs(a.z)); +} +inline Float3 Sqr(const Float3 &a) { return Float3(Sqr(a.x), Sqr(a.y), Sqr(a.z)); } +inline Float3 SafeSqrt(const Float3 &a) { + return Float3(SafeSqrt(a.x), SafeSqrt(a.y), SafeSqrt(a.z)); +} + +// (1 - s) * u + s * v +inline Float3 Lerp(const Float3 &u, const Float3 &v, const float &s) { + return u + (v - u) * s; +} +inline Float3 Clamp(const Float3 &v, const Float3 &l, const Float3 &r) { + return Min(Max(v, l), r); +} + +inline float SqrLength(const Float3 &a) { return Sqr(a.x) + Sqr(a.y) + Sqr(a.z); } +inline float Length(const Float3 &a) { return std::sqrt(Sqr(a.x) + Sqr(a.y) + Sqr(a.z)); } +inline Float3 Normalize(const Float3 &a) { return a / Length(a); } + +inline float SqrDistance(const Float3 &a, const Float3 &b) { return SqrLength(a - b); } +inline float Distance(const Float3 &a, const Float3 &b) { return Length(a - b); } + +inline float Luminance(const Float3 &rgb) { + return Dot(rgb, Float3(0.2126f, 0.7152f, 0.0722f)); +} +inline Float3 RGB2YCoCg(const Float3 &RGB) { + float Co = RGB.x - RGB.z; + float tmp = RGB.z + Co / 2; + float Cg = RGB.y - tmp; + float Y = tmp + Cg / 2; + return Float3(Y, Co, Cg); +} +inline Float3 YCoCg2RGB(const Float3 &YCoCg) { + float tmp = YCoCg.x - YCoCg.z / 2; + float G = YCoCg.z + tmp; + float B = tmp - YCoCg.y / 2; + float R = B + YCoCg.y; + return Float3(R, G, B); +} + +inline std::ostream &operator<<(std::ostream &os, const Float3 &v) { + os << "[ " << v.x << ", " << v.y << ", " << v.z << " ]"; + return os; +} + +class Matrix4x4 { + public: + Matrix4x4() { + memset(m, 0, sizeof(float) * 16); + m[0][0] = m[1][1] = m[2][2] = m[3][3] = 1; + } + Matrix4x4(const float _m[4][4]) { memcpy(m, _m, sizeof(float) * 16); } + Matrix4x4(const float _m[16]) { memcpy(m, _m, sizeof(float) * 16); } + Matrix4x4 operator*(const float &v) const { + Matrix4x4 ret; + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + ret.m[i][j] = m[i][j] * v; + } + } + return ret; + } + Matrix4x4 operator/(const float &v) const { + CHECK(v != 0); + float inv = 1.f / v; + return *this * inv; + } + Matrix4x4 operator*(const Matrix4x4 &mat) const { + Matrix4x4 ret; + for (uint32_t i = 0; i < 4; i++) { + for (uint32_t j = 0; j < 4; j++) { + ret.m[i][j] = 0; + for (uint32_t k = 0; k < 4; k++) { + ret.m[i][j] += m[i][k] * mat.m[k][j]; + } + } + } + return ret; + } + Float3 operator()(const Float3 &p, const Float3::EType &type) const; + + float m[4][4]; + + public: + friend std::ostream &operator<<(std::ostream &os, const Matrix4x4 &mat) { + os << mat.m[0][0] << "\t" << mat.m[0][1] << "\t" << mat.m[0][2] << "\t" + << mat.m[0][3] << "\n" + << mat.m[1][0] << "\t" << mat.m[1][1] << "\t" << mat.m[1][2] << "\t" + << mat.m[1][3] << "\n" + << mat.m[2][0] << "\t" << mat.m[2][1] << "\t" << mat.m[2][2] << "\t" + << mat.m[2][3] << "\n" + << mat.m[3][0] << "\t" << mat.m[3][1] << "\t" << mat.m[3][2] << "\t" + << mat.m[3][3]; + return os; + } + + friend Matrix4x4 Inverse(const Matrix4x4 &mat); + friend Matrix4x4 Transpose(const Matrix4x4 &mat); +}; \ No newline at end of file