diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..224af64df30e1dcd38224cdaaf40e2f5321a6d71
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,16 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "lldb",
+            "request": "launch",
+            "name": "Debug",
+            "program": "${workspaceFolder}/<your program>",
+            "args": [],
+            "cwd": "${workspaceFolder}"
+        }
+    ]
+}
\ No newline at end of file
diff --git a/CgMath/CgDecomposition.h b/CgMath/CgDecomposition.h
index fd262a871d2671603333eb2d6e98b3ecbf74f870..7fdbfc3325068a53d3d5a84b6632babc766d5226 100644
--- a/CgMath/CgDecomposition.h
+++ b/CgMath/CgDecomposition.h
@@ -5,8 +5,6 @@
 #include <iostream>
 #include "glm/glm.hpp"
 #include "Eigen/Dense"
-#include "CgMath/CgEigenDecomposition3x3.h"
-#include <CgMath/Eigen/SVD>
 #include <CgMath/Eigen/Core>
 using namespace Eigen;
 
diff --git a/CgMath/CgEigenDecomposition3x3.cpp b/CgMath/CgEigenDecomposition3x3.cpp
deleted file mode 100644
index 75735712e0dc549ca8b99fb1ed54eae161b4a184..0000000000000000000000000000000000000000
--- a/CgMath/CgEigenDecomposition3x3.cpp
+++ /dev/null
@@ -1,41 +0,0 @@
-#include "CgEigenDecomposition3x3.h"
-
-using namespace Eigen;
-
-CgEigenDecomposition3x3::CgEigenDecomposition3x3(glm::mat3 arg) {
-  Eigen::Matrix3d A;
-  A(0,0) = arg[0][0]; A(0,1) = arg[0][1]; A(0,2) = arg[0][2];
-  A(1,0) = arg[1][0]; A(1,1) = arg[1][1]; A(1,2) = arg[1][2];
-  A(2,0) = arg[2][0]; A(2,1) = arg[2][1]; A(2,2) = arg[2][2];
-
-  // the covariance matrix is a symmetric real matrix and therefore 
-  // self-adjoint
-  Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es;
-  es.compute(A);
-
-  // The eigenvalues of a selfadjoint matrix are always real.
-  Eigen::Matrix3d V = es.eigenvectors();
-  Eigen::Vector3d res = es.eigenvalues();
-
-  // eigenvectors are returned in the COLUMNS of
-  // the Matrix
-  // V(0,1) is the second element of the first ROW
-  for(unsigned int row = 0; row < 3; row++) {
-    for(unsigned int col = 0; col < 3; col++) {
-      m_eigenvectors[col][row] = V(row, col);
-    }
-  }
-
-  m_eigenvalues[0] = res(0); 
-  m_eigenvalues[1] = res(1); 
-  m_eigenvalues[2] = res(2);
-}
-
-
-glm::mat3 CgEigenDecomposition3x3::getEigenvectors() {
-  return m_eigenvectors;
-}
-
-glm::vec3 CgEigenDecomposition3x3::getEigenvalues() {
-     return m_eigenvalues;
-}
diff --git a/CgMath/CgEigenDecomposition3x3.h b/CgMath/CgEigenDecomposition3x3.h
deleted file mode 100644
index d9f481b09abff51530fac624fc509e140ea21365..0000000000000000000000000000000000000000
--- a/CgMath/CgEigenDecomposition3x3.h
+++ /dev/null
@@ -1,20 +0,0 @@
-#ifndef CGEIGENDECOMPOSITION3X3_H
-#define CGEIGENDECOMPOSITION3X3_H
-
-#include "glm/glm.hpp"
-#include "CgMath/Eigen/Dense"
-#include "CgMath/Eigen/Eigenvalues"
-#include "glm/glm.hpp"
-#include <iostream>
-
-class CgEigenDecomposition3x3 {
-public:
-  CgEigenDecomposition3x3(glm::mat3 arg);
-  glm::mat3 getEigenvectors();
-  glm::vec3 getEigenvalues();
-private:
-    glm::mat3 m_eigenvectors;
-    glm::vec3 m_eigenvalues;
-};
-
-#endif // CGEIGENDECOMPOSITION3X3_H
diff --git a/CgMath/CgGMST.h b/CgMath/CgGMST.h
new file mode 100644
index 0000000000000000000000000000000000000000..9905ee46641f8cb1c7e6f5e65c411edb0036bf64
--- /dev/null
+++ b/CgMath/CgGMST.h
@@ -0,0 +1,16 @@
+
+#ifndef CGGMST_H
+#define CGGMST_H
+
+// benchmarking utility
+// takes a closure without params that returns void and runs it 
+// amount times, printing the duration in milliseconds to stdout afterwards
+void run_timed(std::string tag, unsigned int amount, std::function<void ()> fn) {
+    auto t1 = std::chrono::high_resolution_clock::now();
+    for(int i = 0; i < amount; i++) fn();
+    auto t2 = std::chrono::high_resolution_clock::now();
+    std::chrono::duration<double, std::milli> ms_double = t2 - t1;
+    std::cout << "average for " << tag << ": "<< ms_double.count() / double(amount) << " ms" << std::endl;
+}
+
+#endif // CGGMST_H
\ No newline at end of file
diff --git a/CgSceneGraph/CgPointCloud.cpp b/CgSceneGraph/CgPointCloud.cpp
index 4a64ba93eaff5377cfc7c0f77e07b32ae91d3df9..d04c0693ed9de697ed2265f3eff62819e4a09e22 100644
--- a/CgSceneGraph/CgPointCloud.cpp
+++ b/CgSceneGraph/CgPointCloud.cpp
@@ -1,5 +1,4 @@
 #include "CgPointCloud.h"
-#include "CgUtils/Timer.h"
 
 // squared distance between a point and a ray
 float sqrPointRayDist(glm::vec3 point, glm::vec3 origin, glm::vec3 direction) {
@@ -61,6 +60,8 @@ void CgPointCloud::init( std::string filename, bool cheat_normals) {
     ObjLoader loader;
     loader.load(filename);
     loader.getPositionData(m_vertices);
+    m_vertex_normals.resize(m_vertices.size());
+    m_vertex_colors.resize(m_vertices.size());
     std::cout << "loaded " << m_vertices.size() << " points" << std::endl;
     std::cout << "using " << std::thread::hardware_concurrency() << " threads" << std::endl;
     m_centroid = getCentroid(m_vertices);
@@ -78,9 +79,9 @@ void CgPointCloud::init( std::string filename, bool cheat_normals) {
     //calculateSplatOrientations();
 
     //add a standard color for each point if lighting turned off
-    for(unsigned int i = 0;i < m_vertices.size(); i++) {
-      m_vertex_colors.push_back(glm::vec3(0.0,1.0,0.0));
-    }
+    //for(unsigned int i = 0; i < m_vertices.size(); i++) {
+    //  m_vertex_colors[i] = glm::vec3(0.0,1.0,0.0);
+    //}
 }
 
 void CgPointCloud::buildKdTree(glm::vec3* begin, glm::vec3* end, int depth) {
@@ -106,43 +107,52 @@ void CgPointCloud::buildKdTree(glm::vec3* begin, glm::vec3* end, int depth) {
 }
 
 void CgPointCloud::estimateNormals() {
-  unsigned int neighbourhood_size = 25;
-  unsigned int thread_count = std::thread::hardware_concurrency();
+  unsigned int neighbourhood_size = 15;
 
   // find point with highest y-value
-  //std::vector<unsigned int> spanning_tree;
-  //spanning_tree.resize(m_vertices.size());
-  m_vertex_normals.clear();
-  m_vertex_normals.resize(m_vertices.size());
-  //float max_y =-std::numeric_limits<float>::infinity();
-  //unsigned int max_y_index = 0;
-  //for(int i = 0; i < m_vertices.size(); i++) {
-  //  float curr = m_vertices[i].y;
-  //  if(max_y < curr) {
-  //    max_y = curr;
-  //    max_y_index = i;
-  //  }
-  //}
-
-  // the extremal point is selected as root (has no parent)
-  //spanning_tree[max_y_index] = max_y_index;
-
-  // build minimal spanning tree (for each index, record the index we came from)
-  // traverse the tree, search neighborhoods
+  std::vector<bool> visited;
+  visited.resize(m_vertices.size());
+  float max_y = -std::numeric_limits<float>::infinity();
+  unsigned int max_y_index = 0;
+  for(int i = 0; i < m_vertices.size(); i++) {
+    visited[i] = false;
+    float curr = m_vertices[i].y;
+    if(max_y < curr) {
+      max_y = curr;
+      max_y_index = i;
+    }
+  }
+  visited[max_y_index] = true;
+
+  // used as a stack for traversal order
+  std::vector<unsigned int> spanning_tree;
+  // the "best" parent found for each vertex
+  std::vector<std::pair<unsigned int, double>> scores;
+  scores.resize(m_vertices.size());
+  // all k-neighbourhoods
+  std::vector<std::vector<unsigned int>> neighbourhoods;
+  neighbourhoods.resize(m_vertices.size());
+
+  // record all neighbourhoods and calculate (possibly flipped) normals
+  unsigned int thread_count = std::thread::hardware_concurrency();
   std::vector<std::thread> threads;
   threads.reserve(thread_count);
+
   auto worker = [&](
     const unsigned int start_index, 
     const unsigned int count
   ) {
+    // buffer for points of one neighbourhood
     std::vector<glm::vec3> nh(neighbourhood_size);
     for(unsigned int i = start_index; i < start_index + count; i++) {
-      std::vector<unsigned int> nh_indices = getNearestNeighborsFast(i, neighbourhood_size);
-      for(unsigned int j = 0; j < neighbourhood_size; j++) nh[j] = m_vertices[nh_indices[j]];
-      glm::vec3 normal = estimateNormalFromPCA(nh);
-      m_vertex_normals[i] = glm::dot(normal, m_vertices[i] - m_centroid) > 0.0 ? normal : -normal;
+      neighbourhoods[i] = getNearestNeighborsFast(i, neighbourhood_size);
+      for(int j = 0; j < neighbourhood_size; j++) {
+        nh[j] = m_vertices[neighbourhoods[i][j]];
+      }
+      m_vertex_normals[i] = estimateNormalFromPCA(nh);
     }
   };
+
   unsigned int slice_size = m_vertices.size() / thread_count;
   unsigned int slice_rest = m_vertices.size() % thread_count;
   unsigned int t = 0;
@@ -150,7 +160,58 @@ void CgPointCloud::estimateNormals() {
   threads.push_back(std::thread(worker, t * slice_size, slice_size + slice_rest));
   for(t = 0; t < thread_count; t++) threads[t].join();
 
-  // select normal pointing roughly in the same direction as parent's normal
+  auto visit = [&](unsigned int curr, glm::vec3 parent_normal, glm::vec3 parent_pos) {
+    // flip current normal to conform to parent
+    glm::vec3 normal = m_vertex_normals[curr];
+    normal = glm::dot(normal, parent_normal) > 0.0 ? normal : -normal;
+    m_vertex_normals[curr] = normal;
+    m_vertex_colors[curr] = glm::vec3(
+      (normal.x + 1.0) * .5, 
+      (normal.y + 1.0) * .5, 
+      (normal.z + 1.0) * .5
+    );
+
+    // retrieve neighbourhood indices
+    auto curr_nh_ind = neighbourhoods[curr];
+
+    // find out which of the neighbours we didn't process yet
+    // and score them to find out in which order to process
+    for(unsigned int i = 0; i < neighbourhood_size; i++) {
+      unsigned int index = curr_nh_ind[i];
+      // score by how far away it is from the local tangent plane
+      // and how aligned the normals are.
+      double alignment = 1.0 - std::abs(glm::dot(m_vertex_normals[index], normal));
+      double distance = std::abs(glm::dot(m_vertices[index] - m_vertices[curr], normal));
+      double score = distance * alignment;
+      if(!visited[index]) {
+        scores[index].first = curr;
+        scores[index].second = score;
+        spanning_tree.push_back(index);
+        visited[index] = true;
+      } 
+      if(scores[index].second > score) {
+        // update current neighbors parent to current node
+        scores[index].first = curr;
+        scores[index].second = score;
+      }
+    }
+  };
+
+  // get started by aligning the normal of 
+  // the highest point away from the centroid
+  visit(max_y_index, m_vertices[max_y_index] - m_centroid, m_vertices[max_y_index]);
+
+  // start processing
+  while(!spanning_tree.empty()) {
+    unsigned int curr = spanning_tree.back();
+    unsigned int parent = scores[curr].first;
+    spanning_tree.pop_back();
+    glm::vec3 parent_normal = m_vertex_normals[parent];
+    glm::vec3 parent_pos = m_vertices[parent];
+    visit(curr, parent_normal, parent_pos);
+  }
+
+  m_vertex_colors[max_y_index] = glm::vec3(1.0,0,0);
 }
 
 void CgPointCloud::traverseBFO(std::function<void(glm::vec3*, const unsigned int)> fn) {
@@ -235,7 +296,7 @@ std::vector<unsigned int> CgPointCloud::getNearestNeighborsFast(unsigned int cur
 
     glm::vec3 curr_point = *(begin + half_size);
     // insert current point into set
-    candidates.push_back({glm::distance2(query, curr_point), begin + half_size - first});
+    candidates.push_back({glm::distance(query, curr_point), begin + half_size - first});
     std::push_heap(candidates.begin(), candidates.end(), cmp);
     if(candidates.size() > k) {
       std::pop_heap(candidates.begin(), candidates.end(), cmp);
@@ -249,7 +310,7 @@ std::vector<unsigned int> CgPointCloud::getNearestNeighborsFast(unsigned int cur
     std::tie(lower_aabb, higher_aabb) = aabb.split(axis, curr_point[axis]);
     // only push lower child if its aabb intersects with the 
     // neighbourhood we found 
-    double neighbourhood_radius = (*candidates.begin()).first;
+    double neighbourhood_radius = candidates[0].first;
     if(
       begin != begin + half_size
       && lower_aabb.position[axis] + lower_aabb.extent[axis] >= query[axis] - neighbourhood_radius
diff --git a/CgSceneGraph/CgPointCloud.h b/CgSceneGraph/CgPointCloud.h
index df391353c74345b9382b9d62b0f08f14fab329aa..fe5ef5e52b7aa0346150088dda30283e4a4d4b60 100644
--- a/CgSceneGraph/CgPointCloud.h
+++ b/CgSceneGraph/CgPointCloud.h
@@ -20,6 +20,7 @@
 #include <glm/glm.hpp>
 #include <glm/gtx/norm.hpp>
 #include <glm/gtc/matrix_transform.hpp>
+#include "CgUtils/Timer.h"
 
 class CgPointCloud : public CgBasePointCloud {
 public:
diff --git a/CgUtils/Timer.h b/CgUtils/Timer.h
index 1aee94726c44d5e12498ddeefeafe3eb2955c5c3..799c6f80d5806e2d74beaa29ebcb92d0b8011547 100644
--- a/CgUtils/Timer.h
+++ b/CgUtils/Timer.h
@@ -9,7 +9,7 @@
 // benchmarking utility
 // takes a closure without params that returns void and runs it 
 // amount times, printing the duration in milliseconds to stdout afterwards
-void run_timed(std::string tag, unsigned int amount, std::function<void ()> fn) {
+inline void run_timed(std::string tag, unsigned int amount, std::function<void ()> fn) {
     auto t1 = std::chrono::high_resolution_clock::now();
     for(int i = 0; i < amount; i++) fn();
     auto t2 = std::chrono::high_resolution_clock::now();
diff --git a/ExerciseVC.pro b/ExerciseVC.pro
index c623731de3ce1f8fb608023e43b299c1a359356a..e2a40a362350d07db812fe9be302d11013fd2850 100644
--- a/ExerciseVC.pro
+++ b/ExerciseVC.pro
@@ -10,7 +10,6 @@ SOURCES += main.cpp \
     CgEvents/CgPickRayEvent.cpp \
     CgEvents/CgKdTreeEvent.cpp \
     CgEvents/CgSplatEvent.cpp \
-    CgMath/CgEigenDecomposition3x3.cpp \
     CgQtViewer/CGQtGLRenderWidget.cpp \
     CgQtViewer/CgQtGui.cpp \
     CgBase/CgObservable.cpp \
@@ -36,7 +35,6 @@ HEADERS += \
     CgEvents/CgPickRayEvent.h \
     CgEvents/CgKdTreeEvent.h \
     CgEvents/CgSplatEvent.h \
-    CgMath/CgEigenDecomposition3x3.h \
     CgMath/CgDecomposition.h \
     CgMath/Eigen/Core \
     CgMath/Eigen/Eigen \