123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585 |
- /**
- * Loader for UTF8 version2 (after r51) encoded models generated by:
- * http://code.google.com/p/webgl-loader/
- *
- * Code to load/decompress mesh is taken from r100 of this webgl-loader
- */
- THREE.UTF8v2Loader = function () {};
- /**
- * Load UTF8 encoded model
- * @param jsonUrl - URL from which to load json containing information about model
- * @param callback - Callback(THREE.Object3D) on successful loading of model
- * @param options - options on how to load model (see THREE.MTLLoader.MaterialCreator for basic options)
- * Additional options include
- * geometryBase: Base url from which to load referenced geometries
- * materialBase: Base url from which to load referenced textures
- */
- THREE.UTF8v2Loader.prototype.load = function ( jsonUrl, callback, options ) {
- this.downloadModelJson(jsonUrl, options, callback);
- };
- THREE.UTF8v2Loader.GeometryCreator = function() {
- };
- THREE.UTF8v2Loader.GeometryCreator.prototype = {
- create: function (attribArray, indexArray, bboxen) {
- var geometry = new THREE.Geometry();
- this.init_vertices( geometry, attribArray, 8, 0 );
- var uvs = this.init_uvs( attribArray, 8, 3 );
- var normals = this.init_normals( attribArray, 8, 5 );
- this.init_faces( geometry, normals, uvs, indexArray );
- geometry.computeCentroids();
- geometry.computeFaceNormals();
- return geometry;
- },
- init_vertices: function ( scope, data, stride, offset ) {
- var i, x, y, z,
- end = data.length;
- for( i = offset; i < end; i += stride ) {
- x = data[ i ];
- y = data[ i + 1 ];
- z = data[ i + 2 ];
- this.addVertex( scope, x, y, z );
- }
- },
- init_normals: function( data, stride, offset ) {
- var normals = [];
- var i, x, y, z,
- end = data.length;
- for( i = offset; i < end; i += stride ) {
- x = data[ i ];
- y = data[ i + 1 ];
- z = data[ i + 2 ];
- // assumes already normalized to <-1,1> (unlike previous version of UTF8Loader)
- normals.push( x, y, z );
- }
- return normals;
- },
- init_uvs: function( data, stride, offset ) {
- var uvs = [];
- var i, u, v,
- end = data.length;
- for( i = offset; i < end; i += stride ) {
- // Assumes uvs are already normalized (unlike previous version of UTF8Loader)
- // uvs can be negative, need to set wrap for texture map later on...
- u = data[ i ];
- v = data[ i + 1 ];
- uvs.push( u, v );
- }
- return uvs;
- },
- init_faces: function( scope, normals, uvs, indices ) {
- var i,
- a, b, c,
- u1, v1, u2, v2, u3, v3,
- m,
- end = indices.length;
- m = 0; // all faces defaulting to material 0
- for( i = 0; i < end; i += 3 ) {
- a = indices[ i ];
- b = indices[ i + 1 ];
- c = indices[ i + 2 ];
- this.f3n( scope, normals, a, b, c, m, a, b, c );
- u1 = uvs[ a * 2 ];
- v1 = uvs[ a * 2 + 1 ];
- u2 = uvs[ b * 2 ];
- v2 = uvs[ b * 2 + 1 ];
- u3 = uvs[ c * 2 ];
- v3 = uvs[ c * 2 + 1 ];
- this.uv3( scope.faceVertexUvs[ 0 ], u1, v1, u2, v2, u3, v3 );
- }
- },
- addVertex: function ( scope, x, y, z ) {
- scope.vertices.push( new THREE.Vector3( x, y, z ) );
- },
- f3n: function( scope, normals, a, b, c, mi, nai, nbi, nci ) {
- var nax = normals[ nai * 3 ],
- nay = normals[ nai * 3 + 1 ],
- naz = normals[ nai * 3 + 2 ],
- nbx = normals[ nbi * 3 ],
- nby = normals[ nbi * 3 + 1 ],
- nbz = normals[ nbi * 3 + 2 ],
- ncx = normals[ nci * 3 ],
- ncy = normals[ nci * 3 + 1 ],
- ncz = normals[ nci * 3 + 2 ];
- var na = new THREE.Vector3( nax, nay, naz ),
- nb = new THREE.Vector3( nbx, nby, nbz ),
- nc = new THREE.Vector3( ncx, ncy, ncz );
- scope.faces.push( new THREE.Face3( a, b, c, [ na, nb, nc ], null, mi ) );
- },
- uv3: function ( where, u1, v1, u2, v2, u3, v3 ) {
- var uv = [];
- uv.push( new THREE.UV( u1, v1 ) );
- uv.push( new THREE.UV( u2, v2 ) );
- uv.push( new THREE.UV( u3, v3 ) );
- where.push( uv );
- }
- };
- // UTF-8 decoder from webgl-loader (r100)
- // http://code.google.com/p/webgl-loader/
- // Model manifest description. Contains objects like:
- // name: {
- // materials: { 'material_name': { ... } ... },
- // decodeParams: {
- // decodeOffsets: [ ... ],
- // decodeScales: [ ... ],
- // },
- // urls: {
- // 'url': [
- // { material: 'material_name',
- // attribRange: [#, #],
- // indexRange: [#, #],
- // names: [ 'object names' ... ],
- // lengths: [#, #, # ... ]
- // }
- // ],
- // ...
- // }
- // }
- var DEFAULT_DECODE_PARAMS = {
- decodeOffsets: [-4095, -4095, -4095, 0, 0, -511, -511, -511],
- decodeScales: [1/8191, 1/8191, 1/8191, 1/1023, 1/1023, 1/1023, 1/1023, 1/1023]
- // TODO: normal decoding? (see walt.js)
- // needs to know: input, output (from vertex format!)
- //
- // Should split attrib/index.
- // 1) Decode position and non-normal attributes.
- // 2) Decode indices, computing normals
- // 3) Maybe normalize normals? Only necessary for refinement, or fixed?
- // 4) Maybe refine normals? Should this be part of regular refinement?
- // 5) Morphing
- };
- // Triangle strips!
- // TODO: will it be an optimization to specialize this method at
- // runtime for different combinations of stride, decodeOffset and
- // decodeScale?
- THREE.UTF8v2Loader.prototype.decompressAttribsInner_ = function (str, inputStart, inputEnd,
- output, outputStart, stride,
- decodeOffset, decodeScale) {
- var prev = 0;
- for (var j = inputStart; j < inputEnd; j++) {
- var code = str.charCodeAt(j);
- prev += (code >> 1) ^ (-(code & 1));
- output[outputStart] = decodeScale * (prev + decodeOffset);
- outputStart += stride;
- }
- };
- THREE.UTF8v2Loader.prototype.decompressIndices_ = function(str, inputStart, numIndices,
- output, outputStart) {
- var highest = 0;
- for (var i = 0; i < numIndices; i++) {
- var code = str.charCodeAt(inputStart++);
- output[outputStart++] = highest - code;
- if (code == 0) {
- highest++;
- }
- }
- };
- THREE.UTF8v2Loader.prototype.decompressAABBs_ = function (str, inputStart, numBBoxen,
- decodeOffsets, decodeScales) {
- var numFloats = 6 * numBBoxen;
- var inputEnd = inputStart + numFloats;
- var bboxen = new Float32Array(numFloats);
- var outputStart = 0;
- for (var i = inputStart; i < inputEnd; i += 6) {
- var minX = str.charCodeAt(i + 0) + decodeOffsets[0];
- var minY = str.charCodeAt(i + 1) + decodeOffsets[1];
- var minZ = str.charCodeAt(i + 2) + decodeOffsets[2];
- var radiusX = (str.charCodeAt(i + 3) + 1) >> 1;
- var radiusY = (str.charCodeAt(i + 4) + 1) >> 1;
- var radiusZ = (str.charCodeAt(i + 5) + 1) >> 1;
- bboxen[outputStart++] = decodeScales[0] * (minX + radiusX);
- bboxen[outputStart++] = decodeScales[1] * (minY + radiusY);
- bboxen[outputStart++] = decodeScales[2] * (minZ + radiusZ);
- bboxen[outputStart++] = decodeScales[0] * radiusX;
- bboxen[outputStart++] = decodeScales[1] * radiusY;
- bboxen[outputStart++] = decodeScales[2] * radiusZ;
- }
- return bboxen;
- };
- THREE.UTF8v2Loader.prototype.decompressMesh = function (str, meshParams, decodeParams, name, idx, callback) {
- // Extract conversion parameters from attribArrays.
- var stride = decodeParams.decodeScales.length;
- var decodeOffsets = decodeParams.decodeOffsets;
- var decodeScales = decodeParams.decodeScales;
- var attribStart = meshParams.attribRange[0];
- var numVerts = meshParams.attribRange[1];
- // Decode attributes.
- var inputOffset = attribStart;
- var attribsOut = new Float32Array(stride * numVerts);
- for (var j = 0; j < stride; j++) {
- var end = inputOffset + numVerts;
- var decodeScale = decodeScales[j];
- if (decodeScale) {
- // Assume if decodeScale is never set, simply ignore the
- // attribute.
- this.decompressAttribsInner_(str, inputOffset, end,
- attribsOut, j, stride,
- decodeOffsets[j], decodeScale);
- }
- inputOffset = end;
- }
- var indexStart = meshParams.indexRange[0];
- var numIndices = 3*meshParams.indexRange[1];
- var indicesOut = new Uint16Array(numIndices);
- this.decompressIndices_(str, inputOffset, numIndices, indicesOut, 0);
- // Decode bboxen.
- var bboxen = undefined;
- var bboxOffset = meshParams.bboxes;
- if (bboxOffset) {
- bboxen = this.decompressAABBs_(str, bboxOffset, meshParams.names.length,
- decodeOffsets, decodeScales);
- }
- callback(name, idx, attribsOut, indicesOut, bboxen, meshParams);
- };
- THREE.UTF8v2Loader.prototype.copyAttrib = function (stride, attribsOutFixed, lastAttrib, index) {
- for (var j = 0; j < stride; j++) {
- lastAttrib[j] = attribsOutFixed[stride*index + j];
- }
- };
- THREE.UTF8v2Loader.prototype.decodeAttrib2 = function (str, stride, decodeOffsets, decodeScales, deltaStart,
- numVerts, attribsOut, attribsOutFixed, lastAttrib,
- index) {
- for (var j = 0; j < 5; j++) {
- var code = str.charCodeAt(deltaStart + numVerts*j + index);
- var delta = (code >> 1) ^ (-(code & 1));
- lastAttrib[j] += delta;
- attribsOutFixed[stride*index + j] = lastAttrib[j];
- attribsOut[stride*index + j] =
- decodeScales[j] * (lastAttrib[j] + decodeOffsets[j]);
- }
- };
- THREE.UTF8v2Loader.prototype.accumulateNormal = function (i0, i1, i2, attribsOutFixed, crosses) {
- var p0x = attribsOutFixed[8*i0 + 0];
- var p0y = attribsOutFixed[8*i0 + 1];
- var p0z = attribsOutFixed[8*i0 + 2];
- var p1x = attribsOutFixed[8*i1 + 0];
- var p1y = attribsOutFixed[8*i1 + 1];
- var p1z = attribsOutFixed[8*i1 + 2];
- var p2x = attribsOutFixed[8*i2 + 0];
- var p2y = attribsOutFixed[8*i2 + 1];
- var p2z = attribsOutFixed[8*i2 + 2];
- p1x -= p0x;
- p1y -= p0y;
- p1z -= p0z;
- p2x -= p0x;
- p2y -= p0y;
- p2z -= p0z;
- p0x = p1y*p2z - p1z*p2y;
- p0y = p1z*p2x - p1x*p2z;
- p0z = p1x*p2y - p1y*p2x;
- crosses[3*i0 + 0] += p0x;
- crosses[3*i0 + 1] += p0y;
- crosses[3*i0 + 2] += p0z;
- crosses[3*i1 + 0] += p0x;
- crosses[3*i1 + 1] += p0y;
- crosses[3*i1 + 2] += p0z;
- crosses[3*i2 + 0] += p0x;
- crosses[3*i2 + 1] += p0y;
- crosses[3*i2 + 2] += p0z;
- };
- THREE.UTF8v2Loader.prototype.decompressMesh2 = function(str, meshParams, decodeParams, name, idx, callback) {
- var MAX_BACKREF = 96;
- // Extract conversion parameters from attribArrays.
- var stride = decodeParams.decodeScales.length;
- var decodeOffsets = decodeParams.decodeOffsets;
- var decodeScales = decodeParams.decodeScales;
- var deltaStart = meshParams.attribRange[0];
- var numVerts = meshParams.attribRange[1];
- var codeStart = meshParams.codeRange[0];
- var codeLength = meshParams.codeRange[1];
- var numIndices = 3*meshParams.codeRange[2];
- var indicesOut = new Uint16Array(numIndices);
- var crosses = new Int32Array(3*numVerts);
- var lastAttrib = new Uint16Array(stride);
- var attribsOutFixed = new Uint16Array(stride * numVerts);
- var attribsOut = new Float32Array(stride * numVerts);
- var highest = 0;
- var outputStart = 0;
- for (var i = 0; i < numIndices; i += 3) {
- var code = str.charCodeAt(codeStart++);
- var max_backref = Math.min(i, MAX_BACKREF);
- if (code < max_backref) {
- // Parallelogram
- var winding = code % 3;
- var backref = i - (code - winding);
- var i0, i1, i2;
- switch (winding) {
- case 0:
- i0 = indicesOut[backref + 2];
- i1 = indicesOut[backref + 1];
- i2 = indicesOut[backref + 0];
- break;
- case 1:
- i0 = indicesOut[backref + 0];
- i1 = indicesOut[backref + 2];
- i2 = indicesOut[backref + 1];
- break;
- case 2:
- i0 = indicesOut[backref + 1];
- i1 = indicesOut[backref + 0];
- i2 = indicesOut[backref + 2];
- break;
- }
- indicesOut[outputStart++] = i0;
- indicesOut[outputStart++] = i1;
- code = str.charCodeAt(codeStart++);
- var index = highest - code;
- indicesOut[outputStart++] = index;
- if (code === 0) {
- for (var j = 0; j < 5; j++) {
- var deltaCode = str.charCodeAt(deltaStart + numVerts*j + highest);
- var prediction = ((deltaCode >> 1) ^ (-(deltaCode & 1))) +
- attribsOutFixed[stride*i0 + j] +
- attribsOutFixed[stride*i1 + j] -
- attribsOutFixed[stride*i2 + j];
- lastAttrib[j] = prediction;
- attribsOutFixed[stride*highest + j] = prediction;
- attribsOut[stride*highest + j] =
- decodeScales[j] * (prediction + decodeOffsets[j]);
- }
- highest++;
- } else {
- this.copyAttrib(stride, attribsOutFixed, lastAttrib, index);
- }
- this.accumulateNormal(i0, i1, index, attribsOutFixed, crosses);
- } else {
- // Simple
- var index0 = highest - (code - max_backref);
- indicesOut[outputStart++] = index0;
- if (code === max_backref) {
- this.decodeAttrib2(str, stride, decodeOffsets, decodeScales, deltaStart,
- numVerts, attribsOut, attribsOutFixed, lastAttrib,
- highest++);
- } else {
- this.copyAttrib(stride, attribsOutFixed, lastAttrib, index0);
- }
- code = str.charCodeAt(codeStart++);
- var index1 = highest - code;
- indicesOut[outputStart++] = index1;
- if (code === 0) {
- this.decodeAttrib2(str, stride, decodeOffsets, decodeScales, deltaStart,
- numVerts, attribsOut, attribsOutFixed, lastAttrib,
- highest++);
- } else {
- this.copyAttrib(stride, attribsOutFixed, lastAttrib, index1);
- }
- code = str.charCodeAt(codeStart++);
- var index2 = highest - code;
- indicesOut[outputStart++] = index2;
- if (code === 0) {
- for (var j = 0; j < 5; j++) {
- lastAttrib[j] = (attribsOutFixed[stride*index0 + j] +
- attribsOutFixed[stride*index1 + j]) / 2;
- }
- this.decodeAttrib2(str, stride, decodeOffsets, decodeScales, deltaStart,
- numVerts, attribsOut, attribsOutFixed, lastAttrib,
- highest++);
- } else {
- this.copyAttrib(stride, attribsOutFixed, lastAttrib, index2);
- }
- this.accumulateNormal(index0, index1, index2, attribsOutFixed, crosses);
- }
- }
- for (var i = 0; i < numVerts; i++) {
- var nx = crosses[3*i + 0];
- var ny = crosses[3*i + 1];
- var nz = crosses[3*i + 2];
- var norm = 511.0 / Math.sqrt(nx*nx + ny*ny + nz*nz);
- var cx = str.charCodeAt(deltaStart + 5*numVerts + i);
- var cy = str.charCodeAt(deltaStart + 6*numVerts + i);
- var cz = str.charCodeAt(deltaStart + 7*numVerts + i);
- attribsOut[stride*i + 5] = norm*nx + ((cx >> 1) ^ (-(cx & 1)));
- attribsOut[stride*i + 6] = norm*ny + ((cy >> 1) ^ (-(cy & 1)));
- attribsOut[stride*i + 7] = norm*nz + ((cz >> 1) ^ (-(cz & 1)));
- }
- callback(name, idx, attribsOut, indicesOut, undefined, meshParams);
- };
- THREE.UTF8v2Loader.prototype.downloadMesh = function (path, name, meshEntry, decodeParams, callback) {
- var loader = this;
- var idx = 0;
- function onprogress(req, e) {
- while (idx < meshEntry.length) {
- var meshParams = meshEntry[idx];
- var indexRange = meshParams.indexRange;
- if (indexRange) {
- var meshEnd = indexRange[0] + 3*indexRange[1];
- if (req.responseText.length < meshEnd) break;
- loader.decompressMesh(req.responseText, meshParams, decodeParams, name, idx, callback);
- } else {
- var codeRange = meshParams.codeRange;
- var meshEnd = codeRange[0] + codeRange[1];
- if (req.responseText.length < meshEnd) break;
- loader.decompressMesh2(req.responseText, meshParams, decodeParams, name, idx, callback);
- }
- ++idx;
- }
- };
- getHttpRequest(path, function(req, e) {
- if (req.status === 200 || req.status === 0) {
- onprogress(req, e);
- }
- // TODO: handle errors.
- }, onprogress);
- };
- THREE.UTF8v2Loader.prototype.downloadMeshes = function (path, meshUrlMap, decodeParams, callback) {
- for (var url in meshUrlMap) {
- var meshEntry = meshUrlMap[url];
- this.downloadMesh(path + url, url, meshEntry, decodeParams, callback);
- }
- };
- THREE.UTF8v2Loader.prototype.createMeshCallback = function(materialBaseUrl, loadModelInfo, allDoneCallback) {
- var expectedMeshesPerUrl = {};
- var decodedMeshesPerUrl = {};
- var nCompletedUrls = 0;
- var nExpectedUrls = 0;
- var modelParts = {};
- var meshUrlMap = loadModelInfo.urls;
- for (var url in meshUrlMap) {
- expectedMeshesPerUrl[url] = meshUrlMap[url].length;
- decodedMeshesPerUrl[url] = 0;
- nExpectedUrls++;
- modelParts[url] = new THREE.Object3D();
- }
- var model = new THREE.Object3D();
- var geometryCreator = new THREE.UTF8v2Loader.GeometryCreator();
- var materialCreator = new THREE.MTLLoader.MaterialCreator(materialBaseUrl, loadModelInfo.options);
- materialCreator.setMaterials(loadModelInfo.materials);
- // Prepare materials first...
- materialCreator.preload();
- return function(name, idx, attribArray, indexArray, bboxen, meshParams) {
- // Got ourselves a new mesh
- // name identifies this part of the model (url)
- // idx is the mesh index of this mesh of the part
- // attribArray defines the vertices
- // indexArray defines the faces
- // bboxen defines the bounding box
- // meshParams contains the material info
- var geometry = geometryCreator.create( attribArray, indexArray, bboxen);
- var material = materialCreator.create( meshParams.material );
- modelParts[name].add(new THREE.Mesh(geometry, material));
- //model.add(new THREE.Mesh(geometry, material));
- decodedMeshesPerUrl[name]++;
- if (decodedMeshesPerUrl[name] === expectedMeshesPerUrl[name]) {
- nCompletedUrls++;
- model.add(modelParts[name]);
- if (nCompletedUrls === nExpectedUrls) {
- // ALL DONE!!!
- allDoneCallback(model);
- }
- }
- }
- };
- THREE.UTF8v2Loader.prototype.downloadModel = function (geometryBase, materialBase, model, callback) {
- var meshCallback = this.createMeshCallback(materialBase, model, callback);
- this.downloadMeshes(geometryBase, model.urls, model.decodeParams, meshCallback);
- };
- THREE.UTF8v2Loader.prototype.downloadModelJson = function (jsonUrl, options, callback ) {
- getJsonRequest(jsonUrl, function(loaded) {
- if (!loaded.decodeParams) {
- if (options && options.decodeParams) {
- loaded.decodeParams = options.decodeParams;
- } else {
- loaded.decodeParams = DEFAULT_DECODE_PARAMS;
- }
- }
- loaded.options = options;
- var geometryBase = jsonUrl.substr(0,jsonUrl.lastIndexOf("/")+1);
- var materialBase = geometryBase;
- if (options && options.geometryBase) {
- geometryBase = options.geometryBase;
- if (geometryBase.charAt(geometryBase.length-1) !== "/") {
- geometryBase = geometryBase + "/";
- }
- }
- if (options && options.materialBase) {
- materialBase = options.materialBase;
- if (materialBase.charAt(materialBase.length-1) !== "/") {
- materialBase = materialBase + "/";
- }
- }
- this.downloadModel(geometryBase, materialBase, loaded, callback);
- }.bind(this));
- };
- // XMLHttpRequest stuff.
- function getHttpRequest(url, onload, opt_onprogress) {
- var LISTENERS = {
- load: function(e) { onload(req, e); },
- progress: function(e) { opt_onprogress(req, e); }
- };
- var req = new XMLHttpRequest();
- addListeners(req, LISTENERS);
- req.open('GET', url, true);
- req.send(null);
- }
- function getJsonRequest(url, onjson) {
- getHttpRequest(url,
- function(e) { onjson(JSON.parse(e.responseText)); },
- function() {});
- }
- function addListeners(dom, listeners) {
- // TODO: handle event capture, object binding.
- for (var key in listeners) {
- dom.addEventListener(key, listeners[key]);
- }
- }
|