org.bimserver.GeometryGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.bimserver.GeometryGenerator.java

Source

package org.bimserver;

/******************************************************************************
 * Copyright (C) 2009-2015  BIMserver.org
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************/

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.NotImplementedException;
import org.bimserver.database.DatabaseSession;
import org.bimserver.database.OldQuery;
import org.bimserver.emf.IdEObject;
import org.bimserver.emf.IfcModelInterface;
import org.bimserver.emf.IfcModelInterfaceException;
import org.bimserver.emf.OidProvider;
import org.bimserver.emf.PackageMetaData;
import org.bimserver.emf.Schema;
import org.bimserver.geometry.Matrix;
import org.bimserver.geometry.Vector;
import org.bimserver.ifc.BasicIfcModel;
import org.bimserver.models.geometry.GeometryData;
import org.bimserver.models.geometry.GeometryFactory;
import org.bimserver.models.geometry.GeometryInfo;
import org.bimserver.models.geometry.GeometryPackage;
import org.bimserver.models.geometry.Vector3f;
import org.bimserver.models.ifc2x3tc1.IfcShapeRepresentation;
import org.bimserver.models.store.RenderEnginePluginConfiguration;
import org.bimserver.models.store.User;
import org.bimserver.models.store.UserSettings;
import org.bimserver.plugins.ModelHelper;
import org.bimserver.plugins.PluginConfiguration;
import org.bimserver.plugins.PluginManager;
import org.bimserver.plugins.objectidms.HideAllInversesObjectIDM;
import org.bimserver.plugins.renderengine.EntityNotFoundException;
import org.bimserver.plugins.renderengine.IndexFormat;
import org.bimserver.plugins.renderengine.Precision;
import org.bimserver.plugins.renderengine.RenderEngine;
import org.bimserver.plugins.renderengine.RenderEngineException;
import org.bimserver.plugins.renderengine.RenderEngineFilter;
import org.bimserver.plugins.renderengine.RenderEngineGeometry;
import org.bimserver.plugins.renderengine.RenderEngineInstance;
import org.bimserver.plugins.renderengine.RenderEngineModel;
import org.bimserver.plugins.renderengine.RenderEnginePlugin;
import org.bimserver.plugins.renderengine.RenderEngineSettings;
import org.bimserver.plugins.serializers.Serializer;
import org.bimserver.plugins.serializers.SerializerException;
import org.bimserver.plugins.serializers.SerializerPlugin;
import org.bimserver.shared.exceptions.UserException;
import org.bimserver.utils.CollectionUtils;
import org.bimserver.utils.Formatters;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class GeometryGenerator {
    private static final Logger LOGGER = LoggerFactory.getLogger(GeometryGenerator.class);

    private final BimServer bimServer;
    private final Map<Integer, GeometryData> hashes = new ConcurrentHashMap<Integer, GeometryData>();

    private EClass productClass;
    private EClass productRepresentationClass;
    private EStructuralFeature geometryFeature;
    private EStructuralFeature representationFeature;
    private EStructuralFeature representationsFeature;
    private PackageMetaData packageMetaData;

    private AtomicLong bytesSaved = new AtomicLong();
    private AtomicLong totalBytes = new AtomicLong();

    public GeometryGenerator(final BimServer bimServer) {
        this.bimServer = bimServer;
    }

    public class Runner implements Runnable {

        private EClass eClass;
        private RenderEnginePlugin renderEnginePlugin;
        private DatabaseSession databaseSession;
        private RenderEngineSettings renderEngineSettings;
        private RenderEngineFilter renderEngineFilter;
        private RenderEngineFilter renderEngineFilterTransformed = new RenderEngineFilter(true);
        private boolean store;
        private IfcModelInterface targetModel;
        private SerializerPlugin ifcSerializerPlugin;
        private IfcModelInterface model;
        private int pid;
        private int rid;
        private Map<IdEObject, IdEObject> bigMap;
        private GenerateGeometryResult generateGeometryResult;

        public Runner(EClass eClass, RenderEnginePlugin renderEnginePlugin, DatabaseSession databaseSession,
                RenderEngineSettings renderEngineSettings, boolean store, IfcModelInterface targetModel,
                SerializerPlugin ifcSerializerPlugin, IfcModelInterface model, int pid, int rid,
                Map<IdEObject, IdEObject> bigMap, RenderEngineFilter renderEngineFilter,
                GenerateGeometryResult generateGeometryResult) {
            this.eClass = eClass;
            this.renderEnginePlugin = renderEnginePlugin;
            this.databaseSession = databaseSession;
            this.renderEngineSettings = renderEngineSettings;
            this.store = store;
            this.targetModel = targetModel;
            this.ifcSerializerPlugin = ifcSerializerPlugin;
            this.model = model;
            this.pid = pid;
            this.rid = rid;
            this.bigMap = bigMap;
            this.renderEngineFilter = renderEngineFilter;
            this.generateGeometryResult = generateGeometryResult;
        }

        @Override
        public void run() {
            targetModel.generateMinimalExpressIds();

            Serializer ifcSerializer = ifcSerializerPlugin.createSerializer(new PluginConfiguration());
            RenderEngine renderEngine = null;
            try {
                renderEngine = renderEnginePlugin.createRenderEngine(new PluginConfiguration(),
                        model.getPackageMetaData().getSchema().getEPackageName());
            } catch (RenderEngineException e) {
                LOGGER.error("", e);
            }
            try {
                renderEngine.init();
                ifcSerializer.init(targetModel, null, bimServer.getPluginManager(), null,
                        model.getPackageMetaData(), true);

                boolean debug = false;
                InputStream in = null;
                if (debug) {
                    File file = new File((eClass == null ? "all" : eClass.getName()) + ".ifc");
                    FileOutputStream fos = new FileOutputStream(file);
                    IOUtils.copy(ifcSerializer.getInputStream(), fos);
                    fos.close();
                    in = new FileInputStream(file);
                } else {
                    in = ifcSerializer.getInputStream();
                }
                RenderEngineModel renderEngineModel = renderEngine.openModel(in);
                try {
                    renderEngineModel.setSettings(renderEngineSettings);
                    renderEngineModel.setFilter(renderEngineFilter);

                    renderEngineModel.generateGeneralGeometry();

                    List<IdEObject> allWithSubTypes = null;
                    if (eClass == null) {
                        allWithSubTypes = targetModel.getAllWithSubTypes(packageMetaData.getEClass("IfcProduct"));
                    } else {
                        allWithSubTypes = targetModel.getAll(eClass);
                    }
                    for (IdEObject ifcProduct : allWithSubTypes) {
                        IdEObject representation = (IdEObject) ifcProduct.eGet(representationFeature);
                        if (representation != null
                                && ((List<?>) representation.eGet(representationsFeature)).size() > 0) {
                            List<?> representations = (List<?>) representation.eGet(representationsFeature);
                            try {
                                RenderEngineInstance renderEngineInstance = renderEngineModel
                                        .getInstanceFromExpressId(ifcProduct.getExpressId());
                                RenderEngineGeometry geometry = renderEngineInstance.generateGeometry();
                                boolean translate = true;
                                if (geometry == null || geometry.getIndices().length == 0) {
                                    renderEngineModel.setFilter(renderEngineFilterTransformed);
                                    geometry = renderEngineInstance.generateGeometry();
                                    if (geometry != null) {
                                        translate = false;
                                    }
                                    renderEngineModel.setFilter(renderEngineFilter);
                                }
                                if (geometry != null && geometry.getNrIndices() > 0) {
                                    GeometryInfo geometryInfo = null;
                                    if (store) {
                                        geometryInfo = model.createAndAdd(
                                                GeometryPackage.eINSTANCE.getGeometryInfo(), databaseSession
                                                        .newOid(GeometryPackage.eINSTANCE.getGeometryInfo()));
                                        databaseSession.store(geometryInfo, pid, rid);
                                        //                              geometryInfo = packageMetaData.create(GeometryInfo.class);
                                        //                              Long newOid = databaseSession.newOid(GeometryPackage.eINSTANCE.getGeometryInfo());
                                        //                              ((IdEObjectImpl) geometryInfo).setOid(newOid);
                                        //                              model.add(newOid, geometryInfo);
                                    } else {
                                        geometryInfo = GeometryFactory.eINSTANCE.createGeometryInfo();
                                    }

                                    geometryInfo.setMinBounds(createVector3f(packageMetaData, model,
                                            Float.POSITIVE_INFINITY, databaseSession, store, pid, rid));
                                    geometryInfo.setMaxBounds(createVector3f(packageMetaData, model,
                                            Float.NEGATIVE_INFINITY, databaseSession, store, pid, rid));

                                    try {
                                        double area = renderEngineInstance.getArea();
                                        geometryInfo.setArea(area);
                                        double volume = renderEngineInstance.getVolume();
                                        if (volume < 0d) {
                                            volume = -volume;
                                        }
                                        geometryInfo.setVolume(volume);

                                        //                              EStructuralFeature guidFeature = ifcProduct.eClass().getEStructuralFeature("GlobalId");
                                        //                              String guid = (String) ifcProduct.eGet(guidFeature);
                                        //                              System.out.println(guid + ": " + "Area: " + area + ", Volume: " + volume);
                                    } catch (NotImplementedException e) {
                                    }

                                    GeometryData geometryData = null;
                                    if (store) {
                                        geometryData = model.createAndAdd(
                                                GeometryPackage.eINSTANCE.getGeometryData(), databaseSession
                                                        .newOid(GeometryPackage.eINSTANCE.getGeometryData()));
                                        databaseSession.store(geometryData, pid, rid);
                                    } else {
                                        geometryData = GeometryFactory.eINSTANCE.createGeometryData();
                                    }

                                    geometryData.setIndices(intArrayToByteArray(geometry.getIndices()));
                                    geometryData.setVertices(floatArrayToByteArray(geometry.getVertices()));
                                    geometryData
                                            .setMaterialIndices(intArrayToByteArray(geometry.getMaterialIndices()));
                                    geometryData.setNormals(floatArrayToByteArray(geometry.getNormals()));

                                    geometryInfo.setPrimitiveCount(geometry.getIndices().length / 3);

                                    if (geometry.getMaterialIndices() != null
                                            && geometry.getMaterialIndices().length > 0) {
                                        boolean hasMaterial = false;
                                        float[] vertex_colors = new float[geometry.getVertices().length / 3 * 4];
                                        for (int i = 0; i < geometry.getMaterialIndices().length; ++i) {
                                            int c = geometry.getMaterialIndices()[i];
                                            for (int j = 0; j < 3; ++j) {
                                                int k = geometry.getIndices()[i * 3 + j];
                                                if (c > -1) {
                                                    hasMaterial = true;
                                                    for (int l = 0; l < 4; ++l) {
                                                        vertex_colors[4 * k + l] = geometry.getMaterials()[4 * c
                                                                + l];
                                                    }
                                                }
                                            }
                                        }
                                        if (hasMaterial) {
                                            geometryData.setMaterials(floatArrayToByteArray(vertex_colors));
                                        }
                                    }

                                    float[] tranformationMatrix = new float[16];
                                    if (translate && renderEngineInstance.getTransformationMatrix() != null) {
                                        tranformationMatrix = renderEngineInstance.getTransformationMatrix();
                                    } else {
                                        Matrix.setIdentityM(tranformationMatrix, 0);
                                    }

                                    for (int i = 0; i < geometry.getIndices().length; i++) {
                                        processExtends(geometryInfo, tranformationMatrix, geometry.getVertices(),
                                                geometry.getIndices()[i] * 3, generateGeometryResult);
                                    }

                                    geometryInfo.setData(geometryData);

                                    long length = (geometryData.getIndices() != null
                                            ? geometryData.getIndices().length
                                            : 0)
                                            + (geometryData.getVertices() != null
                                                    ? geometryData.getVertices().length
                                                    : 0)
                                            + (geometryData.getNormals() != null ? geometryData.getNormals().length
                                                    : 0)
                                            + (geometryData.getMaterials() != null
                                                    ? geometryData.getMaterials().length
                                                    : 0)
                                            + (geometryData.getMaterialIndices() != null
                                                    ? geometryData.getMaterialIndices().length
                                                    : 0);

                                    setTransformationMatrix(geometryInfo, tranformationMatrix);
                                    if (bimServer.getServerSettingsCache().getServerSettings().isReuseGeometry()) {
                                        int hash = hash(geometryData);
                                        if (hashes.containsKey(hash)) {
                                            databaseSession.removeFromCommit(geometryData);
                                            geometryInfo.setData(hashes.get(hash));
                                            bytesSaved.addAndGet(length);
                                        } else {
                                            hashes.put(hash, geometryData);
                                        }
                                    }
                                    totalBytes.addAndGet(length);

                                    if (bigMap == null) {
                                        ifcProduct.eSet(geometryFeature, geometryInfo);
                                        if (store) {
                                            databaseSession.store(ifcProduct, pid, rid);
                                        }
                                    } else {
                                        bigMap.get(ifcProduct).eSet(geometryFeature, geometryInfo);
                                        ifcProduct.eSet(geometryFeature, geometryInfo); // ??
                                        if (store) {
                                            databaseSession.store(bigMap.get(ifcProduct), pid, rid);
                                        }
                                    }
                                }
                            } catch (EntityNotFoundException e) {
                                // As soon as we find a representation that is not Curve2D, then we should show a "INFO" message in the log to indicate there could be something wrong
                                boolean ignoreNotFound = true;
                                for (Object rep : representations) {
                                    if (rep instanceof IfcShapeRepresentation) {
                                        IfcShapeRepresentation ifcShapeRepresentation = (IfcShapeRepresentation) rep;
                                        if (!"Curve2D".equals(ifcShapeRepresentation.getRepresentationType())) {
                                            ignoreNotFound = false;
                                        }
                                    }
                                }
                                if (!ignoreNotFound) {
                                    LOGGER.info("Entity not found " + ifcProduct.eClass().getName() + " "
                                            + ifcProduct.getExpressId() + "/" + ifcProduct.getOid());
                                }
                            } catch (BimserverDatabaseException | RenderEngineException e) {
                                LOGGER.error("", e);
                            } catch (IfcModelInterfaceException e) {
                                LOGGER.error("", e);
                            }
                        }
                    }
                } finally {
                    in.close();
                    renderEngineModel.close();
                }
            } catch (SerializerException | RenderEngineException | IOException e) {
                LOGGER.error("", e);
            } finally {
                try {
                    renderEngine.close();
                } catch (RenderEngineException e) {
                    LOGGER.error("", e);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    public GenerateGeometryResult generateGeometry(long uoid, final PluginManager pluginManager,
            final DatabaseSession databaseSession, final IfcModelInterface model, final int pid, final int rid,
            final boolean store, GeometryCache geometryCache)
            throws BimserverDatabaseException, GeometryGeneratingException {
        GenerateGeometryResult generateGeometryResult = new GenerateGeometryResult();
        packageMetaData = model.getPackageMetaData();
        productClass = packageMetaData.getEClass("IfcProduct");
        productRepresentationClass = packageMetaData.getEClass("IfcProductRepresentation");
        geometryFeature = productClass.getEStructuralFeature("geometry");
        representationFeature = productClass.getEStructuralFeature("Representation");
        representationsFeature = productRepresentationClass.getEStructuralFeature("Representations");

        if (geometryCache != null && !geometryCache.isEmpty()) {
            returnCachedData(model, geometryCache, databaseSession, pid, rid);
            return null;
        }
        long start = System.nanoTime();
        String pluginName = "";
        if (model.getPackageMetaData().getSchema() == Schema.IFC4) {
            pluginName = "org.bimserver.ifc.step.serializer.Ifc4StepSerializerPlugin";
        } else if (model.getPackageMetaData().getSchema() == Schema.IFC2X3TC1) {
            pluginName = "org.bimserver.ifc.step.serializer.Ifc2x3tc1StepSerializerPlugin";
        }

        try {
            final SerializerPlugin ifcSerializerPlugin = (SerializerPlugin) pluginManager.getPlugin(pluginName,
                    true);
            if (ifcSerializerPlugin == null) {
                throw new UserException("No IFC serializer found");
            }

            User user = (User) databaseSession.get(uoid, OldQuery.getDefault());
            UserSettings userSettings = user.getUserSettings();
            RenderEnginePluginConfiguration defaultRenderEngine = userSettings.getDefaultRenderEngine();
            if (defaultRenderEngine == null) {
                throw new UserException("No default render engine has been selected for this user");
            }
            final RenderEnginePlugin renderEnginePlugin = pluginManager
                    .getRenderEngine(defaultRenderEngine.getPluginDescriptor().getPluginClassName(), true);
            if (renderEnginePlugin == null) {
                throw new UserException("No (enabled) render engine found of type "
                        + defaultRenderEngine.getPluginDescriptor().getPluginClassName());
            }

            int maxSimultanousThreads = Math.min(
                    bimServer.getServerSettingsCache().getServerSettings().getRenderEngineProcesses(),
                    Runtime.getRuntime().availableProcessors());
            if (maxSimultanousThreads < 1) {
                maxSimultanousThreads = 1;
            }

            final RenderEngineSettings settings = new RenderEngineSettings();
            settings.setPrecision(Precision.SINGLE);
            settings.setIndexFormat(IndexFormat.AUTO_DETECT);
            settings.setGenerateNormals(true);
            settings.setGenerateTriangles(true);
            settings.setGenerateWireFrame(false);

            final RenderEngineFilter renderEngineFilter = new RenderEngineFilter();

            if (maxSimultanousThreads == 1) {
                Runner runner = new Runner(null, renderEnginePlugin, databaseSession, settings, store, model,
                        ifcSerializerPlugin, model, pid, rid, null, renderEngineFilter, generateGeometryResult);
                runner.run();
            } else {
                Set<EClass> classes = new HashSet<>();
                for (IdEObject object : model.getAllWithSubTypes(packageMetaData.getEClass("IfcProduct"))) {
                    IdEObject representation = (IdEObject) object.eGet(representationFeature);
                    if (representation != null
                            && ((List<?>) representation.eGet(representationsFeature)).size() > 0) {
                        classes.add(object.eClass());
                    }
                }

                if (classes.size() == 0) {
                    return null;
                }

                classes.remove(packageMetaData.getEClass("IfcAnnotation"));
                classes.remove(packageMetaData.getEClass("IfcOpeningElement"));

                LOGGER.debug("Using " + maxSimultanousThreads + " processes for geometry generation");
                ThreadPoolExecutor executor = new ThreadPoolExecutor(maxSimultanousThreads, maxSimultanousThreads,
                        24, TimeUnit.HOURS, new ArrayBlockingQueue<Runnable>(classes.size()));

                final Map<IdEObject, IdEObject> bigMap = new HashMap<IdEObject, IdEObject>();

                HideAllInversesObjectIDM idm = new HideAllInversesObjectIDM(
                        CollectionUtils.singleSet(packageMetaData.getEPackage()),
                        pluginManager.getMetaDataManager().getPackageMetaData("ifc2x3tc1").getSchemaDefinition());
                OidProvider oidProvider = new OidProvider() {
                    @Override
                    public long newOid(EClass eClass) {
                        return databaseSession.newOid(eClass);
                    }
                };
                for (final EClass eClass : classes) {
                    final BasicIfcModel targetModel = new BasicIfcModel(
                            pluginManager.getMetaDataManager().getPackageMetaData("ifc2x3tc1"), null);
                    ModelHelper modelHelper = new ModelHelper(bimServer.getMetaDataManager(), targetModel);
                    modelHelper.setOidProvider(oidProvider);
                    modelHelper.setObjectIDM(idm);

                    IdEObject newOwnerHistory = modelHelper.copyBasicObjects(model, bigMap);

                    for (IdEObject idEObject : model.getAll(eClass)) {
                        IdEObject newObject = modelHelper.copy(idEObject, false,
                                ModelHelper.createObjectIdm(idEObject.eClass()));
                        modelHelper.copyDecomposes(idEObject, newOwnerHistory);
                        bigMap.put(newObject, idEObject);
                        if (eClass.getName().equals("IfcWallStandardCase")) {
                            EStructuralFeature hasOpeningsFeature = idEObject.eClass()
                                    .getEStructuralFeature("HasOpenings");
                            for (IdEObject ifcRelVoidsElement : ((List<IdEObject>) idEObject
                                    .eGet(hasOpeningsFeature))) {
                                bigMap.put(modelHelper.copy(ifcRelVoidsElement, false), ifcRelVoidsElement);
                                EStructuralFeature relatedOpeningElementFeature = ifcRelVoidsElement.eClass()
                                        .getEStructuralFeature("RelatedOpeningElement");
                                IdEObject relatedOpeningElement = (IdEObject) ifcRelVoidsElement
                                        .eGet(relatedOpeningElementFeature);
                                if (relatedOpeningElement != null) {
                                    bigMap.put(modelHelper.copy(relatedOpeningElement, false),
                                            relatedOpeningElement);
                                }
                            }
                        }
                    }

                    executor.submit(new Runner(eClass, renderEnginePlugin, databaseSession, settings, store,
                            targetModel, ifcSerializerPlugin, model, pid, rid, bigMap, renderEngineFilter,
                            generateGeometryResult));
                }
                executor.shutdown();
                executor.awaitTermination(24, TimeUnit.HOURS);
            }

            long end = System.nanoTime();
            LOGGER.info("Rendertime: " + ((end - start) / 1000000) + "ms, " + "Reused: "
                    + Formatters.bytesToString(bytesSaved.get()) + ", Total: "
                    + Formatters.bytesToString(totalBytes.get()) + ", Final: "
                    + Formatters.bytesToString(totalBytes.get() - bytesSaved.get()));
        } catch (Exception e) {
            LOGGER.error("", e);
            throw new GeometryGeneratingException(e);
        }
        return generateGeometryResult;
    }

    private int hash(GeometryData geometryData) {
        int hashCode = 0;
        if (geometryData.getIndices() != null) {
            hashCode += Arrays.hashCode(geometryData.getIndices());
        }
        if (geometryData.getVertices() != null) {
            hashCode += Arrays.hashCode(geometryData.getVertices());
        }
        if (geometryData.getNormals() != null) {
            hashCode += Arrays.hashCode(geometryData.getNormals());
        }
        if (geometryData.getMaterialIndices() != null) {
            hashCode += Arrays.hashCode(geometryData.getMaterialIndices());
        }
        if (geometryData.getMaterials() != null) {
            hashCode += Arrays.hashCode(geometryData.getMaterials());
        }
        return hashCode;
    }

    private void processExtends(GeometryInfo geometryInfo, float[] transformationMatrix, float[] vertices,
            int index, GenerateGeometryResult generateGeometryResult) {
        float x = vertices[index];
        float y = vertices[index + 1];
        float z = vertices[index + 2];
        float[] result = new float[4];
        Matrix.multiplyMV(result, 0, transformationMatrix, 0, new float[] { x, y, z, 1 }, 0);
        x = result[0];
        y = result[1];
        z = result[2];
        geometryInfo.getMinBounds().setX(Math.min(x, geometryInfo.getMinBounds().getX()));
        geometryInfo.getMinBounds().setY(Math.min(y, geometryInfo.getMinBounds().getY()));
        geometryInfo.getMinBounds().setZ(Math.min(z, geometryInfo.getMinBounds().getZ()));
        geometryInfo.getMaxBounds().setX(Math.max(x, geometryInfo.getMaxBounds().getX()));
        geometryInfo.getMaxBounds().setY(Math.max(y, geometryInfo.getMaxBounds().getY()));
        geometryInfo.getMaxBounds().setZ(Math.max(z, geometryInfo.getMaxBounds().getZ()));

        generateGeometryResult.getMinBounds().setX(Math.min(x, generateGeometryResult.getMinBounds().getX()));
        generateGeometryResult.getMinBounds().setY(Math.min(y, generateGeometryResult.getMinBounds().getY()));
        generateGeometryResult.getMinBounds().setZ(Math.min(z, generateGeometryResult.getMinBounds().getZ()));
        generateGeometryResult.getMaxBounds().setX(Math.max(x, generateGeometryResult.getMaxBounds().getX()));
        generateGeometryResult.getMaxBounds().setY(Math.max(y, generateGeometryResult.getMaxBounds().getY()));
        generateGeometryResult.getMaxBounds().setZ(Math.max(z, generateGeometryResult.getMaxBounds().getZ()));
    }

    private byte[] floatArrayToByteArray(float[] vertices) {
        if (vertices == null) {
            return null;
        }
        ByteBuffer buffer = ByteBuffer.wrap(new byte[vertices.length * 4]);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        FloatBuffer asFloatBuffer = buffer.asFloatBuffer();
        for (float f : vertices) {
            asFloatBuffer.put(f);
        }
        return buffer.array();
    }

    private byte[] intArrayToByteArray(int[] indices) {
        if (indices == null) {
            return null;
        }
        ByteBuffer buffer = ByteBuffer.wrap(new byte[indices.length * 4]);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        IntBuffer asIntBuffer = buffer.asIntBuffer();
        for (int i : indices) {
            asIntBuffer.put(i);
        }
        return buffer.array();
    }

    private void setTransformationMatrix(GeometryInfo geometryInfo, float[] transformationMatrix) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(16 * 4);
        byteBuffer.order(ByteOrder.nativeOrder());
        FloatBuffer asFloatBuffer = byteBuffer.asFloatBuffer();
        for (float f : transformationMatrix) {
            asFloatBuffer.put(f);
        }
        geometryInfo.setTransformation(byteBuffer.array());
    }

    private static boolean almostTheSame(float f1, float f2, float maxDiff) {
        return Math.abs(f1 - f2) < maxDiff;
    }

    /**
     * This function should return a transformation matrix (with translation and
     * rotation, no scaling) overlaying triangle V on U Assumed is that the
     * triangles are indeed the same and the order of the vertices is also the
     * same (shifts are not allowed) This function can probably be optimized for
     * speed and also the LOC can probably be reduced
     * 
     * @param originalV1
     * @param originalV2
     * @param originalV3
     * @param u1
     * @param u2
     * @param u3
     * @return
     */
    private static float[] getTransformationMatrix(float[] originalV1, float[] originalV2, float[] originalV3,
            float[] u1, float[] u2, float[] u3, float maxDiff) {
        float[] v1 = copy(originalV1);
        float[] v2 = copy(originalV2);
        float[] v3 = copy(originalV3);
        u1 = copy(u1);
        u2 = copy(u2);
        u3 = copy(u3);

        float transX = u1[0] - v1[0];
        float transY = u1[1] - v1[1];
        float transZ = u1[2] - v1[2];

        float translation[] = new float[16];
        Matrix.setIdentityM(translation, 0);
        Matrix.translateM(translation, 0, u1[0], u1[1], u1[2]);

        float[] toZeroTranslation = new float[16];
        Matrix.setIdentityM(toZeroTranslation, 0);
        Matrix.translateM(toZeroTranslation, 0, -v1[0], -v1[1], -v1[2]);

        if (almostTheSame(v2[0] + transX, u2[0], maxDiff) && almostTheSame(v2[1] + transY, u2[1], maxDiff)
                && almostTheSame(v2[2] + transZ, u2[2], maxDiff) && almostTheSame(v3[0] + transX, u3[0], maxDiff)
                && almostTheSame(v3[1] + transY, u3[1], maxDiff) && almostTheSame(v3[2] + transZ, u3[2], maxDiff)) {
            // The other two points are already the same, to there was no
            // rotation
            return translation;
        }

        // Normalize both triangles to their first vertex
        subtract(u2, u1);
        subtract(u3, u1);
        subtract(u1, u1);

        subtract(v2, v1);
        subtract(v3, v1);
        subtract(v1, v1);

        float[] u2CrossV2 = Vector.crossProduct(u2, v2);
        float[] r2 = new float[16];
        Matrix.setIdentityM(r2, 0);
        float[] r2v2 = new float[4];
        if (!equalsAlmost(u2, v2, maxDiff)) {
            float u2InV2 = Vector.dot(u2, v2);
            float[] axis = u2CrossV2;
            if (axis[0] == 0 && axis[1] == 0 && axis[2] == 0) {
                axis = new float[] { u2[1], -u2[0], 0, 0 };
            }
            Matrix.rotateM(r2, 0, (float) Math.toDegrees(Math.atan2(Vector.length(u2CrossV2), u2InV2)), axis[0],
                    axis[1], axis[2]);

            Matrix.multiplyMV(r2v2, 0, r2, 0, new float[] { v2[0], v2[1], v2[2], 1 }, 0);

            if (!equalsAlmost(r2v2, u2, maxDiff)) {
                Matrix.setIdentityM(r2, 0);
                Matrix.rotateM(r2, 0, -(float) Math.toDegrees(Math.atan2(Vector.length(u2CrossV2), u2InV2)),
                        axis[0], axis[1], axis[2]);
                Matrix.multiplyMV(r2v2, 0, r2, 0, new float[] { v2[0], v2[1], v2[2], 1 }, 0);
                if (!equalsAlmost(r2v2, u2, maxDiff)) {
                    return null;
                }
            }
        } else {
            r2v2 = copy(v2);
        }

        float[] r2v3 = new float[4];
        Matrix.multiplyMV(r2v3, 0, r2, 0, new float[] { v3[0], v3[1], v3[2], 1 }, 0);

        float[] r3 = new float[16];
        Matrix.setIdentityM(r3, 0);

        float angleDegrees = (float) Math.toDegrees(getPlaneAngle(v1, r2v2, r2v3, u1, u2, u3));

        Matrix.rotateM(r3, 0, angleDegrees, r2v2[0], r2v2[1], r2v2[2]);

        float[] r3v3 = new float[4];
        Matrix.multiplyMV(r3v3, 0, r3, 0, new float[] { r2v3[0], r2v3[1], r2v3[2], 1 }, 0);

        float[] r2v1 = new float[4];
        Matrix.multiplyMV(r2v1, 0, r2, 0, new float[] { v1[0], v1[1], v1[2], 1 }, 0);

        float[] r3v1 = new float[4];
        Matrix.multiplyMV(r3v1, 0, r3, 0, new float[] { r2v1[0], r2v1[1], r2v1[2], 1 }, 0);

        if (!equalsAlmost(r3v3, u3, maxDiff)) {
            Matrix.setIdentityM(r3, 0);
            Matrix.rotateM(r3, 0, -angleDegrees, r2v2[0], r2v2[1], r2v2[2]);
            Matrix.multiplyMV(r3v3, 0, r3, 0, new float[] { r2v3[0], r2v3[1], r2v3[2], 1 }, 0);
            float[] r3v2 = new float[4];
            Matrix.multiplyMV(r3v2, 0, r3, 0, new float[] { r2v2[0], r2v2[1], r2v2[2], 1 }, 0);
            if (!equalsAlmost(r3v3, u3, maxDiff) || !equalsAlmost(r3v2, u2, maxDiff)) {
                return null;
            }
        }
        float[] subResult = new float[16];
        float[] subResult2 = new float[16];
        float[] subResult3 = new float[16];
        float[] totalResult = new float[16];
        float[] startMatrix = new float[16];
        Matrix.setIdentityM(startMatrix, 0);

        Matrix.multiplyMM(subResult, 0, toZeroTranslation, 0, startMatrix, 0);
        Matrix.multiplyMM(subResult2, 0, r2, 0, subResult, 0);
        Matrix.multiplyMM(subResult3, 0, r3, 0, subResult2, 0);
        Matrix.multiplyMM(totalResult, 0, translation, 0, subResult3, 0);

        return totalResult;
    }

    /**
     * Get the angle in radians between two planes
     * 
     * @param v1
     * @param v2
     * @param v3
     * @param u1
     * @param u2
     * @param u3
     * @return
     */
    private static double getPlaneAngle(float[] v1, float[] v2, float[] v3, float[] u1, float[] u2, float[] u3) {
        float[] cross1 = Vector.crossProduct(new float[] { v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2] },
                new float[] { v3[0] - v1[0], v3[1] - v1[1], v3[2] - v1[2] });
        float[] cross2 = Vector.crossProduct(new float[] { u2[0] - u1[0], u2[1] - u1[1], u2[2] - u1[2] },
                new float[] { u3[0] - u1[0], u3[1] - u1[1], u3[2] - u1[2] });

        float num = Vector.dot(cross1, cross2);
        float den = Vector.length(cross1) * Vector.length(cross2);

        float a = num / den;
        if (a > 1) {
            a = 1;
        }
        if (a < -1) {
            a = -1;
        }
        double result = Math.acos(a);

        if (Double.isNaN(result)) {
            System.out.println();
        }
        return result;
    }

    private static boolean equalsAlmost(float[] r2v2, float[] u2, float maxDiff) {
        for (int i = 0; i < 3; i++) {
            if (!almostTheSame(r2v2[i], u2[i], maxDiff)) {
                return false;
            }
        }
        return true;
    }

    private static float[] copy(float[] v1) {
        float[] result = new float[v1.length];
        System.arraycopy(v1, 0, result, 0, v1.length);
        return result;
    }

    private static boolean test(float[] v1, float[] v2, float[] transformationMatrix, float maxDiff) {
        float[] resultVector = new float[4];
        Matrix.multiplyMV(resultVector, 0, transformationMatrix, 0, new float[] { v1[0], v1[1], v1[2], 1 }, 0);
        normalize(resultVector);
        boolean theSame = true;
        for (int i = 0; i < 3; i++) {
            if (!almostTheSame(resultVector[i], v2[i], maxDiff)) {
                theSame = false;
            }
        }
        if (!theSame) {
            System.out.println("Difference");
            Vector.dump("Was", v1);
            Vector.dump("Became", resultVector);
            Vector.dump("Should be", v2);
            System.out.println();
            return false;
        }
        return true;
    }

    private static void normalize(float[] resultVector) {
        resultVector[0] = resultVector[0] * resultVector[3];
        resultVector[1] = resultVector[1] * resultVector[3];
        resultVector[2] = resultVector[2] * resultVector[3];
        resultVector[3] = 1;
    }

    private static void subtract(float[] u2, float[] v1) {
        u2[0] = u2[0] - v1[0];
        u2[1] = u2[1] - v1[1];
        u2[2] = u2[2] - v1[2];
    }

    public static void main(String[] args) {
        float maxDiff = 0.1f;

        test1(maxDiff);
        test2(maxDiff);

        Random random = new Random();
        for (int i = 0; i < 10; i++) {
            float[] matrix = new float[16];
            Matrix.setIdentityM(matrix, 0);
            for (int j = 0; j < 10; j++) {
                Matrix.rotateM(matrix, 0, random.nextFloat() * 360, random.nextFloat(), random.nextFloat(),
                        random.nextFloat());
            }

            float[] v1 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
            float[] v2 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
            float[] v3 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
            float[] r1 = new float[4];
            float[] r2 = new float[4];
            float[] r3 = new float[4];
            Matrix.multiplyMV(r1, 0, matrix, 0, v1, 0);
            Matrix.multiplyMV(r2, 0, matrix, 0, v2, 0);
            Matrix.multiplyMV(r3, 0, matrix, 0, v3, 0);

            float[] calculatedMatrix = getTransformationMatrix(v1, v2, v3, r1, r2, r3, maxDiff);

            test(v1, r1, calculatedMatrix, maxDiff);
            test(v2, r2, calculatedMatrix, maxDiff);
            test(v3, r3, calculatedMatrix, maxDiff);

            for (int j = 0; j < 10; j++) {
                float[] q1 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
                float[] q2 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
                float[] q3 = new float[] { random.nextFloat(), random.nextFloat(), random.nextFloat(), 1 };
                float[] b1 = new float[4];
                float[] b2 = new float[4];
                float[] b3 = new float[4];
                Matrix.multiplyMV(b1, 0, matrix, 0, q1, 0);
                Matrix.multiplyMV(b2, 0, matrix, 0, q2, 0);
                Matrix.multiplyMV(b3, 0, matrix, 0, q3, 0);

                test(q1, b1, calculatedMatrix, maxDiff);
                test(q2, b2, calculatedMatrix, maxDiff);
                test(q3, b3, calculatedMatrix, maxDiff);
            }
        }
    }

    private static void test1(float maxDiff) {
        float[] v1 = new float[] { 1, 2, 0 };
        float[] v2 = new float[] { 1, 1, 0 };
        float[] v3 = new float[] { 3, 2, 0 };
        float[] u1 = new float[] { 0, 2, 0 };
        float[] u2 = new float[] { -1, 2, 0 };
        float[] u3 = new float[] { 0, 2, 2 };

        float[] transformationMatrix = getTransformationMatrix(v1, v2, v3, u1, u2, u3, maxDiff);

        test(v1, u1, transformationMatrix, maxDiff);
        test(v2, u2, transformationMatrix, maxDiff);
        test(v3, u3, transformationMatrix, maxDiff);
    }

    private static void test2(float maxDiff) {
        float[] v1 = new float[] { 3, 0, 0 };
        float[] v2 = new float[] { 4, 0, 0 };
        float[] v3 = new float[] { 4, 1, 0 };
        float[] u1 = new float[] { 1, 3, 0 };
        float[] u2 = new float[] { 0, 3, 0 };
        float[] u3 = new float[] { 0, 2, 0 };

        float[] transformationMatrix = getTransformationMatrix(v1, v2, v3, u1, u2, u3, maxDiff);

        test(v1, u1, transformationMatrix, maxDiff);
        test(v2, u2, transformationMatrix, maxDiff);
        test(v3, u3, transformationMatrix, maxDiff);
    }

    private void returnCachedData(IfcModelInterface model, GeometryCache geometryCache,
            DatabaseSession databaseSession, int pid, int rid) throws BimserverDatabaseException {
        EClass productClass = model.getPackageMetaData().getEClass("IfcProduct");
        List<IdEObject> products = model.getAllWithSubTypes(productClass);
        for (IdEObject ifcProduct : products) {
            GeometryCacheEntry geometryCacheEntry = geometryCache.get(ifcProduct.getExpressId());
            if (geometryCacheEntry != null) {
                GeometryData geometryData = databaseSession.create(GeometryPackage.eINSTANCE.getGeometryData(), pid,
                        rid);
                geometryData.setVertices(geometryCacheEntry.getVertices().array());
                geometryData.setNormals(geometryCacheEntry.getNormals().array());
                GeometryInfo geometryInfo = databaseSession.create(GeometryPackage.eINSTANCE.getGeometryInfo(), pid,
                        rid);
                Vector3f min = databaseSession.create(GeometryPackage.eINSTANCE.getVector3f(), pid, rid);
                min.setX(geometryCacheEntry.getGeometryInfo().getMinBounds().getX());
                min.setY(geometryCacheEntry.getGeometryInfo().getMinBounds().getY());
                min.setZ(geometryCacheEntry.getGeometryInfo().getMinBounds().getZ());
                Vector3f max = databaseSession.create(GeometryPackage.eINSTANCE.getVector3f(), pid, rid);
                max.setX(geometryCacheEntry.getGeometryInfo().getMaxBounds().getX());
                max.setY(geometryCacheEntry.getGeometryInfo().getMaxBounds().getY());
                max.setZ(geometryCacheEntry.getGeometryInfo().getMaxBounds().getZ());
                geometryInfo.setMinBounds(min);
                geometryInfo.setMaxBounds(max);
                geometryInfo.setData(geometryData);
                ifcProduct.eSet(ifcProduct.eClass().getEStructuralFeature("geometry"), geometryInfo);
            }
        }
    }

    private Vector3f createVector3f(PackageMetaData packageMetaData, IfcModelInterface model, float defaultValue,
            DatabaseSession session, boolean store, int pid, int rid)
            throws BimserverDatabaseException, IfcModelInterfaceException {
        Vector3f vector3f = null;
        if (store) {
            vector3f = model.createAndAdd(GeometryPackage.eINSTANCE.getVector3f(),
                    session.newOid(GeometryPackage.eINSTANCE.getVector3f()));
            session.store(vector3f, pid, rid);
        } else {
            vector3f = GeometryFactory.eINSTANCE.createVector3f();
        }
        vector3f.setX(defaultValue);
        vector3f.setY(defaultValue);
        vector3f.setZ(defaultValue);
        return vector3f;
    }
}