UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

1,413 lines 66.2 kB
import { i as Color3, m as Texture, b1 as Color4, a7 as VertexBuffer, V as Vector3, b3 as Vector2, y as Logger, bJ as Geometry, aV as Mesh, aW as VertexData, bK as OBJFileLoaderMetadata, aY as Tools, bn as RegisterSceneLoaderPlugin } from './index-HyNDfLMI.esm.js';
import { A as AssetContainer } from './assetContainer-BhW2gcI4.esm.js';
import './standardMaterial-D4fmziTQ.esm.js';
import { S as StandardMaterial } from './standardMaterial.pure-B45jJn5x.esm.js';
import './buffer-DQKHeFx_.esm.js';
import './prepass.defines-NGwPAvhm.esm.js';
import './material.detailMapConfiguration-C33hA3Hm.esm.js';

/**
 * Class reading and parsing the MTL file bundled with the obj file.
 */
class MTLFileLoader {
    constructor() {
        /**
         * All material loaded from the mtl will be set here
         */
        this.materials = [];
    }
    /**
     * This function will read the mtl file and create each material described inside
     * This function could be improve by adding :
     * -some component missing (Ni, Tf...)
     * -including the specific options available
     *
     * @param scene defines the scene the material will be created in
     * @param data defines the mtl data to parse
     * @param rootUrl defines the rooturl to use in order to load relative dependencies
     * @param assetContainer defines the asset container to store the material in (can be null)
     */
    parseMTL(scene, data, rootUrl, assetContainer) {
        if (data instanceof ArrayBuffer) {
            return;
        }
        //Split the lines from the file
        const lines = data.split("\n");
        // whitespace char ie: [ \t\r\n\f]
        const delimiterPattern = /\s+/;
        //Array with RGB colors
        let color;
        //New material
        let material = null;
        //Look at each line
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim();
            // Blank line or comment
            if (line.length === 0 || line.charAt(0) === "#") {
                continue;
            }
            //Get the first parameter (keyword)
            const pos = line.indexOf(" ");
            let key = pos >= 0 ? line.substring(0, pos) : line;
            key = key.toLowerCase();
            //Get the data following the key
            const value = pos >= 0 ? line.substring(pos + 1).trim() : "";
            //This mtl keyword will create the new material
            if (key === "newmtl") {
                //Check if it is the first material.
                // Materials specifications are described after this keyword.
                if (material) {
                    //Add the previous material in the material array.
                    this.materials.push(material);
                }
                //Create a new material.
                // value is the name of the material read in the mtl file
                scene._blockEntityCollection = !!assetContainer;
                material = new StandardMaterial(value, scene);
                material._parentContainer = assetContainer;
                scene._blockEntityCollection = false;
            }
            else if (key === "kd" && material) {
                // Diffuse color (color under white light) using RGB values
                //value  = "r g b"
                color = value.split(delimiterPattern, 3).map(parseFloat);
                //color = [r,g,b]
                //Set tghe color into the material
                material.diffuseColor = Color3.FromArray(color);
            }
            else if (key === "ka" && material) {
                // Ambient color (color under shadow) using RGB values
                //value = "r g b"
                color = value.split(delimiterPattern, 3).map(parseFloat);
                //color = [r,g,b]
                //Set tghe color into the material
                material.ambientColor = Color3.FromArray(color);
            }
            else if (key === "ks" && material) {
                // Specular color (color when light is reflected from shiny surface) using RGB values
                //value = "r g b"
                color = value.split(delimiterPattern, 3).map(parseFloat);
                //color = [r,g,b]
                //Set the color into the material
                material.specularColor = Color3.FromArray(color);
            }
            else if (key === "ke" && material) {
                // Emissive color using RGB values
                color = value.split(delimiterPattern, 3).map(parseFloat);
                material.emissiveColor = Color3.FromArray(color);
            }
            else if (key === "ns" && material) {
                //value = "Integer"
                material.specularPower = parseFloat(value);
            }
            else if (key === "d" && material) {
                //d is dissolve for current material. It mean alpha for BABYLON
                material.alpha = parseFloat(value);
                //Texture
                //This part can be improved by adding the possible options of texture
            }
            else if (key === "map_ka" && material) {
                // ambient texture map with a loaded image
                //We must first get the folder of the image
                material.ambientTexture = MTLFileLoader._GetTexture(rootUrl, value, scene);
            }
            else if (key === "map_kd" && material) {
                // Diffuse texture map with a loaded image
                material.diffuseTexture = MTLFileLoader._GetTexture(rootUrl, value, scene);
            }
            else if (key === "map_ks" && material) {
                // Specular texture map with a loaded image
                //We must first get the folder of the image
                material.specularTexture = MTLFileLoader._GetTexture(rootUrl, value, scene);
            }
            else if (key === "map_ns") ;
            else if (key === "map_bump" && material) {
                //The bump texture
                const values = value.split(delimiterPattern);
                const bumpMultiplierIndex = values.indexOf("-bm");
                let bumpMultiplier = null;
                if (bumpMultiplierIndex >= 0) {
                    bumpMultiplier = values[bumpMultiplierIndex + 1];
                    values.splice(bumpMultiplierIndex, 2); // remove
                }
                material.bumpTexture = MTLFileLoader._GetTexture(rootUrl, values.join(" "), scene);
                if (material.bumpTexture && bumpMultiplier !== null) {
                    material.bumpTexture.level = parseFloat(bumpMultiplier);
                }
            }
            else if (key === "map_d" && material) {
                // The dissolve of the material
                material.opacityTexture = MTLFileLoader._GetTexture(rootUrl, value, scene);
                //Options for illumination
            }
            else ;
        }
        //At the end of the file, add the last material
        if (material) {
            this.materials.push(material);
        }
    }
    /**
     * Gets the texture for the material.
     *
     * If the material is imported from input file,
     * We sanitize the url to ensure it takes the texture from aside the material.
     *
     * @param rootUrl The root url to load from
     * @param value The value stored in the mtl
     * @param scene
     * @returns The Texture
     */
    static _GetTexture(rootUrl, value, scene) {
        if (!value) {
            return null;
        }
        let url = rootUrl;
        // Load from input file.
        if (rootUrl === "file:") {
            let lastDelimiter = value.lastIndexOf("\\");
            if (lastDelimiter === -1) {
                lastDelimiter = value.lastIndexOf("/");
            }
            if (lastDelimiter > -1) {
                url += value.substring(lastDelimiter + 1);
            }
            else {
                url += value;
            }
        }
        // Not from input file.
        else {
            url += value;
        }
        return new Texture(url, scene, false, MTLFileLoader.INVERT_TEXTURE_Y);
    }
}
/**
 * Invert Y-Axis of referenced textures on load
 */
MTLFileLoader.INVERT_TEXTURE_Y = true;

/**
 * Class used to load mesh data from OBJ content
 */
class SolidParser {
    /**
     * Creates a new SolidParser
     * @param materialToUse defines the array to fill with the list of materials to use (it will be filled by the parse function)
     * @param babylonMeshesArray defines the array to fill with the list of loaded meshes (it will be filled by the parse function)
     * @param loadingOptions defines the loading options to use
     */
    constructor(materialToUse, babylonMeshesArray, loadingOptions) {
        this._positions = []; //values for the positions of vertices
        this._normals = []; //Values for the normals
        this._uvs = []; //Values for the textures
        this._colors = [];
        this._extColors = []; //Extension color
        this._meshesFromObj = []; //[mesh] Contains all the obj meshes
        this._indicesForBabylon = []; //The list of indices for VertexData
        this._wrappedPositionForBabylon = []; //The list of position in vectors
        this._wrappedUvsForBabylon = []; //Array with all value of uvs to match with the indices
        this._wrappedColorsForBabylon = []; // Array with all color values to match with the indices
        this._wrappedNormalsForBabylon = []; //Array with all value of normals to match with the indices
        this._tuplePosNorm = []; //Create a tuple with indice of Position, Normal, UV  [pos, norm, uvs]
        this._curPositionInIndices = 0;
        this._hasMeshes = false; //Meshes are defined in the file
        this._unwrappedPositionsForBabylon = []; //Value of positionForBabylon w/o Vector3() [x,y,z]
        this._unwrappedColorsForBabylon = []; // Value of colorForBabylon w/o Color4() [r,g,b,a]
        this._unwrappedNormalsForBabylon = []; //Value of normalsForBabylon w/o Vector3()  [x,y,z]
        this._unwrappedUVForBabylon = []; //Value of uvsForBabylon w/o Vector3()      [x,y,z]
        this._triangles = []; //Indices from new triangles coming from polygons
        this._materialNameFromObj = ""; //The name of the current material
        this._objMeshName = ""; //The name of the current obj mesh
        this._increment = 1; //Id for meshes created by the multimaterial
        this._isFirstMaterial = true;
        this._grayColor = new Color4(0.5, 0.5, 0.5, 1);
        this._hasLineData = false; //If this mesh has line segment(l) data
        this._materialToUse = materialToUse;
        this._babylonMeshesArray = babylonMeshesArray;
        this._loadingOptions = loadingOptions;
    }
    /**
     * Search for obj in the given array.
     * This function is called to check if a couple of data already exists in an array.
     *
     * If found, returns the index of the founded tuple index. Returns -1 if not found
     * @param arr Array<{ normals: Array<number>, idx: Array<number> }>
     * @param obj Array<number>
     * @returns {boolean}
     */
    _isInArray(arr, obj) {
        if (!arr[obj[0]]) {
            arr[obj[0]] = { normals: [], idx: [] };
        }
        const idx = arr[obj[0]].normals.indexOf(obj[1]);
        return idx === -1 ? -1 : arr[obj[0]].idx[idx];
    }
    _isInArrayUV(arr, obj) {
        if (!arr[obj[0]]) {
            arr[obj[0]] = { normals: [], idx: [], uv: [] };
        }
        const idx = arr[obj[0]].normals.indexOf(obj[1]);
        if (idx != 1 && obj[2] === arr[obj[0]].uv[idx]) {
            return arr[obj[0]].idx[idx];
        }
        return -1;
    }
    /**
     * This function set the data for each triangle.
     * Data are position, normals and uvs
     * If a tuple of (position, normal) is not set, add the data into the corresponding array
     * If the tuple already exist, add only their indice
     *
     * @param data The vertex's data
     * * indicesPositionFromObj: The index in positions array
     * * indicesUvsFromObj: The index in uvs array
     * * indicesNormalFromObj: The index in normals array
     * * positionVectorFromOBJ: The value of position at index objIndice
     * * textureVectorFromOBJ: The value of uvs
     * * normalsVectorFromOBJ: The value of normals at index objNormale
     * * positionColorsFromOBJ: The value of color at index objIndice
     */
    _setData(data) {
        //Use default values if undefined
        data.indiceUvsFromObj ??= -1;
        data.indiceNormalFromObj ??= -1;
        //Check if this tuple already exists in the list of tuples
        let _index;
        if (this._loadingOptions.optimizeWithUV) {
            _index = this._isInArrayUV(this._tuplePosNorm, [data.indicePositionFromObj, data.indiceNormalFromObj, data.indiceUvsFromObj]);
        }
        else {
            _index = this._isInArray(this._tuplePosNorm, [data.indicePositionFromObj, data.indiceNormalFromObj]);
        }
        //If it not exists
        if (_index === -1) {
            //Add an new indice.
            //The array of indices is only an array with his length equal to the number of triangles - 1.
            //We add vertices data in this order
            this._indicesForBabylon.push(this._wrappedPositionForBabylon.length);
            //Push the position of vertice for Babylon
            //Each element is a Vector3(x,y,z)
            this._wrappedPositionForBabylon.push(data.positionVectorFromOBJ);
            if (data.textureVectorFromOBJ !== undefined) {
                //Push the uvs for Babylon
                //Each element is a Vector2(u,v)
                this._wrappedUvsForBabylon.push(data.textureVectorFromOBJ);
            }
            if (data.normalsVectorFromOBJ !== undefined) {
                //Push the normals for Babylon
                //Each element is a Vector3(x,y,z)
                this._wrappedNormalsForBabylon.push(data.normalsVectorFromOBJ);
            }
            if (data.positionColorsFromOBJ !== undefined) {
                //Push the colors for Babylon
                //Each element is a BABYLON.Color4(r,g,b,a)
                this._wrappedColorsForBabylon.push(data.positionColorsFromOBJ);
            }
            //Add the tuple in the comparison list
            this._tuplePosNorm[data.indicePositionFromObj].normals.push(data.indiceNormalFromObj);
            this._tuplePosNorm[data.indicePositionFromObj].idx.push(this._curPositionInIndices++);
            if (this._loadingOptions.optimizeWithUV) {
                this._tuplePosNorm[data.indicePositionFromObj].uv.push(data.indiceUvsFromObj);
            }
        }
        else {
            //The tuple already exists
            //Add the index of the already existing tuple
            //At this index we can get the value of position, normal, color and uvs of vertex
            this._indicesForBabylon.push(_index);
        }
    }
    /**
     * Transform Vector() and BABYLON.Color() objects into numbers in an array
     */
    _unwrapData() {
        try {
            //Every array has the same length
            for (let l = 0; l < this._wrappedPositionForBabylon.length; l++) {
                //Push the x, y, z values of each element in the unwrapped array
                this._unwrappedPositionsForBabylon.push(this._wrappedPositionForBabylon[l].x * this._handednessSign, this._wrappedPositionForBabylon[l].y, this._wrappedPositionForBabylon[l].z);
                if (this._wrappedNormalsForBabylon.length) {
                    this._unwrappedNormalsForBabylon.push(this._wrappedNormalsForBabylon[l].x * this._handednessSign, this._wrappedNormalsForBabylon[l].y, this._wrappedNormalsForBabylon[l].z);
                }
                if (this._wrappedUvsForBabylon.length) {
                    this._unwrappedUVForBabylon.push(this._wrappedUvsForBabylon[l].x, this._wrappedUvsForBabylon[l].y); //z is an optional value not supported by BABYLON
                }
                if (this._wrappedColorsForBabylon.length) {
                    //Push the r, g, b, a values of each element in the unwrapped array
                    this._unwrappedColorsForBabylon.push(this._wrappedColorsForBabylon[l].r, this._wrappedColorsForBabylon[l].g, this._wrappedColorsForBabylon[l].b, this._wrappedColorsForBabylon[l].a);
                }
            }
            // Reset arrays for the next new meshes
            this._wrappedPositionForBabylon.length = 0;
            this._wrappedNormalsForBabylon.length = 0;
            this._wrappedUvsForBabylon.length = 0;
            this._wrappedColorsForBabylon.length = 0;
            this._tuplePosNorm.length = 0;
            this._curPositionInIndices = 0;
        }
        catch (e) {
            throw new Error("Unable to unwrap data while parsing OBJ data.", { cause: e });
        }
    }
    /**
     * Create triangles from polygons
     * It is important to notice that a triangle is a polygon
     * We get 5 patterns of face defined in OBJ File :
     * facePattern1 = ["1","2","3","4","5","6"]
     * facePattern2 = ["1/1","2/2","3/3","4/4","5/5","6/6"]
     * facePattern3 = ["1/1/1","2/2/2","3/3/3","4/4/4","5/5/5","6/6/6"]
     * facePattern4 = ["1//1","2//2","3//3","4//4","5//5","6//6"]
     * facePattern5 = ["-1/-1/-1","-2/-2/-2","-3/-3/-3","-4/-4/-4","-5/-5/-5","-6/-6/-6"]
     * Each pattern is divided by the same method
     * @param faces Array[String] The indices of elements
     * @param v Integer The variable to increment
     */
    _getTriangles(faces, v) {
        //Work for each element of the array
        for (let faceIndex = v; faceIndex < faces.length - 1; faceIndex++) {
            //Add on the triangle variable the indexes to obtain triangles
            this._pushTriangle(faces, faceIndex);
        }
        //Result obtained after 2 iterations:
        //Pattern1 => triangle = ["1","2","3","1","3","4"];
        //Pattern2 => triangle = ["1/1","2/2","3/3","1/1","3/3","4/4"];
        //Pattern3 => triangle = ["1/1/1","2/2/2","3/3/3","1/1/1","3/3/3","4/4/4"];
        //Pattern4 => triangle = ["1//1","2//2","3//3","1//1","3//3","4//4"];
        //Pattern5 => triangle = ["-1/-1/-1","-2/-2/-2","-3/-3/-3","-1/-1/-1","-3/-3/-3","-4/-4/-4"];
    }
    /**
     * To get color between color and extension color
     * @param index Integer The index of the element in the array
     * @returns value of target color
     */
    _getColor(index) {
        if (this._loadingOptions.importVertexColors) {
            return this._extColors[index] ?? this._colors[index];
        }
        else {
            return undefined;
        }
    }
    /**
     * Create triangles and push the data for each polygon for the pattern 1
     * In this pattern we get vertice positions
     * @param face
     * @param v
     */
    _setDataForCurrentFaceWithPattern1(face, v) {
        //Get the indices of triangles for each polygon
        this._getTriangles(face, v);
        //For each element in the triangles array.
        //This var could contains 1 to an infinity of triangles
        for (let k = 0; k < this._triangles.length; k++) {
            // Set position indice
            const indicePositionFromObj = parseInt(this._triangles[k]) - 1;
            this._setData({
                indicePositionFromObj,
                positionVectorFromOBJ: this._positions[indicePositionFromObj],
                positionColorsFromOBJ: this._getColor(indicePositionFromObj),
            });
        }
        //Reset variable for the next line
        this._triangles.length = 0;
    }
    /**
     * Create triangles and push the data for each polygon for the pattern 2
     * In this pattern we get vertice positions and uvs
     * @param face
     * @param v
     */
    _setDataForCurrentFaceWithPattern2(face, v) {
        //Get the indices of triangles for each polygon
        this._getTriangles(face, v);
        for (let k = 0; k < this._triangles.length; k++) {
            //triangle[k] = "1/1"
            //Split the data for getting position and uv
            const point = this._triangles[k].split("/"); // ["1", "1"]
            //Set position indice
            const indicePositionFromObj = parseInt(point[0]) - 1;
            //Set uv indice
            const indiceUvsFromObj = parseInt(point[1]) - 1;
            this._setData({
                indicePositionFromObj,
                indiceUvsFromObj,
                positionVectorFromOBJ: this._positions[indicePositionFromObj],
                textureVectorFromOBJ: this._uvs[indiceUvsFromObj],
                positionColorsFromOBJ: this._getColor(indicePositionFromObj),
            });
        }
        //Reset variable for the next line
        this._triangles.length = 0;
    }
    /**
     * Create triangles and push the data for each polygon for the pattern 3
     * In this pattern we get vertice positions, uvs and normals
     * @param face
     * @param v
     */
    _setDataForCurrentFaceWithPattern3(face, v) {
        //Get the indices of triangles for each polygon
        this._getTriangles(face, v);
        for (let k = 0; k < this._triangles.length; k++) {
            //triangle[k] = "1/1/1"
            //Split the data for getting position, uv, and normals
            const point = this._triangles[k].split("/"); // ["1", "1", "1"]
            // Set position indice
            const indicePositionFromObj = parseInt(point[0]) - 1;
            // Set uv indice
            const indiceUvsFromObj = parseInt(point[1]) - 1;
            // Set normal indice
            const indiceNormalFromObj = parseInt(point[2]) - 1;
            this._setData({
                indicePositionFromObj,
                indiceUvsFromObj,
                indiceNormalFromObj,
                positionVectorFromOBJ: this._positions[indicePositionFromObj],
                textureVectorFromOBJ: this._uvs[indiceUvsFromObj],
                normalsVectorFromOBJ: this._normals[indiceNormalFromObj],
            });
        }
        //Reset variable for the next line
        this._triangles.length = 0;
    }
    /**
     * Create triangles and push the data for each polygon for the pattern 4
     * In this pattern we get vertice positions and normals
     * @param face
     * @param v
     */
    _setDataForCurrentFaceWithPattern4(face, v) {
        this._getTriangles(face, v);
        for (let k = 0; k < this._triangles.length; k++) {
            //triangle[k] = "1//1"
            //Split the data for getting position and normals
            const point = this._triangles[k].split("//"); // ["1", "1"]
            // We check indices, and normals
            const indicePositionFromObj = parseInt(point[0]) - 1;
            const indiceNormalFromObj = parseInt(point[1]) - 1;
            this._setData({
                indicePositionFromObj,
                indiceNormalFromObj,
                positionVectorFromOBJ: this._positions[indicePositionFromObj],
                normalsVectorFromOBJ: this._normals[indiceNormalFromObj],
                positionColorsFromOBJ: this._getColor(indicePositionFromObj),
            });
        }
        //Reset variable for the next line
        this._triangles.length = 0;
    }
    /*
     * Create triangles and push the data for each polygon for the pattern 3
     * In this pattern we get vertice positions, uvs and normals
     * @param face
     * @param v
     */
    _setDataForCurrentFaceWithPattern5(face, v) {
        //Get the indices of triangles for each polygon
        this._getTriangles(face, v);
        for (let k = 0; k < this._triangles.length; k++) {
            //triangle[k] = "-1/-1/-1"
            //Split the data for getting position, uv, and normals
            const point = this._triangles[k].split("/"); // ["-1", "-1", "-1"]
            // Set position indice
            const indicePositionFromObj = this._positions.length + parseInt(point[0]);
            // Set uv indice
            const indiceUvsFromObj = this._uvs.length + parseInt(point[1]);
            // Set normal indice
            const indiceNormalFromObj = this._normals.length + parseInt(point[2]);
            this._setData({
                indicePositionFromObj,
                indiceUvsFromObj,
                indiceNormalFromObj,
                positionVectorFromOBJ: this._positions[indicePositionFromObj],
                textureVectorFromOBJ: this._uvs[indiceUvsFromObj],
                normalsVectorFromOBJ: this._normals[indiceNormalFromObj],
                positionColorsFromOBJ: this._getColor(indicePositionFromObj),
            });
        }
        //Reset variable for the next line
        this._triangles.length = 0;
    }
    _addPreviousObjMesh() {
        //Check if it is not the first mesh. Otherwise we don't have data.
        if (this._meshesFromObj.length > 0) {
            //Get the previous mesh for applying the data about the faces
            //=> in obj file, faces definition append after the name of the mesh
            this._handledMesh = this._meshesFromObj[this._meshesFromObj.length - 1];
            //Set the data into Array for the mesh
            this._unwrapData();
            if (this._loadingOptions.useLegacyBehavior) {
                // Reverse tab. Otherwise face are displayed in the wrong sens
                this._indicesForBabylon.reverse();
            }
            //Set the information for the mesh
            //Slice the array to avoid rewriting because of the fact this is the same var which be rewrited
            this._handledMesh.indices = this._indicesForBabylon.slice();
            this._handledMesh.positions = this._unwrappedPositionsForBabylon.slice();
            if (this._unwrappedNormalsForBabylon.length) {
                this._handledMesh.normals = this._unwrappedNormalsForBabylon.slice();
            }
            if (this._unwrappedUVForBabylon.length) {
                this._handledMesh.uvs = this._unwrappedUVForBabylon.slice();
            }
            if (this._unwrappedColorsForBabylon.length) {
                this._handledMesh.colors = this._unwrappedColorsForBabylon.slice();
            }
            this._handledMesh.hasLines = this._hasLineData;
            //Reset the array for the next mesh
            this._indicesForBabylon.length = 0;
            this._unwrappedPositionsForBabylon.length = 0;
            this._unwrappedColorsForBabylon.length = 0;
            this._unwrappedNormalsForBabylon.length = 0;
            this._unwrappedUVForBabylon.length = 0;
            this._hasLineData = false;
        }
    }
    _optimizeNormals(mesh) {
        const positions = mesh.getVerticesData(VertexBuffer.PositionKind);
        const normals = mesh.getVerticesData(VertexBuffer.NormalKind);
        const mapVertices = {};
        if (!positions || !normals) {
            return;
        }
        for (let i = 0; i < positions.length / 3; i++) {
            const x = positions[i * 3 + 0];
            const y = positions[i * 3 + 1];
            const z = positions[i * 3 + 2];
            const key = x + "_" + y + "_" + z;
            let lst = mapVertices[key];
            if (!lst) {
                lst = [];
                mapVertices[key] = lst;
            }
            lst.push(i);
        }
        const normal = new Vector3();
        for (const key in mapVertices) {
            const lst = mapVertices[key];
            if (lst.length < 2) {
                continue;
            }
            const v0Idx = lst[0];
            for (let i = 1; i < lst.length; ++i) {
                const vIdx = lst[i];
                normals[v0Idx * 3 + 0] += normals[vIdx * 3 + 0];
                normals[v0Idx * 3 + 1] += normals[vIdx * 3 + 1];
                normals[v0Idx * 3 + 2] += normals[vIdx * 3 + 2];
            }
            normal.copyFromFloats(normals[v0Idx * 3 + 0], normals[v0Idx * 3 + 1], normals[v0Idx * 3 + 2]);
            normal.normalize();
            for (let i = 0; i < lst.length; ++i) {
                const vIdx = lst[i];
                normals[vIdx * 3 + 0] = normal.x;
                normals[vIdx * 3 + 1] = normal.y;
                normals[vIdx * 3 + 2] = normal.z;
            }
        }
        mesh.setVerticesData(VertexBuffer.NormalKind, normals);
    }
    static _IsLineElement(line) {
        return line.startsWith("l");
    }
    static _IsObjectElement(line) {
        return line.startsWith("o");
    }
    static _IsGroupElement(line) {
        return line.startsWith("g");
    }
    static _GetZbrushMRGB(line, notParse) {
        if (!line.startsWith("mrgb")) {
            return null;
        }
        line = line.replace("mrgb", "").trim();
        // if include vertex color , not load mrgb anymore
        if (notParse) {
            return [];
        }
        const regex = /[a-z0-9]/g;
        const regArray = line.match(regex);
        if (!regArray || regArray.length % 8 !== 0) {
            return [];
        }
        const array = [];
        for (let regIndex = 0; regIndex < regArray.length / 8; regIndex++) {
            //each item is MMRRGGBB, m is material index
            // const m = regArray[regIndex * 8 + 0] + regArray[regIndex * 8 + 1];
            const r = regArray[regIndex * 8 + 2] + regArray[regIndex * 8 + 3];
            const g = regArray[regIndex * 8 + 4] + regArray[regIndex * 8 + 5];
            const b = regArray[regIndex * 8 + 6] + regArray[regIndex * 8 + 7];
            array.push(new Color4(parseInt(r, 16) / 255, parseInt(g, 16) / 255, parseInt(b, 16) / 255, 1));
        }
        return array;
    }
    /**
     * Function used to parse an OBJ string
     * @param meshesNames defines the list of meshes to load (all if not defined)
     * @param data defines the OBJ string
     * @param scene defines the hosting scene
     * @param assetContainer defines the asset container to load data in
     * @param onFileToLoadFound defines a callback that will be called if a MTL file is found
     */
    parse(meshesNames, data, scene, assetContainer, onFileToLoadFound) {
        //Move Santitize here to forbid delete zbrush data
        // Sanitize data
        data = data.replace(/#MRGB/g, "mrgb");
        data = data.replace(/#.*$/gm, "").trim();
        if (this._loadingOptions.useLegacyBehavior) {
            this._pushTriangle = (faces, faceIndex) => this._triangles.push(faces[0], faces[faceIndex], faces[faceIndex + 1]);
            this._handednessSign = 1;
        }
        else if (scene.useRightHandedSystem) {
            this._pushTriangle = (faces, faceIndex) => this._triangles.push(faces[0], faces[faceIndex + 1], faces[faceIndex]);
            this._handednessSign = 1;
        }
        else {
            this._pushTriangle = (faces, faceIndex) => this._triangles.push(faces[0], faces[faceIndex], faces[faceIndex + 1]);
            this._handednessSign = -1;
        }
        // Split the file into lines
        // Preprocess line data
        const linesOBJ = data.split("\n");
        const lineLines = [];
        let currentGroup = [];
        lineLines.push(currentGroup);
        for (let i = 0; i < linesOBJ.length; i++) {
            const line = linesOBJ[i].trim().replace(/\s\s/g, " ");
            // Comment or newLine
            if (line.length === 0 || line.charAt(0) === "#") {
                continue;
            }
            if (SolidParser._IsGroupElement(line) || SolidParser._IsObjectElement(line)) {
                currentGroup = [];
                lineLines.push(currentGroup);
            }
            if (SolidParser._IsLineElement(line)) {
                const lineValues = line.split(" ");
                // create line elements with two vertices only
                for (let i = 1; i < lineValues.length - 1; i++) {
                    currentGroup.push(`l ${lineValues[i]} ${lineValues[i + 1]}`);
                }
            }
            else {
                currentGroup.push(line);
            }
        }
        const lines = lineLines.flat();
        // Look at each line
        for (let i = 0; i < lines.length; i++) {
            const line = lines[i].trim().replace(/\s\s/g, " ");
            let result;
            // Comment or newLine
            if (line.length === 0 || line.charAt(0) === "#") {
                continue;
            }
            else if (SolidParser.VertexPattern.test(line)) {
                //Get information about one position possible for the vertices
                result = line.match(/[^ ]+/g); // match will return non-null due to passing regex pattern
                // Value of result with line: "v 1.0 2.0 3.0"
                // ["v", "1.0", "2.0", "3.0"]
                // Create a Vector3 with the position x, y, z
                this._positions.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])));
                if (this._loadingOptions.importVertexColors) {
                    if (result.length >= 7) {
                        const r = parseFloat(result[4]);
                        const g = parseFloat(result[5]);
                        const b = parseFloat(result[6]);
                        this._colors.push(new Color4(r > 1 ? r / 255 : r, g > 1 ? g / 255 : g, b > 1 ? b / 255 : b, result.length === 7 || result[7] === undefined ? 1 : parseFloat(result[7])));
                    }
                    else {
                        // TODO: maybe push NULL and if all are NULL to skip (and remove grayColor var).
                        this._colors.push(this._grayColor);
                    }
                }
            }
            else if ((result = SolidParser.NormalPattern.exec(line)) !== null) {
                //Create a Vector3 with the normals x, y, z
                //Value of result
                // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
                //Add the Vector in the list of normals
                this._normals.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3])));
            }
            else if ((result = SolidParser.UVPattern.exec(line)) !== null) {
                //Create a Vector2 with the normals u, v
                //Value of result
                // ["vt 0.1 0.2 0.3", "0.1", "0.2"]
                //Add the Vector in the list of uvs
                this._uvs.push(new Vector2(parseFloat(result[1]) * this._loadingOptions.UVScaling.x, parseFloat(result[2]) * this._loadingOptions.UVScaling.y));
                //Identify patterns of faces
                //Face could be defined in different type of pattern
            }
            else if ((result = SolidParser.FacePattern3.exec(line)) !== null) {
                //Value of result:
                //["f 1/1/1 2/2/2 3/3/3", "1/1/1 2/2/2 3/3/3"...]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern3(result[1].trim().split(" "), // ["1/1/1", "2/2/2", "3/3/3"]
                1);
            }
            else if ((result = SolidParser.FacePattern4.exec(line)) !== null) {
                //Value of result:
                //["f 1//1 2//2 3//3", "1//1 2//2 3//3"...]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern4(result[1].trim().split(" "), // ["1//1", "2//2", "3//3"]
                1);
            }
            else if ((result = SolidParser.FacePattern5.exec(line)) !== null) {
                //Value of result:
                //["f -1/-1/-1 -2/-2/-2 -3/-3/-3", "-1/-1/-1 -2/-2/-2 -3/-3/-3"...]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern5(result[1].trim().split(" "), // ["-1/-1/-1", "-2/-2/-2", "-3/-3/-3"]
                1);
            }
            else if ((result = SolidParser.FacePattern2.exec(line)) !== null) {
                //Value of result:
                //["f 1/1 2/2 3/3", "1/1 2/2 3/3"...]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern2(result[1].trim().split(" "), // ["1/1", "2/2", "3/3"]
                1);
            }
            else if ((result = SolidParser.FacePattern1.exec(line)) !== null) {
                //Value of result
                //["f 1 2 3", "1 2 3"...]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern1(result[1].trim().split(" "), // ["1", "2", "3"]
                1);
                // Define a mesh or an object
                // Each time this keyword is analyzed, create a new Object with all data for creating a babylonMesh
            }
            else if ((result = SolidParser.LinePattern1.exec(line)) !== null) {
                //Value of result
                //["l 1 2"]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern1(result[1].trim().split(" "), // ["1", "2"]
                0);
                this._hasLineData = true;
                // Define a mesh or an object
                // Each time this keyword is analyzed, create a new Object with all data for creating a babylonMesh
            }
            else if ((result = SolidParser.LinePattern2.exec(line)) !== null) {
                //Value of result
                //["l 1/1 2/2"]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern2(result[1].trim().split(" "), // ["1/1", "2/2"]
                0);
                this._hasLineData = true;
                // Define a mesh or an object
                // Each time this keyword is analyzed, create a new Object with all data for creating a babylonMesh
            }
            else if ((result = SolidParser._GetZbrushMRGB(line, !this._loadingOptions.importVertexColors))) {
                for (const element of result) {
                    this._extColors.push(element);
                }
            }
            else if ((result = SolidParser.LinePattern3.exec(line)) !== null) {
                //Value of result
                //["l 1/1/1 2/2/2"]
                //Set the data for this face
                this._setDataForCurrentFaceWithPattern3(result[1].trim().split(" "), // ["1/1/1", "2/2/2"]
                0);
                this._hasLineData = true;
                // Define a mesh or an object
                // Each time this keyword is analyzed, create a new Object with all data for creating a babylonMesh
            }
            else if (SolidParser.GroupDescriptor.test(line) || SolidParser.ObjectDescriptor.test(line)) {
                // Create a new mesh corresponding to the name of the group.
                // Definition of the mesh
                const objMesh = {
                    name: line.substring(2).trim(), //Set the name of the current obj mesh
                    indices: null,
                    positions: null,
                    normals: null,
                    uvs: null,
                    colors: null,
                    materialName: this._materialNameFromObj,
                    isObject: SolidParser.ObjectDescriptor.test(line),
                };
                this._addPreviousObjMesh();
                //Push the last mesh created with only the name
                this._meshesFromObj.push(objMesh);
                //Set this variable to indicate that now meshesFromObj has objects defined inside
                this._hasMeshes = true;
                this._isFirstMaterial = true;
                this._increment = 1;
                //Keyword for applying a material
            }
            else if (SolidParser.UseMtlDescriptor.test(line)) {
                //Get the name of the material
                this._materialNameFromObj = line.substring(7).trim();
                //If this new material is in the same mesh
                if (!this._isFirstMaterial || !this._hasMeshes) {
                    //Set the data for the previous mesh
                    this._addPreviousObjMesh();
                    //Create a new mesh
                    const objMesh = 
                    //Set the name of the current obj mesh
                    {
                        name: (this._objMeshName || "mesh") + "_mm" + this._increment.toString(), //Set the name of the current obj mesh
                        indices: null,
                        positions: null,
                        normals: null,
                        uvs: null,
                        colors: null,
                        materialName: this._materialNameFromObj,
                        isObject: false,
                    };
                    this._increment++;
                    //If meshes are already defined
                    this._meshesFromObj.push(objMesh);
                    this._hasMeshes = true;
                }
                //Set the material name if the previous line define a mesh
                if (this._hasMeshes && this._isFirstMaterial) {
                    //Set the material name to the previous mesh (1 material per mesh)
                    this._meshesFromObj[this._meshesFromObj.length - 1].materialName = this._materialNameFromObj;
                    this._isFirstMaterial = false;
                }
                // Keyword for loading the mtl file
            }
            else if (SolidParser.MtlLibGroupDescriptor.test(line)) {
                // Get the name of mtl file
                onFileToLoadFound(line.substring(7).trim());
                // Apply smoothing
            }
            else if (SolidParser.SmoothDescriptor.test(line)) ;
            else {
                //If there is another possibility
                Logger.Log("Unhandled expression at line : " + line);
            }
        }
        // At the end of the file, add the last mesh into the meshesFromObj array
        if (this._hasMeshes) {
            // Set the data for the last mesh
            this._handledMesh = this._meshesFromObj[this._meshesFromObj.length - 1];
            if (this._loadingOptions.useLegacyBehavior) {
                //Reverse indices for displaying faces in the good sense
                this._indicesForBabylon.reverse();
            }
            //Get the good array
            this._unwrapData();
            //Set array
            this._handledMesh.indices = this._indicesForBabylon;
            this._handledMesh.positions = this._unwrappedPositionsForBabylon;
            if (this._unwrappedNormalsForBabylon.length) {
                this._handledMesh.normals = this._unwrappedNormalsForBabylon;
            }
            if (this._unwrappedUVForBabylon.length) {
                this._handledMesh.uvs = this._unwrappedUVForBabylon;
            }
            if (this._unwrappedColorsForBabylon.length) {
                this._handledMesh.colors = this._unwrappedColorsForBabylon;
            }
            this._handledMesh.hasLines = this._hasLineData;
        }
        // If any o or g keyword not found, create a mesh with a random id
        if (!this._hasMeshes) {
            let newMaterial = null;
            if (this._indicesForBabylon.length) {
                if (this._loadingOptions.useLegacyBehavior) {
                    // reverse tab of indices
                    this._indicesForBabylon.reverse();
                }
                //Get positions normals uvs
                this._unwrapData();
            }
            else {
                // There is no indices in the file. We will have to switch to point cloud rendering
                for (const pos of this._positions) {
                    this._unwrappedPositionsForBabylon.push(pos.x, pos.y, pos.z);
                }
                if (this._normals.length) {
                    for (const normal of this._normals) {
                        this._unwrappedNormalsForBabylon.push(normal.x, normal.y, normal.z);
                    }
                }
                if (this._uvs.length) {
                    for (const uv of this._uvs) {
                        this._unwrappedUVForBabylon.push(uv.x, uv.y);
                    }
                }
                if (this._extColors.length) {
                    for (const color of this._extColors) {
                        this._unwrappedColorsForBabylon.push(color.r, color.g, color.b, color.a);
                    }
                }
                else {
                    if (this._colors.length) {
                        for (const color of this._colors) {
                            this._unwrappedColorsForBabylon.push(color.r, color.g, color.b, color.a);
                        }
                    }
                }
                if (!this._materialNameFromObj) {
                    // Create a material with point cloud on
                    newMaterial = new StandardMaterial(Geometry.RandomId(), scene);
                    newMaterial.pointsCloud = true;
                    this._materialNameFromObj = newMaterial.name;
                    if (!this._normals.length) {
                        newMaterial.disableLighting = true;
                        newMaterial.emissiveColor = Color3.White();
                    }
                }
            }
            //Set data for one mesh
            this._meshesFromObj.push({
                name: Geometry.RandomId(),
                indices: this._indicesForBabylon,
                positions: this._unwrappedPositionsForBabylon,
                colors: this._unwrappedColorsForBabylon,
                normals: this._unwrappedNormalsForBabylon,
                uvs: this._unwrappedUVForBabylon,
                materialName: this._materialNameFromObj,
                directMaterial: newMaterial,
                isObject: true,
                hasLines: this._hasLineData,
            });
        }
        //Set data for each mesh
        for (let j = 0; j < this._meshesFromObj.length; j++) {
            //check meshesNames (stlFileLoader)
            if (meshesNames && this._meshesFromObj[j].name) {
                if (meshesNames instanceof Array) {
                    if (meshesNames.indexOf(this._meshesFromObj[j].name) === -1) {
                        continue;
                    }
                }
                else {
                    if (this._meshesFromObj[j].name !== meshesNames) {
                        continue;
                    }
                }
            }
            //Get the current mesh
            //Set the data with VertexBuffer for each mesh
            this._handledMesh = this._meshesFromObj[j];
            //Create a Mesh with the name of the obj mesh
            scene._blockEntityCollection = !!assetContainer;
            const babylonMesh = new Mesh(this._meshesFromObj[j].name, scene);
            babylonMesh._parentContainer = assetContainer;
            scene._blockEntityCollection = false;
            this._handledMesh._babylonMesh = babylonMesh;
            // If this is a group mesh, it should have an object mesh as a parent. So look for the first object mesh that appears before it.
            if (!this._handledMesh.isObject) {
                for (let k = j - 1; k >= 0; --k) {
                    if (this._meshesFromObj[k].isObject && this._meshesFromObj[k]._babylonMesh) {
                        babylonMesh.parent = this._meshesFromObj[k]._babylonMesh;
                        break;
                    }
                }
            }
            //Push the name of the material to an array
            //This is indispensable for the importMesh function
            this._materialToUse.push(this._meshesFromObj[j].materialName);
            //If the mesh is a line mesh
            if (this._handledMesh.hasLines) {
                babylonMesh._internalMetadata ??= {};
                babylonMesh._internalMetadata["_isLine"] = true; //this is a line mesh
            }
            if (this._handledMesh.positions?.length === 0) {
                //Push the mesh into an array
                this._babylonMeshesArray.push(babylonMesh);
                continue;
            }
            const vertexData = new VertexData(); //The container for the values
            //Set the data for the babylonMesh
            vertexData.indices = this._handledMesh.indices;
            vertexData.positions = this._handledMesh.positions;
            // Empty arrays are truthy; binding a zero-length buffer causes "Vertex buffer is not big enough for the draw call".
            if (this._loadingOptions.computeNormals || !this._handledMesh.normals?.length) {
                // Compute normals if requested or if normals are not defined
                const normals = new Array();
                VertexData.ComputeNormals(this._handledMesh.positions, this._handledMesh.indices, normals);
                vertexData.normals = normals;
            }
            else {
                vertexData.normals = this._handledMesh.normals;
            }
            if (this._handledMesh.uvs?.length) {
                vertexData.uvs = this._handledMesh.uvs;
            }
            if (this._handledMesh.colors?.length) {
                vertexData.colors = this._handledMesh.colors;
            }
            //Set the data from the VertexBuffer to the current Mesh
            vertexData.applyToMesh(babylonMesh);
            if (this._loadingOptions.invertY) {
                babylonMesh.scaling.y *= -1;
            }
            if (this._loadingOptions.optimizeNormals) {
                this._optimizeNormals(babylonMesh);
            }
            //Push the mesh into an array
            this._babylonMeshesArray.push(babylonMesh);
            if (this._handledMesh.directMaterial) {
                babylonMesh.material = this._handledMesh.directMaterial;
            }
        }
    }
}
// Descriptor
/** Object descriptor */
SolidParser.ObjectDescriptor = /^o/;
/** Group descriptor */
SolidParser.GroupDescriptor = /^g/;
/** Material lib descriptor */
SolidParser.MtlLibGroupDescriptor = /^mtllib /;
/** Use a material descriptor */
SolidParser.UseMtlDescriptor = /^usemtl /;
/** Smooth descriptor */
SolidParser.SmoothDescriptor = /^s /;
// Patterns
/** Pattern used to detect a vertex */
SolidParser.VertexPattern = /^v(\s+[\d|.|+|\-|e|E]+){3,7}/;
/** Pattern used to detect a normal */
SolidParser.NormalPattern = /^vn(\s+[\d|.|+|\-|e|E]+)( +[\d|.|+|\-|e|E]+)( +[\d|.|+|\-|e|E]+)/;
/** Pattern used to detect a UV set */
SolidParser.UVPattern = /^vt(\s+[\d|.|+|\-|e|E]+)( +[\d|.|+|\-|e|E]+)/;
/** Pattern used to detect a first kind of face (f vertex vertex vertex) */
SolidParser.FacePattern1 = /^f\s+(([\d]{1,}[\s]?){3,})+/;
/** Pattern used to detect a second kind of face (f vertex/uvs vertex/uvs vertex/uvs) */
SolidParser.FacePattern2 = /^f\s+((([\d]{1,}\/[\d]{1,}[\s]?){3,})+)/;
/** Pattern used to detect a third kind of face (f vertex/uvs/normal vertex/uvs/normal vertex/uvs/normal) */
SolidParser.FacePattern3 = /^f\s+((([\d]{1,}\/[\d]{1,}\/[\d]{1,}[\s]?){3,})+)/;
/** Pattern used to detect a fourth kind of face (f vertex//normal vertex//normal vertex//normal)*/
SolidParser.FacePattern4 = /^f\s+((([\d]{1,}\/\/[\d]{1,}[\s]?){3,})+)/;
/** Pattern used to detect a fifth kind of face (f -vertex/-uvs/-normal -vertex/-uvs/-normal -vertex/-uvs/-normal) */
SolidParser.FacePattern5 = /^f\s+(((-[\d]{1,}\/-[\d]{1,}\/-[\d]{1,}[\s]?){3,})+)/;
/** Pattern used to detect a line(l vertex vertex) */
SolidParser.LinePattern1 = /^l\s+(([\d]{1,}[\s]?){2,})+/;
/** Pattern used to detect a second kind of line (l vertex/uvs vertex/uvs) */
SolidParser.LinePattern2 = /^l\s+((([\d]{1,}\/[\d]{1,}[\s]?){2,})+)/;
/** Pattern used to detect a third kind of line (l vertex/uvs/normal vertex/uvs/normal) */
SolidParser.LinePattern3 = /^l\s+((([\d]{1,}\/[\d]{1,}\/[\d]{1,}[\s]?){2,})+)/;

/**
 * OBJ file type loader.
 * This is a babylon scene loader plugin.
 */
class OBJFileLoader {
    /**
     * Invert Y-Axis of referenced textures on load
     */
    static get INVERT_TEXTURE_Y() {
        return MTLFileLoader.INVERT_TEXTURE_Y;
    }
    static set INVERT_TEXTURE_Y(value) {
        MTLFileLoader.INVERT_TEXTURE_Y = value;
    }
    /**
     * Creates loader for .OBJ files
     *
     * @param loadingOptions options for loading and parsing OBJ/MTL files.
     */
    constructor(loadingOptions) {
        /**
         * Defines the name of the plugin.
         */
        this.name = OBJFileLoaderMetadata.name;
        /**
         * Defines the extension the plugin is able to load.
         */
        this.extensions = OBJFileLoaderMetadata.extensions;
        this._assetContainer = null;
        this._loadingOptions = { ...OBJFileLoader._DefaultLoadingOptions, ...(loadingOptions ?? {}) };
    }
    static get _DefaultLoadingOptions() {
        return {
            computeNormals: OBJFileLoader.COMPUTE_NORMALS,
            optimizeNormals: OBJFileLoader.OPTIMIZE_NORMALS,
            importVertexColors: OBJFileLoader.IMPORT_VERTEX_COLORS,
            invertY: OBJFileLoader.INVERT_Y,
            invertTextureY: OBJFileLoader.INVERT_TEXTURE_Y,
            // eslint-disable-next-line @typescript-eslint/naming-convention
            UVScaling: OBJFileLoader.UV_SCALING,
            materialLoadingFailsSilently: OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY,
            optimizeWithUV: OBJFileLoader.OPTIMIZE_WITH_UV,
            skipMaterials: OBJFileLoader.SKIP_MATERIALS,
            useLegacyBehavior: OBJFileLoader.USE_LEGACY_BEHAVIOR,
        };
    }
    /**
     * Calls synchronously the MTL file attached to this obj.
     * Load function or importMesh function don't enable to load 2 files in the same time asynchronously.
     * Without this function materials are not displayed in the first frame (but displayed after).
     * In consequence it is impossible to get material information in your HTML file
     *
     * @param url The URL of the MTL file
     * @param rootUrl defines where to load data from
     * @param onSuccess Callback function to be called when the MTL file is loaded
     * @param onFailure
     */
    _loadMTL(url, rootUrl, onSuccess, onFailure) {
        //The complete path to the mtl file
        const pathOfFile = rootUrl + url;
        // Loads through the babylon tools to allow fileInput search.
        Tools.LoadFile(pathOfFile, onSuccess, undefined, undefined, false, (request, exception) => {
            onFailure(pathOfFile, exception);
        });
    }
    /** @internal */
    createPlugin(options) {
        return new OBJFileLoader(options[OBJFileLoaderMetadata.name]);
    }
    /**
     * If the data string can be loaded directly.
     * @returns if the data can be loaded directly
     */
    canDirectLoad() {
        return false;
    }
    /**
     * Imports one or more meshes from the loaded OBJ data and adds them to the scene
     * @param meshesNames a string or array of strings of the mesh names that should be loaded from the file
     * @param scene the scene the meshes should be added to
     * @param data the OBJ data to load
     * @param rootUrl root url to load from
     * @returns a promise containing the loaded meshes, particles, skeletons and animations
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    importMeshAsync(meshesNames, scene, data, rootUrl) {
        //get the meshes from OBJ file
        // eslint-disable-next-line github/no-then
        return this._parseSolidAsync(meshesNames, scene, data, rootUrl).then((meshes) => {
            return {
                meshes: meshes,
                particleSystems: [],
                skeletons: [],
                animationGroups: [],
                transformNodes: [],
                geometries: [],
                lights: [],
                spriteManagers: [],
            };
        });
    }
    /**
     * Imports all objects from the loaded OBJ data and adds them to the scene
     * @param scene the scene the objects should be added to
     * @param data the OBJ data to load
     * @param rootUrl root url to load from
     * @returns a promise which completes when objects have been loaded to the scene
     */
    // eslint-disable-next-line no-restricted-syntax
    loadAsync(scene, data, rootUrl) {
        //Get the 3D model
        // eslint-disable-next-line github/no-then
        return this.importMeshAsync(null, scene, data, rootUrl).then(() => {
            // return void
        });
    }
    /**
     * Load into an asset container.
     * @param scene The scene to load into
     * @param data The data to import
     * @param rootUrl The root url for scene and resources
     * @returns The loaded asset container
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    loadAssetContainerAsync(scene, data, rootUrl) {
        const container = new AssetContainer(scene);
        this._assetContainer = container;
        return (this.importMeshAsync(null, scene, data, rootUrl)
            // eslint-disable-next-line github/no-then
            .then((result) => {
            result.meshes.forEach((mesh) => container.meshes.push(mesh));
            result.meshes.forEach((mesh) => {
                const material = mesh.material;
                if (material) {
                    // Materials
                    if (container.materials.indexOf(material) == -1) {
                        container.materials.push(material);
                        // Textures
                        const textures = material.getActiveTextures();
                        textures.forEach((t) => {
                            if (container.textures.indexOf(t) == -1) {
                                container.textures.push(t);
                            }
                        });
                    }
                }
            });
            this._assetContainer = null;
            return container;
        })
            // eslint-disable-next-line github/no-then
            .catch((ex) => {
            this._assetContainer = null;
            throw ex;
        }));
    }
    /**
     * Read the OBJ file and create an Array of meshes.
     * Each mesh contains all information given by the OBJ and the MTL file.
     * i.e. vertices positions and indices, optional normals values, optional UV values, optional material
     * @param meshesNames defines a string or array of strings of the mesh names that should be loaded from the file
     * @param scene defines the scene where are displayed the data
     * @param data defines the content of the obj file
     * @param rootUrl defines the path to the folder
     * @returns the list of loaded meshes
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    _parseSolidAsync(meshesNames, scene, data, rootUrl) {
        let fileToLoad = ""; //The name of the mtlFile to load
        const materialsFromMTLFile = new MTLFileLoader();
        const materialToUse = [];
        const babylonMeshesArray = []; //The mesh for babylon
        // Sanitize data
        data = data.replace(/#.*$/gm, "").trim();
        // Main function
        const solidParser = new SolidParser(materialToUse, babylonMeshesArray, this._loadingOptions);
        solidParser.parse(meshesNames, data, scene, this._assetContainer, (fileName) => {
            fileToLoad = fileName;
        });
        // load the materials
        const mtlPromises = [];
        // Check if we have a file to load
        if (fileToLoad !== "" && !this._loadingOptions.skipMaterials) {
            //Load the file synchronously
            mtlPromises.push(new Promise((resolve, reject) => {
                this._loadMTL(fileToLoad, rootUrl, (dataLoaded) => {
                    try {
                        //Create materials thanks MTLLoader function
                        materialsFromMTLFile.parseMTL(scene, dataLoaded, rootUrl, this._assetContainer);
                        //Look at each material loaded in the mtl file
                        for (let n = 0; n < materialsFromMTLFile.materials.length; n++) {
                            //Three variables to get all meshes with the same material
                            let startIndex = 0;
                            const _indices = [];
                            let _index;
                            //The material from MTL file is used in the meshes loaded
                            //Push the indice in an array
                            //Check if the material is not used for another mesh
                            while ((_index = materialToUse.indexOf(materialsFromMTLFile.materials[n].name, startIndex)) > -1) {
                                _indices.push(_index);
                                startIndex = _index + 1;
                            }
                            //If the material is not used dispose it
                            if (_index === -1 && _indices.length === 0) {
                                //If the material is not needed, remove it
                                materialsFromMTLFile.materials[n].dispose();
                            }
                            else {
                                for (let o = 0; o < _indices.length; o++) {
                                    //Apply the material to the Mesh for each mesh with the material
                                    const mesh = babylonMeshesArray[_indices[o]];
                                    const material = materialsFromMTLFile.materials[n];
                                    mesh.material = material;
                                    if (!mesh.getTotalIndices()) {
                                        // No indices, we need to turn on point cloud
                                        material.pointsCloud = true;
                                    }
                                }
                            }
                        }
                        resolve();
                    }
                    catch (e) {
                        Tools.Warn(`Error processing MTL file: '${fileToLoad}'`);
                        if (this._loadingOptions.materialLoadingFailsSilently) {
                            resolve();
                        }
                        else {
                            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
                            reject(e);
                        }
                    }
                }, (pathOfFile, exception) => {
                    Tools.Warn(`Error downloading MTL file: '${fileToLoad}'`);
                    if (this._loadingOptions.materialLoadingFailsSilently) {
                        resolve();
                    }
                    else {
                        // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
                        reject(exception);
                    }
                });
            }));
        }
        //Return an array with all Mesh
        // eslint-disable-next-line github/no-then
        return Promise.all(mtlPromises).then(() => {
            const isLine = (mesh) => Boolean(mesh._internalMetadata?.["_isLine"] ?? false);
            // Iterate over the mesh, determine if it is a line mesh, clone or modify the material to line rendering.
            babylonMeshesArray.forEach((mesh) => {
                if (isLine(mesh)) {
                    let mat = mesh.material ?? new StandardMaterial(mesh.name + "_line", scene);
                    // If another mesh is using this material and it is not a line then we need to clone it.
                    const needClone = mat.getBindedMeshes().filter((e) => !isLine(e)).length > 0;
                    if (needClone) {
                        mat = mat.clone(mat.name + "_line") ?? mat;
                    }
                    mat.wireframe = true;
                    mesh.material = mat;
                    if (mesh._internalMetadata) {
                        mesh._internalMetadata["_isLine"] = undefined;
                    }
                }
            });
            return babylonMeshesArray;
        });
    }
}
/**
 * Defines if UVs are optimized by default during load.
 */
OBJFileLoader.OPTIMIZE_WITH_UV = true;
/**
 * Invert model on y-axis (does a model scaling inversion)
 */
OBJFileLoader.INVERT_Y = false;
/**
 * Include in meshes the vertex colors available in some OBJ files.  This is not part of OBJ standard.
 */
OBJFileLoader.IMPORT_VERTEX_COLORS = false;
/**
 * Compute the normals for the model, even if normals are present in the file.
 */
OBJFileLoader.COMPUTE_NORMALS = false;
/**
 * Optimize the normals for the model. Lighting can be uneven if you use OptimizeWithUV = true because new vertices can be created for the same location if they pertain to different faces.
 * Using OptimizehNormals = true will help smoothing the lighting by averaging the normals of those vertices.
 */
OBJFileLoader.OPTIMIZE_NORMALS = false;
/**
 * Defines custom scaling of UV coordinates of loaded meshes.
 */
OBJFileLoader.UV_SCALING = new Vector2(1, 1);
/**
 * Skip loading the materials even if defined in the OBJ file (materials are ignored).
 */
OBJFileLoader.SKIP_MATERIALS = false;
/**
 * When a material fails to load OBJ loader will silently fail and onSuccess() callback will be triggered.
 *
 * Defaults to true for backwards compatibility.
 */
OBJFileLoader.MATERIAL_LOADING_FAILS_SILENTLY = true;
/**
 * Loads assets without handedness conversions. This flag is for compatibility. Use it only if absolutely required. Defaults to false.
 */
OBJFileLoader.USE_LEGACY_BEHAVIOR = false;
//Add this loader into the register plugin
let _Registered = false;
/**
 * Registers the OBJFileLoader scene loader plugin.
 * Safe to call multiple times; only the first call has an effect.
 */
function RegisterOBJFileLoader() {
    if (_Registered) {
        return;
    }
    _Registered = true;
    RegisterSceneLoaderPlugin(new OBJFileLoader());
}

/**
 * Re-exports the pure implementation and applies the runtime registration side effect.
 * Import "./objFileLoader.pure" for tree-shakeable, side-effect-free usage.
 */
RegisterOBJFileLoader();

export { OBJFileLoader, RegisterOBJFileLoader };
//# sourceMappingURL=objFileLoader-7bpqMs3Z.esm.js.map