three-stdlib
Version:
stand-alone library of threejs examples
1 lines • 146 kB
Source Map (JSON)
{"version":3,"file":"ColladaLoader.cjs","sources":["../../src/loaders/ColladaLoader.js"],"sourcesContent":["import {\n AmbientLight,\n AnimationClip,\n Bone,\n BufferGeometry,\n ClampToEdgeWrapping,\n Color,\n DirectionalLight,\n DoubleSide,\n Euler,\n FileLoader,\n Float32BufferAttribute,\n FrontSide,\n Group,\n Line,\n LineBasicMaterial,\n LineSegments,\n Loader,\n LoaderUtils,\n MathUtils,\n Matrix4,\n Mesh,\n MeshBasicMaterial,\n MeshLambertMaterial,\n MeshPhongMaterial,\n OrthographicCamera,\n PerspectiveCamera,\n PointLight,\n Quaternion,\n QuaternionKeyframeTrack,\n RepeatWrapping,\n Scene,\n Skeleton,\n SkinnedMesh,\n SpotLight,\n TextureLoader,\n Vector2,\n Vector3,\n VectorKeyframeTrack,\n} from 'three'\nimport { TGALoader } from '../loaders/TGALoader'\nimport { UV1 } from '../_polyfill/uv1'\n\nclass ColladaLoader extends Loader {\n constructor(manager) {\n super(manager)\n }\n\n load(url, onLoad, onProgress, onError) {\n const scope = this\n\n const path = scope.path === '' ? LoaderUtils.extractUrlBase(url) : scope.path\n\n const loader = new FileLoader(scope.manager)\n loader.setPath(scope.path)\n loader.setRequestHeader(scope.requestHeader)\n loader.setWithCredentials(scope.withCredentials)\n loader.load(\n url,\n function (text) {\n try {\n onLoad(scope.parse(text, path))\n } catch (e) {\n if (onError) {\n onError(e)\n } else {\n console.error(e)\n }\n\n scope.manager.itemError(url)\n }\n },\n onProgress,\n onError,\n )\n }\n\n parse(text, path) {\n function getElementsByTagName(xml, name) {\n // Non recursive xml.getElementsByTagName() ...\n\n const array = []\n const childNodes = xml.childNodes\n\n for (let i = 0, l = childNodes.length; i < l; i++) {\n const child = childNodes[i]\n\n if (child.nodeName === name) {\n array.push(child)\n }\n }\n\n return array\n }\n\n function parseStrings(text) {\n if (text.length === 0) return []\n\n const parts = text.trim().split(/\\s+/)\n const array = new Array(parts.length)\n\n for (let i = 0, l = parts.length; i < l; i++) {\n array[i] = parts[i]\n }\n\n return array\n }\n\n function parseFloats(text) {\n if (text.length === 0) return []\n\n const parts = text.trim().split(/\\s+/)\n const array = new Array(parts.length)\n\n for (let i = 0, l = parts.length; i < l; i++) {\n array[i] = parseFloat(parts[i])\n }\n\n return array\n }\n\n function parseInts(text) {\n if (text.length === 0) return []\n\n const parts = text.trim().split(/\\s+/)\n const array = new Array(parts.length)\n\n for (let i = 0, l = parts.length; i < l; i++) {\n array[i] = parseInt(parts[i])\n }\n\n return array\n }\n\n function parseId(text) {\n return text.substring(1)\n }\n\n function generateId() {\n return 'three_default_' + count++\n }\n\n function isEmpty(object) {\n return Object.keys(object).length === 0\n }\n\n // asset\n\n function parseAsset(xml) {\n return {\n unit: parseAssetUnit(getElementsByTagName(xml, 'unit')[0]),\n upAxis: parseAssetUpAxis(getElementsByTagName(xml, 'up_axis')[0]),\n }\n }\n\n function parseAssetUnit(xml) {\n if (xml !== undefined && xml.hasAttribute('meter') === true) {\n return parseFloat(xml.getAttribute('meter'))\n } else {\n return 1 // default 1 meter\n }\n }\n\n function parseAssetUpAxis(xml) {\n return xml !== undefined ? xml.textContent : 'Y_UP'\n }\n\n // library\n\n function parseLibrary(xml, libraryName, nodeName, parser) {\n const library = getElementsByTagName(xml, libraryName)[0]\n\n if (library !== undefined) {\n const elements = getElementsByTagName(library, nodeName)\n\n for (let i = 0; i < elements.length; i++) {\n parser(elements[i])\n }\n }\n }\n\n function buildLibrary(data, builder) {\n for (const name in data) {\n const object = data[name]\n object.build = builder(data[name])\n }\n }\n\n // get\n\n function getBuild(data, builder) {\n if (data.build !== undefined) return data.build\n\n data.build = builder(data)\n\n return data.build\n }\n\n // animation\n\n function parseAnimation(xml) {\n const data = {\n sources: {},\n samplers: {},\n channels: {},\n }\n\n let hasChildren = false\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n let id\n\n switch (child.nodeName) {\n case 'source':\n id = child.getAttribute('id')\n data.sources[id] = parseSource(child)\n break\n\n case 'sampler':\n id = child.getAttribute('id')\n data.samplers[id] = parseAnimationSampler(child)\n break\n\n case 'channel':\n id = child.getAttribute('target')\n data.channels[id] = parseAnimationChannel(child)\n break\n\n case 'animation':\n // hierarchy of related animations\n parseAnimation(child)\n hasChildren = true\n break\n\n default:\n console.log(child)\n }\n }\n\n if (hasChildren === false) {\n // since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment\n\n library.animations[xml.getAttribute('id') || MathUtils.generateUUID()] = data\n }\n }\n\n function parseAnimationSampler(xml) {\n const data = {\n inputs: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'input':\n const id = parseId(child.getAttribute('source'))\n const semantic = child.getAttribute('semantic')\n data.inputs[semantic] = id\n break\n }\n }\n\n return data\n }\n\n function parseAnimationChannel(xml) {\n const data = {}\n\n const target = xml.getAttribute('target')\n\n // parsing SID Addressing Syntax\n\n let parts = target.split('/')\n\n const id = parts.shift()\n let sid = parts.shift()\n\n // check selection syntax\n\n const arraySyntax = sid.indexOf('(') !== -1\n const memberSyntax = sid.indexOf('.') !== -1\n\n if (memberSyntax) {\n // member selection access\n\n parts = sid.split('.')\n sid = parts.shift()\n data.member = parts.shift()\n } else if (arraySyntax) {\n // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.\n\n const indices = sid.split('(')\n sid = indices.shift()\n\n for (let i = 0; i < indices.length; i++) {\n indices[i] = parseInt(indices[i].replace(/\\)/, ''))\n }\n\n data.indices = indices\n }\n\n data.id = id\n data.sid = sid\n\n data.arraySyntax = arraySyntax\n data.memberSyntax = memberSyntax\n\n data.sampler = parseId(xml.getAttribute('source'))\n\n return data\n }\n\n function buildAnimation(data) {\n const tracks = []\n\n const channels = data.channels\n const samplers = data.samplers\n const sources = data.sources\n\n for (const target in channels) {\n if (channels.hasOwnProperty(target)) {\n const channel = channels[target]\n const sampler = samplers[channel.sampler]\n\n const inputId = sampler.inputs.INPUT\n const outputId = sampler.inputs.OUTPUT\n\n const inputSource = sources[inputId]\n const outputSource = sources[outputId]\n\n const animation = buildAnimationChannel(channel, inputSource, outputSource)\n\n createKeyframeTracks(animation, tracks)\n }\n }\n\n return tracks\n }\n\n function getAnimation(id) {\n return getBuild(library.animations[id], buildAnimation)\n }\n\n function buildAnimationChannel(channel, inputSource, outputSource) {\n const node = library.nodes[channel.id]\n const object3D = getNode(node.id)\n\n const transform = node.transforms[channel.sid]\n const defaultMatrix = node.matrix.clone().transpose()\n\n let time, stride\n let i, il, j, jl\n\n const data = {}\n\n // the collada spec allows the animation of data in various ways.\n // depending on the transform type (matrix, translate, rotate, scale), we execute different logic\n\n switch (transform) {\n case 'matrix':\n for (i = 0, il = inputSource.array.length; i < il; i++) {\n time = inputSource.array[i]\n stride = i * outputSource.stride\n\n if (data[time] === undefined) data[time] = {}\n\n if (channel.arraySyntax === true) {\n const value = outputSource.array[stride]\n const index = channel.indices[0] + 4 * channel.indices[1]\n\n data[time][index] = value\n } else {\n for (j = 0, jl = outputSource.stride; j < jl; j++) {\n data[time][j] = outputSource.array[stride + j]\n }\n }\n }\n\n break\n\n case 'translate':\n console.warn('THREE.ColladaLoader: Animation transform type \"%s\" not yet implemented.', transform)\n break\n\n case 'rotate':\n console.warn('THREE.ColladaLoader: Animation transform type \"%s\" not yet implemented.', transform)\n break\n\n case 'scale':\n console.warn('THREE.ColladaLoader: Animation transform type \"%s\" not yet implemented.', transform)\n break\n }\n\n const keyframes = prepareAnimationData(data, defaultMatrix)\n\n const animation = {\n name: object3D.uuid,\n keyframes: keyframes,\n }\n\n return animation\n }\n\n function prepareAnimationData(data, defaultMatrix) {\n const keyframes = []\n\n // transfer data into a sortable array\n\n for (const time in data) {\n keyframes.push({ time: parseFloat(time), value: data[time] })\n }\n\n // ensure keyframes are sorted by time\n\n keyframes.sort(ascending)\n\n // now we clean up all animation data, so we can use them for keyframe tracks\n\n for (let i = 0; i < 16; i++) {\n transformAnimationData(keyframes, i, defaultMatrix.elements[i])\n }\n\n return keyframes\n\n // array sort function\n\n function ascending(a, b) {\n return a.time - b.time\n }\n }\n\n const position = new Vector3()\n const scale = new Vector3()\n const quaternion = new Quaternion()\n\n function createKeyframeTracks(animation, tracks) {\n const keyframes = animation.keyframes\n const name = animation.name\n\n const times = []\n const positionData = []\n const quaternionData = []\n const scaleData = []\n\n for (let i = 0, l = keyframes.length; i < l; i++) {\n const keyframe = keyframes[i]\n\n const time = keyframe.time\n const value = keyframe.value\n\n matrix.fromArray(value).transpose()\n matrix.decompose(position, quaternion, scale)\n\n times.push(time)\n positionData.push(position.x, position.y, position.z)\n quaternionData.push(quaternion.x, quaternion.y, quaternion.z, quaternion.w)\n scaleData.push(scale.x, scale.y, scale.z)\n }\n\n if (positionData.length > 0) tracks.push(new VectorKeyframeTrack(name + '.position', times, positionData))\n if (quaternionData.length > 0) {\n tracks.push(new QuaternionKeyframeTrack(name + '.quaternion', times, quaternionData))\n }\n if (scaleData.length > 0) tracks.push(new VectorKeyframeTrack(name + '.scale', times, scaleData))\n\n return tracks\n }\n\n function transformAnimationData(keyframes, property, defaultValue) {\n let keyframe\n\n let empty = true\n let i, l\n\n // check, if values of a property are missing in our keyframes\n\n for (i = 0, l = keyframes.length; i < l; i++) {\n keyframe = keyframes[i]\n\n if (keyframe.value[property] === undefined) {\n keyframe.value[property] = null // mark as missing\n } else {\n empty = false\n }\n }\n\n if (empty === true) {\n // no values at all, so we set a default value\n\n for (i = 0, l = keyframes.length; i < l; i++) {\n keyframe = keyframes[i]\n\n keyframe.value[property] = defaultValue\n }\n } else {\n // filling gaps\n\n createMissingKeyframes(keyframes, property)\n }\n }\n\n function createMissingKeyframes(keyframes, property) {\n let prev, next\n\n for (let i = 0, l = keyframes.length; i < l; i++) {\n const keyframe = keyframes[i]\n\n if (keyframe.value[property] === null) {\n prev = getPrev(keyframes, i, property)\n next = getNext(keyframes, i, property)\n\n if (prev === null) {\n keyframe.value[property] = next.value[property]\n continue\n }\n\n if (next === null) {\n keyframe.value[property] = prev.value[property]\n continue\n }\n\n interpolate(keyframe, prev, next, property)\n }\n }\n }\n\n function getPrev(keyframes, i, property) {\n while (i >= 0) {\n const keyframe = keyframes[i]\n\n if (keyframe.value[property] !== null) return keyframe\n\n i--\n }\n\n return null\n }\n\n function getNext(keyframes, i, property) {\n while (i < keyframes.length) {\n const keyframe = keyframes[i]\n\n if (keyframe.value[property] !== null) return keyframe\n\n i++\n }\n\n return null\n }\n\n function interpolate(key, prev, next, property) {\n if (next.time - prev.time === 0) {\n key.value[property] = prev.value[property]\n return\n }\n\n key.value[property] =\n ((key.time - prev.time) * (next.value[property] - prev.value[property])) / (next.time - prev.time) +\n prev.value[property]\n }\n\n // animation clips\n\n function parseAnimationClip(xml) {\n const data = {\n name: xml.getAttribute('id') || 'default',\n start: parseFloat(xml.getAttribute('start') || 0),\n end: parseFloat(xml.getAttribute('end') || 0),\n animations: [],\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'instance_animation':\n data.animations.push(parseId(child.getAttribute('url')))\n break\n }\n }\n\n library.clips[xml.getAttribute('id')] = data\n }\n\n function buildAnimationClip(data) {\n const tracks = []\n\n const name = data.name\n const duration = data.end - data.start || -1\n const animations = data.animations\n\n for (let i = 0, il = animations.length; i < il; i++) {\n const animationTracks = getAnimation(animations[i])\n\n for (let j = 0, jl = animationTracks.length; j < jl; j++) {\n tracks.push(animationTracks[j])\n }\n }\n\n return new AnimationClip(name, duration, tracks)\n }\n\n function getAnimationClip(id) {\n return getBuild(library.clips[id], buildAnimationClip)\n }\n\n // controller\n\n function parseController(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'skin':\n // there is exactly one skin per controller\n data.id = parseId(child.getAttribute('source'))\n data.skin = parseSkin(child)\n break\n\n case 'morph':\n data.id = parseId(child.getAttribute('source'))\n console.warn('THREE.ColladaLoader: Morph target animation not supported yet.')\n break\n }\n }\n\n library.controllers[xml.getAttribute('id')] = data\n }\n\n function parseSkin(xml) {\n const data = {\n sources: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'bind_shape_matrix':\n data.bindShapeMatrix = parseFloats(child.textContent)\n break\n\n case 'source':\n const id = child.getAttribute('id')\n data.sources[id] = parseSource(child)\n break\n\n case 'joints':\n data.joints = parseJoints(child)\n break\n\n case 'vertex_weights':\n data.vertexWeights = parseVertexWeights(child)\n break\n }\n }\n\n return data\n }\n\n function parseJoints(xml) {\n const data = {\n inputs: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'input':\n const semantic = child.getAttribute('semantic')\n const id = parseId(child.getAttribute('source'))\n data.inputs[semantic] = id\n break\n }\n }\n\n return data\n }\n\n function parseVertexWeights(xml) {\n const data = {\n inputs: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'input':\n const semantic = child.getAttribute('semantic')\n const id = parseId(child.getAttribute('source'))\n const offset = parseInt(child.getAttribute('offset'))\n data.inputs[semantic] = { id: id, offset: offset }\n break\n\n case 'vcount':\n data.vcount = parseInts(child.textContent)\n break\n\n case 'v':\n data.v = parseInts(child.textContent)\n break\n }\n }\n\n return data\n }\n\n function buildController(data) {\n const build = {\n id: data.id,\n }\n\n const geometry = library.geometries[build.id]\n\n if (data.skin !== undefined) {\n build.skin = buildSkin(data.skin)\n\n // we enhance the 'sources' property of the corresponding geometry with our skin data\n\n geometry.sources.skinIndices = build.skin.indices\n geometry.sources.skinWeights = build.skin.weights\n }\n\n return build\n }\n\n function buildSkin(data) {\n const BONE_LIMIT = 4\n\n const build = {\n joints: [], // this must be an array to preserve the joint order\n indices: {\n array: [],\n stride: BONE_LIMIT,\n },\n weights: {\n array: [],\n stride: BONE_LIMIT,\n },\n }\n\n const sources = data.sources\n const vertexWeights = data.vertexWeights\n\n const vcount = vertexWeights.vcount\n const v = vertexWeights.v\n const jointOffset = vertexWeights.inputs.JOINT.offset\n const weightOffset = vertexWeights.inputs.WEIGHT.offset\n\n const jointSource = data.sources[data.joints.inputs.JOINT]\n const inverseSource = data.sources[data.joints.inputs.INV_BIND_MATRIX]\n\n const weights = sources[vertexWeights.inputs.WEIGHT.id].array\n let stride = 0\n\n let i, j, l\n\n // procces skin data for each vertex\n\n for (i = 0, l = vcount.length; i < l; i++) {\n const jointCount = vcount[i] // this is the amount of joints that affect a single vertex\n const vertexSkinData = []\n\n for (j = 0; j < jointCount; j++) {\n const skinIndex = v[stride + jointOffset]\n const weightId = v[stride + weightOffset]\n const skinWeight = weights[weightId]\n\n vertexSkinData.push({ index: skinIndex, weight: skinWeight })\n\n stride += 2\n }\n\n // we sort the joints in descending order based on the weights.\n // this ensures, we only procced the most important joints of the vertex\n\n vertexSkinData.sort(descending)\n\n // now we provide for each vertex a set of four index and weight values.\n // the order of the skin data matches the order of vertices\n\n for (j = 0; j < BONE_LIMIT; j++) {\n const d = vertexSkinData[j]\n\n if (d !== undefined) {\n build.indices.array.push(d.index)\n build.weights.array.push(d.weight)\n } else {\n build.indices.array.push(0)\n build.weights.array.push(0)\n }\n }\n }\n\n // setup bind matrix\n\n if (data.bindShapeMatrix) {\n build.bindMatrix = new Matrix4().fromArray(data.bindShapeMatrix).transpose()\n } else {\n build.bindMatrix = new Matrix4().identity()\n }\n\n // process bones and inverse bind matrix data\n\n for (i = 0, l = jointSource.array.length; i < l; i++) {\n const name = jointSource.array[i]\n const boneInverse = new Matrix4().fromArray(inverseSource.array, i * inverseSource.stride).transpose()\n\n build.joints.push({ name: name, boneInverse: boneInverse })\n }\n\n return build\n\n // array sort function\n\n function descending(a, b) {\n return b.weight - a.weight\n }\n }\n\n function getController(id) {\n return getBuild(library.controllers[id], buildController)\n }\n\n // image\n\n function parseImage(xml) {\n const data = {\n init_from: getElementsByTagName(xml, 'init_from')[0].textContent,\n }\n\n library.images[xml.getAttribute('id')] = data\n }\n\n function buildImage(data) {\n if (data.build !== undefined) return data.build\n\n return data.init_from\n }\n\n function getImage(id) {\n const data = library.images[id]\n\n if (data !== undefined) {\n return getBuild(data, buildImage)\n }\n\n console.warn(\"THREE.ColladaLoader: Couldn't find image with ID:\", id)\n\n return null\n }\n\n // effect\n\n function parseEffect(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'profile_COMMON':\n data.profile = parseEffectProfileCOMMON(child)\n break\n }\n }\n\n library.effects[xml.getAttribute('id')] = data\n }\n\n function parseEffectProfileCOMMON(xml) {\n const data = {\n surfaces: {},\n samplers: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'newparam':\n parseEffectNewparam(child, data)\n break\n\n case 'technique':\n data.technique = parseEffectTechnique(child)\n break\n\n case 'extra':\n data.extra = parseEffectExtra(child)\n break\n }\n }\n\n return data\n }\n\n function parseEffectNewparam(xml, data) {\n const sid = xml.getAttribute('sid')\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'surface':\n data.surfaces[sid] = parseEffectSurface(child)\n break\n\n case 'sampler2D':\n data.samplers[sid] = parseEffectSampler(child)\n break\n }\n }\n }\n\n function parseEffectSurface(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'init_from':\n data.init_from = child.textContent\n break\n }\n }\n\n return data\n }\n\n function parseEffectSampler(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'source':\n data.source = child.textContent\n break\n }\n }\n\n return data\n }\n\n function parseEffectTechnique(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'constant':\n case 'lambert':\n case 'blinn':\n case 'phong':\n data.type = child.nodeName\n data.parameters = parseEffectParameters(child)\n break\n\n case 'extra':\n data.extra = parseEffectExtra(child)\n break\n }\n }\n\n return data\n }\n\n function parseEffectParameters(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'emission':\n case 'diffuse':\n case 'specular':\n case 'bump':\n case 'ambient':\n case 'shininess':\n case 'transparency':\n data[child.nodeName] = parseEffectParameter(child)\n break\n case 'transparent':\n data[child.nodeName] = {\n opaque: child.hasAttribute('opaque') ? child.getAttribute('opaque') : 'A_ONE',\n data: parseEffectParameter(child),\n }\n break\n }\n }\n\n return data\n }\n\n function parseEffectParameter(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'color':\n data[child.nodeName] = parseFloats(child.textContent)\n break\n\n case 'float':\n data[child.nodeName] = parseFloat(child.textContent)\n break\n\n case 'texture':\n data[child.nodeName] = { id: child.getAttribute('texture'), extra: parseEffectParameterTexture(child) }\n break\n }\n }\n\n return data\n }\n\n function parseEffectParameterTexture(xml) {\n const data = {\n technique: {},\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'extra':\n parseEffectParameterTextureExtra(child, data)\n break\n }\n }\n\n return data\n }\n\n function parseEffectParameterTextureExtra(xml, data) {\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'technique':\n parseEffectParameterTextureExtraTechnique(child, data)\n break\n }\n }\n }\n\n function parseEffectParameterTextureExtraTechnique(xml, data) {\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'repeatU':\n case 'repeatV':\n case 'offsetU':\n case 'offsetV':\n data.technique[child.nodeName] = parseFloat(child.textContent)\n break\n\n case 'wrapU':\n case 'wrapV':\n // some files have values for wrapU/wrapV which become NaN via parseInt\n\n if (child.textContent.toUpperCase() === 'TRUE') {\n data.technique[child.nodeName] = 1\n } else if (child.textContent.toUpperCase() === 'FALSE') {\n data.technique[child.nodeName] = 0\n } else {\n data.technique[child.nodeName] = parseInt(child.textContent)\n }\n\n break\n\n case 'bump':\n data[child.nodeName] = parseEffectExtraTechniqueBump(child)\n break\n }\n }\n }\n\n function parseEffectExtra(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'technique':\n data.technique = parseEffectExtraTechnique(child)\n break\n }\n }\n\n return data\n }\n\n function parseEffectExtraTechnique(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'double_sided':\n data[child.nodeName] = parseInt(child.textContent)\n break\n\n case 'bump':\n data[child.nodeName] = parseEffectExtraTechniqueBump(child)\n break\n }\n }\n\n return data\n }\n\n function parseEffectExtraTechniqueBump(xml) {\n var data = {}\n\n for (var i = 0, l = xml.childNodes.length; i < l; i++) {\n var child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'texture':\n data[child.nodeName] = {\n id: child.getAttribute('texture'),\n texcoord: child.getAttribute('texcoord'),\n extra: parseEffectParameterTexture(child),\n }\n break\n }\n }\n\n return data\n }\n\n function buildEffect(data) {\n return data\n }\n\n function getEffect(id) {\n return getBuild(library.effects[id], buildEffect)\n }\n\n // material\n\n function parseMaterial(xml) {\n const data = {\n name: xml.getAttribute('name'),\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'instance_effect':\n data.url = parseId(child.getAttribute('url'))\n break\n }\n }\n\n library.materials[xml.getAttribute('id')] = data\n }\n\n function getTextureLoader(image) {\n let loader\n\n let extension = image.slice(((image.lastIndexOf('.') - 1) >>> 0) + 2) // http://www.jstips.co/en/javascript/get-file-extension/\n extension = extension.toLowerCase()\n\n switch (extension) {\n case 'tga':\n loader = tgaLoader\n break\n\n default:\n loader = textureLoader\n }\n\n return loader\n }\n\n function buildMaterial(data) {\n const effect = getEffect(data.url)\n const technique = effect.profile.technique\n\n let material\n\n switch (technique.type) {\n case 'phong':\n case 'blinn':\n material = new MeshPhongMaterial()\n break\n\n case 'lambert':\n material = new MeshLambertMaterial()\n break\n\n default:\n material = new MeshBasicMaterial()\n break\n }\n\n material.name = data.name || ''\n\n function getTexture(textureObject) {\n const sampler = effect.profile.samplers[textureObject.id]\n let image = null\n\n // get image\n\n if (sampler !== undefined) {\n const surface = effect.profile.surfaces[sampler.source]\n image = getImage(surface.init_from)\n } else {\n console.warn('THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).')\n image = getImage(textureObject.id)\n }\n\n // create texture if image is avaiable\n\n if (image !== null) {\n const loader = getTextureLoader(image)\n\n if (loader !== undefined) {\n const texture = loader.load(image)\n\n const extra = textureObject.extra\n\n if (extra !== undefined && extra.technique !== undefined && isEmpty(extra.technique) === false) {\n const technique = extra.technique\n\n texture.wrapS = technique.wrapU ? RepeatWrapping : ClampToEdgeWrapping\n texture.wrapT = technique.wrapV ? RepeatWrapping : ClampToEdgeWrapping\n\n texture.offset.set(technique.offsetU || 0, technique.offsetV || 0)\n texture.repeat.set(technique.repeatU || 1, technique.repeatV || 1)\n } else {\n texture.wrapS = RepeatWrapping\n texture.wrapT = RepeatWrapping\n }\n\n return texture\n } else {\n console.warn('THREE.ColladaLoader: Loader for texture %s not found.', image)\n\n return null\n }\n } else {\n console.warn(\"THREE.ColladaLoader: Couldn't create texture with ID:\", textureObject.id)\n\n return null\n }\n }\n\n const parameters = technique.parameters\n\n for (const key in parameters) {\n const parameter = parameters[key]\n\n switch (key) {\n case 'diffuse':\n if (parameter.color) material.color.fromArray(parameter.color)\n if (parameter.texture) material.map = getTexture(parameter.texture)\n break\n case 'specular':\n if (parameter.color && material.specular) material.specular.fromArray(parameter.color)\n if (parameter.texture) material.specularMap = getTexture(parameter.texture)\n break\n case 'bump':\n if (parameter.texture) material.normalMap = getTexture(parameter.texture)\n break\n case 'ambient':\n if (parameter.texture) material.lightMap = getTexture(parameter.texture)\n break\n case 'shininess':\n if (parameter.float && material.shininess) material.shininess = parameter.float\n break\n case 'emission':\n if (parameter.color && material.emissive) material.emissive.fromArray(parameter.color)\n if (parameter.texture) material.emissiveMap = getTexture(parameter.texture)\n break\n }\n }\n\n //\n\n let transparent = parameters['transparent']\n let transparency = parameters['transparency']\n\n // <transparency> does not exist but <transparent>\n\n if (transparency === undefined && transparent) {\n transparency = {\n float: 1,\n }\n }\n\n // <transparent> does not exist but <transparency>\n\n if (transparent === undefined && transparency) {\n transparent = {\n opaque: 'A_ONE',\n data: {\n color: [1, 1, 1, 1],\n },\n }\n }\n\n if (transparent && transparency) {\n // handle case if a texture exists but no color\n\n if (transparent.data.texture) {\n // we do not set an alpha map (see #13792)\n\n material.transparent = true\n } else {\n const color = transparent.data.color\n\n switch (transparent.opaque) {\n case 'A_ONE':\n material.opacity = color[3] * transparency.float\n break\n case 'RGB_ZERO':\n material.opacity = 1 - color[0] * transparency.float\n break\n case 'A_ZERO':\n material.opacity = 1 - color[3] * transparency.float\n break\n case 'RGB_ONE':\n material.opacity = color[0] * transparency.float\n break\n default:\n console.warn('THREE.ColladaLoader: Invalid opaque type \"%s\" of transparent tag.', transparent.opaque)\n }\n\n if (material.opacity < 1) material.transparent = true\n }\n }\n\n //\n\n if (technique.extra !== undefined && technique.extra.technique !== undefined) {\n const techniques = technique.extra.technique\n\n for (const k in techniques) {\n const v = techniques[k]\n\n switch (k) {\n case 'double_sided':\n material.side = v === 1 ? DoubleSide : FrontSide\n break\n\n case 'bump':\n material.normalMap = getTexture(v.texture)\n material.normalScale = new Vector2(1, 1)\n break\n }\n }\n }\n\n return material\n }\n\n function getMaterial(id) {\n return getBuild(library.materials[id], buildMaterial)\n }\n\n // camera\n\n function parseCamera(xml) {\n const data = {\n name: xml.getAttribute('name'),\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'optics':\n data.optics = parseCameraOptics(child)\n break\n }\n }\n\n library.cameras[xml.getAttribute('id')] = data\n }\n\n function parseCameraOptics(xml) {\n for (let i = 0; i < xml.childNodes.length; i++) {\n const child = xml.childNodes[i]\n\n switch (child.nodeName) {\n case 'technique_common':\n return parseCameraTechnique(child)\n }\n }\n\n return {}\n }\n\n function parseCameraTechnique(xml) {\n const data = {}\n\n for (let i = 0; i < xml.childNodes.length; i++) {\n const child = xml.childNodes[i]\n\n switch (child.nodeName) {\n case 'perspective':\n case 'orthographic':\n data.technique = child.nodeName\n data.parameters = parseCameraParameters(child)\n\n break\n }\n }\n\n return data\n }\n\n function parseCameraParameters(xml) {\n const data = {}\n\n for (let i = 0; i < xml.childNodes.length; i++) {\n const child = xml.childNodes[i]\n\n switch (child.nodeName) {\n case 'xfov':\n case 'yfov':\n case 'xmag':\n case 'ymag':\n case 'znear':\n case 'zfar':\n case 'aspect_ratio':\n data[child.nodeName] = parseFloat(child.textContent)\n break\n }\n }\n\n return data\n }\n\n function buildCamera(data) {\n let camera\n\n switch (data.optics.technique) {\n case 'perspective':\n camera = new PerspectiveCamera(\n data.optics.parameters.yfov,\n data.optics.parameters.aspect_ratio,\n data.optics.parameters.znear,\n data.optics.parameters.zfar,\n )\n break\n\n case 'orthographic':\n let ymag = data.optics.parameters.ymag\n let xmag = data.optics.parameters.xmag\n const aspectRatio = data.optics.parameters.aspect_ratio\n\n xmag = xmag === undefined ? ymag * aspectRatio : xmag\n ymag = ymag === undefined ? xmag / aspectRatio : ymag\n\n xmag *= 0.5\n ymag *= 0.5\n\n camera = new OrthographicCamera(\n -xmag,\n xmag,\n ymag,\n -ymag, // left, right, top, bottom\n data.optics.parameters.znear,\n data.optics.parameters.zfar,\n )\n break\n\n default:\n camera = new PerspectiveCamera()\n break\n }\n\n camera.name = data.name || ''\n\n return camera\n }\n\n function getCamera(id) {\n const data = library.cameras[id]\n\n if (data !== undefined) {\n return getBuild(data, buildCamera)\n }\n\n console.warn(\"THREE.ColladaLoader: Couldn't find camera with ID:\", id)\n\n return null\n }\n\n // light\n\n function parseLight(xml) {\n let data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'technique_common':\n data = parseLightTechnique(child)\n break\n }\n }\n\n library.lights[xml.getAttribute('id')] = data\n }\n\n function parseLightTechnique(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'directional':\n case 'point':\n case 'spot':\n case 'ambient':\n data.technique = child.nodeName\n data.parameters = parseLightParameters(child)\n }\n }\n\n return data\n }\n\n function parseLightParameters(xml) {\n const data = {}\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'color':\n const array = parseFloats(child.textContent)\n data.color = new Color().fromArray(array)\n break\n\n case 'falloff_angle':\n data.falloffAngle = parseFloat(child.textContent)\n break\n\n case 'quadratic_attenuation':\n const f = parseFloat(child.textContent)\n data.distance = f ? Math.sqrt(1 / f) : 0\n break\n }\n }\n\n return data\n }\n\n function buildLight(data) {\n let light\n\n switch (data.technique) {\n case 'directional':\n light = new DirectionalLight()\n break\n\n case 'point':\n light = new PointLight()\n break\n\n case 'spot':\n light = new SpotLight()\n break\n\n case 'ambient':\n light = new AmbientLight()\n break\n }\n\n if (data.parameters.color) light.color.copy(data.parameters.color)\n if (data.parameters.distance) light.distance = data.parameters.distance\n\n return light\n }\n\n function getLight(id) {\n const data = library.lights[id]\n\n if (data !== undefined) {\n return getBuild(data, buildLight)\n }\n\n console.warn(\"THREE.ColladaLoader: Couldn't find light with ID:\", id)\n\n return null\n }\n\n // geometry\n\n function parseGeometry(xml) {\n const data = {\n name: xml.getAttribute('name'),\n sources: {},\n vertices: {},\n primitives: [],\n }\n\n const mesh = getElementsByTagName(xml, 'mesh')[0]\n\n // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep\n if (mesh === undefined) return\n\n for (let i = 0; i < mesh.childNodes.length; i++) {\n const child = mesh.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n const id = child.getAttribute('id')\n\n switch (child.nodeName) {\n case 'source':\n data.sources[id] = parseSource(child)\n break\n\n case 'vertices':\n // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];\n data.vertices = parseGeometryVertices(child)\n break\n\n case 'polygons':\n console.warn('THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName)\n break\n\n case 'lines':\n case 'linestrips':\n case 'polylist':\n case 'triangles':\n data.primitives.push(parseGeometryPrimitive(child))\n break\n\n default:\n console.log(child)\n }\n }\n\n library.geometries[xml.getAttribute('id')] = data\n }\n\n function parseSource(xml) {\n const data = {\n array: [],\n stride: 3,\n }\n\n for (let i = 0; i < xml.childNodes.length; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'float_array':\n data.array = parseFloats(child.textContent)\n break\n\n case 'Name_array':\n data.array = parseStrings(child.textContent)\n break\n\n case 'technique_common':\n const accessor = getElementsByTagName(child, 'accessor')[0]\n\n if (accessor !== undefined) {\n data.stride = parseInt(accessor.getAttribute('stride'))\n }\n\n break\n }\n }\n\n return data\n }\n\n function parseGeometryVertices(xml) {\n const data = {}\n\n for (let i = 0; i < xml.childNodes.length; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n data[child.getAttribute('semantic')] = parseId(child.getAttribute('source'))\n }\n\n return data\n }\n\n function parseGeometryPrimitive(xml) {\n const primitive = {\n type: xml.nodeName,\n material: xml.getAttribute('material'),\n count: parseInt(xml.getAttribute('count')),\n inputs: {},\n stride: 0,\n hasUV: false,\n }\n\n for (let i = 0, l = xml.childNodes.length; i < l; i++) {\n const child = xml.childNodes[i]\n\n if (child.nodeType !== 1) continue\n\n switch (child.nodeName) {\n case 'input':\n const id = parseId(child.getAttribute('source'))\n const semantic = child.getAttribute('semantic')\n const offset = parseInt(child.getAttribute('offset'))\n const set = parseInt(child.getAttribute('set'))\n const inputname = set > 0 ? semantic + set : semantic\n primitive.inputs[inputname] = { id: id, offset: offset }\n primitive.stride = Math.max(primitive.stride, offset + 1)\n if (semantic === 'TEXCOORD') primitive.hasUV = true\n break\n\n case 'vcount':\n primitive.vcount = parseInts(child.textContent)\n break\n\n case 'p':\n primitive.p = parseInts(child.textContent)\n break\n }\n }\n\n return primitive\n }\n\n function groupPrimitives(primitives) {\n const build = {}\n\n for (let i = 0; i < primitives.length; i++) {\n const primitive = primitives[i]\n\n if (build[primitive.type] === undefined) build[primitive.type] = []\n\n build[primitive.type].push(primitive)\n }\n\n return build\n }\n\n function checkUVCoordinates(primitives) {\n let count = 0\n\n for (let i = 0, l = primitives.length; i < l; i++) {\n const primitive = primitives[i]\n\n if (primitive.hasUV === true) {\n count++\n }\n }\n\n if (count > 0 && count < primitives.length) {\n primitives.uvsNeedsFix = true\n }\n }\n\n function buildGeometry(data) {\n const build = {}\n\n const sources = data.sources\n const vertices = data.vertices\n const primitives = data.primitives\n\n if (primitives.length === 0) return {}\n\n // our goal is to create one buffer geometry for a single type of primitives\n // first, we group all primitives by their type\n\n const groupedPrimitives = groupPrimitives(primitives)\n\n for (const type in groupedPrimitives) {\n const primitiveType = groupedPrimitives[type]\n\n // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines)\n\n checkUVCoordinates(primitiveType)\n\n // third, create a buffer geometry for each type of primitives\n\n build[type] = buildGeometryType(primitiveType, sources, vertices)\n }\n\n return build\n }\n\n function buildGeometryType(primitives, sources, vertices) {\n const build = {}\n\n const position = { array: [], stride: 0 }\n const normal = { array: [], stride: 0 }\n const uv = { array: [], stride: 0 }\n const uv1 = { array: [], stride: 0 }\n const color = { array: [], stride: 0 }\n\n const skinIndex = { array: [], stride: 4 }\n const skinWeight = { array: [], stride: 4 }\n\n const geometry = new BufferGeometry()\n\n const materialKeys = []\n\n let start = 0\n\n for (let p = 0; p < primitives.length; p++) {\n const primitive = primitives[p]\n const inputs = primitive.inputs\n\n // groups\n\n let count = 0\n\n switch