Hi all.
Recently I found a bug in the VMRLLoader available in threejs Github project. Due to the tricky way of program of this loader, I was unable to fix this issue.
The bug is related with the colors. Here more information about:
As WRL format (VMRL 2.0) is text tag-ordered based format, I tried to make my own convertor with nodejs.
The problem with my code is that it seems I am missing something multiplying matrices in cascade, but I cannot find my issue.
The idea of this conversor is to open a VRML file with hundreds|thousands of bufferGeometries and generate a simplified scene with only an object3d containing at maximum 3 kind of meshes: (Mesh, LineSegments, Points)
Could anybody have a look and help me to find the bug? I think it could be interesthing for people who will have to work with WRL format. (For eample people who needs to take models from CATIA)
Here you can see my code:
/*
*
******************************************************************************************************
* PROGRAM: WRL_Loader_v2.4.js *
* TARGET: convert WRL file to json *
* AUTHOR : Alejandro Insua Castro *
* DATE: 23/03/2019 *
* VERSION: 2.4 *
******************************************************************************************************
* LAST CHANGES: *
* 2019/04/06: Program modifications *
* *
******************************************************************************************************
* ARGUMENTS : N/A *
* *
******************************************************************************************************
* EXAMPLE OF USE: *
* set PATH=%HOME%\Documents\Viewer3d\node-v10.13.0-win-x64;%PATH% *
* set IF=%HOME%\Documents\input.wrl
* set OF=%HOME%\Documents\ouput.glb
* node --max-old-space-size=32000 WRL_Loader_v2.4.js %IF% %OF% *
* *
* *
******************************************************************************************************
* WHERE: *
******************************************************************************************************
* IF: Input folder where WRL are allocated *
* OF: Output File (GLB Draco compressed folder no simplification) *
******************************************************************************************************
*
*/
console.log("START.....");
// ---------------------------------------------------------------------------------------------------------
// - BEFORE REQUIREMENTS:
// ---------------------------------------------------------------------------------------------------------
var start = Date.now();
var before = Date.now();
// ---------------------------------------------------------------------------------------------------------
// - REQUIREMENTS:
// ---------------------------------------------------------------------------------------------------------
var fs = require("fs")
global.THREE = require('three');
console.log("Time to load libraries took", Date.now() - before, "ms");
// ---------------------------------------------------------------------------------------------------------
// - ARGUMENTS:
// ---------------------------------------------------------------------------------------------------------
var inFile = process.argv[2];
var outFile = process.argv[3];
// ---------------------------------------------------------------------------------------------------------
// - VARIABLES:
// ---------------------------------------------------------------------------------------------------------
var fileArray;
console.log("Input file: " + inFile);
console.log("Output file: " + outFile);
var before = Date.now();
// For control states:
var startToGather = false;
var inTransformLevelArray = [];
var inScale = false;
var inTranslation = false;
var inRotation = false;
var inChildrenLevelArray = [];
var inGroupLevelArray = [];
var inShape = false;
var inAppearance = false;
var inMaterial = false;
var inGeometry = false;
var inCoord = false;
var inPoint = false;
var inNormal = false;
var inVector = false;
var inCoordIndex = false;
// Array for saving material, geometries and matrix
var geometryArray = [];
var materialArray = [];
var matrixLevelArray = [];
matrixLevelArray.push(new THREE.Matrix4());
// For control depth level:
var transformationLevel = 0;
var groupLevel = 0;
var level = 0;
// Counters:
var geometryCount = 0;
//Current:
var materialName;
var geometryInfo;
var scale = new THREE.Vector3(1,1,1);
var quaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0,0,0), 1);
var translation = new THREE.Vector3(0,0,0);
var matrix;
var material = new THREE.MeshStandardMaterial();
// Debug:
var debug = true;
// ---------------------------------------------------------------------------------------------------------
// - START OF PROGRAM:
// ---------------------------------------------------------------------------------------------------------
var scene = LoadVMRL(inFile);
showLog(scene);
// ---------------------------------------------------------------------------------------------------------
// - END OF PROGRAM:
// ---------------------------------------------------------------------------------------------------------
// FUNCTIONS :
function showLog(msg) {
if (debug == true) {
console.log(msg);
}
}
function LoadVMRL(inFile) {
var fileArray = fs.readFileSync(inFile, 'utf8').toString().split("\r\n");
for (var i=0; i<fileArray.length; i++) {
var lineText = fileArray[i].trim().replace(/[,/[ ,[\t, ,\,]+/g, ' ').replace(/diffuseColor|emissiveColor/g, 'colorMaterial');
var lineFields = lineText.split(" ");
var firstField = lineFields[0];
if (firstField == "Transform") {
startToGather = true;
}
if (startToGather == true) {
if (isNumber(firstField) == false) {
showLog(firstField);
}
switch (firstField) {
case "Transform": // {}
transformationLevel += 1;
inTransformLevelArray[transformationLevel] = true;
scale = new THREE.Vector3(1,1,1);
quaternion = new THREE.Quaternion();
translation = new THREE.Vector3();
break;
case "scale": // val val val
scale = new THREE.Vector3(parseFloat(lineFields[1]), parseFloat(lineFields[2]), parseFloat(lineFields[3]));
break;
case "translation": // val val val
translation = new THREE.Vector3(parseFloat(lineFields[1]), parseFloat(lineFields[2]), parseFloat(lineFields[3]));
break;
case "rotation": // val val val val
quaternion = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3(parseFloat(lineFields[1]), parseFloat(lineFields[2]), parseFloat(lineFields[3])), parseFloat(lineFields[4]) );
break;
case "children": // []
level += 1;
inChildrenLevelArray[level] = true;
//showLog("level", level, "scale", scale, "quaternion", quaternion, "translation", translation);
if (inTransformLevelArray[transformationLevel] == true) {
showLog("transformationLevel", transformationLevel, "scale", scale, "quaternion", quaternion, "translation", translation);
matrix = new THREE.Matrix4().compose(translation, quaternion, scale);
showLog("matrixLevelArray:" , matrixLevelArray);
showLog("matrix:" , matrix);
var combinedMatrix = new THREE.Matrix4().identity();
matrixLevelArray[transformationLevel] = combinedMatrix.multiplyMatrices( matrixLevelArray[transformationLevel-1], matrix);
}
break;
case "Group": // {}
groupLevel += 1;
inGroupLevelArray[groupLevel] = true;
break;
case "DEF": // Same than Group
groupLevel += 1;
inGroupLevelArray[groupLevel] = true;
break;
case "Shape": // {}
inShape = true;
geometryInfo = {
name: undefined, // numero de mesh
type: undefined, // PointSet, IndexedLineSet, IndexedFaceSet
color: undefined, // color del material
matrix: matrixLevelArray[transformationLevel],
position: [], // arrayOf vertex need to compute normals
index: [] // array of Index
}
break;
case "appearance": //{}
inAppearance = true;
break;
case "material": // {}
inMaterial = true;
materialName = lineFields[2].replace('_', '');
showLog("level: ", level, "; matrix: ", matrixLevelArray[level]);
showLog("matrixLevelArray: ", matrixLevelArray);
geometryInfo.name = materialName;
// check if material is being defined or already exists:
if (lineFields[1] == "DEF") {
// Material being defined
materialArray[materialName] = undefined;
} else {
geometryInfo.color = materialArray[materialName];
}
geometryCount += 1;
break;
case "colorMaterial":
materialArray[materialName] = [parseFloat(lineFields[1]), parseFloat(lineFields[2]), parseFloat(lineFields[3])];
geometryInfo.color = materialArray[materialName];
break;
case "}":
if (inMaterial == true) {
inMaterial = false;
showLog("end of inMaterial");
} else if (inAppearance == true) {
inAppearance = false;
showLog("end of inAppearance");
} else if (inCoord == true ) {
inCoord = false;
showLog("end of inCoord");
} else if (inNormal == true) {
inNormal = false;
} else if (inGeometry == true) {
inGeometry = false;
showLog("end of inGeometry");
} else if (inShape == true) {
inShape = false;
showLog("end of inShape");
createGeometry(geometryInfo);
} else if (inGroupLevelArray[groupLevel] == true) {
inGroupLevelArray[groupLevel] = false;
showLog("end of inGroupLevelArray[" + groupLevel + "]");
groupLevel -= 1;
} else if (inTransformLevelArray[transformationLevel] == true) {
inTransformLevelArray[transformationLevel] = false;
showLog("end of inTransformLevelArray[" + transformationLevel + "]");
inTransformLevelArray[transformationLevel] == false
transformationLevel -= 1;
}
break;
case "geometry":
geometryInfo.type = lineFields[1];
break;
case "solid":
break;
case "coord":
inCoord = true;
break;
case "point":
inPoint = true;
break;
case "normal": // {}
inNormal = true;
break;
case "vector": // []
inVector = true;
break;
case "}coordIndex": // sometimes it can be found in this way
inCoord = false;
showLog("end of inCoord");
case "coordIndex":
inCoordIndex = true;
if (lineFields.length > 1) {
for (var k=1; k<lineFields.length; k++) {
if (isNumber(lineFields[k]) == true ) {
var value = parseInt(lineFields[k]);
if (value != -1) {
geometryInfo.index.push(value);
}
} else if (lineFields[k] == ']') {
inCoordIndex = false;
}
}
}
break;
case "]":
if (inPoint == true) {
inPoint = false;
showLog("end of inPoint");
} else if (inCoordIndex == true) {
inCoordIndex = false;
} else if (inVector == true) {
inVector = false;
} else if (inChildrenLevelArray[level] == true) {
inChildrenLevelArray[level] = false;
showLog("end of inChildrenLevelArray[" + level + "]");
level -= 1;
}
break;
default:
// Any other data
if (isNumber(firstField) == true) {
// numeric data, find if we are in Point or coordIndex Section I always take the 3 first values parsed to Float
if (inPoint == true){
geometryInfo.position.push(parseFloat(lineFields[0]));
geometryInfo.position.push(parseFloat(lineFields[1]));
geometryInfo.position.push(parseFloat(lineFields[2]));
} else if (inCoordIndex == true) {
//showLog("inserting index", geometryInfo.type);
for (var k=0; k<lineFields.length; k++) {
if (isNumber(lineFields[k]) == true ) {
var value = parseInt(lineFields[k]);
if (value != -1) {
geometryInfo.index.push(value);
}
} else if (lineFields[k] == ']') {
inCoordIndex = false;
}
}
}
}
break;
}
}
}
// types: PointSet, IndexedLineSet, IndexedFaceSet:
var name = getFileName(outFile).replace('.glb', '');
var scene2 = new THREE.Scene();
scene2.name = name;
var object3d = new THREE.Object3D();
object3d.name = "O_" + name;
scene2.add(object3d);
if (geometryArray["PointSet"] != undefined) {
var mergedPointGeos = mergeBufferGeometries(geometryArray["PointSet"]);
var pointMesh = new THREE.Points(mergedPointGeos, material);
pointMesh.name = "P_" + name;
object3d.add(pointMesh)
}
if (geometryArray["IndexedLineSet"] != undefined) {
var mergedLineGeos = mergeBufferGeometries(geometryArray["IndexedLineSet"]);
var lineMesh = new THREE.LineSegments(mergedLineGeos, material);
lineMesh.name = "L_" + name;
object3d.add(lineMesh)
//showLog(lineMesh);
}
if (geometryArray["IndexedFaceSet"] != undefined) {
var mergedMeshGeos = mergeBufferGeometries(geometryArray["IndexedFaceSet"]);
var faceMesh = new THREE.Mesh(mergedMeshGeos, material);
faceMesh.name = "M_" + name;
object3d.add(faceMesh)
}
return (scene2);
}
function getFileName(filePath) {
return filePath.split('\\').reverse()[0];
}
function getExtension(filePath) {
var i = filePath.lastIndexOf('.');
return (i < 0) ? '' : filePath.substr(i+1);
}
function getBaseName(filePath){
var i = filePath.lastIndexOf('\\');
var j = filePath.lastIndexOf('.');
return (filePath.substr(i+1, j-i-1));
}
function mergeBufferGeometries(geometries) {
var indexLength = 0,
verticesLength = 0,
attributesInfos = {},
geometriesInfos = [],
geometryInfo,
referenceAttributesKeys = [],
attributesKeys,
countVertices,
i,
j,
k;
var vertex_index = [];
var length_index = [];
// read the geometries and attributes information, calculate indexLength and verticesLength
if (geometries.length > 0 ){
for (i = 0; i < geometries.length; i++) {
length_index.push(indexLength);
vertex_index.push(verticesLength);
attributesKeys = Object.keys(geometries[i].attributes);
if ( geometries[i].attributes[attributesKeys[0]] != undefined ) {
geometryInfo = {
indexed: geometries[i].index !== null,
vertices: geometries[i].attributes[attributesKeys[0]].count
};
geometriesInfos.push(geometryInfo);
if (geometryInfo.indexed) {
indexLength += geometries[i].index.count;
} else {
indexLength += geometryInfo.vertices;
}
verticesLength += geometryInfo.vertices;
for (j = 0; j < attributesKeys.length; j++) {
if (referenceAttributesKeys.indexOf(attributesKeys[j]) === -1) {
referenceAttributesKeys.push(attributesKeys[j]);
attributesInfos[attributesKeys[j]] = {
array: null,
constructor: geometries[i].attributes[attributesKeys[j]].array.constructor,
itemSize: geometries[i].attributes[attributesKeys[j]].itemSize
};
}
}
}
}
// prepare the new BufferGeometry and its attributes
var newGeometry = new THREE.BufferGeometry();
indexArray = verticesLength > 0xFFFF ? new Uint32Array(indexLength) : new Uint16Array(indexLength);
for (i = 0; i < referenceAttributesKeys.length; i++) {
attributesInfos[referenceAttributesKeys[i]].array = new (attributesInfos[referenceAttributesKeys[i]].constructor)(
verticesLength * attributesInfos[referenceAttributesKeys[i]].itemSize
);
newGeometry.addAttribute(referenceAttributesKeys[i], new THREE.BufferAttribute(
attributesInfos[referenceAttributesKeys[i]].array,
attributesInfos[referenceAttributesKeys[i]].itemSize
));
}
// copy all the data in the new BufferGeometry
var offsetIndices = 0,
offsetVertices = 0,
offsetAttribute;
for (i = 0; i < geometries.length; i++) {
geometryInfo = geometriesInfos[i];
if (geometryInfo != undefined) {
if (geometryInfo.indexed) {
for (j = 0; j < geometries[i].index.count; j++) {
indexArray[offsetIndices + j] = offsetVertices + geometries[i].index.array[j];
}
offsetIndices += geometries[i].index.count;
} else {
for (j = 0; j < geometryInfo.vertices; j++) {
indexArray[offsetIndices + j] = offsetVertices + j;
}
offsetIndices += geometryInfo.vertices;
}
for (j = 0; j < referenceAttributesKeys.length; j++) {
offsetAttribute = offsetVertices * attributesInfos[referenceAttributesKeys[j]].itemSize;
if (geometries[i].attributes[referenceAttributesKeys[j]]) {
attributesInfos[referenceAttributesKeys[j]].array.set(geometries[i].attributes[referenceAttributesKeys[j]].array, offsetAttribute);
}
}
offsetVertices += geometryInfo.vertices;
}
}
newGeometry.setIndex(new THREE.BufferAttribute(indexArray, 1));
for (i=0; i<geometries.length; i++){
geometries[i].dispose();
}
geometries = [];
return newGeometry;
} else {
return new THREE.BufferGeometry();
}
}
function isNumber(str) {
var out = true;
if(isNaN(str)){
out = false;
}
return out;
}
function createGeometry(geometryInfo) {
showLog("geometryInfo:");
showLog("----------------------------------");
showLog(geometryInfo);
showLog(matrixLevelArray);
showLog("----------------------------------");
var geo = new THREE.BufferGeometry();
var positionTypedArray = new Float32Array(geometryInfo.position);
var length = positionTypedArray.length;
var numberOfVertex = length / 3;
var colorTypedArray = new Float32Array(length);
var indexTypedArray = new Uint16Array(geometryInfo.index);
var color = geometryInfo.color;
var type = geometryInfo.type;
if (geometryArray[type] == undefined){
geometryArray[type] = [];
}
switch (type) {
case "IndexedFaceSet":
geo.addAttribute( 'position', new THREE.Float32BufferAttribute( positionTypedArray, 3, false ));
geo.addAttribute( 'color', new THREE.Float32BufferAttribute( positionTypedArray, 3, true ));
geo.index = new THREE.Uint16BufferAttribute( indexTypedArray, 1, false );
for (j=0; j<numberOfVertex; j++) {
geo.attributes.color.array[j*3] = color[0];
geo.attributes.color.array[j*3+1] = color[1];
geo.attributes.color.array[j*3+2] = color[2];
}
if (geo != undefined) {
geo.applyMatrix(geometryInfo.matrix);
geo.computeBoundingBox();
geo.computeBoundingSphere();
geo.computeVertexNormals();
geo.normalizeNormals();
geometryArray[type].push(geo);
}
break;
case "IndexedLineSet":
geo.addAttribute( 'position', new THREE.Float32BufferAttribute( positionTypedArray, 3, false ));
geo.addAttribute( 'color', new THREE.Float32BufferAttribute( positionTypedArray, 3, true ));
geo.index = new THREE.Uint16BufferAttribute( indexTypedArray, 1, false );
for (j=0; j<numberOfVertex; j++) {
geo.attributes.color.array[j*3] = color[0];
geo.attributes.color.array[j*3+1] = color[1];
geo.attributes.color.array[j*3+2] = color[2];
}
if (geo != undefined) {
geo.applyMatrix(geometryInfo.matrix);
geo.computeBoundingBox();
geo.computeBoundingSphere();
geometryArray[type].push(geo);
}
break;
case "PointSet":
geo.addAttribute( 'position', new THREE.Float32BufferAttribute( positionTypedArray, 3, false ));
geo.addAttribute( 'color', new THREE.Float32BufferAttribute( positionTypedArray, 3, true ));
for (j=0; j<numberOfVertex; j++) {
geo.attributes.color.array[j*3] = color[0];
geo.attributes.color.array[j*3+1] = color[1];
geo.attributes.color.array[j*3+2] = color[2];
}
if (geo != undefined) {
geo.applyMatrix(geometryInfo.matrix);
geo.computeBoundingBox();
geo.computeBoundingSphere();
geometryArray[type].push(geo);
}
break;
}
}
Best regards