File: //var/www/aspa/three/addons/loaders/AMFLoader.js
import {
	BufferGeometry,
	Color,
	FileLoader,
	Float32BufferAttribute,
	Group,
	Loader,
	Mesh,
	MeshPhongMaterial
} from 'three';
import * as fflate from '../libs/fflate.module.js';
/**
 * Description: Early release of an AMF Loader following the pattern of the
 * example loaders in the three.js project.
 *
 * Usage:
 *	const loader = new AMFLoader();
 *	loader.load('/path/to/project.amf', function(objecttree) {
 *		scene.add(objecttree);
 *	});
 *
 * Materials now supported, material colors supported
 * Zip support, requires fflate
 * No constellation support (yet)!
 *
 */
class AMFLoader extends Loader {
	constructor( manager ) {
		super( manager );
	}
	load( url, onLoad, onProgress, onError ) {
		const scope = this;
		const loader = new FileLoader( scope.manager );
		loader.setPath( scope.path );
		loader.setResponseType( 'arraybuffer' );
		loader.setRequestHeader( scope.requestHeader );
		loader.setWithCredentials( scope.withCredentials );
		loader.load( url, function ( text ) {
			try {
				onLoad( scope.parse( text ) );
			} catch ( e ) {
				if ( onError ) {
					onError( e );
				} else {
					console.error( e );
				}
				scope.manager.itemError( url );
			}
		}, onProgress, onError );
	}
	parse( data ) {
		function loadDocument( data ) {
			let view = new DataView( data );
			const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
			if ( magic === 'PK' ) {
				let zip = null;
				let file = null;
				console.log( 'THREE.AMFLoader: Loading Zip' );
				try {
					zip = fflate.unzipSync( new Uint8Array( data ) );
				} catch ( e ) {
					if ( e instanceof ReferenceError ) {
						console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
						return null;
					}
				}
				for ( file in zip ) {
					if ( file.toLowerCase().slice( - 4 ) === '.amf' ) {
						break;
					}
				}
				console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
				view = new DataView( zip[ file ].buffer );
			}
			const fileText = new TextDecoder().decode( view );
			const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
			if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {
				console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
				return null;
			}
			return xmlData;
		}
		function loadDocumentScale( node ) {
			let scale = 1.0;
			let unit = 'millimeter';
			if ( node.documentElement.attributes.unit !== undefined ) {
				unit = node.documentElement.attributes.unit.value.toLowerCase();
			}
			const scaleUnits = {
				millimeter: 1.0,
				inch: 25.4,
				feet: 304.8,
				meter: 1000.0,
				micron: 0.001
			};
			if ( scaleUnits[ unit ] !== undefined ) {
				scale = scaleUnits[ unit ];
			}
			console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
			return scale;
		}
		function loadMaterials( node ) {
			let matName = 'AMF Material';
			const matId = node.attributes.id.textContent;
			let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
			let loadedMaterial = null;
			for ( let i = 0; i < node.childNodes.length; i ++ ) {
				const matChildEl = node.childNodes[ i ];
				if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {
					if ( matChildEl.attributes.type.value === 'name' ) {
						matName = matChildEl.textContent;
					}
				} else if ( matChildEl.nodeName === 'color' ) {
					color = loadColor( matChildEl );
				}
			}
			loadedMaterial = new MeshPhongMaterial( {
				flatShading: true,
				color: new Color( color.r, color.g, color.b ),
				name: matName
			} );
			if ( color.a !== 1.0 ) {
				loadedMaterial.transparent = true;
				loadedMaterial.opacity = color.a;
			}
			return { id: matId, material: loadedMaterial };
		}
		function loadColor( node ) {
			const color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
			for ( let i = 0; i < node.childNodes.length; i ++ ) {
				const matColor = node.childNodes[ 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.a = matColor.textContent;
				}
			}
			return color;
		}
		function loadMeshVolume( node ) {
			const volume = { name: '', triangles: [], materialid: null };
			let 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' ) {
					const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
					const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
					const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
					volume.triangles.push( v1, v2, v3 );
				}
				currVolumeNode = currVolumeNode.nextElementSibling;
			}
			return volume;
		}
		function loadMeshVertices( node ) {
			const vertArray = [];
			const normalArray = [];
			let currVerticesNode = node.firstElementChild;
			while ( currVerticesNode ) {
				if ( currVerticesNode.nodeName === 'vertex' ) {
					let vNode = currVerticesNode.firstElementChild;
					while ( vNode ) {
						if ( vNode.nodeName === 'coordinates' ) {
							const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
							const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
							const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
							vertArray.push( x, y, z );
						} else if ( vNode.nodeName === 'normal' ) {
							const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
							const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
							const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
							normalArray.push( nx, ny, nz );
						}
						vNode = vNode.nextElementSibling;
					}
				}
				currVerticesNode = currVerticesNode.nextElementSibling;
			}
			return { 'vertices': vertArray, 'normals': normalArray };
		}
		function loadObject( node ) {
			const objId = node.attributes.id.textContent;
			const loadedObject = { name: 'amfobject', meshes: [] };
			let currColor = null;
			let 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 = loadColor( currObjNode );
				} else if ( currObjNode.nodeName === 'mesh' ) {
					let currMeshNode = currObjNode.firstElementChild;
					const mesh = { vertices: [], normals: [], volumes: [], color: currColor };
					while ( currMeshNode ) {
						if ( currMeshNode.nodeName === 'vertices' ) {
							const loadedVertices = loadMeshVertices( currMeshNode );
							mesh.normals = mesh.normals.concat( loadedVertices.normals );
							mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );
						} else if ( currMeshNode.nodeName === 'volume' ) {
							mesh.volumes.push( loadMeshVolume( currMeshNode ) );
						}
						currMeshNode = currMeshNode.nextElementSibling;
					}
					loadedObject.meshes.push( mesh );
				}
				currObjNode = currObjNode.nextElementSibling;
			}
			return { 'id': objId, 'obj': loadedObject };
		}
		const xmlData = loadDocument( data );
		let amfName = '';
		let amfAuthor = '';
		const amfScale = loadDocumentScale( xmlData );
		const amfMaterials = {};
		const amfObjects = {};
		const childNodes = xmlData.documentElement.childNodes;
		let i, j;
		for ( i = 0; i < childNodes.length; i ++ ) {
			const child = childNodes[ i ];
			if ( child.nodeName === 'metadata' ) {
				if ( child.attributes.type !== undefined ) {
					if ( child.attributes.type.value === 'name' ) {
						amfName = child.textContent;
					} else if ( child.attributes.type.value === 'author' ) {
						amfAuthor = child.textContent;
					}
				}
			} else if ( child.nodeName === 'material' ) {
				const loadedMaterial = loadMaterials( child );
				amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
			} else if ( child.nodeName === 'object' ) {
				const loadedObject = loadObject( child );
				amfObjects[ loadedObject.id ] = loadedObject.obj;
			}
		}
		const sceneObject = new Group();
		const defaultMaterial = new MeshPhongMaterial( {
			name: Loader.DEFAULT_MATERIAL_NAME,
			color: 0xaaaaff,
			flatShading: true
		} );
		sceneObject.name = amfName;
		sceneObject.userData.author = amfAuthor;
		sceneObject.userData.loader = 'AMF';
		for ( const id in amfObjects ) {
			const part = amfObjects[ id ];
			const meshes = part.meshes;
			const newObject = new Group();
			newObject.name = part.name || '';
			for ( i = 0; i < meshes.length; i ++ ) {
				let objDefaultMaterial = defaultMaterial;
				const mesh = meshes[ i ];
				const vertices = new Float32BufferAttribute( mesh.vertices, 3 );
				let normals = null;
				if ( mesh.normals.length ) {
					normals = new Float32BufferAttribute( mesh.normals, 3 );
				}
				if ( mesh.color ) {
					const color = mesh.color;
					objDefaultMaterial = defaultMaterial.clone();
					objDefaultMaterial.color = new Color( color.r, color.g, color.b );
					if ( color.a !== 1.0 ) {
						objDefaultMaterial.transparent = true;
						objDefaultMaterial.opacity = color.a;
					}
				}
				const volumes = mesh.volumes;
				for ( j = 0; j < volumes.length; j ++ ) {
					const volume = volumes[ j ];
					const newGeometry = new BufferGeometry();
					let material = objDefaultMaterial;
					newGeometry.setIndex( volume.triangles );
					newGeometry.setAttribute( 'position', vertices.clone() );
					if ( normals ) {
						newGeometry.setAttribute( 'normal', normals.clone() );
					}
					if ( amfMaterials[ volume.materialId ] !== undefined ) {
						material = amfMaterials[ volume.materialId ];
					}
					newGeometry.scale( amfScale, amfScale, amfScale );
					newObject.add( new Mesh( newGeometry, material.clone() ) );
				}
			}
			sceneObject.add( newObject );
		}
		return sceneObject;
	}
}
export { AMFLoader };