Spaces:
Running
Running
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 }; | |