src/plugins/BIMServerLoaderPlugin/lib/BIMServerPerformanceGeometryLoader.js
import {DataInputStreamReader} from "./DataInputStreamReader.js";
import {math as utils} from "../../../viewer/scene/math/math.js";
/**
*
* @param bimServerClient
* @param bimServerClientModel
* @param roid
* @param globalTransformationMatrix
* @param performanceModelBuilder
* @constructor
* @private
*/
function BIMServerPerformanceGeometryLoader(bimServerClient, bimServerClientModel, roid, globalTransformationMatrix, performanceModelBuilder) {
var o = this;
var protocolVersion = null;
var currentState = {};
const progressListeners = [];
var prepareReceived = false;
const todo = [];
const multiUseGeometryLoaded = {};
const objectsWaitingForGeometryData = {};
const singleUseGeometriesWaitingForObjects = {};
const singleUseGeometryLoaded = {};
o.roid = roid;
var infoToOid = {};
this.addProgressListener = function (progressListener) {
progressListeners.push(progressListener);
};
function processMessage(stream) {
var messageType = stream.readByte();
if (messageType === 0) {
if (!readStart(stream)) {
return false;
}
} else if (messageType === 6) {
readEnd(stream);
} else {
readObject(stream, messageType);
}
stream.align8();
return stream.remaining() > 0;
}
this.process = function () {
var data = todo.shift();
var stream;
while (data != null) {
stream = new DataInputStreamReader(data);
var topicId = stream.readLong();
var type = stream.readLong();
if (type == 0) {
while (processMessage(stream)) {
}
} else if (type == 1) {
// End of stream
}
data = todo.shift();
}
};
this.setLoadOids = function (oids) {
o.options = {type: "oids", oids: oids};
};
this.start = function () {
if (!o.options || o.options.type !== "oids") {
throw new Error("Invalid loader configuration");
}
var obj = [];
o.groupId = o.roid;
infoToOid = o.options.oids;
for (var k in infoToOid) {
var oid = parseInt(infoToOid[k]);
bimServerClientModel.get(oid, function (object) {
if (object.object._rgeometry != null) {
if (object.model.objects[object.object._rgeometry] != null) {
// Only if this data is preloaded, otherwise just don't include any gi
object.getGeometry(function (geometryInfo) {
obj.push({
gid: object.object._rgeometry,
oid: object.oid,
object: object,
info: geometryInfo.object
});
});
} else {
obj.push({gid: object.object._rgeometry, oid: object.oid, object: object});
}
}
});
}
obj.sort(function (a, b) {
if (a.info != null && b.info != null) {
var topa = (a.info._emaxBounds.z + a.info._eminBounds.z) / 2;
var topb = (b.info._emaxBounds.z + b.info._eminBounds.z) / 2;
return topa - topb;
} else {
// Resort back to type
// TODO this is dodgy when some objects do have info, and others don't
return a.object.getType().localeCompare(b.object.getType());
}
});
var oids = [];
obj.forEach(function (wrapper) {
oids.push(wrapper.object.object._rgeometry._i);
});
var serializerName = "org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingStreamingSerializerPlugin";
var fieldsToInclude = ["indices"];
fieldsToInclude.push("normals");
fieldsToInclude.push("vertices");
fieldsToInclude.push("colorsQuantized");
var newQuery = {
doublebuffer: false,
type: "GeometryInfo",
oids: oids,
include: {
type: "GeometryInfo",
field: "data",
include: {
type: "GeometryData",
fieldsDirect: fieldsToInclude
}
},
loaderSettings: {
splitGeometry: false
}
};
var oldQuery = {
type: "GeometryInfo",
oids: oids,
include: {
type: "GeometryInfo",
field: "data"
}
};
var useNewQuery = false;
var pluginCallback = function (serializer) {
bimServerClient.call("ServiceInterface", "download", {
roids: [o.roid],
query: JSON.stringify(useNewQuery ? newQuery : oldQuery),
serializerOid: serializer.oid,
sync: false
}, function (topicId) {
o.topicId = topicId;
bimServerClient.registerProgressHandler(o.topicId, progressHandler);
});
};
var promise = bimServerClient.getSerializerByPluginClassName(serializerName + "3", pluginCallback);
if (promise) {
// If this returns a promise (it'll never be cancelled btw. even in case of error) we're
// talking to a newer version of the plugin ecosystem and we can try the new query.
useNewQuery = true;
bimServerClient.getSerializerByPluginClassName(serializerName).then(pluginCallback);
}
};
function progressHandler(topicId, state) {
if (topicId === o.topicId) {
if (state.title === "Done preparing") {
if (!prepareReceived) {
prepareReceived = true;
downloadInitiated();
}
}
if (state.state === "FINISHED") {
bimServerClient.unregisterProgressHandler(o.topicId, progressHandler);
}
}
}
function downloadInitiated() {
currentState = {
mode: 0,
nrObjectsRead: 0,
nrObjects: 0
};
bimServerClient.setBinaryDataListener(o.topicId, binaryDataListener);
bimServerClient.downloadViaWebsocket({
longActionId: o.topicId,
topicId: o.topicId
});
}
function binaryDataListener(data) {
todo.push(data);
}
function afterRegistration(topicId) {
bimServerClient.call("Bimsie1NotificationRegistryInterface", "getProgress", {
topicId: o.topicId
}, function (state) {
progressHandler(o.topicId, state);
});
}
function readStart(data) {
var start = data.readUTF8();
if (start !== "BGS") {
performanceModelBuilder.error("data does not start with BGS (" + start + ")");
return false;
}
protocolVersion = data.readByte();
performanceModelBuilder.log("BIMServer protocol version = " + protocolVersion);
if (protocolVersion !== 10 && protocolVersion !== 11 && protocolVersion !== 16 && protocolVersion !== 17 && protocolVersion != 18 && protocolVersion != 19 && protocolVersion != 20) {
performanceModelBuilder.error("Unimplemented protocol version");
return false;
} else {
currentState.version = protocolVersion;
}
if (protocolVersion > 15) {
o.multiplierToMm = data.readFloat();
}
data.align8();
var boundary = data.readDoubleArray(6);
performanceModelBuilder.gotModelBoundary(boundary);
currentState.mode = 1;
progressListeners.forEach(function (progressListener) {
progressListener("start", currentState.nrObjectsRead, currentState.nrObjectsRead);
});
updateProgress();
return true;
}
function readEnd(data) {
progressListeners.forEach(function (progressListener) {
progressListener("done", currentState.nrObjectsRead, currentState.nrObjectsRead);
});
bimServerClient.call("ServiceInterface", "cleanupLongAction", {topicId: o.topicId}, function () {
});
}
function updateProgress() {
}
function readObject(stream, geometryType) {
//---------------------------------------------------------------------------------
// protocol version assumed to be 16
//---------------------------------------------------------------------------------
let color = new Float32Array([1, 1, 1, 1]);
if (geometryType === 1) {
//-----------------------------------------------------------------------------
// Geometry data received
//-----------------------------------------------------------------------------
let reused = stream.readInt();
let ifcType = stream.readUTF8();
stream.align8();
let roid = stream.readLong();
let croid = stream.readLong();
let hasTransparency = stream.readLong() === 1;
let geometryId = stream.readLong();
let numIndices = stream.readInt();
let indices = stream.readShortArray(numIndices);
stream.align4();
let b = stream.readInt();
let gotColor = (b === 1);
if (gotColor) {
color[0] = stream.readFloat();
color[1] = stream.readFloat();
color[2] = stream.readFloat();
color[3] = stream.readFloat();
}
let numPositions = stream.readInt();
let positions = stream.readFloatArray(numPositions);
let numNormals = stream.readInt();
let normals = stream.readFloatArray(numNormals);
let numColors = stream.readInt();
var colors = null;
if (!gotColor && numColors > 0) {
colors = stream.readFloatArray(numColors);
color[0] = colors[0];
color[1] = colors[1];
color[2] = colors[2];
color[3] = colors[3];
gotColor = true;
}
if (!gotColor) {
color[0] = 1.0;
color[1] = 1.0;
color[2] = 1.0;
color[3] = 1.0;
}
var multiUseGeometry = (reused > 1);
if (multiUseGeometry) {
//------------------------------------------------------------------------------------------------------
// MULTI-USE GEOMETRY
//------------------------------------------------------------------------------------------------------
performanceModelBuilder.createGeometry(geometryId, positions, normals, indices);
multiUseGeometryLoaded[geometryId] = true;
if (objectsWaitingForGeometryData[geometryId] !== null) {
//------------------------------------------------------------------------------------------------------
// OBJECTS ARE WAITING
//------------------------------------------------------------------------------------------------------
let waitingObjects = objectsWaitingForGeometryData[geometryId];
for (var objectId in waitingObjects) {
if (waitingObjects.hasOwnProperty(objectId)) {
let waitingObjectData = waitingObjects[objectId];
let meshColor = color;
let meshOpacity = color[3];
let ifcProps = performanceModelBuilder.objectDefaults[waitingObjectData.ifcType];
if (ifcProps) {
if (ifcProps.colorize) {
meshColor = ifcProps.colorize;
meshOpacity = ifcProps.colorize[3];
}
if (ifcProps.opacity !== null && ifcProps.opacity !== undefined) {
meshOpacity = ifcProps.opacity;
}
}
const meshId = utils.createUUID();
performanceModelBuilder.createMeshInstancingGeometry(meshId, geometryId, waitingObjectData.matrix, meshColor, meshOpacity);
performanceModelBuilder.createEntity(objectId, meshId, waitingObjectData.ifcType);
delete waitingObjects[objectId];
}
}
}
} else {
//------------------------------------------------------------------------------------------------------
// SINGLE USE GEOMETRY
//------------------------------------------------------------------------------------------------------
if (objectsWaitingForGeometryData[geometryId] !== null) {
//--------------------------------------------------------------------------------------------------
// THE OBJECT IS WAITING
//--------------------------------------------------------------------------------------------------
var waitingObjects = objectsWaitingForGeometryData[geometryId];
for (var objectId in waitingObjects) {
if (waitingObjects.hasOwnProperty(objectId)) {
let waitingObjectData = waitingObjects[objectId];
let meshColor = color;
let meshOpacity = color[3];
let ifcProps = performanceModelBuilder.objectDefaults[waitingObjectData.ifcType];
if (ifcProps) {
if (ifcProps.colorize) {
meshColor = ifcProps.colorize;
meshOpacity = ifcProps.colorize[3];
}
if (ifcProps.opacity !== null && ifcProps.opacity !== undefined) {
meshOpacity = ifcProps.opacity;
}
}
const meshId = utils.createUUID();
performanceModelBuilder.createMeshSpecifyingGeometry(meshId, positions, normals, indices, waitingObjectData.matrix, meshColor, meshOpacity);
performanceModelBuilder.createEntity(objectId, meshId, waitingObjectData.ifcType);
}
}
delete objectsWaitingForGeometryData[geometryId];
delete singleUseGeometryLoaded[geometryId];
} else {
//--------------------------------------------------------------------------------------------------
// NO OBJECT IS WAITING
// GEOMETRY NOW WAITS FOR OBJECT
//--------------------------------------------------------------------------------------------------
let waitingGeometryData = {
positions: positions,
normals: normals,
indices: indices
};
singleUseGeometriesWaitingForObjects[geometryId] = waitingGeometryData;
}
}
} else if (geometryType === 5) {
//----------------------------------------------------------------------------------------------------------
// Object data received
//----------------------------------------------------------------------------------------------------------
var inPreparedBuffer = stream.readByte() === 1;
var oid = stream.readLong();
let ifcType = stream.readUTF8();
var numColors = stream.readInt();
if (false /* FIXME */ && numColors > 0) {
colors = stream.readFloatArray(numColors);
color[0] = colors[0];
color[1] = colors[1];
color[2] = colors[2];
color[3] = colors[3];
} else {
color[0] = 1.0;
color[1] = 1.0;
color[2] = 1.0;
color[3] = 1.0;
}
let meshColor = color;
let meshOpacity = color[3];
let ifcProps = performanceModelBuilder.objectDefaults[ifcType];
if (ifcProps) {
if (ifcProps.colorize) {
meshColor = ifcProps.colorize;
meshOpacity = ifcProps.colorize[3];
}
if (ifcProps.opacity !== null && ifcProps.opacity !== undefined) {
meshOpacity = ifcProps.opacity;
}
}
stream.align8();
let roid = stream.readLong();
let geometryInfoOid = stream.readLong();
let hasTransparency = stream.readLong() === 1;
let objectBounds = stream.readDoubleArray(6);
let matrix = stream.readDoubleArray(16);
let geometryId = stream.readLong();
let geometryDataOidFound = geometryId;
oid = infoToOid[geometryInfoOid];
if (oid === null) {
// performanceModelBuilder.error("Not found", infoToOid, geometryInfoOid);
return;
}
var objectId = oid;
if (singleUseGeometriesWaitingForObjects[geometryId]) {
//------------------------------------------------------------------------------------------------------
// SINGLE USE GEOMETRY WAITING
//------------------------------------------------------------------------------------------------------
var waitingGeometryData = singleUseGeometriesWaitingForObjects[geometryId];
var positions = waitingGeometryData.positions;
var normals = waitingGeometryData.normals;
var indices = waitingGeometryData.indices;
performanceModelBuilder.createMeshSpecifyingGeometry(geometryId, positions, normals, indices, matrix, meshColor, meshOpacity);
performanceModelBuilder.createEntity(objectId, geometryId, ifcType);
delete singleUseGeometryLoaded[geometryId];
} else if (multiUseGeometryLoaded[geometryId]) {
//------------------------------------------------------------------------------------------------------
// MULTI-USE GEOMETRY WAITING
//------------------------------------------------------------------------------------------------------
const meshId = utils.createUUID();
performanceModelBuilder.createMeshInstancingGeometry(meshId, geometryId, matrix, meshColor, meshOpacity);
performanceModelBuilder.createEntity(oid, meshId, ifcType);
} else {
//------------------------------------------------------------------------------------------------------
// NO GEOMETRY WAITING
//------------------------------------------------------------------------------------------------------
var waitingObjects = objectsWaitingForGeometryData[geometryId];
if (!waitingObjects) {
waitingObjects = {};
objectsWaitingForGeometryData[geometryId] = waitingObjects;
}
var waitingObjectData = {
ifcType: ifcType,
matrix: matrix
};
waitingObjects[objectId] = waitingObjectData;
}
} else if (geometryType === 9) {
//--------------------------------------------------------------------------
// Minimal object
//--------------------------------------------------------------------------
var oid = stream.readLong();
var type = stream.readUTF8();
var nrColors = stream.readInt();
var roid = stream.readLong();
var geometryInfoOid = stream.readLong();
var hasTransparency = stream.readLong() === 1;
stream.align8();
var objectBounds = stream.readDoubleArrayCopy(6);
var geometryDataOid = stream.readLong();
var geometryDataOidFound = geometryDataOid;
if (hasTransparency) {
// this.createdTransparentObjects.set(oid, {
// nrColors: nrColors,
// type: type
// });
// } else {
// this.createdOpaqueObjects.set(oid, {
// nrColors: nrColors,
// type: type
// });
}
// this.createObject(roid, oid, oid, [], null, hasTransparency, type, objectBounds, true);
} else if (geometryType === 3) {
var reused = stream.readInt();
var type = stream.readUTF8();
stream.align8();
var hasTransparency = stream.readLong() == 1;
var coreIds = [];
var geometryDataOid = stream.readLong();
var nrParts = stream.readInt();
for (var i = 0; i < nrParts; i++) {
var partId = stream.readLong();
var coreId = geometryDataOid + "_" + i;
coreIds.push(coreId);
var nrIndices = stream.readInt();
//o.stats.nrPrimitives += nrIndices / 3;
var indices = stream.readShortArray(nrIndices);
stream.align4();
var b = stream.readIstream;
var rgba;
if (b === 1) {
rgba = {r: stream.readFloat(), g: stream.readFloat(), b: stream.readFloat(), a: stream.readFloat()};
}
stream.align4();
var nrVertices = stream.readInt();
//o.stats.nrVertices += nrVertices;
var vertices = stream.readFloatArray(nrVertices);
var nrNormals = stream.readInt();
//o.stats.nrNormals += nrNormals;
var normals = stream.readFloatArray(nrNormals);
var nrColors = stream.readInt();
//o.stats.nrColors += nrColors;
var colors = stream.readFloatArray(nrColors);
var geometry = {
type: "geometry",
//primitive: o.type
};
if (rgba) {
// Creating vertex colors here anyways (not transmitted over the line is a plus), should find a way to do this with scenejs without vertex-colors
geometry.colors = new Array(nrVertices * 4);
for (var j = 0; j < nrVertices; j++) {
geometry.colors[j * 4 + 0] = rgba.r;
geometry.colors[j * 4 + 1] = rgba.g;
geometry.colors[j * 4 + 2] = rgba.b;
geometry.colors[j * 4 + 3] = rgba.a;
}
}
geometry.coreId = coreId;
/*if (o.type == "lines") {
geometry.indices = o.convertToLines(indices);
} else {
geometry.indices = indices;
}*/
geometry.positions = vertices;
geometry.normals = normals;
if (colors != null && colors.length > 0) {
geometry.colors = colors;
}
//o.library.add("node", geometry);
}
stream.align8();
//o.loadedGeometry[geometryDataOid] = coreIds;
/*if (o.dataToInfo[geometryDataOid] != null) {
o.dataToInfo[geometryDataOid].forEach(function(geometryInfoId){
var node = o.viewer.scene.findNode(geometryInfoId);
if (node != null && node.nodes[0] != null) {
coreIds.forEach(function(coreId){
node.nodes[0].addNode({
type: "geometry",
coreId: coreId
});
});
}
});
delete o.dataToInfo[geometryDataOid];
}*/
} else if (geometryType === 7) {
// this.processPreparedBuffer(stream, true);
} else if (geometryType === 8) {
// this.processPreparedBuffer(stream, false);
} else if (geometryType === 10) {
// this.processPreparedBufferInit(stream, true);
} else if (geometryType === 11) {
// this.processPreparedBufferInit(stream, false);
} else {
console.error("Unsupported geometry type: " + geometryType);
// What's geometryType === 106 ?
}
currentState.nrObjectsRead++;
updateProgress();
}
}
export {BIMServerPerformanceGeometryLoader};