|
@@ -0,0 +1,355 @@
|
|
|
+/*
|
|
|
+ * @author tamarintech / https://tamarintech.com
|
|
|
+ *
|
|
|
+ * Description: Early release of an AMF Loader following the pattern of the
|
|
|
+ * example loaders in the three.js project.
|
|
|
+ *
|
|
|
+ * More information about the AMF format: http://amf.wikispaces.com
|
|
|
+ *
|
|
|
+ * Usage:
|
|
|
+ * var loader = new AMFLoader();
|
|
|
+ * loader.load('/path/to/project.amf', function(objecttree) {
|
|
|
+ * scene.add(objecttree);
|
|
|
+ * });
|
|
|
+ *
|
|
|
+ * Materials now supported, material colors supported
|
|
|
+ * Zip support, requires jszip
|
|
|
+ * TextDecoder polyfill required by some browsers (particularly IE, Edge)
|
|
|
+ * No constellation support (yet)!
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+THREE.AMFLoader = function( manager ) {
|
|
|
+ this.manager = (manager !== undefined) ? manager : THREE.DefaultLoadingManager;
|
|
|
+};
|
|
|
+
|
|
|
+THREE.AMFLoader.prototype = {
|
|
|
+
|
|
|
+ constructor: THREE.AMFLoader,
|
|
|
+
|
|
|
+ load: function(url, onLoad, onProgress, onError) {
|
|
|
+ var scope = this;
|
|
|
+
|
|
|
+ var loader = new THREE.XHRLoader(scope.manager);
|
|
|
+ loader.setCrossOrigin(this.crossOrigin);
|
|
|
+ loader.setResponseType('arraybuffer');
|
|
|
+
|
|
|
+ loader.load(url, function(text) {
|
|
|
+ var amfobject = scope.parse(text);
|
|
|
+ onLoad(amfobject);
|
|
|
+ }, onProgress, onError);
|
|
|
+ },
|
|
|
+
|
|
|
+ parse: function(data) {
|
|
|
+ var amfName = "";
|
|
|
+ var amfAuthor = "";
|
|
|
+ var amfScale = 1.0;
|
|
|
+ var amfMaterials = {};
|
|
|
+ var amfObjects = {};
|
|
|
+
|
|
|
+ var xmldata = this.loaddocument(data);
|
|
|
+
|
|
|
+ amfScale = this.loaddocumentscale(xmldata);
|
|
|
+
|
|
|
+ var documentchildren = xmldata.documentElement.children;
|
|
|
+
|
|
|
+ for(var i = 0; i < documentchildren.length; i++) {
|
|
|
+ if(documentchildren[i].nodeName === 'metadata') {
|
|
|
+ if(documentchildren[i].attributes['type'] !== undefined) {
|
|
|
+ if(documentchildren[i].attributes['type'].value === 'name') {
|
|
|
+ amfName = documentchildren[i].textContent;
|
|
|
+ } else if(documentchildren[i].attributes['type'].value === 'author') {
|
|
|
+ amfAuthor = documentchildren[i].textContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if(documentchildren[i].nodeName === 'material') {
|
|
|
+ var loadedmaterial = this.loadmaterials(documentchildren[i]);
|
|
|
+ amfMaterials[loadedmaterial.id] = loadedmaterial.material;
|
|
|
+ } else if(documentchildren[i].nodeName === 'object') {
|
|
|
+ var loadedobject = this.loadobject(documentchildren[i]);
|
|
|
+ amfObjects[loadedobject.id] = loadedobject.obj;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var sceneobject = new THREE.Object3D();
|
|
|
+
|
|
|
+ sceneobject.name = amfName;
|
|
|
+ sceneobject.userData.author = amfAuthor;
|
|
|
+ sceneobject.userData.loader = "AMF";
|
|
|
+
|
|
|
+ var defaultmaterial = new THREE.MeshPhongMaterial({shading: THREE.FlatShading, color: 0xaaaaff});
|
|
|
+
|
|
|
+ for(var objid in amfObjects) {
|
|
|
+ var newobject = new THREE.Object3D();
|
|
|
+
|
|
|
+ for(var meshi = 0; meshi < amfObjects[objid].meshes.length; meshi++) {
|
|
|
+ var meshvertices = Float32Array.from(amfObjects[objid].meshes[meshi].vertices);
|
|
|
+ var vertices = new THREE.BufferAttribute(Float32Array.from(meshvertices), 3);
|
|
|
+ var objdefaultmaterial = defaultmaterial;
|
|
|
+
|
|
|
+ if(amfObjects[objid].meshes[meshi].color) {
|
|
|
+ var color = amfObjects[objid].meshes[meshi].color;
|
|
|
+ objdefaultmaterial = defaultmaterial.clone();
|
|
|
+ objdefaultmaterial.color = new THREE.Color(color.r, color.g, color.b);
|
|
|
+
|
|
|
+ if(color.a != 1.0) {
|
|
|
+ objdefaultmaterial.transparent = true;
|
|
|
+ objdefaultmaterial.opacity = color.a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for(var voli = 0; voli < amfObjects[objid].meshes[meshi].volumes.length; voli++) {
|
|
|
+ var currvolume = amfObjects[objid].meshes[meshi].volumes[voli];
|
|
|
+ var newgeometry = new THREE.BufferGeometry();
|
|
|
+ var indexes = Uint32Array.from(currvolume.triangles);
|
|
|
+ var normals = new Uint32Array(vertices.length);
|
|
|
+
|
|
|
+ var material = objdefaultmaterial;
|
|
|
+
|
|
|
+ newgeometry.addAttribute('position', vertices.clone());
|
|
|
+ newgeometry.addAttribute('index', new THREE.BufferAttribute(indexes, 1));
|
|
|
+
|
|
|
+ if(amfMaterials[currvolume.materialid] !== undefined) {
|
|
|
+ material = amfMaterials[currvolume.materialid];
|
|
|
+ }
|
|
|
+
|
|
|
+ newgeometry.scale(amfScale, amfScale, amfScale);
|
|
|
+
|
|
|
+ var newmesh = new THREE.Mesh(newgeometry, material.clone());
|
|
|
+
|
|
|
+ newobject.add(newmesh);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sceneobject.add(newobject);
|
|
|
+ }
|
|
|
+
|
|
|
+ return sceneobject;
|
|
|
+ },
|
|
|
+
|
|
|
+ loaddocument: function ( data ) {
|
|
|
+ var view = new DataView(data);
|
|
|
+
|
|
|
+ var magic = String.fromCharCode(view.getUint8(0), view.getUint8(1));
|
|
|
+
|
|
|
+ if(magic === "PK") {
|
|
|
+ console.log("Loading Zip");
|
|
|
+ var zip = null;
|
|
|
+ var file = null;
|
|
|
+
|
|
|
+ try {
|
|
|
+ zip = new JSZip(data);
|
|
|
+ } catch (e) {
|
|
|
+ if (e instanceof ReferenceError) {
|
|
|
+ console.log(" jszip missing and file is compressed.");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ for(file in zip.files) {
|
|
|
+ if(file.toLowerCase().endsWith(".amf")) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(" Trying to load file asset: " + file);
|
|
|
+ view = new DataView(zip.file(file).asArrayBuffer());
|
|
|
+ }
|
|
|
+
|
|
|
+ if(TextDecoder === undefined) {
|
|
|
+ console.log(" TextDecoder not present. Please use TextDecoder polyfill.");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ var filetext = new TextDecoder('utf-8').decode(view);
|
|
|
+
|
|
|
+ var xmldata = new DOMParser().parseFromString(filetext, 'application/xml');
|
|
|
+
|
|
|
+ if(xmldata.documentElement.nodeName.toLowerCase() !== "amf") {
|
|
|
+ console.log(" Error loading AMF - no AMF document found.");
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ return xmldata;
|
|
|
+ },
|
|
|
+
|
|
|
+ loaddocumentscale: function ( xmldata ) {
|
|
|
+ var scale = 1.0;
|
|
|
+
|
|
|
+ var unit = xmldata.documentElement.attributes['unit'].value.toLowerCase();
|
|
|
+
|
|
|
+ var scale_units = {
|
|
|
+ 'millimeter': 1.0,
|
|
|
+ 'inch': 25.4,
|
|
|
+ 'feet': 304.8,
|
|
|
+ 'meter': 1000.0,
|
|
|
+ 'micron': 0.001
|
|
|
+ };
|
|
|
+
|
|
|
+ if(scale_units[unit] !== undefined) {
|
|
|
+ scale = scale_units[unit];
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(" Unit scale: " + scale);
|
|
|
+ return scale;
|
|
|
+ },
|
|
|
+
|
|
|
+ loadmaterials: function ( node ) {
|
|
|
+ var mat = node;
|
|
|
+
|
|
|
+ var loadedmaterial = null;
|
|
|
+ var matname = "AMF Material";
|
|
|
+ var matid = mat.attributes['id'].textContent;
|
|
|
+ var color;
|
|
|
+
|
|
|
+ for(var i = 0; i < mat.children.length; i++) {
|
|
|
+ var matchildel = mat.children[i];
|
|
|
+
|
|
|
+ if(matchildel.nodeName === "metadata" && matchildel.attributes['type'] !== undefined) {
|
|
|
+ if(matchildel.attributes['type'].value === 'name') {
|
|
|
+ matname = matchildel.textContent;
|
|
|
+ }
|
|
|
+ } else if(matchildel.nodeName === 'color') {
|
|
|
+ color = this.loadcolor(matchildel);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ loadedmaterial = new THREE.MeshPhongMaterial({
|
|
|
+ shading: THREE.FlatShading,
|
|
|
+ color: new THREE.Color(color.r, color.g, color.b),
|
|
|
+ name: matname});
|
|
|
+
|
|
|
+ if(color.opacity !== 1.0) {
|
|
|
+ loadedmaterial.transparent = true;
|
|
|
+ loadedmaterial.opacity = color.opacity;
|
|
|
+ }
|
|
|
+
|
|
|
+ return { 'id': matid, 'material': loadedmaterial };
|
|
|
+ },
|
|
|
+
|
|
|
+ loadcolor: function ( node ) {
|
|
|
+ var color = {'r': 1.0, 'g': 1.0, 'b': 1.0, 'a': 1.0, opacity: 1.0};
|
|
|
+
|
|
|
+ for(var i = 0; i < node.children.length; i++) {
|
|
|
+ var matcolor = node.children[i];
|
|
|
+
|
|
|
+ if(matcolor.nodeName === 'r') {
|
|
|
+ color.r = matcolor.textContent;
|
|
|
+ } else if(matcolor.nodeName === 'g') {
|
|
|
+ color.g = matcolor.textContent;
|
|
|
+ } else if(matcolor.nodeName === 'b') {
|
|
|
+ color.b = matcolor.textContent;
|
|
|
+ } else if(matcolor.nodeName === 'a') {
|
|
|
+ color.opacity = matcolor.textContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return color;
|
|
|
+ },
|
|
|
+
|
|
|
+ loadmeshvolume: function( node ) {
|
|
|
+ var volume = { "name": "", "triangles": [], "materialid": null };
|
|
|
+
|
|
|
+ var currvolumenode = node.firstElementChild;
|
|
|
+
|
|
|
+ if(node.attributes['materialid'] !== undefined) {
|
|
|
+ volume.materialid = node.attributes['materialid'].nodeValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ while( currvolumenode ) {
|
|
|
+ if( currvolumenode.nodeName === "metadata" ) {
|
|
|
+ if(currvolumenode.attributes['type'] !== undefined) {
|
|
|
+ if(currvolumenode.attributes['type'].value === 'name') {
|
|
|
+ volume.name = currvolumenode.textContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if ( currvolumenode.nodeName === "triangle" ) {
|
|
|
+ var trianglenode = currvolumenode.firstElementChild;
|
|
|
+
|
|
|
+ while( trianglenode ) {
|
|
|
+ if( trianglenode.nodeName === "v1" ||
|
|
|
+ trianglenode.nodeName === "v2" ||
|
|
|
+ trianglenode.nodeName === "v3") {
|
|
|
+ volume.triangles.push(trianglenode.textContent);
|
|
|
+ }
|
|
|
+
|
|
|
+ trianglenode = trianglenode.nextElementSibling;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ currvolumenode = currvolumenode.nextElementSibling;
|
|
|
+ }
|
|
|
+
|
|
|
+ return volume;
|
|
|
+ },
|
|
|
+
|
|
|
+ loadmeshvertices: function( node ) {
|
|
|
+ var vert_array = [];
|
|
|
+
|
|
|
+ var currverticesnode = node.firstElementChild;
|
|
|
+
|
|
|
+ while( currverticesnode ) {
|
|
|
+ if ( currverticesnode.nodeName === "vertex" ) {
|
|
|
+ var vnode = currverticesnode.firstElementChild;
|
|
|
+
|
|
|
+ while( vnode ) {
|
|
|
+ if( vnode.nodeName === "coordinates") {
|
|
|
+ var coordnode = vnode.firstElementChild;
|
|
|
+
|
|
|
+ while( coordnode ) {
|
|
|
+
|
|
|
+ if( coordnode.nodeName === "x" ||
|
|
|
+ coordnode.nodeName === "y" ||
|
|
|
+ coordnode.nodeName === "z") {
|
|
|
+ vert_array.push(coordnode.textContent);
|
|
|
+ }
|
|
|
+
|
|
|
+ coordnode = coordnode.nextElementSibling;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ vnode = vnode.nextElementSibling;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ currverticesnode = currverticesnode.nextElementSibling;
|
|
|
+ }
|
|
|
+
|
|
|
+ return vert_array;
|
|
|
+ },
|
|
|
+
|
|
|
+ loadobject: function ( node ) {
|
|
|
+ "use strict";
|
|
|
+ var objid = node.attributes['id'].textContent;
|
|
|
+ var loadedobject = { "name": "amfobject", "meshes": [] };
|
|
|
+
|
|
|
+ var currcolor = null;
|
|
|
+
|
|
|
+ var currobjnode = node.firstElementChild;
|
|
|
+
|
|
|
+ while( currobjnode ) {
|
|
|
+ if(currobjnode.nodeName === "metadata") {
|
|
|
+ if(currobjnode.attributes['type'] !== undefined) {
|
|
|
+ if(currobjnode.attributes['type'].value === 'name') {
|
|
|
+ loadedobject.name = currobjnode.textContent;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if(currobjnode.nodeName === "color") {
|
|
|
+ currcolor = this.loadcolor(currobjnode);
|
|
|
+ } else if(currobjnode.nodeName === "mesh") {
|
|
|
+ var currmeshnode = currobjnode.firstElementChild;
|
|
|
+ var mesh = {"vertices": [], "volumes": [], "color": currcolor };
|
|
|
+
|
|
|
+ while( currmeshnode ) {
|
|
|
+ if(currmeshnode.nodeName === "vertices") {
|
|
|
+ mesh.vertices = mesh.vertices.concat(this.loadmeshvertices(currmeshnode));
|
|
|
+ } else if(currmeshnode.nodeName === "volume") {
|
|
|
+ mesh.volumes.push(this.loadmeshvolume(currmeshnode));
|
|
|
+ }
|
|
|
+ currmeshnode = currmeshnode.nextElementSibling;
|
|
|
+ }
|
|
|
+
|
|
|
+ loadedobject.meshes.push(mesh);
|
|
|
+ }
|
|
|
+ currobjnode = currobjnode.nextElementSibling;
|
|
|
+ }
|
|
|
+
|
|
|
+ return { 'id': objid, 'obj': loadedobject };
|
|
|
+ }
|
|
|
+};
|