//
// 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]);
}
}