Created
September 16, 2019 17:54
-
-
Save frnsys/08baa3ea7172042c2e797254acc3247a to your computer and use it in GitHub Desktop.
3d objects code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @author Rich Tibbett / https://github.com/richtr | |
* @author mrdoob / http://mrdoob.com/ | |
* @author Tony Parisi / http://www.tonyparisi.com/ | |
* @author Takahiro / https://github.com/takahirox | |
* @author Don McCurdy / https://www.donmccurdy.com | |
*/ | |
import * as THREE from 'three'; | |
const GLTFLoader = ( function () { | |
function GLTFLoader( manager ) { | |
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; | |
this.dracoLoader = null; | |
} | |
GLTFLoader.prototype = { | |
constructor: GLTFLoader, | |
crossOrigin: 'anonymous', | |
load: function ( url, onLoad, onProgress, onError ) { | |
var scope = this; | |
var resourcePath; | |
if ( this.resourcePath !== undefined ) { | |
resourcePath = this.resourcePath; | |
} else if ( this.path !== undefined ) { | |
resourcePath = this.path; | |
} else { | |
resourcePath = THREE.LoaderUtils.extractUrlBase( url ); | |
} | |
// Tells the LoadingManager to track an extra item, which resolves after | |
// the model is fully loaded. This means the count of items loaded will | |
// be incorrect, but ensures manager.onLoad() does not fire early. | |
scope.manager.itemStart( url ); | |
var _onError = function ( e ) { | |
if ( onError ) { | |
onError( e ); | |
} else { | |
console.error( e ); | |
} | |
scope.manager.itemError( url ); | |
scope.manager.itemEnd( url ); | |
}; | |
var loader = new THREE.FileLoader( scope.manager ); | |
loader.setPath( this.path ); | |
loader.setResponseType( 'arraybuffer' ); | |
loader.load( url, function ( data ) { | |
try { | |
scope.parse( data, resourcePath, function ( gltf ) { | |
onLoad( gltf ); | |
scope.manager.itemEnd( url ); | |
}, _onError ); | |
} catch ( e ) { | |
_onError( e ); | |
} | |
}, onProgress, _onError ); | |
}, | |
setCrossOrigin: function ( value ) { | |
this.crossOrigin = value; | |
return this; | |
}, | |
setPath: function ( value ) { | |
this.path = value; | |
return this; | |
}, | |
setResourcePath: function ( value ) { | |
this.resourcePath = value; | |
return this; | |
}, | |
setDRACOLoader: function ( dracoLoader ) { | |
this.dracoLoader = dracoLoader; | |
return this; | |
}, | |
parse: function ( data, path, onLoad, onError ) { | |
var content; | |
var extensions = {}; | |
if ( typeof data === 'string' ) { | |
content = data; | |
} else { | |
var magic = THREE.LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); | |
if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { | |
try { | |
extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); | |
} catch ( error ) { | |
if ( onError ) onError( error ); | |
return; | |
} | |
content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; | |
} else { | |
content = THREE.LoaderUtils.decodeText( new Uint8Array( data ) ); | |
} | |
} | |
var json = JSON.parse( content ); | |
if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { | |
if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) ); | |
return; | |
} | |
if ( json.extensionsUsed ) { | |
for ( var i = 0; i < json.extensionsUsed.length; ++ i ) { | |
var extensionName = json.extensionsUsed[ i ]; | |
var extensionsRequired = json.extensionsRequired || []; | |
switch ( extensionName ) { | |
case EXTENSIONS.KHR_LIGHTS_PUNCTUAL: | |
extensions[ extensionName ] = new GLTFLightsExtension( json ); | |
break; | |
case EXTENSIONS.KHR_MATERIALS_UNLIT: | |
extensions[ extensionName ] = new GLTFMaterialsUnlitExtension( json ); | |
break; | |
case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: | |
extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension( json ); | |
break; | |
case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: | |
extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); | |
break; | |
case EXTENSIONS.MSFT_TEXTURE_DDS: | |
extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] = new GLTFTextureDDSExtension(); | |
break; | |
case EXTENSIONS.KHR_TEXTURE_TRANSFORM: | |
extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] = new GLTFTextureTransformExtension( json ); | |
break; | |
default: | |
if ( extensionsRequired.indexOf( extensionName ) >= 0 ) { | |
console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); | |
} | |
} | |
} | |
} | |
var parser = new GLTFParser( json, extensions, { | |
path: path || this.resourcePath || '', | |
crossOrigin: this.crossOrigin, | |
manager: this.manager | |
} ); | |
parser.parse( onLoad, onError ); | |
} | |
}; | |
/* GLTFREGISTRY */ | |
function GLTFRegistry() { | |
var objects = {}; | |
return { | |
get: function ( key ) { | |
return objects[ key ]; | |
}, | |
add: function ( key, object ) { | |
objects[ key ] = object; | |
}, | |
remove: function ( key ) { | |
delete objects[ key ]; | |
}, | |
removeAll: function () { | |
objects = {}; | |
} | |
}; | |
} | |
/*********************************/ | |
/********** EXTENSIONS ***********/ | |
/*********************************/ | |
var EXTENSIONS = { | |
KHR_BINARY_GLTF: 'KHR_binary_glTF', | |
KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', | |
KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', | |
KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', | |
KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', | |
KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', | |
MSFT_TEXTURE_DDS: 'MSFT_texture_dds' | |
}; | |
/** | |
* DDS Texture Extension | |
* | |
* Specification: | |
* https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds | |
* | |
*/ | |
function GLTFTextureDDSExtension() { | |
if ( ! THREE.DDSLoader ) { | |
throw new Error( 'THREE.GLTFLoader: Attempting to load .dds texture without importing THREE.DDSLoader' ); | |
} | |
this.name = EXTENSIONS.MSFT_TEXTURE_DDS; | |
this.ddsLoader = new THREE.DDSLoader(); | |
} | |
/** | |
* Lights Extension | |
* | |
* Specification: PENDING | |
*/ | |
function GLTFLightsExtension( json ) { | |
this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; | |
var extension = ( json.extensions && json.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] ) || {}; | |
this.lightDefs = extension.lights || []; | |
} | |
GLTFLightsExtension.prototype.loadLight = function ( lightIndex ) { | |
var lightDef = this.lightDefs[ lightIndex ]; | |
var lightNode; | |
var color = new THREE.Color( 0xffffff ); | |
if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); | |
var range = lightDef.range !== undefined ? lightDef.range : 0; | |
switch ( lightDef.type ) { | |
case 'directional': | |
lightNode = new THREE.DirectionalLight( color ); | |
lightNode.target.position.set( 0, 0, - 1 ); | |
lightNode.add( lightNode.target ); | |
break; | |
case 'point': | |
lightNode = new THREE.PointLight( color ); | |
lightNode.distance = range; | |
break; | |
case 'spot': | |
lightNode = new THREE.SpotLight( color ); | |
lightNode.distance = range; | |
// Handle spotlight properties. | |
lightDef.spot = lightDef.spot || {}; | |
lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; | |
lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; | |
lightNode.angle = lightDef.spot.outerConeAngle; | |
lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; | |
lightNode.target.position.set( 0, 0, - 1 ); | |
lightNode.add( lightNode.target ); | |
break; | |
default: | |
throw new Error( 'THREE.GLTFLoader: Unexpected light type, "' + lightDef.type + '".' ); | |
} | |
// Some lights (e.g. spot) default to a position other than the origin. Reset the position | |
// here, because node-level parsing will only override position if explicitly specified. | |
lightNode.position.set( 0, 0, 0 ); | |
lightNode.decay = 2; | |
if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; | |
lightNode.name = lightDef.name || ( 'light_' + lightIndex ); | |
return Promise.resolve( lightNode ); | |
}; | |
/** | |
* Unlit Materials Extension (pending) | |
* | |
* PR: https://github.com/KhronosGroup/glTF/pull/1163 | |
*/ | |
function GLTFMaterialsUnlitExtension() { | |
this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; | |
} | |
GLTFMaterialsUnlitExtension.prototype.getMaterialType = function () { | |
return THREE.MeshBasicMaterial; | |
}; | |
GLTFMaterialsUnlitExtension.prototype.extendParams = function ( materialParams, materialDef, parser ) { | |
var pending = []; | |
materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); | |
materialParams.opacity = 1.0; | |
var metallicRoughness = materialDef.pbrMetallicRoughness; | |
if ( metallicRoughness ) { | |
if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { | |
var array = metallicRoughness.baseColorFactor; | |
materialParams.color.fromArray( array ); | |
materialParams.opacity = array[ 3 ]; | |
} | |
if ( metallicRoughness.baseColorTexture !== undefined ) { | |
pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); | |
} | |
} | |
return Promise.all( pending ); | |
}; | |
/* BINARY EXTENSION */ | |
var BINARY_EXTENSION_BUFFER_NAME = 'binary_glTF'; | |
var BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; | |
var BINARY_EXTENSION_HEADER_LENGTH = 12; | |
var BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; | |
function GLTFBinaryExtension( data ) { | |
this.name = EXTENSIONS.KHR_BINARY_GLTF; | |
this.content = null; | |
this.body = null; | |
var headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); | |
this.header = { | |
magic: THREE.LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), | |
version: headerView.getUint32( 4, true ), | |
length: headerView.getUint32( 8, true ) | |
}; | |
if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { | |
throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); | |
} else if ( this.header.version < 2.0 ) { | |
throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' ); | |
} | |
var chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); | |
var chunkIndex = 0; | |
while ( chunkIndex < chunkView.byteLength ) { | |
var chunkLength = chunkView.getUint32( chunkIndex, true ); | |
chunkIndex += 4; | |
var chunkType = chunkView.getUint32( chunkIndex, true ); | |
chunkIndex += 4; | |
if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { | |
var contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); | |
this.content = THREE.LoaderUtils.decodeText( contentArray ); | |
} else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { | |
var byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; | |
this.body = data.slice( byteOffset, byteOffset + chunkLength ); | |
} | |
// Clients must ignore chunks with unknown types. | |
chunkIndex += chunkLength; | |
} | |
if ( this.content === null ) { | |
throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); | |
} | |
} | |
/** | |
* DRACO Mesh Compression Extension | |
* | |
* Specification: https://github.com/KhronosGroup/glTF/pull/874 | |
*/ | |
function GLTFDracoMeshCompressionExtension( json, dracoLoader ) { | |
if ( ! dracoLoader ) { | |
throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); | |
} | |
this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; | |
this.json = json; | |
this.dracoLoader = dracoLoader; | |
} | |
GLTFDracoMeshCompressionExtension.prototype.decodePrimitive = function ( primitive, parser ) { | |
var json = this.json; | |
var dracoLoader = this.dracoLoader; | |
var bufferViewIndex = primitive.extensions[ this.name ].bufferView; | |
var gltfAttributeMap = primitive.extensions[ this.name ].attributes; | |
var threeAttributeMap = {}; | |
var attributeNormalizedMap = {}; | |
var attributeTypeMap = {}; | |
for ( var attributeName in gltfAttributeMap ) { | |
var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); | |
threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; | |
} | |
for ( attributeName in primitive.attributes ) { | |
var threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); | |
if ( gltfAttributeMap[ attributeName ] !== undefined ) { | |
var accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; | |
var componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; | |
attributeTypeMap[ threeAttributeName ] = componentType; | |
attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; | |
} | |
} | |
return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { | |
return new Promise( function ( resolve ) { | |
dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { | |
for ( var attributeName in geometry.attributes ) { | |
var attribute = geometry.attributes[ attributeName ]; | |
var normalized = attributeNormalizedMap[ attributeName ]; | |
if ( normalized !== undefined ) attribute.normalized = normalized; | |
} | |
resolve( geometry ); | |
}, threeAttributeMap, attributeTypeMap ); | |
} ); | |
} ); | |
}; | |
/** | |
* Texture Transform Extension | |
* | |
* Specification: | |
*/ | |
function GLTFTextureTransformExtension() { | |
this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; | |
} | |
GLTFTextureTransformExtension.prototype.extendTexture = function ( texture, transform ) { | |
texture = texture.clone(); | |
if ( transform.offset !== undefined ) { | |
texture.offset.fromArray( transform.offset ); | |
} | |
if ( transform.rotation !== undefined ) { | |
texture.rotation = transform.rotation; | |
} | |
if ( transform.scale !== undefined ) { | |
texture.repeat.fromArray( transform.scale ); | |
} | |
if ( transform.texCoord !== undefined ) { | |
console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); | |
} | |
texture.needsUpdate = true; | |
return texture; | |
}; | |
/** | |
* Specular-Glossiness Extension | |
* | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness | |
*/ | |
function GLTFMaterialsPbrSpecularGlossinessExtension() { | |
return { | |
name: EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS, | |
specularGlossinessParams: [ | |
'color', | |
'map', | |
'lightMap', | |
'lightMapIntensity', | |
'aoMap', | |
'aoMapIntensity', | |
'emissive', | |
'emissiveIntensity', | |
'emissiveMap', | |
'bumpMap', | |
'bumpScale', | |
'normalMap', | |
'displacementMap', | |
'displacementScale', | |
'displacementBias', | |
'specularMap', | |
'specular', | |
'glossinessMap', | |
'glossiness', | |
'alphaMap', | |
'envMap', | |
'envMapIntensity', | |
'refractionRatio', | |
], | |
getMaterialType: function () { | |
return THREE.ShaderMaterial; | |
}, | |
extendParams: function ( materialParams, materialDef, parser ) { | |
var pbrSpecularGlossiness = materialDef.extensions[ this.name ]; | |
var shader = THREE.ShaderLib[ 'standard' ]; | |
var uniforms = THREE.UniformsUtils.clone( shader.uniforms ); | |
var specularMapParsFragmentChunk = [ | |
'#ifdef USE_SPECULARMAP', | |
' uniform sampler2D specularMap;', | |
'#endif' | |
].join( '\n' ); | |
var glossinessMapParsFragmentChunk = [ | |
'#ifdef USE_GLOSSINESSMAP', | |
' uniform sampler2D glossinessMap;', | |
'#endif' | |
].join( '\n' ); | |
var specularMapFragmentChunk = [ | |
'vec3 specularFactor = specular;', | |
'#ifdef USE_SPECULARMAP', | |
' vec4 texelSpecular = texture2D( specularMap, vUv );', | |
' texelSpecular = sRGBToLinear( texelSpecular );', | |
' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', | |
' specularFactor *= texelSpecular.rgb;', | |
'#endif' | |
].join( '\n' ); | |
var glossinessMapFragmentChunk = [ | |
'float glossinessFactor = glossiness;', | |
'#ifdef USE_GLOSSINESSMAP', | |
' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', | |
' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', | |
' glossinessFactor *= texelGlossiness.a;', | |
'#endif' | |
].join( '\n' ); | |
var lightPhysicalFragmentChunk = [ | |
'PhysicalMaterial material;', | |
'material.diffuseColor = diffuseColor.rgb;', | |
'material.specularRoughness = clamp( 1.0 - glossinessFactor, 0.04, 1.0 );', | |
'material.specularColor = specularFactor.rgb;', | |
].join( '\n' ); | |
var fragmentShader = shader.fragmentShader | |
.replace( 'uniform float roughness;', 'uniform vec3 specular;' ) | |
.replace( 'uniform float metalness;', 'uniform float glossiness;' ) | |
.replace( '#include <roughnessmap_pars_fragment>', specularMapParsFragmentChunk ) | |
.replace( '#include <metalnessmap_pars_fragment>', glossinessMapParsFragmentChunk ) | |
.replace( '#include <roughnessmap_fragment>', specularMapFragmentChunk ) | |
.replace( '#include <metalnessmap_fragment>', glossinessMapFragmentChunk ) | |
.replace( '#include <lights_physical_fragment>', lightPhysicalFragmentChunk ); | |
delete uniforms.roughness; | |
delete uniforms.metalness; | |
delete uniforms.roughnessMap; | |
delete uniforms.metalnessMap; | |
uniforms.specular = { value: new THREE.Color().setHex( 0x111111 ) }; | |
uniforms.glossiness = { value: 0.5 }; | |
uniforms.specularMap = { value: null }; | |
uniforms.glossinessMap = { value: null }; | |
materialParams.vertexShader = shader.vertexShader; | |
materialParams.fragmentShader = fragmentShader; | |
materialParams.uniforms = uniforms; | |
materialParams.defines = { 'STANDARD': '' }; | |
materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); | |
materialParams.opacity = 1.0; | |
var pending = []; | |
if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { | |
var array = pbrSpecularGlossiness.diffuseFactor; | |
materialParams.color.fromArray( array ); | |
materialParams.opacity = array[ 3 ]; | |
} | |
if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { | |
pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture ) ); | |
} | |
materialParams.emissive = new THREE.Color( 0.0, 0.0, 0.0 ); | |
materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; | |
materialParams.specular = new THREE.Color( 1.0, 1.0, 1.0 ); | |
if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { | |
materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); | |
} | |
if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { | |
var specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; | |
pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); | |
pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef ) ); | |
} | |
return Promise.all( pending ); | |
}, | |
createMaterial: function ( params ) { | |
// setup material properties based on MeshStandardMaterial for Specular-Glossiness | |
var material = new THREE.ShaderMaterial( { | |
defines: params.defines, | |
vertexShader: params.vertexShader, | |
fragmentShader: params.fragmentShader, | |
uniforms: params.uniforms, | |
fog: true, | |
lights: true, | |
opacity: params.opacity, | |
transparent: params.transparent | |
} ); | |
material.isGLTFSpecularGlossinessMaterial = true; | |
material.color = params.color; | |
material.map = params.map === undefined ? null : params.map; | |
material.lightMap = null; | |
material.lightMapIntensity = 1.0; | |
material.aoMap = params.aoMap === undefined ? null : params.aoMap; | |
material.aoMapIntensity = 1.0; | |
material.emissive = params.emissive; | |
material.emissiveIntensity = 1.0; | |
material.emissiveMap = params.emissiveMap === undefined ? null : params.emissiveMap; | |
material.bumpMap = params.bumpMap === undefined ? null : params.bumpMap; | |
material.bumpScale = 1; | |
material.normalMap = params.normalMap === undefined ? null : params.normalMap; | |
if ( params.normalScale ) material.normalScale = params.normalScale; | |
material.displacementMap = null; | |
material.displacementScale = 1; | |
material.displacementBias = 0; | |
material.specularMap = params.specularMap === undefined ? null : params.specularMap; | |
material.specular = params.specular; | |
material.glossinessMap = params.glossinessMap === undefined ? null : params.glossinessMap; | |
material.glossiness = params.glossiness; | |
material.alphaMap = null; | |
material.envMap = params.envMap === undefined ? null : params.envMap; | |
material.envMapIntensity = 1.0; | |
material.refractionRatio = 0.98; | |
material.extensions.derivatives = true; | |
return material; | |
}, | |
/** | |
* Clones a GLTFSpecularGlossinessMaterial instance. The ShaderMaterial.copy() method can | |
* copy only properties it knows about or inherits, and misses many properties that would | |
* normally be defined by MeshStandardMaterial. | |
* | |
* This method allows GLTFSpecularGlossinessMaterials to be cloned in the process of | |
* loading a glTF model, but cloning later (e.g. by the user) would require these changes | |
* AND also updating `.onBeforeRender` on the parent mesh. | |
* | |
* @param {THREE.ShaderMaterial} source | |
* @return {THREE.ShaderMaterial} | |
*/ | |
cloneMaterial: function ( source ) { | |
var target = source.clone(); | |
target.isGLTFSpecularGlossinessMaterial = true; | |
var params = this.specularGlossinessParams; | |
for ( var i = 0, il = params.length; i < il; i ++ ) { | |
target[ params[ i ] ] = source[ params[ i ] ]; | |
} | |
return target; | |
}, | |
// Here's based on refreshUniformsCommon() and refreshUniformsStandard() in WebGLRenderer. | |
refreshUniforms: function ( renderer, scene, camera, geometry, material, group ) { | |
if ( material.isGLTFSpecularGlossinessMaterial !== true ) { | |
return; | |
} | |
var uniforms = material.uniforms; | |
var defines = material.defines; | |
uniforms.opacity.value = material.opacity; | |
uniforms.diffuse.value.copy( material.color ); | |
uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); | |
uniforms.map.value = material.map; | |
uniforms.specularMap.value = material.specularMap; | |
uniforms.alphaMap.value = material.alphaMap; | |
uniforms.lightMap.value = material.lightMap; | |
uniforms.lightMapIntensity.value = material.lightMapIntensity; | |
uniforms.aoMap.value = material.aoMap; | |
uniforms.aoMapIntensity.value = material.aoMapIntensity; | |
// uv repeat and offset setting priorities | |
// 1. color map | |
// 2. specular map | |
// 3. normal map | |
// 4. bump map | |
// 5. alpha map | |
// 6. emissive map | |
var uvScaleMap; | |
if ( material.map ) { | |
uvScaleMap = material.map; | |
} else if ( material.specularMap ) { | |
uvScaleMap = material.specularMap; | |
} else if ( material.displacementMap ) { | |
uvScaleMap = material.displacementMap; | |
} else if ( material.normalMap ) { | |
uvScaleMap = material.normalMap; | |
} else if ( material.bumpMap ) { | |
uvScaleMap = material.bumpMap; | |
} else if ( material.glossinessMap ) { | |
uvScaleMap = material.glossinessMap; | |
} else if ( material.alphaMap ) { | |
uvScaleMap = material.alphaMap; | |
} else if ( material.emissiveMap ) { | |
uvScaleMap = material.emissiveMap; | |
} | |
if ( uvScaleMap !== undefined ) { | |
// backwards compatibility | |
if ( uvScaleMap.isWebGLRenderTarget ) { | |
uvScaleMap = uvScaleMap.texture; | |
} | |
if ( uvScaleMap.matrixAutoUpdate === true ) { | |
uvScaleMap.updateMatrix(); | |
} | |
uniforms.uvTransform.value.copy( uvScaleMap.matrix ); | |
} | |
if ( material.envMap ) { | |
uniforms.envMap.value = material.envMap; | |
uniforms.envMapIntensity.value = material.envMapIntensity; | |
// don't flip CubeTexture envMaps, flip everything else: | |
// WebGLRenderTargetCube will be flipped for backwards compatibility | |
// WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture | |
// this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future | |
uniforms.flipEnvMap.value = material.envMap.isCubeTexture ? - 1 : 1; | |
uniforms.reflectivity.value = material.reflectivity; | |
uniforms.refractionRatio.value = material.refractionRatio; | |
uniforms.maxMipLevel.value = renderer.properties.get( material.envMap ).__maxMipLevel; | |
} | |
uniforms.specular.value.copy( material.specular ); | |
uniforms.glossiness.value = material.glossiness; | |
uniforms.glossinessMap.value = material.glossinessMap; | |
uniforms.emissiveMap.value = material.emissiveMap; | |
uniforms.bumpMap.value = material.bumpMap; | |
uniforms.normalMap.value = material.normalMap; | |
uniforms.displacementMap.value = material.displacementMap; | |
uniforms.displacementScale.value = material.displacementScale; | |
uniforms.displacementBias.value = material.displacementBias; | |
if ( uniforms.glossinessMap.value !== null && defines.USE_GLOSSINESSMAP === undefined ) { | |
defines.USE_GLOSSINESSMAP = ''; | |
// set USE_ROUGHNESSMAP to enable vUv | |
defines.USE_ROUGHNESSMAP = ''; | |
} | |
if ( uniforms.glossinessMap.value === null && defines.USE_GLOSSINESSMAP !== undefined ) { | |
delete defines.USE_GLOSSINESSMAP; | |
delete defines.USE_ROUGHNESSMAP; | |
} | |
} | |
}; | |
} | |
/*********************************/ | |
/********** INTERPOLATION ********/ | |
/*********************************/ | |
// Spline Interpolation | |
// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation | |
function GLTFCubicSplineInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { | |
THREE.Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); | |
} | |
GLTFCubicSplineInterpolant.prototype = Object.create( THREE.Interpolant.prototype ); | |
GLTFCubicSplineInterpolant.prototype.constructor = GLTFCubicSplineInterpolant; | |
GLTFCubicSplineInterpolant.prototype.copySampleValue_ = function ( index ) { | |
// Copies a sample value to the result buffer. See description of glTF | |
// CUBICSPLINE values layout in interpolate_() function below. | |
var result = this.resultBuffer, | |
values = this.sampleValues, | |
valueSize = this.valueSize, | |
offset = index * valueSize * 3 + valueSize; | |
for ( var i = 0; i !== valueSize; i ++ ) { | |
result[ i ] = values[ offset + i ]; | |
} | |
return result; | |
}; | |
GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; | |
GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; | |
GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { | |
var result = this.resultBuffer; | |
var values = this.sampleValues; | |
var stride = this.valueSize; | |
var stride2 = stride * 2; | |
var stride3 = stride * 3; | |
var td = t1 - t0; | |
var p = ( t - t0 ) / td; | |
var pp = p * p; | |
var ppp = pp * p; | |
var offset1 = i1 * stride3; | |
var offset0 = offset1 - stride3; | |
var s2 = - 2 * ppp + 3 * pp; | |
var s3 = ppp - pp; | |
var s0 = 1 - s2; | |
var s1 = s3 - pp + p; | |
// Layout of keyframe output values for CUBICSPLINE animations: | |
// [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] | |
for ( var i = 0; i !== stride; i ++ ) { | |
var p0 = values[ offset0 + i + stride ]; // splineVertex_k | |
var m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) | |
var p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 | |
var m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) | |
result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; | |
} | |
return result; | |
}; | |
/*********************************/ | |
/********** INTERNALS ************/ | |
/*********************************/ | |
/* CONSTANTS */ | |
var WEBGL_CONSTANTS = { | |
FLOAT: 5126, | |
//FLOAT_MAT2: 35674, | |
FLOAT_MAT3: 35675, | |
FLOAT_MAT4: 35676, | |
FLOAT_VEC2: 35664, | |
FLOAT_VEC3: 35665, | |
FLOAT_VEC4: 35666, | |
LINEAR: 9729, | |
REPEAT: 10497, | |
SAMPLER_2D: 35678, | |
POINTS: 0, | |
LINES: 1, | |
LINE_LOOP: 2, | |
LINE_STRIP: 3, | |
TRIANGLES: 4, | |
TRIANGLE_STRIP: 5, | |
TRIANGLE_FAN: 6, | |
UNSIGNED_BYTE: 5121, | |
UNSIGNED_SHORT: 5123 | |
}; | |
var WEBGL_TYPE = { | |
5126: Number, | |
//35674: THREE.Matrix2, | |
35675: THREE.Matrix3, | |
35676: THREE.Matrix4, | |
35664: THREE.Vector2, | |
35665: THREE.Vector3, | |
35666: THREE.Vector4, | |
35678: THREE.Texture | |
}; | |
var WEBGL_COMPONENT_TYPES = { | |
5120: Int8Array, | |
5121: Uint8Array, | |
5122: Int16Array, | |
5123: Uint16Array, | |
5125: Uint32Array, | |
5126: Float32Array | |
}; | |
var WEBGL_FILTERS = { | |
9728: THREE.NearestFilter, | |
9729: THREE.LinearFilter, | |
9984: THREE.NearestMipMapNearestFilter, | |
9985: THREE.LinearMipMapNearestFilter, | |
9986: THREE.NearestMipMapLinearFilter, | |
9987: THREE.LinearMipMapLinearFilter | |
}; | |
var WEBGL_WRAPPINGS = { | |
33071: THREE.ClampToEdgeWrapping, | |
33648: THREE.MirroredRepeatWrapping, | |
10497: THREE.RepeatWrapping | |
}; | |
var WEBGL_SIDES = { | |
1028: THREE.BackSide, // Culling front | |
1029: THREE.FrontSide // Culling back | |
//1032: THREE.NoSide // Culling front and back, what to do? | |
}; | |
var WEBGL_DEPTH_FUNCS = { | |
512: THREE.NeverDepth, | |
513: THREE.LessDepth, | |
514: THREE.EqualDepth, | |
515: THREE.LessEqualDepth, | |
516: THREE.GreaterEqualDepth, | |
517: THREE.NotEqualDepth, | |
518: THREE.GreaterEqualDepth, | |
519: THREE.AlwaysDepth | |
}; | |
var WEBGL_BLEND_EQUATIONS = { | |
32774: THREE.AddEquation, | |
32778: THREE.SubtractEquation, | |
32779: THREE.ReverseSubtractEquation | |
}; | |
var WEBGL_BLEND_FUNCS = { | |
0: THREE.ZeroFactor, | |
1: THREE.OneFactor, | |
768: THREE.SrcColorFactor, | |
769: THREE.OneMinusSrcColorFactor, | |
770: THREE.SrcAlphaFactor, | |
771: THREE.OneMinusSrcAlphaFactor, | |
772: THREE.DstAlphaFactor, | |
773: THREE.OneMinusDstAlphaFactor, | |
774: THREE.DstColorFactor, | |
775: THREE.OneMinusDstColorFactor, | |
776: THREE.SrcAlphaSaturateFactor | |
// The followings are not supported by Three.js yet | |
//32769: CONSTANT_COLOR, | |
//32770: ONE_MINUS_CONSTANT_COLOR, | |
//32771: CONSTANT_ALPHA, | |
//32772: ONE_MINUS_CONSTANT_COLOR | |
}; | |
var WEBGL_TYPE_SIZES = { | |
'SCALAR': 1, | |
'VEC2': 2, | |
'VEC3': 3, | |
'VEC4': 4, | |
'MAT2': 4, | |
'MAT3': 9, | |
'MAT4': 16 | |
}; | |
var ATTRIBUTES = { | |
POSITION: 'position', | |
NORMAL: 'normal', | |
TANGENT: 'tangent', | |
TEXCOORD_0: 'uv', | |
TEXCOORD_1: 'uv2', | |
COLOR_0: 'color', | |
WEIGHTS_0: 'skinWeight', | |
JOINTS_0: 'skinIndex', | |
}; | |
var PATH_PROPERTIES = { | |
scale: 'scale', | |
translation: 'position', | |
rotation: 'quaternion', | |
weights: 'morphTargetInfluences' | |
}; | |
var INTERPOLATION = { | |
CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each | |
// keyframe track will be initialized with a default interpolation type, then modified. | |
LINEAR: THREE.InterpolateLinear, | |
STEP: THREE.InterpolateDiscrete | |
}; | |
var STATES_ENABLES = { | |
2884: 'CULL_FACE', | |
2929: 'DEPTH_TEST', | |
3042: 'BLEND', | |
3089: 'SCISSOR_TEST', | |
32823: 'POLYGON_OFFSET_FILL', | |
32926: 'SAMPLE_ALPHA_TO_COVERAGE' | |
}; | |
var ALPHA_MODES = { | |
OPAQUE: 'OPAQUE', | |
MASK: 'MASK', | |
BLEND: 'BLEND' | |
}; | |
var MIME_TYPE_FORMATS = { | |
'image/png': THREE.RGBAFormat, | |
'image/jpeg': THREE.RGBFormat | |
}; | |
/* UTILITY FUNCTIONS */ | |
function resolveURL( url, path ) { | |
// Invalid URL | |
if ( typeof url !== 'string' || url === '' ) return ''; | |
// Absolute URL http://,https://,// | |
if ( /^(https?:)?\/\//i.test( url ) ) return url; | |
// Data URI | |
if ( /^data:.*,.*$/i.test( url ) ) return url; | |
// Blob URL | |
if ( /^blob:.*$/i.test( url ) ) return url; | |
// Relative URL | |
return path + url; | |
} | |
var defaultMaterial; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material | |
*/ | |
function createDefaultMaterial() { | |
defaultMaterial = defaultMaterial || new THREE.MeshStandardMaterial( { | |
color: 0xFFFFFF, | |
emissive: 0x000000, | |
metalness: 1, | |
roughness: 1, | |
transparent: false, | |
depthTest: true, | |
side: THREE.FrontSide | |
} ); | |
return defaultMaterial; | |
} | |
function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { | |
// Add unknown glTF extensions to an object's userData. | |
for ( var name in objectDef.extensions ) { | |
if ( knownExtensions[ name ] === undefined ) { | |
object.userData.gltfExtensions = object.userData.gltfExtensions || {}; | |
object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; | |
} | |
} | |
} | |
/** | |
* @param {THREE.Object3D|THREE.Material|THREE.BufferGeometry} object | |
* @param {GLTF.definition} gltfDef | |
*/ | |
function assignExtrasToUserData( object, gltfDef ) { | |
if ( gltfDef.extras !== undefined ) { | |
if ( typeof gltfDef.extras === 'object' ) { | |
object.userData = gltfDef.extras; | |
} else { | |
console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); | |
} | |
} | |
} | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets | |
* | |
* @param {THREE.BufferGeometry} geometry | |
* @param {Array<GLTF.Target>} targets | |
* @param {GLTFParser} parser | |
* @return {Promise<THREE.BufferGeometry>} | |
*/ | |
function addMorphTargets( geometry, targets, parser ) { | |
var hasMorphPosition = false; | |
var hasMorphNormal = false; | |
for ( var i = 0, il = targets.length; i < il; i ++ ) { | |
var target = targets[ i ]; | |
if ( target.POSITION !== undefined ) hasMorphPosition = true; | |
if ( target.NORMAL !== undefined ) hasMorphNormal = true; | |
if ( hasMorphPosition && hasMorphNormal ) break; | |
} | |
if ( ! hasMorphPosition && ! hasMorphNormal ) return Promise.resolve( geometry ); | |
var pendingPositionAccessors = []; | |
var pendingNormalAccessors = []; | |
for ( var i = 0, il = targets.length; i < il; i ++ ) { | |
var target = targets[ i ]; | |
if ( hasMorphPosition ) { | |
var pendingAccessor = target.POSITION !== undefined | |
? parser.getDependency( 'accessor', target.POSITION ) | |
: geometry.attributes.position; | |
pendingPositionAccessors.push( pendingAccessor ); | |
} | |
if ( hasMorphNormal ) { | |
var pendingAccessor = target.NORMAL !== undefined | |
? parser.getDependency( 'accessor', target.NORMAL ) | |
: geometry.attributes.normal; | |
pendingNormalAccessors.push( pendingAccessor ); | |
} | |
} | |
return Promise.all( [ | |
Promise.all( pendingPositionAccessors ), | |
Promise.all( pendingNormalAccessors ) | |
] ).then( function ( accessors ) { | |
var morphPositions = accessors[ 0 ]; | |
var morphNormals = accessors[ 1 ]; | |
// Clone morph target accessors before modifying them. | |
for ( var i = 0, il = morphPositions.length; i < il; i ++ ) { | |
if ( geometry.attributes.position === morphPositions[ i ] ) continue; | |
morphPositions[ i ] = cloneBufferAttribute( morphPositions[ i ] ); | |
} | |
for ( var i = 0, il = morphNormals.length; i < il; i ++ ) { | |
if ( geometry.attributes.normal === morphNormals[ i ] ) continue; | |
morphNormals[ i ] = cloneBufferAttribute( morphNormals[ i ] ); | |
} | |
for ( var i = 0, il = targets.length; i < il; i ++ ) { | |
var target = targets[ i ]; | |
var attributeName = 'morphTarget' + i; | |
if ( hasMorphPosition ) { | |
// Three.js morph position is absolute value. The formula is | |
// basePosition | |
// + weight0 * ( morphPosition0 - basePosition ) | |
// + weight1 * ( morphPosition1 - basePosition ) | |
// ... | |
// while the glTF one is relative | |
// basePosition | |
// + weight0 * glTFmorphPosition0 | |
// + weight1 * glTFmorphPosition1 | |
// ... | |
// then we need to convert from relative to absolute here. | |
if ( target.POSITION !== undefined ) { | |
var positionAttribute = morphPositions[ i ]; | |
positionAttribute.name = attributeName; | |
var position = geometry.attributes.position; | |
for ( var j = 0, jl = positionAttribute.count; j < jl; j ++ ) { | |
positionAttribute.setXYZ( | |
j, | |
positionAttribute.getX( j ) + position.getX( j ), | |
positionAttribute.getY( j ) + position.getY( j ), | |
positionAttribute.getZ( j ) + position.getZ( j ) | |
); | |
} | |
} | |
} | |
if ( hasMorphNormal ) { | |
// see target.POSITION's comment | |
if ( target.NORMAL !== undefined ) { | |
var normalAttribute = morphNormals[ i ]; | |
normalAttribute.name = attributeName; | |
var normal = geometry.attributes.normal; | |
for ( var j = 0, jl = normalAttribute.count; j < jl; j ++ ) { | |
normalAttribute.setXYZ( | |
j, | |
normalAttribute.getX( j ) + normal.getX( j ), | |
normalAttribute.getY( j ) + normal.getY( j ), | |
normalAttribute.getZ( j ) + normal.getZ( j ) | |
); | |
} | |
} | |
} | |
} | |
if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; | |
if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; | |
return geometry; | |
} ); | |
} | |
/** | |
* @param {THREE.Mesh} mesh | |
* @param {GLTF.Mesh} meshDef | |
*/ | |
function updateMorphTargets( mesh, meshDef ) { | |
mesh.updateMorphTargets(); | |
if ( meshDef.weights !== undefined ) { | |
for ( var i = 0, il = meshDef.weights.length; i < il; i ++ ) { | |
mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; | |
} | |
} | |
// .extras has user-defined data, so check that .extras.targetNames is an array. | |
if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { | |
var targetNames = meshDef.extras.targetNames; | |
if ( mesh.morphTargetInfluences.length === targetNames.length ) { | |
mesh.morphTargetDictionary = {}; | |
for ( var i = 0, il = targetNames.length; i < il; i ++ ) { | |
mesh.morphTargetDictionary[ targetNames[ i ] ] = i; | |
} | |
} else { | |
console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); | |
} | |
} | |
} | |
function isObjectEqual( a, b ) { | |
if ( Object.keys( a ).length !== Object.keys( b ).length ) return false; | |
for ( var key in a ) { | |
if ( a[ key ] !== b[ key ] ) return false; | |
} | |
return true; | |
} | |
function createPrimitiveKey( primitiveDef ) { | |
var dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; | |
var geometryKey; | |
if ( dracoExtension ) { | |
geometryKey = 'draco:' + dracoExtension.bufferView | |
+ ':' + dracoExtension.indices | |
+ ':' + createAttributesKey( dracoExtension.attributes ); | |
} else { | |
geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; | |
} | |
return geometryKey; | |
} | |
function createAttributesKey( attributes ) { | |
var attributesKey = ''; | |
var keys = Object.keys( attributes ).sort(); | |
for ( var i = 0, il = keys.length; i < il; i ++ ) { | |
attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; | |
} | |
return attributesKey; | |
} | |
function cloneBufferAttribute( attribute ) { | |
if ( attribute.isInterleavedBufferAttribute ) { | |
var count = attribute.count; | |
var itemSize = attribute.itemSize; | |
var array = attribute.array.slice( 0, count * itemSize ); | |
for ( var i = 0, j = 0; i < count; ++ i ) { | |
array[ j ++ ] = attribute.getX( i ); | |
if ( itemSize >= 2 ) array[ j ++ ] = attribute.getY( i ); | |
if ( itemSize >= 3 ) array[ j ++ ] = attribute.getZ( i ); | |
if ( itemSize >= 4 ) array[ j ++ ] = attribute.getW( i ); | |
} | |
return new THREE.BufferAttribute( array, itemSize, attribute.normalized ); | |
} | |
return attribute.clone(); | |
} | |
/* GLTF PARSER */ | |
function GLTFParser( json, extensions, options ) { | |
this.json = json || {}; | |
this.extensions = extensions || {}; | |
this.options = options || {}; | |
// loader object cache | |
this.cache = new GLTFRegistry(); | |
// BufferGeometry caching | |
this.primitiveCache = {}; | |
this.textureLoader = new THREE.TextureLoader( this.options.manager ); | |
this.textureLoader.setCrossOrigin( this.options.crossOrigin ); | |
this.fileLoader = new THREE.FileLoader( this.options.manager ); | |
this.fileLoader.setResponseType( 'arraybuffer' ); | |
} | |
GLTFParser.prototype.parse = function ( onLoad, onError ) { | |
var parser = this; | |
var json = this.json; | |
var extensions = this.extensions; | |
// Clear the loader cache | |
this.cache.removeAll(); | |
// Mark the special nodes/meshes in json for efficient parse | |
this.markDefs(); | |
Promise.all( [ | |
this.getDependencies( 'scene' ), | |
this.getDependencies( 'animation' ), | |
this.getDependencies( 'camera' ), | |
] ).then( function ( dependencies ) { | |
var result = { | |
scene: dependencies[ 0 ][ json.scene || 0 ], | |
scenes: dependencies[ 0 ], | |
animations: dependencies[ 1 ], | |
cameras: dependencies[ 2 ], | |
asset: json.asset, | |
parser: parser, | |
userData: {} | |
}; | |
addUnknownExtensionsToUserData( extensions, result, json ); | |
onLoad( result ); | |
} ).catch( onError ); | |
}; | |
/** | |
* Marks the special nodes/meshes in json for efficient parse. | |
*/ | |
GLTFParser.prototype.markDefs = function () { | |
var nodeDefs = this.json.nodes || []; | |
var skinDefs = this.json.skins || []; | |
var meshDefs = this.json.meshes || []; | |
var meshReferences = {}; | |
var meshUses = {}; | |
// Nothing in the node definition indicates whether it is a Bone or an | |
// Object3D. Use the skins' joint references to mark bones. | |
for ( var skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { | |
var joints = skinDefs[ skinIndex ].joints; | |
for ( var i = 0, il = joints.length; i < il; i ++ ) { | |
nodeDefs[ joints[ i ] ].isBone = true; | |
} | |
} | |
// Meshes can (and should) be reused by multiple nodes in a glTF asset. To | |
// avoid having more than one THREE.Mesh with the same name, count | |
// references and rename instances below. | |
// | |
// Example: CesiumMilkTruck sample model reuses "Wheel" meshes. | |
for ( var nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { | |
var nodeDef = nodeDefs[ nodeIndex ]; | |
if ( nodeDef.mesh !== undefined ) { | |
if ( meshReferences[ nodeDef.mesh ] === undefined ) { | |
meshReferences[ nodeDef.mesh ] = meshUses[ nodeDef.mesh ] = 0; | |
} | |
meshReferences[ nodeDef.mesh ] ++; | |
// Nothing in the mesh definition indicates whether it is | |
// a SkinnedMesh or Mesh. Use the node's mesh reference | |
// to mark SkinnedMesh if node has skin. | |
if ( nodeDef.skin !== undefined ) { | |
meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; | |
} | |
} | |
} | |
this.json.meshReferences = meshReferences; | |
this.json.meshUses = meshUses; | |
}; | |
/** | |
* Requests the specified dependency asynchronously, with caching. | |
* @param {string} type | |
* @param {number} index | |
* @return {Promise<THREE.Object3D|THREE.Material|THREE.Texture|THREE.AnimationClip|ArrayBuffer|Object>} | |
*/ | |
GLTFParser.prototype.getDependency = function ( type, index ) { | |
var cacheKey = type + ':' + index; | |
var dependency = this.cache.get( cacheKey ); | |
if ( ! dependency ) { | |
switch ( type ) { | |
case 'scene': | |
dependency = this.loadScene( index ); | |
break; | |
case 'node': | |
dependency = this.loadNode( index ); | |
break; | |
case 'mesh': | |
dependency = this.loadMesh( index ); | |
break; | |
case 'accessor': | |
dependency = this.loadAccessor( index ); | |
break; | |
case 'bufferView': | |
dependency = this.loadBufferView( index ); | |
break; | |
case 'buffer': | |
dependency = this.loadBuffer( index ); | |
break; | |
case 'material': | |
dependency = this.loadMaterial( index ); | |
break; | |
case 'texture': | |
dependency = this.loadTexture( index ); | |
break; | |
case 'skin': | |
dependency = this.loadSkin( index ); | |
break; | |
case 'animation': | |
dependency = this.loadAnimation( index ); | |
break; | |
case 'camera': | |
dependency = this.loadCamera( index ); | |
break; | |
case 'light': | |
dependency = this.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].loadLight( index ); | |
break; | |
default: | |
throw new Error( 'Unknown type: ' + type ); | |
} | |
this.cache.add( cacheKey, dependency ); | |
} | |
return dependency; | |
}; | |
/** | |
* Requests all dependencies of the specified type asynchronously, with caching. | |
* @param {string} type | |
* @return {Promise<Array<Object>>} | |
*/ | |
GLTFParser.prototype.getDependencies = function ( type ) { | |
var dependencies = this.cache.get( type ); | |
if ( ! dependencies ) { | |
var parser = this; | |
var defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; | |
dependencies = Promise.all( defs.map( function ( def, index ) { | |
return parser.getDependency( type, index ); | |
} ) ); | |
this.cache.add( type, dependencies ); | |
} | |
return dependencies; | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views | |
* @param {number} bufferIndex | |
* @return {Promise<ArrayBuffer>} | |
*/ | |
GLTFParser.prototype.loadBuffer = function ( bufferIndex ) { | |
var bufferDef = this.json.buffers[ bufferIndex ]; | |
var loader = this.fileLoader; | |
if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { | |
throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); | |
} | |
// If present, GLB container is required to be the first buffer. | |
if ( bufferDef.uri === undefined && bufferIndex === 0 ) { | |
return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); | |
} | |
var options = this.options; | |
return new Promise( function ( resolve, reject ) { | |
loader.load( resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { | |
reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); | |
} ); | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views | |
* @param {number} bufferViewIndex | |
* @return {Promise<ArrayBuffer>} | |
*/ | |
GLTFParser.prototype.loadBufferView = function ( bufferViewIndex ) { | |
var bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; | |
return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { | |
var byteLength = bufferViewDef.byteLength || 0; | |
var byteOffset = bufferViewDef.byteOffset || 0; | |
return buffer.slice( byteOffset, byteOffset + byteLength ); | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors | |
* @param {number} accessorIndex | |
* @return {Promise<THREE.BufferAttribute|THREE.InterleavedBufferAttribute>} | |
*/ | |
GLTFParser.prototype.loadAccessor = function ( accessorIndex ) { | |
var parser = this; | |
var json = this.json; | |
var accessorDef = this.json.accessors[ accessorIndex ]; | |
if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { | |
// Ignore empty accessors, which may be used to declare runtime | |
// information about attributes coming from another source (e.g. Draco | |
// compression extension). | |
return Promise.resolve( null ); | |
} | |
var pendingBufferViews = []; | |
if ( accessorDef.bufferView !== undefined ) { | |
pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); | |
} else { | |
pendingBufferViews.push( null ); | |
} | |
if ( accessorDef.sparse !== undefined ) { | |
pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); | |
pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); | |
} | |
return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { | |
var bufferView = bufferViews[ 0 ]; | |
var itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; | |
var TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; | |
// For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. | |
var elementBytes = TypedArray.BYTES_PER_ELEMENT; | |
var itemBytes = elementBytes * itemSize; | |
var byteOffset = accessorDef.byteOffset || 0; | |
var byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; | |
var normalized = accessorDef.normalized === true; | |
var array, bufferAttribute; | |
// The buffer is not interleaved if the stride is the item size in bytes. | |
if ( byteStride && byteStride !== itemBytes ) { | |
var ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType; | |
var ib = parser.cache.get( ibCacheKey ); | |
if ( ! ib ) { | |
// Use the full buffer if it's interleaved. | |
array = new TypedArray( bufferView ); | |
// Integer parameters to IB/IBA are in array elements, not bytes. | |
ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes ); | |
parser.cache.add( ibCacheKey, ib ); | |
} | |
bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, byteOffset / elementBytes, normalized ); | |
} else { | |
if ( bufferView === null ) { | |
array = new TypedArray( accessorDef.count * itemSize ); | |
} else { | |
array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); | |
} | |
bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized ); | |
} | |
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors | |
if ( accessorDef.sparse !== undefined ) { | |
var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; | |
var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; | |
var byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; | |
var byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; | |
var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); | |
var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); | |
if ( bufferView !== null ) { | |
// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. | |
bufferAttribute.setArray( bufferAttribute.array.slice() ); | |
} | |
for ( var i = 0, il = sparseIndices.length; i < il; i ++ ) { | |
var index = sparseIndices[ i ]; | |
bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); | |
if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); | |
if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); | |
if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); | |
if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); | |
} | |
} | |
return bufferAttribute; | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures | |
* @param {number} textureIndex | |
* @return {Promise<THREE.Texture>} | |
*/ | |
GLTFParser.prototype.loadTexture = function ( textureIndex ) { | |
var parser = this; | |
var json = this.json; | |
var options = this.options; | |
var textureLoader = this.textureLoader; | |
var URL = window.URL || window.webkitURL; | |
var textureDef = json.textures[ textureIndex ]; | |
var textureExtensions = textureDef.extensions || {}; | |
var source; | |
if ( textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] ) { | |
source = json.images[ textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].source ]; | |
} else { | |
source = json.images[ textureDef.source ]; | |
} | |
var sourceURI = source.uri; | |
var isObjectURL = false; | |
if ( source.bufferView !== undefined ) { | |
// Load binary image data from bufferView, if provided. | |
sourceURI = parser.getDependency( 'bufferView', source.bufferView ).then( function ( bufferView ) { | |
isObjectURL = true; | |
var blob = new Blob( [ bufferView ], { type: source.mimeType } ); | |
sourceURI = URL.createObjectURL( blob ); | |
return sourceURI; | |
} ); | |
} | |
return Promise.resolve( sourceURI ).then( function ( sourceURI ) { | |
// Load Texture resource. | |
var loader = THREE.Loader.Handlers.get( sourceURI ); | |
if ( ! loader ) { | |
loader = textureExtensions[ EXTENSIONS.MSFT_TEXTURE_DDS ] | |
? parser.extensions[ EXTENSIONS.MSFT_TEXTURE_DDS ].ddsLoader | |
: textureLoader; | |
} | |
return new Promise( function ( resolve, reject ) { | |
loader.load( resolveURL( sourceURI, options.path ), resolve, undefined, reject ); | |
} ); | |
} ).then( function ( texture ) { | |
// Clean up resources and configure Texture. | |
if ( isObjectURL === true ) { | |
URL.revokeObjectURL( sourceURI ); | |
} | |
texture.flipY = false; | |
if ( textureDef.name !== undefined ) texture.name = textureDef.name; | |
// Ignore unknown mime types, like DDS files. | |
if ( source.mimeType in MIME_TYPE_FORMATS ) { | |
texture.format = MIME_TYPE_FORMATS[ source.mimeType ]; | |
} | |
var samplers = json.samplers || {}; | |
var sampler = samplers[ textureDef.sampler ] || {}; | |
texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || THREE.LinearFilter; | |
texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || THREE.LinearMipMapLinearFilter; | |
texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || THREE.RepeatWrapping; | |
texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || THREE.RepeatWrapping; | |
return texture; | |
} ); | |
}; | |
/** | |
* Asynchronously assigns a texture to the given material parameters. | |
* @param {Object} materialParams | |
* @param {string} mapName | |
* @param {Object} mapDef | |
* @return {Promise} | |
*/ | |
GLTFParser.prototype.assignTexture = function ( materialParams, mapName, mapDef ) { | |
var parser = this; | |
return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { | |
if ( ! texture.isCompressedTexture ) { | |
switch ( mapName ) { | |
case 'aoMap': | |
case 'emissiveMap': | |
case 'metalnessMap': | |
case 'normalMap': | |
case 'roughnessMap': | |
texture.format = THREE.RGBFormat; | |
break; | |
} | |
} | |
if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { | |
var transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; | |
if ( transform ) { | |
texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); | |
} | |
} | |
materialParams[ mapName ] = texture; | |
} ); | |
}; | |
/** | |
* Assigns final material to a Mesh, Line, or Points instance. The instance | |
* already has a material (generated from the glTF material options alone) | |
* but reuse of the same glTF material may require multiple threejs materials | |
* to accomodate different primitive types, defines, etc. New materials will | |
* be created if necessary, and reused from a cache. | |
* @param {THREE.Object3D} mesh Mesh, Line, or Points instance. | |
*/ | |
GLTFParser.prototype.assignFinalMaterial = function ( mesh ) { | |
var geometry = mesh.geometry; | |
var material = mesh.material; | |
var extensions = this.extensions; | |
var useVertexTangents = geometry.attributes.tangent !== undefined; | |
var useVertexColors = geometry.attributes.color !== undefined; | |
var useFlatShading = geometry.attributes.normal === undefined; | |
var useSkinning = mesh.isSkinnedMesh === true; | |
var useMorphTargets = Object.keys( geometry.morphAttributes ).length > 0; | |
var useMorphNormals = useMorphTargets && geometry.morphAttributes.normal !== undefined; | |
if ( mesh.isPoints ) { | |
var cacheKey = 'PointsMaterial:' + material.uuid; | |
var pointsMaterial = this.cache.get( cacheKey ); | |
if ( ! pointsMaterial ) { | |
pointsMaterial = new THREE.PointsMaterial(); | |
THREE.Material.prototype.copy.call( pointsMaterial, material ); | |
pointsMaterial.color.copy( material.color ); | |
pointsMaterial.map = material.map; | |
pointsMaterial.lights = false; // PointsMaterial doesn't support lights yet | |
this.cache.add( cacheKey, pointsMaterial ); | |
} | |
material = pointsMaterial; | |
} else if ( mesh.isLine ) { | |
var cacheKey = 'LineBasicMaterial:' + material.uuid; | |
var lineMaterial = this.cache.get( cacheKey ); | |
if ( ! lineMaterial ) { | |
lineMaterial = new THREE.LineBasicMaterial(); | |
THREE.Material.prototype.copy.call( lineMaterial, material ); | |
lineMaterial.color.copy( material.color ); | |
lineMaterial.lights = false; // LineBasicMaterial doesn't support lights yet | |
this.cache.add( cacheKey, lineMaterial ); | |
} | |
material = lineMaterial; | |
} | |
// Clone the material if it will be modified | |
if ( useVertexTangents || useVertexColors || useFlatShading || useSkinning || useMorphTargets ) { | |
var cacheKey = 'ClonedMaterial:' + material.uuid + ':'; | |
if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; | |
if ( useSkinning ) cacheKey += 'skinning:'; | |
if ( useVertexTangents ) cacheKey += 'vertex-tangents:'; | |
if ( useVertexColors ) cacheKey += 'vertex-colors:'; | |
if ( useFlatShading ) cacheKey += 'flat-shading:'; | |
if ( useMorphTargets ) cacheKey += 'morph-targets:'; | |
if ( useMorphNormals ) cacheKey += 'morph-normals:'; | |
var cachedMaterial = this.cache.get( cacheKey ); | |
if ( ! cachedMaterial ) { | |
cachedMaterial = material.isGLTFSpecularGlossinessMaterial | |
? extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].cloneMaterial( material ) | |
: material.clone(); | |
if ( useSkinning ) cachedMaterial.skinning = true; | |
if ( useVertexTangents ) cachedMaterial.vertexTangents = true; | |
if ( useVertexColors ) cachedMaterial.vertexColors = THREE.VertexColors; | |
if ( useFlatShading ) cachedMaterial.flatShading = true; | |
if ( useMorphTargets ) cachedMaterial.morphTargets = true; | |
if ( useMorphNormals ) cachedMaterial.morphNormals = true; | |
this.cache.add( cacheKey, cachedMaterial ); | |
} | |
material = cachedMaterial; | |
} | |
// workarounds for mesh and geometry | |
if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { | |
console.log( 'THREE.GLTFLoader: Duplicating UVs to support aoMap.' ); | |
geometry.addAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) ); | |
} | |
if ( material.isGLTFSpecularGlossinessMaterial ) { | |
// for GLTFSpecularGlossinessMaterial(ShaderMaterial) uniforms runtime update | |
mesh.onBeforeRender = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].refreshUniforms; | |
} | |
mesh.material = material; | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials | |
* @param {number} materialIndex | |
* @return {Promise<THREE.Material>} | |
*/ | |
GLTFParser.prototype.loadMaterial = function ( materialIndex ) { | |
var parser = this; | |
var json = this.json; | |
var extensions = this.extensions; | |
var materialDef = json.materials[ materialIndex ]; | |
var materialType; | |
var materialParams = {}; | |
var materialExtensions = materialDef.extensions || {}; | |
var pending = []; | |
if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { | |
var sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; | |
materialType = sgExtension.getMaterialType(); | |
pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); | |
} else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { | |
var kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; | |
materialType = kmuExtension.getMaterialType(); | |
pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); | |
} else { | |
// Specification: | |
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material | |
materialType = THREE.MeshStandardMaterial; | |
var metallicRoughness = materialDef.pbrMetallicRoughness || {}; | |
materialParams.color = new THREE.Color( 1.0, 1.0, 1.0 ); | |
materialParams.opacity = 1.0; | |
if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { | |
var array = metallicRoughness.baseColorFactor; | |
materialParams.color.fromArray( array ); | |
materialParams.opacity = array[ 3 ]; | |
} | |
if ( metallicRoughness.baseColorTexture !== undefined ) { | |
pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture ) ); | |
} | |
materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; | |
materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; | |
if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { | |
pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); | |
pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); | |
} | |
} | |
if ( materialDef.doubleSided === true ) { | |
materialParams.side = THREE.DoubleSide; | |
} | |
var alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; | |
if ( alphaMode === ALPHA_MODES.BLEND ) { | |
materialParams.transparent = true; | |
} else { | |
materialParams.transparent = false; | |
if ( alphaMode === ALPHA_MODES.MASK ) { | |
materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; | |
} | |
} | |
if ( materialDef.normalTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { | |
pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); | |
materialParams.normalScale = new THREE.Vector2( 1, 1 ); | |
if ( materialDef.normalTexture.scale !== undefined ) { | |
materialParams.normalScale.set( materialDef.normalTexture.scale, materialDef.normalTexture.scale ); | |
} | |
} | |
if ( materialDef.occlusionTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { | |
pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); | |
if ( materialDef.occlusionTexture.strength !== undefined ) { | |
materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; | |
} | |
} | |
if ( materialDef.emissiveFactor !== undefined && materialType !== THREE.MeshBasicMaterial ) { | |
materialParams.emissive = new THREE.Color().fromArray( materialDef.emissiveFactor ); | |
} | |
if ( materialDef.emissiveTexture !== undefined && materialType !== THREE.MeshBasicMaterial ) { | |
pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture ) ); | |
} | |
return Promise.all( pending ).then( function () { | |
var material; | |
if ( materialType === THREE.ShaderMaterial ) { | |
material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); | |
} else { | |
material = new materialType( materialParams ); | |
} | |
if ( materialDef.name !== undefined ) material.name = materialDef.name; | |
// baseColorTexture, emissiveTexture, and specularGlossinessTexture use sRGB encoding. | |
if ( material.map ) material.map.encoding = THREE.sRGBEncoding; | |
if ( material.emissiveMap ) material.emissiveMap.encoding = THREE.sRGBEncoding; | |
if ( material.specularMap ) material.specularMap.encoding = THREE.sRGBEncoding; | |
assignExtrasToUserData( material, materialDef ); | |
if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); | |
return material; | |
} ); | |
}; | |
/** | |
* @param {THREE.BufferGeometry} geometry | |
* @param {GLTF.Primitive} primitiveDef | |
* @param {GLTFParser} parser | |
* @return {Promise<THREE.BufferGeometry>} | |
*/ | |
function addPrimitiveAttributes( geometry, primitiveDef, parser ) { | |
var attributes = primitiveDef.attributes; | |
var pending = []; | |
function assignAttributeAccessor( accessorIndex, attributeName ) { | |
return parser.getDependency( 'accessor', accessorIndex ) | |
.then( function ( accessor ) { | |
geometry.addAttribute( attributeName, accessor ); | |
} ); | |
} | |
for ( var gltfAttributeName in attributes ) { | |
var threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); | |
// Skip attributes already provided by e.g. Draco extension. | |
if ( threeAttributeName in geometry.attributes ) continue; | |
pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); | |
} | |
if ( primitiveDef.indices !== undefined && ! geometry.index ) { | |
var accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { | |
geometry.setIndex( accessor ); | |
} ); | |
pending.push( accessor ); | |
} | |
assignExtrasToUserData( geometry, primitiveDef ); | |
return Promise.all( pending ).then( function () { | |
return primitiveDef.targets !== undefined | |
? addMorphTargets( geometry, primitiveDef.targets, parser ) | |
: geometry; | |
} ); | |
} | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry | |
* | |
* Creates BufferGeometries from primitives. | |
* | |
* @param {Array<GLTF.Primitive>} primitives | |
* @return {Promise<Array<THREE.BufferGeometry>>} | |
*/ | |
GLTFParser.prototype.loadGeometries = function ( primitives ) { | |
var parser = this; | |
var extensions = this.extensions; | |
var cache = this.primitiveCache; | |
function createDracoPrimitive( primitive ) { | |
return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] | |
.decodePrimitive( primitive, parser ) | |
.then( function ( geometry ) { | |
return addPrimitiveAttributes( geometry, primitive, parser ); | |
} ); | |
} | |
var pending = []; | |
for ( var i = 0, il = primitives.length; i < il; i ++ ) { | |
var primitive = primitives[ i ]; | |
var cacheKey = createPrimitiveKey( primitive ); | |
// See if we've already created this geometry | |
var cached = cache[ cacheKey ]; | |
if ( cached ) { | |
// Use the cached geometry if it exists | |
pending.push( cached.promise ); | |
} else { | |
var geometryPromise; | |
if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { | |
// Use DRACO geometry if available | |
geometryPromise = createDracoPrimitive( primitive ); | |
} else { | |
// Otherwise create a new geometry | |
geometryPromise = addPrimitiveAttributes( new THREE.BufferGeometry(), primitive, parser ); | |
} | |
// Cache this geometry | |
cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; | |
pending.push( geometryPromise ); | |
} | |
} | |
return Promise.all( pending ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes | |
* @param {number} meshIndex | |
* @return {Promise<THREE.Group|THREE.Mesh|THREE.SkinnedMesh>} | |
*/ | |
GLTFParser.prototype.loadMesh = function ( meshIndex ) { | |
var parser = this; | |
var json = this.json; | |
var extensions = this.extensions; | |
var meshDef = json.meshes[ meshIndex ]; | |
var primitives = meshDef.primitives; | |
var pending = []; | |
for ( var i = 0, il = primitives.length; i < il; i ++ ) { | |
var material = primitives[ i ].material === undefined | |
? createDefaultMaterial() | |
: this.getDependency( 'material', primitives[ i ].material ); | |
pending.push( material ); | |
} | |
return Promise.all( pending ).then( function ( originalMaterials ) { | |
return parser.loadGeometries( primitives ).then( function ( geometries ) { | |
var meshes = []; | |
for ( var i = 0, il = geometries.length; i < il; i ++ ) { | |
var geometry = geometries[ i ]; | |
var primitive = primitives[ i ]; | |
// 1. create Mesh | |
var mesh; | |
var material = originalMaterials[ i ]; | |
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || | |
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || | |
primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || | |
primitive.mode === undefined ) { | |
// .isSkinnedMesh isn't in glTF spec. See .markDefs() | |
mesh = meshDef.isSkinnedMesh === true | |
? new THREE.SkinnedMesh( geometry, material ) | |
: new THREE.Mesh( geometry, material ); | |
if ( mesh.isSkinnedMesh === true ) mesh.normalizeSkinWeights(); // #15319 | |
if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { | |
mesh.drawMode = THREE.TriangleStripDrawMode; | |
} else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { | |
mesh.drawMode = THREE.TriangleFanDrawMode; | |
} | |
} else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { | |
mesh = new THREE.LineSegments( geometry, material ); | |
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { | |
mesh = new THREE.Line( geometry, material ); | |
} else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { | |
mesh = new THREE.LineLoop( geometry, material ); | |
} else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { | |
mesh = new THREE.Points( geometry, material ); | |
} else { | |
throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); | |
} | |
if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { | |
updateMorphTargets( mesh, meshDef ); | |
} | |
mesh.name = meshDef.name || ( 'mesh_' + meshIndex ); | |
if ( geometries.length > 1 ) mesh.name += '_' + i; | |
assignExtrasToUserData( mesh, meshDef ); | |
parser.assignFinalMaterial( mesh ); | |
meshes.push( mesh ); | |
} | |
if ( meshes.length === 1 ) { | |
return meshes[ 0 ]; | |
} | |
var group = new THREE.Group(); | |
for ( var i = 0, il = meshes.length; i < il; i ++ ) { | |
group.add( meshes[ i ] ); | |
} | |
return group; | |
} ); | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras | |
* @param {number} cameraIndex | |
* @return {Promise<THREE.Camera>} | |
*/ | |
GLTFParser.prototype.loadCamera = function ( cameraIndex ) { | |
var camera; | |
var cameraDef = this.json.cameras[ cameraIndex ]; | |
var params = cameraDef[ cameraDef.type ]; | |
if ( ! params ) { | |
console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); | |
return; | |
} | |
if ( cameraDef.type === 'perspective' ) { | |
camera = new THREE.PerspectiveCamera( THREE.Math.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); | |
} else if ( cameraDef.type === 'orthographic' ) { | |
camera = new THREE.OrthographicCamera( params.xmag / - 2, params.xmag / 2, params.ymag / 2, params.ymag / - 2, params.znear, params.zfar ); | |
} | |
if ( cameraDef.name !== undefined ) camera.name = cameraDef.name; | |
assignExtrasToUserData( camera, cameraDef ); | |
return Promise.resolve( camera ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins | |
* @param {number} skinIndex | |
* @return {Promise<Object>} | |
*/ | |
GLTFParser.prototype.loadSkin = function ( skinIndex ) { | |
var skinDef = this.json.skins[ skinIndex ]; | |
var skinEntry = { joints: skinDef.joints }; | |
if ( skinDef.inverseBindMatrices === undefined ) { | |
return Promise.resolve( skinEntry ); | |
} | |
return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { | |
skinEntry.inverseBindMatrices = accessor; | |
return skinEntry; | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations | |
* @param {number} animationIndex | |
* @return {Promise<THREE.AnimationClip>} | |
*/ | |
GLTFParser.prototype.loadAnimation = function ( animationIndex ) { | |
var json = this.json; | |
var animationDef = json.animations[ animationIndex ]; | |
var pendingNodes = []; | |
var pendingInputAccessors = []; | |
var pendingOutputAccessors = []; | |
var pendingSamplers = []; | |
var pendingTargets = []; | |
for ( var i = 0, il = animationDef.channels.length; i < il; i ++ ) { | |
var channel = animationDef.channels[ i ]; | |
var sampler = animationDef.samplers[ channel.sampler ]; | |
var target = channel.target; | |
var name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. | |
var input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; | |
var output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; | |
pendingNodes.push( this.getDependency( 'node', name ) ); | |
pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); | |
pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); | |
pendingSamplers.push( sampler ); | |
pendingTargets.push( target ); | |
} | |
return Promise.all( [ | |
Promise.all( pendingNodes ), | |
Promise.all( pendingInputAccessors ), | |
Promise.all( pendingOutputAccessors ), | |
Promise.all( pendingSamplers ), | |
Promise.all( pendingTargets ) | |
] ).then( function ( dependencies ) { | |
var nodes = dependencies[ 0 ]; | |
var inputAccessors = dependencies[ 1 ]; | |
var outputAccessors = dependencies[ 2 ]; | |
var samplers = dependencies[ 3 ]; | |
var targets = dependencies[ 4 ]; | |
var tracks = []; | |
for ( var i = 0, il = nodes.length; i < il; i ++ ) { | |
var node = nodes[ i ]; | |
var inputAccessor = inputAccessors[ i ]; | |
var outputAccessor = outputAccessors[ i ]; | |
var sampler = samplers[ i ]; | |
var target = targets[ i ]; | |
if ( node === undefined ) continue; | |
node.updateMatrix(); | |
node.matrixAutoUpdate = true; | |
var TypedKeyframeTrack; | |
switch ( PATH_PROPERTIES[ target.path ] ) { | |
case PATH_PROPERTIES.weights: | |
TypedKeyframeTrack = THREE.NumberKeyframeTrack; | |
break; | |
case PATH_PROPERTIES.rotation: | |
TypedKeyframeTrack = THREE.QuaternionKeyframeTrack; | |
break; | |
case PATH_PROPERTIES.position: | |
case PATH_PROPERTIES.scale: | |
default: | |
TypedKeyframeTrack = THREE.VectorKeyframeTrack; | |
break; | |
} | |
var targetName = node.name ? node.name : node.uuid; | |
var interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : THREE.InterpolateLinear; | |
var targetNames = []; | |
if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { | |
// Node may be a THREE.Group (glTF mesh with several primitives) or a THREE.Mesh. | |
node.traverse( function ( object ) { | |
if ( object.isMesh === true && object.morphTargetInfluences ) { | |
targetNames.push( object.name ? object.name : object.uuid ); | |
} | |
} ); | |
} else { | |
targetNames.push( targetName ); | |
} | |
for ( var j = 0, jl = targetNames.length; j < jl; j ++ ) { | |
var track = new TypedKeyframeTrack( | |
targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], | |
inputAccessor.array, | |
outputAccessor.array, | |
interpolation | |
); | |
// Override interpolation with custom factory method. | |
if ( sampler.interpolation === 'CUBICSPLINE' ) { | |
track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { | |
// A CUBICSPLINE keyframe in glTF has three output values for each input value, | |
// representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() | |
// must be divided by three to get the interpolant's sampleSize argument. | |
return new GLTFCubicSplineInterpolant( this.times, this.values, this.getValueSize() / 3, result ); | |
}; | |
// Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. | |
track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; | |
} | |
tracks.push( track ); | |
} | |
} | |
var name = animationDef.name !== undefined ? animationDef.name : 'animation_' + animationIndex; | |
return new THREE.AnimationClip( name, undefined, tracks ); | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy | |
* @param {number} nodeIndex | |
* @return {Promise<THREE.Object3D>} | |
*/ | |
GLTFParser.prototype.loadNode = function ( nodeIndex ) { | |
var json = this.json; | |
var extensions = this.extensions; | |
var parser = this; | |
var meshReferences = json.meshReferences; | |
var meshUses = json.meshUses; | |
var nodeDef = json.nodes[ nodeIndex ]; | |
return ( function () { | |
// .isBone isn't in glTF spec. See .markDefs | |
if ( nodeDef.isBone === true ) { | |
return Promise.resolve( new THREE.Bone() ); | |
} else if ( nodeDef.mesh !== undefined ) { | |
return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { | |
var node; | |
if ( meshReferences[ nodeDef.mesh ] > 1 ) { | |
var instanceNum = meshUses[ nodeDef.mesh ] ++; | |
node = mesh.clone(); | |
node.name += '_instance_' + instanceNum; | |
// onBeforeRender copy for Specular-Glossiness | |
node.onBeforeRender = mesh.onBeforeRender; | |
for ( var i = 0, il = node.children.length; i < il; i ++ ) { | |
node.children[ i ].name += '_instance_' + instanceNum; | |
node.children[ i ].onBeforeRender = mesh.children[ i ].onBeforeRender; | |
} | |
} else { | |
node = mesh; | |
} | |
// if weights are provided on the node, override weights on the mesh. | |
if ( nodeDef.weights !== undefined ) { | |
node.traverse( function ( o ) { | |
if ( ! o.isMesh ) return; | |
for ( var i = 0, il = nodeDef.weights.length; i < il; i ++ ) { | |
o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; | |
} | |
} ); | |
} | |
return node; | |
} ); | |
} else if ( nodeDef.camera !== undefined ) { | |
return parser.getDependency( 'camera', nodeDef.camera ); | |
} else if ( nodeDef.extensions | |
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ] | |
&& nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light !== undefined ) { | |
return parser.getDependency( 'light', nodeDef.extensions[ EXTENSIONS.KHR_LIGHTS_PUNCTUAL ].light ); | |
} else { | |
return Promise.resolve( new THREE.Object3D() ); | |
} | |
}() ).then( function ( node ) { | |
if ( nodeDef.name !== undefined ) { | |
node.name = THREE.PropertyBinding.sanitizeNodeName( nodeDef.name ); | |
} | |
assignExtrasToUserData( node, nodeDef ); | |
if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); | |
if ( nodeDef.matrix !== undefined ) { | |
var matrix = new THREE.Matrix4(); | |
matrix.fromArray( nodeDef.matrix ); | |
node.applyMatrix( matrix ); | |
} else { | |
if ( nodeDef.translation !== undefined ) { | |
node.position.fromArray( nodeDef.translation ); | |
} | |
if ( nodeDef.rotation !== undefined ) { | |
node.quaternion.fromArray( nodeDef.rotation ); | |
} | |
if ( nodeDef.scale !== undefined ) { | |
node.scale.fromArray( nodeDef.scale ); | |
} | |
} | |
return node; | |
} ); | |
}; | |
/** | |
* Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes | |
* @param {number} sceneIndex | |
* @return {Promise<THREE.Scene>} | |
*/ | |
GLTFParser.prototype.loadScene = function () { | |
// scene node hierachy builder | |
function buildNodeHierachy( nodeId, parentObject, json, parser ) { | |
var nodeDef = json.nodes[ nodeId ]; | |
return parser.getDependency( 'node', nodeId ).then( function ( node ) { | |
if ( nodeDef.skin === undefined ) return node; | |
// build skeleton here as well | |
var skinEntry; | |
return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { | |
skinEntry = skin; | |
var pendingJoints = []; | |
for ( var i = 0, il = skinEntry.joints.length; i < il; i ++ ) { | |
pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); | |
} | |
return Promise.all( pendingJoints ); | |
} ).then( function ( jointNodes ) { | |
var meshes = node.isGroup === true ? node.children : [ node ]; | |
for ( var i = 0, il = meshes.length; i < il; i ++ ) { | |
var mesh = meshes[ i ]; | |
var bones = []; | |
var boneInverses = []; | |
for ( var j = 0, jl = jointNodes.length; j < jl; j ++ ) { | |
var jointNode = jointNodes[ j ]; | |
if ( jointNode ) { | |
bones.push( jointNode ); | |
var mat = new THREE.Matrix4(); | |
if ( skinEntry.inverseBindMatrices !== undefined ) { | |
mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); | |
} | |
boneInverses.push( mat ); | |
} else { | |
console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); | |
} | |
} | |
mesh.bind( new THREE.Skeleton( bones, boneInverses ), mesh.matrixWorld ); | |
} | |
return node; | |
} ); | |
} ).then( function ( node ) { | |
// build node hierachy | |
parentObject.add( node ); | |
var pending = []; | |
if ( nodeDef.children ) { | |
var children = nodeDef.children; | |
for ( var i = 0, il = children.length; i < il; i ++ ) { | |
var child = children[ i ]; | |
pending.push( buildNodeHierachy( child, node, json, parser ) ); | |
} | |
} | |
return Promise.all( pending ); | |
} ); | |
} | |
return function loadScene( sceneIndex ) { | |
var json = this.json; | |
var extensions = this.extensions; | |
var sceneDef = this.json.scenes[ sceneIndex ]; | |
var parser = this; | |
var scene = new THREE.Scene(); | |
if ( sceneDef.name !== undefined ) scene.name = sceneDef.name; | |
assignExtrasToUserData( scene, sceneDef ); | |
if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); | |
var nodeIds = sceneDef.nodes || []; | |
var pending = []; | |
for ( var i = 0, il = nodeIds.length; i < il; i ++ ) { | |
pending.push( buildNodeHierachy( nodeIds[ i ], scene, json, parser ) ); | |
} | |
return Promise.all( pending ).then( function () { | |
return scene; | |
} ); | |
}; | |
}(); | |
return GLTFLoader; | |
} )(); | |
export default GLTFLoader; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> | |
<meta name="viewport" content="width=device-width,initial-scale=1"> | |
<title>3d objects</title> | |
<link rel="stylesheet" type="text/css" href="style.css"> | |
</head> | |
<body> | |
<main id="main"></main> | |
<script src="dist/main.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Scene from './scene'; | |
import GLTFLoader from './gltf'; | |
import * as THREE from 'three'; | |
// For loading GLTF models | |
const loader = new GLTFLoader(); | |
// Create the scene | |
const el = document.getElementById('main'); | |
const scene = new Scene({ | |
maxHeight: el.clientHeight, | |
maxWidth: el.clientWidth, | |
camera: 'perspective' | |
}); | |
el.appendChild(scene.renderer.domElement); | |
// Load a model | |
let model; | |
function loadModel(path, cb) { | |
loader.load(path, (gltf) => { | |
model = gltf.scene.children[0]; | |
model.scale.set(60, 60, 60); | |
model.rotation.x = -Math.PI/4; | |
model.rotation.z = -Math.PI/8; | |
scene.add(model); | |
cb(); | |
}); | |
} | |
// Render the scene | |
function render() { | |
// Rotate the model | |
model.rotation.y += 0.01; | |
// Render the scene | |
scene.render(); | |
requestAnimationFrame(render); | |
} | |
loadModel('models/fruit.gltf', render); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "3d_objects", | |
"version": "0.0.0", | |
"scripts": { | |
"start": "webpack-dev-server --watch --progress --mode=development" | |
}, | |
"dependencies": { | |
"@babel/polyfill": "^7.0.0", | |
"three": "^0.97.0" | |
}, | |
"devDependencies": { | |
"@babel/core": "^7.0.0-beta.39", | |
"@babel/preset-env": "^7.0.0-beta.39", | |
"babel-loader": "^8.0.0-beta.0", | |
"webpack": "^4.21.0", | |
"webpack-cli": "^3.3.4", | |
"webpack-dev-server": "^3.1.14" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as THREE from 'three'; | |
const VIEW_ANGLE = 45; | |
const NEAR = 0.1; | |
const FAR = 10000; | |
const D = 1; | |
class Scene { | |
constructor(opts) { | |
opts = opts || {}; | |
opts.camera = opts.camera || 'perspective'; | |
opts.width = opts.maxWidth ? Math.min(opts.maxWidth, window.innerWidth) : window.innerWidth; | |
opts.height = opts.maxHeight ? Math.min(opts.maxHeight, window.innerHeight) : window.innerHeight; | |
this.opts = opts; | |
this.scene = new THREE.Scene(); | |
this.renderer = new THREE.WebGLRenderer({antialias: false, alpha: true}); | |
this.renderer.setPixelRatio(window.devicePixelRatio); | |
this.renderer.setSize(opts.width, opts.height); | |
this.renderer.setClearColor(0xeeeeee, 0); | |
// Fix for dark gltf objects | |
this.renderer.gammaFactor = 3; | |
this.renderer.gammaOutput = true; | |
// Lights aren't necessary here | |
// var hemiLight = new THREE.HemisphereLight( 0xffffff, 0x000000, 0.9 ); | |
// this.scene.add(hemiLight); | |
// var amli = new THREE.AmbientLight( 0x404040 ); // soft white light | |
// this.scene.add( amli ); | |
// let light = new THREE.DirectionalLight( 0xffffff, 0.2 ); | |
// light.position.y = 200; | |
// light.position.x = 200; | |
// this.scene.add(light); | |
let aspect = opts.width/opts.height; | |
if (opts.camera == 'perspective') { | |
this.camera = new THREE.PerspectiveCamera( | |
VIEW_ANGLE, | |
aspect, | |
NEAR, FAR); | |
} else { | |
this.camera = new THREE.OrthographicCamera(-D*aspect, D*aspect, D, -D, NEAR, FAR); | |
this.camera.zoom = 2; | |
this.camera.zoom = 0.005; | |
} | |
this.camera.position.z = 400; | |
this.camera.position.y = 400; | |
// this.camera.position.x = 400; | |
this.camera.lookAt(0,0,0); | |
this.camera.updateProjectionMatrix(); | |
window.addEventListener('resize', () => { | |
opts.width = opts.maxWidth ? Math.min(opts.maxWidth, window.innerWidth) : window.innerWidth; | |
opts.height = opts.maxHeight ? Math.min(opts.maxHeight, window.innerHeight) : window.innerHeight; | |
let aspect = opts.width/opts.height; | |
if (opts.camera == 'perspective') { | |
this.camera.aspect = aspect; | |
} else { | |
this.camera.left = -D * aspect; | |
this.camera.right = D * aspect; | |
this.camera.top = D; | |
this.camera.bottom = -D; | |
} | |
this.camera.updateProjectionMatrix(); | |
this.renderer.setSize(opts.width, opts.height); | |
}, false); | |
} | |
add(mesh) { | |
this.scene.add(mesh); | |
} | |
remove(mesh) { | |
this.scene.remove(mesh); | |
} | |
clear() { | |
this.scene.children.forEach((obj) => { | |
this.scene.remove(obj); | |
}); | |
} | |
render() { | |
this.renderer.render(this.scene, this.camera); | |
} | |
} | |
export default Scene; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
margin: 0; | |
padding: 0; | |
overflow: hidden; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let path = require('path'); | |
module.exports = { | |
entry: { | |
'main': ['@babel/polyfill', './src/main'], | |
}, | |
output: { | |
path: path.resolve(__dirname, 'dist'), | |
filename: '[name].js' | |
}, | |
devtool: 'inline-source-map', | |
module: { | |
rules: [{ | |
test: /\.js$/, | |
exclude: /node_modules/, | |
use: { | |
loader: 'babel-loader', | |
options: { | |
presets: ['@babel/preset-env'] | |
} | |
} | |
}] | |
}, | |
resolve: { | |
extensions: ['.js'] | |
}, | |
devServer: { | |
writeToDisk: true | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment