diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index 95be675460717cd065363a73f2a3efac55678ff8..49396f66f75cf008b3edee10b8f666c4cefa13b5 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 ab4649bcaeb9d0c8145dc5a0fede7ca2d2ae7583..eff290369a61ab6eda730cbd1756b07ecdf35bc1 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 f05a859fbb7f693717a6f114b4ec793c5bb0717e..ca17117bea4e6c1870393a3e5aa5a21ad94396c8 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 a91410011b146218cb90045dac03a5444f0cff84..8d10352d9aad9777a24e7aa4d1ad7003a688c03b 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: