src/plugins/XML3DLoaderPlugin/XML3DSceneGraphLoader.js
import {Node} from "../../viewer/scene/nodes/Node.js";
import {Mesh} from "../../viewer/scene/mesh/Mesh.js";
import {ReadableGeometry} from "../../viewer/scene/geometry/ReadableGeometry.js";
import {PhongMaterial} from "../../viewer/scene/materials/PhongMaterial.js";
import {MetallicMaterial} from "../../viewer/scene/materials/MetallicMaterial.js";
import {SpecularMaterial} from "../../viewer/scene/materials/SpecularMaterial.js";
import {LambertMaterial} from "../../viewer/scene/materials/LambertMaterial.js";
import {math} from "../../viewer/scene/math/math.js";
import {zipLib} from "./zipjs/zip.js";
import {zipExt} from "./zipjs/zip-ext.js";
const zip = zipLib.zip;
zipExt(zip);
const supportedSchemas = ["4.2"];
/**
* @private
*/
class XML3DSceneGraphLoader {
constructor(plugin, cfg = {}) {
/**
* Supported 3DXML schema versions
* @property supportedSchemas
* @type {string[]}
*/
this.supportedSchemas = supportedSchemas;
this._xrayOpacity = 0.7;
this._src = null;
this._options = cfg;
/**
* Default viewpoint, containing eye, look and up vectors.
* Only defined if found in the 3DXML file.
* @property viewpoint
* @type {Number[]}
*/
this.viewpoint = null;
if (!cfg.workerScriptsPath) {
plugin.error("Config expected: workerScriptsPath");
return
}
zip.workerScriptsPath = cfg.workerScriptsPath;
this.src = cfg.src;
this.xrayOpacity = 0.7;
this.displayEffect = cfg.displayEffect;
this.createMetaModel = cfg.createMetaModel;
}
load(plugin, modelNode, src, options, ok, error) {
switch (options.materialType) {
case "MetallicMaterial":
modelNode._defaultMaterial = new MetallicMaterial(modelNode, {
baseColor: [1, 1, 1],
metallic: 0.6,
roughness: 0.6
});
break;
case "SpecularMaterial":
modelNode._defaultMaterial = new SpecularMaterial(modelNode, {
diffuse: [1, 1, 1],
specular: math.vec3([1.0, 1.0, 1.0]),
glossiness: 0.5
});
break;
default:
modelNode._defaultMaterial = new PhongMaterial(modelNode, {
reflectivity: 0.75,
shiness: 100,
diffuse: [1, 1, 1]
});
}
modelNode._wireframeMaterial = new LambertMaterial(modelNode, {
color: [0, 0, 0],
lineWidth: 2
});
var spinner = modelNode.scene.canvas.spinner;
spinner.processes++;
load3DXML(plugin, modelNode, src, options, function () {
spinner.processes--;
if (ok) {
ok();
}
modelNode.fire("loaded", true, false);
},
function (msg) {
spinner.processes--;
modelNode.error(msg);
if (error) {
error(msg);
}
/**
Fired whenever this XML3D fails to load the 3DXML file
specified by {@link XML3D/src}.
@event error
@param msg {String} Description of the error
*/
modelNode.fire("error", msg);
},
function (err) {
console.log("Error, Will Robinson: " + err);
});
}
}
var load3DXML = (function () {
return function (plugin, modelNode, src, options, ok, error) {
loadZIP(src, function (zip) { // OK
parse3DXML(plugin, zip, options, modelNode, ok, error);
},
error);
};
})();
var parse3DXML = (function () {
return function (plugin, zip, options, modelNode, ok) {
var ctx = {
plugin: plugin,
zip: zip,
edgeThreshold: 30, // Guess at degrees of normal deviation between adjacent tris below which we remove edge between them
materialType: options.materialType,
scene: modelNode.scene,
modelNode: modelNode,
info: {
references: {}
},
materials: {}
};
if (options.createMetaModel) {
ctx.metaModelData = {
modelId: modelNode.id,
metaObjects: [{
name: modelNode.id,
type: "Default",
id: modelNode.id
}]
};
}
modelNode.scene.loading++; // Disables (re)compilation
parseDocument(ctx, function () {
if (ctx.metaModelData) {
plugin.viewer.metaScene.createMetaModel(modelNode.id, ctx.metaModelData);
}
modelNode.scene.loading--; // Re-enables (re)compilation
ok();
});
};
function parseDocument(ctx, ok) {
ctx.zip.getFile("Manifest.xml", function (xmlDoc, json) {
var node = json;
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Manifest":
parseManifest(ctx, child, ok);
break;
}
}
});
}
function parseManifest(ctx, manifest, ok) {
var children = manifest.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Root":
var rootFileSrc = child.children[0];
ctx.zip.getFile(rootFileSrc, function (xmlDoc, json) {
parseRoot(ctx, json, ok);
});
break;
}
}
}
function parseRoot(ctx, node, ok) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Model_3dxml":
parseModel(ctx, child, ok);
break;
}
}
}
function parseModel(ctx, node, ok) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Header":
parseHeader(ctx, child);
break;
case "ProductStructure":
parseProductStructure(ctx, child, ok);
break;
case "DefaultView":
parseDefaultView(ctx, child);
break;
}
}
}
function parseHeader(ctx, node) {
var children = node.children;
var metaData = {};
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "SchemaVersion":
metaData.schemaVersion = child.children[0];
if (!isSchemaVersionSupported(ctx, metaData.schemaVersion)) {
ctx.plugin.error("Schema version not supported: " + metaData.schemaVersion + " - supported versions are: " + supportedSchemas.join(","));
} else {
//ctx.plugin.log("Parsing schema version: " + metaData.schemaVersion);
}
break;
case "Title":
metaData.title = child.children[0];
break;
case "Author":
metaData.author = child.children[0];
break;
case "Created":
metaData.created = child.children[0];
break;
}
}
ctx.modelNode.meta = metaData;
}
function isSchemaVersionSupported(ctx, schemaVersion) {
for (var i = 0, len = supportedSchemas.length; i < len; i++) {
if (schemaVersion === supportedSchemas[i]) {
return true;
}
}
return false;
}
function parseProductStructure(ctx, productStructureNode, ok) {
parseReferenceReps(ctx, productStructureNode, function (referenceReps) {
// Parse out an intermediate scene DAG representation, that we can then
// recursive descend through to build a xeokit Object hierarchy.
var children = productStructureNode.children;
var reference3Ds = {};
var instanceReps = {};
var instance3Ds = {};
var rootNode;
var nodes = {};
// Map all the elements
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Reference3D":
reference3Ds[child.id] = {
type: "Reference3D",
id: child.id,
name: child.name,
instance3Ds: {},
instanceReps: {}
};
break;
case "InstanceRep":
var isAggregatedBy;
var isInstanceOf;
var relativeMatrix;
for (var j = 0, lenj = child.children.length; j < lenj; j++) {
var 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":
var isAggregatedBy;
var isInstanceOf;
var relativeMatrix;
for (var j = 0, lenj = child.children.length; j < lenj; j++) {
var 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 (var id in instance3Ds) {
var instance3D = instance3Ds[id];
var reference3D = reference3Ds[instance3D.isAggregatedBy];
if (reference3D) {
reference3D.instance3Ds[instance3D.id] = instance3D;
} else {
alert("foo")
}
}
// Connect Instance3Ds to the Reference3Ds they instantiate
for (var id in instance3Ds) {
var instance3D = instance3Ds[id];
var reference3D = reference3Ds[instance3D.isInstanceOf];
instance3D.reference3Ds[reference3D.id] = reference3D;
reference3D.instance3D = instance3D;
}
// Connect InstanceReps to the ReferenceReps they instantiate
for (var id in instanceReps) {
var instanceRep = instanceReps[id];
var referenceRep = referenceReps[instanceRep.isInstanceOf];
if (referenceRep) {
instanceRep.referenceReps[referenceRep.id] = referenceRep;
}
}
// Connect Reference3Ds to the InstanceReps they aggregate
for (var id in instanceReps) {
var instanceRep = instanceReps[id];
var reference3D = reference3Ds[instanceRep.isAggregatedBy];
if (reference3D) {
reference3D.instanceReps[instanceRep.id] = instanceRep;
}
}
function parseReference3D(ctx, reference3D, group) {
//ctx.plugin.log("parseReference3D( " + reference3D.id + " )");
for (var id in reference3D.instance3Ds) {
parseInstance3D(ctx, reference3D.instance3Ds[id], group);
}
for (var id in reference3D.instanceReps) {
parseInstanceRep(ctx, reference3D.instanceReps[id], group);
}
}
function parseInstance3D(ctx, instance3D, group) {
//ctx.plugin.log("parseInstance3D( " + instance3D.id + " )");
if (instance3D.relativeMatrix) {
var matrix = parseFloatArray(instance3D.relativeMatrix, 12);
var translate = [matrix[9], matrix[10], matrix[11]];
var mat3 = matrix.slice(0, 9); // Rotation matrix
var mat4 = math.mat3ToMat4(mat3, math.identityMat4()); // Convert rotation matrix to 4x4
var childGroup = new Node(ctx.modelNode, {
position: translate
});
if (ctx.metaModelData) {
ctx.metaModelData.metaObjects.push({
id: childGroup.id,
type: "Default",
name: instance3D.name,
parent: group ? group.id : ctx.modelNode.id
});
}
if (group) {
group.addChild(childGroup, true);
} else {
ctx.modelNode.addChild(childGroup, true);
}
group = childGroup;
childGroup = new Node(ctx.modelNode, {
matrix: mat4
});
if (ctx.metaModelData) {
ctx.metaModelData.metaObjects.push({
id: childGroup.id,
type: "Default",
name: instance3D.name,
parent: group ? group.id : ctx.modelNode.id
});
}
group.addChild(childGroup, true);
group = childGroup;
} else {
var childGroup = new Node(ctx.modelNode, {});
if (ctx.metaModelData) {
ctx.metaModelData.metaObjects.push({
id: childGroup.id,
type: "Default",
name: instance3D.name,
parent: group ? group.id : ctx.modelNode.id
});
}
if (group) {
group.addChild(childGroup, true);
} else {
ctx.modelNode.addChild(childGroup, true);
}
group = childGroup;
}
for (var id in instance3D.reference3Ds) {
parseReference3D(ctx, instance3D.reference3Ds[id], group);
}
}
function parseInstanceRep(ctx, instanceRep, group) {
//ctx.plugin.log("parseInstanceRep( " + instanceRep.id + " )");
if (instanceRep.referenceReps) {
for (var id in instanceRep.referenceReps) {
var referenceRep = instanceRep.referenceReps[id];
for (var id2 in referenceRep) {
if (id2 === "id") {
continue; // HACK
}
var meshCfg = referenceRep[id2];
var lines = meshCfg.geometry.primitive === "lines";
var material = lines ? ctx.modelNode._wireframeMaterial : (meshCfg.materialId ? ctx.materials[meshCfg.materialId] : null);
var colorize = meshCfg.color;
var mesh = new Mesh(ctx.modelNode, {
isObject: true,
geometry: meshCfg.geometry,
material: material || ctx.modelNode._defaultMaterial,
colorize: colorize,
backfaces: false
});
if (ctx.metaModelData) {
ctx.metaModelData.metaObjects.push({
id: mesh.id,
type: "Default",
name: instanceRep.name,
parent: group ? group.id : ctx.modelNode.id
});
}
if (group) {
group.addChild(mesh, true);
} else {
ctx.modelNode.addChild(mesh, true);
}
mesh.colorize = colorize; // HACK: Mesh has inherited modelNode's colorize state, so we need to restore it (we'd better not modify colorize on the modelNode).
}
}
}
}
// Find the root Reference3D
for (var id in reference3Ds) {
var reference3D = reference3Ds[id];
if (!reference3D.instance3D) {
parseReference3D(ctx, reference3D, null); // HACK: Assuming that root has id == "1"
ok();
return;
}
}
alert("No root Reference3D element found in this modelNode - can't load.");
ok();
});
}
function parseIntArray(str) {
var parts = str.trim().split(" ");
var result = new Int32Array(parts.length);
for (var i = 0; i < parts.length; i++) {
result[i] = parseInt(parts[i]);
}
return result;
}
function parseReferenceReps(ctx, node, ok) {
var referenceReps = {};
var children = node.children;
var numToLoad = 0;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
if (child.type === "ReferenceRep") {
numToLoad++;
}
}
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "ReferenceRep":
if (child.associatedFile) {
var src = stripURN(child.associatedFile);
(function () {
var childId = child.id;
ctx.zip.getFile(src, function (xmlDoc, json) {
var materialIds = xmlDoc.getElementsByTagName("MaterialId");
loadCATMaterialRefDocuments(ctx, materialIds, function () {
// ctx.plugin.log("reference loaded: " + src);
var referenceRep = {
id: childId
};
parse3DRepDocument(ctx, json, referenceRep);
referenceReps[childId] = referenceRep;
if (--numToLoad === 0) {
ok(referenceReps);
}
});
},
function (error) {
// TODO:
});
})();
}
break;
}
}
}
function parseDefaultView(ctx, node) {
// ctx.plugin.log("parseDefaultView");
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Viewpoint":
var children2 = child.children;
ctx.modelNode.viewpoint = {};
for (var i2 = 0, len2 = children2.length; i2 < len2; i2++) {
var child2 = children2[i];
switch (child2.type) {
case "Position":
ctx.modelNode.viewpoint.eye = parseFloatArray(child2.children[0], 3);
break;
case "Sight":
ctx.modelNode.viewpoint.look = parseFloatArray(child2.children[0], 3);
break;
case "Up":
ctx.modelNode.viewpoint.up = parseFloatArray(child2.children[0], 3);
break;
}
}
break;
case "DefaultViewProperty":
break;
}
}
}
function parse3DRepDocument(ctx, node, result) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "XMLRepresentation":
parseXMLRepresentation(ctx, child, result);
break;
}
}
}
function parseXMLRepresentation(ctx, node, result) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var 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;
}
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var 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;
}
var meshesResult = {
edgeThreshold: ctx.edgeThreshold || 30,
compressGeometry: true
};
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Rep":
parse3DRepRep(ctx, child, result);
break;
case "Edges":
// Ignoring edges because we auto-generate our own using xeokit
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) {
var geometry = new ReadableGeometry(ctx.modelNode, meshesResult);
result[geometry.id] = {
geometry: geometry,
color: meshesResult.color || [1.0, 1.0, 1.0, 1.0],
materialId: meshesResult.materialId
};
}
}
function parseEdges(ctx, node, result) {
result.positions = [];
result.indices = [];
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Polyline":
parsePolyline(ctx, child, result);
break;
}
}
}
function parsePolyline(ctx, node, result) {
var vertices = node.vertices;
if (vertices) {
var positions = parseFloatArray(vertices, 3);
if (positions.length > 0) {
var positionsOffset = result.positions.length / 3;
for (var i = 0, len = positions.length; i < len; i++) {
result.positions.push(positions[i]);
}
for (var i = 0, len = (positions.length / 3) - 1; i < len; i++) {
result.indices.push(positionsOffset + i);
result.indices.push(positionsOffset + i + 1);
}
}
}
}
function parseFaces(ctx, node, result) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Face":
parseFace(ctx, child, result);
break;
}
}
}
function parseFace(ctx, node, result) {
var strips = node.strips;
if (strips) {
// Triangle strips
var arrays = parseIntArrays(strips);
if (arrays.length > 0) {
result.primitive = "triangles";
var indices = [];
for (var i = 0, len = arrays.length; i < len; i++) {
var array = convertTriangleStrip(arrays[i]);
for (var j = 0, lenj = array.length; j < lenj; j++) {
indices.push(array[j]);
}
}
result.indices = indices; // TODO
}
} else {
// Triangle meshes
var triangles = node.triangles;
if (triangles) {
result.primitive = "triangles";
result.indices = parseIntArray(triangles);
}
}
// Material
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "SurfaceAttributes":
parseSurfaceAttributes(ctx, child, result);
break;
}
}
}
function convertTriangleStrip(indices) {
var ccw = false;
var indices2 = [];
for (var 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) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Positions":
result.positions = parseFloatArray(child.children[0], 3);
break;
case "Normals":
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) {
var coordStrings = str.split(",");
var array = [];
for (var i = 0, len = coordStrings.length; i < len; i++) {
var coordStr = coordStrings[i].trim();
if (coordStr.length > 0) {
var elemStrings = coordStr.trim().split(" ");
var arr = new Int16Array(elemStrings.length);
var arrIdx = 0;
for (var 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(",");
var arr = new Float32Array(str.length * numElems);
var arrIdx = 0;
for (var i = 0, len = str.length; i < len; i++) {
var value = str[i];
value = value.split(" ");
for (var 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(" ");
var arr = new Int32Array(str.length);
var arrIdx = 0;
for (var i = 0, len = str.length; i < len; i++) {
var value = str[i];
arr[i] = parseInt(value);
}
return arr;
}
function parseSurfaceAttributes(ctx, node, result) {
result.color = [1, 1, 1, 1];
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var 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":
var children2 = child.children;
for (var j = 0, lenj = children2.length; j < lenj; j++) {
var child2 = children2[j];
switch (child2.type) {
case "MaterialId":
var materialId = getIDFromURI(child2.id);
var material = ctx.materials[materialId];
if (!material) {
ctx.plugin.error("material not found: " + materialId);
}
result.materialId = materialId;
break;
}
}
break;
}
}
}
})();
function loadCATMaterialRefDocuments(ctx, materialIds, ok) {
var loaded = {};
function load(i, done) {
if (i >= materialIds.length) {
ok();
return;
}
var materialId = materialIds[i];
var src = materialId.id;
var colonIdx = src.lastIndexOf(":");
if (colonIdx > 0) {
src = src.substring(colonIdx + 1);
}
var hashIdx = src.lastIndexOf("#");
if (hashIdx > 0) {
src = src.substring(0, hashIdx);
}
if (!loaded[src]) {
loadCATMaterialRefDocument(ctx, src, function () {
loaded[src] = true;
load(i + 1, done);
});
} else {
load(i + 1, done);
}
}
load(0, ok);
}
function loadCATMaterialRefDocument(ctx, src, ok) { // Loads CATMaterialRef.3dxml
ctx.zip.getFile(src, function (xmlDoc, json) {
parseCATMaterialRefDocument(ctx, json, ok);
});
}
function parseCATMaterialRefDocument(ctx, node, ok) { // Parse CATMaterialRef.3dxml
// ctx.plugin.log("parseCATMaterialRefDocument");
var children = node.children;
var child;
for (var i = 0, len = children.length; i < len; i++) {
child = children[i];
if (child.type === "Model_3dxml") {
parseModel_3dxml(ctx, child, ok);
}
}
}
function parseModel_3dxml(ctx, node, ok) { // Parse CATMaterialRef.3dxml
// ctx.plugin.log("parseModel_3dxml");
var children = node.children;
var child;
for (var i = 0, len = children.length; i < len; i++) {
child = children[i];
if (child.type === "CATMaterialRef") {
parseCATMaterialRef(ctx, child, ok);
}
}
}
function parseCATMaterialRef(ctx, node, ok) {
var domainToReferenceMap = {};
var materials = {};
var result = {};
var children = node.children;
var child;
var numToLoad = 0;
for (var j = 0, lenj = children.length; j < lenj; j++) {
var child2 = children[j];
switch (child2.type) {
case "MaterialDomainInstance":
var isAggregatedBy;
var isInstanceOf;
for (var k = 0, lenk = child2.children.length; k < lenk; k++) {
var 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 (var j = 0, lenj = children.length; j < lenj; j++) {
var child2 = children[j];
switch (child2.type) {
case "MaterialDomain":
numToLoad++;
break;
}
}
// Now load them
for (var j = 0, lenj = children.length; j < lenj; j++) {
var child2 = children[j];
switch (child2.type) {
case "MaterialDomain":
if (child2.associatedFile) {
(function () {
var childId = child2.id;
var src = stripURN(child2.associatedFile);
ctx.zip.getFile(src, function (xmlDoc, json) {
// ctx.plugin.log("Material def loaded: " + src);
ctx.materials[domainToReferenceMap[childId]] = parseMaterialDefDocument(ctx, json);
if (--numToLoad === 0) {
// console.log("All ReferenceReps loaded.");
ok();
}
},
function (error) {
// TODO:
});
})();
}
break;
}
}
}
function parseMaterialDefDocument(ctx, node) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var child = children[i];
switch (child.type) {
case "Osm":
return parseMaterialDefDocumentOsm(ctx, child);
break;
}
}
}
function parseMaterialDefDocumentOsm(ctx, node) {
var children = node.children;
for (var i = 0, len = children.length; i < len; i++) {
var 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.
var coeffs = {};
var materialCfg = {};
var children2 = child.children;
var j;
var lenj;
var child2;
for (j = 0, lenj = children2.length; j < lenj; j++) {
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 (j = 0, lenj = children2.length; j < lenj; j++) {
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":
var alpha = 1.0 - parseFloat(child2.Value); // GOTCHA: Degree of transparency, not degree of opacity
if (alpha < 1.0) {
materialCfg.alpha = alpha;
materialCfg.alphaMode = "blend";
}
break;
}
}
var material;
switch (ctx.materialType) {
case "MetallicMaterial":
material = new MetallicMaterial(ctx.modelNode, {
baseColor: materialCfg.diffuse,
metallic: 0.7,
roughness: 0.5,
emissive: materialCfg.emissive,
alpha: materialCfg.alpha,
alphaMode: materialCfg.alphaMode
});
break;
case "SpecularMaterial":
material = new SpecularMaterial(ctx.modelNode, {
diffuse: materialCfg.diffuse,
specular: materialCfg.specular,
glossiness: 0.5,
emissive: materialCfg.emissive,
alpha: materialCfg.alpha,
alphaMode: materialCfg.alphaMode
});
break;
default:
material = new PhongMaterial(ctx.modelNode, {
reflectivity: 0.5,
ambient: materialCfg.ambient,
diffuse: materialCfg.diffuse,
specular: materialCfg.specular,
// shininess: node.shine,
emissive: materialCfg.emissive,
alphaMode: materialCfg.alphaMode,
alpha: node.alpha
});
}
return material;
}
break;
}
}
}
function parseRGB(str, coeff) {
coeff = (coeff !== undefined) ? coeff : 0.5;
var openBracketIndex = str.indexOf("[");
var closeBracketIndex = str.indexOf("]");
str = str.substring(openBracketIndex + 1, closeBracketIndex - openBracketIndex);
str = str.split(",");
var arr = new Float32Array(str.length);
var arrIdx = 0;
for (var i = 0, len = str.length; i < len; i++) {
var value = str[i];
value = value.trim().split(" ");
for (var j = 0, lenj = value.length; j < lenj; j++) {
if (value[j] !== "") {
arr[arrIdx++] = parseFloat(value[j]) * coeff;
}
}
}
return arr;
}
//----------------------------------------------------------------------------------------------------
/**
* Wraps zip.js to provide an in-memory ZIP archive representing the 3DXML file bundle.
*
* Allows us to pluck each file from it as XML and JSON.
*
* @constructor
*/
var ZIP = function () {
var reader;
var files = {};
/**
Loads this ZIP
@param src
@param ok
@param error
*/
this.load = function (src, ok, error) {
var self = this;
zip.createReader(new zip.HttpReader(src), function (reader) {
reader.getEntries(function (entries) {
if (entries.length > 0) {
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
files[entry.filename] = entry;
}
}
ok();
});
}, error);
};
/**
Gets a file as XML and JSON from this ZIP
@param src
@param ok
@param error
*/
this.getFile = function (src, ok, error) {
var entry = files[src];
if (!entry) {
var errMsg = "ZIP entry not found: " + src;
console.error(errMsg);
if (error) {
error(errMsg);
}
return;
}
entry.getData(new zip.TextWriter(), function (text) {
// Parse to XML
var parser = new DOMParser();
var xmlDoc = parser.parseFromString(text, "text/xml");
// Parse to JSON
var json = xmlToJSON(xmlDoc, {});
ok(xmlDoc, json);
});
};
function xmlToJSON(node, attributeRenamer) {
if (node.nodeType === node.TEXT_NODE) {
var v = node.nodeValue;
if (v.match(/^\s+$/) === null) {
return v;
}
} else if (node.nodeType === node.ELEMENT_NODE ||
node.nodeType === node.DOCUMENT_NODE) {
var json = {type: node.nodeName, children: []};
if (node.nodeType === node.ELEMENT_NODE) {
for (var j = 0; j < node.attributes.length; j++) {
var attribute = node.attributes[j];
var nm = attributeRenamer[attribute.nodeName] || attribute.nodeName;
json[nm] = attribute.nodeValue;
}
}
for (var i = 0; i < node.childNodes.length; i++) {
var item = node.childNodes[i];
var j = xmlToJSON(item, attributeRenamer);
if (j) json.children.push(j);
}
return json;
}
}
/**
Disposes of this ZIP
*/
this.destroy = function () {
reader.close(function () {
// onclose callback
});
};
};
function
loadZIP(src, ok, err) {
var zip = new ZIP();
zip.load(src, function () {
ok(zip);
}, function (errMsg) {
err("Error loading ZIP archive: " + errMsg);
})
}
function
stripURN(str) {
var subStr = "urn:3DXML:";
return (str.indexOf(subStr) === 0) ? str.substring(subStr.length) : str;
}
function
getIDFromURI(str) {
var hashIdx = str.lastIndexOf("#");
return hashIdx !== -1 ? str.substring(hashIdx + 1) : str;
}
export {XML3DSceneGraphLoader};