All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bimserver.serializers.binarygeometry.BinaryGeometryMessagingStreamingSerializer Maven / Gradle / Ivy

package org.bimserver.serializers.binarygeometry;

/******************************************************************************
 * Copyright (C) 2009-2019  BIMserver.org
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see {@literal}.
 *****************************************************************************/

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.bimserver.BimserverDatabaseException;
import org.bimserver.database.queries.om.QueryException;
import org.bimserver.emf.PackageMetaData;
import org.bimserver.geometry.Matrix;
import org.bimserver.geometry.Matrix3;
import org.bimserver.geometry.Vector;
import org.bimserver.geometry.Vector3D;
import org.bimserver.interfaces.objects.SVector3f;
import org.bimserver.models.geometry.GeometryPackage;
import org.bimserver.plugins.LittleEndianSerializerDataOutputStream;
import org.bimserver.plugins.PluginManagerInterface;
import org.bimserver.plugins.SerializerDataOutputStream;
import org.bimserver.plugins.serializers.MessagingStreamingSerializer;
import org.bimserver.plugins.serializers.ObjectProvider;
import org.bimserver.plugins.serializers.ProgressReporter;
import org.bimserver.plugins.serializers.ProjectInfo;
import org.bimserver.plugins.serializers.SerializerException;
import org.bimserver.serializers.binarygeometry.clipping.Point;
import org.bimserver.shared.AbstractHashMapVirtualObject;
import org.bimserver.shared.HashMapVirtualObject;
import org.bimserver.shared.HashMapWrappedVirtualObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.primitives.UnsignedBytes;

public class BinaryGeometryMessagingStreamingSerializer implements MessagingStreamingSerializer {
	private static final Logger LOGGER = LoggerFactory.getLogger(BinaryGeometryMessagingStreamingSerializer.class);

	/*
	 * Format history (starting at version 8):
	 * 
	 * Version 8:
	 * 	- Using short instead of int for indices. SceneJS was converting the indices to Uint16 anyways, so this saves bytes and a conversion on the client-side
	 * Version 9:
	 *  - Sending the materials/colors for splitted geometry as well, before sending the actual parts
	 *  - Aligning bytes to 8s instead of 4s when sending splitted geometry
	 *  - Incrementing splitcounter instead of decrementing (no idea why it was doing that)
	 *  Version 10:
	 *  - Sending the materials/colors for parts as well again
	 *  Version 11:
	 *  - Added ability to send one specific color for all geometry contained in a GeometryData object
	 *  Version 12:
	 *  - Added boolean value that indicates whether an object/geometry has transparency
	 *  Version 13:
	 *  - Added integer value that indicates how many times geometry is being reused
	 *  Version 14:
	 *  - Added ifcproduct oid to simplify client-side operations, also added type of object, also added type for GeometryData
	 *  Version 15:
	 *  - Also sending a multiplier to convert to mm
	 *  Version 16:
	 *  - Just a version bump to make sure older client will err-out
	 *  Version 17:
	 *  - Sending the amount of colors now for GeometryInfo and also sending 1 byte to indicate whether geometry is in a completeBuffer
	 *  - Added writePreparedBuffer (should not have an impact if you don't send the prepareBuffers option)
	 *  Version 18:
	 *  - Oct-encoding of normals, mat4 globalTransformation input is now vec3 globalTranslationVector
	 *  Version 19:
	 *  - Added optional generateLineRenders, protocol has changed because the counts are always sent regardless of setting
	 */
	
	private static final byte FORMAT_VERSION = 19;
	
	private enum Mode {
		LOAD,
		START,
		DATA,
		PREPARED_BUFFER_INIT,
		PREPARED_BUFFER_TRANSPARENT,
		PREPARED_BUFFER_OPAQUE,
		END
	}
	
	private enum MessageType {
		INIT((byte)0),
		GEOMETRY_TRIANGLES_PARTED((byte)3),
		GEOMETRY_TRIANGLES((byte)1),
		GEOMETRY_INFO((byte)5),
		MINIMAL_GEOMETRY_INFO((byte)9),
		PREPARED_BUFFER_TRANSPARENT((byte)7),
		PREPARED_BUFFER_OPAQUE((byte)8),
		PREPARED_BUFFER_TRANSPARENT_INIT((byte)10),
		PREPARED_BUFFER_OPAQUE_INIT((byte)11),
		END((byte)6);
		
		private byte id;

		private MessageType(byte id) {
			this.id = id;
		}
		
		public byte getId() {
			return id;
		}
	}

	private boolean splitGeometry = true;
	private boolean useSingleColors = false;
	private boolean quantizeNormals = false;
	private boolean octEncodeNormals = false;
	private boolean quantizeVertices = false;
	private boolean quantizeColors = false;
	private boolean prepareBuffers = false;
	private boolean normalizeUnitsToMM = false;
	private boolean useSmallInts = true;
	private boolean reportProgress = true;
	private boolean useUuidAndRid = false;
	private Map vertexQuantizationMatrices;
	
	private GeometryMainBuffer transparentGeometryBuffer;
	private GeometryMainBuffer opaqueGeometryBuffer;
	
	private final Map oidToGeometryData = new HashMap<>();
	private final Map dataToGeometryInfo = new HashMap<>();
	private double[] vertexQuantizationMatrix;
	
	private Mode mode = Mode.LOAD;
	private long splitCounter = 0;
	private ObjectProvider objectProvider;
	private ProjectInfo projectInfo;
	private SerializerDataOutputStream serializerDataOutputStream;
	private HashMapVirtualObject next;
	private ProgressReporter progressReporter;
	private int nrObjectsWritten;
	private int size;

	private ByteBuffer lastTransformation;

	private Set reusedDataOids;

	private double[] globalTranslationVector;

	private boolean generateLineRenders;

	private float[] lastNormal;

	@Override
	public void init(ObjectProvider objectProvider, ProjectInfo projectInfo, PluginManagerInterface pluginManager, PackageMetaData packageMetaData) throws SerializerException {
		this.objectProvider = objectProvider;
		this.projectInfo = projectInfo;
		ObjectNode queryNode = objectProvider.getQueryNode();
		if (queryNode.has("tiles")) {
			ObjectNode tilesNode = (ObjectNode)queryNode.get("tiles");
			if (tilesNode.has("geometryDataToReuse") && !tilesNode.get("geometryDataToReuse").isNull()) {
				ArrayNode reuseNodes = (ArrayNode)tilesNode.get("geometryDataToReuse");
				this.reusedDataOids = new HashSet<>();
				for (JsonNode jsonNode : reuseNodes) {
					this.reusedDataOids.add(jsonNode.asLong());
				}
			}
		}
		if (queryNode.has("loaderSettings")) {
			ObjectNode geometrySettings = (ObjectNode) queryNode.get("loaderSettings");
			
			useUuidAndRid = geometrySettings.has("useUuidAndRid") && geometrySettings.get("useUuidAndRid").asBoolean();
			useSingleColors = geometrySettings.has("useObjectColors") && geometrySettings.get("useObjectColors").asBoolean();
			splitGeometry = geometrySettings.has("splitGeometry") && geometrySettings.get("splitGeometry").asBoolean();
			quantizeNormals = geometrySettings.has("quantizeNormals") && geometrySettings.get("quantizeNormals").asBoolean();
			octEncodeNormals = geometrySettings.has("octEncodeNormals") && geometrySettings.get("octEncodeNormals").asBoolean();
			quantizeVertices = geometrySettings.has("quantizeVertices") && geometrySettings.get("quantizeVertices").asBoolean();
			quantizeColors = geometrySettings.has("quantizeColors") && geometrySettings.get("quantizeColors").asBoolean();
			normalizeUnitsToMM = geometrySettings.has("normalizeUnitsToMM") && geometrySettings.get("normalizeUnitsToMM").asBoolean();
			reportProgress = !geometrySettings.has("reportProgress") || geometrySettings.get("reportProgress").asBoolean(); // default is true for backwards compat
			useSmallInts = !geometrySettings.has("useSmallInts") || geometrySettings.get("useSmallInts").asBoolean(); // default is true for backwards compat
			prepareBuffers = geometrySettings.has("prepareBuffers") && geometrySettings.get("prepareBuffers").asBoolean();
			generateLineRenders = geometrySettings.has("generateLineRenders") && geometrySettings.get("generateLineRenders").asBoolean();
			if (geometrySettings.has("globalTranslationVector")) {
				this.globalTranslationVector = new double[3];
				ArrayNode matrixNode = (ArrayNode) geometrySettings.get("globalTranslationVector");
				int i=0;
				for (JsonNode v : matrixNode) {
					this.globalTranslationVector[i++] = v.asDouble();
				}
			}
			if (prepareBuffers) {
				transparentGeometryBuffer = new GeometryMainBuffer();
				opaqueGeometryBuffer = new GeometryMainBuffer();
			}
			if (quantizeVertices) {
				if (queryNode.has("loaderSettings")) {
					ArrayNode vqmNode = (ArrayNode) geometrySettings.get("vertexQuantizationMatrix");
					if (vqmNode != null) {
						vertexQuantizationMatrix = new double[16];
						int i=0;
						for (JsonNode v : vqmNode) {
							vertexQuantizationMatrix[i++] = v.doubleValue();
						}
//						Matrix.dump(vertexQuantizationMatrix);
					}
				}

				ObjectNode vqmNode = (ObjectNode) geometrySettings.get("vertexQuantizationMatrices");
				if (vqmNode != null) {
					Iterator fieldNames = vqmNode.fieldNames();
					vertexQuantizationMatrices = new HashMap<>();
					while (fieldNames.hasNext()) {
						String key = fieldNames.next();
						long croid = Long.parseLong(key);
						float[] vertexQuantizationMatrix = new float[16];
						ArrayNode mNode = (ArrayNode) vqmNode.get(key);
						vertexQuantizationMatrices.put(croid, vertexQuantizationMatrix);
						int i=0;
						for (JsonNode v : mNode) {
							vertexQuantizationMatrix[i++] = v.floatValue();
						}
					}
				}
			}
		}
	}

	@Override
	public boolean writeMessage(OutputStream outputStream, ProgressReporter progressReporter) throws IOException, SerializerException {
		this.progressReporter = progressReporter;
		serializerDataOutputStream = null;
		if (outputStream instanceof SerializerDataOutputStream) {
			serializerDataOutputStream = (SerializerDataOutputStream)outputStream;
		} else {
			serializerDataOutputStream = new LittleEndianSerializerDataOutputStream(outputStream);
		}
		switch (mode) {
		case LOAD: {
			load();
			mode = Mode.START;
			// Explicitly no break here, move on to start right away
		}
		case START:
			writeStart();
			serializerDataOutputStream.align8();
			if (next == null) {
				mode = Mode.END;
			} else {
				mode = Mode.DATA;
			}
			break;
		case DATA:
			if (!writeData()) {
				serializerDataOutputStream.align8();
				if (prepareBuffers) {
					mode = Mode.PREPARED_BUFFER_OPAQUE;
				} else {
					mode = Mode.END;
				}
				return true;
			}
			serializerDataOutputStream.align8();
			break;
		case PREPARED_BUFFER_OPAQUE:
			if (!writePreparedBuffer(mode)) {
				mode = Mode.PREPARED_BUFFER_TRANSPARENT;
			}
			break;
		case PREPARED_BUFFER_TRANSPARENT:
			if (!writePreparedBuffer(mode)) {
				mode = Mode.END;
			}
			break;
		case END:
			writeEnd();
			serializerDataOutputStream.align8();
			return false;
		default:
			break;
		}
		return true;
	}

	private boolean writePreparedBuffer(Mode mode) throws IOException, SerializerException {
		try {
			GeometryMainBuffer geometryMainBuffer = getCurrentMainBuffer();
			if (geometryMainBuffer == null) {
				return false;
			}
			GeometryBuffer geometryBuffer = geometryMainBuffer.getCurrentReadBuffer();
			while (geometryBuffer == null || geometryBuffer.isEmpty() || !geometryBuffer.hasNextGeometryMapping()) {
				if (geometryMainBuffer.hasNextReadBuffer()) {
					geometryBuffer = geometryMainBuffer.getNextReadBuffer();
				} else {
					return false;
				}
			}
			if (!geometryBuffer.initSent()) {
				ByteBuffer buffer = ByteBuffer.allocate(25).order(ByteOrder.LITTLE_ENDIAN);

				buffer.put(mode == Mode.PREPARED_BUFFER_OPAQUE ? MessageType.PREPARED_BUFFER_OPAQUE_INIT.getId() : MessageType.PREPARED_BUFFER_TRANSPARENT_INIT.getId());
				buffer.putInt(geometryBuffer.getNrObjects());
				buffer.putInt(geometryBuffer.getNrIndices());
				buffer.putInt(generateLineRenders ? geometryBuffer.getNrLineIndices() : 0);
				buffer.putInt(geometryBuffer.getNrVertices());
				buffer.putInt(geometryBuffer.getNrVertices());
				buffer.putInt(geometryBuffer.getNrColors());
	
				serializerDataOutputStream.write(buffer.array());
				serializerDataOutputStream.align8();
				
				geometryBuffer.setInitSent();
				
				return true;
			}
			int vertexPosition = 0;
			serializerDataOutputStream.writeByte(mode == Mode.PREPARED_BUFFER_TRANSPARENT ? 7 : 8);
			GeometrySubBuffer geometryMapping = geometryBuffer.getNextGeometryMapping();
			ByteBuffer buffer = ByteBuffer.allocate(geometryMapping.getPreparedByteSize()).order(ByteOrder.LITTLE_ENDIAN);
	
			buffer.putInt(geometryMapping.getNrObjects());
			buffer.putInt(geometryMapping.getNrIndices());
			buffer.putInt(generateLineRenders ? geometryMapping.getNrLineIndices() : 0);
			buffer.putInt(geometryMapping.getNrVertices());
			buffer.putInt(geometryMapping.getNrVertices());
			buffer.putInt(geometryMapping.getNrColors());
			
			int indicesStartByte = 24;
			int lineIndicesStartByte = indicesStartByte + geometryMapping.getNrIndices() * 4;
			int originalLineIndicesStartByte = lineIndicesStartByte;
			int indicesMappingStartByte = generateLineRenders ? (lineIndicesStartByte + geometryMapping.getNrLineIndices() * 4) : lineIndicesStartByte;
			int verticesStartByte = indicesMappingStartByte + geometryMapping.getNrObjects() * ((44 + (generateLineRenders ? 8 : 0)) + (useUuidAndRid ? 20 : 0)) + geometryMapping.getTotalColorPackSize();
			int normalsStartByte = verticesStartByte + geometryMapping.getNrVertices() * (quantizeVertices ? 2 : 4);
			int endByte = normalsStartByte + (quantizeNormals ? (octEncodeNormals ? geometryMapping.getNrVertices() / 3 * 2 : geometryMapping.getNrVertices()) : geometryMapping.getNrVertices() * 4);
			
			int baseIndex = geometryMapping.getBaseIndex();
			
			for (HashMapVirtualObject info : geometryMapping.keySet()) {
				HashMapVirtualObject data = geometryMapping.get(info);
	//			short cid = (short) data.get("type");
	//			EClass eClass = objectProvider.getEClassForCid(cid);
				
				DoubleBuffer transformation = ByteBuffer.wrap((byte[]) info.eGet(info.eClass().getEStructuralFeature("transformation"))).order(ByteOrder.LITTLE_ENDIAN).asDoubleBuffer();
				double[] ms = new double[16];
				for (int i=0; i<16; i++) {
					ms[i] = transformation.get();
				}
				
				AbstractHashMapVirtualObject indicesBuffer = data.getDirectFeature(GeometryPackage.eINSTANCE.getGeometryData_Indices());
				byte[] normals = null;
				byte[] vertices = null;
				AbstractHashMapVirtualObject normalsBuffer = data.getDirectFeature(GeometryPackage.eINSTANCE.getGeometryData_Normals());
				normals = (byte[]) normalsBuffer.get("data");
				AbstractHashMapVirtualObject verticesBuffer = data.getDirectFeature(GeometryPackage.eINSTANCE.getGeometryData_Vertices());
				vertices = (byte[]) verticesBuffer.get("data");
				IntBuffer indices = ByteBuffer.wrap((byte[]) indicesBuffer.get("data")).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
	
				ByteBuffer vertexByteBuffer = ByteBuffer.wrap(vertices).order(ByteOrder.LITTLE_ENDIAN);
				ByteBuffer normalsByteBuffer = ByteBuffer.wrap(normals).order(ByteOrder.LITTLE_ENDIAN);

				AbstractHashMapVirtualObject lineIndicesBuffer = data.getDirectFeature(GeometryPackage.eINSTANCE.getGeometryData_LineIndices());
				byte[] lineIndicesBytes = null;
				if (lineIndicesBuffer != null) {
					lineIndicesBytes = (byte[]) lineIndicesBuffer.get("data");
				}
				
				Long oid = (Long)info.get("ifcProductOid");
				buffer.putLong(indicesMappingStartByte, oid);
				indicesMappingStartByte += 8;
				if (useUuidAndRid) {
					buffer.position(indicesMappingStartByte);
					byte[] bytes = (byte[])info.get("ifcProductUuid");
					buffer.put(bytes);
					indicesMappingStartByte += 16;
					buffer.putInt(indicesMappingStartByte, (int)info.get("ifcProductRid"));
					indicesMappingStartByte += 4;
				}
				
				// Start index for object
				int indicesStart = (indicesStartByte - 24) / 4;
				buffer.putInt(indicesMappingStartByte, indicesStart);
				indicesMappingStartByte += 4;
				
				// Start line index for object
				buffer.putInt(indicesMappingStartByte, (lineIndicesStartByte - originalLineIndicesStartByte) / 4);
				indicesMappingStartByte += 4;

				// Nr of indices
				buffer.putInt(indicesMappingStartByte, indices.capacity());
				indicesMappingStartByte += 4;
				
				// Nr of line indices
				buffer.putInt(indicesMappingStartByte, lineIndicesBytes == null ? 0 :lineIndicesBytes.length / 4);
				indicesMappingStartByte += 4;

				// Nr of vertices
				buffer.putInt(indicesMappingStartByte, vertices.length / 8);
				indicesMappingStartByte += 4;

				int minIndex = -1;
				int maxIndex = -1;
				
				for (int i=0; i maxIndex) {
						maxIndex = modifiedIndex;
					}
					
					buffer.putInt(indicesStartByte, modifiedIndex);
					indicesStartByte += 4;
				}
				
				int minLineIndex = -1;
				int maxLineIndex = -1;
				
				if (generateLineRenders) {
					IntBuffer lineIndicesInt = ByteBuffer.wrap(lineIndicesBytes).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer();
					
					for (int i=0; i maxLineIndex) {
							maxLineIndex = modifiedIndex;
						}
						
						buffer.putInt(lineIndicesStartByte, modifiedIndex);
						lineIndicesStartByte += 4;
					}
				}
				
				buffer.putInt(indicesMappingStartByte, minIndex);
				indicesMappingStartByte += 4;
				buffer.putInt(indicesMappingStartByte, maxIndex);
				indicesMappingStartByte += 4;

				if (generateLineRenders) {
					buffer.putInt(indicesMappingStartByte, minLineIndex);
					indicesMappingStartByte += 4;
					buffer.putInt(indicesMappingStartByte, maxLineIndex);
					indicesMappingStartByte += 4;
				}
				
				float density = (float) info.get("density");
				buffer.putFloat(indicesMappingStartByte, density);
				indicesMappingStartByte += 4;
				
				HashMapVirtualObject colorPack = (HashMapVirtualObject) data.getDirectFeature(GeometryPackage.eINSTANCE.getGeometryData_ColorPack());
				byte[] colorPackData = colorPack == null ? null : (byte[]) colorPack.eGet(GeometryPackage.eINSTANCE.getColorPack_Data());
				if (colorPackData == null || colorPackData.length == 0) {
					buffer.putInt(indicesMappingStartByte, 0);
					indicesMappingStartByte += 4;
				} else {
					int colorPackSize = colorPackData.length / 8;
					buffer.putInt(indicesMappingStartByte, colorPackSize);
					indicesMappingStartByte += 4;
					buffer.position(indicesMappingStartByte);
					buffer.put(colorPackData);
					indicesMappingStartByte += colorPackData.length;
				}

				double[] in = new double[4];
				double[] vertex = new double[4];
				double[] result = new double[4];
				in[3] = 1;
				vertex[3] = 1;
				int nrPos = vertexByteBuffer.capacity() / 8;
				for (int i=0; i -0.0001 && normal[i] < 0.0001) {
				normal[i] = 0;
			}
		}
	}

	private void reorderForLineRendering(IntBuffer indices, DoubleBuffer vertices, FloatBuffer normals) {
		Mesh mesh = new Mesh(indices, vertices, normals);
		Mesh newMesh = mesh.copy();
		MeshChanger meshChanger = new MeshChanger(mesh, newMesh);
		
		Map lineSegments = new HashMap<>();
		for (int i=0; i= 0f ? 1 : -1;
	}
	
//	function octEncodeVec3(array, i, xfunc, yfunc) {
//        var x = array[i    ] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2]));
//        var y = array[i + 1] / (Math.abs(array[i]) + Math.abs(array[i + 1]) + Math.abs(array[i + 2]));
//
//        if (array[i + 2] < 0) {
//            var tempx = x;
//            var tempy = y;
//            tempx = (1 - Math.abs(y)) * (x >= 0 ? 1 : -1);
//            tempy = (1 - Math.abs(x)) * (y >= 0 ? 1 : -1);
//            x = tempx;
//            y = tempy;
//        }
//
//        return new Int8Array([
//            Math[xfunc](x * 127.5 + (x < 0 ? -1 : 0)),
//            Math[yfunc](y * 127.5 + (y < 0 ? -1 : 0))
//        ]);
//
//    }
	
	private void writeNormalToOct(ByteBuffer buffer, int normalsStartByte, float[] normal) {
		float x = Math.abs(normal[0]) + Math.abs(normal[1]) + Math.abs(normal[2]);
		float[] p = new float[] {normal[0] / x, normal[1] / x};
		
		if (normal[2] <= 0f) {
			float a = (1f - Math.abs(p[0])) * signNotZero(p[0]);
			float b = (1f - Math.abs(p[1])) * signNotZero(p[1]);
			p = new float[]{a, b};
		}

		byte a = (byte)(p[0] * 127f);
		buffer.put(normalsStartByte, a);
		byte b = (byte)(p[1] * 127f);
		buffer.put(normalsStartByte + 1, b);
		
//		System.out.println(normal[0] + ", " + normal[1] + ", " + normal[2]);
//		if (lastNormal == null || !Arrays.equals(this.lastNormal, normal)) {
//			System.out.println(normal[0] + ", " + normal[1] + ", " + normal[2]);
//			System.out.println(a + ", " + b);
//			if (this.lastNormal == null) {
//				this.lastNormal = new float[3];
//			}
//			System.arraycopy(normal, 0, this.lastNormal, 0, 3);
//		}
	}

	private void load() throws SerializerException {
//		long start = System.nanoTime();
		if (reportProgress) {
			size = 0;
			HashMapVirtualObject next = null;
			try {
				next = objectProvider.next();
				while (next != null) {
					if (next.eClass() == GeometryPackage.eINSTANCE.getGeometryInfo()) {
						size++;
					}
					next = objectProvider.next();
				}
			} catch (BimserverDatabaseException e) {
				throw new SerializerException(e);
			}
			try {
				objectProvider = objectProvider.copy();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (QueryException e) {
				e.printStackTrace();
			}
		}
		try {
			this.next = objectProvider.next();
		} catch (BimserverDatabaseException e) {
			e.printStackTrace();
		}
//		long end = System.nanoTime();
//		System.out.println(((end - start) / 1000000) + " ms prepare time");
	}
	
	private boolean writeEnd() throws IOException {
		serializerDataOutputStream.write(MessageType.END.getId());
		return true;
	}
	
	private void writeStart() throws IOException {
		// Identifier for clients to determine if this server is even serving binary geometry
		serializerDataOutputStream.writeByte(MessageType.INIT.getId());
		serializerDataOutputStream.writeUTF("BGS");
		
		// Version of the current format being outputted, should be changed for every (released) change in protocol 
		serializerDataOutputStream.writeByte(FORMAT_VERSION);
		
		serializerDataOutputStream.writeFloat(projectInfo.getMultiplierToMm());
		serializerDataOutputStream.align8();

		// TODO These are known to be wrong for multi-roid queries
		SVector3f minBounds = projectInfo.getMinBounds();
		SVector3f maxBounds = projectInfo.getMaxBounds();
		
		if (normalizeUnitsToMM && projectInfo.getMultiplierToMm() != 1f) {
			serializerDataOutputStream.writeDouble(minBounds.getX() * projectInfo.getMultiplierToMm());
			serializerDataOutputStream.writeDouble(minBounds.getY() * projectInfo.getMultiplierToMm());
			serializerDataOutputStream.writeDouble(minBounds.getZ() * projectInfo.getMultiplierToMm());
			serializerDataOutputStream.writeDouble(maxBounds.getX() * projectInfo.getMultiplierToMm());
			serializerDataOutputStream.writeDouble(maxBounds.getY() * projectInfo.getMultiplierToMm());
			serializerDataOutputStream.writeDouble(maxBounds.getZ() * projectInfo.getMultiplierToMm());
		} else {
			serializerDataOutputStream.writeDouble(minBounds.getX());
			serializerDataOutputStream.writeDouble(minBounds.getY());
			serializerDataOutputStream.writeDouble(minBounds.getZ());
			serializerDataOutputStream.writeDouble(maxBounds.getX());
			serializerDataOutputStream.writeDouble(maxBounds.getY());
			serializerDataOutputStream.writeDouble(maxBounds.getZ());
		}
	}

	private boolean writeData() throws IOException, SerializerException {
		if (next == null) {
			return false;
		}
		boolean wroteSomething = false;
		while (!wroteSomething && next != null) {
			if (GeometryPackage.eINSTANCE.getGeometryInfo() == next.eClass()) {
				writeGeometryInfo(next);
				wroteSomething = true;
			} else if (GeometryPackage.eINSTANCE.getGeometryData() == next.eClass()) {
				wroteSomething = writeGeometryData(next, next.getOid());
			}
			try {
				next = objectProvider.next();
			} catch (BimserverDatabaseException e) {
				LOGGER.error("", e);
			}
		}
		return next != null;
	}

	private void writeGeometryInfo(HashMapVirtualObject info) throws IOException, SerializerException {
		boolean hasTransparancy = (boolean)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_HasTransparency());
		long geometryDataId = (long)info.eGet(info.eClass().getEStructuralFeature("data"));
		boolean inPreparedBuffer = false;
		long oid = (long) info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_IfcProductOid());
		if (prepareBuffers && (reusedDataOids == null || !reusedDataOids.contains(geometryDataId))) {
			inPreparedBuffer = true;
			if (oidToGeometryData.containsKey(geometryDataId)) {
				GeometryMainBuffer geometryMainBuffer = getCurrentMainBuffer(hasTransparancy);
				GeometryBuffer currentBuffer = geometryMainBuffer.getCurrentWriteBuffer();
				GeometrySubBuffer geometryMapping = currentBuffer.getCurrentGeometryMapping(true);
				HashMapVirtualObject gd = oidToGeometryData.get(geometryDataId);
				geometryMapping.put(info, gd);
				updateSize(gd, currentBuffer);
			} else {
				dataToGeometryInfo.put(geometryDataId, info);
			}
			writeMinimalGeometryInfo(info);
			return;
		}
		
		byte[] transformation = (byte[]) info.eGet(info.eClass().getEStructuralFeature("transformation"));
		long dataOid = geometryDataId;
		
		serializerDataOutputStream.writeByte(MessageType.GEOMETRY_INFO.getId());
		serializerDataOutputStream.writeByte(inPreparedBuffer ? (byte)1 : (byte)0);
		serializerDataOutputStream.writeLong(oid);
		
		if (useUuidAndRid) {
			serializerDataOutputStream.write((byte[])info.get("ifcProductUuid"));
			serializerDataOutputStream.writeInt((int)info.get("ifcProductRid"));
		}
		
		String type = objectProvider.getEClassForOid(oid).getName();
		serializerDataOutputStream.writeUTF(type);
		int nrColors = (int)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_NrColors());
		if (nrColors == 0) {
			int nrVertices = (int)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_NrVertices());
			nrColors = nrVertices / 3 * 4;
		}
		serializerDataOutputStream.writeInt(nrColors);
		serializerDataOutputStream.align8();
		serializerDataOutputStream.ensureExtraCapacity(24);
		serializerDataOutputStream.writeLongUnchecked(info.getRoid());
		serializerDataOutputStream.writeLongUnchecked(info.getOid());
		serializerDataOutputStream.writeLongUnchecked(hasTransparancy ? 1 : 0);
		writeBounds(info);
		
		lastTransformation = ByteBuffer.wrap(transformation);
		lastTransformation.order(ByteOrder.LITTLE_ENDIAN);
		
		ByteBuffer newTransformation = lastTransformation;
		
		// Apply the globalTranslation (usually used to move the model to around 0, 0, 0)
		if (globalTranslationVector != null) {
			DoubleBuffer asDoubleBuffer = lastTransformation.asDoubleBuffer();
			double[] ms = new double[16];
			for (int i=0; i<16; i++) {
				ms[i] = asDoubleBuffer.get();
			}

			double[] tmp = new double[16];
			double[] translationMatrix = new double[16];
			Matrix.setIdentityM(translationMatrix, 0);
			Matrix.translateM(translationMatrix, 0, translationMatrix, 0, globalTranslationVector[0] / projectInfo.getMultiplierToMm(), globalTranslationVector[1] / projectInfo.getMultiplierToMm(), globalTranslationVector[2] / projectInfo.getMultiplierToMm());
			Matrix.multiplyMM(tmp, 0, translationMatrix, 0, ms, 0);
			ms = tmp;

			newTransformation = ByteBuffer.wrap(new byte[16 * 8]).order(ByteOrder.LITTLE_ENDIAN);
			for (double d : ms) {
				newTransformation.putDouble(d);
			}
		}
		
		serializerDataOutputStream.ensureExtraCapacity(((byte[])transformation).length + 8);
		serializerDataOutputStream.write(newTransformation.array());
		serializerDataOutputStream.writeLong(dataOid);
		
		nrObjectsWritten++;
		if (reportProgress) {
			if (progressReporter != null) {
				progressReporter.update(nrObjectsWritten, size);
			}
		}
	}

	private void writeBounds(HashMapVirtualObject info) throws IOException {
		HashMapWrappedVirtualObject bounds = (HashMapWrappedVirtualObject) info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_Bounds());
		HashMapWrappedVirtualObject minBounds = (HashMapWrappedVirtualObject) bounds.eGet(GeometryPackage.eINSTANCE.getBounds_Min());
		HashMapWrappedVirtualObject maxBounds = (HashMapWrappedVirtualObject) bounds.eGet(GeometryPackage.eINSTANCE.getBounds_Max());
		Double minX = (Double) minBounds.eGet("x");
		Double minY = (Double) minBounds.eGet("y");
		Double minZ = (Double) minBounds.eGet("z");
		Double maxX = (Double) maxBounds.eGet("x");
		Double maxY = (Double) maxBounds.eGet("y");
		Double maxZ = (Double) maxBounds.eGet("z");
		
		if (normalizeUnitsToMM && projectInfo.getMultiplierToMm() != 1f) {
			minX = minX * projectInfo.getMultiplierToMm();
			minY = minY * projectInfo.getMultiplierToMm();
			minZ = minZ * projectInfo.getMultiplierToMm();
			maxX = maxX * projectInfo.getMultiplierToMm();
			maxY = maxY * projectInfo.getMultiplierToMm();
			maxZ = maxZ * projectInfo.getMultiplierToMm();
		}
		
		serializerDataOutputStream.ensureExtraCapacity(8 * 6);
		serializerDataOutputStream.writeDoubleUnchecked(minX);
		serializerDataOutputStream.writeDoubleUnchecked(minY);
		serializerDataOutputStream.writeDoubleUnchecked(minZ);
		serializerDataOutputStream.writeDoubleUnchecked(maxX);
		serializerDataOutputStream.writeDoubleUnchecked(maxY);
		serializerDataOutputStream.writeDoubleUnchecked(maxZ);
	}

	private void writeMinimalGeometryInfo(HashMapVirtualObject info) throws IOException {
		boolean hasTransparancy = (boolean)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_HasTransparency());
		long geometryDataId = (long)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_Data());
		long oid = (long) info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_IfcProductOid());
		
		serializerDataOutputStream.writeByte(MessageType.MINIMAL_GEOMETRY_INFO.getId());
		serializerDataOutputStream.writeLong(oid);
		if (useUuidAndRid) {
			byte[] b = (byte[])info.get("ifcProductUuid");
			if (b.length != 16) {
				throw new RuntimeException("Must be 16 bytes");
			}
			serializerDataOutputStream.write(b);
			serializerDataOutputStream.writeInt((int)info.get("ifcProductRid"));
		}
		String type = objectProvider.getEClassForOid(oid).getName();
		serializerDataOutputStream.writeUTF(type);
		int nrColors = (int)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_NrColors());
		if (nrColors == 0) {
			int nrVertices = (int)info.eGet(GeometryPackage.eINSTANCE.getGeometryInfo_NrVertices());
			nrColors = nrVertices / 3 * 4;
		}
		serializerDataOutputStream.writeInt(nrColors);
		serializerDataOutputStream.ensureExtraCapacity(32);
		serializerDataOutputStream.writeLongUnchecked(info.getRoid());
		serializerDataOutputStream.writeLongUnchecked(info.getOid());
		serializerDataOutputStream.writeLongUnchecked(hasTransparancy ? 1 : 0);
		serializerDataOutputStream.align8();
		
		writeBounds(info);
		
		serializerDataOutputStream.writeLong(geometryDataId);
		
		nrObjectsWritten++;
		if (reportProgress) {
			if (progressReporter != null) {
				progressReporter.update(nrObjectsWritten, size);
			}
		}		
	}

	private boolean writeGeometryData(HashMapVirtualObject data, long oid) throws IOException, SerializerException {
		// This geometry info is pointing to a not-yet-sent geometry data, so we send that first
		// This way the client can be sure that geometry data is always available when geometry info is received, simplifying bookkeeping
		EStructuralFeature hasTransparencyFeature = data.eClass().getEStructuralFeature("hasTransparency");
		boolean hasTransparancy = (boolean)data.eGet(hasTransparencyFeature);
		
		if (prepareBuffers && (reusedDataOids == null || !reusedDataOids.contains(oid))) {
			oidToGeometryData.put(oid, data);
			if (dataToGeometryInfo.containsKey(oid)) {
				GeometryMainBuffer geometryMainBuffer = getCurrentMainBuffer(hasTransparancy);
				GeometryBuffer currentBuffer = geometryMainBuffer.getCurrentWriteBuffer();
				GeometrySubBuffer geometryMapping = currentBuffer.getCurrentGeometryMapping(true);
				geometryMapping.put(dataToGeometryInfo.get(oid), data);
				updateSize(data, currentBuffer);
			}
			return false;
		}
		
		EStructuralFeature indicesFeature = data.eClass().getEStructuralFeature("indices");
		EStructuralFeature lineIndicesFeature = data.eClass().getEStructuralFeature("lineIndices");
		EStructuralFeature verticesFeature = data.eClass().getEStructuralFeature("vertices");
		EStructuralFeature verticesQuantizedFeature = data.eClass().getEStructuralFeature("verticesQuantized");
		EStructuralFeature normalsFeature = data.eClass().getEStructuralFeature("normals");
		EStructuralFeature normalsQuantizedFeature = data.eClass().getEStructuralFeature("normalsQuantized");
		EStructuralFeature colorsFeature = data.eClass().getEStructuralFeature("colorsQuantized");
		EStructuralFeature colorFeature = data.eClass().getEStructuralFeature("color");
		EStructuralFeature mostUsedColorFeature = data.eClass().getEStructuralFeature("mostUsedColor");

		AbstractHashMapVirtualObject indicesBuffer = data.getDirectFeature(indicesFeature);
		byte[] normals = null;
		byte[] normalsQuantized = null;
		byte[] vertices = null;
		byte[] verticesQuantized = null;
		AbstractHashMapVirtualObject quantizedNormalsBuffer = data.getDirectFeature(normalsQuantizedFeature);
		if (quantizeNormals && quantizedNormalsBuffer != null) {
			normalsQuantized = (byte[]) quantizedNormalsBuffer.get("data");
		} else {
			AbstractHashMapVirtualObject normalsBuffer = data.getDirectFeature(normalsFeature);
			normals = (byte[]) normalsBuffer.get("data");
		}
		AbstractHashMapVirtualObject quantizedVerticesBuffer = data.getDirectFeature(verticesQuantizedFeature);
		if (quantizeVertices && quantizedVerticesBuffer != null) {
			verticesQuantized = (byte[]) quantizedVerticesBuffer.get("data");
		} else {
			AbstractHashMapVirtualObject verticesBuffer = data.getDirectFeature(verticesFeature);
			vertices = (byte[]) verticesBuffer.get("data");
		}
		AbstractHashMapVirtualObject colorsBuffer = data.getDirectFeature(colorsFeature);
		HashMapWrappedVirtualObject color = (HashMapWrappedVirtualObject)data.eGet(colorFeature);
		HashMapWrappedVirtualObject mostUsedColor = (HashMapWrappedVirtualObject)data.eGet(mostUsedColorFeature);
		
		byte[] indices = (byte[]) indicesBuffer.get("data");
		byte[] colors = null;
		if (colorsBuffer != null) {
			colors = (byte[]) colorsBuffer.get("data");			
		}

		int totalNrIndices = indices.length / 4;
		int maxIndexValues = 16389;

		if (splitGeometry && totalNrIndices > maxIndexValues) {
			serializerDataOutputStream.writeByte(MessageType.GEOMETRY_TRIANGLES_PARTED.getId());
			serializerDataOutputStream.writeInt((int) data.get("reused"));
			short cid = (short) data.get("type");
			String type = objectProvider.getEClassForCid(cid).getName();
			serializerDataOutputStream.writeUTF(type);
			serializerDataOutputStream.align8();
			serializerDataOutputStream.writeLong(hasTransparancy ? 1 : 0);
			serializerDataOutputStream.writeLong(data.getOid());
			
			// Split geometry, this algorithm - for now - just throws away all the reuse of vertices that might be there
			// Also, although usually the vertices buffers are too large, this algorithm is based on the indices, so we
			// probably are not cramming as much data as we can in each "part", but that's not really a problem I think

			int nrParts = (totalNrIndices + maxIndexValues - 1) / maxIndexValues;
			serializerDataOutputStream.writeInt(nrParts);

			ByteBuffer indicesByteBuffer = ByteBuffer.wrap(indices);
			indicesByteBuffer.order(ByteOrder.LITTLE_ENDIAN);
			IntBuffer indicesIntBuffer = indicesByteBuffer.asIntBuffer();

			ByteBuffer vertexBuffer = ByteBuffer.wrap(vertices);
			vertexBuffer.order(ByteOrder.LITTLE_ENDIAN);
			DoubleBuffer verticesDoubleBuffer = vertexBuffer.asDoubleBuffer();
			
			ByteBuffer normalsBuffer = ByteBuffer.wrap(normals);
			normalsBuffer.order(ByteOrder.LITTLE_ENDIAN);
			FloatBuffer normalsFloatBuffer = normalsBuffer.asFloatBuffer();

			for (int part=0; part bounds[3] || a.getY() > bounds[4] || a.getZ() > bounds[5]) {
			return false;
		}
		return true;
	}

	@Override
	public void close() {
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy