From e5d32c2b252f2bdd32a9f4e5b08f330bc61bbd8c Mon Sep 17 00:00:00 2001 From: Sandro Weber <webers@in.tum.de> Date: Thu, 26 Mar 2020 09:49:20 +0000 Subject: [PATCH] =?UTF-8?q?Fusion=20effectu=C3=A9e=20NRRPLT-7843-lookat-mo?= =?UTF-8?q?de-broken=20(pull=20request=20#35)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NRRPLT-7843 lookat mode broken * [NRRPLT-7843] updated getBoundingSphere() calls * [NRRPLT-7843] prettified * [NRRPLT-7843] precommit stage lint messing up prettier formatting? * [NRRPLT-7843] fixed formatting * [NRRPLT-7843] added /include dir to prettier list of files Approuvé par : Axel von Arnim Approuvé par : Daniel Dyrda --- .prettierignore | 2 +- gz3d/build/gz3d.js | 40 +- gz3d/client/js/include/ColladaLoader.js | 6005 ++++++++--------- .../js/include/ThreeBackwardsCompatibility.js | 19 +- gz3d/client/js/include/avatar-controls.js | 427 +- gz3d/client/js/include/gzwebsocket.js | 9 +- gz3d/client/js/include/lookat-controls.js | 487 +- gz3d/src/gz3d-fluid-vis.js | 20 +- gz3d/src/gziface.js | 18 +- gz3d/src/gzlabelmanager.js | 3 +- package.json | 3 +- 11 files changed, 3425 insertions(+), 3608 deletions(-) diff --git a/.prettierignore b/.prettierignore index 337b2f9..60a1650 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,7 +1,7 @@ package.json gz3d/utils/package.json gz3d/utils/Gruntfile.js -gz3d/client/js +gz3d/client/js/external bower_components node_modules build diff --git a/gz3d/build/gz3d.js b/gz3d/build/gz3d.js index 0579629..6cda9ab 100644 --- a/gz3d/build/gz3d.js +++ b/gz3d/build/gz3d.js @@ -387,8 +387,7 @@ GZ3D.AnimatedModel.prototype.updateJoint = function( } }; - -GZ3D.VisualFluidModel = function (scene) { +GZ3D.VisualFluidModel = function(scene) { this.scene = scene; this.rootGroup = new THREE.Group(); this.rootGroup.name = 'VisualFluidModel'; @@ -396,7 +395,7 @@ GZ3D.VisualFluidModel = function (scene) { this.rootGroup.visible = true; }; -GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { +GZ3D.VisualFluidModel.prototype.initFluidBuffers = function(message) { this.scales = new Float32Array(message.position.length); this.scales.fill(10.0); this.positions = new Float32Array(message.position.length * 3); @@ -404,7 +403,10 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { this.positionAttribute = new THREE.BufferAttribute(this.positions, 3); this.positionAttribute.setDynamic(true); this.geometry.addAttribute('position', this.positionAttribute); - this.geometry.addAttribute('scale', new THREE.BufferAttribute(this.scales, 1)); + this.geometry.addAttribute( + 'scale', + new THREE.BufferAttribute(this.scales, 1) + ); this.material = new THREE.ParticleBasicMaterial({ size: 5.0, sizeAttenuation: false, @@ -418,14 +420,19 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { this.rootGroup.add(this.particles); }; -GZ3D.VisualFluidModel.prototype.updateVisualization = function (message) { +GZ3D.VisualFluidModel.prototype.updateVisualization = function(message) { var vertex; for (var i = 0, l = message.position.length; i < l; i++) { - vertex = new THREE.Vector3(message.position[i].x, message.position[i].y, message.position[i].z); + vertex = new THREE.Vector3( + message.position[i].x, + message.position[i].y, + message.position[i].z + ); vertex.toArray(this.positions, i * 3); } this.positionAttribute.needsUpdate = true; }; + GZ3D.VisualMuscleModel = function(scene, robotName) { this.scene = scene; this.cylinderShapes = {}; @@ -4870,11 +4877,11 @@ GZ3D.GZIface.prototype.onConnected = function() { ros: this.webSocket, name: '~/fluid_pos', message_type: 'fluid', - throttle_rate: 1.0 / 200.0 * 1000.0, + throttle_rate: 1.0 / 200.0 * 1000.0 }); // function for updating client system visualization - var updateMuscleVisualization = function (message) { + var updateMuscleVisualization = function(message) { if (!(message.robot_name in this.scene.muscleVisuzalizations)) { this.scene.muscleVisuzalizations[ message.robot_name @@ -4887,11 +4894,13 @@ GZ3D.GZIface.prototype.onConnected = function() { ); } }; - + // function for updating client system visualization - var updateFluidVisualization = function (message) { + var updateFluidVisualization = function(message) { if (!this.scene.fluidVisualizations.length) { - this.scene.fluidVisualizations.push(new GZ3D.VisualFluidModel(this.scene, message)); + this.scene.fluidVisualizations.push( + new GZ3D.VisualFluidModel(this.scene, message) + ); this.scene.fluidVisualizations[0].initFluidBuffers(message); } this.scene.fluidVisualizations[0].updateVisualization(message); @@ -4903,9 +4912,11 @@ GZ3D.GZIface.prototype.onConnected = function() { ); // Subscription to fluid_pos topic - this.fluidVisualizationSubscriber.subscribe(updateFluidVisualization.bind(this)); + this.fluidVisualizationSubscriber.subscribe( + updateFluidVisualization.bind(this) + ); - var requestUpdate = function (message) { + var requestUpdate = function(message) { if (message.request === 'entity_delete') { var entity = this.scene.getByName(message.data); if (entity) { @@ -6695,7 +6706,8 @@ GZ3D.LabelManager.prototype.onRender = function() { var labelPos = []; bbox.setFromObject(root); - var bsphere = bbox.getBoundingSphere(); + var bsphere = new THREE.Sphere(); + bbox.getBoundingSphere(bsphere); for (var i = 0; i < labelInfoList.length; i++) { var labelInfo = labelInfoList[i]; diff --git a/gz3d/client/js/include/ColladaLoader.js b/gz3d/client/js/include/ColladaLoader.js index 7712ab9..19e183e 100644 --- a/gz3d/client/js/include/ColladaLoader.js +++ b/gz3d/client/js/include/ColladaLoader.js @@ -3,3538 +3,3111 @@ * @author Mugen87 / https://github.com/Mugen87 */ -THREE.ColladaLoader = function ( manager ) { +THREE.ColladaLoader = function(manager) { + this.manager = manager !== undefined ? manager : THREE.DefaultLoadingManager; +}; - this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; +THREE.ColladaLoader.prototype = { + constructor: THREE.ColladaLoader, - }; + crossOrigin: 'Anonymous', - THREE.ColladaLoader.prototype = { + load: function(url, onLoad, onProgress, onError) { + var scope = this; - constructor: THREE.ColladaLoader, + var path = THREE.LoaderUtils.extractUrlBase(url); - crossOrigin: 'Anonymous', + var loader = new THREE.FileLoader(scope.manager); + loader.load( + url, + function(text) { + onLoad(scope.parse(text, path)); + }, + onProgress, + onError + ); + }, - load: function ( url, onLoad, onProgress, onError ) { + options: { + set convertUpAxis(value) { + console.warn( + 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' + ); + } + }, - var scope = this; + setCrossOrigin: function(value) { + this.crossOrigin = value; + }, - var path = THREE.LoaderUtils.extractUrlBase( url ); + parse: function(text, path) { + function getElementsByTagName(xml, name) { + // Non recursive xml.getElementsByTagName() ... - var loader = new THREE.FileLoader( scope.manager ); - loader.load( url, function ( text ) { + var array = []; + var childNodes = xml.childNodes; - onLoad( scope.parse( text, path ) ); + for (var i = 0, l = childNodes.length; i < l; i++) { + var child = childNodes[i]; - }, onProgress, onError ); + if (child.nodeName === name) { + array.push(child); + } + } - }, + return array; + } - options: { + function parseStrings(text) { + if (text.length === 0) return []; - set convertUpAxis( value ) { + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' ); + for (var i = 0, l = parts.length; i < l; i++) { + array[i] = parts[i]; + } - } + return array; + } - }, + function parseFloats(text) { + if (text.length === 0) return []; - setCrossOrigin: function ( value ) { + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - this.crossOrigin = value; + for (var i = 0, l = parts.length; i < l; i++) { + array[i] = parseFloat(parts[i]); + } - }, + return array; + } - parse: function ( text, path ) { + function parseInts(text) { + if (text.length === 0) return []; - function getElementsByTagName( xml, name ) { + var parts = text.trim().split(/\s+/); + var array = new Array(parts.length); - // Non recursive xml.getElementsByTagName() ... + for (var i = 0, l = parts.length; i < l; i++) { + array[i] = parseInt(parts[i]); + } - var array = []; - var childNodes = xml.childNodes; + return array; + } - for ( var i = 0, l = childNodes.length; i < l; i ++ ) { + function parseId(text) { + return text.substring(1); + } - var child = childNodes[ i ]; + function generateId() { + return 'three_default_' + count++; + } - if ( child.nodeName === name ) { + function isEmpty(object) { + return Object.keys(object).length === 0; + } - array.push( child ); + // asset - } + function parseAsset(xml) { + return { + unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]), + upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]) + }; + } + + function parseAssetUnit(xml) { + return xml !== undefined ? parseFloat(xml.getAttribute('meter')) : 1; + } - } + function parseAssetUpAxis(xml) { + return xml !== undefined ? xml.textContent : 'Y_UP'; + } - return array; + // library - } + function parseLibrary(xml, libraryName, nodeName, parser) { + var library = getElementsByTagName(xml, libraryName)[0]; - function parseStrings( text ) { + if (library !== undefined) { + var elements = getElementsByTagName(library, nodeName); - if ( text.length === 0 ) return []; + for (var i = 0; i < elements.length; i++) { + parser(elements[i]); + } + } + } - var parts = text.trim().split( /\s+/ ); - var array = new Array( parts.length ); + function buildLibrary(data, builder) { + for (var name in data) { + var object = data[name]; + object.build = builder(data[name]); + } + } - for ( var i = 0, l = parts.length; i < l; i ++ ) { + // get - array[ i ] = parts[ i ]; + function getBuild(data, builder) { + if (data !== undefined) { + if (data.build !== undefined) return data.build; - } + data.build = builder(data); - return array; + return data.build; + } + return null; + } - } + // animation - function parseFloats( text ) { + function parseAnimation(xml) { + var data = { + sources: {}, + samplers: {}, + channels: {} + }; - if ( text.length === 0 ) return []; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - var parts = text.trim().split( /\s+/ ); - var array = new Array( parts.length ); + if (child.nodeType !== 1) continue; - for ( var i = 0, l = parts.length; i < l; i ++ ) { + var id; - array[ i ] = parseFloat( parts[ i ] ); + 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; - return array; + case 'channel': + id = child.getAttribute('target'); + data.channels[id] = parseAnimationChannel(child); + break; - } + default: + break; + } + } - function parseInts( text ) { + library.animations[xml.getAttribute('id')] = data; + } - if ( text.length === 0 ) return []; + function parseAnimationSampler(xml) { + var data = { + inputs: {} + }; - var parts = text.trim().split( /\s+/ ); - var array = new Array( parts.length ); + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - for ( var i = 0, l = parts.length; i < l; i ++ ) { + if (child.nodeType !== 1) continue; - array[ i ] = parseInt( parts[ i ] ); + switch (child.nodeName) { + case 'input': + var id = parseId(child.getAttribute('source')); + var semantic = child.getAttribute('semantic'); + data.inputs[semantic] = id; + break; + } + } - } + return data; + } - return array; + function parseAnimationChannel(xml) { + var data = {}; - } + var target = xml.getAttribute('target'); - function parseId( text ) { + // parsing SID Addressing Syntax - return text.substring( 1 ); + var parts = target.split('/'); - } + var id = parts.shift(); + var sid = parts.shift(); - function generateId() { + // check selection syntax - return 'three_default_' + ( count ++ ); + var arraySyntax = sid.indexOf('(') !== -1; + var memberSyntax = sid.indexOf('.') !== -1; - } + if (memberSyntax) { + // member selection access - function isEmpty( object ) { + 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. - return Object.keys( object ).length === 0; + var indices = sid.split('('); + sid = indices.shift(); - } + for (var i = 0; i < indices.length; i++) { + indices[i] = parseInt(indices[i].replace(/\)/, '')); + } - // asset + data.indices = indices; + } - function parseAsset( xml ) { + data.id = id; + data.sid = sid; - return { - unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ), - upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] ) - }; + data.arraySyntax = arraySyntax; + data.memberSyntax = memberSyntax; - } + data.sampler = parseId(xml.getAttribute('source')); - function parseAssetUnit( xml ) { + return data; + } - return xml !== undefined ? parseFloat( xml.getAttribute( 'meter' ) ) : 1; + function buildAnimation(data) { + var tracks = []; - } + var channels = data.channels; + var samplers = data.samplers; + var sources = data.sources; - function parseAssetUpAxis( xml ) { + for (var target in channels) { + if (channels.hasOwnProperty(target)) { + var channel = channels[target]; + var sampler = samplers[channel.sampler]; - return xml !== undefined ? xml.textContent : 'Y_UP'; + var inputId = sampler.inputs.INPUT; + var outputId = sampler.inputs.OUTPUT; - } + var inputSource = sources[inputId]; + var outputSource = sources[outputId]; - // library + var animation = buildAnimationChannel( + channel, + inputSource, + outputSource + ); - function parseLibrary( xml, libraryName, nodeName, parser ) { + createKeyframeTracks(animation, tracks); + } + } - var library = getElementsByTagName( xml, libraryName )[ 0 ]; + return tracks; + } - if ( library !== undefined ) { + function getAnimation(id) { + return getBuild(library.animations[id], buildAnimation); + } - var elements = getElementsByTagName( library, nodeName ); + function buildAnimationChannel(channel, inputSource, outputSource) { + var node = library.nodes[channel.id]; + var object3D = getNode(node.id); - for ( var i = 0; i < elements.length; i ++ ) { + var transform = node.transforms[channel.sid]; + var defaultMatrix = node.matrix.clone().transpose(); - parser( elements[ i ] ); + 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; - function buildLibrary( data, builder ) { + if (data[time] === undefined) data[time] = {}; - for ( var name in data ) { + if (channel.arraySyntax === true) { + var value = outputSource.array[stride]; + var index = channel.indices[0] + 4 * channel.indices[1]; - var object = data[ name ]; - object.build = builder( data[ name ] ); + 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; - // get + case 'rotate': + console.warn( + 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', + transform + ); + break; - function getBuild( data, builder ) { - if (data !== undefined) { - if (data.build !== undefined) return data.build; + case 'scale': + console.warn( + 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', + transform + ); + break; + } - data.build = builder(data); + var keyframes = prepareAnimationData(data, defaultMatrix); - return data.build; - } - return null; - } + 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) + ); - // animation + return tracks; + } - function parseAnimation( xml ) { + function transformAnimationData(keyframes, property, defaultValue) { + var keyframe; + + var empty = true; + var i, l; - var data = { - sources: {}, - samplers: {}, - channels: {} - }; + // check, if values of a property are missing in our keyframes - for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + for (i = 0, l = keyframes.length; i < l; i++) { + keyframe = keyframes[i]; - var child = xml.childNodes[ i ]; + if (keyframe.value[property] === undefined) { + keyframe.value[property] = null; // mark as missing + } else { + empty = false; + } + } - if ( child.nodeType !== 1 ) continue; + if (empty === true) { + // no values at all, so we set a default value - var id; + for (i = 0, l = keyframes.length; i < l; i++) { + keyframe = keyframes[i]; - switch ( child.nodeName ) { + 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; + } - case 'source': - id = child.getAttribute( 'id' ); - data.sources[ id ] = parseSource( child ); - break; + key.value[property] = + (key.time - prev.time) * + (next.value[property] - prev.value[property]) / + (next.time - prev.time) + + prev.value[property]; + } - case 'sampler': - id = child.getAttribute( 'id' ); - data.samplers[ id ] = parseAnimationSampler( child ); - break; + // animation clips - case 'channel': - id = child.getAttribute( 'target' ); - data.channels[ id ] = parseAnimationChannel( child ); - break; + function parseAnimationClip(xml) { + var data = { + name: xml.getAttribute('id') || 'default', + start: parseFloat(xml.getAttribute('start') || 0), + end: parseFloat(xml.getAttribute('end') || 0), + animations: [] + }; - default: - break; + 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.animations[ xml.getAttribute( 'id' ) ] = data; + library.clips[xml.getAttribute('id')] = data; + } - } + function buildAnimationClip(data) { + var tracks = []; - function parseAnimationSampler( xml ) { + var name = data.name; + var duration = data.end - data.start || -1; + var animations = data.animations; - var data = { - inputs: {}, - }; + 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]); + } + } - for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + return new THREE.AnimationClip(name, duration, tracks); + } - var child = xml.childNodes[ i ]; + 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': + data.id = parseId(child.getAttribute('source')); + console.warn( + 'THREE.ColladaLoader: Morph target animation not supported yet.' + ); + break; + } + } + + library.controllers[xml.getAttribute('id')] = data; + } + + function parseSkin(xml) { + var data = { + sources: {} + }; - if ( child.nodeType !== 1 ) continue; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - switch ( child.nodeName ) { + 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: {} + }; - case 'input': - var id = parseId( child.getAttribute( 'source' ) ); - var semantic = child.getAttribute( 'semantic' ); - data.inputs[ semantic ] = id; - break; + 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; + } - return data; + function parseVertexWeights(xml) { + var data = { + inputs: {} + }; - } + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - function parseAnimationChannel( xml ) { + if (child.nodeType !== 1) continue; - var data = {}; + 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; - var target = xml.getAttribute( 'target' ); + 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 + }; - // parsing SID Addressing Syntax + var geometry = library.geometries[build.id]; - var parts = target.split( '/' ); + if (data.skin !== undefined) { + build.skin = buildSkin(data.skin); - var id = parts.shift(); - var sid = parts.shift(); + // we enhance the 'sources' property of the corresponding geometry with our skin data - // check selection syntax + geometry.sources.skinIndices = build.skin.indices; + geometry.sources.skinWeights = build.skin.weights; + } - var arraySyntax = ( sid.indexOf( '(' ) !== - 1 ); - var memberSyntax = ( sid.indexOf( '.' ) !== - 1 ); + return build; + } - if ( memberSyntax ) { + function buildSkin(data) { + var BONE_LIMIT = 4; - // member selection access + var build = { + joints: [], // this must be an array to preserve the joint order + indices: { + array: [], + stride: BONE_LIMIT + }, + weights: { + array: [], + stride: BONE_LIMIT + } + }; - parts = sid.split( '.' ); - sid = parts.shift(); - data.member = parts.shift(); + var sources = data.sources; + var vertexWeights = data.vertexWeights; - } else if ( arraySyntax ) { + var vcount = vertexWeights.vcount; + var v = vertexWeights.v; + var jointOffset = vertexWeights.inputs.JOINT.offset; + var weightOffset = vertexWeights.inputs.WEIGHT.offset; - // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. + var jointSource = data.sources[data.joints.inputs.JOINT]; + var inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]; - var indices = sid.split( '(' ); - sid = indices.shift(); + var weights = sources[vertexWeights.inputs.WEIGHT.id].array; + var stride = 0; - for ( var i = 0; i < indices.length; i ++ ) { + var i, j, l; - indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) ); + // 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 = []; - data.indices = indices; + 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 }); - data.id = id; - data.sid = sid; + stride += 2; + } - data.arraySyntax = arraySyntax; - data.memberSyntax = memberSyntax; + // we sort the joints in descending order based on the weights. + // this ensures, we only procced the most important joints of the vertex - data.sampler = parseId( xml.getAttribute( 'source' ) ); + vertexSkinData.sort(descending); - return data; + // 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]; - function buildAnimation( data ) { + 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); + } + } + } - var tracks = []; + // setup bind matrix - var channels = data.channels; - var samplers = data.samplers; - var sources = data.sources; + build.bindMatrix = new THREE.Matrix4() + .fromArray(data.bindShapeMatrix) + .transpose(); - for ( var target in channels ) { + // process bones and inverse bind matrix data - if ( channels.hasOwnProperty( target ) ) { + 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(); - var channel = channels[ target ]; - var sampler = samplers[ channel.sampler ]; + build.joints.push({ name: name, boneInverse: boneInverse }); + } - var inputId = sampler.inputs.INPUT; - var outputId = sampler.inputs.OUTPUT; + return build; - var inputSource = sources[ inputId ]; - var outputSource = sources[ outputId ]; + // array sort function - var animation = buildAnimationChannel( channel, inputSource, outputSource ); + function descending(a, b) { + return b.weight - a.weight; + } + } - createKeyframeTracks( animation, tracks ); + function getController(id) { + return getBuild(library.controllers[id], buildController); + } - } + // image - } + function parseImage(xml) { + var data = { + init_from: getElementsByTagName(xml, 'init_from')[0].textContent + }; - return tracks; + library.images[xml.getAttribute('id')] = data; + } - } + function buildImage(data) { + if (data.build !== undefined) return data.build; - function getAnimation( id ) { + return data.init_from; + } - return getBuild( library.animations[ id ], buildAnimation ); + function getImage(id) { + return getBuild(library.images[id], buildImage); + } - } + // effect - function buildAnimationChannel( channel, inputSource, outputSource ) { + function parseEffect(xml) { + var data = {}; - var node = library.nodes[ channel.id ]; - var object3D = getNode( node.id ); + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - var transform = node.transforms[ channel.sid ]; - var defaultMatrix = node.matrix.clone().transpose(); + if (child.nodeType !== 1) continue; - var time, stride; - var i, il, j, jl; + switch (child.nodeName) { + case 'profile_COMMON': + data.profile = parseEffectProfileCOMMON(child); + break; + } + } - var data = {}; + library.effects[xml.getAttribute('id')] = 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 + function parseEffectProfileCOMMON(xml) { + var data = { + surfaces: {}, + samplers: {} + }; - switch ( transform ) { + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - case 'matrix': + if (child.nodeType !== 1) continue; - for ( i = 0, il = inputSource.array.length; i < il; i ++ ) { + switch (child.nodeName) { + case 'newparam': + parseEffectNewparam(child, data); + break; - time = inputSource.array[ i ]; - stride = i * outputSource.stride; + case 'technique': + data.technique = parseEffectTechnique(child); + break; + } + } - if ( data[ time ] === undefined ) data[ time ] = {}; + return data; + } - if ( channel.arraySyntax === true ) { + function parseEffectNewparam(xml, data) { + var sid = xml.getAttribute('sid'); - var value = outputSource.array[ stride ]; - var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ]; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - data[ time ][ index ] = value; + if (child.nodeType !== 1) continue; - } else { + switch (child.nodeName) { + case 'surface': + data.surfaces[sid] = parseEffectSurface(child); + break; - for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) { + case 'sampler2D': + data.samplers[sid] = parseEffectSampler(child); + break; + } + } + } - data[ time ][ j ] = outputSource.array[ stride + j ]; + 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; + } + } - break; + return data; + } - case 'translate': - console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); - break; + function parseEffectSampler(xml) { + var data = {}; - case 'rotate': - console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); - break; + 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 = { parameters: {} }; + + 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; + + case 'extra': + data.parameters = Object.assign( + {}, + data.parameters, + parseEffectParameters(child) + ); + break; + + case 'bump': + data.bump = parseEffectParameter(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 'ambient': + case 'emission': + case 'diffuse': + case 'specular': + case 'shininess': + case 'transparent': + case 'transparency': + case 'reflective': + var effect = parseEffectParameter(child); + if (child.nodeName !== 'ambient' || effect.texture) { + data[child.nodeName] = effect; + } + break; + + case 'technique': + return parseEffectTechnique(child); + } + } + + 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 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 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 getTexturePath(textureObject) { + var sampler = effect.profile.samplers[textureObject.id]; + var surface = effect.profile.surfaces[sampler.source]; + + if (sampler !== undefined && surface !== null) { + var img = getImage(surface.init_from); + + return textureLoader.path + img; + } + } + + function isTexturePBR(textureObject) { + var p = getTexturePath(textureObject); + if (!p) return false; + + return p.indexOf('PBR_') >= 0 || p.indexOf('PBRFULL_') >= 0; + } + + function getTexture(textureObject) { + var sampler = effect.profile.samplers[textureObject.id]; + var surface = effect.profile.surfaces[sampler.source]; + + if (sampler !== undefined && surface !== null) { + var img = getImage(surface.init_from); + if (img) { + var texture = textureLoader.load(img, textureLoadedCallback); + + 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; + } + } + + return null; + } + + var parameters = technique.parameters; + var parameter; + var pbrMaterial = null; + + var pbrKeyToThreeJSMap = { + Base_Color: 'map', + Metallic: 'metalnessMap', + Roughness: 'roughnessMap', + Mixed_AO: 'aoMap', + Emissive: 'emissiveMap', + Normal: 'normalMap', + Height: 'displacementMap' + }; + + for (var key in parameters) { + parameter = parameters[key]; + if (parameter.texture) { + var texturePath = getTexturePath(parameter.texture); + + if ( + texturePath && + (texturePath.indexOf('PBR_') >= 0 || + texturePath.indexOf('PBRFULL_') >= 0) + ) + for (var k in pbrKeyToThreeJSMap) { + if (texturePath.indexOf(k) >= 0) { + if (pbrMaterial === null) { + pbrMaterial = {}; + } + + pbrMaterial[pbrKeyToThreeJSMap[k]] = texturePath; + + if ( + texturePath.indexOf('PBRFULL_') >= 0 && + k === 'Base_Color' + ) { + // If the prefix is PBRFULL it means that all the PBR textures are present + // but that only the basic color map have been linked to the dae. + + var allpbrkeywords = [ + 'Metallic', + 'Mixed_AO', + 'Roughness', + 'Normal', + 'Roughness' + ]; + + for (var pbri in allpbrkeywords) { + var pbrk = allpbrkeywords[pbri]; + var path = texturePath; + + path = path.replace('Base_Color', pbrk); + if (pbrk === 'Normal') { + // Normal map must be png. In case the Base_Color is a jpg, + // change extension to png + + path = path.replace('.jpg', '.png'); + path = path.replace('.jpeg', '.png'); + } + + pbrMaterial[pbrKeyToThreeJSMap[pbrk]] = path; + } + } + } + } + } + + switch (key) { + case 'diffuse': + if (parameter.color) material.color.fromArray(parameter.color); + if (parameter.texture && !isTexturePBR(parameter.texture)) + material.map = getTexture(parameter.texture); + break; + case 'specular': + if (parameter.color && material.specular) + material.specular.fromArray(parameter.color); + if (parameter.texture && !isTexturePBR(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; + case 'transparent': + if (parameter.texture && !isTexturePBR(parameter.texture)) + material.alphaMap = getTexture(parameter.texture); + material.transparent = true; + break; + case 'transparency': + if (parameter.float !== undefined) + material.opacity = parameter.float; + material.transparent = true; + break; + } + } + + if (pbrMaterial !== null) { + material.pbrMaterialDescription = pbrMaterial; // If PBR material has been found, store it for future usage + } + + 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) { + return getBuild(library.cameras[id], buildCamera); + } + + // 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 = {}; - case 'scale': - console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); - break; + 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; + } - var keyframes = prepareAnimationData( data, defaultMatrix ); + function buildLight(data) { + var light; - var animation = { - name: object3D.uuid, - keyframes: keyframes - }; + switch (data.technique) { + case 'directional': + light = new THREE.DirectionalLight(); + break; - return animation; + case 'point': + light = new THREE.PointLight(); + break; - } + case 'spot': + light = new THREE.SpotLight(); + break; - function prepareAnimationData( data, defaultMatrix ) { + case 'ambient': + light = new THREE.AmbientLight(); + break; + } - var keyframes = []; + if (data.parameters.color) light.color.copy(data.parameters.color); + if (data.parameters.distance) light.distance = data.parameters.distance; - // transfer data into a sortable array + return light; + } - for ( var time in data ) { + function getLight(id) { + return getBuild(library.lights[id], buildLight); + } - keyframes.push( { time: parseFloat( time ), value: data[ time ] } ); + // geometry - } + function parseGeometry(xml) { + var data = { + name: xml.getAttribute('name'), + sources: {}, + vertices: {}, + primitives: [] + }; + + 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; - // ensure keyframes are sorted by time + for (var i = 0; i < mesh.childNodes.length; i++) { + var child = mesh.childNodes[i]; - keyframes.sort( ascending ); + if (child.nodeType !== 1) continue; - // now we clean up all animation data, so we can use them for keyframe tracks + 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: + break; + } + } + + library.geometries[xml.getAttribute('id')] = data; + } - for ( var i = 0; i < 16; i ++ ) { + function parseSource(xml) { + var data = { + array: [], + stride: 3 + }; - transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] ); + for (var i = 0; i < xml.childNodes.length; i++) { + var child = xml.childNodes[i]; - } + if (child.nodeType !== 1) continue; - return keyframes; + switch (child.nodeName) { + case 'float_array': + data.array = parseFloats(child.textContent); + break; - // array sort function + case 'Name_array': + data.array = parseStrings(child.textContent); + break; - function ascending( a, b ) { + case 'technique_common': + var accessor = getElementsByTagName(child, 'accessor')[0]; - return a.time - b.time; + if (accessor !== undefined) { + data.stride = parseInt(accessor.getAttribute('stride')); + } + break; + } + } - } + return data; + } - } + function parseGeometryVertices(xml) { + var data = {}; - var position = new THREE.Vector3(); - var scale = new THREE.Vector3(); - var quaternion = new THREE.Quaternion(); + for (var i = 0; i < xml.childNodes.length; i++) { + var child = xml.childNodes[i]; - function createKeyframeTracks( animation, tracks ) { + if (child.nodeType !== 1) continue; - var keyframes = animation.keyframes; - var name = animation.name; + data[child.getAttribute('semantic')] = parseId( + child.getAttribute('source') + ); + } - var times = []; - var positionData = []; - var quaternionData = []; - var scaleData = []; + return data; + } - for ( var i = 0, l = keyframes.length; i < l; i ++ ) { + function parseGeometryPrimitive(xml) { + var primitive = { + type: xml.nodeName, + material: xml.getAttribute('material'), + count: parseInt(xml.getAttribute('count')), + inputs: {}, + stride: 0 + }; - var keyframe = keyframes[ i ]; + for (var i = 0, l = xml.childNodes.length; i < l; i++) { + var child = xml.childNodes[i]; - var time = keyframe.time; - var value = keyframe.value; + if (child.nodeType !== 1) continue; - matrix.fromArray( value ).transpose(); - matrix.decompose( position, quaternion, scale ); + 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); + break; - 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 ); + case 'vcount': + primitive.vcount = parseInts(child.textContent); + break; - } + case 'p': + primitive.p = parseInts(child.textContent); + break; + } + } - 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 primitive; + } - return tracks; + function groupPrimitives(primitives) { + var build = {}; - } + for (var i = 0; i < primitives.length; i++) { + var primitive = primitives[i]; - function transformAnimationData( keyframes, property, defaultValue ) { + if (build[primitive.type] === undefined) build[primitive.type] = []; - var keyframe; + build[primitive.type].push(primitive); + } - var empty = true; - var i, l; + return build; + } - // check, if values of a property are missing in our keyframes + function buildGeometry(data) { + var build = {}; - for ( i = 0, l = keyframes.length; i < l; i ++ ) { + var sources = data.sources; + var vertices = data.vertices; + var primitives = data.primitives; - keyframe = keyframes[ i ]; + if (primitives.length === 0) return {}; - if ( keyframe.value[ property ] === undefined ) { + // our goal is to create one buffer geoemtry for a single type of primitives + // first, we group all primitives by their type + + var groupedPrimitives = groupPrimitives(primitives); + + for (var type in groupedPrimitives) { + // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry + + build[type] = buildGeometryType( + groupedPrimitives[type], + sources, + vertices + ); + } + + return build; + } + + 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': + 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 + ); + } + 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; + } + } + } + + // build 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) { + return getBuild(library.geometries[id], buildGeometry); + } + + // 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: + break; + } + } + + 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; - keyframe.value[ property ] = null; // mark as missing + case 'skeleton': + data.skeletons.push(parseId(child.textContent)); + break; - } else { + 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 = getNode(skeleton); + + // setup bone data for a single bone hierarchy + + buildBoneHierarchy(root, joints, boneData); + } + + // 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; + } + } + } - empty = false; + // 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; + } + } - if ( empty === true ) { + // setup arrays for skeleton creation - // no values at all, so we set a default value + var bones = []; + var boneInverses = []; - for ( i = 0, l = keyframes.length; i < l; i ++ ) { + for (i = 0; i < sortedBoneData.length; i++) { + data = sortedBoneData[i]; - keyframe = keyframes[ i ]; + bones.push(data.bone); + boneInverses.push(data.boneInverse); + } - keyframe.value[ property ] = defaultValue; + return new THREE.Skeleton(bones, boneInverses); + } - } + function buildBoneHierarchy(root, joints, boneData) { + if (root === undefined) { + console.log('buildBoneHierarchy: Undefined root node given, return'); + return; + } - } else { + // setup bone data from visual scene - // filling gaps + root.traverse(function(object) { + if (object.isBone === true) { + var boneInverse; - createMissingKeyframes( keyframes, property ); + // 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; + } + } - function createMissingKeyframes( keyframes, property ) { + 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. - var prev, next; + boneInverse = new THREE.Matrix4(); + } - for ( var i = 0, l = keyframes.length; i < l; i ++ ) { + boneData.push({ + bone: object, + boneInverse: boneInverse, + processed: false + }); + } + }); + } - var keyframe = keyframes[ i ]; + function buildNode(data) { + var objects = []; - if ( keyframe.value[ property ] === null ) { + 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; - prev = getPrev( keyframes, i, property ); - next = getNext( keyframes, i, property ); + // nodes - if ( prev === null ) { + for (var i = 0, l = nodes.length; i < l; i++) { + objects.push(getNode(nodes[i])); + } - keyframe.value[ property ] = next.value[ property ]; - continue; + // instance cameras - } + for (var i = 0, l = instanceCameras.length; i < l; i++) { + objects.push(getCamera(instanceCameras[i]).clone()); + } - if ( next === null ) { + // instance controllers - keyframe.value[ property ] = prev.value[ property ]; - continue; + 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; - interpolate( keyframe, prev, next, property ); + 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); + } + } - function getPrev( keyframes, i, property ) { + // instance lights - while ( i >= 0 ) { + for (var i = 0, l = instanceLights.length; i < l; i++) { + objects.push(getLight(instanceLights[i]).clone()); + } - var keyframe = keyframes[ i ]; + // instance geometries - if ( keyframe.value[ property ] !== null ) return keyframe; + for (var i = 0, l = instanceGeometries.length; i < l; i++) { + var instance = instanceGeometries[i]; - 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); - return null; + for (var j = 0, jl = newObjects.length; j < jl; j++) { + objects.push(newObjects[j]); + } + } - } + // instance nodes - function getNext( keyframes, i, property ) { + for (var i = 0, l = instanceNodes.length; i < l; i++) { + objects.push(getNode(instanceNodes[i]).clone()); + } - while ( i < keyframes.length ) { + var object; - var keyframe = keyframes[ i ]; + if (nodes.length === 0 && objects.length === 1) { + object = objects[0]; + } else { + object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group(); - if ( keyframe.value[ property ] !== null ) return keyframe; + for (var i = 0; i < objects.length; i++) { + object.add(objects[i]); + } + } - i ++; + object.name = type === 'JOINT' ? data.sid : data.name; + object.matrix.copy(matrix); + object.matrix.decompose(object.position, object.quaternion, object.scale); - } + return object; + } - return null; + 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)); + } - function interpolate( key, prev, next, property ) { + return materials; + } - if ( ( next.time - prev.time ) === 0 ) { + function buildObjects(geometries, instanceMaterials) { + var objects = []; - key.value[ property ] = prev.value[ property ]; - return; + for (var type in geometries) { + var geometry = geometries[type]; - } + var materials = resolveMaterialBinding( + geometry.materialKeys, + instanceMaterials + ); - key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ]; + // 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()); + } + } - // animation clips + // regard skinning - function parseAnimationClip( xml ) { + var skinning = geometry.data.attributes.skinIndex !== undefined; - var data = { - name: xml.getAttribute( 'id' ) || 'default', - start: parseFloat( xml.getAttribute( 'start' ) || 0 ), - end: parseFloat( xml.getAttribute( 'end' ) || 0 ), - animations: [] - }; + if (skinning) { + for (var i = 0, l = materials.length; i < l; i++) { + materials[i].skinning = true; + } + } - for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + // choose between a single or multi materials (material array) - var child = xml.childNodes[ i ]; + var material = materials.length === 1 ? materials[0] : materials; - if ( child.nodeType !== 1 ) continue; + // now create a specific 3D object - switch ( child.nodeName ) { + var object; - case 'instance_animation': - data.animations.push( parseId( child.getAttribute( 'url' ) ) ); - break; + 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; + } - library.clips[ xml.getAttribute( 'id' ) ] = data; + objects.push(object); + } - } + return objects; + } - function buildAnimationClip( data ) { + function getNode(id) { + return getBuild(library.nodes[id], buildNode); + } - var tracks = []; + // visual scenes - var name = data.name; - var duration = ( data.end - data.start ) || - 1; - var animations = data.animations; + function parseVisualScene(xml) { + var data = { + name: xml.getAttribute('name'), + children: [] + }; - for ( var i = 0, il = animations.length; i < il; i ++ ) { + prepareNodes(xml); - var animationTracks = getAnimation( animations[ i ] ); + var elements = getElementsByTagName(xml, 'node'); - for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) { + for (var i = 0; i < elements.length; i++) { + data.children.push(parseNode(elements[i])); + } - tracks.push( animationTracks[ j ] ); + library.visualScenes[xml.getAttribute('id')] = data; + } - } + function buildVisualScene(data) { + var group = new THREE.Group(); + group.name = data.name; - } + var children = data.children; - return new THREE.AnimationClip( name, duration, tracks ); + for (var i = 0; i < children.length; i++) { + var child = children[i]; - } + if (child.id === null) { + group.add(buildNode(child)); + } else { + // if there is an ID, let's try to get the finished build (e.g. joints are already build) - function getAnimationClip( id ) { + group.add(getNode(child.id)); + } + } - return getBuild( library.clips[ id ], buildAnimationClip ); + return group; + } - } + function getVisualScene(id) { + return getBuild(library.visualScenes[id], buildVisualScene); + } - // controller + // scenes - function parseController( xml ) { + function parseScene(xml) { + var instance = getElementsByTagName(xml, 'instance_visual_scene')[0]; + return getVisualScene(parseId(instance.getAttribute('url'))); + } - 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': - data.id = parseId( child.getAttribute( 'source' ) ); - console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' ); - break; - - } - - } - - library.controllers[ xml.getAttribute( 'id' ) ] = data; - - } - - 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 ]; - - 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; - - } - - 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; - - } - - } - - 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 = {parameters:{}}; - - 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; - - case 'extra': - data.parameters = Object.assign({}, data.parameters, parseEffectParameters( child ) ); - break; - - case 'bump': - data.bump = parseEffectParameter(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 'ambient': - case 'emission': - case 'diffuse': - case 'specular': - case 'shininess': - case 'transparent': - case 'transparency': - case 'reflective': - var effect = parseEffectParameter( child ); - if (child.nodeName!=='ambient' || effect.texture ) - { - data[ child.nodeName ] = effect; - } - break; - - case 'technique': - return parseEffectTechnique(child); - } - } - - 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 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 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 getTexturePath( textureObject ) { - var sampler = effect.profile.samplers[ textureObject.id ]; - var surface = effect.profile.surfaces[ sampler.source ]; - - if ( sampler !== undefined && surface !== null) { - - var img = getImage( surface.init_from ); - - return textureLoader.path+img; - } - } - - function isTexturePBR( textureObject ) { - - var p = getTexturePath(textureObject); - if (!p) return false; - - return (p.indexOf("PBR_") >= 0 || p.indexOf("PBRFULL_") >= 0); - } - - function getTexture( textureObject) { - - var sampler = effect.profile.samplers[ textureObject.id ]; - var surface = effect.profile.surfaces[ sampler.source ]; - - if ( sampler !== undefined && surface !== null) { - var img = getImage( surface.init_from ); - if (img) - { - var texture = textureLoader.load( img, textureLoadedCallback ); - - 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; - } - - } - - return null; - - } - - var parameters = technique.parameters; - var parameter; - var pbrMaterial = null; - - var pbrKeyToThreeJSMap = { - 'Base_Color': 'map', - 'Metallic': 'metalnessMap', - 'Roughness': 'roughnessMap', - 'Mixed_AO': 'aoMap', - 'Emissive': 'emissiveMap', - 'Normal': 'normalMap', - 'Height': 'displacementMap' - }; - - for ( var key in parameters ) - { - parameter = parameters[ key ]; - if (parameter.texture) - { - var texturePath = getTexturePath(parameter.texture); - - if (texturePath && (texturePath.indexOf("PBR_") >= 0 || texturePath.indexOf("PBRFULL_") >= 0) ) - - for (var k in pbrKeyToThreeJSMap) - { - if (texturePath.indexOf(k) >= 0) - { - if (pbrMaterial === null) - { - pbrMaterial = {}; - } - - pbrMaterial[pbrKeyToThreeJSMap[k]] = texturePath; - - if (texturePath.indexOf("PBRFULL_") >= 0 && k==='Base_Color') - { - // If the prefix is PBRFULL it means that all the PBR textures are present - // but that only the basic color map have been linked to the dae. - - var allpbrkeywords = ['Metallic','Mixed_AO','Roughness','Normal','Roughness']; - - for(var pbri in allpbrkeywords) - { - var pbrk = allpbrkeywords[pbri]; - var path = texturePath; - - path = path.replace('Base_Color', pbrk); - if (pbrk==='Normal') - { - // Normal map must be png. In case the Base_Color is a jpg, - // change extension to png - - path = path.replace('.jpg', '.png'); - path = path.replace('.jpeg', '.png'); - } - - pbrMaterial[pbrKeyToThreeJSMap[pbrk]] = path; - } - } - } - } - } - - switch ( key ) { - case 'diffuse': - if ( parameter.color ) material.color.fromArray( parameter.color ); - if ( parameter.texture && !isTexturePBR(parameter.texture)) material.map = getTexture( parameter.texture ); - break; - case 'specular': - if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color ); - if ( parameter.texture && !isTexturePBR(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; - case 'transparent': - if ( parameter.texture && !isTexturePBR(parameter.texture)) material.alphaMap = getTexture( parameter.texture ); - material.transparent = true; - break; - case 'transparency': - if ( parameter.float !== undefined ) material.opacity = parameter.float; - material.transparent = true; - break; - } - } - - if (pbrMaterial !== null) - { - material.pbrMaterialDescription = pbrMaterial; // If PBR material has been found, store it for future usage - } - - 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 ) { - - return getBuild( library.cameras[ id ], buildCamera ); - - } - - // 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 ) { - - return getBuild( library.lights[ id ], buildLight ); - - } - - // geometry - - function parseGeometry( xml ) { - - var data = { - name: xml.getAttribute( 'name' ), - sources: {}, - vertices: {}, - primitives: [] - }; - - 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: - break; - - } - - } - - 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 - }; - - 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 ); - 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 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 geoemtry for a single type of primitives - // first, we group all primitives by their type - - var groupedPrimitives = groupPrimitives( primitives ); - - for ( var type in groupedPrimitives ) { - - // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry - - build[ type ] = buildGeometryType( groupedPrimitives[ type ], sources, vertices ); - - } - - return build; - - } - - 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': - 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 ); - - } - 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; - - } - - } - - } - - // build 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 ) { - - return getBuild( library.geometries[ id ], buildGeometry ); - - } - - // 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: - break; - - } - - } - - 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 = getNode( skeleton ); - - // setup bone data for a single bone hierarchy - - buildBoneHierarchy( root, joints, boneData ); - - } - - // 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 ) { - if (root === undefined) { - console.log('buildBoneHierarchy: Undefined root node given, return'); - return; - } - - // 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 ++ ) { - - objects.push( getCamera( instanceCameras[ i ] ).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 ++ ) { - - objects.push( getLight( instanceLights[ i ] ).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 ] ); - - } - - } - - 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 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 ]; - - if ( child.id === null ) { - - group.add( buildNode( child ) ); - - } else { - - // if there is an ID, let's try to get the finished build (e.g. joints are already build) - - group.add( getNode( child.id ) ); - - } - - } - - return group; - - } - - 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' ); - - 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: {} - }; - - var textureLoadedCallback = this.textureLoadedCallback; - - //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' ) { + 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'); + + 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: {} + }; + + var textureLoadedCallback = this.textureLoadedCallback; + + //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.rotation.x = - Math.PI / 2; }*/ - scene.scale.multiplyScalar( asset.unit ); - - //console.timeEnd( 'THREE.ColladaLoader' ); - - return { - animations: animations, - kinematics: kinematics, - library: library, - scene: scene - }; + scene.scale.multiplyScalar(asset.unit); - } + //console.timeEnd( 'THREE.ColladaLoader' ); - }; + return { + animations: animations, + kinematics: kinematics, + library: library, + scene: scene + }; + } +}; diff --git a/gz3d/client/js/include/ThreeBackwardsCompatibility.js b/gz3d/client/js/include/ThreeBackwardsCompatibility.js index 2260fda..8bc34c8 100644 --- a/gz3d/client/js/include/ThreeBackwardsCompatibility.js +++ b/gz3d/client/js/include/ThreeBackwardsCompatibility.js @@ -2,21 +2,18 @@ * Created by Sandro Weber (webers@in.tum.de) on 07.08.15. */ - /** * getDescendants() was removed in r68, this reimplements it to ensure compatibility * all getDescendants() calls could eventually be replaced with Object3D.traverse(function(node){...}); */ -THREE.Object3D.prototype.getDescendants = function ( array ) { - if ( array === undefined ) array = []; - - Array.prototype.push.apply( array, this.children ); - - for ( var i = 0, l = this.children.length; i < l; i ++ ) { +THREE.Object3D.prototype.getDescendants = function(array) { + if (array === undefined) array = []; - this.children[ i ].getDescendants( array ); + Array.prototype.push.apply(array, this.children); - } + for (var i = 0, l = this.children.length; i < l; i++) { + this.children[i].getDescendants(array); + } - return array; -}; \ No newline at end of file + return array; +}; diff --git a/gz3d/client/js/include/avatar-controls.js b/gz3d/client/js/include/avatar-controls.js index 455e67c..5d51035 100644 --- a/gz3d/client/js/include/avatar-controls.js +++ b/gz3d/client/js/include/avatar-controls.js @@ -5,8 +5,7 @@ /* global THREE: true */ /* global console: false */ -THREE.AvatarControls = function(userNavigationService, gz3d) -{ +THREE.AvatarControls = function(userNavigationService, gz3d) { 'use strict'; var that = this; @@ -40,7 +39,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.azimuth = 0.0; this.zenith = 0.0; this.zenithMin = Math.PI; - this.zenithMax = 2*Math.PI; + this.zenithMax = 2 * Math.PI; this.azimuthOnRotStart = this.azimuth; this.zenithOnRotStart = this.zenith; @@ -76,7 +75,9 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.cameraLookDirection = new THREE.Vector3(); // init ROS velocity topic for avatar movement - this.rosConnection = this.userNavigationService.roslib.getOrCreateConnectionTo(this.userNavigationService.rosbridgeWebsocketUrl); + this.rosConnection = this.userNavigationService.roslib.getOrCreateConnectionTo( + this.userNavigationService.rosbridgeWebsocketUrl + ); this.linearVelocity = new THREE.Vector3(); @@ -94,22 +95,23 @@ THREE.AvatarControls = function(userNavigationService, gz3d) }; this.createAvatarTopics = function(avatarName) { - this.linearVelocityTopicName = '/' + avatarName + '/user_avatar_basic/body/cmd_vel'; + this.linearVelocityTopicName = + '/' + avatarName + '/user_avatar_basic/body/cmd_vel'; this.linearVelocityTopic = this.userNavigationService.roslib.createTopic( - this.rosConnection, - this.linearVelocityTopicName, - 'geometry_msgs/Vector3' + this.rosConnection, + this.linearVelocityTopicName, + 'geometry_msgs/Vector3' ); this.avatarRotationTopicName = '/' + avatarName + '/cmd_rot'; this.avatarRotationTopic = this.userNavigationService.roslib.createTopic( - this.rosConnection, - this.avatarRotationTopicName, - 'geometry_msgs/Quaternion' + this.rosConnection, + this.avatarRotationTopicName, + 'geometry_msgs/Quaternion' ); }; - this.onMouseDown = function (event) { + this.onMouseDown = function(event) { // HBP-NRP: The next three lines are commented since this leads to problems in chrome with respect // to AngularJS, also see: [NRRPLT-1992] //if (this.domElementPointerBindings !== document) { @@ -135,7 +137,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) } }; - this.onMouseUp = function (event) { + this.onMouseUp = function(event) { event.preventDefault(); // We do not stop the event propagation here, since there may be other @@ -156,7 +158,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) } }; - this.onMouseMove = function (event) { + this.onMouseMove = function(event) { // only update the position, when a mouse button is pressed // else end the lookAround-mode @@ -171,7 +173,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) } }; - this.onTouchStart = function (event) { + this.onTouchStart = function(event) { switch (event.touches.length) { case 1: // look around @@ -182,7 +184,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) return; } - that.singleTouchPosOnStart.set(event.touches[0].pageX, event.touches[0].pageY); + that.singleTouchPosOnStart.set( + event.touches[0].pageX, + event.touches[0].pageY + ); that.singleTouchPosCurrent.copy(that.singleTouchPosOnStart); that.azimuthOnRotStart = that.azimuth; that.zenithOnRotStart = that.zenith; @@ -194,25 +199,36 @@ THREE.AvatarControls = function(userNavigationService, gz3d) // move that.endLookAround(); - var touch1 = new THREE.Vector2(event.touches[0].pageX, event.touches[0].pageY); - var touch2 = new THREE.Vector2(event.touches[1].pageX, event.touches[1].pageY); + var touch1 = new THREE.Vector2( + event.touches[0].pageX, + event.touches[0].pageY + ); + var touch2 = new THREE.Vector2( + event.touches[1].pageX, + event.touches[1].pageY + ); // Compute distance of both touches when they start touching the display that.doubleTouchDistanceOnStart = touch1.distanceTo(touch2); // Compute the mid of both touches - that.doubleTouchMidPosOnStart.addVectors(touch1, touch2).divideScalar(2.0); + that.doubleTouchMidPosOnStart + .addVectors(touch1, touch2) + .divideScalar(2.0); break; } }; - this.onTouchMove = function (event) { + this.onTouchMove = function(event) { event.preventDefault(); switch (event.touches.length) { case 1: // look around - that.singleTouchPosCurrent.set(event.touches[0].pageX, event.touches[0].pageY); + that.singleTouchPosCurrent.set( + event.touches[0].pageX, + event.touches[0].pageY + ); break; case 2: @@ -220,12 +236,19 @@ THREE.AvatarControls = function(userNavigationService, gz3d) that.endLookAround(); // Compute distance of both touches - var touch1 = new THREE.Vector2(event.touches[0].pageX, event.touches[0].pageY); - var touch2 = new THREE.Vector2(event.touches[1].pageX, event.touches[1].pageY); + var touch1 = new THREE.Vector2( + event.touches[0].pageX, + event.touches[0].pageY + ); + var touch2 = new THREE.Vector2( + event.touches[1].pageX, + event.touches[1].pageY + ); //TODO: this could become relative to the magnitude of the delta later that.doubleTouchDistanceCurrent = touch1.distanceTo(touch2); - var distanceDelta = that.doubleTouchDistanceCurrent - that.doubleTouchDistanceOnStart; + var distanceDelta = + that.doubleTouchDistanceCurrent - that.doubleTouchDistanceOnStart; // move forward / backward if (distanceDelta > that.TOUCH_MOVE_THRESHOLD) { that.moveForward = true; @@ -236,8 +259,13 @@ THREE.AvatarControls = function(userNavigationService, gz3d) that.moveBackward = false; } - that.doubleTouchMidPosCurrent.addVectors(touch1, touch2).divideScalar(2.0); - var posDelta = new THREE.Vector2().sub(that.doubleTouchMidPosCurrent, that.doubleTouchMidPosOnStart); + that.doubleTouchMidPosCurrent + .addVectors(touch1, touch2) + .divideScalar(2.0); + var posDelta = new THREE.Vector2().sub( + that.doubleTouchMidPosCurrent, + that.doubleTouchMidPosOnStart + ); // move up / down if (posDelta.y > that.TOUCH_MOVE_THRESHOLD) { that.moveDown = true; @@ -261,7 +289,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) } }; - this.onTouchEnd = function (event) { + this.onTouchEnd = function(event) { switch (event.touches.length) { case 0: that.endLookAround(); @@ -290,82 +318,105 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.moveDown = false; }; - this.onKeyDown = function (event) { - if(that.keyboardBindingsEnabled === false || event.metaKey || event.ctrlKey) { + this.onKeyDown = function(event) { + if ( + that.keyboardBindingsEnabled === false || + event.metaKey || + event.ctrlKey + ) { return; } that.shiftHold = event.shiftKey; that.altHold = event.altKey; - switch(event.code) { - case "ArrowUp": - that.keyboardRotateUp = true; break; - case "KeyW": - that.moveForward = true; break; + switch (event.code) { + case 'ArrowUp': + that.keyboardRotateUp = true; + break; + case 'KeyW': + that.moveForward = true; + break; - case "ArrowLeft": - that.keyboardRotateLeft = true; break; - case "KeyA": - that.moveLeft = true; break; + case 'ArrowLeft': + that.keyboardRotateLeft = true; + break; + case 'KeyA': + that.moveLeft = true; + break; - case "ArrowDown": - that.keyboardRotateDown = true; break; - case "KeyS": - that.moveBackward = true; break; + case 'ArrowDown': + that.keyboardRotateDown = true; + break; + case 'KeyS': + that.moveBackward = true; + break; - case "ArrowRight": - that.keyboardRotateRight = true; break; - case "KeyD": - that.moveRight = true; break; + case 'ArrowRight': + that.keyboardRotateRight = true; + break; + case 'KeyD': + that.moveRight = true; + break; - case "PageUp": - case "KeyR": - that.moveUp = true; break; + case 'PageUp': + case 'KeyR': + that.moveUp = true; + break; - case "PageDown": - case "KeyF": - that.moveDown = true; break; + case 'PageDown': + case 'KeyF': + that.moveDown = true; + break; } event.preventDefault(); }; - this.onKeyUp = function (event) { - if(that.keyboardBindingsEnabled === false) { + this.onKeyUp = function(event) { + if (that.keyboardBindingsEnabled === false) { return; } that.shiftHold = event.shiftKey; that.altHold = event.altKey; - switch(event.code) { - case "ArrowUp": - that.keyboardRotateUp = false; break; - case "KeyW": - that.moveForward = false; break; - - case "ArrowLeft": - that.keyboardRotateLeft = false; break; - case "KeyA": - that.moveLeft = false; break; - - case "ArrowDown": - that.keyboardRotateDown = false; break; - case "KeyS": - that.moveBackward = false; break; - - case "ArrowRight": - that.keyboardRotateRight = false; break; - case "KeyD": - that.moveRight = false; break; - - case "PageUp": - case "KeyR": - that.moveUp = false; break; - - case "PageDown": - case "KeyF": - that.moveDown = false; break; - - case "KeyT": - { + switch (event.code) { + case 'ArrowUp': + that.keyboardRotateUp = false; + break; + case 'KeyW': + that.moveForward = false; + break; + + case 'ArrowLeft': + that.keyboardRotateLeft = false; + break; + case 'KeyA': + that.moveLeft = false; + break; + + case 'ArrowDown': + that.keyboardRotateDown = false; + break; + case 'KeyS': + that.moveBackward = false; + break; + + case 'ArrowRight': + that.keyboardRotateRight = false; + break; + case 'KeyD': + that.moveRight = false; + break; + + case 'PageUp': + case 'KeyR': + that.moveUp = false; + break; + + case 'PageDown': + case 'KeyF': + that.moveDown = false; + break; + + case 'KeyT': { that.thirdPerson = !that.thirdPerson; if (that.thirdPerson) { that.camera.position.set(0, -5, that.avatarEyeHeight + 1); @@ -384,7 +435,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) // rotate left/right // rotation happens around the world up axis so up remains up (no upside-down) this.azimuth += rightAmount; - this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith - upAmount)); + this.zenith = Math.max( + this.zenithMin, + Math.min(this.zenithMax, this.zenith - upAmount) + ); }; /** @@ -396,7 +450,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.applyPosePosition = position; this.applyPoseLookAt = lookAt; this.applyPoseDuringUpdate = true; - } + }; /** * set pose from a position and point to look at @@ -404,7 +458,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) * @param lookAt THREE.Vector3 point to look at (world coordinates) */ this.setPose = function(position, lookAt) { - if (!(position instanceof THREE.Vector3) || !(lookAt instanceof THREE.Vector3)) { + if ( + !(position instanceof THREE.Vector3) || + !(lookAt instanceof THREE.Vector3) + ) { return; } @@ -414,15 +471,17 @@ THREE.AvatarControls = function(userNavigationService, gz3d) //TODO: animate "fall down" this.avatar.position.copy(posOnGround); this.avatar.updateMatrixWorld(); - } - else { + } else { this.avatar.position.copy(position); this.avatar.updateMatrixWorld(); } // update orientation var camWorldPosition = this.camera.getWorldPosition(); - var vecForward = new THREE.Vector3().add(lookAt).sub(camWorldPosition).normalize(); + var vecForward = new THREE.Vector3() + .add(lookAt) + .sub(camWorldPosition) + .normalize(); this.updateSphericalAnglesFromForwardVector(vecForward); }; @@ -447,10 +506,16 @@ THREE.AvatarControls = function(userNavigationService, gz3d) // check for intersection below position var upVector = this.camera.up.clone(); var raycaster = new THREE.Raycaster(position, upVector.negate()); - var intersects = raycaster.intersectObjects(gz3d.scene.scene.children, true); - for (var i=0; i < intersects.length; i=i+1) { + var intersects = raycaster.intersectObjects( + gz3d.scene.scene.children, + true + ); + for (var i = 0; i < intersects.length; i = i + 1) { // check that we hit some actual geometry and it is not our own avatar - if (intersects[i].object instanceof THREE.Mesh && !isObjectPartOfAvatar(intersects[i].object)) { + if ( + intersects[i].object instanceof THREE.Mesh && + !isObjectPartOfAvatar(intersects[i].object) + ) { return intersects[i].point; } } @@ -458,8 +523,11 @@ THREE.AvatarControls = function(userNavigationService, gz3d) // if no hits below, check if avatar can be placed above raycaster.set(position, upVector.negate()); intersects = raycaster.intersectObjects(gz3d.scene.scene.children, true); - for (var j=0; j < intersects.length; j=j+1) { - if (intersects[i].object instanceof THREE.Mesh && !isObjectPartOfAvatar(intersects[i].object)) { + for (var j = 0; j < intersects.length; j = j + 1) { + if ( + intersects[i].object instanceof THREE.Mesh && + !isObjectPartOfAvatar(intersects[i].object) + ) { return intersects[i].point; } } @@ -477,19 +545,26 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.azimuth = Math.atan2(vecForward.y, vecForward.x) - 0.5 * Math.PI; this.azimuthOnRotStart = this.azimuth; - this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, Math.acos(vecForward.z) + Math.PI)); + this.zenith = Math.max( + this.zenithMin, + Math.min(this.zenithMax, Math.acos(vecForward.z) + Math.PI) + ); this.zenithOnRotStart = this.zenith; }; - this.updateSphericalAnglesFromUserInput = function(timeDelta, rotationSensitivity) { + this.updateSphericalAnglesFromUserInput = function( + timeDelta, + rotationSensitivity + ) { /* --- rotation by means of a manipulator --- */ - var speedup ; + var speedup; if (this.shiftHold) speedup = 2.0; else if (this.altHold) speedup = 0.3; else speedup = 1.0; - var keyboardRotationSpeed = speedup * this.KEYBOARD_ROTATION_SPEED * rotationSensitivity; + var keyboardRotationSpeed = + speedup * this.KEYBOARD_ROTATION_SPEED * rotationSensitivity; if (this.keyboardRotateUp || this.keyboardRotateDown) { var sign = this.keyboardRotateUp ? 1.0 : -1.0; this.keyboardRotate(0.0, sign * keyboardRotationSpeed); @@ -508,7 +583,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) actualLookSpeed = 0; } - var mouseDelta = new THREE.Vector2().subVectors(this.mousePosCurrent, this.mousePosOnKeyDown); + var mouseDelta = new THREE.Vector2().subVectors( + this.mousePosCurrent, + this.mousePosOnKeyDown + ); this.azimuth = this.azimuthOnRotStart - mouseDelta.x * actualLookSpeed; @@ -516,7 +594,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) if (this.lookVertical) { this.zenith = this.zenithOnRotStart + mouseDelta.y * actualLookSpeed; - this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith)); + this.zenith = Math.max( + this.zenithMin, + Math.min(this.zenithMax, this.zenith) + ); } else { this.zenith = Math.PI / 2; } @@ -529,7 +610,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) actualLookSpeed = 0; } - var touchDelta = new THREE.Vector2().subVectors(this.singleTouchPosCurrent, this.singleTouchPosOnStart); + var touchDelta = new THREE.Vector2().subVectors( + this.singleTouchPosCurrent, + this.singleTouchPosOnStart + ); this.azimuth = this.azimuthOnRotStart + touchDelta.x * actualLookSpeed; @@ -537,7 +621,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) if (this.lookVertical) { this.zenith = this.zenithOnRotStart - touchDelta.y * actualLookSpeed; - this.zenith = Math.max(this.zenithMin, Math.min(this.zenithMax, this.zenith)); + this.zenith = Math.max( + this.zenithMin, + Math.min(this.zenithMax, this.zenith) + ); } else { this.zenith = Math.PI / 2; } @@ -554,7 +641,10 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.azimuth += 2 * Math.PI; }*/ - this.avatarRotation.setFromAxisAngle(new THREE.Vector3(0, 0, 1), this.azimuth); + this.avatarRotation.setFromAxisAngle( + new THREE.Vector3(0, 0, 1), + this.azimuth + ); this.avatarRotation.normalize(); }; @@ -577,24 +667,24 @@ THREE.AvatarControls = function(userNavigationService, gz3d) */ this.updateCameraRotation = function() { var rotation = new THREE.Quaternion(); - rotation.multiply(new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(-1, 0, 0), this.zenith)); + rotation.multiply( + new THREE.Quaternion().setFromAxisAngle( + new THREE.Vector3(-1, 0, 0), + this.zenith + ) + ); this.camera.quaternion.copy(rotation); this.camera.updateMatrixWorld(); }; this.updateLinearVelocity = function(delta, translationSensitivity) { - var speedFactor; if (this.shiftHold) { - speedFactor = this.SHIFT_SPEEDUP_FACTOR; - } - else if (this.altHold) - { + speedFactor = this.SHIFT_SPEEDUP_FACTOR; + } else if (this.altHold) { speedFactor = this.ALT_SPEEDUP_FACTOR; - } - else - { + } else { speedFactor = 1.0; } @@ -613,7 +703,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) if (this.moveRight) { this.linearVelocity.x = 1; } - if (!(this.lockVerticalMovement)) { + if (!this.lockVerticalMovement) { if (this.moveUp) { this.linearVelocity.z = 1; } @@ -641,8 +731,7 @@ THREE.AvatarControls = function(userNavigationService, gz3d) */ this.update = function(delta, translationSensitivity, rotationSensitivity) { if (!this.enabled) { - if (this.mouseRotationEnabled || this.touchRotationEnabled) - { + if (this.mouseRotationEnabled || this.touchRotationEnabled) { this.endLookAround(); } return; @@ -656,12 +745,13 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.setPose(this.applyPosePosition, this.applyPoseLookAt); gz3d.scene.emitter.emit('entityChanged', this.avatar); - var vecForward = new THREE.Vector3().subVectors(this.applyPoseLookAt, this.applyPosePosition).normalize(); + var vecForward = new THREE.Vector3() + .subVectors(this.applyPoseLookAt, this.applyPosePosition) + .normalize(); this.updateSphericalAnglesFromForwardVector(vecForward); this.applyPoseDuringUpdate = false; - } - else { + } else { if (!this.freeze) { this.updateLinearVelocity(delta, translationSensitivity); this.publishLinearVelocity(); @@ -689,33 +779,96 @@ THREE.AvatarControls = function(userNavigationService, gz3d) this.domElementPointerBindings = userViewDOM ? userViewDOM : document; this.domElementKeyboardBindings = document; - this.domElementPointerBindings.addEventListener('mousedown', this.onMouseDown, false); - this.domElementPointerBindings.addEventListener('mousemove', this.onMouseMove, false); - this.domElementPointerBindings.addEventListener('mouseup', this.onMouseUp, false); - this.domElementPointerBindings.addEventListener('touchstart', this.onTouchStart, false); - this.domElementPointerBindings.addEventListener('touchmove', this.onTouchMove, false); - this.domElementPointerBindings.addEventListener('touchend', this.onTouchEnd, false); + this.domElementPointerBindings.addEventListener( + 'mousedown', + this.onMouseDown, + false + ); + this.domElementPointerBindings.addEventListener( + 'mousemove', + this.onMouseMove, + false + ); + this.domElementPointerBindings.addEventListener( + 'mouseup', + this.onMouseUp, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchstart', + this.onTouchStart, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchmove', + this.onTouchMove, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchend', + this.onTouchEnd, + false + ); - this.domElementKeyboardBindings.addEventListener('keydown', this.onKeyDown, false); - this.domElementKeyboardBindings.addEventListener('keyup', this.onKeyUp, false); + this.domElementKeyboardBindings.addEventListener( + 'keydown', + this.onKeyDown, + false + ); + this.domElementKeyboardBindings.addEventListener( + 'keyup', + this.onKeyUp, + false + ); }; this.detachEventListeners = function() { if (this.domElementPointerBindings) { - this.domElementPointerBindings.removeEventListener('mousedown', this.onMouseDown, false); - this.domElementPointerBindings.removeEventListener('mousemove', this.onMouseMove, false); - this.domElementPointerBindings.removeEventListener('mouseup', this.onMouseUp, false); - this.domElementPointerBindings.removeEventListener('touchstart', this.onTouchStart, false); - this.domElementPointerBindings.removeEventListener('touchmove', this.onTouchMove, false); - this.domElementPointerBindings.removeEventListener('touchend', this.onTouchEnd, false); + this.domElementPointerBindings.removeEventListener( + 'mousedown', + this.onMouseDown, + false + ); + this.domElementPointerBindings.removeEventListener( + 'mousemove', + this.onMouseMove, + false + ); + this.domElementPointerBindings.removeEventListener( + 'mouseup', + this.onMouseUp, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchstart', + this.onTouchStart, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchmove', + this.onTouchMove, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchend', + this.onTouchEnd, + false + ); } if (this.domElementKeyboardBindings) { - this.domElementKeyboardBindings.removeEventListener('keydown', this.onKeyDown, false); - this.domElementKeyboardBindings.removeEventListener('keyup', this.onKeyUp, false); + this.domElementKeyboardBindings.removeEventListener( + 'keydown', + this.onKeyDown, + false + ); + this.domElementKeyboardBindings.removeEventListener( + 'keyup', + this.onKeyUp, + false + ); } }; - }; THREE.AvatarControls.prototype = Object.create(THREE.EventDispatcher.prototype); diff --git a/gz3d/client/js/include/gzwebsocket.js b/gz3d/client/js/include/gzwebsocket.js index aaf36ce..762ec4f 100644 --- a/gz3d/client/js/include/gzwebsocket.js +++ b/gz3d/client/js/include/gzwebsocket.js @@ -29,7 +29,7 @@ GZWebSocket.prototype.connect = function(url) { * @param event - the argument to emit with the event. */ function onOpen(event) { - console.log("connected!"); + console.log('connected!'); that.emit('connection', event); } @@ -66,16 +66,15 @@ GZWebSocket.prototype.connect = function(url) { } } - console.log("got msg!" + message.data); + console.log('got msg!' + message.data); var data = JSON.parse(message.data); handleMessage(data); - } - console.log("connecting to " + url); + console.log('connecting to ' + url); - this.socket = new WebSocket(url, "default"); + this.socket = new WebSocket(url, 'default'); this.socket.onopen = onOpen; this.socket.onclose = onClose; this.socket.onerror = onError; diff --git a/gz3d/client/js/include/lookat-controls.js b/gz3d/client/js/include/lookat-controls.js index 629d7f4..0d4bc92 100644 --- a/gz3d/client/js/include/lookat-controls.js +++ b/gz3d/client/js/include/lookat-controls.js @@ -6,8 +6,7 @@ /* global THREE: true */ /* global console: false */ -THREE.LookatControls = function (userView) -{ +THREE.LookatControls = function(userView) { 'use strict'; var that = this; @@ -31,7 +30,7 @@ THREE.LookatControls = function (userView) this.startTouchDistance = new THREE.Vector2(); this.startTouchMid = new THREE.Vector2(); - this.mouseWheelSensitivity = Math.min(0.25*this.minDistance,0.25); + this.mouseWheelSensitivity = Math.min(0.25 * this.minDistance, 0.25); this.speedUpFactor = 3.0; this.speedDownFactor = 0.1; @@ -49,8 +48,7 @@ THREE.LookatControls = function (userView) this.rotateUp = false; this.rotateDown = false; - this.onMouseDown = function (event) - { + this.onMouseDown = function(event) { // HBP-NRP: The next three lines are commented since this leads to problems in chrome with respect // to AngularJS, also see: [NRRPLT-1992] //if (this.domElement !== document) { @@ -61,10 +59,8 @@ THREE.LookatControls = function (userView) event.preventDefault(); - if (that.activeLook) - { - switch (event.button) - { + if (that.activeLook) { + switch (event.button) { case 0: that.mousePosLast.set(event.pageX, event.pageY); that.mousePosCurrent.copy(that.mousePosLast); @@ -74,24 +70,19 @@ THREE.LookatControls = function (userView) } }; - this.setLookatTarget = function (target) - { + this.setLookatTarget = function(target) { this.lookAtTarget = target; - if (this.lookAtTarget) - { + if (this.lookAtTarget) { var bbox = new THREE.Box3().setFromObject(this.lookAtTarget); - this.minDistance = bbox.getBoundingSphere().radius; - if (this.minDistance === Infinity) - { + this.minDistance = bbox.getBoundingSphere(new THREE.Sphere()).radius; + if (this.minDistance === Infinity) { this.minDistance = 0.25; } } - }; - this.onMouseUp = function (event) - { + this.onMouseUp = function(event) { event.preventDefault(); // We do not stop the event propagation here, since there may be other @@ -99,10 +90,8 @@ THREE.LookatControls = function (userView) // and expect the event to be fired. //event.stopPropagation(); - if (that.activeLook) - { - switch (event.button) - { + if (that.activeLook) { + switch (event.button) { case 0: that.mouseDragOn = false; break; @@ -110,29 +99,24 @@ THREE.LookatControls = function (userView) } }; - this.onMouseMove = function (event) - { + this.onMouseMove = function(event) { // only update the position, when a mouse button is pressed // else end the lookAround-mode - if (event.buttons !== 0) - { + if (event.buttons !== 0) { that.mousePosCurrent.set(event.pageX, event.pageY); - } else - { + } else { that.mouseDragOn = false; } }; - this.onMouseWheel = function (event) - { - var delta = Math.max(-1, Math.min(1, (-event.wheelDelta || event.detail))); - window.lookatControls.distanceDelta += delta * window.lookatControls.mouseWheelSensitivity; + this.onMouseWheel = function(event) { + var delta = Math.max(-1, Math.min(1, -event.wheelDelta || event.detail)); + window.lookatControls.distanceDelta += + delta * window.lookatControls.mouseWheelSensitivity; }; - this.onTouchStart = function (event) - { - switch (event.touches.length) - { + this.onTouchStart = function(event) { + switch (event.touches.length) { case 1: // look around that.mousePosLast.set(event.touches[0].pageX, event.touches[0].pageY); @@ -145,14 +129,15 @@ THREE.LookatControls = function (userView) } }; - this.onTouchMove = function (event) - { + this.onTouchMove = function(event) { event.preventDefault(); - switch (event.touches.length) - { + switch (event.touches.length) { case 1: // look around - that.mousePosCurrent.set(event.touches[0].pageX, event.touches[0].pageY); + that.mousePosCurrent.set( + event.touches[0].pageX, + event.touches[0].pageY + ); break; case 2: that.mouseDragOn = false; @@ -160,129 +145,133 @@ THREE.LookatControls = function (userView) } }; - this.onTouchEnd = function (event) - { + this.onTouchEnd = function(event) { that.mouseDragOn = false; }; - this.onKeyDown = function (event) - { - if (that.keyBindingsEnabled === false || event.metaKey || event.ctrlKey) - { + this.onKeyDown = function(event) { + if (that.keyBindingsEnabled === false || event.metaKey || event.ctrlKey) { return; } that.shiftHold = event.shiftKey; that.altHold = event.altKey; - switch (event.code) - { - case "PageUp": - case "KeyR": - that.moveForward = true; break; + switch (event.code) { + case 'PageUp': + case 'KeyR': + that.moveForward = true; + break; - case "PageDown": - case "KeyF": - that.moveBackward = true; break; + case 'PageDown': + case 'KeyF': + that.moveBackward = true; + break; - case "KeyA": - that.moveLeft = true; break; + case 'KeyA': + that.moveLeft = true; + break; - case "KeyD": - that.moveRight = true; break; + case 'KeyD': + that.moveRight = true; + break; - case "ArrowUp": - that.rotateUp = true; break; + case 'ArrowUp': + that.rotateUp = true; + break; - case "ArrowLeft": - that.rotateLeft = true; break; + case 'ArrowLeft': + that.rotateLeft = true; + break; - case "ArrowDown": - that.rotateDown = true; break; + case 'ArrowDown': + that.rotateDown = true; + break; - case "ArrowRight": - that.rotateRight = true; break; + case 'ArrowRight': + that.rotateRight = true; + break; - case "KeyW": - that.moveUp = true; break; + case 'KeyW': + that.moveUp = true; + break; - case "KeyS": - that.moveDown = true; break; + case 'KeyS': + that.moveDown = true; + break; } event.preventDefault(); }; - this.onKeyUp = function (event) - { - if (that.keyBindingsEnabled === false) - { + this.onKeyUp = function(event) { + if (that.keyBindingsEnabled === false) { return; } that.shiftHold = event.shiftKey; that.altHold = event.altKey; - switch (event.code) - { - - case "PageUp": - case "KeyR": - that.moveForward = false; break; + switch (event.code) { + case 'PageUp': + case 'KeyR': + that.moveForward = false; + break; - case "PageDown": - case "KeyF": - that.moveBackward = false; break; + case 'PageDown': + case 'KeyF': + that.moveBackward = false; + break; - case "KeyA": - that.moveLeft = false; break; + case 'KeyA': + that.moveLeft = false; + break; - case "KeyD": - that.moveRight = false; break; + case 'KeyD': + that.moveRight = false; + break; - case "ArrowUp": - that.rotateUp = false; break; + case 'ArrowUp': + that.rotateUp = false; + break; - case "ArrowLeft": - that.rotateLeft = false; break; + case 'ArrowLeft': + that.rotateLeft = false; + break; - case "ArrowDown": - that.rotateDown = false; break; + case 'ArrowDown': + that.rotateDown = false; + break; - case "ArrowRight": - that.rotateRight = false; break; + case 'ArrowRight': + that.rotateRight = false; + break; - case "KeyW": - that.moveUp = false; break; + case 'KeyW': + that.moveUp = false; + break; - case "KeyS": - that.moveDown = false; break; + case 'KeyS': + that.moveDown = false; + break; } event.preventDefault(); }; - this.setDistance = function (dist) - { + this.setDistance = function(dist) { this.applyAbsDistance = dist; }; - this.update = function (elapsed, translationSensitivity, rotationSensitivity) - { - if (!elapsed) - { + this.update = function(elapsed, translationSensitivity, rotationSensitivity) { + if (!elapsed) { elapsed = 0; } - if (this.shiftHold) - { + if (this.shiftHold) { elapsed *= this.speedUpFactor; - } - else if (this.altHold) - { + } else if (this.altHold) { elapsed *= this.speedDownFactor; } - if (!this.enabled) - { - if (this.mouseDragOn) - { + if (!this.enabled) { + if (this.mouseDragOn) { this.mouseDragOn = false; } return; @@ -290,38 +279,30 @@ THREE.LookatControls = function (userView) var delta = null; - if (this.rotateLeft || this.moveLeft) - { - if (!delta) - { - delta = new THREE.Vector2(0,0); + if (this.rotateLeft || this.moveLeft) { + if (!delta) { + delta = new THREE.Vector2(0, 0); } delta.x += elapsed * 0.1; } - if (this.rotateRight || this.moveRight) - { - if (!delta) - { - delta = new THREE.Vector2(0,0); + if (this.rotateRight || this.moveRight) { + if (!delta) { + delta = new THREE.Vector2(0, 0); } delta.x -= elapsed * 0.1; } - if (this.rotateUp || this.moveForward) - { - if (!delta) - { - delta = new THREE.Vector2(0,0); + if (this.rotateUp || this.moveForward) { + if (!delta) { + delta = new THREE.Vector2(0, 0); } delta.y += elapsed * 0.1; } - if (this.rotateDown || this.moveBackward) - { - if (!delta) - { - delta = new THREE.Vector2(0,0); + if (this.rotateDown || this.moveBackward) { + if (!delta) { + delta = new THREE.Vector2(0, 0); } delta.y -= elapsed * 0.1; } @@ -333,48 +314,53 @@ THREE.LookatControls = function (userView) currentDistance = this.currentVector.length(); this.currentVector.normalize(); - if (this.applyAbsDistance) - { + if (this.applyAbsDistance) { currentDistance = this.applyAbsDistance; this.applyAbsDistance = null; } - if (this.distanceDelta) - { + if (this.distanceDelta) { currentDistance += this.distanceDelta * translationSensitivity; this.distanceDelta = 0; - this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance); + this.averageDistance = currentDistance = Math.max( + Math.min(currentDistance, 20.0), + this.minDistance + ); } - - if (this.moveUp) - { - currentDistance -= elapsed * Math.min(0.004*this.minDistance,0.004); - currentDistance = Math.max(Math.min( currentDistance, 20.0), this.minDistance); - this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance); + if (this.moveUp) { + currentDistance -= elapsed * Math.min(0.004 * this.minDistance, 0.004); + currentDistance = Math.max( + Math.min(currentDistance, 20.0), + this.minDistance + ); + this.averageDistance = currentDistance = Math.max( + Math.min(currentDistance, 20.0), + this.minDistance + ); } - if (this.moveDown) - { - currentDistance += elapsed * Math.min(0.004*this.minDistance,0.004); - currentDistance = Math.max(Math.min( currentDistance, 20.0), this.minDistance); - this.averageDistance = currentDistance = Math.max(Math.min(currentDistance, 20.0), this.minDistance); + if (this.moveDown) { + currentDistance += elapsed * Math.min(0.004 * this.minDistance, 0.004); + currentDistance = Math.max( + Math.min(currentDistance, 20.0), + this.minDistance + ); + this.averageDistance = currentDistance = Math.max( + Math.min(currentDistance, 20.0), + this.minDistance + ); } - if (this.averageDistance <= 0) - { + if (this.averageDistance <= 0) { this.averageDistance = currentDistance; } - currentDistance = Math.min(currentDistance, this.averageDistance*1.2); + currentDistance = Math.min(currentDistance, this.averageDistance * 1.2); - - if (this.mouseDragOn) - { - - if (!delta) - { + if (this.mouseDragOn) { + if (!delta) { delta = new THREE.Vector2(); } @@ -384,22 +370,25 @@ THREE.LookatControls = function (userView) this.mousePosLast = this.mousePosCurrent.clone(); } - if (delta) - { - this.currentVector.applyAxisAngle(new THREE.Vector3(0, 0, -1), delta.x * 0.01 * rotationSensitivity); + if (delta) { + this.currentVector.applyAxisAngle( + new THREE.Vector3(0, 0, -1), + delta.x * 0.01 * rotationSensitivity + ); this.currentVector.z += delta.y * 0.01 * rotationSensitivity; this.currentVector.normalize(); this.currentVector.z = Math.max(this.currentVector.z, 0.18); this.currentVector.normalize(); this.averageZVector = this.currentVector.z; - } - else if (this.averageZVector<=0) - { + } else if (this.averageZVector <= 0) { this.averageZVector = this.currentVector.z; } - this.currentVector.z = Math.max(this.currentVector.z, this.averageZVector*0.8); + this.currentVector.z = Math.max( + this.currentVector.z, + this.averageZVector * 0.8 + ); this.currentVector.normalize(); var v = this.currentVector.clone(); @@ -410,67 +399,151 @@ THREE.LookatControls = function (userView) this.userView.camera.updateMatrixWorld(); }; - this.onMouseDownManipulator = function (action) - { + this.onMouseDownManipulator = function(action) { that[action] = true; }; - this.onMouseUpManipulator = function (action) - { + this.onMouseUpManipulator = function(action) { that[action] = false; }; - this.attachEventListeners = function () - { + this.attachEventListeners = function() { var userViewDOM = this.userView.container; this.domElementPointerBindings = userViewDOM ? userViewDOM : document; this.domElementKeyboardBindings = document; - this.domElementPointerBindings.addEventListener('contextmenu', function (event) { event.preventDefault(); }, false); - this.domElementPointerBindings.addEventListener('mousedown', this.onMouseDown, false); - this.domElementPointerBindings.addEventListener('mousemove', this.onMouseMove, false); - this.domElementPointerBindings.addEventListener('mouseup', this.onMouseUp, false); - this.domElementPointerBindings.addEventListener('touchstart', this.onTouchStart, false); - this.domElementPointerBindings.addEventListener('touchmove', this.onTouchMove, false); - this.domElementPointerBindings.addEventListener('touchend', this.onTouchEnd, false); - - this.domElementPointerBindings.addEventListener('mousewheel', this.onMouseWheel, false); - this.domElementPointerBindings.addEventListener('DOMMouseScroll', this.onMouseWheel, false); - - this.domElementKeyboardBindings.addEventListener('keydown', this.onKeyDown, false); - this.domElementKeyboardBindings.addEventListener('keyup', this.onKeyUp, false); + this.domElementPointerBindings.addEventListener( + 'contextmenu', + function(event) { + event.preventDefault(); + }, + false + ); + this.domElementPointerBindings.addEventListener( + 'mousedown', + this.onMouseDown, + false + ); + this.domElementPointerBindings.addEventListener( + 'mousemove', + this.onMouseMove, + false + ); + this.domElementPointerBindings.addEventListener( + 'mouseup', + this.onMouseUp, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchstart', + this.onTouchStart, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchmove', + this.onTouchMove, + false + ); + this.domElementPointerBindings.addEventListener( + 'touchend', + this.onTouchEnd, + false + ); + + this.domElementPointerBindings.addEventListener( + 'mousewheel', + this.onMouseWheel, + false + ); + this.domElementPointerBindings.addEventListener( + 'DOMMouseScroll', + this.onMouseWheel, + false + ); + + this.domElementKeyboardBindings.addEventListener( + 'keydown', + this.onKeyDown, + false + ); + this.domElementKeyboardBindings.addEventListener( + 'keyup', + this.onKeyUp, + false + ); }; - this.detachEventListeners = function () - { + this.detachEventListeners = function() { if (this.domElementPointerBindings) { - this.domElementPointerBindings.removeEventListener('contextmenu', function (event) { event.preventDefault(); }, false); - this.domElementPointerBindings.removeEventListener('mousedown', this.onMouseDown, false); - this.domElementPointerBindings.removeEventListener('mousemove', this.onMouseMove, false); - this.domElementPointerBindings.removeEventListener('mouseup', this.onMouseUp, false); - this.domElementPointerBindings.removeEventListener('touchstart', this.onTouchStart, false); - this.domElementPointerBindings.removeEventListener('touchmove', this.onTouchMove, false); - this.domElementPointerBindings.removeEventListener('touchend', this.onTouchEnd, false); - - this.domElementPointerBindings.removeEventListener('mousewheel', this.onMouseWheel, false); - this.domElementPointerBindings.removeEventListener('DOMMouseScroll', this.onMouseWheel, false); + this.domElementPointerBindings.removeEventListener( + 'contextmenu', + function(event) { + event.preventDefault(); + }, + false + ); + this.domElementPointerBindings.removeEventListener( + 'mousedown', + this.onMouseDown, + false + ); + this.domElementPointerBindings.removeEventListener( + 'mousemove', + this.onMouseMove, + false + ); + this.domElementPointerBindings.removeEventListener( + 'mouseup', + this.onMouseUp, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchstart', + this.onTouchStart, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchmove', + this.onTouchMove, + false + ); + this.domElementPointerBindings.removeEventListener( + 'touchend', + this.onTouchEnd, + false + ); + + this.domElementPointerBindings.removeEventListener( + 'mousewheel', + this.onMouseWheel, + false + ); + this.domElementPointerBindings.removeEventListener( + 'DOMMouseScroll', + this.onMouseWheel, + false + ); } if (this.domElementKeyboardBindings) { - this.domElementKeyboardBindings.removeEventListener('keydown', this.onKeyDown, false); - this.domElementKeyboardBindings.removeEventListener('keyup', this.onKeyUp, false); + this.domElementKeyboardBindings.removeEventListener( + 'keydown', + this.onKeyDown, + false + ); + this.domElementKeyboardBindings.removeEventListener( + 'keyup', + this.onKeyUp, + false + ); } - }; - function bind(scope, fn) - { - return function () - { + function bind(scope, fn) { + return function() { fn.apply(scope, arguments); }; } - }; THREE.LookatControls.prototype = Object.create(THREE.EventDispatcher.prototype); diff --git a/gz3d/src/gz3d-fluid-vis.js b/gz3d/src/gz3d-fluid-vis.js index 49305bb..9563bff 100644 --- a/gz3d/src/gz3d-fluid-vis.js +++ b/gz3d/src/gz3d-fluid-vis.js @@ -1,5 +1,4 @@ - -GZ3D.VisualFluidModel = function (scene) { +GZ3D.VisualFluidModel = function(scene) { this.scene = scene; this.rootGroup = new THREE.Group(); this.rootGroup.name = 'VisualFluidModel'; @@ -7,7 +6,7 @@ GZ3D.VisualFluidModel = function (scene) { this.rootGroup.visible = true; }; -GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { +GZ3D.VisualFluidModel.prototype.initFluidBuffers = function(message) { this.scales = new Float32Array(message.position.length); this.scales.fill(10.0); this.positions = new Float32Array(message.position.length * 3); @@ -15,7 +14,10 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { this.positionAttribute = new THREE.BufferAttribute(this.positions, 3); this.positionAttribute.setDynamic(true); this.geometry.addAttribute('position', this.positionAttribute); - this.geometry.addAttribute('scale', new THREE.BufferAttribute(this.scales, 1)); + this.geometry.addAttribute( + 'scale', + new THREE.BufferAttribute(this.scales, 1) + ); this.material = new THREE.ParticleBasicMaterial({ size: 5.0, sizeAttenuation: false, @@ -29,11 +31,15 @@ GZ3D.VisualFluidModel.prototype.initFluidBuffers = function (message) { this.rootGroup.add(this.particles); }; -GZ3D.VisualFluidModel.prototype.updateVisualization = function (message) { +GZ3D.VisualFluidModel.prototype.updateVisualization = function(message) { var vertex; for (var i = 0, l = message.position.length; i < l; i++) { - vertex = new THREE.Vector3(message.position[i].x, message.position[i].y, message.position[i].z); + vertex = new THREE.Vector3( + message.position[i].x, + message.position[i].y, + message.position[i].z + ); vertex.toArray(this.positions, i * 3); } this.positionAttribute.needsUpdate = true; -}; \ No newline at end of file +}; diff --git a/gz3d/src/gziface.js b/gz3d/src/gziface.js index 96c6b16..2d4e87c 100644 --- a/gz3d/src/gziface.js +++ b/gz3d/src/gziface.js @@ -363,11 +363,11 @@ GZ3D.GZIface.prototype.onConnected = function() { ros: this.webSocket, name: '~/fluid_pos', message_type: 'fluid', - throttle_rate: 1.0 / 200.0 * 1000.0, + throttle_rate: 1.0 / 200.0 * 1000.0 }); // function for updating client system visualization - var updateMuscleVisualization = function (message) { + var updateMuscleVisualization = function(message) { if (!(message.robot_name in this.scene.muscleVisuzalizations)) { this.scene.muscleVisuzalizations[ message.robot_name @@ -380,11 +380,13 @@ GZ3D.GZIface.prototype.onConnected = function() { ); } }; - + // function for updating client system visualization - var updateFluidVisualization = function (message) { + var updateFluidVisualization = function(message) { if (!this.scene.fluidVisualizations.length) { - this.scene.fluidVisualizations.push(new GZ3D.VisualFluidModel(this.scene, message)); + this.scene.fluidVisualizations.push( + new GZ3D.VisualFluidModel(this.scene, message) + ); this.scene.fluidVisualizations[0].initFluidBuffers(message); } this.scene.fluidVisualizations[0].updateVisualization(message); @@ -396,9 +398,11 @@ GZ3D.GZIface.prototype.onConnected = function() { ); // Subscription to fluid_pos topic - this.fluidVisualizationSubscriber.subscribe(updateFluidVisualization.bind(this)); + this.fluidVisualizationSubscriber.subscribe( + updateFluidVisualization.bind(this) + ); - var requestUpdate = function (message) { + var requestUpdate = function(message) { if (message.request === 'entity_delete') { var entity = this.scene.getByName(message.data); if (entity) { diff --git a/gz3d/src/gzlabelmanager.js b/gz3d/src/gzlabelmanager.js index 97369b3..7647921 100644 --- a/gz3d/src/gzlabelmanager.js +++ b/gz3d/src/gzlabelmanager.js @@ -195,7 +195,8 @@ GZ3D.LabelManager.prototype.onRender = function() { var labelPos = []; bbox.setFromObject(root); - var bsphere = bbox.getBoundingSphere(); + var bsphere = new THREE.Sphere(); + bbox.getBoundingSphere(bsphere); for (var i = 0; i < labelInfoList.length; i++) { var labelInfo = labelInfoList[i]; diff --git a/package.json b/package.json index 4f06b1f..190a859 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ ], "*.js": [ "node_modules/prettier/bin/prettier.js --write", - "node_modules/eslint/bin/eslint.js", "git add" ] }, @@ -32,4 +31,4 @@ "devDependencies": { "prettier": "1.7.4" } -} +} \ No newline at end of file -- GitLab