444 lines
13 KiB
JavaScript
444 lines
13 KiB
JavaScript
import { WaitWhile } from '../core/taskrunner.js';
|
|
import { Direction } from '../geometry/geometry.js';
|
|
import { Matrix } from '../geometry/matrix.js';
|
|
import { Transformation } from '../geometry/transformation.js';
|
|
import { Base64DataURIToArrayBuffer, CreateObjectUrl, GetFileExtensionFromMimeType } from '../io/bufferutils.js';
|
|
import { GetFileExtension, GetFileName } from '../io/fileutils.js';
|
|
import { PhongMaterial, TextureMap } from '../model/material.js';
|
|
import { Node } from '../model/node.js';
|
|
import { ConvertThreeColorToColor, ConvertThreeGeometryToMesh, ThreeSRGBToLinearColorConverter } from '../threejs/threeutils.js';
|
|
import { ImporterBase } from './importerbase.js';
|
|
|
|
import * as THREE from 'three';
|
|
import { TGALoader } from 'three/examples/jsm/loaders/TGALoader.js';
|
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader.js';
|
|
import { VRMLLoader } from 'three/examples/jsm/loaders/VRMLLoader.js';
|
|
import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js';
|
|
import { AMFLoader } from 'three/examples/jsm/loaders/AMFLoader.js';
|
|
|
|
export class ImporterThreeBase extends ImporterBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
|
|
this.colorConverter = null;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject;
|
|
}
|
|
|
|
IsMeshVisible (mesh)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
ClearContent ()
|
|
{
|
|
this.loader = null;
|
|
this.materialIdToIndex = null;
|
|
this.objectUrlToFileName = null;
|
|
}
|
|
|
|
ResetContent ()
|
|
{
|
|
this.loader = null;
|
|
this.materialIdToIndex = new Map ();
|
|
this.objectUrlToFileName = new Map ();
|
|
}
|
|
|
|
ImportContent (fileContent, onFinish)
|
|
{
|
|
this.LoadModel (fileContent, onFinish);
|
|
}
|
|
|
|
LoadModel (fileContent, onFinish)
|
|
{
|
|
let isAllLoadersDone = false;
|
|
let loadingManager = new THREE.LoadingManager (() => {
|
|
isAllLoadersDone = true;
|
|
});
|
|
|
|
const mainFileUrl = CreateObjectUrl (fileContent);
|
|
loadingManager.setURLModifier ((url) => {
|
|
if (url === mainFileUrl) {
|
|
return url;
|
|
}
|
|
const name = GetFileName (url);
|
|
const extension = GetFileExtension (url);
|
|
if (extension.length > 0) {
|
|
const buffer = this.callbacks.getFileBuffer (url);
|
|
if (buffer !== null) {
|
|
let objectUrl = CreateObjectUrl (buffer);
|
|
this.objectUrlToFileName.set (objectUrl, name);
|
|
return objectUrl;
|
|
}
|
|
}
|
|
return url;
|
|
});
|
|
|
|
const threeLoader = this.CreateLoader (loadingManager);
|
|
if (threeLoader === null) {
|
|
onFinish ();
|
|
return;
|
|
}
|
|
|
|
threeLoader.load (mainFileUrl,
|
|
(object) => {
|
|
WaitWhile (() => {
|
|
if (isAllLoadersDone) {
|
|
this.OnThreeObjectsLoaded (object, onFinish);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
},
|
|
() => {
|
|
},
|
|
(err) => {
|
|
this.SetError (err);
|
|
onFinish ();
|
|
}
|
|
);
|
|
}
|
|
|
|
OnThreeObjectsLoaded (loadedObject, onFinish)
|
|
{
|
|
function GetObjectTransformation (threeObject)
|
|
{
|
|
let matrix = new Matrix ().CreateIdentity ();
|
|
threeObject.updateMatrix ();
|
|
if (threeObject.matrix !== undefined && threeObject.matrix !== null) {
|
|
matrix.Set (threeObject.matrix.elements);
|
|
}
|
|
return new Transformation (matrix);
|
|
}
|
|
|
|
function AddObject (importer, model, threeObject, parentNode)
|
|
{
|
|
let node = new Node ();
|
|
if (threeObject.name !== undefined) {
|
|
node.SetName (threeObject.name);
|
|
}
|
|
node.SetTransformation (GetObjectTransformation (threeObject));
|
|
parentNode.AddChildNode (node);
|
|
|
|
for (let childObject of threeObject.children) {
|
|
AddObject (importer, model, childObject, node);
|
|
}
|
|
if (threeObject.isMesh && importer.IsMeshVisible (threeObject)) {
|
|
let mesh = importer.ConvertThreeMesh (threeObject);
|
|
let meshIndex = model.AddMesh (mesh);
|
|
node.AddMeshIndex (meshIndex);
|
|
}
|
|
}
|
|
|
|
let mainObject = this.GetMainObject (loadedObject);
|
|
let rootNode = this.model.GetRootNode ();
|
|
rootNode.SetTransformation (GetObjectTransformation (mainObject));
|
|
for (let childObject of mainObject.children) {
|
|
AddObject (this, this.model, childObject, rootNode);
|
|
}
|
|
|
|
onFinish ();
|
|
}
|
|
|
|
ConvertThreeMesh (threeMesh)
|
|
{
|
|
let mesh = null;
|
|
if (Array.isArray (threeMesh.material)) {
|
|
mesh = ConvertThreeGeometryToMesh (threeMesh.geometry, null, this.colorConverter);
|
|
if (threeMesh.geometry.attributes.color === undefined || threeMesh.geometry.attributes.color === null) {
|
|
let materialIndices = [];
|
|
for (let i = 0; i < threeMesh.material.length; i++) {
|
|
const material = threeMesh.material[i];
|
|
const materialIndex = this.FindOrCreateMaterial (material);
|
|
materialIndices.push (materialIndex);
|
|
}
|
|
for (let i = 0; i < threeMesh.geometry.groups.length; i++) {
|
|
let group = threeMesh.geometry.groups[i];
|
|
let groupEnd = null;
|
|
if (group.count === Infinity) {
|
|
groupEnd = mesh.TriangleCount ();
|
|
} else {
|
|
groupEnd = group.start / 3 + group.count / 3;
|
|
}
|
|
for (let j = group.start / 3; j < groupEnd; j++) {
|
|
let triangle = mesh.GetTriangle (j);
|
|
triangle.SetMaterial (materialIndices[group.materialIndex]);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
const materialIndex = this.FindOrCreateMaterial (threeMesh.material);
|
|
mesh = ConvertThreeGeometryToMesh (threeMesh.geometry, materialIndex, this.colorConverter);
|
|
}
|
|
if (threeMesh.name !== undefined && threeMesh.name !== null) {
|
|
mesh.SetName (threeMesh.name);
|
|
}
|
|
return mesh;
|
|
}
|
|
|
|
FindOrCreateMaterial (threeMaterial)
|
|
{
|
|
if (this.materialIdToIndex.has (threeMaterial.id)) {
|
|
return this.materialIdToIndex.get (threeMaterial.id);
|
|
}
|
|
let material = this.ConvertThreeMaterial (threeMaterial);
|
|
let materialIndex = null;
|
|
if (material !== null) {
|
|
materialIndex = this.model.AddMaterial (material);
|
|
}
|
|
this.materialIdToIndex.set (threeMaterial.id, materialIndex);
|
|
return materialIndex;
|
|
}
|
|
|
|
ConvertThreeMaterial (threeMaterial)
|
|
{
|
|
function CreateTexture (threeMap, objectUrlToFileName)
|
|
{
|
|
function GetDataUrl (img)
|
|
{
|
|
if (img.data !== undefined && img.data !== null) {
|
|
let imageData = new ImageData (img.width, img.height);
|
|
let imageSize = img.width * img.height * 4;
|
|
for (let i = 0; i < imageSize; i++) {
|
|
imageData.data[i] = img.data[i];
|
|
}
|
|
return THREE.ImageUtils.getDataURL (imageData);
|
|
} else {
|
|
return THREE.ImageUtils.getDataURL (img);
|
|
}
|
|
}
|
|
|
|
if (threeMap === undefined || threeMap === null) {
|
|
return null;
|
|
}
|
|
|
|
if (threeMap.image === undefined || threeMap.image === null) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
const dataUrl = GetDataUrl (threeMap.image);
|
|
const base64Buffer = Base64DataURIToArrayBuffer (dataUrl);
|
|
let texture = new TextureMap ();
|
|
let textureName = null;
|
|
if (objectUrlToFileName.has (threeMap.image.src)) {
|
|
textureName = objectUrlToFileName.get (threeMap.image.src);
|
|
} else if (threeMap.name !== undefined && threeMap.name !== null) {
|
|
textureName = threeMap.name + '.' + GetFileExtensionFromMimeType (base64Buffer.mimeType);
|
|
} else {
|
|
textureName = 'Embedded_' + threeMap.id.toString () + '.' + GetFileExtensionFromMimeType (base64Buffer.mimeType);
|
|
}
|
|
texture.name = textureName;
|
|
texture.mimeType = base64Buffer.mimeType;
|
|
texture.buffer = base64Buffer.buffer;
|
|
texture.rotation = threeMap.rotation;
|
|
texture.offset.x = threeMap.offset.x;
|
|
texture.offset.y = threeMap.offset.y;
|
|
texture.scale.x = threeMap.repeat.x;
|
|
texture.scale.y = threeMap.repeat.y;
|
|
return texture;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
if (threeMaterial.name === THREE.Loader.DEFAULT_MATERIAL_NAME) {
|
|
return null;
|
|
}
|
|
|
|
let material = new PhongMaterial ();
|
|
material.name = threeMaterial.name;
|
|
material.color = this.ConvertThreeColor (threeMaterial.color);
|
|
material.opacity = threeMaterial.opacity;
|
|
material.transparent = threeMaterial.transparent;
|
|
material.alphaTest = threeMaterial.alphaTest;
|
|
if (threeMaterial.type === 'MeshPhongMaterial') {
|
|
material.specular = this.ConvertThreeColor (threeMaterial.specular);
|
|
material.shininess = threeMaterial.shininess / 100.0;
|
|
}
|
|
material.diffuseMap = CreateTexture (threeMaterial.map, this.objectUrlToFileName);
|
|
material.normalMap = CreateTexture (threeMaterial.normalMap, this.objectUrlToFileName);
|
|
material.bumpMap = CreateTexture (threeMaterial.bumpMap, this.objectUrlToFileName);
|
|
|
|
return material;
|
|
}
|
|
|
|
ConvertThreeColor (threeColor)
|
|
{
|
|
if (this.colorConverter !== null) {
|
|
threeColor = this.colorConverter.Convert (threeColor);
|
|
}
|
|
return ConvertThreeColorToColor (threeColor);
|
|
}
|
|
}
|
|
|
|
export class ImporterThreeFbx extends ImporterThreeBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
}
|
|
|
|
CanImportExtension (extension)
|
|
{
|
|
return extension === 'fbx';
|
|
}
|
|
|
|
GetUpDirection ()
|
|
{
|
|
return Direction.Y;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
manager.addHandler (/\.tga$/i, new TGALoader (manager));
|
|
return new FBXLoader (manager);
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject;
|
|
}
|
|
}
|
|
|
|
export class ImporterThreeDae extends ImporterThreeBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
}
|
|
|
|
CanImportExtension (extension)
|
|
{
|
|
return extension === 'dae';
|
|
}
|
|
|
|
GetUpDirection ()
|
|
{
|
|
return Direction.Y;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
manager.addHandler (/\.tga$/i, new TGALoader (manager));
|
|
return new ColladaLoader (manager);
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject.scene;
|
|
}
|
|
}
|
|
|
|
export class ImporterThreeWrl extends ImporterThreeBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
}
|
|
|
|
CanImportExtension (extension)
|
|
{
|
|
return extension === 'wrl';
|
|
}
|
|
|
|
GetUpDirection ()
|
|
{
|
|
return Direction.Y;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
return new VRMLLoader (manager);
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject;
|
|
}
|
|
|
|
IsMeshVisible (mesh)
|
|
{
|
|
let isVisible = true;
|
|
if (Array.isArray (mesh.material)) {
|
|
for (let i = 0; i < mesh.material.length; i++) {
|
|
if (mesh.material[i].side === THREE.BackSide) {
|
|
isVisible = false;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
isVisible = (mesh.material.side !== THREE.BackSide);
|
|
}
|
|
return isVisible;
|
|
}
|
|
}
|
|
|
|
export class ImporterThree3mf extends ImporterThreeBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
this.colorConverter = new ThreeSRGBToLinearColorConverter ();
|
|
}
|
|
|
|
CanImportExtension (extension)
|
|
{
|
|
return extension === '3mf';
|
|
}
|
|
|
|
GetUpDirection ()
|
|
{
|
|
return Direction.Z;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
return new ThreeMFLoader (manager);
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject;
|
|
}
|
|
}
|
|
|
|
export class ImporterThreeAmf extends ImporterThreeBase
|
|
{
|
|
constructor ()
|
|
{
|
|
super ();
|
|
}
|
|
|
|
CanImportExtension (extension)
|
|
{
|
|
return extension === 'amf';
|
|
}
|
|
|
|
GetUpDirection ()
|
|
{
|
|
return Direction.Z;
|
|
}
|
|
|
|
CreateLoader (manager)
|
|
{
|
|
return new AMFLoader (manager);
|
|
}
|
|
|
|
GetMainObject (loadedObject)
|
|
{
|
|
return loadedObject;
|
|
}
|
|
}
|