diff --git a/CMakeLists.txt b/CMakeLists.txt index c806330973304b785780e9d644a6808b9e101149..70753536ad8a4081912073c2a5db6394005e0964 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ cmake_minimum_required(VERSION 3.16) set(namespace "fggl") +set(CMAKE_CXX_STANDARD 20) + option(FGGL_CONAN "Should we use conan to find missing dependencies?" ON) set(CONAN_BUILD_TYPE "Debug") diff --git a/demo/CMakeLists.txt b/demo/CMakeLists.txt index 4318eb64b7f405717aa5e6fe7ca2b106fd6f99e9..f2e3b4c3ebef8b8eeb3b20700deae2c18df54cab 100644 --- a/demo/CMakeLists.txt +++ b/demo/CMakeLists.txt @@ -15,6 +15,9 @@ target_include_directories(demo ) target_link_libraries(demo fggl) + +find_package(spdlog) +target_link_libraries(demo spdlog::spdlog) #target_include_directories(FgglDemo PUBLIC ${PROJECT_BINARY_DIR}) # rssources diff --git a/demo/data/include/phong.glsl b/demo/data/include/phong.glsl index 1cf3dfb7c05119a907673e6b19f06198ce32f3c3..65cc6a4f1e96bc18fc90ab1eaf7292161b074f46 100644 --- a/demo/data/include/phong.glsl +++ b/demo/data/include/phong.glsl @@ -1,5 +1,118 @@ /** * OpenGL phong lighting model. + * Adapted from LearnOpenGL.com, by Joey de Vries, CC BY-NC 4.0 */ -// TODO write script \ No newline at end of file +struct Material { + sampler2D diffuse; + sample2D specular; + float shininess; +}; + +struct DirectionalLight { + vec3 direction; + + vec3 ambient; + vec3 diffuse; + vec3 specular; +}; + +struct PointLight { + vec3 position; + + vec3 ambient; + vec3 diffuse; + vec3 specular; + + float constant; + float linear; + float quadratic; +}; + +struct SpotLight { + vec3 position; + vec3 direction; + + vec3 ambient; + vec3 diffuse; + vec3 specular; + + float constant; + float linear; + float quadratic; + + float cutOff; + float outerCutOff; +}; + +vec3 CalcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir) { + vec3 lightDir = normalize(-light.direction); + + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + return (ambient + diffuse + specular); +} + +vec3 CalcPointLight(PointLight light, vec3 normal, vec3 fragPos, vec3 viewDir) { + vec3 lightDir = normalize(light.position - fragPos); + + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + + // attenuation + float distance = length(light.position - fragPos); + float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); + + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + + ambient *= attenuation; + diffuse *= attenuation; + specular *= attenuation; + return (ambient + diffuse + specular); +} + +vec3 CalcSpotLight(SpotLight light, vec3 normal, vec3 fragPos, vec3 viewDir) { + vec3 lightDir = normalize(light.position - fragPos); + + // diffuse shading + float diff = max(dot(normal, lightDir), 0.0); + + // specular shading + vec3 reflectDir = reflect(-lightDir, normal); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess); + + // attenuation + float distance = length(light.position - fragPos); + float attenuation = 1.0 / (light.constant + light.linear * distance + light.quadratic * (distance * distance)); + + // spotlight intensity + float theta = dot(lightDir, normalize(-light.direction)); + float epsilon = light.cutOff - light.outerCutOff; + float intensity = clamp((theta - light.outerCutOff) / epsilon, 0.0, 1.0); + + // combine results + vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords)); + vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords)); + vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords)); + + ambient *= attenuation * intensity; + diffuse *= attenuation * intensity; + specular *= attenuation * intensity; + return (ambient + diffuse + specular); +} \ No newline at end of file diff --git a/demo/demo/rollball.cpp b/demo/demo/rollball.cpp index d20e2f3e784ad7ffeb309b5a05895f350b14dbb3..580c96541747377b3ee64d7b793593abcc1dc649 100644 --- a/demo/demo/rollball.cpp +++ b/demo/demo/rollball.cpp @@ -25,6 +25,7 @@ #include "fggl/input/camera_input.h" #include "fggl/util/service.h" #include "fggl/ecs3/prototype/loader.hpp" +#include "fggl/debug/draw.hpp" #include <array> @@ -198,6 +199,10 @@ namespace demo { moved = true; } + if (input.keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_SPACE))) { + state.mode = (DebugMode)( ((int)state.mode + 1) % 3 ); + } + // setup dynamic force if ( moved ) { float forceFactor = 3.0F; @@ -216,33 +221,96 @@ namespace demo { camTransform->origin( camComp->target + cameraOffset ); } - // rotation - float closestDistance = FLT_MAX; - fggl::ecs::entity_t closestEntity = fggl::ecs::NULL_ENTITY; - auto playerPos = world.get<fggl::math::Transform>(state.player)->origin(); - - for ( auto& entity : state.collectables ) { - if ( world.alive(entity) ) { - auto* transform = world.get<fggl::math::Transform>(entity); - - // rotate the cubes - fggl::math::vec3 angles{1.0F, 2.0F, 3.0F}; - transform->rotateEuler( angles * (60.0F / 1000) ); - - auto distance = glm::distance(transform->origin(), playerPos); - if ( distance < closestDistance) { - closestDistance = distance; - closestEntity = entity; + // distance mode + if ( state.mode == DebugMode::DISTANCE ) { + closestPickup(world); + } else { + if ( state.closestPickup != fggl::ecs::NULL_ENTITY ) { + if ( world.alive(state.closestPickup) ){ + auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup); + if (renderer != nullptr) { + renderer->diffuse = {1.0f, 1.0f, 1.0f}; + } } - //auto* renderer = world.get<fggl::data::StaticMesh>( entity ); - //renderer->hintColour = fggl::math::vec3(1.0F, 1.0F, 1.0F); + state.closestPickup = fggl::ecs::NULL_ENTITY; } } + + spinCubes(world); state.time += (60.0f / 1000); } } + void RollBall::closestPickup(fggl::ecs3::World &world) { + float closestDistance = FLT_MAX; + fggl::ecs::entity_t closestEntity = fggl::ecs::NULL_ENTITY; + auto playerPos = world.get<fggl::math::Transform>(state.player)->origin(); + + for ( const auto& pickup : state.collectables ) { + if ( !world.alive(pickup) ) { + continue; + } + + auto* transform = world.get<fggl::math::Transform>(pickup); + auto distance = glm::distance(transform->origin(), playerPos); + if ( distance < closestDistance) { + closestDistance = distance; + closestEntity = pickup; + } + } + + if ( state.closestPickup != closestEntity ) { + if ( state.closestPickup != fggl::ecs::NULL_ENTITY ){ + // deal with the previous closest pickup + auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup); + if (renderer != nullptr) { + renderer->diffuse = {1.0f, 1.0f, 1.0f}; + } + } + + // now, deal with the new closest + state.closestPickup = closestEntity; + if (closestEntity != fggl::ecs::NULL_ENTITY) { + auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup); + if (renderer != nullptr) { + renderer->diffuse = {0.0f, 0.0f, 1.0f}; + } + } + } + + // closest pickup should face the player + if ( state.closestPickup != fggl::ecs::NULL_ENTITY) { + auto* transform = world.get<fggl::math::Transform>(state.closestPickup); + auto* playerTransform = world.get<fggl::math::Transform>( state.player ); + transform->lookAt(playerTransform->origin()); + + // lazy, so using the debug draw line for this bit + dd::line( &playerTransform->origin()[0], &transform->origin()[0], dd::colors::White ); + } + + } + + void RollBall::spinCubes(fggl::ecs3::World& world) { + // rotation + for ( const auto& entity : state.collectables ) { + if ( !world.alive(entity) || entity == state.closestPickup ) { + continue; + } + + auto* transform = world.get<fggl::math::Transform>(entity); + + // rotate the cubes + fggl::math::vec3 angles{15, 30, 45}; + transform->rotateEuler( angles * (60.0F / 1000) ); + + + //auto* renderer = world.get<fggl::data::StaticMesh>( entity ); + //renderer->hintColour = fggl::math::vec3(1.0F, 1.0F, 1.0F); + } + + } + void RollBall::render(fggl::gfx::Graphics &gfx) { Game::render(gfx); } diff --git a/demo/include/rollball.hpp b/demo/include/rollball.hpp index de86c6451899a82b4425126aa7e6220396c07e35..adda4f10469dfb62d8d89d127cd6e1dbe3dbc1af 100644 --- a/demo/include/rollball.hpp +++ b/demo/include/rollball.hpp @@ -29,8 +29,17 @@ namespace demo { + enum class DebugMode { + NORMAL = 0, + DISTANCE = 1, + VISION = 2 + }; + struct RollState { fggl::ecs::entity_t player = fggl::ecs::NULL_ENTITY; + fggl::ecs::entity_t closestPickup; + DebugMode mode = DebugMode::NORMAL; + std::array<fggl::ecs3::entity_t, 3> collectables { fggl::ecs::NULL_ENTITY, fggl::ecs::NULL_ENTITY, @@ -41,6 +50,7 @@ namespace demo { class RollBall : public fggl::scenes::Game { public: + explicit RollBall(fggl::App& app); void activate() override; @@ -49,8 +59,12 @@ namespace demo { void render(fggl::gfx::Graphics& gfx) override; private: + constexpr static fggl::math::vec3 HINT_COLOUR{0.5f, 0.0f, 0.0f}; RollState state; fggl::math::vec3 cameraOffset = {-15.0F, 15.0F, 0.0F}; + + void closestPickup(fggl::ecs3::World& world); + void spinCubes(fggl::ecs3::World& world); }; } diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index c9529e9d8bca4f0f182b53f5f22a79e3d8a6006b..99e994ab1a32af1f0cbe2ad90af9e2ff93e3e4c5 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -12,8 +12,8 @@ target_include_directories(fggl $<INSTALL_INTERFACE:include> ) -# users of this library need at least C++17 -target_compile_features(fggl PUBLIC cxx_std_17) +# users of this library need at least C++20 +target_compile_features(fggl PUBLIC cxx_std_20) # IDE support for nice header files source_group( diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index c51a07058920489710f22cf8ff58c9fde11d05a7..c713cba9cd3fd8f4618c90a2ebb5ec45c5f131e7 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -1,5 +1,5 @@ #include <fggl/util/service.h> -#include <spdlog/spdlog.h> +#include "fggl/debug/logging.hpp" #include "fggl/gfx/ogl/common.hpp" #include "fggl/gfx/window.hpp" @@ -77,24 +77,25 @@ constexpr auto static fggl_ogl_type(GLenum type) -> const char * { #pragma ide diagnostic ignored "bugprone-easily-swappable-parameters" #endif +constexpr const char* OGL_LOG_FMT {"[GL] {}, {}: [{}]: {}"}; + void static fggl_ogl_to_spdlog(GLenum source, GLenum type, unsigned int msgId, GLenum severity, GLsizei /*length*/, const char *message, const void * /*userParam*/) { - std::string fmt = "[GL] {}, {}: [{}]: {}"; const auto *const sourceStr = fggl_ogl_source(source); const auto *const typeStr = fggl_ogl_type(type); // table 20.3, GL spec 4.5 switch (severity) { - case GL_DEBUG_SEVERITY_HIGH: spdlog::error(fmt, sourceStr, typeStr, msgId, message); - break; - default: - case GL_DEBUG_SEVERITY_MEDIUM: spdlog::warn(fmt, sourceStr, typeStr, msgId, message); - break; - case GL_DEBUG_SEVERITY_LOW: spdlog::info(fmt, sourceStr, typeStr, msgId, message); - break; - case GL_DEBUG_SEVERITY_NOTIFICATION: spdlog::debug(fmt, sourceStr, typeStr, msgId, message); - break; + case GL_DEBUG_SEVERITY_HIGH: fggl::debug::error(OGL_LOG_FMT, sourceStr, typeStr, msgId, message); + break; + default: + case GL_DEBUG_SEVERITY_MEDIUM: fggl::debug::warning(OGL_LOG_FMT, sourceStr, typeStr, msgId, message); + break; + case GL_DEBUG_SEVERITY_LOW: fggl::debug::info(OGL_LOG_FMT, sourceStr, typeStr, msgId, message); + break; + case GL_DEBUG_SEVERITY_NOTIFICATION: fggl::debug::debug(OGL_LOG_FMT, sourceStr, typeStr, msgId, message); + break; } } @@ -134,7 +135,7 @@ namespace fggl::gfx { GLint flags = 0; glGetIntegerv(GL_CONTEXT_FLAGS, &flags); if ( (flags & GL_CONTEXT_FLAG_DEBUG_BIT) == GL_CONTEXT_FLAG_DEBUG_BIT) { // NOLINT(hicpp-signed-bitwise) - spdlog::info("enabling OpenGL debug output"); + debug::info("enabling OpenGL debug output"); glEnable(GL_DEBUG_OUTPUT); glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback(fggl_ogl_to_spdlog, nullptr); @@ -209,13 +210,13 @@ namespace fggl::gfx { void GlMeshRenderer::render(fggl::ecs3::World &ecs, ecs3::entity_t camera, float dt) { if (camera == ecs::NULL_ENTITY) { - spdlog::warn("tried to render a scene, but no camera exists!"); + debug::warning("tried to render a scene, but no camera exists!"); return; } auto entities = ecs.findMatching<GlRenderToken>(); if (entities.empty()) { - spdlog::warn("asked to render, but no entities are renderable"); + debug::warning("asked to render, but no entities are renderable"); return; } diff --git a/fggl/gfx/ogl4/models.cpp b/fggl/gfx/ogl4/models.cpp index db1facd20f19325af4cfdb11779403aece7e7677..e13e92736c9707fe3800ca951bc092585c6d72e6 100644 --- a/fggl/gfx/ogl4/models.cpp +++ b/fggl/gfx/ogl4/models.cpp @@ -74,7 +74,7 @@ namespace fggl::gfx::ogl4 { // FIXME: this needs something reactive or performance will suck. auto renderables = world.findMatching<data::StaticMesh>(); for (auto& renderable : renderables){ - auto currModel = world.get<StaticModel>( renderable ); + auto currModel = world.tryGet<StaticModel>( renderable ); if ( currModel != nullptr ){ continue; } diff --git a/fggl/phys/bullet/simulation.cpp b/fggl/phys/bullet/simulation.cpp index e771e5d82ec6ab5a8b2a1f0d66427fbb49ea37a6..85cacddf965fc591dbe0d08d4d9180878bd6b631 100644 --- a/fggl/phys/bullet/simulation.cpp +++ b/fggl/phys/bullet/simulation.cpp @@ -19,9 +19,14 @@ */ #include "fggl/phys/bullet/types.hpp" +#include "fggl/phys/bullet/motion.hpp" namespace fggl::phys::bullet { + inline btVector3 to_bullet(const math::vec3 fgglVec) { + return {fgglVec.x, fgglVec.y, fgglVec.z}; + } + BulletPhysicsEngine::BulletPhysicsEngine(ecs3::World* world) : m_ecs(world), m_config(), m_world(nullptr) { m_config.broadphase = new btDbvtBroadphase(); m_config.collisionConfiguration = new btDefaultCollisionConfiguration(); @@ -38,6 +43,9 @@ namespace fggl::phys::bullet { m_world->setDebugDrawer( m_debug.get() ); m_debug->setDebugMode(1); + // callbacks (for handling bullet -> ecs) + + // ensure we deal with ecs -> bullet changes m_ecs->addDeathListener( [this](auto entity) { this->onEntityDeath(entity);} ); } @@ -62,30 +70,13 @@ namespace fggl::phys::bullet { } m_world->stepSimulation(60.0f); - syncToECS(); - dealWithCollisions(); + //syncToECS(); + pollCollisions(); m_world->debugDrawWorld(); } - - static void build_motation_state(math::Transform* myState, BulletBody* btState) { - auto myPos = myState->origin(); - btVector3 position{myPos.x, myPos.y, myPos.z}; - - auto myRot = myState->euler(); - btQuaternion rotation; - rotation.setEulerZYX(myRot.x, myRot.y, myRot.z); - - btTransform transform; - transform.setIdentity(); - transform.setRotation(rotation); - transform.setOrigin(position); - - btState->motion = new btDefaultMotionState(transform); - } - - inline btCollisionShape* shapeToBullet(const phys::RigidBody* fgglBody) { + inline btCollisionShape* shape_to_bullet(const phys::RigidBody* fgglBody) { if ( fgglBody->shape == nullptr ) { // they forgot to put a shape, we'll assume a unit sphere to avoid crashes... return new btSphereShape(0.5f); @@ -109,20 +100,19 @@ namespace fggl::phys::bullet { // FIXME without reactive-based approaches this is very slow auto entsWantPhys = m_ecs->findMatching<phys::RigidBody>(); for (auto ent : entsWantPhys) { - auto* btComp = m_ecs->get<BulletBody>(ent); + auto* btComp = m_ecs->tryGet<BulletBody>(ent); if ( btComp != nullptr ) { - // they are already in the simluation + // they are already in the simulation continue; } + // set up the bullet proxy for our object btComp = m_ecs->add<BulletBody>(ent); const auto* fgBody = m_ecs->get<phys::RigidBody>(ent); + btComp->motion = new FgglMotionState(m_ecs, ent); - // setup the starting motion state - auto* transform = m_ecs->get<math::Transform>(ent); - build_motation_state(transform, btComp); - - btCollisionShape* colShape = shapeToBullet(fgBody); + // collisions + btCollisionShape* colShape = shape_to_bullet(fgBody); btVector3 localInt(0, 0, 0); if ( !fgBody->isStatic() ) { colShape->calculateLocalInertia(fgBody->mass, localInt); @@ -135,27 +125,31 @@ namespace fggl::phys::bullet { colShape, localInt ); - auto* body = new btRigidBody(bodyCI); - body->setUserIndex(static_cast<int>(ent)); + btComp->body = new btRigidBody(bodyCI); + btComp->body->setUserIndex( static_cast<int>(ent) ); - btComp->body = body; if (!fgBody->isStatic()) { - body->setRestitution(0.5f); - body->setRollingFriction(0.0f); + btComp->body->setRestitution(0.5f); + btComp->body->setRollingFriction(0.0f); } if ( fgBody->type == BodyType::KINEMATIC ) { - body->setCollisionFlags( body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT ); + auto flags = btComp->body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT; + btComp->body->setCollisionFlags( flags ); + + // we don't have a clean way of saying a kinematic object moved, so just prevent sleeping. + // if this turns out to be an issue, we'll need to revisit this. + btComp->body->setActivationState( DISABLE_DEACTIVATION ); } - m_world->addRigidBody(btComp->body ); + m_world->addRigidBody( btComp->body ); } } - void BulletPhysicsEngine::syncToECS() { - auto physEnts = m_ecs->findMatching<BulletBody>(); - for (auto& ent : physEnts) { + void BulletPhysicsEngine::forceSyncToECS() { + const auto physEnts = m_ecs->findMatching<BulletBody>(); + for (const auto& ent : physEnts) { auto* transform = m_ecs->get<math::Transform>(ent); auto* physBody = m_ecs->get<BulletBody>(ent); @@ -185,9 +179,10 @@ namespace fggl::phys::bullet { auto entRB = m_ecs->findMatching<BulletBody>(); for (auto& ent : entRB) { auto* bulletBody = m_ecs->get<BulletBody>(ent); - delete bulletBody->motion; m_world->removeCollisionObject(bulletBody->body); - delete bulletBody->body; + + // release resources and delete + bulletBody->release(); m_ecs->remove<BulletBody>(ent); } @@ -223,7 +218,7 @@ namespace fggl::phys::bullet { } } - void BulletPhysicsEngine::dealWithCollisions() { + void BulletPhysicsEngine::pollCollisions() { // flush collision caches auto caches = m_ecs->findMatching<CollisionCache>(); for( auto& ent : caches) { @@ -249,13 +244,21 @@ namespace fggl::phys::bullet { continue; } - std::cerr << "contact: " << body0->getUserIndex() << " on " << body1->getUserIndex() << std::endl; + int numContacts = contactManifold->getNumContacts(); + for ( auto contactIdx = 0; contactIdx < numContacts; ++contactIdx ) { + auto& point = contactManifold->getContactPoint(contactIdx); + if ( point.getDistance() < 0.0F ) { + auto worldPosA = point.getPositionWorldOnA(); + auto worldPosB = point.getPositionWorldOnB(); + auto normal = point.m_normalWorldOnB; + } + } handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex()); handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex()); } - // note conacts that have ended + // note contacts that have ended caches = m_ecs->findMatching<CollisionCache>(); // re-fetch, entities can (and probably do) die in handlers. for( auto& ent : caches) { auto* cache = m_ecs->get<CollisionCache>(ent); @@ -279,9 +282,74 @@ namespace fggl::phys::bullet { void BulletPhysicsEngine::onEntityDeath(ecs::entity_t entity) { auto* btPhysics = m_ecs->tryGet<BulletBody>(entity); if ( btPhysics != nullptr) { + // decouple physics from entity btPhysics->body->setUserIndex( ecs::NULL_ENTITY ); m_world->removeRigidBody( btPhysics->body ); + btPhysics->release(); } } + void BulletPhysicsEngine::onCollisionCreate(ContactPoint point) { + m_contactCache.created.push_back(point); + } + + void BulletPhysicsEngine::onCollisionProcess(ContactPoint point) { + m_contactCache.modified.push_back( point ); + } + + void BulletPhysicsEngine::onCollisionDestroyed(ContactPoint point) { + m_contactCache.removed.push_back(point); + } + + ecs3::entity_t BulletPhysicsEngine::raycast(math::vec3 from, math::vec3 to) { + const auto btFrom = to_bullet(from); + const auto btTo = to_bullet(to); + + btCollisionWorld::ClosestRayResultCallback rayTestResult(btFrom, btTo); + m_world->rayTest(btFrom, btTo, rayTestResult); + + if ( !rayTestResult.hasHit() ) { + return ecs3::NULL_ENTITY; + } + + // tell the user what it hit + return rayTestResult.m_collisionObject->getUserIndex(); + } + + std::vector<ContactPoint> BulletPhysicsEngine::scanCollisions(ecs3::entity_t entity) { + return std::vector<ContactPoint>(); + } + + std::vector<ecs3::entity_t> BulletPhysicsEngine::raycastAll(math::vec3 fromPos, math::vec3 toPos) { + const auto btFrom = to_bullet(fromPos); + const auto btTo = to_bullet(toPos); + + btCollisionWorld::AllHitsRayResultCallback rayTestResult(btFrom, btTo); + m_world->rayTest(btFrom, btTo, rayTestResult); + + // we didn't hit anything + if ( !rayTestResult.hasHit() ) { + return {}; + } + + // hit processing + const auto hits = rayTestResult.m_collisionObjects.size(); + + std::vector<ecs3::entity_t> hitVec; + hitVec.reserve(hits); + + for ( auto i = 0; i < hits; ++i) { + ecs3::entity_t entity = rayTestResult.m_collisionObjects[i]->getUserIndex(); + hitVec.push_back(entity); + } + + return hitVec; + } + + std::vector<ecs3::entity_t> BulletPhysicsEngine::sweep(PhyShape &shape, + math::Transform &from, + math::Transform &to) { + return std::vector<ecs3::entity_t>(); + } + } // namespace fggl::phys::bullet \ No newline at end of file diff --git a/include/fggl/debug/debug.h b/include/fggl/debug/debug.h index 4e303f65f6a20a1fc874bf7f5e6a90da5f32adae..098616186f17f827bb68867d0942ced8ed31a264 100644 --- a/include/fggl/debug/debug.h +++ b/include/fggl/debug/debug.h @@ -1,11 +1,13 @@ #ifndef FGGL_DEBUG_H #define FGGL_DEBUG_H +#include <string> +#include <memory> #include <utility> #include <functional> #include <unordered_map> -#include <fggl/gfx/window.hpp> +#include "fggl/gfx/window.hpp" namespace fggl::debug { diff --git a/include/fggl/debug/impl/logging_spdlog.hpp b/include/fggl/debug/impl/logging_spdlog.hpp new file mode 100644 index 0000000000000000000000000000000000000000..f641238e2dd6ad71cdc853c2a98c4794dccc3f92 --- /dev/null +++ b/include/fggl/debug/impl/logging_spdlog.hpp @@ -0,0 +1,99 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 08/06/22. +// + +#ifndef FGGL_DEBUG_IMPL_LOGGING_SPDLOG_HPP +#define FGGL_DEBUG_IMPL_LOGGING_SPDLOG_HPP + +#include <string> +#include <iostream> +#include <memory> + +#include <spdlog/spdlog.h> +#include <spdlog/sinks/stdout_color_sinks.h> + +namespace fggl::debug { + + using FmtType = const std::string_view; + + /** + * Logging levels + */ + enum class Level { + critical = spdlog::level::critical, + error = spdlog::level::err, + warning = spdlog::level::warn, + info = spdlog::level::info, + debug = spdlog::level::debug, + trace = spdlog::level::trace + }; + + template<typename ...T> + void error(const FmtType& fmt, T&& ...args) { + spdlog::error(fmt, args...); + } + + template<typename ...T> + void warning(const FmtType& fmt, T&& ...args ){ + spdlog::warn(fmt, args...); + } + + template<typename ...T> + void info(const FmtType& fmt, T&& ...args ) { + spdlog::info(fmt, args...); + } + + template<typename ...T> + void debug(const FmtType& fmt, T&& ...args ) { + spdlog::debug(fmt, args...); + } + + template<typename ...T> + void trace(const FmtType& fmt, T&& ...args ) { + spdlog::trace(fmt, args...); + } + + template<typename ...T> + void log(const FmtType& fmt, T&& ...args) { + spdlog::log(Level::info, fmt, args...); + } + + template<typename ...T> + void log(Level level, const FmtType& fmt, T&& ...args) { + spdlog::log(level, fmt, args...); + } + + class Logger { + public: + Logger(); + + template<typename ...Args> + void log(Level level, const FmtType& fmt, Args&& ...args) { + spdlog::log(level, fmt, args...); + } + }; + +} + + +#endif //FGGL_DEBUG_IMPL_LOGGING_SPDLOG_HPP diff --git a/include/fggl/debug/impl/logging_std20.hpp b/include/fggl/debug/impl/logging_std20.hpp new file mode 100644 index 0000000000000000000000000000000000000000..b82d8523a588862bfe81440910b6619c0968fc94 --- /dev/null +++ b/include/fggl/debug/impl/logging_std20.hpp @@ -0,0 +1,121 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 11/06/22. +// + +#ifndef FGGL_DEBUG_IMPL_LOGGING_STD20_HPP +#define FGGL_DEBUG_IMPL_LOGGING_STD20_HPP + +#include <fmt/format.h> +#include <iostream> +#include <string> +#include <string_view> +#include <cstdio> + +constexpr std::string_view CERR_FMT{"[{}] {}\n"}; + +namespace fggl::debug { + using FmtType = const std::string_view; + + /** + * Logging levels + */ + enum class Level { + critical = 0, + error = 1, + warning = 2, + info = 3, + debug = 4, + trace = 5 + }; + + constexpr std::string_view level_to_string(Level level) { + switch (level) { + case Level::critical: + return "CRITITAL"; + case Level::error: + return "ERROR"; + case Level::warning: + return "WARNING"; + case Level::info: + return "INFO"; + case Level::debug: + return "DEBUG"; + case Level::trace: + return "TRACE"; + default: + return "UNKNOWN"; + } + } + + inline void vlog(const char* file, int line, fmt::string_view format, fmt::format_args args) { + fmt::print("{}: {}", file, line); + fmt::vprint(format, args); + } + + template <typename S, typename... Args> + void logf(const char* file, int line, const S& format, Args&&... args) { + vlog( file, line, format, fmt::make_args_checked<Args...>(format, args...)); + } + + #define info_va(format, ...) \ + logf(__FILE__, __LINE__, FMT_STRING(format), __VA_ARGS__) + + template<typename ...T> + void log(Level level, FmtType fmt, T &&...args) { + auto fmtStr = fmt::format(fmt::runtime(fmt), args...); + fmt::print(CERR_FMT, level_to_string(level), fmtStr); + } + + template<typename ...T> + void error(FmtType fmt, T &&...args) { + log( Level::error, fmt, args...); + } + + template<typename ...T> + void warning(FmtType fmt, T &&...args) { + log( Level::warning, fmt, args...); + } + + template<typename ...T> + void info(FmtType fmt, T &&...args) { + log( Level::info, fmt, args... ); + } + + template<typename ...T> + void log(FmtType fmt, T &&...args) { + log( Level::info, fmt, args...); + } + + template<typename ...T> + void debug(FmtType fmt, T &&...args) { + log( Level::debug, fmt, args...); + } + + template<typename ...T> + void trace(FmtType fmt, T &&...args) { + log( Level::trace, fmt, args...); + } + +} + +#endif //FGGL_DEBUG_IMPL_LOGGING_STD20_HPP diff --git a/include/fggl/debug/logging.hpp b/include/fggl/debug/logging.hpp new file mode 100644 index 0000000000000000000000000000000000000000..dda37ce671507fb12eb04a0916a1628c4557e8c5 --- /dev/null +++ b/include/fggl/debug/logging.hpp @@ -0,0 +1,57 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 11/06/22. +// + +#ifndef FGGL_DEBUG_LOGGING_HPP +#define FGGL_DEBUG_LOGGING_HPP + +namespace fggl::debug { + + using FmtType = const std::string_view; + enum class Level; + + template<typename ...T> + void error(FmtType fmt, T&& ...args); + + template<typename ...T> + void warning(FmtType fmt, T&& ...args ); + + template<typename ...T> + void info(FmtType fmt, T&& ...args ); + + template<typename ...T> + void debug(FmtType fmt, T&& ...args ); + + template<typename ...T> + void trace(FmtType fmt, T&& ...args ); + + template<typename ...T> + void log(FmtType fmt, T&& ...args); + + template<typename ...T> + void log(Level level, FmtType fmt, T&& ...args); +} + +#include "fggl/debug/impl/logging_std20.hpp" + +#endif //FGGL_DEBUG_LOGGING_HPP diff --git a/include/fggl/ecs3/prototype/world.h b/include/fggl/ecs3/prototype/world.h index c22ee52074bce6b33a5b793073943111fd0ce660..7cd6287556f0ec846d140341aaceedaba8330b58 100644 --- a/include/fggl/ecs3/prototype/world.h +++ b/include/fggl/ecs3/prototype/world.h @@ -9,7 +9,8 @@ #include <functional> #include <unordered_set> -#include <fggl/ecs3/types.hpp> +#include "fggl/ecs3/types.hpp" +#include "fggl/debug/logging.hpp" #include <yaml-cpp/yaml.h> /** @@ -280,17 +281,14 @@ namespace fggl::ecs3::prototype { template<typename C> C* tryGet(entity_t entity_id) const { - assert( entity_id != NULL_ENTITY && "attempted to tryGet component on null entity" ); + if ( entity_id == NULL_ENTITY) { + return nullptr; + } try { - const auto &entity = m_entities.at(entity_id); - try { - return entity.get<C>(); - } catch ( std::out_of_range& e ) { - return nullptr; - } + return get<C>(entity_id); } catch ( std::out_of_range& e) { - std::cerr << "someone requested an entity that didn't exist, entity was: " << entity_id << std::endl; + fggl::debug::info("component {} on {} did not exist", C::name, entity_id); return nullptr; } } @@ -298,11 +296,9 @@ namespace fggl::ecs3::prototype { template<typename C> C *get(entity_t entity_id) const { assert( exists(entity_id) && "attempted to get component on null entity" ); - C* ptr = tryGet<C>(entity_id); - if (ptr == nullptr) { - std::cerr << "entity " << entity_id << " does not have component "<< C::name << std::endl; - } - return ptr; + + const auto& entity = m_entities.at(entity_id); + return entity.get<C>(); } template<typename C> diff --git a/include/fggl/gfx/phong.hpp b/include/fggl/gfx/phong.hpp index 2e59714fab9e8f223a2a2de267c690b7afedefbf..9276c7ce5cbc3ca7ff4b85af0a8128c6e4b77ac8 100644 --- a/include/fggl/gfx/phong.hpp +++ b/include/fggl/gfx/phong.hpp @@ -37,6 +37,28 @@ namespace fggl::gfx { float shininess; }; + enum class LightType { + Directional, + Point, + Spot + }; + + struct Light { + constexpr static const char* name = "gfx::light"; + LightType type; + math::vec3 position; + + // colours + math::vec3 ambient; + math::vec3 diffuse; + math::vec3 specular; + + // distance data + math::vec3 falloffs; // (constant, linear, quadratic) + float cutOff; + float outerCutOff; + }; + } // namesapce fggl::gfx #endif //FGGL_GFX_PHONG_HPP diff --git a/include/fggl/math/types.hpp b/include/fggl/math/types.hpp index 872e5db98b6c7c46e270724fb206f43cc592762e..7a7be0630e51e8548e78b0c4ec1de54562f13a9f 100644 --- a/include/fggl/math/types.hpp +++ b/include/fggl/math/types.hpp @@ -6,8 +6,9 @@ #include <glm/glm.hpp> #include <glm/ext/matrix_transform.hpp> -#include <glm/gtx/transform.hpp> #include <glm/gtc/quaternion.hpp> +#include <glm/gtx/transform.hpp> +#include <glm/gtx/euler_angles.hpp> #include <glm/gtx/quaternion.hpp> #ifndef M_PI @@ -185,6 +186,15 @@ namespace fggl::math { m_model = parent * local(); } + inline void lookAt(vec3 target) { +// auto direction = m_origin - target; + auto result = glm::lookAt(m_origin, target, math::AXIS_Y); + + math::vec3 resultAngles; + glm::extractEulerAngleXYZ(result, resultAngles.x, resultAngles.y, resultAngles.z); + m_euler = glm::degrees(resultAngles); + } + private: mat4 m_model; // us -> world vec3 m_origin; diff --git a/include/fggl/phys/bullet/motion.hpp b/include/fggl/phys/bullet/motion.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e38e6ec60d35e4bfdee4199586d0188e985aa55f --- /dev/null +++ b/include/fggl/phys/bullet/motion.hpp @@ -0,0 +1,63 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 11/06/22. +// + +#ifndef FGGL_PHYS_BULLET_MOTION_HPP +#define FGGL_PHYS_BULLET_MOTION_HPP + +#include "fggl/phys/bullet/types.hpp" + +namespace fggl::phys::bullet { + + class FgglMotionState : public btMotionState { + public: + FgglMotionState(fggl::ecs3::World* world, fggl::ecs3::entity_t entity) : m_world(world), m_entity(entity) { + } + virtual ~FgglMotionState() = default; + + void getWorldTransform(btTransform& worldTrans) const override { + const auto* transform = m_world->get<fggl::math::Transform>(m_entity); + worldTrans.setFromOpenGLMatrix( glm::value_ptr(transform->model()) ); + } + + void setWorldTransform(const btTransform& worldTrans) override { + auto* transform = m_world->get<fggl::math::Transform>(m_entity); + + // set position + auto btOrigin = worldTrans.getOrigin(); + transform->origin( {btOrigin.x(), btOrigin.y(), btOrigin.z()} ); + + // set rotation + math::vec3 angles; + worldTrans.getRotation().getEulerZYX(angles.x, angles.y, angles.z); + transform->euler(angles); + } + + private: + fggl::ecs3::World* m_world; + fggl::ecs3::entity_t m_entity; + }; + +} // namespace fggl::phys::bullet + +#endif //FGGL_PHYS_BULLET_INTEGRATIONS_HPP diff --git a/include/fggl/phys/bullet/types.hpp b/include/fggl/phys/bullet/types.hpp index 04d8385edc93c41c441b6c0a58fc5434e6d011ad..8197759c2dd2eb278985974f432b691c7e580de3 100644 --- a/include/fggl/phys/bullet/types.hpp +++ b/include/fggl/phys/bullet/types.hpp @@ -48,6 +48,13 @@ namespace fggl::phys::bullet { constexpr static const char* name = "phys::bullet::body"; btMotionState* motion; btRigidBody* body; + + inline void release() { + delete motion; + delete body; + motion = nullptr; + body = nullptr; + } }; /** @@ -62,13 +69,20 @@ namespace fggl::phys::bullet { ~BulletPhysicsEngine() override; void step() override; - void onEntityDeath(ecs::entity_t entity); + + + std::vector<ContactPoint> scanCollisions(ecs3::entity_t entity) override; + ecs3::entity_t raycast(math::vec3 from, math::vec3 to) override; + std::vector<ecs3::entity_t> raycastAll(math::vec3 from, math::vec3 to) override; + std::vector<ecs3::entity_t> sweep(PhyShape& shape, math::Transform& from, math::Transform& to) override; + private: ecs3::World* m_ecs; BulletConfiguration m_config; btDiscreteDynamicsWorld* m_world; std::unique_ptr<debug::BulletDebugDrawList> m_debug; + ContactCache m_contactCache; /** * Check for ECS components which aren't in the physics world and add them. @@ -78,12 +92,17 @@ namespace fggl::phys::bullet { /** * Sync the bullet world state back to the ECS. */ - void syncToECS(); + void forceSyncToECS(); /** * Deal with physics collisions */ - void dealWithCollisions(); + void pollCollisions(); + + // bullet callback functions + void onCollisionCreate( ContactPoint point ); + void onCollisionProcess( ContactPoint point ); + void onCollisionDestroyed( ContactPoint point ); }; } // namespace fggl::phys::bullet diff --git a/include/fggl/phys/types.hpp b/include/fggl/phys/types.hpp index c3b68de411c6975473b06ee8dc47533cbd7ead2b..6204d62742f45655865016874c7ceef57d5eda08 100644 --- a/include/fggl/phys/types.hpp +++ b/include/fggl/phys/types.hpp @@ -76,6 +76,27 @@ namespace fggl::phys { math::vec3 force = math::VEC3_ZERO; }; + struct ContactPoint { + ecs3::entity_t entityA; + ecs3::entity_t entityB; + math::vec3 localA; + math::vec3 localB; + math::vec3 normal; + float distance; + }; + + struct ContactCache { + std::vector<ContactPoint> created; + std::vector<ContactPoint> modified; + std::vector<ContactPoint> removed; + + void clear() { + created.clear(); + modified.clear(); + removed.clear(); + } + }; + class PhysicsEngine { public: PhysicsEngine() = default; @@ -87,6 +108,13 @@ namespace fggl::phys { PhysicsEngine& operator=(PhysicsEngine&) = delete; PhysicsEngine& operator=(PhysicsEngine&&) = delete; + // query methods (first cut - unstable APIs) + virtual std::vector<ContactPoint> scanCollisions(ecs3::entity_t entity) = 0; + virtual ecs3::entity_t raycast(math::vec3 from, math::vec3 to) = 0; + virtual std::vector<ecs3::entity_t> raycastAll(math::vec3 from, math::vec3 to) = 0; + virtual std::vector<ecs3::entity_t> sweep(PhyShape& shape, math::Transform& from, math::Transform& to) = 0; + + // update virtual void step() = 0; };