+namespace {
+/// map p onto cube, s gives the surface, u and v the position in [-1,1]
+void cubemap(const glm::dvec3 &p, int &s, double &u, double &v) noexcept {
+ const glm::dvec3 p_abs(glm::abs(p));
+ const glm::bvec3 p_pos(glm::greaterThan(p, glm::dvec3(0.0)));
+ double max_axis = 0.0;
+
+ if (p_pos.x && p_abs.x >= p_abs.y && p_abs.x >= p_abs.z) {
+ max_axis = p_abs.x;
+ u = p.y;
+ v = p.z;
+ s = 1;
+ }
+ if (!p_pos.x && p_abs.x >= p_abs.y && p_abs.x >= p_abs.z) {
+ max_axis = p_abs.x;
+ u = -p.y;
+ v = -p.z;
+ s = 4;
+ }
+ if (p_pos.y && p_abs.y >= p_abs.x && p_abs.y >= p_abs.z) {
+ max_axis = p_abs.y;
+ u = p.z;
+ v = p.x;
+ s = 2;
+ }
+ if (!p_pos.y && p_abs.y >= p_abs.x && p_abs.y >= p_abs.z) {
+ max_axis = p_abs.y;
+ u = -p.z;
+ v = -p.x;
+ s = 5;
+ }
+ if (p_pos.z && p_abs.z >= p_abs.x && p_abs.z >= p_abs.y) {
+ max_axis = p_abs.z;
+ u = p.x;
+ v = p.y;
+ s = 0;
+ }
+ if (!p_pos.z && p_abs.z >= p_abs.x && p_abs.z >= p_abs.y) {
+ max_axis = p_abs.z;
+ u = -p.x;
+ v = -p.y;
+ s = 3;
+ }
+ u /= max_axis;
+ v /= max_axis;
+}
+/// get p from cube, s being surface, u and v the position in [-1,1],
+/// gives a vector from the center to the surface
+glm::dvec3 cubeunmap(int s, double u, double v) {
+ switch (s) {
+ default:
+ case 0: return glm::dvec3(u, v, 1.0); // +Z
+ case 1: return glm::dvec3(1.0, u, v); // +X
+ case 2: return glm::dvec3(v, 1.0, u); // +Y
+ case 3: return glm::dvec3(-u, -v, -1.0); // -Z
+ case 4: return glm::dvec3(-1.0, -u, -v); // -X
+ case 5: return glm::dvec3(-v, -1.0, -u); // -Y
+ };
+}
+}
+
+Tile &Planet::TileAt(const glm::dvec3 &p) noexcept {
+ int srf = 0;
+ double u = 0.0;
+ double v = 0.0;
+ cubemap(p, srf, u, v);
+ int x = glm::clamp(int(u * Radius() + Radius()), 0, sidelength - 1);
+ int y = glm::clamp(int(v * Radius() + Radius()), 0, sidelength - 1);
+ return TileAt(srf, x, y);
+}
+
+const Tile &Planet::TileAt(const glm::dvec3 &p) const noexcept {
+ int srf = 0;
+ double u = 0.0;
+ double v = 0.0;
+ cubemap(p, srf, u, v);
+ int x = glm::clamp(int(u * Radius() + Radius()), 0, sidelength - 1);
+ int y = glm::clamp(int(v * Radius() + Radius()), 0, sidelength - 1);
+ return TileAt(srf, x, y);
+}
+
+const TileType &Planet::TileTypeAt(const glm::dvec3 &p) const noexcept {
+ return GetSimulation().TileTypes()[TileAt(p).type];
+}
+
+Tile &Planet::TileAt(int surface, int x, int y) noexcept {
+ return tiles[IndexOf(surface, x, y)];
+}
+
+const Tile &Planet::TileAt(int surface, int x, int y) const noexcept {
+ return tiles[IndexOf(surface, x, y)];
+}
+
+const TileType &Planet::TypeAt(int srf, int x, int y) const noexcept {
+ return GetSimulation().TileTypes()[TileAt(srf, x, y).type];
+}
+
+glm::dvec3 Planet::TileCenter(int srf, int x, int y, double e) const noexcept {
+ double u = (double(x) - Radius() + 0.5) / Radius();
+ double v = (double(y) - Radius() + 0.5) / Radius();
+ return glm::normalize(cubeunmap(srf, u, v)) * (Radius() + e);
+}
+
+void Planet::BuildVAO() {
+ vao.reset(new graphics::SimpleVAO<Attributes, unsigned int>);
+ vao->Bind();
+ vao->BindAttributes();
+ vao->EnableAttribute(0);
+ vao->EnableAttribute(1);
+ vao->EnableAttribute(2);
+ vao->EnableAttribute(3);
+ vao->EnableAttribute(4);
+ vao->EnableAttribute(5);
+ vao->AttributePointer<glm::vec3>(0, false, offsetof(Attributes, position));
+ vao->AttributePointer<glm::vec3>(1, false, offsetof(Attributes, normal));
+ vao->AttributePointer<glm::vec3>(2, false, offsetof(Attributes, tex_coord));
+ vao->AttributePointer<float>(3, false, offsetof(Attributes, shiny));
+ vao->AttributePointer<float>(4, false, offsetof(Attributes, glossy));
+ vao->AttributePointer<float>(5, false, offsetof(Attributes, metallic));
+ vao->ReserveAttributes(TilesTotal() * 4, GL_STATIC_DRAW);