//
//  Sphere.cpp
//  BlockyFroggy
//
//  Created by John Ryland on 21/9/17.
//  Copyright © 2017 John Ryland. All rights reserved.
//

#include <cstdio>
#include <cmath>
#include <map>
#include <algorithm>
#include <cstdint>
#include "Sphere.h"

// Based on code from here:
//   http://blog.andreaskahler.com/2009/06/creating-icosphere-mesh-in-code.html
// Ideas for texturing
//   http://www.mvps.org/directx/articles/spheremap.htm
struct SphereCreatorState
{
  std::vector<vector3>       positions;
  std::map<uint64_t, size_t> middlePointIndexCache;
};


// add vertex to mesh, fix position to be on unit sphere, return index
static void addVertex(std::vector<vector3>& positions, vector3 p)
{
  float length = sqrt(p.x * p.x + p.y * p.y + p.z * p.z);
  positions.push_back(vector3{p.x/length, p.y/length, p.z/length});
}


// return index of point in the middle of p1 and p2
static size_t getMiddlePoint(SphereCreatorState& state, size_t p1, size_t p2)
{
  // first check if we have it already
  bool firstIsSmaller = p1 < p2;
  uint64_t smallerIndex = firstIsSmaller ? p1 : p2;
  uint64_t greaterIndex = firstIsSmaller ? p2 : p1;
  uint64_t key = (smallerIndex << 32) | greaterIndex;
  if ( state.middlePointIndexCache.count(key) ) // find(key) != state.middlePointIndexCache.end() )
  {
    return state.middlePointIndexCache[key];
  }
  
  // not in cache, calculate it
  vector3 point1 = state.positions[p1];
  vector3 point2 = state.positions[p2];
  vector3 middle = vector3{(point1.x + point2.x) * 0.5f,
                    (point1.y + point2.y) * 0.5f, (point1.z + point2.z) * 0.5f};
  
  // add vertex makes sure point is on unit sphere
  addVertex(state.positions, middle);
  size_t i = state.positions.size() - 1;
  
  // store it, return index
  state.middlePointIndexCache[key] = i;
  return i;
}

void CreateIcoSphere(int recursionLevel, std::vector<vector3>& outputVertexBuffer)
{
  struct triangle
  {
    size_t x, y, z;
  };
  SphereCreatorState state;
  
  // create 12 vertices of a icosahedron
  float t = (1.0 + sqrt(5.0f)) / 2.0;
  float triX[12] = {  -1,  1, -1,  1,  0,  0,  0,  0,  t,  t, -t, -t  };
  float triY[12] = {   t,  t, -t, -t, -1,  1, -1,  1,  0,  0,  0,  0  };
  float triZ[12] = {   0,  0,  0,  0,  t,  t, -t, -t, -1,  1, -1,  1  };
  for (int i = 0; i < 12; i++)
    addVertex(state.positions, vector3{triX[i], triY[i], triZ[i]});

  // create 20 triangles of the icosahedron
  std::vector<triangle> startFaces;
  size_t facesP0[20] = {  0,  0,  0,  0,  0,   1,  5, 11, 10,  7,    3,  3,  3,  3,  3,   4,  2,  6,  8,  9 };
  size_t facesP1[20] = { 11,  5,  1,  7, 10,   5, 11, 10,  7,  1,    9,  4,  2,  6,  8,   9,  4,  2,  6,  8 };
  size_t facesP2[20] = {  5,  1,  7, 10, 11,   9,  4,  2,  6,  8,    4,  2,  6,  8,  9,   5, 11, 10,  7,  1 };
  for (int i = 0; i < 20; i++)
    startFaces.push_back(triangle{ facesP0[i], facesP1[i], facesP2[i] });
  
  // refine triangles
  std::vector<triangle> workingFaces;
  std::vector<triangle>* faces = &startFaces;
  std::vector<triangle>* faces2 = &workingFaces;
  for (int i = 0; i < recursionLevel; i++)
  {
    for (int r = 0; r < faces->size(); r++)
    {
      triangle tri = (*faces)[r];
      // replace triangle by 4 triangles
      size_t a = getMiddlePoint(state, tri.x, tri.y);
      size_t b = getMiddlePoint(state, tri.y, tri.z);
      size_t c = getMiddlePoint(state, tri.z, tri.x);
      faces2->push_back(triangle{tri.x, a, c});
      faces2->push_back(triangle{tri.y, b, a});
      faces2->push_back(triangle{tri.z, c, b});
      faces2->push_back(triangle{a, b, c});
    }
    faces->clear();
    std::vector<triangle>* oldFaces = faces;
    faces = faces2;
    faces2 = oldFaces;
  }
  
  // done, now add triangles to mesh
  outputVertexBuffer.clear();
  for (int r = 0; r < faces->size(); r++)
  {
    triangle tri = (*faces)[r];
    outputVertexBuffer.push_back(state.positions[tri.x]);
    outputVertexBuffer.push_back(state.positions[tri.y]);
    outputVertexBuffer.push_back(state.positions[tri.z]);
  }
}

