UNPKG

three-stdlib

Version:

stand-alone library of threejs examples

1 lines 91.4 kB
{"version":3,"file":"AssimpLoader.cjs","sources":["../../src/loaders/AssimpLoader.js"],"sourcesContent":["import {\n Bone,\n BufferAttribute,\n BufferGeometry,\n Color,\n FileLoader,\n Loader,\n LoaderUtils,\n Matrix4,\n Mesh,\n MeshLambertMaterial,\n MeshPhongMaterial,\n Object3D,\n Quaternion,\n Skeleton,\n SkinnedMesh,\n TextureLoader,\n Vector3,\n} from 'three'\n\nclass AssimpLoader extends Loader {\n load(url, onLoad, onProgress, onError) {\n var scope = this\n\n var path = scope.path === '' ? LoaderUtils.extractUrlBase(url) : scope.path\n\n var loader = new FileLoader(scope.manager)\n loader.setPath(scope.path)\n loader.setResponseType('arraybuffer')\n loader.setRequestHeader(scope.requestHeader)\n loader.setWithCredentials(scope.withCredentials)\n\n loader.load(\n url,\n function (buffer) {\n try {\n onLoad(scope.parse(buffer, 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(buffer, path) {\n var textureLoader = new TextureLoader(this.manager)\n textureLoader.setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin)\n\n var Virtulous = {}\n\n Virtulous.KeyFrame = class {\n constructor(time, matrix) {\n this.time = time\n this.matrix = matrix.clone()\n this.position = new Vector3()\n this.quaternion = new Quaternion()\n this.scale = new Vector3(1, 1, 1)\n this.matrix.decompose(this.position, this.quaternion, this.scale)\n this.clone = function () {\n var n = new Virtulous.KeyFrame(this.time, this.matrix)\n return n\n }\n\n this.lerp = function (nextKey, time) {\n time -= this.time\n var dist = nextKey.time - this.time\n var l = time / dist\n var l2 = 1 - l\n var keypos = this.position\n var keyrot = this.quaternion\n // var keyscl = key.parentspaceScl || key.scl;\n var key2pos = nextKey.position\n var key2rot = nextKey.quaternion\n // var key2scl = key2.parentspaceScl || key2.scl;\n Virtulous.KeyFrame.tempAniPos.x = keypos.x * l2 + key2pos.x * l\n Virtulous.KeyFrame.tempAniPos.y = keypos.y * l2 + key2pos.y * l\n Virtulous.KeyFrame.tempAniPos.z = keypos.z * l2 + key2pos.z * l\n // tempAniScale.x = keyscl[0] * l2 + key2scl[0] * l;\n // tempAniScale.y = keyscl[1] * l2 + key2scl[1] * l;\n // tempAniScale.z = keyscl[2] * l2 + key2scl[2] * l;\n Virtulous.KeyFrame.tempAniQuat.set(keyrot.x, keyrot.y, keyrot.z, keyrot.w)\n Virtulous.KeyFrame.tempAniQuat.slerp(key2rot, l)\n return Virtulous.KeyFrame.tempAniMatrix.compose(\n Virtulous.KeyFrame.tempAniPos,\n Virtulous.KeyFrame.tempAniQuat,\n Virtulous.KeyFrame.tempAniScale,\n )\n }\n }\n }\n\n Virtulous.KeyFrame.tempAniPos = new Vector3()\n Virtulous.KeyFrame.tempAniQuat = new Quaternion()\n Virtulous.KeyFrame.tempAniScale = new Vector3(1, 1, 1)\n Virtulous.KeyFrame.tempAniMatrix = new Matrix4()\n Virtulous.KeyFrameTrack = function () {\n this.keys = []\n this.target = null\n this.time = 0\n this.length = 0\n this._accelTable = {}\n this.fps = 20\n this.addKey = function (key) {\n this.keys.push(key)\n }\n\n this.init = function () {\n this.sortKeys()\n\n if (this.keys.length > 0) this.length = this.keys[this.keys.length - 1].time\n else this.length = 0\n\n if (!this.fps) return\n\n for (let j = 0; j < this.length * this.fps; j++) {\n for (let i = 0; i < this.keys.length; i++) {\n if (this.keys[i].time == j) {\n this._accelTable[j] = i\n break\n } else if (this.keys[i].time < j / this.fps && this.keys[i + 1] && this.keys[i + 1].time >= j / this.fps) {\n this._accelTable[j] = i\n break\n }\n }\n }\n }\n\n this.parseFromThree = function (data) {\n var fps = data.fps\n this.target = data.node\n var track = data.hierarchy[0].keys\n for (let i = 0; i < track.length; i++) {\n this.addKey(new Virtulous.KeyFrame(i / fps || track[i].time, track[i].targets[0].data))\n }\n\n this.init()\n }\n\n this.parseFromCollada = function (data) {\n var track = data.keys\n var fps = this.fps\n\n for (let i = 0; i < track.length; i++) {\n this.addKey(new Virtulous.KeyFrame(i / fps || track[i].time, track[i].matrix))\n }\n\n this.init()\n }\n\n this.sortKeys = function () {\n this.keys.sort(this.keySortFunc)\n }\n\n this.keySortFunc = function (a, b) {\n return a.time - b.time\n }\n\n this.clone = function () {\n var t = new Virtulous.KeyFrameTrack()\n t.target = this.target\n t.time = this.time\n t.length = this.length\n\n for (let i = 0; i < this.keys.length; i++) {\n t.addKey(this.keys[i].clone())\n }\n\n t.init()\n return t\n }\n\n this.reTarget = function (root, compareitor) {\n if (!compareitor) compareitor = Virtulous.TrackTargetNodeNameCompare\n this.target = compareitor(root, this.target)\n }\n\n this.keySearchAccel = function (time) {\n time *= this.fps\n time = Math.floor(time)\n return this._accelTable[time] || 0\n }\n\n this.setTime = function (time) {\n time = Math.abs(time)\n if (this.length) time = (time % this.length) + 0.05\n var key0 = null\n var key1 = null\n\n for (let i = this.keySearchAccel(time); i < this.keys.length; i++) {\n if (this.keys[i].time == time) {\n key0 = this.keys[i]\n key1 = this.keys[i]\n break\n } else if (this.keys[i].time < time && this.keys[i + 1] && this.keys[i + 1].time > time) {\n key0 = this.keys[i]\n key1 = this.keys[i + 1]\n break\n } else if (this.keys[i].time < time && i == this.keys.length - 1) {\n key0 = this.keys[i]\n key1 = this.keys[0].clone()\n key1.time += this.length + 0.05\n break\n }\n }\n\n if (key0 && key1 && key0 !== key1) {\n this.target.matrixAutoUpdate = false\n this.target.matrix.copy(key0.lerp(key1, time))\n this.target.matrixWorldNeedsUpdate = true\n return\n }\n\n if (key0 && key1 && key0 == key1) {\n this.target.matrixAutoUpdate = false\n this.target.matrix.copy(key0.matrix)\n this.target.matrixWorldNeedsUpdate = true\n return\n }\n }\n }\n\n Virtulous.TrackTargetNodeNameCompare = function (root, target) {\n function find(node, name) {\n if (node.name == name) return node\n\n for (let i = 0; i < node.children.length; i++) {\n var r = find(node.children[i], name)\n if (r) return r\n }\n\n return null\n }\n\n return find(root, target.name)\n }\n\n Virtulous.Animation = function () {\n this.tracks = []\n this.length = 0\n\n this.addTrack = function (track) {\n this.tracks.push(track)\n this.length = Math.max(track.length, this.length)\n }\n\n this.setTime = function (time) {\n this.time = time\n\n for (let i = 0; i < this.tracks.length; i++) this.tracks[i].setTime(time)\n }\n\n this.clone = function (target, compareitor) {\n if (!compareitor) compareitor = Virtulous.TrackTargetNodeNameCompare\n var n = new Virtulous.Animation()\n n.target = target\n for (let i = 0; i < this.tracks.length; i++) {\n var track = this.tracks[i].clone()\n track.reTarget(target, compareitor)\n n.addTrack(track)\n }\n\n return n\n }\n }\n\n var ASSBIN_CHUNK_AICAMERA = 0x1234\n var ASSBIN_CHUNK_AILIGHT = 0x1235\n var ASSBIN_CHUNK_AITEXTURE = 0x1236\n var ASSBIN_CHUNK_AIMESH = 0x1237\n var ASSBIN_CHUNK_AINODEANIM = 0x1238\n var ASSBIN_CHUNK_AISCENE = 0x1239\n var ASSBIN_CHUNK_AIBONE = 0x123a\n var ASSBIN_CHUNK_AIANIMATION = 0x123b\n var ASSBIN_CHUNK_AINODE = 0x123c\n var ASSBIN_CHUNK_AIMATERIAL = 0x123d\n var ASSBIN_CHUNK_AIMATERIALPROPERTY = 0x123e\n var ASSBIN_MESH_HAS_POSITIONS = 0x1\n var ASSBIN_MESH_HAS_NORMALS = 0x2\n var ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS = 0x4\n var ASSBIN_MESH_HAS_TEXCOORD_BASE = 0x100\n var ASSBIN_MESH_HAS_COLOR_BASE = 0x10000\n var AI_MAX_NUMBER_OF_COLOR_SETS = 1\n var AI_MAX_NUMBER_OF_TEXTURECOORDS = 4\n //var aiLightSource_UNDEFINED = 0x0;\n //! A directional light source has a well-defined direction\n //! but is infinitely far away. That's quite a good\n //! approximation for sun light.\n var aiLightSource_DIRECTIONAL = 0x1\n //! A point light source has a well-defined position\n //! in space but no direction - it emits light in all\n //! directions. A normal bulb is a point light.\n //var aiLightSource_POINT = 0x2;\n //! A spot light source emits light in a specific\n //! angle. It has a position and a direction it is pointing to.\n //! A good example for a spot light is a light spot in\n //! sport arenas.\n var aiLightSource_SPOT = 0x3\n //! The generic light level of the world, including the bounces\n //! of all other lightsources.\n //! Typically, there's at most one ambient light in a scene.\n //! This light type doesn't have a valid position, direction, or\n //! other properties, just a color.\n //var aiLightSource_AMBIENT = 0x4;\n /** Flat shading. Shading is done on per-face base,\n * diffuse only. Also known as 'faceted shading'.\n */\n //var aiShadingMode_Flat = 0x1;\n /** Simple Gouraud shading.\n */\n //var aiShadingMode_Gouraud = 0x2;\n /** Phong-Shading -\n */\n //var aiShadingMode_Phong = 0x3;\n /** Phong-Blinn-Shading\n */\n //var aiShadingMode_Blinn = 0x4;\n /** Toon-Shading per pixel\n *\n * Also known as 'comic' shader.\n */\n //var aiShadingMode_Toon = 0x5;\n /** OrenNayar-Shading per pixel\n *\n * Extension to standard Lambertian shading, taking the\n * roughness of the material into account\n */\n //var aiShadingMode_OrenNayar = 0x6;\n /** Minnaert-Shading per pixel\n *\n * Extension to standard Lambertian shading, taking the\n * \"darkness\" of the material into account\n */\n //var aiShadingMode_Minnaert = 0x7;\n /** CookTorrance-Shading per pixel\n *\n * Special shader for metallic surfaces.\n */\n //var aiShadingMode_CookTorrance = 0x8;\n /** No shading at all. Constant light influence of 1.0.\n */\n //var aiShadingMode_NoShading = 0x9;\n /** Fresnel shading\n */\n //var aiShadingMode_Fresnel = 0xa;\n //var aiTextureType_NONE = 0x0;\n /** The texture is combined with the result of the diffuse\n * lighting equation.\n */\n var aiTextureType_DIFFUSE = 0x1\n /** The texture is combined with the result of the specular\n * lighting equation.\n */\n //var aiTextureType_SPECULAR = 0x2;\n /** The texture is combined with the result of the ambient\n * lighting equation.\n */\n //var aiTextureType_AMBIENT = 0x3;\n /** The texture is added to the result of the lighting\n * calculation. It isn't influenced by incoming light.\n */\n //var aiTextureType_EMISSIVE = 0x4;\n /** The texture is a height map.\n *\n * By convention, higher gray-scale values stand for\n * higher elevations from the base height.\n */\n //var aiTextureType_HEIGHT = 0x5;\n /** The texture is a (tangent space) normal-map.\n *\n * Again, there are several conventions for tangent-space\n * normal maps. Assimp does (intentionally) not\n * distinguish here.\n */\n var aiTextureType_NORMALS = 0x6\n /** The texture defines the glossiness of the material.\n *\n * The glossiness is in fact the exponent of the specular\n * (phong) lighting equation. Usually there is a conversion\n * function defined to map the linear color values in the\n * texture to a suitable exponent. Have fun.\n */\n //var aiTextureType_SHININESS = 0x7;\n /** The texture defines per-pixel opacity.\n *\n * Usually 'white' means opaque and 'black' means\n * 'transparency'. Or quite the opposite. Have fun.\n */\n var aiTextureType_OPACITY = 0x8\n /** Displacement texture\n *\n * The exact purpose and format is application-dependent.\n * Higher color values stand for higher vertex displacements.\n */\n //var aiTextureType_DISPLACEMENT = 0x9;\n /** Lightmap texture (aka Ambient Occlusion)\n *\n * Both 'Lightmaps' and dedicated 'ambient occlusion maps' are\n * covered by this material property. The texture contains a\n * scaling value for the final color value of a pixel. Its\n * intensity is not affected by incoming light.\n */\n var aiTextureType_LIGHTMAP = 0xa\n /** Reflection texture\n *\n * Contains the color of a perfect mirror reflection.\n * Rarely used, almost never for real-time applications.\n */\n //var aiTextureType_REFLECTION = 0xB;\n /** Unknown texture\n *\n * A texture reference that does not match any of the definitions\n * above is considered to be 'unknown'. It is still imported,\n * but is excluded from any further postprocessing.\n */\n //var aiTextureType_UNKNOWN = 0xC;\n var BONESPERVERT = 4\n\n function ASSBIN_MESH_HAS_TEXCOORD(n) {\n return ASSBIN_MESH_HAS_TEXCOORD_BASE << n\n }\n\n function ASSBIN_MESH_HAS_COLOR(n) {\n return ASSBIN_MESH_HAS_COLOR_BASE << n\n }\n\n function markBones(scene) {\n for (let i in scene.mMeshes) {\n var mesh = scene.mMeshes[i]\n for (let k in mesh.mBones) {\n var boneNode = scene.findNode(mesh.mBones[k].mName)\n if (boneNode) boneNode.isBone = true\n }\n }\n }\n\n function cloneTreeToBones(root, scene) {\n var rootBone = new Bone()\n rootBone.matrix.copy(root.matrix)\n rootBone.matrixWorld.copy(root.matrixWorld)\n rootBone.position.copy(root.position)\n rootBone.quaternion.copy(root.quaternion)\n rootBone.scale.copy(root.scale)\n scene.nodeCount++\n rootBone.name = 'bone_' + root.name + scene.nodeCount.toString()\n\n if (!scene.nodeToBoneMap[root.name]) scene.nodeToBoneMap[root.name] = []\n scene.nodeToBoneMap[root.name].push(rootBone)\n for (let i in root.children) {\n var child = cloneTreeToBones(root.children[i], scene)\n rootBone.add(child)\n }\n\n return rootBone\n }\n\n function sortWeights(indexes, weights) {\n var pairs = []\n\n for (let i = 0; i < indexes.length; i++) {\n pairs.push({\n i: indexes[i],\n w: weights[i],\n })\n }\n\n pairs.sort(function (a, b) {\n return b.w - a.w\n })\n\n while (pairs.length < 4) {\n pairs.push({\n i: 0,\n w: 0,\n })\n }\n\n if (pairs.length > 4) pairs.length = 4\n var sum = 0\n\n for (let i = 0; i < 4; i++) {\n sum += pairs[i].w * pairs[i].w\n }\n\n sum = Math.sqrt(sum)\n\n for (let i = 0; i < 4; i++) {\n pairs[i].w = pairs[i].w / sum\n indexes[i] = pairs[i].i\n weights[i] = pairs[i].w\n }\n }\n\n function findMatchingBone(root, name) {\n if (root.name.indexOf('bone_' + name) == 0) return root\n\n for (let i in root.children) {\n var ret = findMatchingBone(root.children[i], name)\n\n if (ret) return ret\n }\n\n return undefined\n }\n\n class aiMesh {\n constructor() {\n this.mPrimitiveTypes = 0\n this.mNumVertices = 0\n this.mNumFaces = 0\n this.mNumBones = 0\n this.mMaterialIndex = 0\n this.mVertices = []\n this.mNormals = []\n this.mTangents = []\n this.mBitangents = []\n this.mColors = [[]]\n this.mTextureCoords = [[]]\n this.mFaces = []\n this.mBones = []\n this.hookupSkeletons = function (scene) {\n if (this.mBones.length == 0) return\n\n var allBones = []\n var offsetMatrix = []\n var skeletonRoot = scene.findNode(this.mBones[0].mName)\n\n while (skeletonRoot.mParent && skeletonRoot.mParent.isBone) {\n skeletonRoot = skeletonRoot.mParent\n }\n\n var threeSkeletonRoot = skeletonRoot.toTHREE(scene)\n var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene)\n this.threeNode.add(threeSkeletonRootBone)\n\n for (let i = 0; i < this.mBones.length; i++) {\n var bone = findMatchingBone(threeSkeletonRootBone, this.mBones[i].mName)\n\n if (bone) {\n var tbone = bone\n allBones.push(tbone)\n //tbone.matrixAutoUpdate = false;\n offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE())\n } else {\n var skeletonRoot = scene.findNode(this.mBones[i].mName)\n if (!skeletonRoot) return\n var threeSkeletonRoot = skeletonRoot.toTHREE(scene)\n var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene)\n this.threeNode.add(threeSkeletonRootBone)\n var bone = findMatchingBone(threeSkeletonRootBone, this.mBones[i].mName)\n var tbone = bone\n allBones.push(tbone)\n //tbone.matrixAutoUpdate = false;\n offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE())\n }\n }\n\n var skeleton = new Skeleton(allBones, offsetMatrix)\n\n this.threeNode.bind(skeleton, new Matrix4())\n this.threeNode.material.skinning = true\n }\n\n this.toTHREE = function (scene) {\n if (this.threeNode) return this.threeNode\n var geometry = new BufferGeometry()\n var mat\n if (scene.mMaterials[this.mMaterialIndex]) mat = scene.mMaterials[this.mMaterialIndex].toTHREE(scene)\n else mat = new MeshLambertMaterial()\n geometry.setIndex(new BufferAttribute(new Uint32Array(this.mIndexArray), 1))\n geometry.setAttribute('position', new BufferAttribute(this.mVertexBuffer, 3))\n if (this.mNormalBuffer && this.mNormalBuffer.length > 0) {\n geometry.setAttribute('normal', new BufferAttribute(this.mNormalBuffer, 3))\n }\n if (this.mColorBuffer && this.mColorBuffer.length > 0) {\n geometry.setAttribute('color', new BufferAttribute(this.mColorBuffer, 4))\n }\n if (this.mTexCoordsBuffers[0] && this.mTexCoordsBuffers[0].length > 0) {\n geometry.setAttribute('uv', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[0]), 2))\n }\n if (this.mTexCoordsBuffers[1] && this.mTexCoordsBuffers[1].length > 0) {\n geometry.setAttribute('uv1', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[1]), 2))\n }\n if (this.mTangentBuffer && this.mTangentBuffer.length > 0) {\n geometry.setAttribute('tangents', new BufferAttribute(this.mTangentBuffer, 3))\n }\n if (this.mBitangentBuffer && this.mBitangentBuffer.length > 0) {\n geometry.setAttribute('bitangents', new BufferAttribute(this.mBitangentBuffer, 3))\n }\n if (this.mBones.length > 0) {\n var weights = []\n var bones = []\n\n for (let i = 0; i < this.mBones.length; i++) {\n for (let j = 0; j < this.mBones[i].mWeights.length; j++) {\n var weight = this.mBones[i].mWeights[j]\n if (weight) {\n if (!weights[weight.mVertexId]) weights[weight.mVertexId] = []\n if (!bones[weight.mVertexId]) bones[weight.mVertexId] = []\n weights[weight.mVertexId].push(weight.mWeight)\n bones[weight.mVertexId].push(parseInt(i))\n }\n }\n }\n\n for (let i in bones) {\n sortWeights(bones[i], weights[i])\n }\n\n var _weights = []\n var _bones = []\n\n for (let i = 0; i < weights.length; i++) {\n for (let j = 0; j < 4; j++) {\n if (weights[i] && bones[i]) {\n _weights.push(weights[i][j])\n _bones.push(bones[i][j])\n } else {\n _weights.push(0)\n _bones.push(0)\n }\n }\n }\n\n geometry.setAttribute('skinWeight', new BufferAttribute(new Float32Array(_weights), BONESPERVERT))\n geometry.setAttribute('skinIndex', new BufferAttribute(new Float32Array(_bones), BONESPERVERT))\n }\n\n var mesh\n\n if (this.mBones.length == 0) mesh = new Mesh(geometry, mat)\n\n if (this.mBones.length > 0) {\n mesh = new SkinnedMesh(geometry, mat)\n mesh.normalizeSkinWeights()\n }\n\n this.threeNode = mesh\n //mesh.matrixAutoUpdate = false;\n return mesh\n }\n }\n }\n\n class aiFace {\n constructor() {\n this.mNumIndices = 0\n this.mIndices = []\n }\n }\n\n class aiVector3D {\n constructor() {\n this.x = 0\n this.y = 0\n this.z = 0\n\n this.toTHREE = function () {\n return new Vector3(this.x, this.y, this.z)\n }\n }\n }\n\n class aiColor3D {\n constructor() {\n this.r = 0\n this.g = 0\n this.b = 0\n this.a = 0\n this.toTHREE = function () {\n return new Color(this.r, this.g, this.b)\n }\n }\n }\n\n class aiQuaternion {\n constructor() {\n this.x = 0\n this.y = 0\n this.z = 0\n this.w = 0\n this.toTHREE = function () {\n return new Quaternion(this.x, this.y, this.z, this.w)\n }\n }\n }\n\n class aiVertexWeight {\n constructor() {\n this.mVertexId = 0\n this.mWeight = 0\n }\n }\n\n class aiString {\n constructor() {\n this.data = []\n this.toString = function () {\n var str = ''\n this.data.forEach(function (i) {\n str += String.fromCharCode(i)\n })\n return str.replace(/[^\\x20-\\x7E]+/g, '')\n }\n }\n }\n\n class aiVectorKey {\n constructor() {\n this.mTime = 0\n this.mValue = null\n }\n }\n\n class aiQuatKey {\n constructor() {\n this.mTime = 0\n this.mValue = null\n }\n }\n\n class aiNode {\n constructor() {\n this.mName = ''\n this.mTransformation = []\n this.mNumChildren = 0\n this.mNumMeshes = 0\n this.mMeshes = []\n this.mChildren = []\n this.toTHREE = function (scene) {\n if (this.threeNode) return this.threeNode\n var o = new Object3D()\n o.name = this.mName\n o.matrix = this.mTransformation.toTHREE()\n\n for (let i = 0; i < this.mChildren.length; i++) {\n o.add(this.mChildren[i].toTHREE(scene))\n }\n\n for (let i = 0; i < this.mMeshes.length; i++) {\n o.add(scene.mMeshes[this.mMeshes[i]].toTHREE(scene))\n }\n\n this.threeNode = o\n //o.matrixAutoUpdate = false;\n o.matrix.decompose(o.position, o.quaternion, o.scale)\n return o\n }\n }\n }\n\n class aiBone {\n constructor() {\n this.mName = ''\n this.mNumWeights = 0\n this.mOffsetMatrix = 0\n }\n }\n\n class aiMaterialProperty {\n constructor() {\n this.mKey = ''\n this.mSemantic = 0\n this.mIndex = 0\n this.mData = []\n this.mDataLength = 0\n this.mType = 0\n this.dataAsColor = function () {\n var array = new Uint8Array(this.mData).buffer\n var reader = new DataView(array)\n var r = reader.getFloat32(0, true)\n var g = reader.getFloat32(4, true)\n var b = reader.getFloat32(8, true)\n //var a = reader.getFloat32(12, true);\n return new Color(r, g, b)\n }\n\n this.dataAsFloat = function () {\n var array = new Uint8Array(this.mData).buffer\n var reader = new DataView(array)\n var r = reader.getFloat32(0, true)\n return r\n }\n\n this.dataAsBool = function () {\n var array = new Uint8Array(this.mData).buffer\n var reader = new DataView(array)\n var r = reader.getFloat32(0, true)\n return !!r\n }\n\n this.dataAsString = function () {\n var s = new aiString()\n s.data = this.mData\n return s.toString()\n }\n\n this.dataAsMap = function () {\n var s = new aiString()\n s.data = this.mData\n var path = s.toString()\n path = path.replace(/\\\\/g, '/')\n\n if (path.indexOf('/') != -1) {\n path = path.substr(path.lastIndexOf('/') + 1)\n }\n\n return textureLoader.load(path)\n }\n }\n }\n\n var namePropMapping = {\n '?mat.name': 'name',\n '$mat.shadingm': 'shading',\n '$mat.twosided': 'twoSided',\n '$mat.wireframe': 'wireframe',\n '$clr.ambient': 'ambient',\n '$clr.diffuse': 'color',\n '$clr.specular': 'specular',\n '$clr.emissive': 'emissive',\n '$clr.transparent': 'transparent',\n '$clr.reflective': 'reflect',\n '$mat.shininess': 'shininess',\n '$mat.reflectivity': 'reflectivity',\n '$mat.refracti': 'refraction',\n '$tex.file': 'map',\n }\n\n var nameTypeMapping = {\n '?mat.name': 'string',\n '$mat.shadingm': 'bool',\n '$mat.twosided': 'bool',\n '$mat.wireframe': 'bool',\n '$clr.ambient': 'color',\n '$clr.diffuse': 'color',\n '$clr.specular': 'color',\n '$clr.emissive': 'color',\n '$clr.transparent': 'color',\n '$clr.reflective': 'color',\n '$mat.shininess': 'float',\n '$mat.reflectivity': 'float',\n '$mat.refracti': 'float',\n '$tex.file': 'map',\n }\n\n class aiMaterial {\n constructor() {\n this.mNumAllocated = 0\n this.mNumProperties = 0\n this.mProperties = []\n this.toTHREE = function () {\n var mat = new MeshPhongMaterial()\n\n for (let i = 0; i < this.mProperties.length; i++) {\n if (nameTypeMapping[this.mProperties[i].mKey] == 'float') {\n mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsFloat()\n }\n if (nameTypeMapping[this.mProperties[i].mKey] == 'color') {\n mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsColor()\n }\n if (nameTypeMapping[this.mProperties[i].mKey] == 'bool') {\n mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsBool()\n }\n if (nameTypeMapping[this.mProperties[i].mKey] == 'string') {\n mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsString()\n }\n if (nameTypeMapping[this.mProperties[i].mKey] == 'map') {\n var prop = this.mProperties[i]\n if (prop.mSemantic == aiTextureType_DIFFUSE) mat.map = this.mProperties[i].dataAsMap()\n if (prop.mSemantic == aiTextureType_NORMALS) mat.normalMap = this.mProperties[i].dataAsMap()\n if (prop.mSemantic == aiTextureType_LIGHTMAP) mat.lightMap = this.mProperties[i].dataAsMap()\n if (prop.mSemantic == aiTextureType_OPACITY) mat.alphaMap = this.mProperties[i].dataAsMap()\n }\n }\n\n mat.ambient.r = 0.53\n mat.ambient.g = 0.53\n mat.ambient.b = 0.53\n mat.color.r = 1\n mat.color.g = 1\n mat.color.b = 1\n return mat\n }\n }\n }\n\n function veclerp(v1, v2, l) {\n var v = new Vector3()\n var lm1 = 1 - l\n v.x = v1.x * l + v2.x * lm1\n v.y = v1.y * l + v2.y * lm1\n v.z = v1.z * l + v2.z * lm1\n return v\n }\n\n function quatlerp(q1, q2, l) {\n return q1.clone().slerp(q2, 1 - l)\n }\n\n function sampleTrack(keys, time, lne, lerp) {\n if (keys.length == 1) return keys[0].mValue.toTHREE()\n\n var dist = Infinity\n var key = null\n var nextKey = null\n\n for (let i = 0; i < keys.length; i++) {\n var timeDist = Math.abs(keys[i].mTime - time)\n\n if (timeDist < dist && keys[i].mTime <= time) {\n dist = timeDist\n key = keys[i]\n nextKey = keys[i + 1]\n }\n }\n\n if (!key) {\n return null\n } else if (nextKey) {\n var dT = nextKey.mTime - key.mTime\n var T = key.mTime - time\n var l = T / dT\n\n return lerp(key.mValue.toTHREE(), nextKey.mValue.toTHREE(), l)\n } else {\n nextKey = keys[0].clone()\n nextKey.mTime += lne\n\n var dT = nextKey.mTime - key.mTime\n var T = key.mTime - time\n var l = T / dT\n\n return lerp(key.mValue.toTHREE(), nextKey.mValue.toTHREE(), l)\n }\n }\n\n class aiNodeAnim {\n constructor() {\n this.mNodeName = ''\n this.mNumPositionKeys = 0\n this.mNumRotationKeys = 0\n this.mNumScalingKeys = 0\n this.mPositionKeys = []\n this.mRotationKeys = []\n this.mScalingKeys = []\n this.mPreState = ''\n this.mPostState = ''\n this.init = function (tps) {\n if (!tps) tps = 1\n\n function t(t) {\n t.mTime /= tps\n }\n\n this.mPositionKeys.forEach(t)\n this.mRotationKeys.forEach(t)\n this.mScalingKeys.forEach(t)\n }\n\n this.sortKeys = function () {\n function comp(a, b) {\n return a.mTime - b.mTime\n }\n\n this.mPositionKeys.sort(comp)\n this.mRotationKeys.sort(comp)\n this.mScalingKeys.sort(comp)\n }\n\n this.getLength = function () {\n return Math.max(\n Math.max.apply(\n null,\n this.mPositionKeys.map(function (a) {\n return a.mTime\n }),\n ),\n Math.max.apply(\n null,\n this.mRotationKeys.map(function (a) {\n return a.mTime\n }),\n ),\n Math.max.apply(\n null,\n this.mScalingKeys.map(function (a) {\n return a.mTime\n }),\n ),\n )\n }\n\n this.toTHREE = function (o) {\n this.sortKeys()\n var length = this.getLength()\n var track = new Virtulous.KeyFrameTrack()\n\n for (let i = 0; i < length; i += 0.05) {\n var matrix = new Matrix4()\n var time = i\n var pos = sampleTrack(this.mPositionKeys, time, length, veclerp)\n var scale = sampleTrack(this.mScalingKeys, time, length, veclerp)\n var rotation = sampleTrack(this.mRotationKeys, time, length, quatlerp)\n matrix.compose(pos, rotation, scale)\n\n var key = new Virtulous.KeyFrame(time, matrix)\n track.addKey(key)\n }\n\n track.target = o.findNode(this.mNodeName).toTHREE()\n\n var tracks = [track]\n\n if (o.nodeToBoneMap[this.mNodeName]) {\n for (let i = 0; i < o.nodeToBoneMap[this.mNodeName].length; i++) {\n var t2 = track.clone()\n t2.target = o.nodeToBoneMap[this.mNodeName][i]\n tracks.push(t2)\n }\n }\n\n return tracks\n }\n }\n }\n\n class aiAnimation {\n constructor() {\n this.mName = ''\n this.mDuration = 0\n this.mTicksPerSecond = 0\n this.mNumChannels = 0\n this.mChannels = []\n this.toTHREE = function (root) {\n var animationHandle = new Virtulous.Animation()\n\n for (let i in this.mChannels) {\n this.mChannels[i].init(this.mTicksPerSecond)\n\n var tracks = this.mChannels[i].toTHREE(root)\n\n for (let j in tracks) {\n tracks[j].init()\n animationHandle.addTrack(tracks[j])\n }\n }\n\n animationHandle.length = Math.max.apply(\n null,\n animationHandle.tracks.map(function (e) {\n return e.length\n }),\n )\n return animationHandle\n }\n }\n }\n\n class aiTexture {\n constructor() {\n this.mWidth = 0\n this.mHeight = 0\n this.texAchFormatHint = []\n this.pcData = []\n }\n }\n\n class aiLight {\n constructor() {\n this.mName = ''\n this.mType = 0\n this.mAttenuationConstant = 0\n this.mAttenuationLinear = 0\n this.mAttenuationQuadratic = 0\n this.mAngleInnerCone = 0\n this.mAngleOuterCone = 0\n this.mColorDiffuse = null\n this.mColorSpecular = null\n this.mColorAmbient = null\n }\n }\n\n class aiCamera {\n constructor() {\n this.mName = ''\n this.mPosition = null\n this.mLookAt = null\n this.mUp = null\n this.mHorizontalFOV = 0\n this.mClipPlaneNear = 0\n this.mClipPlaneFar = 0\n this.mAspect = 0\n }\n }\n\n class aiScene {\n constructor() {\n this.versionMajor = 0\n this.versionMinor = 0\n this.versionRevision = 0\n this.compileFlags = 0\n this.mFlags = 0\n this.mNumMeshes = 0\n this.mNumMaterials = 0\n this.mNumAnimations = 0\n this.mNumTextures = 0\n this.mNumLights = 0\n this.mNumCameras = 0\n this.mRootNode = null\n this.mMeshes = []\n this.mMaterials = []\n this.mAnimations = []\n this.mLights = []\n this.mCameras = []\n this.nodeToBoneMap = {}\n this.findNode = function (name, root) {\n if (!root) {\n root = this.mRootNode\n }\n\n if (root.mName == name) {\n return root\n }\n\n for (let i = 0; i < root.mChildren.length; i++) {\n var ret = this.findNode(name, root.mChildren[i])\n if (ret) return ret\n }\n\n return null\n }\n\n this.toTHREE = function () {\n this.nodeCount = 0\n\n markBones(this)\n\n var o = this.mRootNode.toTHREE(this)\n\n for (let i in this.mMeshes) this.mMeshes[i].hookupSkeletons(this)\n\n if (this.mAnimations.length > 0) {\n var a = this.mAnimations[0].toTHREE(this)\n }\n\n return { object: o, animation: a }\n }\n }\n }\n\n class aiMatrix4 {\n constructor() {\n this.elements = [[], [], [], []]\n this.toTHREE = function () {\n var m = new Matrix4()\n\n for (let i = 0; i < 4; ++i) {\n for (let i2 = 0; i2 < 4; ++i2) {\n m.elements[i * 4 + i2] = this.elements[i2][i]\n }\n }\n\n return m\n }\n }\n }\n\n var littleEndian = true\n\n function readFloat(dataview) {\n var val = dataview.getFloat32(dataview.readOffset, littleEndian)\n dataview.readOffset += 4\n return val\n }\n\n function Read_double(dataview) {\n var val = dataview.getFloat64(dataview.readOffset, littleEndian)\n dataview.readOffset += 8\n return val\n }\n\n function Read_uint8_t(dataview) {\n var val = dataview.getUint8(dataview.readOffset)\n dataview.readOffset += 1\n return val\n }\n\n function Read_uint16_t(dataview) {\n var val = dataview.getUint16(dataview.readOffset, littleEndian)\n dataview.readOffset += 2\n return val\n }\n\n function Read_unsigned_int(dataview) {\n var val = dataview.getUint32(dataview.readOffset, littleEndian)\n dataview.readOffset += 4\n return val\n }\n\n function Read_uint32_t(dataview) {\n var val = dataview.getUint32(dataview.readOffset, littleEndian)\n dataview.readOffset += 4\n return val\n }\n\n function Read_aiVector3D(stream) {\n var v = new aiVector3D()\n v.x = readFloat(stream)\n v.y = readFloat(stream)\n v.z = readFloat(stream)\n return v\n }\n\n function Read_aiColor3D(stream) {\n var c = new aiColor3D()\n c.r = readFloat(stream)\n c.g = readFloat(stream)\n c.b = readFloat(stream)\n return c\n }\n\n function Read_aiQuaternion(stream) {\n var v = new aiQuaternion()\n v.w = readFloat(stream)\n v.x = readFloat(stream)\n v.y = readFloat(stream)\n v.z = readFloat(stream)\n return v\n }\n\n function Read_aiString(stream) {\n var s = new aiString()\n var stringlengthbytes = Read_unsigned_int(stream)\n stream.ReadBytes(s.data, 1, stringlengthbytes)\n return s.toString()\n }\n\n function Read_aiVertexWeight(stream) {\n var w = new aiVertexWeight()\n w.mVertexId = Read_unsigned_int(stream)\n w.mWeight = readFloat(stream)\n return w\n }\n\n function Read_aiMatrix4x4(stream) {\n var m = new aiMatrix4()\n\n for (let i = 0; i < 4; ++i) {\n for (let i2 = 0; i2 < 4; ++i2) {\n m.elements[i][i2] = readFloat(stream)\n }\n }\n\n return m\n }\n\n function Read_aiVectorKey(stream) {\n var v = new aiVectorKey()\n v.mTime = Read_double(stream)\n v.mValue = Read_aiVector3D(stream)\n return v\n }\n\n function Read_aiQuatKey(stream) {\n var v = new aiQuatKey()\n v.mTime = Read_double(stream)\n v.mValue = Read_aiQuaternion(stream)\n return v\n }\n\n function ReadArray_aiVertexWeight(stream, data, size) {\n for (let i = 0; i < size; i++) data[i] = Read_aiVertexWeight(stream)\n }\n\n function ReadArray_aiVectorKey(stream, data, size) {\n for (let i = 0; i < size; i++) data[i] = Read_aiVectorKey(stream)\n }\n\n function ReadArray_aiQuatKey(stream, data, size) {\n for (let i = 0; i < size; i++) data[i] = Read_aiQuatKey(stream)\n }\n\n function ReadBounds(stream, T /*p*/, n) {\n // not sure what to do here, the data isn't really useful.\n return stream.Seek(sizeof(T) * n, aiOrigin_CUR)\n }\n\n function ai_assert(bool) {\n if (!bool) throw 'asset failed'\n }\n\n function ReadBinaryNode(stream, parent, depth) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AINODE)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n var node = new aiNode()\n node.mParent = parent\n node.mDepth = depth\n node.mName = Read_aiString(stream)\n node.mTransformation = Read_aiMatrix4x4(stream)\n node.mNumChildren = Read_unsigned_int(stream)\n node.mNumMeshes = Read_unsigned_int(stream)\n\n if (node.mNumMeshes) {\n node.mMeshes = []\n\n for (let i = 0; i < node.mNumMeshes; ++i) {\n node.mMeshes[i] = Read_unsigned_int(stream)\n }\n }\n\n if (node.mNumChildren) {\n node.mChildren = []\n\n for (let i = 0; i < node.mNumChildren; ++i) {\n var node2 = ReadBinaryNode(stream, node, depth++)\n node.mChildren[i] = node2\n }\n }\n\n return node\n }\n\n // -----------------------------------------------------------------------------------\n\n function ReadBinaryBone(stream, b) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AIBONE)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n b.mName = Read_aiString(stream)\n b.mNumWeights = Read_unsigned_int(stream)\n b.mOffsetMatrix = Read_aiMatrix4x4(stream)\n // for the moment we write dumb min/max values for the bones, too.\n // maybe I'll add a better, hash-like solution later\n if (shortened) {\n ReadBounds(stream, b.mWeights, b.mNumWeights)\n } else {\n // else write as usual\n\n b.mWeights = []\n ReadArray_aiVertexWeight(stream, b.mWeights, b.mNumWeights)\n }\n\n return b\n }\n\n function ReadBinaryMesh(stream, mesh) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AIMESH)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n mesh.mPrimitiveTypes = Read_unsigned_int(stream)\n mesh.mNumVertices = Read_unsigned_int(stream)\n mesh.mNumFaces = Read_unsigned_int(stream)\n mesh.mNumBones = Read_unsigned_int(stream)\n mesh.mMaterialIndex = Read_unsigned_int(stream)\n mesh.mNumUVComponents = []\n // first of all, write bits for all existent vertex components\n var c = Read_unsigned_int(stream)\n\n if (c & ASSBIN_MESH_HAS_POSITIONS) {\n if (shortened) {\n ReadBounds(stream, mesh.mVertices, mesh.mNumVertices)\n } else {\n // else write as usual\n\n mesh.mVertices = []\n mesh.mVertexBuffer = stream.subArray32(stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4)\n stream.Seek(mesh.mNumVertices * 3 * 4, aiOrigin_CUR)\n }\n }\n\n if (c & ASSBIN_MESH_HAS_NORMALS) {\n if (shortened) {\n ReadBounds(stream, mesh.mNormals, mesh.mNumVertices)\n } else {\n // else write as usual\n\n mesh.mNormals = []\n mesh.mNormalBuffer = stream.subArray32(stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4)\n stream.Seek(mesh.mNumVertices * 3 * 4, aiOrigin_CUR)\n }\n }\n\n if (c & ASSBIN_MESH_HAS_TANGENTS_AND_BITANGENTS) {\n if (shortened) {\n ReadBounds(stream, mesh.mTangents, mesh.mNumVertices)\n ReadBounds(stream, mesh.mBitangents, mesh.mNumVertices)\n } else {\n // else write as usual\n\n mesh.mTangents = []\n mesh.mTangentBuffer = stream.subArray32(stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4)\n stream.Seek(mesh.mNumVertices * 3 * 4, aiOrigin_CUR)\n mesh.mBitangents = []\n mesh.mBitangentBuffer = stream.subArray32(stream.readOffset, stream.readOffset + mesh.mNumVertices * 3 * 4)\n stream.Seek(mesh.mNumVertices * 3 * 4, aiOrigin_CUR)\n }\n }\n\n for (let n = 0; n < AI_MAX_NUMBER_OF_COLOR_SETS; ++n) {\n if (!(c & ASSBIN_MESH_HAS_COLOR(n))) break\n\n if (shortened) {\n ReadBounds(stream, mesh.mColors[n], mesh.mNumVertices)\n } else {\n // else write as usual\n\n mesh.mColors[n] = []\n mesh.mColorBuffer = stream.subArray32(stream.readOffset, stream.readOffset + mesh.mNumVertices * 4 * 4)\n stream.Seek(mesh.mNumVertices * 4 * 4, aiOrigin_CUR)\n }\n }\n\n mesh.mTexCoordsBuffers = []\n\n for (let n = 0; n < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++n) {\n if (!(c & ASSBIN_MESH_HAS_TEXCOORD(n))) break\n\n // write number of UV components\n mesh.mNumUVComponents[n] = Read_unsigned_int(stream)\n\n if (shortened) {\n ReadBounds(stream, mesh.mTextureCoords[n], mesh.mNumVertices)\n } else {\n // else write as usual\n\n mesh.mTextureCoords[n] = []\n //note that assbin always writes 3d texcoords\n mesh.mTexCoordsBuffers[n] = []\n\n for (let uv = 0; uv < mesh.mNumVertices; uv++) {\n mesh.mTexCoordsBuffers[n].push(readFloat(stream))\n mesh.mTexCoordsBuffers[n].push(readFloat(stream))\n readFloat(stream)\n }\n }\n }\n\n // write faces. There are no floating-point calculations involved\n // in these, so we can write a simple hash over the face data\n // to the dump file. We generate a single 32 Bit hash for 512 faces\n // using Assimp's standard hashing function.\n if (shortened) {\n Read_unsigned_int(stream)\n } else {\n // else write as usual\n\n // if there are less than 2^16 vertices, we can simply use 16 bit integers ...\n mesh.mFaces = []\n mesh.mIndexArray = []\n\n for (let i = 0; i < mesh.mNumFaces; ++i) {\n var f = (mesh.mFaces[i] = new aiFace())\n // BOOST_STATIC_ASSERT(AI_MAX_FACE_INDICES <= 0xffff);\n f.mNumIndices = Read_uint16_t(stream)\n f.mIndices = []\n\n for (let a = 0; a < f.mNumIndices; ++a) {\n if (mesh.mNumVertices < 1 << 16) {\n f.mIndices[a] = Read_uint16_t(stream)\n } else {\n f.mIndices[a] = Read_unsigned_int(stream)\n }\n }\n\n if (f.mNumIndices === 3) {\n mesh.mIndexArray.push(f.mIndices[0])\n mesh.mIndexArray.push(f.mIndices[1])\n mesh.mIndexArray.push(f.mIndices[2])\n } else if (f.mNumIndices === 4) {\n mesh.mIndexArray.push(f.mIndices[0])\n mesh.mIndexArray.push(f.mIndices[1])\n mesh.mIndexArray.push(f.mIndices[2])\n mesh.mIndexArray.push(f.mIndices[2])\n mesh.mIndexArray.push(f.mIndices[3])\n mesh.mIndexArray.push(f.mIndices[0])\n } else {\n throw new Error(\"Sorry, can't currently triangulate polys. Use the triangulate preprocessor in Assimp.\")\n }\n }\n }\n\n // write bones\n if (mesh.mNumBones) {\n mesh.mBones = []\n\n for (let a = 0; a < mesh.mNumBones; ++a) {\n mesh.mBones[a] = new aiBone()\n ReadBinaryBone(stream, mesh.mBones[a])\n }\n }\n }\n\n function ReadBinaryMaterialProperty(stream, prop) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AIMATERIALPROPERTY)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n prop.mKey = Read_aiString(stream)\n prop.mSemantic = Read_unsigned_int(stream)\n prop.mIndex = Read_unsigned_int(stream)\n prop.mDataLength = Read_unsigned_int(stream)\n prop.mType = Read_unsigned_int(stream)\n prop.mData = []\n stream.ReadBytes(prop.mData, 1, prop.mDataLength)\n }\n\n // -----------------------------------------------------------------------------------\n\n function ReadBinaryMaterial(stream, mat) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AIMATERIAL)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n mat.mNumAllocated = mat.mNumProperties = Read_unsigned_int(stream)\n\n if (mat.mNumProperties) {\n if (mat.mProperties) {\n delete mat.mProperties\n }\n\n mat.mProperties = []\n\n for (let i = 0; i < mat.mNumProperties; ++i) {\n mat.mProperties[i] = new aiMaterialProperty()\n ReadBinaryMaterialProperty(stream, mat.mProperties[i])\n }\n }\n }\n\n function ReadBinaryNodeAnim(stream, nd) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AINODEANIM)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n nd.mNodeName = Read_aiString(stream)\n nd.mNumPositionKeys = Read_unsigned_int(stream)\n nd.mNumRotationKeys = Read_unsigned_int(stream)\n nd.mNumScalingKeys = Read_unsigned_int(stream)\n nd.mPreState = Read_unsigned_int(stream)\n nd.mPostState = Read_unsigned_int(stream)\n\n if (nd.mNumPositionKeys) {\n if (shortened) {\n ReadBounds(stream, nd.mPositionKeys, nd.mNumPositionKeys)\n } else {\n // else write as usual\n\n nd.mPositionKeys = []\n ReadArray_aiVectorKey(stream, nd.mPositionKeys, nd.mNumPositionKeys)\n }\n }\n\n if (nd.mNumRotationKeys) {\n if (shortened) {\n ReadBounds(stream, nd.mRotationKeys, nd.mNumRotationKeys)\n } else {\n // else write as usual\n\n nd.mRotationKeys = []\n ReadArray_aiQuatKey(stream, nd.mRotationKeys, nd.mNumRotationKeys)\n }\n }\n\n if (nd.mNumScalingKeys) {\n if (shortened) {\n ReadBounds(stream, nd.mScalingKeys, nd.mNumScalingKeys)\n } else {\n // else write as usual\n\n nd.mScalingKeys = []\n ReadArray_aiVectorKey(stream, nd.mScalingKeys, nd.mNumScalingKeys)\n }\n }\n }\n\n function ReadBinaryAnim(stream, anim) {\n var chunkID = Read_uint32_t(stream)\n ai_assert(chunkID == ASSBIN_CHUNK_AIANIMATION)\n /*uint32_t size =*/\n Read_uint32_t(stream)\n anim.mName = Read_aiString(stream)\n anim.mDuration = Read_double(stream)\n anim.mTicksPerSecond = Read_double(stream)\n anim.mNumChannels = Re