Created
May 6, 2018 17:02
-
-
Save frankiezafe/5ccc704fd37b2dc30768a8c4c00db457 to your computer and use it in GitHub Desktop.
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 mrdoob / http://mrdoob.com/ | |
* @author Mugen87 / https://github.com/Mugen87 | |
*/ | |
THREE.ColladaLoader = function ( manager ) { | |
this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; | |
}; | |
THREE.ColladaLoader.prototype = { | |
constructor: THREE.ColladaLoader, | |
crossOrigin: 'Anonymous', | |
load: function ( url, onLoad, onProgress, onError ) { | |
var scope = this; | |
var path = scope.path === undefined ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path; | |
var loader = new THREE.FileLoader( scope.manager ); | |
loader.load( url, function ( text ) { | |
onLoad( scope.parse( text, path ) ); | |
}, onProgress, onError ); | |
}, | |
setPath: function ( value ) { | |
this.path = value; | |
}, | |
options: { | |
set convertUpAxis( value ) { | |
console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' ); | |
} | |
}, | |
setCrossOrigin: function ( value ) { | |
this.crossOrigin = value; | |
}, | |
parse: function ( text, path ) { | |
function getElementsByTagName( xml, name ) { | |
// Non recursive xml.getElementsByTagName() ... | |
var array = []; | |
var childNodes = xml.childNodes; | |
for ( var i = 0, l = childNodes.length; i < l; i ++ ) { | |
var child = childNodes[ i ]; | |
if ( child.nodeName === name ) { | |
array.push( child ); | |
} | |
} | |
return array; | |
} | |
function parseStrings( text ) { | |
if ( text.length === 0 ) return []; | |
var parts = text.trim().split( /\s+/ ); | |
var array = new Array( parts.length ); | |
for ( var i = 0, l = parts.length; i < l; i ++ ) { | |
array[ i ] = parts[ i ]; | |
} | |
return array; | |
} | |
function parseFloats( text ) { | |
if ( text.length === 0 ) return []; | |
var parts = text.trim().split( /\s+/ ); | |
var array = new Array( parts.length ); | |
for ( var i = 0, l = parts.length; i < l; i ++ ) { | |
array[ i ] = parseFloat( parts[ i ] ); | |
} | |
return array; | |
} | |
function parseInts( text ) { | |
if ( text.length === 0 ) return []; | |
var parts = text.trim().split( /\s+/ ); | |
var array = new Array( parts.length ); | |
for ( var i = 0, l = parts.length; i < l; i ++ ) { | |
array[ i ] = parseInt( parts[ i ] ); | |
} | |
return array; | |
} | |
function parseId( text ) { | |
return text.substring( 1 ); | |
} | |
function generateId() { | |
return 'three_default_' + ( count ++ ); | |
} | |
function isEmpty( object ) { | |
return Object.keys( object ).length === 0; | |
} | |
// asset | |
function parseAsset( xml ) { | |
return { | |
unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ), | |
upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] ) | |
}; | |
} | |
function parseAssetUnit( xml ) { | |
if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) { | |
return parseFloat( xml.getAttribute( 'meter' ) ); | |
} else { | |
return 1; // default 1 meter | |
} | |
} | |
function parseAssetUpAxis( xml ) { | |
return xml !== undefined ? xml.textContent : 'Y_UP'; | |
} | |
// library | |
function parseLibrary( xml, libraryName, nodeName, parser ) { | |
var library = getElementsByTagName( xml, libraryName )[ 0 ]; | |
if ( library !== undefined ) { | |
var elements = getElementsByTagName( library, nodeName ); | |
for ( var i = 0; i < elements.length; i ++ ) { | |
parser( elements[ i ] ); | |
} | |
} | |
} | |
function buildLibrary( data, builder ) { | |
for ( var name in data ) { | |
var object = data[ name ]; | |
object.build = builder( data[ name ] ); | |
} | |
} | |
// get | |
function getBuild( data, builder ) { | |
if ( data.build !== undefined ) return data.build; | |
data.build = builder( data ); | |
return data.build; | |
} | |
// animation | |
function parseAnimation( xml ) { | |
var data = { | |
sources: {}, | |
samplers: {}, | |
channels: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
var id; | |
switch ( child.nodeName ) { | |
case 'source': | |
id = child.getAttribute( 'id' ); | |
data.sources[ id ] = parseSource( child ); | |
break; | |
case 'sampler': | |
id = child.getAttribute( 'id' ); | |
data.samplers[ id ] = parseAnimationSampler( child ); | |
break; | |
case 'channel': | |
id = child.getAttribute( 'target' ); | |
data.channels[ id ] = parseAnimationChannel( child ); | |
break; | |
default: | |
console.log( child ); | |
} | |
} | |
library.animations[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseAnimationSampler( xml ) { | |
var data = { | |
inputs: {}, | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'input': | |
var id = parseId( child.getAttribute( 'source' ) ); | |
var semantic = child.getAttribute( 'semantic' ); | |
data.inputs[ semantic ] = id; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseAnimationChannel( xml ) { | |
var data = {}; | |
var target = xml.getAttribute( 'target' ); | |
// parsing SID Addressing Syntax | |
var parts = target.split( '/' ); | |
var id = parts.shift(); | |
var sid = parts.shift(); | |
// check selection syntax | |
var arraySyntax = ( sid.indexOf( '(' ) !== - 1 ); | |
var memberSyntax = ( sid.indexOf( '.' ) !== - 1 ); | |
if ( memberSyntax ) { | |
// member selection access | |
parts = sid.split( '.' ); | |
sid = parts.shift(); | |
data.member = parts.shift(); | |
} else if ( arraySyntax ) { | |
// array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. | |
var indices = sid.split( '(' ); | |
sid = indices.shift(); | |
for ( var i = 0; i < indices.length; i ++ ) { | |
indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) ); | |
} | |
data.indices = indices; | |
} | |
data.id = id; | |
data.sid = sid; | |
data.arraySyntax = arraySyntax; | |
data.memberSyntax = memberSyntax; | |
data.sampler = parseId( xml.getAttribute( 'source' ) ); | |
return data; | |
} | |
function buildAnimation( data ) { | |
var tracks = []; | |
var channels = data.channels; | |
var samplers = data.samplers; | |
var sources = data.sources; | |
for ( var target in channels ) { | |
if ( channels.hasOwnProperty( target ) ) { | |
var channel = channels[ target ]; | |
var sampler = samplers[ channel.sampler ]; | |
var inputId = sampler.inputs.INPUT; | |
var outputId = sampler.inputs.OUTPUT; | |
var inputSource = sources[ inputId ]; | |
var outputSource = sources[ outputId ]; | |
var animation = buildAnimationChannel( channel, inputSource, outputSource ); | |
createKeyframeTracks( animation, tracks ); | |
} | |
} | |
return tracks; | |
} | |
function getAnimation( id ) { | |
return getBuild( library.animations[ id ], buildAnimation ); | |
} | |
function buildAnimationChannel( channel, inputSource, outputSource ) { | |
var node = library.nodes[ channel.id ]; | |
var object3D = getNode( node.id ); | |
var transform = node.transforms[ channel.sid ]; | |
var defaultMatrix = node.matrix.clone().transpose(); | |
var time, stride; | |
var i, il, j, jl; | |
var data = {}; | |
// the collada spec allows the animation of data in various ways. | |
// depending on the transform type (matrix, translate, rotate, scale), we execute different logic | |
switch ( transform ) { | |
case 'matrix': | |
for ( i = 0, il = inputSource.array.length; i < il; i ++ ) { | |
time = inputSource.array[ i ]; | |
stride = i * outputSource.stride; | |
if ( data[ time ] === undefined ) data[ time ] = {}; | |
if ( channel.arraySyntax === true ) { | |
var value = outputSource.array[ stride ]; | |
var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ]; | |
data[ time ][ index ] = value; | |
} else { | |
for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) { | |
data[ time ][ j ] = outputSource.array[ stride + j ]; | |
} | |
} | |
} | |
break; | |
case 'translate': | |
console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); | |
break; | |
case 'rotate': | |
console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); | |
break; | |
case 'scale': | |
console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); | |
break; | |
} | |
var keyframes = prepareAnimationData( data, defaultMatrix ); | |
var animation = { | |
name: object3D.uuid, | |
keyframes: keyframes | |
}; | |
return animation; | |
} | |
function prepareAnimationData( data, defaultMatrix ) { | |
var keyframes = []; | |
// transfer data into a sortable array | |
for ( var time in data ) { | |
keyframes.push( { time: parseFloat( time ), value: data[ time ] } ); | |
} | |
// ensure keyframes are sorted by time | |
keyframes.sort( ascending ); | |
// now we clean up all animation data, so we can use them for keyframe tracks | |
for ( var i = 0; i < 16; i ++ ) { | |
transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] ); | |
} | |
return keyframes; | |
// array sort function | |
function ascending( a, b ) { | |
return a.time - b.time; | |
} | |
} | |
var position = new THREE.Vector3(); | |
var scale = new THREE.Vector3(); | |
var quaternion = new THREE.Quaternion(); | |
function createKeyframeTracks( animation, tracks ) { | |
var keyframes = animation.keyframes; | |
var name = animation.name; | |
var times = []; | |
var positionData = []; | |
var quaternionData = []; | |
var scaleData = []; | |
for ( var i = 0, l = keyframes.length; i < l; i ++ ) { | |
var keyframe = keyframes[ i ]; | |
var time = keyframe.time; | |
var value = keyframe.value; | |
matrix.fromArray( value ).transpose(); | |
matrix.decompose( position, quaternion, scale ); | |
times.push( time ); | |
positionData.push( position.x, position.y, position.z ); | |
quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w ); | |
scaleData.push( scale.x, scale.y, scale.z ); | |
} | |
if ( positionData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.position', times, positionData ) ); | |
if ( quaternionData.length > 0 ) tracks.push( new THREE.QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) ); | |
if ( scaleData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.scale', times, scaleData ) ); | |
return tracks; | |
} | |
function transformAnimationData( keyframes, property, defaultValue ) { | |
var keyframe; | |
var empty = true; | |
var i, l; | |
// check, if values of a property are missing in our keyframes | |
for ( i = 0, l = keyframes.length; i < l; i ++ ) { | |
keyframe = keyframes[ i ]; | |
if ( keyframe.value[ property ] === undefined ) { | |
keyframe.value[ property ] = null; // mark as missing | |
} else { | |
empty = false; | |
} | |
} | |
if ( empty === true ) { | |
// no values at all, so we set a default value | |
for ( i = 0, l = keyframes.length; i < l; i ++ ) { | |
keyframe = keyframes[ i ]; | |
keyframe.value[ property ] = defaultValue; | |
} | |
} else { | |
// filling gaps | |
createMissingKeyframes( keyframes, property ); | |
} | |
} | |
function createMissingKeyframes( keyframes, property ) { | |
var prev, next; | |
for ( var i = 0, l = keyframes.length; i < l; i ++ ) { | |
var keyframe = keyframes[ i ]; | |
if ( keyframe.value[ property ] === null ) { | |
prev = getPrev( keyframes, i, property ); | |
next = getNext( keyframes, i, property ); | |
if ( prev === null ) { | |
keyframe.value[ property ] = next.value[ property ]; | |
continue; | |
} | |
if ( next === null ) { | |
keyframe.value[ property ] = prev.value[ property ]; | |
continue; | |
} | |
interpolate( keyframe, prev, next, property ); | |
} | |
} | |
} | |
function getPrev( keyframes, i, property ) { | |
while ( i >= 0 ) { | |
var keyframe = keyframes[ i ]; | |
if ( keyframe.value[ property ] !== null ) return keyframe; | |
i --; | |
} | |
return null; | |
} | |
function getNext( keyframes, i, property ) { | |
while ( i < keyframes.length ) { | |
var keyframe = keyframes[ i ]; | |
if ( keyframe.value[ property ] !== null ) return keyframe; | |
i ++; | |
} | |
return null; | |
} | |
function interpolate( key, prev, next, property ) { | |
if ( ( next.time - prev.time ) === 0 ) { | |
key.value[ property ] = prev.value[ property ]; | |
return; | |
} | |
key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ]; | |
} | |
// animation clips | |
function parseAnimationClip( xml ) { | |
var data = { | |
name: xml.getAttribute( 'id' ) || 'default', | |
start: parseFloat( xml.getAttribute( 'start' ) || 0 ), | |
end: parseFloat( xml.getAttribute( 'end' ) || 0 ), | |
animations: [] | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'instance_animation': | |
data.animations.push( parseId( child.getAttribute( 'url' ) ) ); | |
break; | |
} | |
} | |
library.clips[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function buildAnimationClip( data ) { | |
var tracks = []; | |
var name = data.name; | |
var duration = ( data.end - data.start ) || - 1; | |
var animations = data.animations; | |
for ( var i = 0, il = animations.length; i < il; i ++ ) { | |
var animationTracks = getAnimation( animations[ i ] ); | |
for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) { | |
tracks.push( animationTracks[ j ] ); | |
} | |
} | |
return new THREE.AnimationClip( name, duration, tracks ); | |
} | |
function getAnimationClip( id ) { | |
return getBuild( library.clips[ id ], buildAnimationClip ); | |
} | |
// controller | |
function parseController( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'skin': | |
// there is exactly one skin per controller | |
data.id = parseId( child.getAttribute( 'source' ) ); | |
data.skin = parseSkin( child ); | |
break; | |
case 'morph': | |
var c = child.getAttribute( 'source' ); | |
data.id = parseId( child.getAttribute( 'source' ) ); | |
data.morphs = parseMorphs( child ); | |
console.warn( 'THREE.ColladaLoader: Morph targets loading is not complete at all...' ); | |
break; | |
} | |
} | |
library.controllers[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseMorphs( xml ) { | |
var morphs = undefined; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i++ ) { | |
var child = xml.childNodes[ i ]; | |
switch ( child.nodeName ) { | |
case 'source': | |
for ( var j = 0, m = child.childNodes.length; j < m; j++ ) { | |
var subchild = child.childNodes[ j ]; | |
// console.log( subchild.nodeName ); | |
switch ( subchild.nodeName ) { | |
case 'IDREF_array': | |
morphs = subchild.textContent.split(" "); | |
default: | |
break; | |
} | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
// console.log( morphs ); | |
return morphs; | |
} | |
function parseSkin( xml ) { | |
var data = { | |
sources: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'bind_shape_matrix': | |
data.bindShapeMatrix = parseFloats( child.textContent ); | |
break; | |
case 'source': | |
var id = child.getAttribute( 'id' ); | |
data.sources[ id ] = parseSource( child ); | |
break; | |
case 'joints': | |
data.joints = parseJoints( child ); | |
break; | |
case 'vertex_weights': | |
data.vertexWeights = parseVertexWeights( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseJoints( xml ) { | |
var data = { | |
inputs: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'input': | |
var semantic = child.getAttribute( 'semantic' ); | |
var id = parseId( child.getAttribute( 'source' ) ); | |
data.inputs[ semantic ] = id; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseVertexWeights( xml ) { | |
var data = { | |
inputs: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'input': | |
var semantic = child.getAttribute( 'semantic' ); | |
var id = parseId( child.getAttribute( 'source' ) ); | |
var offset = parseInt( child.getAttribute( 'offset' ) ); | |
data.inputs[ semantic ] = { id: id, offset: offset }; | |
break; | |
case 'vcount': | |
data.vcount = parseInts( child.textContent ); | |
break; | |
case 'v': | |
data.v = parseInts( child.textContent ); | |
break; | |
} | |
} | |
return data; | |
} | |
function buildController( data ) { | |
var build = { | |
id: data.id | |
}; | |
var geometry = library.geometries[ build.id ]; | |
// console.log( build.id ); | |
if ( data.skin !== undefined ) { | |
build.skin = buildSkin( data.skin ); | |
// we enhance the 'sources' property of the corresponding geometry with our skin data | |
geometry.sources.skinIndices = build.skin.indices; | |
geometry.sources.skinWeights = build.skin.weights; | |
} | |
if ( data.morphs !== undefined ) { | |
geometry.morph_data = []; | |
console.log("buildController, creation of morphTargets for '" + build.id + "'"); | |
for( var i in data.morphs ) { | |
var key = data.morphs[ i ]; | |
if ( key == build.id ) { | |
console.log( "skipping " + key ); | |
continue; | |
} | |
for( var gid in library.geometries ) { | |
if ( gid == key ) { | |
var morph_geometry = library.geometries[ gid ]; | |
var morph = { | |
id : morph_geometry.id, | |
name : morph_geometry.name | |
}; | |
if ( morph_geometry.sources[ gid + "-positions" ] === undefined ) { | |
// mandatory to have positions to continue creating the morphs | |
console.log( "failed to transfer geometry id:" + gid + " in morphTarget name:" + morph.name ); | |
break; | |
} else { | |
morph.positions = { | |
name : morph_geometry.name, | |
array: morph_geometry.sources[ gid + "-positions" ].array, | |
stride: morph_geometry.sources[ gid + "-positions" ].stride | |
}; | |
} | |
if ( morph_geometry.sources[ gid + "-normals" ] === undefined ) { | |
console.log( "failed to transfer normals id:" + gid + " in morphNormal name:" + morph.name ); | |
} else { | |
morph.normals = { | |
name : morph_geometry.name, | |
array: morph_geometry.sources[ gid + "-normals" ].array, | |
stride: morph_geometry.sources[ gid + "-normals" ].stride | |
}; | |
} | |
if ( morph_geometry.sources[ gid + "-colors-Col" ] === undefined ) { | |
console.log( "failed to transfer colors id:" + gid + " in morphNormal name:" + morph.name ); | |
} else { | |
morph.colors = { | |
name : morph_geometry.name, | |
array: morph_geometry.sources[ gid + "-colors-Col" ].array, | |
stride: morph_geometry.sources[ gid + "-colors-Col" ].stride | |
}; | |
} | |
geometry.morph_data.push( morph ); | |
console.log( "transfer of '" + gid + "' in morphTarget '" + morph.name + "'" ); | |
delete library.geometries[ gid ]; | |
break; | |
} | |
} | |
} | |
// console.log( library.geometries ); | |
} | |
console.log( "buildController > Implement morph targets controller" ); | |
return build; | |
} | |
function buildSkin( data ) { | |
var BONE_LIMIT = 4; | |
var build = { | |
joints: [], // this must be an array to preserve the joint order | |
indices: { | |
array: [], | |
stride: BONE_LIMIT | |
}, | |
weights: { | |
array: [], | |
stride: BONE_LIMIT | |
} | |
}; | |
var sources = data.sources; | |
var vertexWeights = data.vertexWeights; | |
var vcount = vertexWeights.vcount; | |
var v = vertexWeights.v; | |
var jointOffset = vertexWeights.inputs.JOINT.offset; | |
var weightOffset = vertexWeights.inputs.WEIGHT.offset; | |
var jointSource = data.sources[ data.joints.inputs.JOINT ]; | |
var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ]; | |
var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array; | |
var stride = 0; | |
var i, j, l; | |
// procces skin data for each vertex | |
for ( i = 0, l = vcount.length; i < l; i ++ ) { | |
var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex | |
var vertexSkinData = []; | |
for ( j = 0; j < jointCount; j ++ ) { | |
var skinIndex = v[ stride + jointOffset ]; | |
var weightId = v[ stride + weightOffset ]; | |
var skinWeight = weights[ weightId ]; | |
vertexSkinData.push( { index: skinIndex, weight: skinWeight } ); | |
stride += 2; | |
} | |
// we sort the joints in descending order based on the weights. | |
// this ensures, we only procced the most important joints of the vertex | |
vertexSkinData.sort( descending ); | |
// now we provide for each vertex a set of four index and weight values. | |
// the order of the skin data matches the order of vertices | |
for ( j = 0; j < BONE_LIMIT; j ++ ) { | |
var d = vertexSkinData[ j ]; | |
if ( d !== undefined ) { | |
build.indices.array.push( d.index ); | |
build.weights.array.push( d.weight ); | |
} else { | |
build.indices.array.push( 0 ); | |
build.weights.array.push( 0 ); | |
} | |
} | |
} | |
// setup bind matrix | |
build.bindMatrix = new THREE.Matrix4().fromArray( data.bindShapeMatrix ).transpose(); | |
// process bones and inverse bind matrix data | |
for ( i = 0, l = jointSource.array.length; i < l; i ++ ) { | |
var name = jointSource.array[ i ]; | |
var boneInverse = new THREE.Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose(); | |
build.joints.push( { name: name, boneInverse: boneInverse } ); | |
} | |
return build; | |
// array sort function | |
function descending( a, b ) { | |
return b.weight - a.weight; | |
} | |
} | |
function getController( id ) { | |
return getBuild( library.controllers[ id ], buildController ); | |
} | |
// image | |
function parseImage( xml ) { | |
var data = { | |
init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent | |
}; | |
library.images[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function buildImage( data ) { | |
if ( data.build !== undefined ) return data.build; | |
return data.init_from; | |
} | |
function getImage( id ) { | |
return getBuild( library.images[ id ], buildImage ); | |
} | |
// effect | |
function parseEffect( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'profile_COMMON': | |
data.profile = parseEffectProfileCOMMON( child ); | |
break; | |
} | |
} | |
library.effects[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseEffectProfileCOMMON( xml ) { | |
var data = { | |
surfaces: {}, | |
samplers: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'newparam': | |
parseEffectNewparam( child, data ); | |
break; | |
case 'technique': | |
data.technique = parseEffectTechnique( child ); | |
break; | |
case 'extra': | |
data.extra = parseEffectExtra( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectNewparam( xml, data ) { | |
var sid = xml.getAttribute( 'sid' ); | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'surface': | |
data.surfaces[ sid ] = parseEffectSurface( child ); | |
break; | |
case 'sampler2D': | |
data.samplers[ sid ] = parseEffectSampler( child ); | |
break; | |
} | |
} | |
} | |
function parseEffectSurface( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'init_from': | |
data.init_from = child.textContent; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectSampler( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'source': | |
data.source = child.textContent; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectTechnique( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'constant': | |
case 'lambert': | |
case 'blinn': | |
case 'phong': | |
data.type = child.nodeName; | |
data.parameters = parseEffectParameters( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectParameters( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'emission': | |
case 'diffuse': | |
case 'specular': | |
case 'shininess': | |
case 'transparency': | |
data[ child.nodeName ] = parseEffectParameter( child ); | |
break; | |
case 'transparent': | |
data[ child.nodeName ] = { | |
opaque: child.getAttribute( 'opaque' ), | |
data: parseEffectParameter( child ) | |
}; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectParameter( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'color': | |
data[ child.nodeName ] = parseFloats( child.textContent ); | |
break; | |
case 'float': | |
data[ child.nodeName ] = parseFloat( child.textContent ); | |
break; | |
case 'texture': | |
data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) }; | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectParameterTexture( xml ) { | |
var data = { | |
technique: {} | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'extra': | |
parseEffectParameterTextureExtra( child, data ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectParameterTextureExtra( xml, data ) { | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'technique': | |
parseEffectParameterTextureExtraTechnique( child, data ); | |
break; | |
} | |
} | |
} | |
function parseEffectParameterTextureExtraTechnique( xml, data ) { | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'repeatU': | |
case 'repeatV': | |
case 'offsetU': | |
case 'offsetV': | |
data.technique[ child.nodeName ] = parseFloat( child.textContent ); | |
break; | |
case 'wrapU': | |
case 'wrapV': | |
// some files have values for wrapU/wrapV which become NaN via parseInt | |
if ( child.textContent.toUpperCase() === 'TRUE' ) { | |
data.technique[ child.nodeName ] = 1; | |
} else if ( child.textContent.toUpperCase() === 'FALSE' ) { | |
data.technique[ child.nodeName ] = 0; | |
} else { | |
data.technique[ child.nodeName ] = parseInt( child.textContent ); | |
} | |
break; | |
} | |
} | |
} | |
function parseEffectExtra( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'technique': | |
data.technique = parseEffectExtraTechnique( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseEffectExtraTechnique( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'double_sided': | |
data[ child.nodeName ] = parseInt( child.textContent ); | |
break; | |
} | |
} | |
return data; | |
} | |
function buildEffect( data ) { | |
return data; | |
} | |
function getEffect( id ) { | |
return getBuild( library.effects[ id ], buildEffect ); | |
} | |
// material | |
function parseMaterial( xml ) { | |
var data = { | |
name: xml.getAttribute( 'name' ) | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'instance_effect': | |
data.url = parseId( child.getAttribute( 'url' ) ); | |
break; | |
} | |
} | |
library.materials[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function buildMaterial( data ) { | |
var effect = getEffect( data.url ); | |
var technique = effect.profile.technique; | |
var extra = effect.profile.extra; | |
var material; | |
switch ( technique.type ) { | |
case 'phong': | |
case 'blinn': | |
material = new THREE.MeshPhongMaterial(); | |
break; | |
case 'lambert': | |
material = new THREE.MeshLambertMaterial(); | |
break; | |
default: | |
material = new THREE.MeshBasicMaterial(); | |
break; | |
} | |
material.name = data.name; | |
function getTexture( textureObject ) { | |
var sampler = effect.profile.samplers[ textureObject.id ]; | |
var image; | |
// get image | |
if ( sampler !== undefined ) { | |
var surface = effect.profile.surfaces[ sampler.source ]; | |
image = getImage( surface.init_from ); | |
} else { | |
console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' ); | |
image = getImage( textureObject.id ); | |
} | |
// create texture if image is avaiable | |
if ( image !== undefined ) { | |
var texture = textureLoader.load( image ); | |
var extra = textureObject.extra; | |
if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) { | |
var technique = extra.technique; | |
texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; | |
texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; | |
texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 ); | |
texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 ); | |
} else { | |
texture.wrapS = THREE.RepeatWrapping; | |
texture.wrapT = THREE.RepeatWrapping; | |
} | |
return texture; | |
} else { | |
console.error( 'THREE.ColladaLoader: Unable to load texture with ID:', textureObject.id ); | |
return null; | |
} | |
} | |
var parameters = technique.parameters; | |
for ( var key in parameters ) { | |
var parameter = parameters[ key ]; | |
switch ( key ) { | |
case 'diffuse': | |
if ( parameter.color ) material.color.fromArray( parameter.color ); | |
if ( parameter.texture ) material.map = getTexture( parameter.texture ); | |
break; | |
case 'specular': | |
if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color ); | |
if ( parameter.texture ) material.specularMap = getTexture( parameter.texture ); | |
break; | |
case 'shininess': | |
if ( parameter.float && material.shininess ) | |
material.shininess = parameter.float; | |
break; | |
case 'emission': | |
if ( parameter.color && material.emissive ) | |
material.emissive.fromArray( parameter.color ); | |
break; | |
} | |
} | |
// | |
var transparent = parameters[ 'transparent' ]; | |
var transparency = parameters[ 'transparency' ]; | |
// <transparency> does not exist but <transparent> | |
if ( transparency === undefined && transparent ) { | |
transparency = { | |
float: 1 | |
}; | |
} | |
// <transparent> does not exist but <transparency> | |
if ( transparent === undefined && transparency ) { | |
transparent = { | |
opaque: 'A_ONE', | |
data: { | |
color: [ 1, 1, 1, 1 ] | |
} }; | |
} | |
if ( transparent && transparency ) { | |
// handle case if a texture exists but no color | |
if ( transparent.data.texture ) { | |
material.alphaMap = getTexture( transparent.data.texture ); | |
material.transparent = true; | |
} else { | |
var color = transparent.data.color; | |
switch ( transparent.opaque ) { | |
case 'A_ONE': | |
material.opacity = color[ 3 ] * transparency.float; | |
break; | |
case 'RGB_ZERO': | |
material.opacity = 1 - ( color[ 0 ] * transparency.float ); | |
break; | |
case 'A_ZERO': | |
material.opacity = 1 - ( color[ 3 ] * transparency.float ); | |
break; | |
case 'RGB_ONE': | |
material.opacity = color[ 0 ] * transparency.float; | |
break; | |
default: | |
console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque ); | |
} | |
if ( material.opacity < 1 ) material.transparent = true; | |
} | |
} | |
// | |
if ( extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1 ) { | |
material.side = THREE.DoubleSide; | |
} | |
return material; | |
} | |
function getMaterial( id ) { | |
return getBuild( library.materials[ id ], buildMaterial ); | |
} | |
// camera | |
function parseCamera( xml ) { | |
var data = { | |
name: xml.getAttribute( 'name' ) | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'optics': | |
data.optics = parseCameraOptics( child ); | |
break; | |
} | |
} | |
library.cameras[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseCameraOptics( xml ) { | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
switch ( child.nodeName ) { | |
case 'technique_common': | |
return parseCameraTechnique( child ); | |
} | |
} | |
return {}; | |
} | |
function parseCameraTechnique( xml ) { | |
var data = {}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
switch ( child.nodeName ) { | |
case 'perspective': | |
case 'orthographic': | |
data.technique = child.nodeName; | |
data.parameters = parseCameraParameters( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseCameraParameters( xml ) { | |
var data = {}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
switch ( child.nodeName ) { | |
case 'xfov': | |
case 'yfov': | |
case 'xmag': | |
case 'ymag': | |
case 'znear': | |
case 'zfar': | |
case 'aspect_ratio': | |
data[ child.nodeName ] = parseFloat( child.textContent ); | |
break; | |
} | |
} | |
return data; | |
} | |
function buildCamera( data ) { | |
var camera; | |
switch ( data.optics.technique ) { | |
case 'perspective': | |
camera = new THREE.PerspectiveCamera( | |
data.optics.parameters.yfov, | |
data.optics.parameters.aspect_ratio, | |
data.optics.parameters.znear, | |
data.optics.parameters.zfar | |
); | |
break; | |
case 'orthographic': | |
var ymag = data.optics.parameters.ymag; | |
var xmag = data.optics.parameters.xmag; | |
var aspectRatio = data.optics.parameters.aspect_ratio; | |
xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag; | |
ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag; | |
xmag *= 0.5; | |
ymag *= 0.5; | |
camera = new THREE.OrthographicCamera( | |
- xmag, xmag, ymag, - ymag, // left, right, top, bottom | |
data.optics.parameters.znear, | |
data.optics.parameters.zfar | |
); | |
break; | |
default: | |
camera = new THREE.PerspectiveCamera(); | |
break; | |
} | |
camera.name = data.name; | |
return camera; | |
} | |
function getCamera( id ) { | |
var data = library.cameras[ id ]; | |
if ( data !== undefined ) { | |
return getBuild( data, buildCamera ); | |
} | |
console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id ); | |
return null; | |
} | |
// light | |
function parseLight( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'technique_common': | |
data = parseLightTechnique( child ); | |
break; | |
} | |
} | |
library.lights[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseLightTechnique( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'directional': | |
case 'point': | |
case 'spot': | |
case 'ambient': | |
data.technique = child.nodeName; | |
data.parameters = parseLightParameters( child ); | |
} | |
} | |
return data; | |
} | |
function parseLightParameters( xml ) { | |
var data = {}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'color': | |
var array = parseFloats( child.textContent ); | |
data.color = new THREE.Color().fromArray( array ); | |
break; | |
case 'falloff_angle': | |
data.falloffAngle = parseFloat( child.textContent ); | |
break; | |
case 'quadratic_attenuation': | |
var f = parseFloat( child.textContent ); | |
data.distance = f ? Math.sqrt( 1 / f ) : 0; | |
break; | |
} | |
} | |
return data; | |
} | |
function buildLight( data ) { | |
var light; | |
switch ( data.technique ) { | |
case 'directional': | |
light = new THREE.DirectionalLight(); | |
break; | |
case 'point': | |
light = new THREE.PointLight(); | |
break; | |
case 'spot': | |
light = new THREE.SpotLight(); | |
break; | |
case 'ambient': | |
light = new THREE.AmbientLight(); | |
break; | |
} | |
if ( data.parameters.color ) light.color.copy( data.parameters.color ); | |
if ( data.parameters.distance ) light.distance = data.parameters.distance; | |
return light; | |
} | |
function getLight( id ) { | |
var data = library.lights[ id ]; | |
if ( data !== undefined ) { | |
return getBuild( data, buildLight ); | |
} | |
console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id ); | |
return null; | |
} | |
// geometry | |
function parseGeometry( xml ) { | |
var data = { | |
id: xml.getAttribute( 'id' ), | |
name: xml.getAttribute( 'name' ), | |
sources: {}, | |
vertices: {}, | |
primitives: [] | |
}; | |
// console.log( "parseGeometry: " + data.id + " | " + data.name ); | |
var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ]; | |
// the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep | |
if ( mesh === undefined ) return; | |
for ( var i = 0; i < mesh.childNodes.length; i ++ ) { | |
var child = mesh.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
var id = child.getAttribute( 'id' ); | |
switch ( child.nodeName ) { | |
case 'source': | |
data.sources[ id ] = parseSource( child ); | |
break; | |
case 'vertices': | |
// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; | |
data.vertices = parseGeometryVertices( child ); | |
break; | |
case 'polygons': | |
console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName ); | |
break; | |
case 'lines': | |
case 'linestrips': | |
case 'polylist': | |
case 'triangles': | |
data.primitives.push( parseGeometryPrimitive( child ) ); | |
break; | |
default: | |
console.log( child ); | |
} | |
} | |
library.geometries[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function parseSource( xml ) { | |
var data = { | |
array: [], | |
stride: 3 | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'float_array': | |
data.array = parseFloats( child.textContent ); | |
break; | |
case 'Name_array': | |
data.array = parseStrings( child.textContent ); | |
break; | |
case 'technique_common': | |
var accessor = getElementsByTagName( child, 'accessor' )[ 0 ]; | |
if ( accessor !== undefined ) { | |
data.stride = parseInt( accessor.getAttribute( 'stride' ) ); | |
} | |
break; | |
} | |
} | |
return data; | |
} | |
function parseGeometryVertices( xml ) { | |
var data = {}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) ); | |
} | |
return data; | |
} | |
function parseGeometryPrimitive( xml ) { | |
var primitive = { | |
type: xml.nodeName, | |
material: xml.getAttribute( 'material' ), | |
count: parseInt( xml.getAttribute( 'count' ) ), | |
inputs: {}, | |
stride: 0, | |
hasUV: false | |
}; | |
for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'input': | |
var id = parseId( child.getAttribute( 'source' ) ); | |
var semantic = child.getAttribute( 'semantic' ); | |
var offset = parseInt( child.getAttribute( 'offset' ) ); | |
primitive.inputs[ semantic ] = { id: id, offset: offset }; | |
primitive.stride = Math.max( primitive.stride, offset + 1 ); | |
if ( semantic === 'TEXCOORD' ) primitive.hasUV = true; | |
break; | |
case 'vcount': | |
primitive.vcount = parseInts( child.textContent ); | |
break; | |
case 'p': | |
primitive.p = parseInts( child.textContent ); | |
break; | |
} | |
} | |
return primitive; | |
} | |
function groupPrimitives( primitives ) { | |
var build = {}; | |
for ( var i = 0; i < primitives.length; i ++ ) { | |
var primitive = primitives[ i ]; | |
if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = []; | |
build[ primitive.type ].push( primitive ); | |
} | |
return build; | |
} | |
function checkUVCoordinates( primitives ) { | |
var count = 0; | |
for ( var i = 0, l = primitives.length; i < l; i ++ ) { | |
var primitive = primitives[ i ]; | |
if ( primitive.hasUV === true ) { | |
count ++; | |
} | |
} | |
if ( count > 0 && count < primitives.length ) { | |
primitives.uvsNeedsFix = true; | |
} | |
} | |
function buildGeometry( data ) { | |
var build = {}; | |
var sources = data.sources; | |
var vertices = data.vertices; | |
var primitives = data.primitives; | |
if ( primitives.length === 0 ) return {}; | |
// our goal is to create one buffer geometry for a single type of primitives | |
// first, we group all primitives by their type | |
var groupedPrimitives = groupPrimitives( primitives ); | |
for ( var type in groupedPrimitives ) { | |
var primitiveType = groupedPrimitives[ type ]; | |
// second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) | |
checkUVCoordinates( primitiveType ); | |
// third, create a buffer geometry for each type of primitives | |
build[ type ] = buildGeometryType( primitiveType, sources, vertices ); | |
console.log( "*********** buildGeometry, build[ " + type + " ] ***********" ); | |
console.log( build[ type ].data ); | |
} | |
if ( build[ "polylist" ] !== undefined && data.morph_data !== undefined ) { | |
console.log( "** Implementation might be hazardous at this point! **" ); | |
console.log( data ); | |
// getting the first primitive | |
var primitive = data.primitives[0]; | |
var poly = build[ "polylist" ].data; | |
var vertex_offset = primitive.inputs.VERTEX.offset; | |
var normal_offset = primitive.inputs.NORMAL.offset; | |
var color_offset = primitive.inputs.COLOR.offset; | |
var total_offset = 9; | |
poly.morphAttributes = {}; | |
console.log( "faces " + primitive.count ); | |
console.log( "vertices " + ( primitive.count * 3 ) ); | |
poly.morphAttributes.position = []; | |
poly.morphAttributes.normal = []; | |
for ( var i in data.morph_data ) { | |
var dpos = data.morph_data[i].positions; | |
var dnorms = data.morph_data[i].normals; | |
var position_array = new Float32Array( primitive.count * 3 ); | |
var normal_array = undefined; | |
if ( dnorms !== undefined ) { | |
normal_array = new Float32Array( primitive.count * 3 ); | |
} | |
for ( | |
var v = 0, id = 0; | |
v < primitive.count * total_offset; | |
v += total_offset, ++id | |
) { | |
for ( var axis = 0; axis < 3; ++axis ) { | |
position_array[ id ] = dpos.array[ v + vertex_offset * 3 + axis ]; | |
if ( dnorms !== undefined ) { | |
normal_array[ id ] = dnorms.array[ v + normal_offset * 3 + axis ]; | |
} | |
} | |
} | |
var mattr_name = data.morph_data[i].name; | |
var buffattr = new THREE.Float32BufferAttribute( | |
position_array, | |
dpos.stride | |
); | |
buffattr.name = mattr_name; | |
poly.morphAttributes.position.push( buffattr ); | |
if ( dnorms !== undefined ) { | |
buffattr = new THREE.Float32BufferAttribute( | |
normal_array, | |
dnorms.stride | |
); | |
buffattr.name = mattr_name; | |
poly.morphAttributes.normal.push( buffattr ); | |
} | |
// console.log( data.morph_data[i].id ); | |
console.log( "morphAttributes > " + mattr_name ); | |
// console.log( poly.morphAttributes[ mattr_name ] ); | |
} | |
} | |
return build; | |
} | |
function buildMorphTargets( obj, morphTargets ) { | |
} | |
function buildGeometryType( primitives, sources, vertices ) { | |
var build = {}; | |
var position = { array: [], stride: 0 }; | |
var normal = { array: [], stride: 0 }; | |
var uv = { array: [], stride: 0 }; | |
var color = { array: [], stride: 0 }; | |
var skinIndex = { array: [], stride: 4 }; | |
var skinWeight = { array: [], stride: 4 }; | |
var geometry = new THREE.BufferGeometry(); | |
var materialKeys = []; | |
var start = 0, count = 0; | |
for ( var p = 0; p < primitives.length; p ++ ) { | |
var primitive = primitives[ p ]; | |
var inputs = primitive.inputs; | |
var triangleCount = 1; | |
if ( primitive.vcount && primitive.vcount[ 0 ] === 4 ) { | |
triangleCount = 2; // one quad -> two triangles | |
} | |
// groups | |
if ( primitive.type === 'lines' || primitive.type === 'linestrips' ) { | |
count = primitive.count * 2; | |
} else { | |
count = primitive.count * 3 * triangleCount; | |
} | |
geometry.addGroup( start, count, p ); | |
start += count; | |
// material | |
if ( primitive.material ) { | |
materialKeys.push( primitive.material ); | |
} | |
// geometry data | |
for ( var name in inputs ) { | |
var input = inputs[ name ]; | |
switch ( name ) { | |
case 'VERTEX': | |
for ( var key in vertices ) { | |
var id = vertices[ key ]; | |
switch ( key ) { | |
case 'POSITION': | |
var prevLength = position.array.length; | |
buildGeometryData( primitive, sources[ id ], input.offset, position.array ); | |
position.stride = sources[ id ].stride; | |
if ( sources.skinWeights && sources.skinIndices ) { | |
buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array ); | |
buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array ); | |
} | |
// see #3803 | |
if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) { | |
var count = ( position.array.length - prevLength ) / position.stride; | |
for ( var i = 0; i < count; i ++ ) { | |
// fill missing uv coordinates | |
uv.array.push( 0, 0 ); | |
} | |
} | |
break; | |
case 'NORMAL': | |
buildGeometryData( primitive, sources[ id ], input.offset, normal.array ); | |
normal.stride = sources[ id ].stride; | |
break; | |
case 'COLOR': | |
buildGeometryData( primitive, sources[ id ], input.offset, color.array ); | |
color.stride = sources[ id ].stride; | |
break; | |
case 'TEXCOORD': | |
buildGeometryData( primitive, sources[ id ], input.offset, uv.array ); | |
uv.stride = sources[ id ].stride; | |
break; | |
default: | |
console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key ); | |
} | |
} | |
break; | |
case 'NORMAL': | |
buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array ); | |
normal.stride = sources[ input.id ].stride; | |
break; | |
case 'COLOR': | |
buildGeometryData( primitive, sources[ input.id ], input.offset, color.array ); | |
color.stride = sources[ input.id ].stride; | |
break; | |
case 'TEXCOORD': | |
buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array ); | |
uv.stride = sources[ input.id ].stride; | |
break; | |
default: | |
console.log( "buildGeometryType, case " + name + " ??? " ); | |
break; | |
} | |
} | |
} | |
// build geometry | |
// console.log( "***************** buildGeometryType, geometry *****************" ); | |
// console.log( geometry ); | |
if ( position.array.length > 0 ) geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( position.array, position.stride ) ); | |
if ( normal.array.length > 0 ) geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normal.array, normal.stride ) ); | |
if ( color.array.length > 0 ) geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( color.array, color.stride ) ); | |
if ( uv.array.length > 0 ) geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uv.array, uv.stride ) ); | |
if ( skinIndex.array.length > 0 ) geometry.addAttribute( 'skinIndex', new THREE.Float32BufferAttribute( skinIndex.array, skinIndex.stride ) ); | |
if ( skinWeight.array.length > 0 ) geometry.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeight.array, skinWeight.stride ) ); | |
build.data = geometry; | |
build.type = primitives[ 0 ].type; | |
build.materialKeys = materialKeys; | |
return build; | |
} | |
function buildGeometryData( primitive, source, offset, array ) { | |
var indices = primitive.p; | |
var stride = primitive.stride; | |
var vcount = primitive.vcount; | |
function pushVector( i ) { | |
var index = indices[ i + offset ] * sourceStride; | |
var length = index + sourceStride; | |
for ( ; index < length; index ++ ) { | |
array.push( sourceArray[ index ] ); | |
} | |
} | |
var maxcount = 0; | |
var sourceArray = source.array; | |
var sourceStride = source.stride; | |
if ( primitive.vcount !== undefined ) { | |
var index = 0; | |
for ( var i = 0, l = vcount.length; i < l; i ++ ) { | |
var count = vcount[ i ]; | |
if ( count === 4 ) { | |
var a = index + stride * 0; | |
var b = index + stride * 1; | |
var c = index + stride * 2; | |
var d = index + stride * 3; | |
pushVector( a ); pushVector( b ); pushVector( d ); | |
pushVector( b ); pushVector( c ); pushVector( d ); | |
} else if ( count === 3 ) { | |
var a = index + stride * 0; | |
var b = index + stride * 1; | |
var c = index + stride * 2; | |
pushVector( a ); pushVector( b ); pushVector( c ); | |
} else { | |
maxcount = Math.max( maxcount, count ); | |
} | |
index += stride * count; | |
} | |
if ( maxcount > 0 ) { | |
console.log( 'THREE.ColladaLoader: Geometry has faces with more than 4 vertices.' ); | |
} | |
} else { | |
for ( var i = 0, l = indices.length; i < l; i += stride ) { | |
pushVector( i ); | |
} | |
} | |
} | |
function getGeometry( id ) { | |
var obj = getBuild( library.geometries[ id ], buildGeometry ); | |
console.log( "******** getGeometry " + id + " ********* " ); | |
console.log( obj ); | |
return obj; | |
} | |
// kinematics | |
function parseKinematicsModel( xml ) { | |
var data = { | |
name: xml.getAttribute( 'name' ) || '', | |
joints: {}, | |
links: [] | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'technique_common': | |
parseKinematicsTechniqueCommon( child, data ); | |
break; | |
} | |
} | |
library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function buildKinematicsModel( data ) { | |
if ( data.build !== undefined ) return data.build; | |
return data; | |
} | |
function getKinematicsModel( id ) { | |
return getBuild( library.kinematicsModels[ id ], buildKinematicsModel ); | |
} | |
function parseKinematicsTechniqueCommon( xml, data ) { | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'joint': | |
data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child ); | |
break; | |
case 'link': | |
data.links.push( parseKinematicsLink( child ) ); | |
break; | |
} | |
} | |
} | |
function parseKinematicsJoint( xml ) { | |
var data; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'prismatic': | |
case 'revolute': | |
data = parseKinematicsJointParameter( child ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseKinematicsJointParameter( xml, data ) { | |
var data = { | |
sid: xml.getAttribute( 'sid' ), | |
name: xml.getAttribute( 'name' ) || '', | |
axis: new THREE.Vector3(), | |
limits: { | |
min: 0, | |
max: 0 | |
}, | |
type: xml.nodeName, | |
static: false, | |
zeroPosition: 0, | |
middlePosition: 0 | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'axis': | |
var array = parseFloats( child.textContent ); | |
data.axis.fromArray( array ); | |
break; | |
case 'limits': | |
var max = child.getElementsByTagName( 'max' )[ 0 ]; | |
var min = child.getElementsByTagName( 'min' )[ 0 ]; | |
data.limits.max = parseFloat( max.textContent ); | |
data.limits.min = parseFloat( min.textContent ); | |
break; | |
} | |
} | |
// if min is equal to or greater than max, consider the joint static | |
if ( data.limits.min >= data.limits.max ) { | |
data.static = true; | |
} | |
// calculate middle position | |
data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0; | |
return data; | |
} | |
function parseKinematicsLink( xml ) { | |
var data = { | |
sid: xml.getAttribute( 'sid' ), | |
name: xml.getAttribute( 'name' ) || '', | |
attachments: [], | |
transforms: [] | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'attachment_full': | |
data.attachments.push( parseKinematicsAttachment( child ) ); | |
break; | |
case 'matrix': | |
case 'translate': | |
case 'rotate': | |
data.transforms.push( parseKinematicsTransform( child ) ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseKinematicsAttachment( xml ) { | |
var data = { | |
joint: xml.getAttribute( 'joint' ).split( '/' ).pop(), | |
transforms: [], | |
links: [] | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'link': | |
data.links.push( parseKinematicsLink( child ) ); | |
break; | |
case 'matrix': | |
case 'translate': | |
case 'rotate': | |
data.transforms.push( parseKinematicsTransform( child ) ); | |
break; | |
} | |
} | |
return data; | |
} | |
function parseKinematicsTransform( xml ) { | |
var data = { | |
type: xml.nodeName | |
}; | |
var array = parseFloats( xml.textContent ); | |
switch ( data.type ) { | |
case 'matrix': | |
data.obj = new THREE.Matrix4(); | |
data.obj.fromArray( array ).transpose(); | |
break; | |
case 'translate': | |
data.obj = new THREE.Vector3(); | |
data.obj.fromArray( array ); | |
break; | |
case 'rotate': | |
data.obj = new THREE.Vector3(); | |
data.obj.fromArray( array ); | |
data.angle = THREE.Math.degToRad( array[ 3 ] ); | |
break; | |
} | |
return data; | |
} | |
function parseKinematicsScene( xml ) { | |
var data = { | |
bindJointAxis: [] | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'bind_joint_axis': | |
data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) ); | |
break; | |
} | |
} | |
library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data; | |
} | |
function parseKinematicsBindJointAxis( xml ) { | |
var data = { | |
target: xml.getAttribute( 'target' ).split( '/' ).pop() | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'axis': | |
var param = child.getElementsByTagName( 'param' )[ 0 ]; | |
data.axis = param.textContent; | |
var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ]; | |
data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 ); | |
break; | |
} | |
} | |
return data; | |
} | |
function buildKinematicsScene( data ) { | |
if ( data.build !== undefined ) return data.build; | |
return data; | |
} | |
function getKinematicsScene( id ) { | |
return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene ); | |
} | |
function setupKinematics() { | |
var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ]; | |
var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ]; | |
var visualSceneId = Object.keys( library.visualScenes )[ 0 ]; | |
if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return; | |
var kinematicsModel = getKinematicsModel( kinematicsModelId ); | |
var kinematicsScene = getKinematicsScene( kinematicsSceneId ); | |
var visualScene = getVisualScene( visualSceneId ); | |
var bindJointAxis = kinematicsScene.bindJointAxis; | |
var jointMap = {}; | |
for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) { | |
var axis = bindJointAxis[ i ]; | |
// the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' | |
var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' ); | |
if ( targetElement ) { | |
// get the parent of the transfrom element | |
var parentVisualElement = targetElement.parentElement; | |
// connect the joint of the kinematics model with the element in the visual scene | |
connect( axis.jointIndex, parentVisualElement ); | |
} | |
} | |
function connect( jointIndex, visualElement ) { | |
var visualElementName = visualElement.getAttribute( 'name' ); | |
var joint = kinematicsModel.joints[ jointIndex ]; | |
visualScene.traverse( function ( object ) { | |
if ( object.name === visualElementName ) { | |
jointMap[ jointIndex ] = { | |
object: object, | |
transforms: buildTransformList( visualElement ), | |
joint: joint, | |
position: joint.zeroPosition | |
}; | |
} | |
} ); | |
} | |
var m0 = new THREE.Matrix4(); | |
kinematics = { | |
joints: kinematicsModel && kinematicsModel.joints, | |
getJointValue: function ( jointIndex ) { | |
var jointData = jointMap[ jointIndex ]; | |
if ( jointData ) { | |
return jointData.position; | |
} else { | |
console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' ); | |
} | |
}, | |
setJointValue: function ( jointIndex, value ) { | |
var jointData = jointMap[ jointIndex ]; | |
if ( jointData ) { | |
var joint = jointData.joint; | |
if ( value > joint.limits.max || value < joint.limits.min ) { | |
console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' ); | |
} else if ( joint.static ) { | |
console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' ); | |
} else { | |
var object = jointData.object; | |
var axis = joint.axis; | |
var transforms = jointData.transforms; | |
matrix.identity(); | |
// each update, we have to apply all transforms in the correct order | |
for ( var i = 0; i < transforms.length; i ++ ) { | |
var transform = transforms[ i ]; | |
// if there is a connection of the transform node with a joint, apply the joint value | |
if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) { | |
switch ( joint.type ) { | |
case 'revolute': | |
matrix.multiply( m0.makeRotationAxis( axis, THREE.Math.degToRad( value ) ) ); | |
break; | |
case 'prismatic': | |
matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) ); | |
break; | |
default: | |
console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type ); | |
break; | |
} | |
} else { | |
switch ( transform.type ) { | |
case 'matrix': | |
matrix.multiply( transform.obj ); | |
break; | |
case 'translate': | |
matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); | |
break; | |
case 'scale': | |
matrix.scale( transform.obj ); | |
break; | |
case 'rotate': | |
matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) ); | |
break; | |
} | |
} | |
} | |
object.matrix.copy( matrix ); | |
object.matrix.decompose( object.position, object.quaternion, object.scale ); | |
jointMap[ jointIndex ].position = value; | |
} | |
} else { | |
console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' ); | |
} | |
} | |
}; | |
} | |
function buildTransformList( node ) { | |
var transforms = []; | |
var xml = collada.querySelector( '[id="' + node.id + '"]' ); | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'matrix': | |
var array = parseFloats( child.textContent ); | |
var matrix = new THREE.Matrix4().fromArray( array ).transpose(); | |
transforms.push( { | |
sid: child.getAttribute( 'sid' ), | |
type: child.nodeName, | |
obj: matrix | |
} ); | |
break; | |
case 'translate': | |
case 'scale': | |
var array = parseFloats( child.textContent ); | |
var vector = new THREE.Vector3().fromArray( array ); | |
transforms.push( { | |
sid: child.getAttribute( 'sid' ), | |
type: child.nodeName, | |
obj: vector | |
} ); | |
break; | |
case 'rotate': | |
var array = parseFloats( child.textContent ); | |
var vector = new THREE.Vector3().fromArray( array ); | |
var angle = THREE.Math.degToRad( array[ 3 ] ); | |
transforms.push( { | |
sid: child.getAttribute( 'sid' ), | |
type: child.nodeName, | |
obj: vector, | |
angle: angle | |
} ); | |
break; | |
} | |
} | |
return transforms; | |
} | |
// nodes | |
function prepareNodes( xml ) { | |
var elements = xml.getElementsByTagName( 'node' ); | |
// ensure all node elements have id attributes | |
for ( var i = 0; i < elements.length; i ++ ) { | |
var element = elements[ i ]; | |
if ( element.hasAttribute( 'id' ) === false ) { | |
element.setAttribute( 'id', generateId() ); | |
} | |
} | |
} | |
var matrix = new THREE.Matrix4(); | |
var vector = new THREE.Vector3(); | |
function parseNode( xml ) { | |
var data = { | |
name: xml.getAttribute( 'name' ) || '', | |
type: xml.getAttribute( 'type' ), | |
id: xml.getAttribute( 'id' ), | |
sid: xml.getAttribute( 'sid' ), | |
matrix: new THREE.Matrix4(), | |
nodes: [], | |
instanceCameras: [], | |
instanceControllers: [], | |
instanceLights: [], | |
instanceGeometries: [], | |
instanceNodes: [], | |
transforms: {} | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
if ( child.nodeType !== 1 ) continue; | |
switch ( child.nodeName ) { | |
case 'node': | |
data.nodes.push( child.getAttribute( 'id' ) ); | |
parseNode( child ); | |
break; | |
case 'instance_camera': | |
data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) ); | |
break; | |
case 'instance_controller': | |
data.instanceControllers.push( parseNodeInstance( child ) ); | |
break; | |
case 'instance_light': | |
data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) ); | |
break; | |
case 'instance_geometry': | |
data.instanceGeometries.push( parseNodeInstance( child ) ); | |
break; | |
case 'instance_node': | |
data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) ); | |
break; | |
case 'matrix': | |
var array = parseFloats( child.textContent ); | |
data.matrix.multiply( matrix.fromArray( array ).transpose() ); | |
data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; | |
break; | |
case 'translate': | |
var array = parseFloats( child.textContent ); | |
vector.fromArray( array ); | |
data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) ); | |
data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; | |
break; | |
case 'rotate': | |
var array = parseFloats( child.textContent ); | |
var angle = THREE.Math.degToRad( array[ 3 ] ); | |
data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) ); | |
data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; | |
break; | |
case 'scale': | |
var array = parseFloats( child.textContent ); | |
data.matrix.scale( vector.fromArray( array ) ); | |
data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; | |
break; | |
case 'extra': | |
break; | |
default: | |
console.log( child ); | |
} | |
} | |
library.nodes[ data.id ] = data; | |
return data; | |
} | |
function parseNodeInstance( xml ) { | |
var data = { | |
id: parseId( xml.getAttribute( 'url' ) ), | |
materials: {}, | |
skeletons: [] | |
}; | |
for ( var i = 0; i < xml.childNodes.length; i ++ ) { | |
var child = xml.childNodes[ i ]; | |
switch ( child.nodeName ) { | |
case 'bind_material': | |
var instances = child.getElementsByTagName( 'instance_material' ); | |
for ( var j = 0; j < instances.length; j ++ ) { | |
var instance = instances[ j ]; | |
var symbol = instance.getAttribute( 'symbol' ); | |
var target = instance.getAttribute( 'target' ); | |
data.materials[ symbol ] = parseId( target ); | |
} | |
break; | |
case 'skeleton': | |
data.skeletons.push( parseId( child.textContent ) ); | |
break; | |
default: | |
break; | |
} | |
} | |
return data; | |
} | |
function buildSkeleton( skeletons, joints ) { | |
var boneData = []; | |
var sortedBoneData = []; | |
var i, j, data; | |
// a skeleton can have multiple root bones. collada expresses this | |
// situtation with multiple "skeleton" tags per controller instance | |
for ( i = 0; i < skeletons.length; i ++ ) { | |
var skeleton = skeletons[ i ]; | |
var root; | |
if ( hasNode( skeleton ) ) { | |
root = getNode( skeleton ); | |
buildBoneHierarchy( root, joints, boneData ); | |
} else if ( hasVisualScene( skeleton ) ) { | |
// handle case where the skeleton refers to the visual scene (#13335) | |
var visualScene = library.visualScenes[ skeleton ]; | |
var children = visualScene.children; | |
for ( var j = 0; j < children.length; j ++ ) { | |
var child = children[ j ]; | |
if ( child.type === 'JOINT' ) { | |
var root = getNode( child.id ); | |
buildBoneHierarchy( root, joints, boneData ); | |
} | |
} | |
} else { | |
console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton ); | |
} | |
} | |
// sort bone data (the order is defined in the corresponding controller) | |
for ( i = 0; i < joints.length; i ++ ) { | |
for ( j = 0; j < boneData.length; j ++ ) { | |
data = boneData[ j ]; | |
if ( data.bone.name === joints[ i ].name ) { | |
sortedBoneData[ i ] = data; | |
data.processed = true; | |
break; | |
} | |
} | |
} | |
// add unprocessed bone data at the end of the list | |
for ( i = 0; i < boneData.length; i ++ ) { | |
data = boneData[ i ]; | |
if ( data.processed === false ) { | |
sortedBoneData.push( data ); | |
data.processed = true; | |
} | |
} | |
// setup arrays for skeleton creation | |
var bones = []; | |
var boneInverses = []; | |
for ( i = 0; i < sortedBoneData.length; i ++ ) { | |
data = sortedBoneData[ i ]; | |
bones.push( data.bone ); | |
boneInverses.push( data.boneInverse ); | |
} | |
return new THREE.Skeleton( bones, boneInverses ); | |
} | |
function buildBoneHierarchy( root, joints, boneData ) { | |
// setup bone data from visual scene | |
root.traverse( function ( object ) { | |
if ( object.isBone === true ) { | |
var boneInverse; | |
// retrieve the boneInverse from the controller data | |
for ( var i = 0; i < joints.length; i ++ ) { | |
var joint = joints[ i ]; | |
if ( joint.name === object.name ) { | |
boneInverse = joint.boneInverse; | |
break; | |
} | |
} | |
if ( boneInverse === undefined ) { | |
// Unfortunately, there can be joints in the visual scene that are not part of the | |
// corresponding controller. In this case, we have to create a dummy boneInverse matrix | |
// for the respective bone. This bone won't affect any vertices, because there are no skin indices | |
// and weights defined for it. But we still have to add the bone to the sorted bone list in order to | |
// ensure a correct animation of the model. | |
boneInverse = new THREE.Matrix4(); | |
} | |
boneData.push( { bone: object, boneInverse: boneInverse, processed: false } ); | |
} | |
} ); | |
} | |
function buildNode( data ) { | |
var objects = []; | |
var matrix = data.matrix; | |
var nodes = data.nodes; | |
var type = data.type; | |
var instanceCameras = data.instanceCameras; | |
var instanceControllers = data.instanceControllers; | |
var instanceLights = data.instanceLights; | |
var instanceGeometries = data.instanceGeometries; | |
var instanceNodes = data.instanceNodes; | |
// nodes | |
for ( var i = 0, l = nodes.length; i < l; i ++ ) { | |
objects.push( getNode( nodes[ i ] ) ); | |
} | |
// instance cameras | |
for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) { | |
var instanceCamera = getCamera( instanceCameras[ i ] ); | |
if ( instanceCamera !== null ) { | |
objects.push( instanceCamera.clone() ); | |
} | |
} | |
// instance controllers | |
for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) { | |
var instance = instanceControllers[ i ]; | |
var controller = getController( instance.id ); | |
var geometries = getGeometry( controller.id ); | |
var newObjects = buildObjects( geometries, instance.materials ); | |
var skeletons = instance.skeletons; | |
var joints = controller.skin.joints; | |
var skeleton = buildSkeleton( skeletons, joints ); | |
for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) { | |
var object = newObjects[ j ]; | |
if ( object.isSkinnedMesh ) { | |
object.bind( skeleton, controller.skin.bindMatrix ); | |
object.normalizeSkinWeights(); | |
} | |
objects.push( object ); | |
} | |
} | |
// instance lights | |
for ( var i = 0, l = instanceLights.length; i < l; i ++ ) { | |
var instanceLight = getLight( instanceLights[ i ] ); | |
if ( instanceLight !== null ) { | |
objects.push( instanceLight.clone() ); | |
} | |
} | |
// instance geometries | |
for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) { | |
var instance = instanceGeometries[ i ]; | |
// a single geometry instance in collada can lead to multiple object3Ds. | |
// this is the case when primitives are combined like triangles and lines | |
var geometries = getGeometry( instance.id ); | |
var newObjects = buildObjects( geometries, instance.materials ); | |
for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) { | |
objects.push( newObjects[ j ] ); | |
} | |
} | |
// instance nodes | |
for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) { | |
objects.push( getNode( instanceNodes[ i ] ).clone() ); | |
} | |
var object; | |
if ( nodes.length === 0 && objects.length === 1 ) { | |
object = objects[ 0 ]; | |
} else { | |
object = ( type === 'JOINT' ) ? new THREE.Bone() : new THREE.Group(); | |
for ( var i = 0; i < objects.length; i ++ ) { | |
object.add( objects[ i ] ); | |
} | |
} | |
if ( object.name === '' ) { | |
object.name = ( type === 'JOINT' ) ? data.sid : data.name; | |
} | |
object.matrix.copy( matrix ); | |
object.matrix.decompose( object.position, object.quaternion, object.scale ); | |
return object; | |
} | |
function resolveMaterialBinding( keys, instanceMaterials ) { | |
var materials = []; | |
for ( var i = 0, l = keys.length; i < l; i ++ ) { | |
var id = instanceMaterials[ keys[ i ] ]; | |
materials.push( getMaterial( id ) ); | |
} | |
return materials; | |
} | |
function buildObjects( geometries, instanceMaterials ) { | |
var objects = []; | |
for ( var type in geometries ) { | |
var geometry = geometries[ type ]; | |
var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials ); | |
// handle case if no materials are defined | |
if ( materials.length === 0 ) { | |
if ( type === 'lines' || type === 'linestrips' ) { | |
materials.push( new THREE.LineBasicMaterial() ); | |
} else { | |
materials.push( new THREE.MeshPhongMaterial() ); | |
} | |
} | |
// regard skinning | |
var skinning = ( geometry.data.attributes.skinIndex !== undefined ); | |
if ( skinning ) { | |
for ( var i = 0, l = materials.length; i < l; i ++ ) { | |
materials[ i ].skinning = true; | |
} | |
} | |
// choose between a single or multi materials (material array) | |
var material = ( materials.length === 1 ) ? materials[ 0 ] : materials; | |
// now create a specific 3D object | |
var object; | |
switch ( type ) { | |
case 'lines': | |
object = new THREE.LineSegments( geometry.data, material ); | |
break; | |
case 'linestrips': | |
object = new THREE.Line( geometry.data, material ); | |
break; | |
case 'triangles': | |
case 'polylist': | |
if ( skinning ) { | |
object = new THREE.SkinnedMesh( geometry.data, material ); | |
} else { | |
object = new THREE.Mesh( geometry.data, material ); | |
} | |
break; | |
} | |
objects.push( object ); | |
} | |
return objects; | |
} | |
function hasNode( id ) { | |
return library.nodes[ id ] !== undefined; | |
} | |
function getNode( id ) { | |
return getBuild( library.nodes[ id ], buildNode ); | |
} | |
// visual scenes | |
function parseVisualScene( xml ) { | |
var data = { | |
name: xml.getAttribute( 'name' ), | |
children: [] | |
}; | |
prepareNodes( xml ); | |
var elements = getElementsByTagName( xml, 'node' ); | |
for ( var i = 0; i < elements.length; i ++ ) { | |
data.children.push( parseNode( elements[ i ] ) ); | |
} | |
library.visualScenes[ xml.getAttribute( 'id' ) ] = data; | |
} | |
function buildVisualScene( data ) { | |
var group = new THREE.Group(); | |
group.name = data.name; | |
var children = data.children; | |
for ( var i = 0; i < children.length; i ++ ) { | |
var child = children[ i ]; | |
group.add( getNode( child.id ) ); | |
} | |
return group; | |
} | |
function hasVisualScene( id ) { | |
return library.visualScenes[ id ] !== undefined; | |
} | |
function getVisualScene( id ) { | |
return getBuild( library.visualScenes[ id ], buildVisualScene ); | |
} | |
// scenes | |
function parseScene( xml ) { | |
var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ]; | |
return getVisualScene( parseId( instance.getAttribute( 'url' ) ) ); | |
} | |
function setupAnimations() { | |
var clips = library.clips; | |
if ( isEmpty( clips ) === true ) { | |
if ( isEmpty( library.animations ) === false ) { | |
// if there are animations but no clips, we create a default clip for playback | |
var tracks = []; | |
for ( var id in library.animations ) { | |
var animationTracks = getAnimation( id ); | |
for ( var i = 0, l = animationTracks.length; i < l; i ++ ) { | |
tracks.push( animationTracks[ i ] ); | |
} | |
} | |
animations.push( new THREE.AnimationClip( 'default', - 1, tracks ) ); | |
} | |
} else { | |
for ( var id in clips ) { | |
animations.push( getAnimationClip( id ) ); | |
} | |
} | |
} | |
console.time( 'THREE.ColladaLoader' ); | |
if ( text.length === 0 ) { | |
return { scene: new THREE.Scene() }; | |
} | |
console.time( 'THREE.ColladaLoader: DOMParser' ); | |
var xml = new DOMParser().parseFromString( text, 'application/xml' ); | |
console.timeEnd( 'THREE.ColladaLoader: DOMParser' ); | |
var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ]; | |
// metadata | |
var version = collada.getAttribute( 'version' ); | |
console.log( 'THREE.ColladaLoader: File version', version ); | |
var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] ); | |
var textureLoader = new THREE.TextureLoader( this.manager ); | |
textureLoader.setPath( path ).setCrossOrigin( this.crossOrigin ); | |
// | |
var animations = []; | |
var kinematics = {}; | |
var count = 0; | |
// | |
var library = { | |
animations: {}, | |
clips: {}, | |
controllers: {}, | |
images: {}, | |
effects: {}, | |
materials: {}, | |
cameras: {}, | |
lights: {}, | |
geometries: {}, | |
nodes: {}, | |
visualScenes: {}, | |
kinematicsModels: {}, | |
kinematicsScenes: {} | |
}; | |
console.time( 'THREE.ColladaLoader: Parse' ); | |
parseLibrary( collada, 'library_animations', 'animation', parseAnimation ); | |
parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip ); | |
parseLibrary( collada, 'library_controllers', 'controller', parseController ); | |
parseLibrary( collada, 'library_images', 'image', parseImage ); | |
parseLibrary( collada, 'library_effects', 'effect', parseEffect ); | |
parseLibrary( collada, 'library_materials', 'material', parseMaterial ); | |
parseLibrary( collada, 'library_cameras', 'camera', parseCamera ); | |
parseLibrary( collada, 'library_lights', 'light', parseLight ); | |
parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry ); | |
parseLibrary( collada, 'library_nodes', 'node', parseNode ); | |
parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene ); | |
parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel ); | |
parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene ); | |
console.timeEnd( 'THREE.ColladaLoader: Parse' ); | |
console.time( 'THREE.ColladaLoader: Build' ); | |
buildLibrary( library.animations, buildAnimation ); | |
buildLibrary( library.clips, buildAnimationClip ); | |
buildLibrary( library.controllers, buildController ); | |
buildLibrary( library.images, buildImage ); | |
buildLibrary( library.effects, buildEffect ); | |
buildLibrary( library.materials, buildMaterial ); | |
buildLibrary( library.cameras, buildCamera ); | |
buildLibrary( library.lights, buildLight ); | |
buildLibrary( library.geometries, buildGeometry ); | |
buildLibrary( library.visualScenes, buildVisualScene ); | |
console.timeEnd( 'THREE.ColladaLoader: Build' ); | |
setupAnimations(); | |
setupKinematics(); | |
var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] ); | |
if ( asset.upAxis === 'Z_UP' ) { | |
scene.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) ); | |
} | |
scene.scale.multiplyScalar( asset.unit ); | |
console.timeEnd( 'THREE.ColladaLoader' ); | |
return { | |
animations: animations, | |
kinematics: kinematics, | |
library: library, | |
scene: scene | |
}; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment