Winter

Mesh / UV Sphere

Shape

UVSphere.h

#pragma once

#include "glm/vec2.hpp"
#include "glm/vec3.hpp"
#include "glm/geometric.hpp"
#include <vector>

using namespace glm;

struct UVSphere {
	std::vector<int> index;
	std::vector<vec3> pos;
	std::vector<vec2> uvs;
};

UVSphere MakeUVSphere(int latCount, int latCount);

UVSphere.cpp

#include "uvsphere.h"

static const float pi = 3.1415927f;

UVSphere MakeUVSphere(int latCount, int lonCount) {
	if (latCount < 2 || lonCount < 2) {
		return {};
	}

	// Each longitudinal count makes two triangles (6 indices) for every
	// lateral count except for the top and bottom poles, which only make
	// one triangle per longitudinal count.

	// UV maps require two vertices with the same position, but different UVs
	// so we need counts + 1.

	const int totalIndexCount = 6 * (latCount - 1) * lonCount;
	const int totalVertexCount = (latCount + 1) * (lonCount + 1);
	const float latStep = pi / latCount;
	const float lonStep = 2 * pi / lonCount;
 
	UVSphere sphere;

	sphere.index.resize(totalIndexCount);
	sphere.pos.resize(totalVertexCount);
	sphere.uvs.resize(totalVertexCount);
 
	int currentVertex = 0;
	int currentIndex = 0;

	for (int lat = 0; lat <= latCount; lat++) {
		for (int lon = 0; lon <= lonCount; lon++) {
			sphere.pos[currentVertex] = vec3(
				cos(lon * lonStep) * sin(lat * latStep),
				sin(lon * lonStep) * sin(lat * latStep),
				cos(lat * latStep - pi)
			);

			sphere.uvs[currentVertex] = vec2(
				(float)lon / lonCount,
				(float)lat / latCount
			);

			currentVertex += 1;
		}
	}
 
	// Top cap
	//
	// One triangle connects the first lateral layer to the second per longitudinal count.
	// Even though the top points all have the same position, their UVs are different,
	// so each triangle uses a different point.
	//
	//          -------- lonCount -------->
	//                      lon
	//    |                  *
	//    |                / | \
	//    1             /    |    \
	//    |          /       |       \
	//    |       /                     \
	//   \ /     *------------*------------*
	//           v          v + 1        v + 2
 
	int v = lonCount + 1;
	for (int lon = 0; lon < lonCount; lon++) {
		sphere.index[currentIndex++] = lon;
		sphere.index[currentIndex++] = v;
		sphere.index[currentIndex++] = v + 1;

		v += 1;
	}

	// Middle
	//
	// Each lateral layer has 2 triangles for every longitudinal count.
	//
	//          -------- lonCount -------->
	//          v          v + 1        v + 2
	//    |     *------------*------------*
	//    |     |          / |          / |
	// latCount |       /    |       /    |
	//    |     |    /       |    /       |
	//    |     | /          | /          |
	//   \ /    *------------*------------*
	//      v + lc + 1   v + lc + 2   (v + 1) + lc + 2

	v = lonCount + 1;
	for (int lat = 1; lat < latCount - 1; lat++) {
		for (int lon = 0; lon < lonCount; lon++) {
			sphere.index[currentIndex++] = v;
			sphere.index[currentIndex++] = v + lonCount + 1;
			sphere.index[currentIndex++] = v + 1;
 
			sphere.index[currentIndex++] = v + 1;
			sphere.index[currentIndex++] = v + lonCount + 1;
			sphere.index[currentIndex++] = v + lonCount + 2;

			v += 1;
		}

		v += 1;
	}

	// Bottom cap

	// Same as top cap, but reversed.
	//
	//          -------- lonCount -------->
	//          v          v + 1        v + 2
	//    |     *------------*------------*
	//    |       \          |          /
	//    1          \       |       /
	//    |             \    |    /
	//    |                \ | /
	//   \ /                 *
	//                   v + lc + 1
 
	for (int lon = 0; lon < lonCount; lon++) {
		sphere.index[currentIndex++] = v;
		sphere.index[currentIndex++] = v + lonCount + 1;
		sphere.index[currentIndex++] = v + 1;

		v += 1;
	}
 
	return sphere;
}