expo-three
Version:
Utilities for using THREE.js on Expo
389 lines (324 loc) • 13.9 kB
text/typescript
/*
* @author tamarintech / https://tamarintech.com
* @author evan bacon / somewhere online
* 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)!
*
*/
import { TextDecoder } from 'text-encoding';
import THREE from '../Three';
// @ts-ignore
const {JSZip} = window;
class AMFLoader {
constructor(manager) {
// @ts-ignore
this.manager =
manager !== undefined ? manager : THREE.DefaultLoadingManager;
}
load = (url, onLoad, onProgress, onError) => {
// @ts-ignore
const loader = new THREE.FileLoader(this.manager);
loader.setResponseType('arraybuffer');
loader.load(url, text => onLoad(this.parse(text)), 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 = new JSZip(data);
} catch (e) {
if (e instanceof ReferenceError) {
console.log(
'THREE.AMFLoader: jszip missing and file is compressed.'
);
return null;
}
}
// @ts-ignore
for (file in zip.files) {
// @ts-ignore
if (file.toLowerCase().substr(-4) === '.amf') {
break;
}
}
console.log(`THREE.AMFLoader: Trying to load file asset: ${file}`);
// @ts-ignore
view = new DataView(zip.file(file).asArrayBuffer());
}
const fileText = new TextDecoder('utf-8').decode(view);
const xmlData = new DOMParser().parseFromString(
fileText,
'application/xml'
);
if (xmlData.documentElement.nodeName.toLowerCase() !== 'amf') {
throw new Error(
'THREE.AMFLoader: Error loading AMF - no AMF document found.'
);
}
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';
let matId = node.getAttribute('id').textContent;
let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
let loadedMaterial = null;
for (const i in node.children) {
let matChildEl = node.children[i];
if (
matChildEl.nodeName === 'metadata' &&
matChildEl.getAttribute('type') !== undefined
) {
if (matChildEl.getAttribute('type').value === 'name') {
// @ts-ignore
matname = matChildEl.textContent;
}
} else if (matChildEl.nodeName === 'color') {
color = loadColor(matChildEl);
}
}
// @ts-ignore
loadedMaterial = new THREE.MeshPhongMaterial({
flatShading: true,
color: new THREE.Color(color.r, color.g, color.b),
name: matName,
});
if (color.a !== 1.0) {
// @ts-ignore
loadedMaterial.transparent = true;
// @ts-ignore
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 (const i in node.children) {
const 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.a = matColor.textContent;
}
}
return color;
}
function loadMeshVolume(node) {
const volume = { name: '', triangles: [], materialid: null };
if (node.getAttribute('materialid') !== undefined) {
// @ts-ignore
volume.materialId = node.getAttribute('materialid').nodeValue;
}
for (let i in node.childNodes) {
const currVolumeNode = node.childNodes[i];
if (currVolumeNode.nodeName === 'metadata') {
if (currVolumeNode.getAttribute('type') !== undefined) {
if (currVolumeNode.getAttribute('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;
// @ts-ignore
volume.triangles.push(v1, v2, v3);
}
}
return volume;
}
function loadMeshVertices(node) {
const vertArray = [];
const normalArray = [];
for (let i in node.childNodes) {
const currVerticesNode = node.childNodes[i];
if (currVerticesNode.nodeName === 'vertex') {
for (let i in currVerticesNode.childNodes) {
const vNode = currVerticesNode.childNodes[i];
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;
// @ts-ignore
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;
// @ts-ignore
normalArray.push(nx, ny, nz);
}
}
}
}
return { vertices: vertArray, normals: normalArray };
}
function loadObject(node) {
const objId = node.getAttribute('id');
const loadedObject = { name: 'amfobject', meshes: [] };
let currColor = null;
for (let j = 0; j < node.childNodes.length; j++) {
let currObjNode = node.childNodes[j];
if (currObjNode.nodeName === 'metadata') {
if (currObjNode.getAttribute('type') !== undefined) {
if (currObjNode.getAttribute('type').value === 'name') {
loadedObject.name = currObjNode.textContent;
}
}
} else if (currObjNode.nodeName === 'color') {
// @ts-ignore
currColor = loadColor(currObjNode);
} else if (currObjNode.nodeName === 'mesh') {
const mesh = {
vertices: [],
normals: [],
volumes: [],
color: currColor,
};
for (let i in currObjNode.childNodes) {
const currMeshNode = currObjNode.childNodes[i];
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') {
// @ts-ignore
mesh.volumes.push(loadMeshVolume(currMeshNode));
}
}
// @ts-ignore
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 = {};
// @ts-ignore
const children = xmlData.documentElement.childNodes;
let i;
let j;
for (i = 0; i < children.length; i++) {
const child = children[i];
if (child.nodeName === 'metadata') {
// @ts-ignore
if (child.getAttribute('type') !== undefined) {
// @ts-ignore
if (child.getAttribute('type').value === 'name') {
// @ts-ignore
amfName = child.textContent;
// @ts-ignore
} else if (child.getAttribute('type').value === 'author') {
// @ts-ignore
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 THREE.Group();
const defaultMaterial = new THREE.MeshPhongMaterial({
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 THREE.Group();
newObject.name = part.name || '';
for (i = 0; i < meshes.length; i++) {
let objDefaultMaterial = defaultMaterial;
const mesh = meshes[i];
const vertices = new THREE.Float32BufferAttribute(mesh.vertices, 3);
let normals = null;
if (mesh.normals.length) {
// @ts-ignore
normals = new THREE.Float32BufferAttribute(mesh.normals, 3);
}
if (mesh.color) {
const color = mesh.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;
}
}
const volumes = mesh.volumes;
for (j = 0; j < volumes.length; j++) {
const volume = volumes[j];
const newGeometry = new THREE.BufferGeometry();
let material = objDefaultMaterial;
newGeometry.setIndex(volume.triangles);
newGeometry.addAttribute('position', vertices.clone());
if (normals) {
// @ts-ignore
newGeometry.addAttribute('normal', normals.clone());
}
if (amfMaterials[volume.materialId] !== undefined) {
material = amfMaterials[volume.materialId];
}
newGeometry.scale(amfScale, amfScale, amfScale);
newObject.add(new THREE.Mesh(newGeometry, material.clone()));
}
}
sceneObject.add(newObject);
}
return sceneObject;
};
}
// @ts-ignore
THREE.AMFLoader = AMFLoader;