From 9f3c113b0b9e16b80a8289823138b71fd72f940e Mon Sep 17 00:00:00 2001
From: Joseph Walton-Rivers <joseph@walton-rivers.uk>
Date: Sun, 24 Apr 2022 14:36:52 +0100
Subject: [PATCH] add sphere generator to procedural shape library

---
 demo/demo/rollball.cpp           | 196 +++++++++++++++++++++++++++++-
 fggl/data/procedural.cpp         | 202 ++++++++++++++++++++++---------
 include/fggl/data/procedural.hpp |  47 +++----
 include/fggl/math/types.hpp      |  15 ++-
 4 files changed, 369 insertions(+), 91 deletions(-)

diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp
index 95be675..49396f6 100644
--- a/demo/demo/rollball.cpp
+++ b/demo/demo/rollball.cpp
@@ -19,8 +19,191 @@
  */
 
 #include "rollball.hpp"
+#include "fggl/data/model.hpp"
+#include "fggl/data/procedural.hpp"
+#include "fggl/gfx/camera.hpp"
+#include "fggl/input/camera_input.h"
 
-#include "fggl/util/service.h"
+struct Prefabs {
+	fggl::ecs3::entity_t wallX;
+	fggl::ecs3::entity_t wallZ;
+	fggl::ecs3::entity_t floor;
+	fggl::ecs3::entity_t collectable;
+	fggl::ecs3::entity_t player;
+};
+
+static void setupPrefabs(fggl::ecs3::World& world, Prefabs& prefabs) {
+
+	{
+		// walls
+		prefabs.wallX = world.create(true);
+		world.add<fggl::math::Transform>(prefabs.wallX);
+
+		fggl::data::StaticMesh meshComponent;
+		meshComponent.pipeline = "phong";
+
+		// create a procedural cube, but scale the model to be a long wall
+		auto transform = glm::scale(fggl::data::OFFSET_NONE, {1.0f, 5.f, 41.0f});
+		fggl::data::make_cube(meshComponent.mesh, transform );
+		world.set<fggl::data::StaticMesh>(prefabs.wallX, &meshComponent);
+	}
+
+	{
+		// walls
+		prefabs.wallZ = world.create(true);
+		world.add<fggl::math::Transform>(prefabs.wallZ);
+
+		fggl::data::StaticMesh meshComponent;
+		meshComponent.pipeline = "phong";
+
+		// create a procedural cube, but scale the model to be a long wall
+		auto transform = glm::scale(fggl::data::OFFSET_NONE, {39.f, 5.f, 1.0f});
+		fggl::data::make_cube(meshComponent.mesh, transform );
+		world.set<fggl::data::StaticMesh>(prefabs.wallZ, &meshComponent);
+	}
+
+	{
+		// floor
+		prefabs.floor = world.create(true);
+		world.add<fggl::math::Transform>(prefabs.floor);
+
+		fggl::data::StaticMesh meshComponent;
+		meshComponent.pipeline = "phong";
+
+		// create a procedural cube, but scale the model to be a long wall
+		auto transform = glm::scale(fggl::data::OFFSET_NONE, {40.f, 0.5f, 40.0f});
+
+		// TODO proper planes
+		fggl::data::make_cube(meshComponent.mesh, transform );
+		world.set<fggl::data::StaticMesh>(prefabs.floor, &meshComponent);
+	}
+
+	{
+		// player (cube because my sphere function doesn't exist yet
+		prefabs.player = world.create(true);
+		world.add<fggl::math::Transform>(prefabs.player);
+
+		fggl::data::StaticMesh meshComponent;
+		meshComponent.pipeline = "phong";
+		fggl::data::make_sphere(meshComponent.mesh);
+		world.set<fggl::data::StaticMesh>(prefabs.player, &meshComponent);
+	}
+
+	{
+		// collectable
+		prefabs.collectable = world.create(true);
+		world.add<fggl::math::Transform>(prefabs.collectable);
+
+		fggl::data::StaticMesh meshComponent;
+		meshComponent.pipeline = "phong";
+		fggl::data::make_cube(meshComponent.mesh);
+		world.set<fggl::data::StaticMesh>(prefabs.collectable, &meshComponent);
+	}
+
+}
+
+static void setupCamera(fggl::ecs3::World& world) {
+	auto prototype = world.create(false);
+
+	// setup camera position/transform
+	auto* transform = world.add<fggl::math::Transform>(prototype);
+	if ( transform != nullptr) {
+		transform->origin(glm::vec3(10.0f, 3.0f, 10.0f));
+	}
+
+	// setup camera components
+	world.add<fggl::gfx::Camera>(prototype);
+	auto* cameraKeys = world.add<fggl::input::FreeCamKeys>(prototype);
+	if ( cameraKeys != nullptr ) {
+		cameraKeys->forward = glfwGetKeyScancode(GLFW_KEY_W);
+		cameraKeys->backward = glfwGetKeyScancode(GLFW_KEY_S);
+		cameraKeys->left = glfwGetKeyScancode(GLFW_KEY_A);
+		cameraKeys->right = glfwGetKeyScancode(GLFW_KEY_D);
+		cameraKeys->rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
+		cameraKeys->rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
+	}
+}
+
+static void setupEnvironment(fggl::ecs3::World& world, const Prefabs prefabs, fggl::math::vec2& size) {
+	{
+		auto northWall = world.copy(prefabs.wallX);
+		auto* transform = world.get<fggl::math::Transform>(northWall);
+		transform->origin({size.x/2, 0.0f, 0.0f});
+	}
+
+	{
+		auto southWall = world.copy(prefabs.wallX);
+		auto* transform = world.get<fggl::math::Transform>(southWall);
+		transform->origin({-size.x/2, 0.0f, 0.0f});
+	}
+
+	{
+		auto westWall = world.copy(prefabs.wallZ);
+		auto* transform = world.get<fggl::math::Transform>(westWall);
+		transform->origin({0.0f, 0.0f, -size.y/2});
+	}
+
+	{
+		auto eastWall = world.copy(prefabs.wallZ);
+		auto* transform = world.get<fggl::math::Transform>(eastWall);
+		transform->origin({0.0f, 0.0f, size.y/2});
+	}
+
+	{
+		auto floor = world.copy(prefabs.floor);
+		auto *transform = world.get<fggl::math::Transform>(floor);
+		transform->origin({0.0f, -2.5f, 0.0f});
+	}
+
+	{
+		// player just starts off as the prefab dictates
+		auto player = world.copy(prefabs.player);
+	}
+
+	{
+		// collectables
+		std::array<fggl::math::vec3, 3> collectPos {{
+			{-5.0f, 0.0f, 12.0f},
+			{15.0f, 0.0f, 0.5f},
+			{6.0f, 0.0f, -15.0f}
+		}};
+
+		// build the collectables
+		for (auto& pos : collectPos) {
+			auto collectable = world.copy(prefabs.collectable);
+			auto* transform = world.get<fggl::math::Transform>(collectable);
+			transform->origin(pos);
+		}
+	}
+}
+
+enum camera_type { cam_free, cam_arcball };
+static camera_type cam_mode = cam_free;
+
+static void process_camera(fggl::ecs3::World& ecs, const fggl::input::Input& input) {
+	auto cameras = ecs.findMatching<fggl::gfx::Camera>();
+	fggl::ecs3::entity_t cam = cameras[0];
+
+	auto camTransform = ecs.get<fggl::math::Transform>(cam);
+	auto camComp = ecs.get<fggl::gfx::Camera>(cam);
+
+	const glm::vec3 dir = ( camTransform->origin() - camComp->target );
+	const glm::vec3 forward = glm::normalize( dir );
+
+	// scroll wheel
+	glm::vec3 motion(0.0f);
+	float delta = input.mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
+	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
+		motion -= (forward * delta);
+	camTransform->origin( camTransform->origin() + motion );
+
+	if ( cam_mode == cam_arcball || input.mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
+		fggl::input::process_arcball(ecs, input, cam);
+	} else if ( cam_mode == cam_free ) {
+		fggl::input::process_freecam(ecs, input, cam);
+	}
+	fggl::input::process_edgescroll( ecs, input, cam );
+}
 
 namespace demo {
 
@@ -29,10 +212,21 @@ namespace demo {
 
 	void RollBall::activate() {
 		Game::activate();
+
+		Prefabs prefabs{};
+		setupPrefabs(world(), prefabs);
+
+		// actual scene objects
+		setupCamera(world());
+
+		// create a 20x20 grid
+		fggl::math::vec2 size{40.0f, 40.0f};
+		setupEnvironment(world(), prefabs, size);
 	}
 
 	void RollBall::update() {
 		Game::update();
+		process_camera(world(), input());
 	}
 
 	void RollBall::render(fggl::gfx::Graphics &gfx) {
diff --git a/fggl/data/procedural.cpp b/fggl/data/procedural.cpp
index ab4649b..eff2903 100644
--- a/fggl/data/procedural.cpp
+++ b/fggl/data/procedural.cpp
@@ -13,41 +13,10 @@
 using namespace fggl::data;
 
 // from https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
-static glm::vec3 calcSurfaceNormal(glm::vec3 a, glm::vec3 b, glm::vec3 c) {
-	glm::vec3 u = b - a;
-	glm::vec3 v = c - a;
-	return glm::normalize( glm::cross( u, v ) );
-}
-
-static void computeNormals( fggl::data::Mesh& mesh, const int* idx, const int* colIdx, int nPoints) {
-
-	// we're assuming all the normals are zero...
-	// FIXME this will touch any vertex that appears in idx more than once mutliple times...
-	for ( int i=0; i<nPoints; i++ ) {
-		auto& vertex = mesh.vertex( colIdx[ idx[i] ] );
-		vertex.normal = glm::vec3(0.0F);
-	}
-
-	// each vertex normal should be the sum of the triangles it's part of.
-	for (int i=0; i<nPoints; i += 3) {
-		auto& v1 = mesh.vertex( colIdx[ idx[i] ] );
-		auto& v2 = mesh.vertex( colIdx[ idx[i + 1] ] );
-		auto& v3 = mesh.vertex( colIdx[ idx[i + 2] ] );
-
-		const glm::vec3 normal = calcSurfaceNormal( v1.posititon, v2.posititon, v3.posititon );
-//		v2.normal += calcSurfaceNormal( v2.posititon, v3.posititon, v1.posititon );
-//		v3.normal += calcSurfaceNormal( v3.posititon, v1.posititon, v2.posititon );
-		v1.normal += normal;
-		v2.normal += normal;
-		v3.normal += normal;
-
-	}
-
-	// each vertex should currently be the sum of every triangle it's part of
-	// so we need to normalize them
-	for (auto& vertex : mesh.vertexList() ) {
-		vertex.normal = glm::normalize( vertex.normal );
-	}
+static glm::vec3 calcSurfaceNormal(glm::vec3 vert1, glm::vec3 vert2, glm::vec3 vert3) {
+	const glm::vec3 edge1 = vert2 - vert1;
+	const glm::vec3 edge2 = vert3 - vert1;
+	return glm::normalize( glm::cross( edge1, edge2 ) );
 }
 
 static void computeNormalsDirect( fggl::data::Mesh& mesh, const int* colIdx, int nPoints) {
@@ -71,6 +40,146 @@ static void computeNormalsDirect( fggl::data::Mesh& mesh, const int* colIdx, int
 	}
 }
 
+static void computeNormals( fggl::data::Mesh& mesh,
+								   const std::vector<int> idxList, // source index
+								   const std::vector<unsigned int>& idxMapping // source-to-mesh lookup
+								   ) {
+
+	// clear the normals, so the summation below works correctly
+	for (unsigned int vertexIndex : idxMapping) {
+		auto& vertex = mesh.vertex( vertexIndex );
+		vertex.normal = glm::vec3(0.0F);
+	}
+
+	// we need to calculate the contribution for each vertex
+	// this assumes IDXList describes a raw triangle list (ie, not quads and not a strip)
+	for ( int i=0; i < idxList.size(); i += 3) {
+		auto& v1 = mesh.vertex( idxMapping[idxList[i]] );
+		auto& v2 = mesh.vertex( idxMapping[idxList[i + 1]] );
+		auto& v3 = mesh.vertex( idxMapping[idxList[i + 2]] );
+
+		float area = glm::length(glm::cross(v3.posititon - v2.posititon, v1.posititon - v3.posititon)) / 2;
+		auto faceNormal = calcSurfaceNormal(v1.posititon, v2.posititon, v3.posititon);
+
+		v1.normal += area * faceNormal;
+		v2.normal += area * faceNormal;
+		v3.normal += area * faceNormal;
+	}
+
+	// re-normalise the normals
+	for (unsigned int vertexIndex : idxMapping) {
+		auto& vertex = mesh.vertex( vertexIndex );
+		vertex.normal = glm::normalize(vertex.normal);
+	}
+}
+
+constexpr float HALF_PI = M_PI / 2.0F;
+constexpr fggl::math::vec3 ILLEGAL_NORMAL{0.0F, 0.0F, 0.F};
+constexpr fggl::math::vec3 DEFAULT_COLOUR{1.0F, 1.0F, 1.0F};
+
+static void populateMesh(fggl::data::Mesh& mesh, const fggl::math::mat4 transform,
+						 const int nIdx, const fggl::math::vec3* pos, const int* idx ) {
+
+	int* colIdx = new int[nIdx];
+
+	// generate mesh
+	for (int i=0; i<nIdx; i++) {
+		glm::vec3 rawPos = transform * glm::vec4( pos[ idx[i] ], 1.0 );
+
+		Vertex vert{};
+		vert.posititon = rawPos;
+		vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
+		vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
+		colIdx[ i ] = mesh.pushVertex( vert );
+		mesh.pushIndex( colIdx[i] );
+	}
+
+	computeNormalsDirect( mesh, colIdx, nIdx );
+
+	delete[] colIdx;
+}
+
+static void populateMesh(fggl::data::Mesh& mesh,
+						 const fggl::math::mat4 transform,
+						 const std::vector<fggl::math::vec3>& posList,
+						 const std::vector<int>& idxList) {
+
+	// tmp store the resulting mesh indexes (incase the mesh has multiple primatives)
+	std::vector<unsigned int> colIdx;
+	colIdx.reserve(posList.size());
+
+	// clion this thinks this loop is infinite, my assumption is it's gone bananas
+	for (std::size_t i = 0; i < posList.size(); ++i) {
+		Vertex vert{
+			posList[i],
+			ILLEGAL_NORMAL,
+			DEFAULT_COLOUR
+		};
+		colIdx[i] = mesh.pushVertex(vert);
+	}
+
+	// use the remapped indexes for the mesh
+	for (auto idx : idxList) {
+		mesh.pushIndex( colIdx[idx] );
+	}
+
+	computeNormals(mesh, idxList, colIdx);
+}
+
+namespace fggl::data {
+
+	static void quads2Tris(std::vector<int>& indexList, int stacks, int slices) {
+		const auto HORZ_SIZE = slices + 1;
+		for (int vertical = 0; vertical < stacks; vertical++) {
+			for (int horz = 0; horz < slices; horz++) {
+				int lt = vertical * HORZ_SIZE + horz;
+				int rt = vertical * HORZ_SIZE + (horz + 1);
+				int lb = (vertical + 1) * HORZ_SIZE + horz;
+				int rb = (vertical + 1) * HORZ_SIZE + (horz + 1);
+
+				indexList.push_back(lt);
+				indexList.push_back(lb);
+				indexList.push_back(rt);
+
+				indexList.push_back(rt);
+				indexList.push_back(lb);
+				indexList.push_back(rb);
+			}
+		}
+	}
+
+	void make_sphere(Mesh &mesh, const math::mat4& offset, int slices, int stacks) {
+
+		std::vector<math::vec3> positions;
+
+		float verticalAngularStride = M_PI / stacks;
+		float horizontalAngularStride = (M_PI * 2) / slices;
+
+		// calculate vertex positions
+		for (int vertical = 0; vertical < (stacks + 1); vertical++) {
+			float theta = (M_PI / 2.0f) - verticalAngularStride * vertical;
+			for ( int horz = 0; horz < (slices + 1); horz++ ) {
+				float phi = horizontalAngularStride * horz;
+				math::vec3 position {
+					cosf(theta) * cosf(phi),
+					cosf(theta) * sinf(phi),
+					sinf(theta)
+				};
+				positions.push_back(position);
+			}
+		}
+
+		// combine the vertices into triangles
+		std::vector<int> indexList;
+		quads2Tris(indexList, stacks, slices);
+
+		populateMesh(mesh, offset, positions, indexList);
+	}
+
+
+} // namespace fggl::data
+
+
 fggl::data::Mesh fggl::data::make_triangle() {
     constexpr fggl::math::vec3 pos[]{
             {-0.5F, -0.5F, 0.0F},
@@ -155,28 +264,6 @@ fggl::data::Mesh fggl::data::make_quad_xz() {
 }
 
 
-constexpr float HALF_PI = M_PI / 2.0F;
-static void populateMesh(fggl::data::Mesh& mesh, const fggl::math::mat4 transform, 
-		          const int nIdx, const fggl::math::vec3* pos, const int* idx ) {
-
-	int* colIdx = new int[nIdx];
-
-	// generate mesh
-	for (int i=0; i<nIdx; i++) {
-		glm::vec3 rawPos = transform * glm::vec4( pos[ idx[i] ], 1.0 );
-
-		Vertex vert{};
-		vert.posititon = rawPos;
-		vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
-		vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
-		colIdx[ i ] = mesh.pushVertex( vert );
-		mesh.pushIndex( colIdx[i] );
-	}
-
-	computeNormalsDirect( mesh, colIdx, nIdx );
-
-	delete[] colIdx;
-}
 
 
 fggl::data::Mesh fggl::data::make_cube(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
@@ -244,6 +331,7 @@ fggl::data::Mesh fggl::data::make_slope(fggl::data::Mesh& mesh, const fggl::math
 	return mesh;
 }
 
+
 fggl::data::Mesh fggl::data::make_ditch(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
 
 	// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
diff --git a/include/fggl/data/procedural.hpp b/include/fggl/data/procedural.hpp
index f05a859..ca17117 100644
--- a/include/fggl/data/procedural.hpp
+++ b/include/fggl/data/procedural.hpp
@@ -7,37 +7,30 @@ namespace fggl::data {
 
 	constexpr math::mat4 OFFSET_NONE(1.0f);
 
+	// basic shapes 'building blocks'
 	Mesh make_triangle();
-
-	// quads
 	Mesh make_quad_xy();
 	Mesh make_quad_xz();
 
-	// simple shapes
-	Mesh make_cube(Mesh &mesh, const math::mat4 &offset);
-
-	inline Mesh make_cube(Mesh &mesh) {
-		return make_cube(mesh, OFFSET_NONE);
-	}
-
-	// blockout shapes
-	Mesh make_slope(Mesh &mesh, const math::mat4 &offset);
-
-	inline Mesh make_slope(Mesh &mesh) {
-		return make_slope(mesh, OFFSET_NONE);
-	}
-
-	Mesh make_ditch(Mesh &mesh, const math::mat4 &offset);
-
-	inline Mesh make_ditch(Mesh &mesh) {
-		return make_ditch(mesh, OFFSET_NONE);
-	}
-
-	Mesh make_point(Mesh &mesh, const math::mat4 &offset);
-
-	inline Mesh make_point(Mesh &mesh) {
-		return make_point(mesh, OFFSET_NONE);
-	}
+	// platonic solids
+	void make_tetrahedron(Mesh& mesh, const math::mat4& offset = OFFSET_NONE);
+	Mesh make_cube(Mesh &mesh, const math::mat4 &offset = OFFSET_NONE);
+	void make_octahedron(Mesh& mesh, const math::mat4& offset = OFFSET_NONE);
+	void make_icosahedron(Mesh& mesh, const math::mat4& offset = OFFSET_NONE);
+	void make_dodecahedron(Mesh& mesh, const math::mat4& offset = OFFSET_NONE);
+
+	// useful shapes
+	void make_sphere_uv(Mesh &mesh, const math::mat4& offset = OFFSET_NONE);
+	void make_sphere_iso(Mesh &mesh, const math::mat4& offset = OFFSET_NONE);
+
+	// level block-out shapes
+	Mesh make_slope(Mesh &mesh, const math::mat4 &offset = OFFSET_NONE);
+	Mesh make_ditch(Mesh &mesh, const math::mat4 &offset = OFFSET_NONE);
+	Mesh make_point(Mesh &mesh, const math::mat4 &offset = OFFSET_NONE);
+
+	// other useful types people expect
+	void make_capsule(Mesh& mesh);
+	void make_sphere(Mesh &mesh, const math::mat4& offset = OFFSET_NONE, int stacks = 16, int slices = 16);
 }
 
 #endif
\ No newline at end of file
diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp
index a914100..8d10352 100644
--- a/include/fggl/math/types.hpp
+++ b/include/fggl/math/types.hpp
@@ -85,7 +85,7 @@ namespace fggl::math {
 	struct Transform {
 			constexpr static const char name[] = "Transform";
 
-			Transform() : m_local(1.0f), m_origin(0.0f), m_model(1.0f), m_rotation() {
+			Transform() : m_local(1.0f), m_origin(0.0f), m_model(1.0f), m_rotation(1.0f, 0.0f, 0.0f, 0.0f) {
 			}
 
 			// local reference vectors
@@ -119,6 +119,12 @@ namespace fggl::math {
 				return m_origin;
 			}
 
+			inline void rotate(math::vec3 axis, float angle) {
+				// documentation claims this is in degrees, based on experimentation this actually appears to be radians...
+				m_rotation = glm::rotate(m_rotation, angle, axis);
+				update();
+			}
+
 			inline void euler(vec3 angles) {
 				m_rotation = quat(angles);
 				update();
@@ -130,11 +136,8 @@ namespace fggl::math {
 			}
 
 			inline mat4 model() const {
-				mat4 tmp(1.0f);
-				tmp = glm::translate(tmp, m_origin);
-				tmp = tmp * glm::toMat4(m_rotation);
-
-				return tmp;
+				return glm::translate(glm::mat4(1.0F), m_origin)
+					* glm::toMat4(m_rotation);
 			}
 
 		private:
-- 
GitLab