Reference Source

src/parsers/parse3DXMLIntoXKTModel.js

import {ZIPArchive} from "./ZIPArchive.js";
import {math} from "../lib/math.js";

const supportedSchemas = ["4.2"];

/**
 * @desc Loads 3DXML into an {@link XKTModel}.
 *
 * Supports 3DXML Schema 4.2.
 *
 * @param {Object} params Parsing parameters.
 * @param {ArrayBuffer} params.data 3DXML BLOB data.
 * @param {DOMParser} params.domParser A DOMParser implementation (eg. ````xmldom````), required when
 * we're not running in a browser and ````window.DOMParser```` is not available.
 * @param {XKTModel} params.xktModel XKTModel to parse into.
 * @param {Boolean} [params.autoNormals=false] When true, the parser will ignore the 3DXML geometry normals, and the 3DXML
 * data will rely on the xeokit ````Viewer```` to automatically generate them. This has the limitation that the
 * normals will be face-aligned, and therefore the ````Viewer```` will only be able to render a flat-shaded representation
 * of the 3DXML model. This is ````false```` by default because CAD models tend to prefer smooth shading.
 * @param {Object} [params.stats] Collects statistics.
 * @param {function} [params.log] Logging callback.
 */
function parse3DXMLIntoXKTModel({data, domParser, xktModel, autoNormals = false, stats = {}, log}) {

    return new Promise(function (resolve, reject) {

        const isBrowser = (typeof window !== 'undefined');

        if (isBrowser) {
            domParser = new DOMParser();

        } else if (!domParser) {
            reject("Config expected: domParser (needed when running in node.js)");
            return;
        }

        if (!data) {
            reject("Config expected: data");
            return;
        }

        if (!xktModel) {
            reject("Config expected: xktModel");
            return;
        }

        const zipArchive = new ZIPArchive(domParser);

        zipArchive.init(data).then(() => {

            const rootMetaObjectId = math.createUUID();

            xktModel.createMetaObject({
                metaObjectId: rootMetaObjectId,
                metaObjectType: "Model",
                metaObjectName: "Model"
            });

            const modelMetaObjectId = math.createUUID();

            xktModel.createMetaObject({
                metaObjectId: modelMetaObjectId,
                metaObjectType: "3DXML",
                metaObjectName: "3DXML",
                parentMetaObjectId: rootMetaObjectId
            });

            const ctx = {
                rootMetaObjectId: modelMetaObjectId,
                zipArchive: zipArchive,
                edgeThreshold: 10,
                xktModel: xktModel,
                autoNormals: autoNormals,
                info: {
                    references: {}
                },
                log: (msg) => {
                    if (log) {
                        log(msg);
                    }
                },
                warn: (msg) => {
                    if (log) {
                        log("Warning: " + msg);
                    }
                },
                error: (msg) => {
                    if (log) {
                        log("Error: " + msg);
                    }
                },
                nextId: 0,
                materials: {},
                stats: {
                    numObjects: 0,
                    numGeometries: 0,
                    numTriangles: 0,
                    numVertices: 0,

                    sourceFormat: "3DXML",
                    schemaVersion: "",
                    title: "",
                    author: "",
                    created: ""
                }
            };

            parseDocument(ctx).then(() => {
                ctx.log("Converted drawable objects: " + ctx.stats.numObjects);
                ctx.log("Converted geometries: " + ctx.stats.numGeometries);
                ctx.log("Converted triangles: " + ctx.stats.numTriangles);
                ctx.log("Converted vertices: " + ctx.stats.numVertices);

                if (stats) {
                    stats.numTriangles = ctx.stats.numTriangles;
                    stats.numVertices = ctx.stats.numVertices;
                    stats.numObjects = ctx.stats.numObjects;
                    stats.numGeometries = ctx.stats.numGeometries;
                }

                resolve();
            });


        }, (errMsg) => {
            reject(errMsg);
        });
    });
}

async function parseDocument(ctx) {
    const files = await ctx.zipArchive.getFile("Manifest.xml");
    const node = files.json;
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Manifest":
                await parseManifest(ctx, child);
                break;
        }
    }
}

async function parseManifest(ctx, manifest) {
    const children = manifest.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Root":
                const rootFileSrc = child.children[0];
                const files = await ctx.zipArchive.getFile(rootFileSrc);
                const json = files.json;
                await parseRoot(ctx, json);
                break;
        }
    }
}

async function parseRoot(ctx, node) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Model_3dxml":
                await parseModel(ctx, child);
                break;
        }
    }
}

async function parseModel(ctx, node) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Header":
                parseHeader(ctx, child);
                break;
            case "ProductStructure":
                await parseProductStructure(ctx, child);
                break;
            case "DefaultView":
                parseDefaultView(ctx, child);
                break;
        }
    }
}

function parseHeader(ctx, node) {
    const children = node.children;
    const metaData = {};
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "SchemaVersion":
                ctx.stats.schemaVersion = child.children[0];
                if (!isSchemaVersionSupported(ctx, ctx.stats.schemaVersion)) {
                    ctx.error("3DXML schema version not supported: " + ctx.stats.schemaVersion + " - supported versions are: " + supportedSchemas.join(","));
                } else {
                    ctx.log("Parsing 3DXML schema version: " + ctx.stats.schemaVersion);
                }
                break;
            case "Title":
                ctx.stats.title = child.children[0];
                break;
            case "Author":
                ctx.stats.author = child.children[0];
                break;
            case "Created":
                ctx.stats.created = child.children[0];
                break;
        }
    }
}

function isSchemaVersionSupported(ctx, schemaVersion) {
    for (let i = 0, len = supportedSchemas.length; i < len; i++) {
        if (schemaVersion === supportedSchemas[i]) {
            return true;
        }
    }
    return false;
}

async function parseProductStructure(ctx, productStructureNode) {

    const referenceReps = await parseReferenceReps(ctx, productStructureNode);

    // Parse out an intermediate scene DAG representation, that we can then
    // recursive descend through to build the XKTModel.

    const children = productStructureNode.children;

    const reference3Ds = {};
    const instanceReps = {};
    const instance3Ds = {};

    // Map all the elements

    for (let i = 0, len = children.length; i < len; i++) {

        const child = children[i];

        let isAggregatedBy;
        let isInstanceOf;
        let relativeMatrix;

        switch (child.type) {
            case "Reference3D":
                reference3Ds[child.id] = {
                    type: "Reference3D",
                    id: child.id,
                    name: child.name,
                    instance3Ds: {},
                    instanceReps: {}
                };
                break;

            case "InstanceRep":
                for (let j = 0, lenj = child.children.length; j < lenj; j++) {
                    const child2 = child.children[j];
                    switch (child2.type) {
                        case "IsAggregatedBy":
                            isAggregatedBy = child2.children[0];
                            break;
                        case "IsInstanceOf":
                            isInstanceOf = child2.children[0];
                            break;
                    }
                }
                instanceReps[child.id] = {
                    type: "InstanceRep",
                    id: child.id,
                    name: child.name,
                    isAggregatedBy: isAggregatedBy,
                    isInstanceOf: isInstanceOf,
                    referenceReps: {}
                };
                break;

            case "Instance3D":
                for (let j = 0, lenj = child.children.length; j < lenj; j++) {
                    const child2 = child.children[j];
                    switch (child2.type) {
                        case "IsAggregatedBy":
                            isAggregatedBy = child2.children[0];
                            break;
                        case "IsInstanceOf":
                            isInstanceOf = child2.children[0];
                            break;
                        case "RelativeMatrix":
                            relativeMatrix = child2.children[0];
                            break;
                    }
                }
                instance3Ds[child.id] = {
                    type: "Instance3D",
                    id: child.id,
                    name: child.name,
                    isAggregatedBy: isAggregatedBy,
                    isInstanceOf: isInstanceOf,
                    relativeMatrix: relativeMatrix,
                    reference3Ds: {}
                };
                break;
        }
    }

    // Connect Reference3Ds to the Instance3Ds they aggregate

    for (let id in instance3Ds) {
        const instance3D = instance3Ds[id];
        const reference3D = reference3Ds[instance3D.isAggregatedBy];
        if (reference3D) {
            reference3D.instance3Ds[instance3D.id] = instance3D;
        } else {
        }
    }

    // Connect Instance3Ds to the Reference3Ds they instantiate

    for (let id in instance3Ds) {
        const instance3D = instance3Ds[id];
        const reference3D = reference3Ds[instance3D.isInstanceOf];
        instance3D.reference3Ds[reference3D.id] = reference3D;
        reference3D.instance3D = instance3D;
    }

    // Connect InstanceReps to the ReferenceReps they instantiate

    for (let id in instanceReps) {
        const instanceRep = instanceReps[id];
        const referenceRep = referenceReps[instanceRep.isInstanceOf];
        if (referenceRep) {
            instanceRep.referenceReps[referenceRep.id] = referenceRep;
        }
    }

    // Connect Reference3Ds to the InstanceReps they aggregate

    for (let id in instanceReps) {
        const instanceRep = instanceReps[id];
        const reference3D = reference3Ds[instanceRep.isAggregatedBy];
        if (reference3D) {
            reference3D.instanceReps[instanceRep.id] = instanceRep;
        }
    }

    // Find the root Reference3D

    const parentMatrix = math.identityMat4();

    for (let id in reference3Ds) {
        const reference3D = reference3Ds[id];
        if (!reference3D.instance3D) {
            parseReference3D(ctx, reference3D, ctx.rootMetaObjectId, parentMatrix);
            return;
        }
    }

    ctx.error("No root Reference3D element found in this modelNode - can't load 3DXML file.");

}

function parseInstance3D(ctx, instance3D, parentMetaObjectId, parentMatrix) {

    const objectId = ctx.nextId++;

    const rotationMatrix = math.identityMat4();
    const translationMatrix = math.identityMat4();
    const localMatrix = math.identityMat4();
    const worldMatrix = math.identityMat4();

    if (instance3D.relativeMatrix) {

        const relativeMatrix = parseFloatArray(instance3D.relativeMatrix, 12);

        const translate = [relativeMatrix[9], relativeMatrix[10], relativeMatrix[11]];

        math.translationMat4v(translate, translationMatrix)
        math.mat3ToMat4(relativeMatrix.slice(0, 9), rotationMatrix);
        math.mulMat4(rotationMatrix, translationMatrix, localMatrix);

        math.mulMat4(parentMatrix, localMatrix, worldMatrix);

        ctx.xktModel.createMetaObject({
            metaObjectId: objectId,
            metaObjectType: "Default",
            metaObjectName: instance3D.name,
            parentMetaObjectId: parentMetaObjectId
        });

        for (let id in instance3D.reference3Ds) {
            parseReference3D(ctx, instance3D.reference3Ds[id], objectId, worldMatrix);
        }

    } else {

        ctx.xktModel.createMetaObject({
            metaObjectId: objectId,
            metaObjectType: "Default",
            metaObjectName: instance3D.name,
            parentMetaObjectId: parentMetaObjectId
        });

        for (let id in instance3D.reference3Ds) {
            parseReference3D(ctx, instance3D.reference3Ds[id], objectId, parentMatrix);
        }
    }
}

function parseReference3D(ctx, reference3D, parentMetaObjectId, parentMatrix) {
    for (let id in reference3D.instance3Ds) {
        parseInstance3D(ctx, reference3D.instance3Ds[id], parentMetaObjectId, parentMatrix);
    }
    for (let id in reference3D.instanceReps) {
        parseInstanceRep(ctx, reference3D.instanceReps[id], parentMetaObjectId, parentMatrix);
    }
}

function parseInstanceRep(ctx, instanceRep, parentMetaObjectId, parentMatrix) {

    if (instanceRep.referenceReps) {

        for (let id in instanceRep.referenceReps) {

            const referenceRep = instanceRep.referenceReps[id];

            for (let id2 in referenceRep) {
                if (id2 === "id") {
                    continue; // HACK
                }
                const meshCfg = referenceRep[id2];
                const colorize = meshCfg.color;

                const meshId = "" + ctx.nextId++;
                const entityId = "" + ctx.nextId++;

                ctx.xktModel.createMesh({
                    meshId: meshId,
                    geometryId: meshCfg.geometryId,
                    matrix: parentMatrix,
                    color: colorize
                });

                ctx.xktModel.createEntity({
                    entityId: entityId,
                    meshIds: [meshId]
                });

                ctx.stats.numObjects++;

                ctx.xktModel.createMetaObject({
                    metaObjectId: entityId,
                    metaObjectType: "Default",
                    metaObjectName: instanceRep.name,
                    parentMetaObjectId: parentMetaObjectId
                });
            }
        }
    }
}

async function parseReferenceReps(ctx, node) {
    const referenceReps = {};
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "ReferenceRep":
                if (child.associatedFile) {
                    const src = stripURN(child.associatedFile);
                    const childId = child.id;
                    const file = await ctx.zipArchive.getFile(src);
                    const materialIds = file.xmlDoc.getElementsByTagName("MaterialId");
                    await loadCATMaterialRefDocuments(ctx, materialIds);
                    const referenceRep = {
                        id: childId
                    };
                    parse3DRepDocument(ctx, file.json, referenceRep);
                    referenceReps[childId] = referenceRep;
                }
                break;
        }
    }
    return referenceReps;
}

function parseDefaultView(ctx, node) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Viewpoint":
                const children2 = child.children;
                ctx.viewpoint = {};
                for (let i2 = 0, len2 = children2.length; i2 < len2; i2++) {
                    const child2 = children2[i];
                    switch (child2.type) {
                        case "Position":
                            ctx.viewpoint.eye = parseFloatArray(child2.children[0], 3);
                            break;
                        case "Sight":
                            ctx.viewpoint.look = parseFloatArray(child2.children[0], 3);
                            break;
                        case "Up":
                            ctx.viewpoint.up = parseFloatArray(child2.children[0], 3);
                            break;
                    }
                }
                break;
            case "DefaultViewProperty":
                break;
        }
    }
}

function parse3DRepDocument(ctx, node, result) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "XMLRepresentation":
                parseXMLRepresentation(ctx, child, result);
                break;
        }
    }
}

function parseXMLRepresentation(ctx, node, result) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Root":
                parse3DRepRoot(ctx, child, result);
                break;
        }
    }
}

function parse3DRepRoot(ctx, node, result) {
    switch (node["xsi:type"]) {
        case "BagRepType":
            break;
        case "PolygonalRepType":
            break;
    }
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Rep":
                parse3DRepRep(ctx, child, result);
                break;
        }
    }
}

function parse3DRepRep(ctx, node, result) {

    switch (node["xsi:type"]) {
        case "BagRepType":
            break;
        case "PolygonalRepType":
            break;
    }

    const meshesResult = {};
    const children = node.children;

    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Rep":
                parse3DRepRep(ctx, child, result);
                break;
            case "Edges": // Ignoring edges because we auto-generate them
                break;
            case "Faces":
                meshesResult.primitive = "triangles";
                parseFaces(ctx, child, meshesResult);
                break;
            case "VertexBuffer":
                parseVertexBuffer(ctx, child, meshesResult);
                break;
            case "SurfaceAttributes":
                parseSurfaceAttributes(ctx, child, meshesResult);
                break;
        }
    }

    if (meshesResult.positions) {

        const geometryId = "" + ctx.nextId++;

        ctx.xktModel.createGeometry({
            geometryId: geometryId,
            primitiveType: meshesResult.primitive,
            positions: meshesResult.positions,
            normals: meshesResult.normals,
            indices: meshesResult.indices,
        });

        result[geometryId] = {
            geometryId: geometryId,
            color: meshesResult.color || [1.0, 1.0, 1.0, 1.0],
            materialId: meshesResult.materialId
        };

        ctx.stats.numGeometries++;
        ctx.stats.numVertices += meshesResult.positions ? meshesResult.positions.length / 3 : 0;
        ctx.stats.numTriangles += meshesResult.indices ? meshesResult.indices.length / 3 : 0;
    }
}

function parseFaces(ctx, node, result) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Face":
                parseFace(ctx, child, result);
                break;
        }
    }
}

function parseFace(ctx, node, result) {
    const strips = node.strips;
    if (strips) {
        const arrays = parseIntArrays(strips);
        if (arrays.length > 0) {
            result.primitive = "triangles";
            const indices = [];
            for (let i = 0, len = arrays.length; i < len; i++) {
                const array = convertTriangleStrip(arrays[i]);
                for (let j = 0, lenj = array.length; j < lenj; j++) {
                    indices.push(array[j]);
                }
            }
            result.indices = indices; // TODO
        }
    } else {
        const triangles = node.triangles;
        if (triangles) {
            result.primitive = "triangles";
            result.indices = parseIntArray(triangles);
        }
    }
    const children = node.children; // Material
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "SurfaceAttributes":
                parseSurfaceAttributes(ctx, child, result);
                break;
        }
    }
}

function convertTriangleStrip(indices) {
    const ccw = false;
    const indices2 = [];
    for (let i = 0, len = indices.length; i < len - 2; i++) {
        if (ccw) {
            if (i & 1) { //
                indices2.push(indices[i]);
                indices2.push(indices[i + 1]);
                indices2.push(indices[i + 2]);
            } else {
                indices2.push(indices[i]);
                indices2.push(indices[i + 2]);
                indices2.push(indices[i + 1]);
            }
        } else {
            if (i & 1) { //
                indices2.push(indices[i]);
                indices2.push(indices[i + 2]);
                indices2.push(indices[i + 1]);
            } else {
                indices2.push(indices[i]);
                indices2.push(indices[i + 1]);
                indices2.push(indices[i + 2]);
            }
        }
    }
    return indices2;
}

function parseVertexBuffer(ctx, node, result) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Positions":
                result.positions = parseFloatArray(child.children[0], 3);
                break;
            case "Normals":
                if (!ctx.autoNormals) {
                    result.normals = parseFloatArray(child.children[0], 3);
                }
                break;
            case "TextureCoordinates": // TODO: Support dimension and channel?
                result.uv = parseFloatArray(child.children[0], 2);
                break;
        }
    }
}

function parseIntArrays(str) {
    const coordStrings = str.split(",");
    const array = [];
    for (let i = 0, len = coordStrings.length; i < len; i++) {
        const coordStr = coordStrings[i].trim();
        if (coordStr.length > 0) {
            const elemStrings = coordStr.trim().split(" ");
            const arr = new Int16Array(elemStrings.length);
            let arrIdx = 0;
            for (let j = 0, lenj = elemStrings.length; j < lenj; j++) {
                if (elemStrings[j] !== "") {
                    arr[arrIdx++] = parseInt(elemStrings[j]);
                }
            }
            array.push(arr);
        }
    }
    return array;
}

function parseFloatArray(str, numElems) {
    str = str.split(",");
    const arr = new Float64Array(str.length * numElems);
    let arrIdx = 0;
    for (let i = 0, len = str.length; i < len; i++) {
        const value = str[i].split(" ");
        for (let j = 0, lenj = value.length; j < lenj; j++) {
            if (value[j] !== "") {
                arr[arrIdx++] = parseFloat(value[j]);
            }
        }
    }
    return arr;
}

function parseIntArray(str) {
    str = str.trim().split(" ");
    const arr = new Int32Array(str.length);
    let arrIdx = 0;
    for (let i = 0, len = str.length; i < len; i++) {
        const value = str[i];
        arr[arrIdx++] = parseInt(value);
    }
    return arr;
}

function parseSurfaceAttributes(ctx, node, result) {
    result.color = [1, 1, 1, 1];
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Color":
                result.color[0] = child.red;
                result.color[1] = child.green;
                result.color[2] = child.blue;
                result.color[3] = child.alpha;
                break;
            case "MaterialApplication":
                const children2 = child.children;
                for (let j = 0, lenj = children2.length; j < lenj; j++) {
                    const child2 = children2[j];
                    switch (child2.type) {
                        case "MaterialId":
                            const materialId = getIDFromURI(child2.id);
                            const material = ctx.materials[materialId];
                            if (!material) {
                                ctx.error("material  not found: " + materialId);
                            }
                            result.materialId = materialId;
                            break;
                    }
                }
                break;
        }
    }
}

async function loadCATMaterialRefDocuments(ctx, materialIds) {
    const loaded = {};

    async function load(i) {
        if (i >= materialIds.length) {
            return;
        }
        const materialId = materialIds[i];
        const colonIdx = src.lastIndexOf(":");
        let src = materialId.id;
        if (colonIdx > 0) {
            src = src.substring(colonIdx + 1);
        }
        const hashIdx = src.lastIndexOf("#");
        if (hashIdx > 0) {
            src = src.substring(0, hashIdx);
        }
        if (!loaded[src]) {
            await loadCATMaterialRefDocument(ctx, src);
            loaded[src] = true;
            await load(i + 1);
        } else {
            await load(i + 1);
        }
    }

    await load(0);
}

async function loadCATMaterialRefDocument(ctx, src) { // Loads CATMaterialRef.3dxml
    const fileData = await ctx.zipArchive.getFile(src);
    await parseCATMaterialRefDocument(ctx, fileData.json);
}

async function parseCATMaterialRefDocument(ctx, node) { // Parse CATMaterialRef.3dxml
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        if (child.type === "Model_3dxml") {
            await parseModel_3dxml(ctx, child);
        }
    }
}

async function parseModel_3dxml(ctx, node) { // Parse CATMaterialRef.3dxml
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        if (child.type === "CATMaterialRef") {
            await parseCATMaterialRef(ctx, child);
        }
    }
}

async function parseCATMaterialRef(ctx, node) {

    const domainToReferenceMap = {};
    const children = node.children;

    for (let j = 0, lenj = children.length; j < lenj; j++) {
        const child2 = children[j];
        switch (child2.type) {
            case "MaterialDomainInstance":
                let isAggregatedBy;
                let isInstanceOf;
                for (let k = 0, lenk = child2.children.length; k < lenk; k++) {
                    const child3 = child2.children[k];
                    switch (child3.type) {
                        case "IsAggregatedBy":
                            isAggregatedBy = child3.children[0];
                            break;
                        case "IsInstanceOf":
                            isInstanceOf = child3.children[0];
                            break;
                    }
                }
                domainToReferenceMap[isInstanceOf] = isAggregatedBy;
                break;
        }
    }

    for (let j = 0, lenj = children.length; j < lenj; j++) {
        const child2 = children[j];
        switch (child2.type) {
            case "MaterialDomain":
                if (child2.associatedFile) {
                    const childId = child2.id;
                    const src = stripURN(child2.associatedFile);
                    const fileData = await ctx.zipArchive.getFile(src);
                    ctx.materials[domainToReferenceMap[childId]] = parseMaterialDefDocument(ctx, fileData.json);
                }
        }
    }
}

function parseMaterialDefDocument(ctx, node) {
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "Osm":
                return parseMaterialDefDocumentOsm(ctx, child);
        }
    }
    return {};
}

function parseMaterialDefDocumentOsm(ctx, node) {
    const materialCfg = {};
    const children = node.children;
    for (let i = 0, len = children.length; i < len; i++) {
        const child = children[i];
        switch (child.type) {
            case "RenderingRootFeature":
                break;
            case "Feature":
                if (child.Alias === "RenderingFeature") {
                    // Parse the coefficients, then parse the colors, scaling those by their coefficients.
                    const coeffs = {};
                    const children2 = child.children;
                    for (let j = 0, lenj = children2.length; j < lenj; j++) {
                        const child2 = children2[j];
                        switch (child2.Name) {
                            case "AmbientCoef":
                                coeffs.ambient = parseFloat(child2.Value);
                                break;
                            case "DiffuseCoef":
                                coeffs.diffuse = parseFloat(child2.Value);
                                break;
                            case "EmissiveCoef":
                                coeffs.emissive = parseFloat(child2.Value);
                                break;
                            case "SpecularExponent":
                                coeffs.specular = parseFloat(child2.Value);
                                break;
                        }
                    }
                    for (let j = 0, lenj = children2.length; j < lenj; j++) {
                        const child2 = children2[j];
                        switch (child2.Name) {
                            case "AmbientColor":
                                materialCfg.ambient = parseRGB(child2.Value, coeffs.ambient);
                                break;
                            case "DiffuseColor":
                                materialCfg.diffuse = parseRGB(child2.Value, coeffs.diffuse);
                                break;
                            case "EmissiveColor":
                                materialCfg.emissive = parseRGB(child2.Value, coeffs.emissive);
                                break;
                            case "SpecularColor":
                                materialCfg.specular = parseRGB(child2.Value, coeffs.specular);
                                break;
                            case "Transparency":
                                const alpha = 1.0 - parseFloat(child2.Value); // Degree of transparency, not degree of opacity
                                if (alpha < 1.0) {
                                    materialCfg.alpha = alpha;
                                    materialCfg.alphaMode = "blend";
                                }
                                break;
                        }
                    }
                }
                break;
        }
    }
    return materialCfg;
}

function parseRGB(str, coeff) {
    coeff = (coeff !== undefined) ? coeff : 0.5;
    const openBracketIndex = str.indexOf("[");
    const closeBracketIndex = str.indexOf("]");
    str = str.substring(openBracketIndex + 1, closeBracketIndex - openBracketIndex);
    str = str.split(",");
    const arr = new Float32Array(str.length);
    let arrIdx = 0;
    for (let i = 0, len = str.length; i < len; i++) {
        const value = str[i].trim().split(" ");
        for (let j = 0, lenj = value.length; j < lenj; j++) {
            if (value[j] !== "") {
                arr[arrIdx++] = parseFloat(value[j]) * coeff;
            }
        }
    }
    return arr;
}

function stripURN(str) {
    const subStr = "urn:3DXML:";
    return (str.indexOf(subStr) === 0) ? str.substring(subStr.length) : str;
}

function getIDFromURI(str) {
    const hashIdx = str.lastIndexOf("#");
    return hashIdx !== -1 ? str.substring(hashIdx + 1) : str;
}

export {parse3DXMLIntoXKTModel};