Source

Loaders/ModelLoader.js

import { DrawingMode, FaceCulling } from '../StateBlock';
import FileLoader from './FileLoader';
import Geometry from '../Geometry';
import Material from '../Material/Material';
import Mesh from '../Objects/Mesh';
import ProgramLibrary from '../Extra/ProgramLibrary';
import Texture from '../Textures/Texture';
import Type from '../Types';
import { VertexFormat, VertexElement } from '../VertexFormat';

/**
 * A class to load 3D models
 *
 * @category Loaders
 */
class ModelLoader {
    /**
     * Load a 3D model from a JSON file
     *
     * @param {string} filePath Path to the file with the 3D model
     * @param {Model} model Model to fill with data
     */
    static async loadFromFile(filePath, model) {
        const response = await FileLoader.load(filePath);
        const folder = filePath.replace(/[^/]*$/, ''); // Compute path to the parent folder
        ModelLoader.parseJSON(response.data, model, folder);
    }

    /**
     * Parse given data and fill model with it
     *
     * @param {string} data JSON data
     * @param {Model} model Model to fill with data
     * @param {string?} relativePath Relative path to the externals assets
     * @see https://github.com/acgessler/assimp2json
     * @private
     */
    static parseJSON(data, model, relativePath = '') {
        // Get JSON data
        /* eslint max-len: ["error", { "code": 400 }] */
        const obj = /** @type {{materials:Array<{properties: Array<{index: number, key:string, semantic: number, type:number, value: (Array|number)}>}>, meshes: Array<{vertices: Array, normals: Array, faces: Array, texturecoords: Array, numuvcomponents: Array, materialindex: number}>}} */(JSON.parse(data));

        // Materials.
        const materials = [];
        let i = 0;
        let j = 0;
        let k = 0;
        let l = 0;

        for (i = 0; i < obj.materials.length; i += 1) {
            const material = new Material();
            const pass = material.createPass();

            // Default data
            pass.drawingMode = DrawingMode.Triangles;

            const properties = obj.materials[i].properties;
            for (j = 0; j < properties.length; j += 1) {
                const property = properties[j];

                // Colors.
                if (property.type === 1) {
                    if (property.key === '$mat.twosided' && property.value === 1) {
                        pass.faceCulling = FaceCulling.None;
                    } else if (property.key === '$mat.shininess') {
                        pass.add('material.shininess', Type.Float, /** @type {number} */(property.value));
                    } else if (property.key === '$clr.ambient') {
                        pass.add('material.ambient', Type.Float, [property.value[0], property.value[1], property.value[2]]);
                    } else if (property.key === '$clr.diffuse') {
                        pass.add('material.diffuse', Type.Float, [property.value[0], property.value[1], property.value[2]]);
                    } else if (property.key === '$clr.specular') {
                        pass.add('material.specular', Type.Float, [property.value[0], property.value[1], property.value[2]]);
                    } else if (property.key === '$clr.emissive') {
                        pass.add('material.emissive', Type.Float, [property.value[0], property.value[1], property.value[2]]);
                    }
                } else if (property.type === 3 && property.key !== '?mat.name') {
                    const texture = new Texture();
                    texture.loadFromFile(relativePath + property.value);

                    let name = 'material.diffuseTexture';
                    switch (property.semantic) {
                    default:
                    case 1:
                        name = 'material.diffuseTexture';
                        break;
                    case 2:
                        name = 'material.specularTexture';
                        break;
                    case 3:
                        name = 'material.ambientTexture';
                        break;
                    case 4:
                        name = 'material.emissiveTexture';
                        break;
                    case 5:
                        name = 'material.heightTexture';
                        break;
                    case 6:
                        name = 'material.normalsTexture';
                        break;
                    case 7:
                        name = 'material.shininessTexture';
                        break;
                    case 8:
                        name = 'material.opacityTexture';
                        break;
                    case 9:
                        name = 'material.displacementTexture';
                        break;
                    }

                    pass.add(name, Type.Texture2D, texture);
                }
            }

            materials.push(material);
        }

        // Geometries
        for (i = 0; i < obj.meshes.length; i += 1) {
            // Create a new mesh
            const mesh = new Mesh();
            model.meshes.push(mesh);

            // Add data
            const meshData = obj.meshes[i];

            // Geometry
            {
                const geometry = new Geometry();

                // Format
                const format = new VertexFormat();
                geometry.setVertexFormat(format);

                // Positions
                const positions = new Float32Array(meshData.vertices);
                format.add(new VertexElement(VertexElement.Usage.Position, 0, VertexElement.Type.Float, 3, false));
                geometry.setPositions(positions);

                // Normals
                const normals = new Float32Array(meshData.normals);
                format.add(new VertexElement(VertexElement.Usage.Normal, 1, VertexElement.Type.Float, 3, false));
                geometry.setNormals(normals);

                // UVs
                if (meshData.texturecoords) {
                    const uvs = new Float32Array(meshData.texturecoords[0]);
                    format.add(new VertexElement(VertexElement.Usage.UVS, 2, VertexElement.Type.Float, meshData.numuvcomponents[0], false));
                    geometry.setTextureUVs(uvs);
                }

                // Indices
                const indices = new Uint16Array(meshData.faces.length * 3);
                for (k = 0, l = 0; k < meshData.faces.length; k += 1, l += 3) {
                    indices[l + 0] = meshData.faces[k][0];
                    indices[l + 1] = meshData.faces[k][1];
                    indices[l + 2] = meshData.faces[k][2];
                }
                geometry.setIndices(indices);

                mesh.setGeometry(geometry);
            }

            // Material.
            if (meshData.materialindex !== undefined && materials[meshData.materialindex]) {
                mesh.setMaterial(materials[meshData.materialindex]);
            } else {
                const material = new Material();
                const pass = material.createPass();
                pass.drawingMode = DrawingMode.Triangles;
                pass.add('material.ambient', Type.Float, [0.0, 0.0, 0.0]);
                pass.add('material.diffuse', Type.Float, [0.55, 0.55, 0.55]);
                pass.add('material.specular', Type.Float, [0.7, 0.7, 0.7]);
                pass.add('material.shininess', Type.Float, 38.4);
                mesh.setMaterial(material);
            }

            // Program
            mesh.setProgram(ProgramLibrary.get('PhongShader'));
        }
    }
}

export default ModelLoader;