var CTM = CTM || {}; CTM.CompressionMethod = { RAW: 0x00574152, MG1: 0x0031474d, MG2: 0x0032474d }; CTM.Flags = { NORMALS: 0x00000001 }; CTM.File = function(stream){ this.load(stream); }; CTM.File.prototype.load = function(stream){ this.header = new CTM.FileHeader(stream); this.body = new CTM.FileBody(this.header); this.getReader().read(stream, this.body); }; CTM.File.prototype.getReader = function(){ var reader; switch(this.header.compressionMethod){ case CTM.CompressionMethod.RAW: reader = new CTM.ReaderRAW(); break; case CTM.CompressionMethod.MG1: reader = new CTM.ReaderMG1(); break; case CTM.CompressionMethod.MG2: reader = new CTM.ReaderMG2(); break; } return reader; }; CTM.FileHeader = function(stream){ stream.readInt32(); //magic "OCTM" this.fileFormat = stream.readInt32(); this.compressionMethod = stream.readInt32(); this.vertexCount = stream.readInt32(); this.triangleCount = stream.readInt32(); this.uvMapCount = stream.readInt32(); this.attrMapCount = stream.readInt32(); this.flags = stream.readInt32(); this.comment = stream.readString(); }; CTM.FileHeader.prototype.hasNormals = function(){ return this.flags & CTM.Flags.NORMALS; }; CTM.FileBody = function(header){ var i = header.triangleCount * 3, v = header.vertexCount * 3, n = header.hasNormals()? header.vertexCount * 3: 0, u = header.vertexCount * 2, a = header.vertexCount * 4, j = 0; var data = new ArrayBuffer( (i + v + n + (u * header.uvMapCount) + (a * header.attrMapCount) ) * 4); this.indices = new Uint32Array(data, 0, i); this.vertices = new Float32Array(data, i * 4, v); if ( header.hasNormals() ){ this.normals = new Float32Array(data, (i + v) * 4, n); } if (header.uvMapCount){ this.uvMaps = []; for (j = 0; j < header.uvMapCount; ++ j){ this.uvMaps[j] = {uv: new Float32Array(data, (i + v + n + (j * u) ) * 4, u) }; } } if (header.attrMapCount){ this.attrMaps = []; for (j = 0; j < header.attrMapCount; ++ j){ this.attrMaps[j] = {attr: new Float32Array(data, (i + v + n + (u * header.uvMapCount) + (j * a) ) * 4, a) }; } } }; CTM.FileMG2Header = function(stream){ stream.readInt32(); //magic "MG2H" this.vertexPrecision = stream.readFloat32(); this.normalPrecision = stream.readFloat32(); this.lowerBoundx = stream.readFloat32(); this.lowerBoundy = stream.readFloat32(); this.lowerBoundz = stream.readFloat32(); this.higherBoundx = stream.readFloat32(); this.higherBoundy = stream.readFloat32(); this.higherBoundz = stream.readFloat32(); this.divx = stream.readInt32(); this.divy = stream.readInt32(); this.divz = stream.readInt32(); this.sizex = (this.higherBoundx - this.lowerBoundx) / this.divx; this.sizey = (this.higherBoundy - this.lowerBoundy) / this.divy; this.sizez = (this.higherBoundz - this.lowerBoundz) / this.divz; }; CTM.ReaderRAW = function(){ }; CTM.ReaderRAW.prototype.read = function(stream, body){ this.readIndices(stream, body.indices); this.readVertices(stream, body.vertices); if (body.normals){ this.readNormals(stream, body.normals); } if (body.uvMaps){ this.readUVMaps(stream, body.uvMaps); } if (body.attrMaps){ this.readAttrMaps(stream, body.attrMaps); } }; CTM.ReaderRAW.prototype.readIndices = function(stream, indices){ stream.readInt32(); //magic "INDX" stream.readArrayInt32(indices); }; CTM.ReaderRAW.prototype.readVertices = function(stream, vertices){ stream.readInt32(); //magic "VERT" stream.readArrayFloat32(vertices); }; CTM.ReaderRAW.prototype.readNormals = function(stream, normals){ stream.readInt32(); //magic "NORM" stream.readArrayFloat32(normals); }; CTM.ReaderRAW.prototype.readUVMaps = function(stream, uvMaps){ var i = 0; for (; i < uvMaps.length; ++ i){ stream.readInt32(); //magic "TEXC" uvMaps[i].name = stream.readString(); uvMaps[i].filename = stream.readString(); stream.readArrayFloat32(uvMaps[i].uv); } }; CTM.ReaderRAW.prototype.readAttrMaps = function(stream, attrMaps){ var i = 0; for (; i < attrMaps.length; ++ i){ stream.readInt32(); //magic "ATTR" attrMaps[i].name = stream.readString(); stream.readArrayFloat32(attrMaps[i].attr); } }; CTM.ReaderMG1 = function(){ }; CTM.ReaderMG1.prototype.read = function(stream, body){ this.readIndices(stream, body.indices); this.readVertices(stream, body.vertices); if (body.normals){ this.readNormals(stream, body.normals); } if (body.uvMaps){ this.readUVMaps(stream, body.uvMaps); } if (body.attrMaps){ this.readAttrMaps(stream, body.attrMaps); } }; CTM.ReaderMG1.prototype.readIndices = function(stream, indices){ stream.readInt32(); //magic "INDX" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(indices, 3); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); CTM.restoreIndices(indices, indices.length); }; CTM.ReaderMG1.prototype.readVertices = function(stream, vertices){ stream.readInt32(); //magic "VERT" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(vertices, 1); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); }; CTM.ReaderMG1.prototype.readNormals = function(stream, normals){ stream.readInt32(); //magic "NORM" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(normals, 3); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); }; CTM.ReaderMG1.prototype.readUVMaps = function(stream, uvMaps){ var i = 0; for (; i < uvMaps.length; ++ i){ stream.readInt32(); //magic "TEXC" uvMaps[i].name = stream.readString(); uvMaps[i].filename = stream.readString(); stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(uvMaps[i].uv, 2); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); } }; CTM.ReaderMG1.prototype.readAttrMaps = function(stream, attrMaps){ var i = 0; for (; i < attrMaps.length; ++ i){ stream.readInt32(); //magic "ATTR" attrMaps[i].name = stream.readString(); stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(attrMaps[i].attr, 4); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); } }; CTM.ReaderMG2 = function(){ }; CTM.ReaderMG2.prototype.read = function(stream, body){ this.MG2Header = new CTM.FileMG2Header(stream); this.readVertices(stream, body.vertices); this.readIndices(stream, body.indices); if (body.normals){ this.readNormals(stream, body); } if (body.uvMaps){ this.readUVMaps(stream, body.uvMaps); } if (body.attrMaps){ this.readAttrMaps(stream, body.attrMaps); } }; CTM.ReaderMG2.prototype.readVertices = function(stream, vertices){ stream.readInt32(); //magic "VERT" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(vertices, 3); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); var gridIndices = this.readGridIndices(stream, vertices); CTM.restoreVertices(vertices, this.MG2Header, gridIndices, this.MG2Header.vertexPrecision); }; CTM.ReaderMG2.prototype.readGridIndices = function(stream, vertices){ stream.readInt32(); //magic "GIDX" stream.readInt32(); //packed size var gridIndices = new Uint32Array(vertices.length / 3); var interleaved = new CTM.InterleavedStream(gridIndices, 1); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); CTM.restoreGridIndices(gridIndices, gridIndices.length); return gridIndices; }; CTM.ReaderMG2.prototype.readIndices = function(stream, indices){ stream.readInt32(); //magic "INDX" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(indices, 3); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); CTM.restoreIndices(indices, indices.length); }; CTM.ReaderMG2.prototype.readNormals = function(stream, body){ stream.readInt32(); //magic "NORM" stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(body.normals, 3); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); var smooth = CTM.calcSmoothNormals(body.indices, body.vertices); CTM.restoreNormals(body.normals, smooth, this.MG2Header.normalPrecision); }; CTM.ReaderMG2.prototype.readUVMaps = function(stream, uvMaps){ var i = 0; for (; i < uvMaps.length; ++ i){ stream.readInt32(); //magic "TEXC" uvMaps[i].name = stream.readString(); uvMaps[i].filename = stream.readString(); var precision = stream.readFloat32(); stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(uvMaps[i].uv, 2); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); CTM.restoreMap(uvMaps[i].uv, 2, precision); } }; CTM.ReaderMG2.prototype.readAttrMaps = function(stream, attrMaps){ var i = 0; for (; i < attrMaps.length; ++ i){ stream.readInt32(); //magic "ATTR" attrMaps[i].name = stream.readString(); var precision = stream.readFloat32(); stream.readInt32(); //packed size var interleaved = new CTM.InterleavedStream(attrMaps[i].attr, 4); LZMA.decompress(stream, stream, interleaved, interleaved.data.length); CTM.restoreMap(attrMaps[i].attr, 4, precision); } }; CTM.restoreIndices = function(indices, len){ var i = 3; if (len > 0){ indices[2] += indices[0]; } for (; i < len; i += 3){ indices[i] += indices[i - 3]; if (indices[i] === indices[i - 3]){ indices[i + 1] += indices[i - 2]; }else{ indices[i + 1] += indices[i]; } indices[i + 2] += indices[i]; } }; CTM.restoreGridIndices = function(gridIndices, len){ var i = 1; for (; i < len; ++ i){ gridIndices[i] += gridIndices[i - 1]; } }; CTM.restoreVertices = function(vertices, grid, gridIndices, precision){ var gridIdx, delta, x, y, z, intVertices = new Uint32Array(vertices.buffer, vertices.byteOffset, vertices.length), ydiv = grid.divx, zdiv = ydiv * grid.divy, prevGridIdx = 0x7fffffff, prevDelta = 0, i = 0, j = 0, len = gridIndices.length; for (; i < len; j += 3){ x = gridIdx = gridIndices[i ++]; z = ~~(x / zdiv); x -= ~~(z * zdiv); y = ~~(x / ydiv); x -= ~~(y * ydiv); delta = intVertices[j]; if (gridIdx === prevGridIdx){ delta += prevDelta; } vertices[j] = grid.lowerBoundx + x * grid.sizex + precision * delta; vertices[j + 1] = grid.lowerBoundy + y * grid.sizey + precision * intVertices[j + 1]; vertices[j + 2] = grid.lowerBoundz + z * grid.sizez + precision * intVertices[j + 2]; prevGridIdx = gridIdx; prevDelta = delta; } }; CTM.restoreNormals = function(normals, smooth, precision){ var ro, phi, theta, sinPhi, nx, ny, nz, by, bz, len, intNormals = new Uint32Array(normals.buffer, normals.byteOffset, normals.length), i = 0, k = normals.length, PI_DIV_2 = 3.141592653589793238462643 * 0.5; for (; i < k; i += 3){ ro = intNormals[i] * precision; phi = intNormals[i + 1]; if (phi === 0){ normals[i] = smooth[i] * ro; normals[i + 1] = smooth[i + 1] * ro; normals[i + 2] = smooth[i + 2] * ro; }else{ if (phi <= 4){ theta = (intNormals[i + 2] - 2) * PI_DIV_2; }else{ theta = ( (intNormals[i + 2] * 4 / phi) - 2) * PI_DIV_2; } phi *= precision * PI_DIV_2; sinPhi = ro * Math.sin(phi); nx = sinPhi * Math.cos(theta); ny = sinPhi * Math.sin(theta); nz = ro * Math.cos(phi); bz = smooth[i + 1]; by = smooth[i] - smooth[i + 2]; len = Math.sqrt(2 * bz * bz + by * by); if (len > 1e-20){ by /= len; bz /= len; } normals[i] = smooth[i] * nz + (smooth[i + 1] * bz - smooth[i + 2] * by) * ny - bz * nx; normals[i + 1] = smooth[i + 1] * nz - (smooth[i + 2] + smooth[i] ) * bz * ny + by * nx; normals[i + 2] = smooth[i + 2] * nz + (smooth[i] * by + smooth[i + 1] * bz) * ny + bz * nx; } } }; CTM.restoreMap = function(map, count, precision){ var delta, value, intMap = new Uint32Array(map.buffer, map.byteOffset, map.length), i = 0, j, len = map.length; for (; i < count; ++ i){ delta = 0; for (j = i; j < len; j += count){ value = intMap[j]; delta += value & 1? -( (value + 1) >> 1): value >> 1; map[j] = delta * precision; } } }; CTM.calcSmoothNormals = function(indices, vertices){ var smooth = new Float32Array(vertices.length), indx, indy, indz, nx, ny, nz, v1x, v1y, v1z, v2x, v2y, v2z, len, i, k; for (i = 0, k = indices.length; i < k;){ indx = indices[i ++] * 3; indy = indices[i ++] * 3; indz = indices[i ++] * 3; v1x = vertices[indy] - vertices[indx]; v2x = vertices[indz] - vertices[indx]; v1y = vertices[indy + 1] - vertices[indx + 1]; v2y = vertices[indz + 1] - vertices[indx + 1]; v1z = vertices[indy + 2] - vertices[indx + 2]; v2z = vertices[indz + 2] - vertices[indx + 2]; nx = v1y * v2z - v1z * v2y; ny = v1z * v2x - v1x * v2z; nz = v1x * v2y - v1y * v2x; len = Math.sqrt(nx * nx + ny * ny + nz * nz); if (len > 1e-10){ nx /= len; ny /= len; nz /= len; } smooth[indx] += nx; smooth[indx + 1] += ny; smooth[indx + 2] += nz; smooth[indy] += nx; smooth[indy + 1] += ny; smooth[indy + 2] += nz; smooth[indz] += nx; smooth[indz + 1] += ny; smooth[indz + 2] += nz; } for (i = 0, k = smooth.length; i < k; i += 3){ len = Math.sqrt(smooth[i] * smooth[i] + smooth[i + 1] * smooth[i + 1] + smooth[i + 2] * smooth[i + 2]); if(len > 1e-10){ smooth[i] /= len; smooth[i + 1] /= len; smooth[i + 2] /= len; } } return smooth; }; CTM.isLittleEndian = (function(){ var buffer = new ArrayBuffer(2), bytes = new Uint8Array(buffer), ints = new Uint16Array(buffer); bytes[0] = 1; return ints[0] === 1; }()); CTM.InterleavedStream = function(data, count){ this.data = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); this.offset = CTM.isLittleEndian? 3: 0; this.count = count * 4; this.len = this.data.length; }; CTM.InterleavedStream.prototype.writeByte = function(value){ this.data[this.offset] = value; this.offset += this.count; if (this.offset >= this.len){ this.offset -= this.len - 4; if (this.offset >= this.count){ this.offset -= this.count + (CTM.isLittleEndian? 1: -1); } } }; CTM.Stream = function(data){ this.data = data; this.offset = 0; }; CTM.Stream.prototype.TWO_POW_MINUS23 = Math.pow(2, -23); CTM.Stream.prototype.TWO_POW_MINUS126 = Math.pow(2, -126); CTM.Stream.prototype.readByte = function(){ return this.data.charCodeAt(this.offset ++) & 0xff; }; CTM.Stream.prototype.readInt32 = function(){ var i = this.readByte(); i |= this.readByte() << 8; i |= this.readByte() << 16; return i | (this.readByte() << 24); }; CTM.Stream.prototype.readFloat32 = function(){ var m = this.readByte(); m += this.readByte() << 8; var b1 = this.readByte(); var b2 = this.readByte(); m += (b1 & 0x7f) << 16; var e = ( (b2 & 0x7f) << 1) | ( (b1 & 0x80) >>> 7); var s = b2 & 0x80? -1: 1; if (e === 255){ return m !== 0? NaN: s * Infinity; } if (e > 0){ return s * (1 + (m * this.TWO_POW_MINUS23) ) * Math.pow(2, e - 127); } if (m !== 0){ return s * m * this.TWO_POW_MINUS126; } return s * 0; }; CTM.Stream.prototype.readString = function(){ var len = this.readInt32(); this.offset += len; return this.data.substr(this.offset - len, len); }; CTM.Stream.prototype.readArrayInt32 = function(array){ var i = 0, len = array.length; while(i < len){ array[i ++] = this.readInt32(); } return array; }; CTM.Stream.prototype.readArrayFloat32 = function(array){ var i = 0, len = array.length; while(i < len){ array[i ++] = this.readFloat32(); } return array; };