diff --git a/Comparison/pom.xml b/Comparison/pom.xml
index 00895559491d9bcfb94d38c3a59f0313b2bf5de1..6480685ee3f5cec1d275ec0ec7b113af325faba6 100644
--- a/Comparison/pom.xml
+++ b/Comparison/pom.xml
@@ -24,6 +24,7 @@
                        <publicPackage>cz.fidentis.analyst.visitors.*</publicPackage>
                        <publicPackage>cz.fidentis.analyst.visitors.mesh.*</publicPackage>
                        <publicPackage>cz.fidentis.analyst.visitors.kdtree.*</publicPackage>
+                       <publicPackage>cz.fidentis.analyst.visitors.octree.*</publicPackage>
                    </publicPackages>
                 </configuration>
             </plugin>
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitor.java b/Comparison/src/main/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitor.java
new file mode 100644
index 0000000000000000000000000000000000000000..a2740c97cc2b8de6cf3b9568a8039b3e4aedda08
--- /dev/null
+++ b/Comparison/src/main/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitor.java
@@ -0,0 +1,436 @@
+package cz.fidentis.analyst.visitors.octree;
+
+import cz.fidentis.analyst.mesh.MeshVisitor;
+import cz.fidentis.analyst.mesh.core.MeshFacet;
+import cz.fidentis.analyst.mesh.core.MeshPoint;
+import cz.fidentis.analyst.mesh.core.MeshTriangle;
+import cz.fidentis.analyst.octree.OctNode;
+import cz.fidentis.analyst.octree.Octree;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3d;
+
+/**
+ * This visitor goes through all the notes in mainFacets and for each point
+ * it calculates the distance to the other meshes (octrees)
+ * by throwing a ray from that point
+ * 
+ * @author Enkh-Undral EnkhBayar
+ */
+public class OctreeArrayIntersectionVisitor extends MeshVisitor {
+    /** 
+     * main mesh facets from which the intersections are calculated
+     */
+    private final Set<MeshFacet> mainFacets;
+    
+    /** 
+     * helper set for calculations, 
+     * used to cache which triangles were already calculated
+     */
+    private Set<MeshTriangle> calculatedTriangles = new HashSet<>();
+    
+    /**
+     * calculated intersections.
+     * In format {@code <main mainFacet, <index, <mainFacet, intersection>>>}
+     * main mainFacet - main mainFacet saved in mainFacets which was used for intersection calculations
+     * index          - index of the starting point in main mainFacet, 
+     *                  said point can be accessed by mainFacet.get(index)
+     * mainFacet      - mainFacet holding the intersection point
+     * intersection   - intersection of ray coming from starting point and mainFacet
+     */
+    private Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> intersections = new HashMap<>();
+    
+    /**
+     * Constructor
+     *
+     * @param mainFacet the main Mesh mainFacet from which to calculate the distances.
+     * Must not be {@code null}
+     * @throws IllegalArgumentException if some parameter is wrong
+     */
+    public OctreeArrayIntersectionVisitor(MeshFacet mainFacet) {
+        this(new HashSet<>(Collections.singleton(mainFacet)));
+        if (mainFacet == null) {
+            throw new IllegalArgumentException("mainFacet");
+        }
+    }
+    
+    /**
+     * Constructor
+     *
+     * @param mainFacets the main Mesh mainFacet from which to calculate the distances. 
+     *                   Must not be {@code null}
+     * @throws IllegalArgumentException if some parameter is wrong
+     */
+    public OctreeArrayIntersectionVisitor(Set<MeshFacet> mainFacets) {
+        if (mainFacets == null || mainFacets.isEmpty() || 
+                (mainFacets.size() == 1 && mainFacets.contains(null))) {
+            throw new IllegalArgumentException("mainFacets");
+        }
+        this.mainFacets = mainFacets;
+    }
+    
+    /**
+     * @return intersections from point in mainFacets to other mesh facets
+     */
+    public Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> getIntersections() {
+        Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> unmodifiableMap = new HashMap<>();
+        for (var entry : intersections.entrySet()) {
+            MeshFacet mainFacet = entry.getKey();
+            Map<Integer, Map<MeshFacet, Point3d>> unmodifiableInnerMap = new HashMap<>();
+            for (var innerEntry : entry.getValue().entrySet()) {
+                unmodifiableInnerMap.put(innerEntry.getKey(), Collections.unmodifiableMap(innerEntry.getValue()));
+            }
+            unmodifiableMap.put(mainFacet, Collections.unmodifiableMap(unmodifiableInnerMap));
+        }
+        return Collections.unmodifiableMap(unmodifiableMap);
+    }
+    
+    @Override
+    public void visitMeshFacet(MeshFacet facet) {
+        if (facet == null) {
+            throw new IllegalArgumentException("facet is null");
+        }
+        Octree octree = new Octree(facet);
+        for (MeshFacet mainFacet : mainFacets) {
+            int i = 0;
+            for (MeshPoint meshPoint : mainFacet.getVertices()) {
+                Vector3d vector = meshPoint.getNormal();
+                Point3d point = meshPoint.getPosition();
+                calculatedTriangles.clear();
+                calculateDistanceInPoint(mainFacet, i, octree.getRoot(), point, vector, octree.getMinLen());
+                vector.scale(-1);
+                calculatedTriangles.clear();
+                calculateDistanceInPoint(mainFacet, i, octree.getRoot(), point, vector, octree.getMinLen());
+                calculatedTriangles.clear();
+                i++;
+            }
+        }
+    }
+
+    /**
+     * updates the intersections. If there are already is an intersection with 
+     * the facet, the one with lower distance is kept and other discarded.
+     * 
+     * @param mainFacet mainFacet holding the starting point.
+     *                  has to be in {@code this.mainFacets}
+     *                  Must not be {@code null}.
+     * @param startPointIndex index of the point in mainFacet from which the 
+     *                        intersection was calculated.
+     *                        mainFacet has to hold a point with startPointIndex
+     *                        Must not be {@code null)
+     * @param facet mainFacet in the node with the intersection.
+              Must not be {@code null)
+     * @param intersection intersection of mainFacet and ray from start.
+                     Must not be {@code null).
+     */
+    private void updateIntersections(MeshFacet mainFacet, int startPointIndex, MeshFacet facet, Point3d intersection) {
+        if (mainFacet == null) {
+            throw new IllegalArgumentException("start is null");
+        }
+        if (facet == null) {
+            throw new IllegalArgumentException("facet is null");
+        }
+        if (intersection == null) {
+            throw new IllegalArgumentException("intersection is null");
+        }
+        Map<Integer, Map<MeshFacet, Point3d>> startingPointMap = new HashMap<>();
+        Map<MeshFacet, Point3d> intersectionsMap = new HashMap<>();
+        if (intersections.containsKey(mainFacet)) {
+            startingPointMap = intersections.get(mainFacet);
+            if (startingPointMap.containsKey(startPointIndex)) {
+                intersectionsMap = startingPointMap.get(startPointIndex);
+                if (intersectionsMap.containsKey(facet)) {
+                    Point3d oldIntersection = intersectionsMap.get(facet);
+                    Point3d startingPoint = mainFacet.getVertex(startPointIndex).getPosition();
+                    if (startingPoint.distance(oldIntersection) <= startingPoint.distance(intersection)) {
+                        return;
+                    }
+                }
+            }
+        }
+        intersectionsMap.put(facet, intersection);
+        startingPointMap.put(startPointIndex, intersectionsMap);
+        intersections.put(mainFacet, startingPointMap);
+    }
+    
+    /**
+     * Goes through the octree provided in param node and calculates all the 
+     * distances from that node to other meshes
+     * 
+     * @param mainFacet mainFacet holding the starting point.
+     *                  has to be in {@code this.mainFacets}
+     *                  Must not be {@code null}
+     * @param startPointIndex index of the point in mainFacet from which the 
+     *                        intersection is calculated.
+     *                        mainFacet has to hold a point with startPointIndex
+     *                        Must not be {@code null}
+     * @param node to calculate the distances to.
+     *             Must not be {@code null}
+     * @param p point in this cube (node) and if node is internal node 
+     *          it is also in the next cube. 
+     *          Must not be {@code null}.
+     * @param v vector from starting point in main node which defines the ray
+     *          Must not be {@code null} nor (0, 0, 0).
+     * @param minLen the smallest length in any cube (node) in node. 
+     *        Must not be equal to 0
+     * @return the point in next cube through which the ray passes
+     */
+    private Point3d calculateDistanceInPoint(MeshFacet mainFacet, int origPointIndex, OctNode node, Point3d p, Vector3d v, double minLen) {
+        if (mainFacet == null) {
+            throw new IllegalArgumentException("meshFacet is null");
+        }
+        if (node == null) {
+            throw new IllegalArgumentException("node is null");
+        }
+        if (p == null) {
+            throw new IllegalArgumentException("p is null");
+        }
+        if (v == null) {
+            throw new IllegalArgumentException("v is null");
+        }
+        if (v == new Vector3d()) {
+            throw new IllegalArgumentException("v is (0, 0, 0)");
+        }
+        if (minLen == 0) {
+            throw new IllegalArgumentException("minLen is equal to 0");
+        }
+        Point3d origPoint = mainFacet.getVertex(origPointIndex).getPosition();
+
+        if (!node.isLeafNode()) {
+            int index = getOctantIndex(p, node);
+            OctNode child;
+            if (index == -1) {
+                p = rayCubeIntersection(origPoint, v, node, false);
+                p = getPointInNextCube(p, v, node, minLen);
+                index = getOctantIndex(p, node);
+            }
+            while (index != -1) {
+                child = node.getOctant(index);
+                p = calculateDistanceInPoint(mainFacet, origPointIndex, child, p, v, minLen);
+                index = getOctantIndex(p, node);
+            }
+            return p;
+        }
+        
+        for (Map.Entry<MeshFacet, Integer> entry : node.getFacets().entrySet()) {
+            MeshFacet facet = entry.getKey();
+            Integer index = entry.getValue();
+            for (MeshTriangle triangle : facet.getAdjacentTriangles(index)) {
+                if (calculatedTriangles.contains(triangle)) {
+                    continue;
+                }
+                calculatedTriangles.add(triangle);
+                Point3d intersection = triangle.getRayIntersection(origPoint, v);
+                if (intersection != null) {
+                    updateIntersections(
+                            mainFacet,
+                            origPointIndex,
+                            facet,
+                            intersection
+                    );
+                }
+            }
+        }
+        Point3d point = rayCubeIntersection(origPoint, v, node, true);
+        if (point == null) {
+            throw new RuntimeException("Didnt find intersection with bounding box");
+        }
+        return getPointInNextCube(point, v, node, minLen);
+    }
+    
+    /**
+     * Calculates the point in the next cube. 
+     * 
+     * @param p point on the side / edge of the cube
+     *          Must not be {@code null}.
+     * @param v vector of the ray
+     *          Must not be {@code null} nor (0, 0, 0).
+     * @param cube cube from which to calculate point in next cube is needed
+     *             Must not be {@code null}.
+     * @param minLen the smallest length in any cube (node) in node. 
+     *        Must not be equal to 0
+     * @return the point in next cube through which the ray passes
+     */
+    private Point3d getPointInNextCube(Point3d p, Vector3d v, OctNode cube, double minLen) {
+        if (p == null) {
+            throw new IllegalArgumentException("p is null");
+        }
+        if (v == null) {
+            throw new IllegalArgumentException("v is null");
+        }
+        if (v == new Vector3d()) {
+            throw new IllegalArgumentException("v is (0, 0, 0)");
+        }
+        if (cube == null) {
+            throw new IllegalArgumentException("cube is null");
+        }
+        if (minLen == 0) {
+            throw new IllegalArgumentException("minLen is equal to 0");
+        }
+        double[] resultCoor = {p.x, p.y, p.z};
+        double[] vCoor = {v.x, v.y, v.z};
+        Point3d small = cube.getSmallBoundary();
+        double[] smallCoor = {small.x, small.y, small.z};
+        Point3d large = cube.getLargeBoundary();
+        double[] largeCoor = {large.x, large.y, large.z};
+        for (int i = 0; i < 3; i++) {
+            int sign;
+            if (vCoor[i] < 0) {
+                sign = -1;
+            } else if (vCoor[i] > 0) {
+                sign = 1;
+            } else {
+                continue;
+            }
+            if (resultCoor[i] == smallCoor[i]) {
+                resultCoor[i] += sign * (minLen / 2);
+            } else if (resultCoor[i] == largeCoor[i]) {
+                resultCoor[i] += sign * (minLen / 2);
+            }
+        }
+        Point3d resultPoint = new Point3d(resultCoor[0], resultCoor[1], resultCoor[2]);
+        if (resultPoint == p) {
+            throw new IllegalArgumentException("p is not on the edges of the cube");
+        }
+        return resultPoint;
+    }
+    
+    /**
+     * Calculates the intersection between ray and the cube sides
+     * 
+     * @param p starting point of the ray.
+     *          Must not be {@code null}.
+     * @param v vector of the ray
+     *          Must not be {@code null} nor (0, 0, 0).
+     * @param cube cube with the sides
+     *          Must not be {@code null}.
+     * @param gotInsideOnce boolean paramater that tells us if ray got inside 
+     *                      the most outer cube. If it did we care about the 
+     *                      second point in the direction of the ray instead 
+     *                      of the first one
+     * @return returns the intersection in the direction of the ray
+     */
+    private Point3d rayCubeIntersection(Point3d p, Vector3d v, OctNode cube, boolean gotInsideOnce) {
+        if (p == null) {
+            throw new IllegalArgumentException("p is null");
+        }
+        if (v == null) {
+            throw new IllegalArgumentException("v is null");
+        }
+        if (v == new Vector3d()) {
+            throw new IllegalArgumentException("v is (0, 0, 0)");
+        }
+        if (cube == null) {
+            throw new IllegalArgumentException("cube is null");
+        }
+        Point3d smallBoundary = cube.getSmallBoundary();
+        Point3d largeBoundary = cube.getLargeBoundary();
+        Vector3d[] planeNormals = {new Vector3d(1, 0, 0), new Vector3d(0, 1, 0), new Vector3d(0, 0, 1)};
+        double[] t = {-Double.MAX_VALUE, Double.MAX_VALUE};
+        for (Vector3d planeNormal : planeNormals) {
+            double np = planeNormal.dot(new Vector3d(p));
+            double vp = planeNormal.dot(v);
+            if (vp == 0) {
+                continue;
+            }
+            double[] offset = {planeNormal.dot(new Vector3d(smallBoundary)),
+                               planeNormal.dot(new Vector3d(largeBoundary))};
+            double[] tTmp = {(offset[0] - np) / vp, (offset[1] - np) / vp};
+            if (tTmp[0] > tTmp[1]) {
+                double tmp = tTmp[0];
+                tTmp[0] = tTmp[1];
+                tTmp[1] = tmp;
+            }
+            t[0] = Double.max(tTmp[0], t[0]);
+            t[1] = Double.min(tTmp[1], t[1]);
+        }
+        if (t[0] > t[1]) {
+            return null;
+        }
+        Point3d point = new Point3d(p);
+        Vector3d vector = new Vector3d(v);
+        if (gotInsideOnce) {
+            vector.scale(t[1]);
+        } else {
+            vector.scale(t[0]);
+        }
+        point.add(vector);
+        return point;
+    }
+    
+    /** 
+     * checks if p is inside the cube
+     * 
+     * @param p point to be checked.
+     *          Must not be {@code null}.
+     * @param cube cube with the bounding box.
+     *             Must not be {@code null}.
+     * @return True if p is inside the cube false otherwise
+     */
+    private boolean isPointInCube(Point3d p, OctNode cube) {
+        if (p == null) {
+            throw new IllegalArgumentException("p is null");
+        }
+        if (cube == null) {
+            throw new IllegalArgumentException("cube is null");
+        }
+        Point3d smallBoundary = cube.getSmallBoundary();
+        Point3d largeBoundary = cube.getLargeBoundary();
+        if (p.x < smallBoundary.x) {
+            return false;
+        }
+        if (p.y < smallBoundary.y) {
+            return false;
+        }
+        if (p.z < smallBoundary.z) {
+            return false;
+        }
+        if (p.x > largeBoundary.x) {
+            return false;
+        }
+        if (p.y > largeBoundary.y) {
+            return false;
+        }
+        return p.z <= largeBoundary.z;
+    }
+    
+    /**
+     * Calculates the index of the child which can house the point provided
+     * 
+     * @param p point, must not be {@code null}
+     * @param cube cube, must not be {@code null}
+     * @return index (0 - 7) of child in cube. -1 if p is not inside the cube
+     */
+    private int getOctantIndex(Point3d p, OctNode cube) {
+        if (p == null) {
+            throw new IllegalArgumentException("p is null");
+        }
+        if (cube == null) {
+            throw new IllegalArgumentException("cube is null");
+        }
+        if (!isPointInCube(p, cube)) {
+            return -1;
+        }
+        Point3d smallBoundary = cube.getSmallBoundary();
+        Point3d largeBoundary = cube.getLargeBoundary();
+        Point3d middlePoint = new Point3d(
+                (smallBoundary.x + largeBoundary.x) / 2,
+                (smallBoundary.y + largeBoundary.y) / 2,
+                (smallBoundary.z + largeBoundary.z) / 2);
+        int octantIndex = 0;
+        if (p.x > middlePoint.x) {
+            octantIndex += 4;
+        }
+        if (p.y > middlePoint.y) {
+            octantIndex += 2;
+        }
+        if (p.z > middlePoint.z) {
+            octantIndex += 1;
+        }
+        return octantIndex;
+    }
+}
diff --git a/Comparison/src/test/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitorTest.java b/Comparison/src/test/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..da31133a5b51f3a3f77452dad6cf785a55b0e13a
--- /dev/null
+++ b/Comparison/src/test/java/cz/fidentis/analyst/visitors/octree/OctreeArrayIntersectionVisitorTest.java
@@ -0,0 +1,188 @@
+package cz.fidentis.analyst.visitors.octree;
+
+import cz.fidentis.analyst.mesh.core.CornerTableRow;
+import cz.fidentis.analyst.mesh.core.MeshFacet;
+import cz.fidentis.analyst.mesh.core.MeshFacetImpl;
+import cz.fidentis.analyst.mesh.core.MeshPoint;
+import cz.fidentis.analyst.mesh.core.MeshPointImpl;
+import cz.fidentis.analyst.octree.Octree;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3d;
+import org.apache.commons.lang3.tuple.Pair;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Enkh-Undral EnkhBayar
+ */
+public class OctreeArrayIntersectionVisitorTest {
+    
+    private MeshFacet getTrivialFacet(double offset, double size) {
+        MeshFacet facet = new MeshFacetImpl();
+        facet.addVertex(new MeshPointImpl(new Point3d(0, 0, offset), new Vector3d(0, 0, 1), new Vector3d()));
+        facet.addVertex(new MeshPointImpl(new Point3d(size, 0, offset), new Vector3d(0, 0, 1), new Vector3d()));
+        facet.addVertex(new MeshPointImpl(new Point3d(0, size, offset), new Vector3d(0, 0, 1), new Vector3d()));
+
+        facet.getCornerTable().addRow(new CornerTableRow(0, -1));
+        facet.getCornerTable().addRow(new CornerTableRow(1, -1));
+        facet.getCornerTable().addRow(new CornerTableRow(2, -1));
+
+        return facet;
+    }
+    
+    private void printIntersection(Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> map) {
+        StringBuilder builder = new StringBuilder();
+        for (var entry : map.entrySet()) {
+            MeshFacet mainFacet = entry.getKey();
+            builder.append(mainFacet.toString()).append('\n');
+            var startingPointMap = entry.getValue();
+            int i = 0;
+            for (var startingPointEntry : startingPointMap.entrySet()) {
+                if (i == startingPointMap.size() - 1) {
+                    builder.append("\\- ");
+                } else {
+                    builder.append("|- ");
+                }
+                int index = startingPointEntry.getKey();
+                builder.append(index).append(": ").append(mainFacet.getVertex(index).getPosition());
+                builder.append('\n');
+                int j = 0;
+                var intersectionMap = startingPointEntry.getValue();
+                for (var intersectionEntry : intersectionMap.entrySet()) {
+                    builder.append('\t');
+                    if (j == intersectionMap.size() - 1) {
+                        builder.append("\\- ");
+                    } else {
+                        builder.append("|- ");
+                    }
+                    builder.append(intersectionEntry.getKey());
+                    builder.append(' ');
+                    builder.append(intersectionEntry.getValue());
+                }
+                builder.append('\n');
+                i++;
+            }
+        }
+        System.out.println(builder);
+    }
+    
+    private void checkMap(Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> map, Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> correct) {
+        assertTrue(map != null, "map is null");
+        Set<MeshFacet> mapKeySet = new HashSet<>(map.keySet());
+        assertTrue(map.size() == correct.size());
+        for (var entry : correct.entrySet()) {
+            MeshFacet mainFacet = entry.getKey();
+            assertTrue(map.containsKey(mainFacet));
+            Map<Integer, Map<MeshFacet, Point3d>> startingPointMapCorrect = entry.getValue();
+            Map<Integer, Map<MeshFacet, Point3d>> startingPointMap = map.get(mainFacet);
+            assertTrue(startingPointMap.size() == startingPointMapCorrect.size());
+            Set<Integer> startingPointMapKeySet = new HashSet(startingPointMap.keySet());
+            for (var startingPointEntry : startingPointMapCorrect.entrySet()) {
+                int index = startingPointEntry.getKey();
+                assertTrue(startingPointMap.containsKey(index));
+                var intersectionsMapCorrect = startingPointEntry.getValue();
+                var intersectionsMap = startingPointMap.get(index);
+                assertTrue(intersectionsMapCorrect.size() == intersectionsMap.size());
+                Set<MeshFacet> intersectionMapKeySet = new HashSet(intersectionsMap.keySet());
+                for (var intersectionEntry : intersectionsMapCorrect.entrySet()) {
+                    MeshFacet facet = intersectionEntry.getKey();
+                    assertTrue(intersectionsMap.containsKey(facet));
+                    Point3d pointCorrect = intersectionEntry.getValue();
+                    Point3d point = intersectionsMap.get(facet);
+                    assertTrue(point.equals(pointCorrect));
+                    intersectionMapKeySet.remove(facet);
+                }
+                assertTrue(intersectionMapKeySet.isEmpty());
+                startingPointMapKeySet.remove(index);
+            }
+            assertTrue(startingPointMapKeySet.isEmpty());
+            mapKeySet.remove(mainFacet);
+        }
+        assertTrue(mapKeySet.isEmpty());
+    }
+    
+    private List<Integer> findIndices(List<Point3d> points, List<MeshPoint> vertices) {
+        List<Integer> indices = new ArrayList<>();
+        for (int i = 0; i < points.size(); i++) {
+            indices.add(-1);
+        }
+        int i = 0;
+        for (MeshPoint vertex : vertices) {
+            for (int j = 0; j < points.size(); j++) {
+                if (vertex.getPosition().equals(points.get(j))) {
+                    indices.set(j, i);
+                }
+            }
+            i++;
+        }
+        if (indices.contains(-1)) {
+            return null;
+        }
+        return indices;
+    }
+    
+    @Test
+    public void zeroCollision() {
+        MeshFacet main = getTrivialFacet(4, 0);
+        MeshFacet facet = new MeshFacetImpl();
+        facet.addVertex(new MeshPointImpl(new Point3d(1, 1, 1), new Vector3d(0, 0, 1), new Vector3d()));
+        facet.addVertex(new MeshPointImpl(new Point3d(2, 1, 1), new Vector3d(0, 0, 1), new Vector3d()));
+        facet.addVertex(new MeshPointImpl(new Point3d(1, 2, 1), new Vector3d(0, 0, 1), new Vector3d()));
+
+        facet.getCornerTable().addRow(new CornerTableRow(0, -1));
+        facet.getCornerTable().addRow(new CornerTableRow(1, -1));
+        facet.getCornerTable().addRow(new CornerTableRow(2, -1));
+        var visitor = new OctreeArrayIntersectionVisitor(main);
+        visitor.visitMeshFacet(facet);
+        var intersections = visitor.getIntersections();
+        assertTrue(intersections.isEmpty());
+    }
+
+    @Test
+    public void twoMeshesOneCollision() {
+        MeshFacet main = getTrivialFacet(1, 2);
+        MeshFacet second = getTrivialFacet(2, 1);
+        
+        List<Point3d> startingPoints = List.of(new Point3d(0, 0, 1));
+        List<Integer> indices = findIndices(startingPoints, main.getVertices());
+        assertTrue(indices != null);
+
+        var visitor = new OctreeArrayIntersectionVisitor(main);
+        visitor.visitMeshFacet(second);
+        var intersections = visitor.getIntersections();
+        Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> correct = Map.of(
+            main,
+            Map.of(
+                indices.get(0), 
+                    Map.of(second, new Point3d(0, 0, 2))
+            )
+        );
+        checkMap(intersections, correct);
+    }
+    
+    @Test
+    public void twoMeshesMultipleCollision() {
+        MeshFacet main = getTrivialFacet(2, 1);
+        MeshFacet second = getTrivialFacet(1, 2);
+        var visitor = new OctreeArrayIntersectionVisitor(main);
+        visitor.visitMeshFacet(second);
+        var intersections = visitor.getIntersections();
+        List<Point3d> startingPoints = List.of(new Point3d(0, 0, 2), new Point3d(0, 1, 2), new Point3d(1, 0, 2));
+        List<Integer> indices = findIndices(startingPoints, main.getVertices());
+        assertTrue(indices != null);
+        Map<MeshFacet, Map<Integer, Map<MeshFacet, Point3d>>> correct = Map.of(
+            main,
+            Map.of(
+                indices.get(0), Map.of(second, new Point3d(0, 0, 1)), 
+                indices.get(1), Map.of(second, new Point3d(0, 1, 1)),
+                indices.get(2), Map.of(second, new Point3d(1, 0, 1))
+            )
+        );
+        checkMap(intersections, correct);
+    }
+}
diff --git a/MeshModel/pom.xml b/MeshModel/pom.xml
index 23054306dcf92c96e7aebc9d57251be11c04f74d..f4d243e431f3719c29391c78d21ec6593794fc19 100644
--- a/MeshModel/pom.xml
+++ b/MeshModel/pom.xml
@@ -22,6 +22,7 @@
                        <publicPackage>cz.fidentis.analyst.mesh.io.*</publicPackage>
                        <publicPackage>cz.fidentis.analyst.mesh.*</publicPackage>
                        <publicPackage>cz.fidentis.analyst.kdtree.*</publicPackage>
+                       <publicPackage>cz.fidentis.analyst.octree.*</publicPackage>
                        <publicPackage>cz.fidentis.analyst.feature.*</publicPackage>
                        <!--<publicPackage>cz.fidentis.analyst.mesh.core.MeshFacet</publicPackage>-->
                        <!--<publicPackage>cz.fidentis.analyst.mesh.core.MeshPoint</publicPackage>-->
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java
index e2fedd373a13d6a374a4ced08b93299f39d931e3..8911867b1f118119e2b69a15282578cfe7a9772e 100644
--- a/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/mesh/core/MeshTriangle.java
@@ -111,6 +111,134 @@ public class MeshTriangle implements Iterable<MeshPoint> {
         return normal;
     }
     
+    /** 
+     * Computes intersection between two coplanar lines.
+     * 
+     * @param firstOrigin starting point of first line
+     *         Must not be {@code null}
+     * @param firstVector vector of the first line
+     *         Has to be normalized. Must not be {@code null}
+     * @param secondOrigin starting point of second line
+     *         Must not be {@code null}
+     * @param secondVector vector of the second line
+     *         Has to be normalized. Must not be {@code null}
+     * @return point of intersection or {@code null} if the lines are parallel
+     * 
+     * @author Enkh-Undral EnkhBayar
+     */
+    public Point3d getLinesIntersection(Point3d firstOrigin, Vector3d firstVector, Point3d secondOrigin, Vector3d secondVector) {
+        double scalar = firstVector.dot(secondVector);
+        if (1 - EPS <= scalar && scalar <= 1 + EPS) { // lines are parallel
+            return null;
+        }
+        Vector3d ba = new Vector3d(firstOrigin);
+        ba.sub(secondOrigin);
+        Vector3d ab = new Vector3d(firstOrigin);
+        ab.scale(-1);
+        double a = ba.dot(secondVector);
+        double b = ab.dot(firstVector) * scalar;
+        double c = 1 - scalar * scalar;
+        double s = (a + b) / c;
+        
+        Vector3d scaledV = new Vector3d(secondVector);
+        scaledV.scale(s);
+        Point3d intersection = new Point3d(secondOrigin);
+        intersection.add(scaledV);
+        return intersection;
+    }
+    
+    /** 
+     * determines whether or not the intersection between 
+     * line origin + t * vector and edge AB (first - second) is valid.
+     * 
+     * @param origin starting point of line
+     *         Must not be {@code null}
+     * @param vector vector of the line
+     *         Must not be {@code null}
+     * @param intersection point on line origin + t * vector 
+     *         which is on line containing AB
+     *         Must not be {@code null}
+     * @param first Vertex of this {@code MeshTriangle}
+     *         Must not be {@code null}
+     * @param second Vertex of this {@code MeshTriangle}
+     *         Must not be {@code null}
+     * @return true if intersection is valid, false otherwise
+     * 
+     * @author Enkh-Undral EnkhBayar
+     */
+    private static boolean isIntersectionValid(Point3d origin, Vector3d vector, Point3d intersection, Point3d first, Point3d second) {
+        double[] aCoor = {first.x, first.y, first.z};
+        double[] bCoor = {second.x, second.y, second.z};
+        double[] iCoor = {intersection.x, intersection.y, second.z};
+        // is intersection between first and second?
+        for (int i = 0; i < 3; i++) {
+            if (!(Double.min(aCoor[i], bCoor[i]) <= iCoor[i] && 
+                    iCoor[i] <= Double.min(aCoor[i], bCoor[i]))) {
+                return false;
+            }
+        }
+        // is intersection in direction of vector?
+        Vector3d oi = new Vector3d(intersection);
+        oi.sub(origin);
+        double scalar = vector.dot(oi);
+        return scalar > 0;
+    }
+    
+    /** 
+     * calculates the intersection between this triangle and ray given
+     * 
+     * @param origin Point of origin form which the ray starts
+     * @param vector directional vector of the ray
+     * @return point of intersection or null if there is no intersection
+     * 
+     * @author Enkh-Undral EnkhBayar
+     */
+    public Point3d getRayIntersection(Point3d origin, Vector3d vector) {
+        Vector3d normal = computeNormal();
+        double np = normal.dot(new Vector3d(origin));
+        double nv = normal.dot(vector);
+        if (nv == 0) { // plane and vector are parallel
+            if (np != 0) { // ray is not in plane
+                return null;
+            }
+            if (isPointInTriangle(origin)) {
+                return origin;
+            }
+            Vector3d u = new Vector3d(vector);
+            u.normalize();
+            Point3d[] vertices = {getVertex1(), getVertex2(), getVertex3()};
+            Point3d closestIntersection = null;
+            double smallestDistance = 0;
+            for (int i = 0; i < 3; i++) {
+                Vector3d v = new Vector3d(vertices[(i + 1) % 3]);
+                v.sub(vertices[i]);
+                v.normalize();
+                Point3d newIntersection = getLinesIntersection(origin, u, vertices[i], v);
+                if (newIntersection == null) {
+                    continue;
+                }
+                if (!isIntersectionValid(origin, vector, newIntersection, vertices[i], vertices[(i + 1) % 3])) {
+                    continue;
+                }
+                double newDistance = origin.distance(newIntersection);
+                if (closestIntersection == null || newDistance < smallestDistance) {
+                    closestIntersection = newIntersection;
+                    smallestDistance = newDistance;
+                }
+            }
+            return closestIntersection;
+        }
+        
+        double offset = normal.dot(new Vector3d(getVertex1()));
+        double t = (offset - nv) / np;
+        
+        Point3d intersection = new Point3d(origin);
+        Vector3d scaledVector = new Vector3d(vector);
+        scaledVector.scale(t);
+        intersection.add(scaledVector);
+        return intersection;
+    }
+    
     /**
      * Computes the point laying on the triangle which is closest to 
      * given 3D point. Return point is either one of the tringle's vertices,
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/octree/OctNode.java b/MeshModel/src/main/java/cz/fidentis/analyst/octree/OctNode.java
new file mode 100644
index 0000000000000000000000000000000000000000..71d77eaee60706b7513ba6d403df19bd1243bddd
--- /dev/null
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/octree/OctNode.java
@@ -0,0 +1,237 @@
+package cz.fidentis.analyst.octree;
+
+import cz.fidentis.analyst.mesh.core.MeshFacet;
+import cz.fidentis.analyst.mesh.core.MeshPoint;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.vecmath.Point3d;
+
+/**
+ * A single node of a Octree. Sharing vertices across meshes is supported
+ * (the node links all faces that share the same vertex).
+ *
+ * @author Enkh-Undral EnkhBayar
+ */
+public class OctNode implements Serializable {
+    
+    /**
+     * 3D location of the node, is null in internal nodes and empty node
+     */
+    private MeshPoint location;
+
+    /**
+     * Mesh facets sharing the stored vertex
+     */
+    private Map<MeshFacet, Integer> facets = new HashMap<>();
+
+    /**
+     * Octree topology
+     */
+    private List<OctNode> octants;
+    
+    /**
+     * 3D location of the boundary box - corner with smallest coordinates
+     */
+    private Point3d smallestBoundary;
+    
+    /**
+     * 3D location of the boundary box - corner with largest coordinates
+     */
+    private Point3d largestBoundary;
+    
+    /**
+     * Constructor of OctNode
+     *
+     * @param facets Mesh facet containing the mesh vertex. Must not be null nor empty
+     * @param indices The index under which the vertex is stored in the mesh facet.
+     *              Must be &gt;= 0
+     * @param smallest boundary box - corner with smallest coordinates.
+     *                 Must not be null
+     * @param largest boundary box - corner with largest coordinates.
+     *                Must not be null
+     * @throws IllegalArgumentException if some parameter is wrong
+     */
+    public OctNode(List<MeshFacet> facets, List<Integer> indices, Point3d smallest, Point3d largest) {
+        if (facets == null || facets.isEmpty()) {
+            throw new IllegalArgumentException("facets");
+        }
+        if (indices == null || indices.isEmpty()) {
+            throw new IllegalArgumentException("indices");
+        }
+        if (facets.size() != indices.size()) {
+            throw new IllegalArgumentException("The number of facets and indiecs mismatch");
+        }
+        if (smallest == null) {
+            throw new IllegalArgumentException("Smallest boundary in OctNode cannot be null");
+        }
+        if (largest == null) {
+            throw new IllegalArgumentException("Largest boundary in OctNode cannot be null");
+        }
+        
+        for (int i = 0; i < facets.size(); i++) {
+            this.facets.putIfAbsent(facets.get(i), indices.get(i));
+        }
+        this.location = facets.get(0).getVertex(indices.get(0));
+        this.smallestBoundary = smallest;
+        this.largestBoundary = largest;
+    }
+    
+    /**
+     * Constructor of OctNode for internal nodes
+     *
+     * @param octants List of octNodes which are children for this node. 
+     *                Must not be null and has to have size of 8.
+     * @param smallest boundary box - corner with smallest coordinates.
+     *                 Must not be null
+     * @param largest boundary box - corner with largest coordinates.
+     *                Must not be null
+     * @throws IllegalArgumentException if some parameter is wrong
+     */
+    protected OctNode(List<OctNode> octants, Point3d smallest, Point3d largest) {
+        if (octants == null || octants.size() != 8) {
+            throw new IllegalArgumentException("octants");
+        }
+        if (smallest == null) {
+            throw new IllegalArgumentException("Smallest boundary in OctNode cannot be null");
+        }
+        if (largest == null) {
+            throw new IllegalArgumentException("Largest boundary in OctNode cannot be null");
+        }
+        
+        this.octants = octants;
+        this.smallestBoundary = smallest;
+        this.largestBoundary = largest;
+    }
+    
+    /**
+     * Constructor of OctNode for empty nodes.
+     *
+     * @param smallest boundary box - corner with smallest coordinates.
+     *                 Must not be null
+     * @param largest boundary box - corner with largest coordinates.
+     *                Must not be null
+     * @throws IllegalArgumentException if some parameter is wrong
+     */
+    protected OctNode(Point3d smallest, Point3d largest) {
+        if (smallest == null) {
+            throw new IllegalArgumentException("Smallest boundary in OctNode cannot be null");
+        }
+        if (largest == null) {
+            throw new IllegalArgumentException("Largest boundary in OctNode cannot be null");
+        }
+        
+        this.smallestBoundary = smallest;
+        this.largestBoundary = largest;
+    }
+    
+    public boolean isLeafNode() {
+        return octants == null || octants.isEmpty();
+    }
+    
+    /**
+     * returns Octant under specific index
+     * 
+     * @param index index of the octant returned.
+     *              Must be between 0 and 7 including.
+     * @return Octant under specific index
+     */
+    public OctNode getOctant(int index) {
+        if (!(0 <= index && index < 8)) {
+            throw new IllegalArgumentException("getOctant passed with illegal index, can only be 0 to 7");
+        }
+        if (isLeafNode()) {
+            return null;
+        }
+        return octants.get(index);
+    }
+    
+    /**
+     * Returns 3D location of vertices stored in this node
+     * @return 3D location of vertices stored in this node
+     */
+    public MeshPoint getLocation() {
+        return location;
+    }
+    
+    /**
+     * returns boundary box - corner with smallest coordinates.
+     * @return boundary box - corner with smallest coordinates.
+     */
+    public Point3d getSmallBoundary() {
+        return smallestBoundary;
+    }
+
+    /**
+     * returns boundary box - corner with largest coordinates.
+     * @return boundary box - corner with largest coordinates.
+     */
+    public Point3d getLargeBoundary() {
+        return largestBoundary;
+    }
+    
+    /**
+     * Returns a map of all mesh facets that share the stored vertex. 
+     * Value in the map contains the index which the 
+     * vertex is stored in the mesh facet.
+     * 
+     * @return Map of facets sharing the stored mesh vertex
+     */
+    public Map<MeshFacet, Integer> getFacets() {
+        return Collections.unmodifiableMap(facets);
+    }
+    
+    /**
+     * Set octants / children
+     *
+     * @param octants children of this node.
+     *                Must not be null and has to have size of 8
+     * 
+     */
+    protected void setOctants(List<OctNode> octants) {
+        if (octants == null || octants.size() != 8) {
+            throw new IllegalArgumentException("setOctants passed with null or illegal size");
+        }
+        this.location = null;
+        this.octants = octants;
+    }
+
+    @Override
+    public String toString() {
+        String result = "[" + smallestBoundary + ", " + largestBoundary + "]";
+        if (isLeafNode() && location != null) {
+            result += " " + location.getPosition();
+        }
+        return result;
+    }
+
+    protected String toString(String prefix) {
+        if (isLeafNode()) {
+            return adjustPrefix(prefix) + toString() + '\n';
+        }
+        StringBuilder result = new StringBuilder(adjustPrefix(prefix));
+        result.append(toString()).append('\n');
+        if (prefix.endsWith("\\")) {
+            prefix = prefix.substring(0, prefix.length() - 1) + ' ';
+        }
+        for (int i = 0; i < 7; i++) {
+            result.append(getOctant(i).toString(prefix + '|'));
+        }
+        result.append(getOctant(7).toString(prefix + '\\'));
+        return result.toString();
+    }
+    
+    private String adjustPrefix(String prefix) {
+        if (prefix.isEmpty()) {
+            return "";
+        }
+        StringBuilder result = new StringBuilder();
+        for (int i = 0; i < prefix.length() - 1; i++) {
+            result.append(prefix.charAt(i)).append("   ");
+        }
+        result.append(prefix.charAt(prefix.length() - 1)).append("-- ");
+        return result.toString();
+    }
+}
diff --git a/MeshModel/src/main/java/cz/fidentis/analyst/octree/Octree.java b/MeshModel/src/main/java/cz/fidentis/analyst/octree/Octree.java
new file mode 100644
index 0000000000000000000000000000000000000000..b6dfe2158efd496a1411e0be79f51eb9978854c6
--- /dev/null
+++ b/MeshModel/src/main/java/cz/fidentis/analyst/octree/Octree.java
@@ -0,0 +1,289 @@
+package cz.fidentis.analyst.octree;
+
+import cz.fidentis.analyst.mesh.core.MeshFacet;
+import cz.fidentis.analyst.mesh.core.MeshFacetImpl;
+import cz.fidentis.analyst.mesh.core.MeshPoint;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.vecmath.Point3d;
+
+/**
+ * {@code Octree} for storing vertices ({@code MeshPoint}s) of 
+ * triangular meshes ({@code MeshFacet}s).
+ * Multiple mesh facets can by stored in a single {@code Octree}. In this case, 
+ * vertices that are shared across multiple facets (have the same 3D location) 
+ * are shared in the same node of the {@code Octree}.
+ * 
+ * @author Enkh-Undral EnkhBayar
+ */
+public class Octree implements Serializable {
+    
+    private OctNode root;
+    
+    /**
+     * distance of the smallest cell in {@code Octree}.
+     * It is the smallest between dx, dy and dz
+     */ 
+    private Double minLen = Double.MAX_VALUE;
+
+    /**
+     * Constructor. 
+     * 
+     * If no mesh points (vertices) are provided, then an empty 
+     * {@code Octree} is constructed (with the root node set to null).
+     *
+     * @param points A set of individual mesh points. 
+     */
+    public Octree(Set<MeshPoint> points) {
+        if (points == null) {
+            this.root = null;
+            return;
+        }
+        MeshFacet newFacet = new MeshFacetImpl();
+        for (MeshPoint point : points) {
+            newFacet.addVertex(point);
+        }
+        buildTree(new LinkedList<>(Collections.singleton(newFacet)));
+    }
+
+    /**
+     * Constructor. 
+     * 
+     * If no mesh points (vertices) are provided, then an empty 
+     * {@code Octree} is constructed (with the root node set to null).
+     *
+     * @param facet Mesh facet
+     */
+    public Octree(MeshFacet facet) {
+        this(new LinkedList<>(Collections.singleton(facet)));
+    }
+    
+    /**
+     * Constructor. 
+     * 
+     * If no mesh points (vertices) are provided, then an empty 
+     * {@code Octree} is constructed (with the root node set to null).
+     * If multiple mesh facets share the same vertex, then they are stored 
+     * efficiently in the same node of the {@code Octree}.
+     *
+     * @param facets The list of mesh facets to be stored. Facets can share vertices.
+     */
+    public Octree(List<MeshFacet> facets) {
+        if(facets == null ||  facets.isEmpty() || facets.get(0).getVertices().isEmpty() ){
+            this.root = null;
+            return;
+        }
+        buildTree(facets);
+    }
+    
+    /**
+     * Tree traversal - go to the "root" of the tree.
+     * 
+     * @return root node of the tree
+     */
+    public OctNode getRoot() {
+        return root;
+    }
+    
+    /**
+     * @return distance of the smallest cell in {@code Octree}.
+     */
+    public Double getMinLen() {
+        return minLen;
+    }
+
+    /** 
+     * Recursively display the contents of the tree in a verbose format.
+     * Individual nodes are represented in format (if the node does not hold a point)
+     * [small boundary, large boundary]
+     * and in format if it does
+     * [small boundary, large boundary] point
+     * 
+     * @return representation of the tree
+     */
+    @Override
+    public String toString() {
+        return root.toString("");
+    }
+
+    /***********************************************************
+     *  PRIVATE METHODS                                        *
+     ***********************************************************/
+    
+    private Point3d[] updateBoundaries(Point3d small, Point3d large) {
+        double[] coorSmall = {small.x, small.y, small.z};
+        double[] coorLarge = {large.x, large.y, large.z};
+        int maxIndex = 0;
+        for (int i = 1; i <= 2; i++) {
+            if (coorLarge[i] - coorSmall[i] > coorLarge[maxIndex] - coorSmall[maxIndex]) {
+                maxIndex = i;
+            }
+        }
+        double halfMaxDiff = (coorLarge[maxIndex] - coorSmall[maxIndex]) / 2;
+        for (int i = 0; i <= 2; i++) {
+            if (i == maxIndex) {
+                continue;
+            }
+            double average = (coorSmall[i] + coorLarge[i]) / 2;
+            coorSmall[i] = average - halfMaxDiff;
+            coorLarge[i] = average + halfMaxDiff;
+        }
+        return new Point3d[] {
+            new Point3d(coorSmall[0], coorSmall[1], coorSmall[2]), 
+            new Point3d(coorLarge[0], coorLarge[1], coorLarge[2])
+        };
+    }
+
+    private void updateMinLen(Point3d smallestPoint, Point3d largestPoint) {
+        minLen = Double.min(minLen, largestPoint.x - smallestPoint.x);
+        minLen = Double.min(minLen, largestPoint.y - smallestPoint.y);
+        minLen = Double.min(minLen, largestPoint.z - smallestPoint.z);
+    }
+    
+    private void buildTree(List<MeshFacet> facets) {
+        HashMap<MeshPoint, AggregatedVertex> vertices = new HashMap();
+        
+        /*
+         * Find bounding box and aggregate vertices
+         * with the same 3D location.
+         */
+        Point3d smallestPoint = null;
+        Point3d largestPoint = null;
+        for (MeshFacet facet: facets) {
+            int index = 0;
+            for (MeshPoint p: facet.getVertices()) {
+                if (vertices.containsKey(p)) {
+                    vertices.get(p).facets.add(facet);
+                    vertices.get(p).indices.add(index);
+                } else {
+                    vertices.put(p, new AggregatedVertex(facet, index));
+                    Point3d point = p.getPosition();
+                    if (smallestPoint == null) {
+                        smallestPoint = p.getPosition();
+                    } else {
+                        smallestPoint = new Point3d(
+                            Double.min(smallestPoint.x, point.x),
+                            Double.min(smallestPoint.y, point.y),
+                            Double.min(smallestPoint.z, point.z)
+                        );
+                    }
+                    if (largestPoint == null) {
+                        largestPoint = p.getPosition();
+                    } else {
+                        largestPoint = new Point3d(
+                            Double.max(largestPoint.x, point.x),
+                            Double.max(largestPoint.y, point.y),
+                            Double.max(largestPoint.z, point.z)
+                        );
+                    }
+                }
+                index++;
+            }
+        }
+        Point3d[] boundaries = updateBoundaries(smallestPoint, largestPoint);
+        smallestPoint = boundaries[0];
+        largestPoint = boundaries[1];
+        updateMinLen(smallestPoint, largestPoint);
+        root = buildTree(vertices, smallestPoint, largestPoint);
+    }
+    
+    /**
+     * Builds Octree.
+     *
+     * @param vertices List of aggregated sorted vertices
+     * @param smallestPoint boundary coordinate with x, y, z lower or equal
+     * than any other point in created OctNode
+     * @param largestPoint boundary coordinate with x, y, z higher or equal
+     * than any other point in created OctNode
+     * @return new node of the Octree
+     */
+    private OctNode buildTree(HashMap<MeshPoint, AggregatedVertex> vertices, Point3d smallestPoint, Point3d largestPoint) {
+        if (vertices.isEmpty()) {
+            return new OctNode(smallestPoint, largestPoint);
+        }
+        
+        if (vertices.size() == 1) {
+            updateMinLen(smallestPoint, largestPoint);
+            Map.Entry<MeshPoint, AggregatedVertex> entry = vertices.entrySet().stream().findFirst().get();
+            return new OctNode(entry.getValue().facets, entry.getValue().indices, smallestPoint, largestPoint);
+        }
+        
+        Point3d middlePoint = new Point3d(
+                (smallestPoint.x + largestPoint.x) / 2,
+                (smallestPoint.y + largestPoint.y) / 2,
+                (smallestPoint.z + largestPoint.z) / 2);
+
+        List<HashMap<MeshPoint, AggregatedVertex>> octants = splitSpace(vertices, middlePoint);
+        double[] xCoors = {smallestPoint.x, middlePoint.x, largestPoint.x};
+        double[] yCoors = {smallestPoint.y, middlePoint.y, largestPoint.y};
+        double[] zCoors = {smallestPoint.z, middlePoint.z, largestPoint.z};
+        List<OctNode> doneOctants = new ArrayList<>();
+        for (int i = 0; i < 8; i++) {
+            HashMap<MeshPoint, AggregatedVertex> octant = octants.get(i);
+            Point3d newSmallestPoint = new Point3d(
+                xCoors[i >> 2], yCoors[i >> 1 & 1], zCoors[i & 1]
+            );
+            Point3d newLargestPoint = new Point3d(
+                xCoors[(i >> 2) + 1], yCoors[(i >> 1 & 1) + 1], zCoors[(i & 1) + 1]
+            );
+            doneOctants.add(buildTree(octant, newSmallestPoint, newLargestPoint));
+        }
+        OctNode node = new OctNode(doneOctants, smallestPoint, largestPoint);
+        return node;
+    }
+    
+    /**
+     * Splits all points into 8 octants separated by 3 planes which all 
+     * intersect in middlePoint
+     *
+     * @param vertices List of aggregated sorted vertices
+     * @param middlePoint middle point of space
+     * @return 8 octants containing all vertices in space
+     */
+    private List<HashMap<MeshPoint, AggregatedVertex>> splitSpace(HashMap<MeshPoint, AggregatedVertex> vertices, Point3d middlePoint) {
+        List<HashMap<MeshPoint, AggregatedVertex>> octants = new ArrayList<>();
+        for (int i = 0; i < 8; i++) {
+            octants.add(new HashMap<>());
+        }
+        for (MeshPoint vertex : vertices.keySet()) {
+            int octantIndex = 0;
+            if (vertex.getPosition().x > middlePoint.x) {
+                octantIndex += 4;
+            }
+            if (vertex.getPosition().y > middlePoint.y) {
+                octantIndex += 2;
+            }
+            if (vertex.getPosition().z > middlePoint.z) {
+                octantIndex += 1;
+            }
+            octants.get(octantIndex).put(vertex, vertices.get(vertex));
+        }
+        return octants;
+    }
+
+    /***********************************************************
+    *  EMBEDDED CLASSES
+    ************************************************************/   
+
+    /**
+     * Helper class used during the Octree creation to store mesh vertices
+     * with the same 3D location.
+     * 
+     * @author Radek Oslejsek
+     */
+    private class AggregatedVertex {
+        public final List<MeshFacet> facets = new ArrayList<>();
+        public final List<Integer> indices = new ArrayList<>();
+        
+        AggregatedVertex(MeshFacet f, int i) {
+            facets.add(f);
+            indices.add(i);
+        }
+    }
+}
diff --git a/MeshModel/src/test/java/cz/fidentis/analyst/mesh/octree/OctreeTest.java b/MeshModel/src/test/java/cz/fidentis/analyst/mesh/octree/OctreeTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1cbc79c7a934728fd5f5bb661a0231900fc3c1e
--- /dev/null
+++ b/MeshModel/src/test/java/cz/fidentis/analyst/mesh/octree/OctreeTest.java
@@ -0,0 +1,274 @@
+package cz.fidentis.analyst.mesh.octree;
+
+import cz.fidentis.analyst.mesh.core.MeshFacet;
+import cz.fidentis.analyst.mesh.core.MeshFacetImpl;
+import cz.fidentis.analyst.mesh.core.MeshPoint;
+import cz.fidentis.analyst.mesh.core.MeshPointImpl;
+import cz.fidentis.analyst.octree.OctNode;
+import cz.fidentis.analyst.octree.Octree;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import javax.vecmath.Point3d;
+import javax.vecmath.Vector3d;
+import org.apache.commons.lang3.tuple.Pair;
+
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author Enkh-Undral EnkhBayar
+ */
+public class OctreeTest {
+    
+    private Point3d position = new Point3d(0.1f, 0.5f, 0.7f);
+    private Vector3d normalAndTextCoord = new Vector3d(0,0,0);
+    
+    private boolean isPointInCube(Point3d location, OctNode cube) {
+        Point3d smallBoundary = cube.getSmallBoundary();
+        Point3d largeBoundary = cube.getLargeBoundary();
+        if (!(smallBoundary.x <= location.x && location.x <= largeBoundary.x)) {
+            return false;
+        }
+        if (!(smallBoundary.y <= location.y && location.y <= largeBoundary.y)) {
+            return false;
+        }
+        return smallBoundary.z <= location.z && location.z <= largeBoundary.z;
+    }
+    
+    private boolean checkBoundaries(OctNode parent, OctNode child, int index) {
+        Point3d pSmallBoundary = parent.getSmallBoundary();
+        Point3d pLargeBoundary = parent.getLargeBoundary();
+        Point3d middlePoint = new Point3d(
+                (pSmallBoundary.x + pLargeBoundary.x) / 2,
+                (pSmallBoundary.y + pLargeBoundary.y) / 2,
+                (pSmallBoundary.z + pLargeBoundary.z) / 2);
+        Point3d[] coors = {pSmallBoundary, middlePoint, pLargeBoundary};
+        Point3d correctSmall = new Point3d(
+            coors[index >> 2].x, coors[index >> 1 & 1].y, coors[index & 1].z
+        );
+        Point3d correctLarge = new Point3d(
+            coors[(index >> 2) + 1].x, coors[(index >> 1 & 1) + 1].y, coors[(index & 1) + 1].z
+        );
+        Point3d cSmallBoundary = child.getSmallBoundary();
+        Point3d cLargeBoundary = child.getLargeBoundary();
+        double[] childCoors = {
+            cSmallBoundary.x, cSmallBoundary.y, cSmallBoundary.z,
+            cLargeBoundary.x, cLargeBoundary.y, cLargeBoundary.z
+        };
+        double[] correctCoors = {
+            correctSmall.x, correctSmall.y, correctSmall.z,
+            correctLarge.x, correctLarge.y, correctLarge.z
+        };
+        for (int i = 0; i < 6; i++) {
+            if (childCoors[i] != correctCoors[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    private void testCorrectlyBuiltTree(Octree tree) {
+        if (tree.getRoot() == null) {
+            return;
+        }
+        int maxDepth = 0;
+        Queue<Pair<OctNode, Integer>> queue = new LinkedList<>();
+        queue.add(Pair.of(tree.getRoot(), 0));
+        
+        while (!queue.isEmpty()) {
+            Pair<OctNode, Integer> pair = queue.poll();
+            final OctNode node = pair.getKey();
+            final int depth = pair.getValue();
+            maxDepth = Integer.max(maxDepth, depth);
+            assertTrue(node != null);
+            assertTrue(node.getSmallBoundary() != null, "Small boundary can't be null");
+            assertTrue(node.getLargeBoundary() != null, "Large boundary can't be null");
+            
+            if (node.isLeafNode()) {
+                MeshPoint location = node.getLocation();
+                if (location != null) {
+                    assertTrue(isPointInCube(location.getPosition(), node), "Point is not within boundaries");
+                }
+                continue;
+            }
+            assertTrue(node.getLocation() == null, "Internal node with location");
+            boolean allEmpty = true;
+            for (int i = 0; i < 8; i++) {
+                final OctNode child = node.getOctant(i);
+                queue.add(Pair.of(child, depth + 1));
+                assertTrue(checkBoundaries(node, child, i), "Boundaries in node are wrong");
+                if (!(child.isLeafNode() && child.getLocation() == null)) {
+                    allEmpty = false;
+                }
+            }
+            assertFalse(allEmpty, "All child nodes in some octnode are empty");
+        }
+        assertTrue(tree.getMinLen() != null);
+        Point3d small = tree.getRoot().getSmallBoundary();
+        Point3d large = tree.getRoot().getLargeBoundary();
+        double length = large.x - small.x;
+        length = Double.min(length, large.y - small.y);
+        length = Double.min(length, large.z - small.z);
+        assertTrue((length / Math.pow(2, maxDepth)) == tree.getMinLen(), "minLen is wrongly calculated");
+    }
+    
+    private boolean containsPoint(OctNode subTree, MeshPoint p) {
+        if (subTree == null) {
+            return false;
+        }
+        if (subTree.isLeafNode()) {
+            return subTree.getLocation() != null && subTree.getLocation().equals(p);
+        }
+        int index = 0;
+        Point3d smallestPoint = subTree.getSmallBoundary();
+        Point3d largestPoint = subTree.getLargeBoundary();
+        Point3d middlePoint = new Point3d(
+                (smallestPoint.x + largestPoint.x) / 2,
+                (smallestPoint.y + largestPoint.y) / 2,
+                (smallestPoint.z + largestPoint.z) / 2);
+        if (p.getPosition().x > middlePoint.x) {
+            index += 4;
+        }
+        if (p.getPosition().y > middlePoint.y) {
+            index += 2;
+        }
+        if (p.getPosition().z > middlePoint.z) {
+            index += 1;
+        }
+        return containsPoint(subTree.getOctant(index), p);
+    }
+    
+    @Test
+    public void testPut(){
+        MeshPoint p = new MeshPointImpl(position,normalAndTextCoord,normalAndTextCoord);
+
+        List<MeshFacet> facets = new LinkedList<>();
+        MeshFacet facet = new MeshFacetImpl();
+        facet.addVertex(p);
+        facets.add(facet);
+        Octree tree = new Octree(facets);
+        testCorrectlyBuiltTree(tree);
+        
+        assertTrue(containsPoint(tree.getRoot(), p), "point put in octree is not there after init");
+    }
+    
+    @Test
+    public void testCorrectBuild() {
+        final Set<MeshPoint> points = new HashSet<>();
+        points.add(new MeshPointImpl(new Point3d(-331, 203, 320), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(-371, -222, -111), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(-223, 190, 113), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(275, -414, -378), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(-357, 98, -217), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(297, 403, 299), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(145, 252, -77), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(319, 13, 87), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(-284, 40, -231), normalAndTextCoord, normalAndTextCoord));
+        final Octree tree = new Octree(points);
+        testCorrectlyBuiltTree(tree);
+        for (MeshPoint point : points) {
+            assertTrue(containsPoint(tree.getRoot(), point), "point put in octree is not there after init");
+        }
+    }
+    
+    @Test
+    public void testCorrectBuildUnbalanced() {
+        final Set<MeshPoint> points = new HashSet<>();
+        int depth = 8;
+        double start = 0;
+        double end = Math.pow(2, depth);
+        points.add(new MeshPointImpl(new Point3d(-end, -end, -end), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(end, end, end), normalAndTextCoord, normalAndTextCoord));
+        for (int i = 0; i < depth - 1; i++) {
+            double coor = start + (end - start) / 4;
+            start += (end - start) / 2;
+            Point3d p = new Point3d(coor, coor, coor);
+            points.add(new MeshPointImpl(p, normalAndTextCoord, normalAndTextCoord));
+        }
+        
+        final Octree tree = new Octree(points);
+        testCorrectlyBuiltTree(tree);
+        for (MeshPoint point : points) {
+            assertTrue(containsPoint(tree.getRoot(), point), "point put in octree is not there after init");
+        }
+    }
+    
+    @Test
+    public void testCorrectBuildBalanced() {
+        final Set<MeshPoint> points = new HashSet<>();
+        int depth = 4;
+        double end = Math.pow(2, depth);
+        points.add(new MeshPointImpl(new Point3d(-end, -end, -end), normalAndTextCoord, normalAndTextCoord));
+        points.add(new MeshPointImpl(new Point3d(end, end, end), normalAndTextCoord, normalAndTextCoord));
+        int[][] directions = {
+            {-1, -1, -1}, {-1, -1, 1}, {-1, 1, -1}, {-1, 1, 1}, 
+            {1, -1, -1}, {1, -1, 1}, {1, 1, -1}, {1, 1, 1}
+        };
+        for (double coor = 1; coor < end - 1; coor += 2) {
+            for (int[] direction : directions) {
+                Point3d p = new Point3d(coor * direction[0], coor * direction[1], coor * direction[2]);
+                points.add(new MeshPointImpl(p, normalAndTextCoord, normalAndTextCoord));
+            }
+        }
+        double coor = end - 1;
+        for (int i = 1; i < 7; i++) {
+            int[] direction = directions[i];
+            Point3d p = new Point3d(coor * direction[0], coor * direction[1], coor * direction[2]);
+            points.add(new MeshPointImpl(p, normalAndTextCoord, normalAndTextCoord));
+        }
+        
+        final Octree tree = new Octree(points);
+        testCorrectlyBuiltTree(tree);
+        for (MeshPoint point : points) {
+            assertTrue(containsPoint(tree.getRoot(), point), "point put in octree is not there after init");
+        }
+    }
+    
+    @Test
+    public void testPutNothing(){
+        MeshPoint p = new MeshPointImpl(position,normalAndTextCoord,normalAndTextCoord);
+
+        List<MeshFacet> facets = new LinkedList<>();
+        Octree tree = new Octree(facets);
+        testCorrectlyBuiltTree(tree);
+
+        assertFalse(containsPoint(tree.getRoot(), p));
+    }
+    
+    @Test
+    public void testTwoFacets(){
+        List<MeshFacet> facets = new LinkedList<>();
+
+        MeshFacet facet1 = new MeshFacetImpl();
+        MeshFacet facet2 = new MeshFacetImpl();
+
+        List<MeshPoint> points = new LinkedList<>();
+        Point3d positionOfPoints;
+
+        for(int i = 0; i < 5; i++){
+            positionOfPoints = new Point3d(0.1f * i, 0.5f * i, 0.7f * i);
+            MeshPoint point = new MeshPointImpl(positionOfPoints, normalAndTextCoord, normalAndTextCoord);
+            points.add(point);
+            facet1.addVertex(point);
+        }
+        for(int i = 5; i < 10; i++){
+            positionOfPoints = new Point3d(0.1f * i, 0.5f * i, 0.7f * i);
+            MeshPoint point = new MeshPointImpl(positionOfPoints, normalAndTextCoord, normalAndTextCoord);
+            points.add(point);
+            facet2.addVertex(point);
+        }
+        facets.add(facet1);
+        facets.add(facet2);
+        Octree tree = new Octree(facets);
+        testCorrectlyBuiltTree(tree);
+
+        for (MeshPoint point : points) {
+            assertTrue(containsPoint(tree.getRoot(), point), "point put in octree is not there after init");
+        }
+    }
+}