Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gamedev/fggl
  • onuralpsezer/fggl
2 results
Show changes
Showing
with 680 additions and 280 deletions
/*
* This file is part of FGGL.
*
* FGGL 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.
*
* FGGL 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 FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 19/09/22.
//
#ifndef FGGL_PLATFORM_FALLBACK_FILE_HPP
#define FGGL_PLATFORM_FALLBACK_FILE_HPP
#include <cstdio>
#include <cassert>
namespace fggl::platform {
class File {
public:
inline File(FILE* filePtr) : m_handle(filePtr) {
assert(filePtr != nullptr);
}
inline ~File() {
release();
}
template<typename T>
inline void write(const T* dataPtr) {
assert( m_handle != nullptr );
int status = fwrite(dataPtr, sizeof(T), 1, m_handle );
assert( status == 1);
}
template<typename T>
inline void writeArr(const T* dataPtr, std::size_t numElms) {
assert( m_handle != nullptr);
int status = fwrite(dataPtr, sizeof(T), numElms, m_handle );
assert( status == 1);
}
private:
std::FILE* m_handle;
inline void release() {
if ( m_handle != NULL) {
fclose(m_handle);
}
}
};
} // namespace fggl::platform
#endif //FGGL_PLATFORM_FALLBACK_FILE_HPP
......@@ -18,8 +18,8 @@
// see https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
//
#ifndef FGGL_PLATFORM_LINUX_PATHS_HPP
#define FGGL_PLATFORM_LINUX_PATHS_HPP
#ifndef FGGL_PLATFORM_FALLBACK_PATHS_HPP
#define FGGL_PLATFORM_FALLBACK_PATHS_HPP
#include "fggl/platform/paths.hpp"
......@@ -30,13 +30,44 @@
namespace fggl::platform {
constexpr const char* ENV_USER_CONFIG = "FGGL_CONFIG_HOME";
constexpr const char* ENV_USER_DATA = "FGGL_DATA_HOME";
constexpr const char* ENV_USER_CACHE = "FGGL_CACHE_HOME";
/**
* The environment variable used for containing user configurations.
*
* The directory mentioned by this environment variable should be read/writeable.
* The directory should be used for storing user-editable configuration options (eg. preferences).
*/
constexpr const char *ENV_USER_CONFIG = "FGGL_CONFIG_HOME";
// fallback user paths defined in the XDG spec
constexpr const char* DEFAULT_USER_CONFIG = "user_config";
constexpr const char* DEFAULT_USER_DATA = "user_data";
/**
* Fallback user configuration directory.
*
* Default user configuration directory if none is set by the environment variable.
*/
constexpr const char *DEFAULT_USER_CONFIG = "user_config";
/**
* The environment variable used for containing engine data.
*
* This directory is used for storing persistent user data and therefore should be read/writable.
* The directory should be used for storing user-modifiable state (eg, save files) or downloaded modifications.
*/
constexpr const char *ENV_USER_DATA = "FGGL_DATA_HOME";
/**
* Fallback user data directory.
*
* Default user data directory if none is set by the environment variable.
*/
constexpr const char *DEFAULT_USER_DATA = "user_data";
/**
* The environment variable used for containing semi-persistent user data.
*
* The application should be able to expect this directory to exist while the application is running, but otherwise
* cannot expect the data to be persistent. It MAY be persistent but the application should not rely on this.
* It should be used for resources which can be re-generated if needed, but can be useful if already present.
*/
constexpr const char *ENV_USER_CACHE = "FGGL_CACHE_HOME";
struct EnginePaths {
std::filesystem::path userConfig;
......@@ -44,13 +75,22 @@ namespace fggl::platform {
std::filesystem::path userCache;
};
EnginePaths calc_engine_paths(const char* base);
/**
* Fallback implementation of calc engine paths.
*
* For operating systems we don't know about, this simply uses the environment variables defined above and some
* sane defaults to construct the paths used to locate our data and user configuration.
*
* @param base an application-unique string used to construct the paths.
* @return the generated paths, for use with the rest of the engine.
*/
EnginePaths calc_engine_paths(const char *base);
// search routines for finding data and configuration files
std::filesystem::path locate_data(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_config(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_cache(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_data(const EnginePaths &paths, const std::filesystem::path &relPath);
std::filesystem::path locate_config(const EnginePaths &paths, const std::filesystem::path &relPath);
std::filesystem::path locate_cache(const EnginePaths &paths, const std::filesystem::path &relPath);
}
#endif //FGGL_PLATFORM_LINUX_PATHS_HPP
#endif //FGGL_PLATFORM_FALLBACK_PATHS_HPP
......@@ -30,21 +30,21 @@
namespace fggl::platform {
constexpr const char* ENV_USER_CONFIG = "XDG_CONFIG_HOME";
constexpr const char* ENV_USER_DATA = "XDG_DATA_HOME";
constexpr const char* ENV_USER_CACHE = "XDG_CACHE_HOME";
constexpr const char *ENV_USER_CONFIG = "XDG_CONFIG_HOME";
constexpr const char *ENV_USER_DATA = "XDG_DATA_HOME";
constexpr const char *ENV_USER_CACHE = "XDG_CACHE_HOME";
constexpr const char* ENV_DATA_DIRS = "XDG_DATA_DIRS";
constexpr const char* ENV_CONFIG_DIRS = "XDG_CONFIG_DIRS";
constexpr const char *ENV_DATA_DIRS = "XDG_DATA_DIRS";
constexpr const char *ENV_CONFIG_DIRS = "XDG_CONFIG_DIRS";
// fallback user paths defined in the XDG spec
constexpr const char* DEFAULT_USER_CONFIG = "~/.config";
constexpr const char* DEFAULT_USER_DATA = "~/.local/share";
constexpr const char* DEFAULT_USER_CACHE = "~/.cache";
constexpr const char *DEFAULT_USER_CONFIG = "~/.config";
constexpr const char *DEFAULT_USER_DATA = "~/.local/share";
constexpr const char *DEFAULT_USER_CACHE = "~/.cache";
// fallback search paths defined in the XDG spec
constexpr const std::array<const char*, 2> DEFAULT_DATA_DIRS = {"/usr/local/share/", "/usr/share/"};
constexpr const std::array<const char*, 1> DEFAULT_CONFIG_DIRS = {"/etc/xdg"};
constexpr const std::array<const char *, 2> DEFAULT_DATA_DIRS = {"/usr/local/share/", "/usr/share/"};
constexpr const std::array<const char *, 1> DEFAULT_CONFIG_DIRS = {"/etc/xdg"};
struct EnginePaths {
std::filesystem::path userConfig;
......@@ -54,12 +54,12 @@ namespace fggl::platform {
std::vector<std::filesystem::path> configDirs;
};
EnginePaths calc_engine_paths(const char* base);
EnginePaths calc_engine_paths(const char *base);
// search routines for finding data and configuration files
std::filesystem::path locate_data(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_config(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_cache(const EnginePaths& paths, const std::filesystem::path& relPath);
std::filesystem::path locate_data(const EnginePaths &paths, const std::filesystem::path &relPath);
std::filesystem::path locate_config(const EnginePaths &paths, const std::filesystem::path &relPath);
std::filesystem::path locate_cache(const EnginePaths &paths, const std::filesystem::path &relPath);
}
......
......@@ -23,8 +23,10 @@
#include <vector>
#ifdef __linux__
#define FGGL_PLATFORM_PATHS linux
#include "fggl/platform/linux/paths.hpp"
#else
#define FGGL_PLATFORM_PATHS fallback
#include "fggl/platform/fallback/paths.hpp"
#endif
......
......@@ -20,37 +20,68 @@
#define FGGL_SCENES_GAME_HPP
#include "fggl/app.hpp"
#include "fggl/entity/entity.hpp"
#include "fggl/phys/types.hpp"
namespace fggl::scenes {
class GameBase : public fggl::AppState {
public:
explicit GameBase(fggl::App &app);
void update(float dt) override;
void render(fggl::gfx::Graphics &gfx) override = 0;
protected:
inline auto input() -> input::Input & {
return *m_input;
}
inline void returnToMenu() {
m_owner.change_state(m_previous);
}
private:
input::Input *m_input;
std::string m_previous = "menu";
};
class Game : public fggl::AppState {
public:
explicit Game(fggl::App& app);
explicit Game(fggl::App &app);
void activate() override;
void deactivate() override;
void update() override;
void render(fggl::gfx::Graphics& gfx) override;
void update(float dt) override;
void render(fggl::gfx::Graphics &gfx) override;
protected:
inline auto world() -> ecs3::World& {
inline auto world() -> entity::EntityManager & {
return *m_world;
}
inline auto phys() -> phys::PhysicsEngine& {
protected:
bool hasPhys() const {
return m_phys != nullptr;
}
inline auto phys() -> phys::PhysicsEngine & {
assert(m_phys != nullptr);
return *m_phys;
}
inline auto input() -> input::Input& {
inline auto input() -> input::Input & {
return *m_input;
}
bool m_debug;
private:
input::Input* m_input;
std::unique_ptr<ecs3::World> m_world;
input::Input *m_input;
std::unique_ptr<entity::EntityManager> m_world;
std::unique_ptr<phys::PhysicsEngine> m_phys;
std::string m_previous = "menu";
};
......
......@@ -26,29 +26,29 @@
namespace fggl::scenes {
using callback = std::function<void(void)>;
using Callback = std::function<void(void)>;
class BasicMenu : public AppState {
public:
explicit BasicMenu(App &owner);
void update() override;
void update(float dt) override;
void render(gfx::Graphics &paint) override;
void activate() override;
void deactivate() override;
void add(const std::string &label, callback cb);
void add(const std::string &label, const Callback& /*cb*/);
private:
input::Input* m_inputs;
std::map<const std::string, callback> m_items;
input::Input *m_inputs;
std::map<const std::string, Callback> m_items;
// menu state
std::string m_active;
math::vec2 m_cursorPos;
gui::Container m_canvas;
gui::Widget* m_hover;
gui::Widget *m_hover;
};
} // namepace fggl::scenes
......
/*
* This file is part of FGGL.
*
* FGGL 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.
*
* FGGL 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 FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 15/10/22.
//
#ifndef FGGL_SCRIPT_ENGINE_H
#define FGGL_SCRIPT_ENGINE_H
#include "fggl/modules/module.hpp"
namespace fggl::script {
class ScriptEngine {
public:
virtual ~ScriptEngine() = default;
// TODO use protected virtual pattern
// scene callbacks
virtual void onActivate() = 0;
virtual void onDeactivate() = 0;
virtual void onUpdate() = 0;
// trigger callback
virtual void onEvent(const std::string& name) = 0;
// run code in engine
virtual bool run(const char* script) = 0;
virtual bool load(const char* filename) = 0;
virtual void setGlobal(const char* name, void* ptr) = 0;
};
class ScriptProvider {
public:
constexpr static const modules::ServiceName service = modules::make_service("fggl::script::service");
virtual ScriptEngine* create() = 0;
};
}
#endif //FGGL_SCRIPT_ENGINE_H
......@@ -21,7 +21,10 @@
#define FGGL_UTIL_GUID_HPP
#include <cstdint>
#include <cassert>
#include "fggl/util/safety.hpp"
#include "fggl/debug/logging.hpp"
namespace fggl::util {
......@@ -38,7 +41,7 @@ namespace fggl::util {
* @param str the string to hash.
* @return the hashed value
*/
constexpr uint32_t hash_fnv1a_32(const char* str) {
constexpr uint32_t hash_fnv1a_32(const char *str) {
assert(str != nullptr);
uint32_t hash = FNV_OFFSET_BASIS_32;
for (int i = 0; str[i] != '\0'; i++) {
......@@ -54,7 +57,7 @@ namespace fggl::util {
* @param str the string to be hashed
* @return the hashed value
*/
constexpr uint64_t hash_fnv1a_64(const char* str) {
constexpr uint64_t hash_fnv1a_64(const char *str) {
assert(str != nullptr);
uint64_t hash = FNV_OFFSET_BASIS_64;
for (int i = 0; str[i] != '\0'; i++) {
......@@ -64,17 +67,69 @@ namespace fggl::util {
return hash;
}
constexpr GUID make_guid(const char* str) {
return GUID::make(hash_fnv1a_64(str));
template<unsigned N>
struct FString {
char c[N];
};
// https://stackoverflow.com/a/65440575
template<unsigned ...Len>
constexpr auto cat(const char (&...strings)[Len]) {
constexpr unsigned N = (... + Len) - sizeof...(Len);
FString<N + 1> result = {};
result.c[N] = '\0';
char* dst = result.c;
for (const char* src : {strings...}) {
for (; *src != '\0'; src++, dst++) {
*dst = *src;
}
}
return result;
}
// debug-only functions
GUID internString(const char* str);
std::string guidToString(GUID guid);
#ifndef NDEBUG
GUID intern_string(const char *str);
std::string guid_to_string(GUID guid);
#endif
constexpr GUID make_guid(const char *str) {
return GUID(hash_fnv1a_64(str));
}
inline GUID make_guid_rt(const char* str) {
#ifndef NDEBUG
return intern_string(str);
#else
return make_guid(str);
#endif
}
inline GUID make_guid_rt(const std::string &str) {
return make_guid_rt(str.c_str());
}
} // namespace fggl::util
fggl::util::GUID operator "" _fid(const char* str);
fggl::util::GUID operator "" _fid(const char* str, std::size_t);
// formatter
template<> struct fmt::formatter<fggl::util::GUID> {
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
template <typename FormatContext>
auto format(const fggl::util::GUID& guid, FormatContext& ctx) const -> decltype(ctx.out()) {
#ifndef NDEBUG
return fmt::format_to(ctx.out(), "GUID[{}]", guid_to_string(guid));
#else
return fmt::format_to(ctx.out(), "GUID[{}]", guid.get());
#endif
}
};
fggl::util::GUID operator "" _fid(const char *str);
fggl::util::GUID operator "" _fid(const char *str, std::size_t);
#endif //FGGL_UTIL_GUID_HPP
......@@ -26,7 +26,7 @@ namespace fggl::util {
/**
* A type-safe opaque handle.
*
* Lots of low-level libaries we use pass around handles as some primative type. It's fairly easy to accidentally
* Lots of low-level libraries we use pass around handles as some primative type. It's fairly easy to accidentally
* mix these up. This wrapper's job is to make sure that mixing up handle types is impossible (and results in
* compiler errors).
*
......@@ -39,20 +39,53 @@ namespace fggl::util {
T m_value;
public:
constexpr OpaqueName(T value) : m_value(value) {}
explicit constexpr OpaqueName(T value) : m_value(value) {}
constexpr explicit operator std::string_view() const { return m_value; }
constexpr OpaqueName() : m_value() {}
constexpr T get() const {
return m_value;
}
/**
* Check for equality of two handles.
*
* Two values are considered the same of the values contained inside them are considered equal, and both
* types share the same tagging interface.
*
* @param other the value being compared against.
* @return true iff the contained values are equal
*/
bool operator==(const OpaqueName<T, Tag> &other) const {
return m_value == other.m_value;
}
/**
* Check for equality of two handles.
*
* Two values are considered the same of the values contained inside them are considered equal, and both
* types share the same tagging interface.
*
* @param other the value being compared against.
* @return true iff the contained values are not equal
*/
bool operator!=(const OpaqueName<T, Tag> &other) const {
return m_value != other.m_value;
}
bool operator<(const OpaqueName<T, Tag> &other) const {
return m_value < other.m_value;
}
std::strong_ordering operator<=>(const OpaqueName<T, Tag> &other) const noexcept {
return m_value <=> other.m_value;
}
/**
* Generate a new tagged instance of a handle.
*/
constexpr static OpaqueName<T, Tag> make(T value) {
return OpaqueName(value);
return OpaqueName<T, Tag>(value);
}
};
......
......@@ -20,6 +20,8 @@
#include <memory>
#include <unordered_map>
#include "fggl/debug/logging.hpp"
namespace fggl::util {
template<typename S, typename I>
......@@ -49,7 +51,7 @@ namespace fggl::util {
}
bool change(const Identifer &name) {
if ( m_states.find(name) == m_states.end() ) {
if (m_states.find(name) == m_states.end()) {
debug::error("attempted to change to non-existent state {}, ignoring you.", name);
return false;
}
......@@ -61,7 +63,7 @@ namespace fggl::util {
}
StateType &active() const {
assertm(m_states.find(m_active) != m_states.end(), "active state does not exist!");
ASSERT_MSG(m_states.find(m_active) != m_states.end(), "active state does not exist!");
return *(m_states.at(m_active).get());
}
......
# Integration Modules
This adds support for 3rd party libraries to the engine. These can be included in projects
to expose modules which can then be used in your applications.
## Integrations Provided
The following integration modules are povided in this folder. For more information please see the readme for that module.
| Integration | Module Name | Description |
|-------------|-----------------------|----------------------------------------------------|
| Bullet | `fggl::phys::Bullet3` | Provides Bullet3 integration into the game engine. |
| Lua | `fggl::script::Lua` | Provides LUA scripting support for the engine |
Modules that cannot be included for legal reasons will be packaged independently of this repository.
\ No newline at end of file
......@@ -5,34 +5,39 @@ if ( NOT Bullet_FOUND )
else()
message( STATUS "Bullet is poorly packaged, you might need to disable support for it" )
add_library(fgglbt STATIC)
if ( MSVC )
# see https://github.com/microsoft/vcpkg/issues/7877
target_link_libraries(fggl PUBLIC LinearMath Bullet3Common BulletDynamics BulletSoftBody BulletCollision BulletInverseDynamics)
target_link_libraries(fgglbt PUBLIC LinearMath Bullet3Common BulletDynamics BulletSoftBody BulletCollision BulletInverseDynamics)
else()
# FIXME: this shouldn't be necessary, for modern cmake, linking the libraries should be enough
target_compile_definitions(fggl PUBLIC ${BULLET_DEFINITIONS})
target_compile_definitions(fgglbt PUBLIC ${BULLET_DEFINITIONS})
if ( BULLET_INCLUDE_DIRS STREQUAL "include/bullet" )
message( STATUS "Bullet include path is relative - hard-coding" )
# FIXME possible debian packing bug: path is relative in BulletConfig.cmake
# FIXME debian packaging bug: BulletConfig.cmake lists BulletInverseDynamics, but that's packaged in bullet-extras
target_include_directories(fggl PUBLIC ${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS})
target_include_directories(fgglbt PUBLIC ${BULLET_ROOT_DIR}/${BULLET_INCLUDE_DIRS})
else()
target_include_directories(fggl PUBLIC ${BULLET_INCLUDE_DIRS})
target_include_directories(fgglbt PUBLIC ${BULLET_INCLUDE_DIRS})
endif()
target_link_libraries(fggl PUBLIC ${BULLET_LIBRARIES})
target_link_libraries(fgglbt PUBLIC ${BULLET_LIBRARIES})
endif()
target_include_directories( fggl
target_link_libraries( fgglbt PUBLIC fggl )
target_include_directories( fgglbt
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>
$<INSTALL_INTERFACE:include>
)
# bullet cpp files
target_sources( fggl
target_sources( fgglbt
PRIVATE
src/module.cpp
src/simulation.cpp
src/phys_draw.cpp
src/service.cpp
)
endif()
......@@ -35,50 +35,8 @@
#define FGGL_MODULE_BULLET fggl::phys::Bullet3
#include "fggl/ecs3/module/module.hpp"
#include "fggl/phys/types.hpp"
#include "fggl/phys/bullet/types.hpp"
namespace fggl::phys::bullet {
/**
* Bullet integration module.
*
* This provides the ability for FGGL to use bullet as a physics backened. Bullet is mature, stable physics
* engine which makes it suitable for most use cases. For use with FGGL there is a reasonable amount of copying
* back and forth to keep the two states in sync.
*/
struct BulletModule : ecs3::Module {
public:
BulletModule() = default;
[[nodiscard]]
std::string name() const override {
return "phys::Bullet3";
}
void onLoad(ecs3::ModuleManager & /*manager*/, ecs3::TypeRegistry &types) override {
// dependencies
types.make<phys::CollisionCallbacks>();
types.make<phys::CollisionCache>();
types.make<phys::RigidBody>();
types.make<phys::Dynamics>();
// my types
types.make<phys::bullet::BulletBody>();
}
};
} // namespace fggl::phys::bullet
namespace fggl::phys {
// allows using fggl::phys::Bullet3 as the module name
using Bullet3 = bullet::BulletModule;
} // namespace fggl::phys
#include "fggl/phys/bullet/module.hpp"
#endif //FGGL_PHYS_BULLET_BULLET_HPP
/*
* This file is part of FGGL.
*
* FGGL 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.
*
* FGGL 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 FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 31/07/22.
//
#ifndef FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
#define FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
#include "fggl/modules/module.hpp"
#include "fggl/entity/module.hpp"
#include "fggl/phys/types.hpp"
#include "fggl/phys/service.hpp"
#include "fggl/phys/bullet/types.hpp"
namespace fggl::phys::bullet {
constexpr util::GUID CONFIG_PHYS_BODY = util::make_guid("phys::body");
struct Bullet {
constexpr static const char* name = "fggl::phys::bullet";
constexpr static const std::array<modules::ModuleService, 1> provides = {
phys::PhysicsProvider::service
};
constexpr static const std::array<modules::ModuleService, 1> depends = {
entity::EntityFactory::service
};
static bool factory(modules::ModuleService name, modules::Services& serviceManager);
};
} // namespace fggl::phys::bullet
namespace fggl::phys {
using Bullet3 = bullet::Bullet;
} // namespace fggl::phys
#endif //FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_MODULE_HPP
......@@ -34,36 +34,42 @@
#define FGGL_PHYS_BULLET_MOTION_HPP
#include "types.hpp"
#include "fggl/debug/logging.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) {
FgglMotionState(entity::EntityManager* world, entity::EntityID entity) : m_world(world), m_entity(entity) {
}
virtual ~FgglMotionState() = default;
~FgglMotionState() override = 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()) );
const auto* transform = m_world->tryGet<fggl::math::Transform>(m_entity);
if ( transform == nullptr ) {
debug::error("BT: attempted to get transform of entity without transform component.");
return;
}
worldTrans.setFromOpenGLMatrix(glm::value_ptr(transform->model()));
}
void setWorldTransform(const btTransform& worldTrans) override {
auto* transform = m_world->get<fggl::math::Transform>(m_entity);
auto& transform = m_world->get<fggl::math::Transform>(m_entity);
// set position
auto btOrigin = worldTrans.getOrigin();
transform->origin( {btOrigin.x(), btOrigin.y(), btOrigin.z()} );
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);
transform.euler(angles);
}
private:
fggl::ecs3::World* m_world;
fggl::ecs3::entity_t m_entity;
entity::EntityManager* m_world;
entity::EntityID m_entity;
};
} // namespace fggl::phys::bullet
......
......@@ -12,28 +12,22 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS3_ECS_HPP
#define FGGL_ECS3_ECS_HPP
//
// Created by webpigeon on 01/08/22.
//
#include "fggl/ecs3/module/module.hpp"
#include <fggl/ecs3/prototype/world.hpp>
#include <fggl/math/types.hpp>
#ifndef FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H
#define FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H
namespace fggl::ecs3 {
#include "fggl/phys/service.hpp"
using World = prototype::World;
class ecsTypes : public Module {
namespace fggl::phys::bullet {
class BulletProvider : public phys::PhysicsProvider {
public:
inline std::string name() const override {
return "ecs::core";
}
inline void onLoad(ModuleManager& manager, TypeRegistry& types) override {
}
PhysicsEngine* create(entity::EntityManager* entityManager, entity::EntityFactory* factory) override;
};
}
#endif
\ No newline at end of file
#endif //FGGL_INTEGRATIONS_BULLET_PHYS_BULLET_BULLETSERVICE_H
......@@ -33,7 +33,6 @@
#ifndef FGGL_PHYS_BULLET_TYPES_HPP
#define FGGL_PHYS_BULLET_TYPES_HPP
#include "fggl/ecs3/ecs.hpp"
#include "fggl/phys/types.hpp"
#include "phys_draw.hpp"
......@@ -73,34 +72,52 @@ namespace fggl::phys::bullet {
*/
class BulletPhysicsEngine : public PhysicsEngine {
public:
explicit BulletPhysicsEngine(ecs3::World* world);
explicit BulletPhysicsEngine(entity::EntityManager* world);
~BulletPhysicsEngine() override;
// copy is not allowed
BulletPhysicsEngine(const BulletPhysicsEngine&) = delete;
BulletPhysicsEngine& operator=(const BulletPhysicsEngine&) = delete;
// move is not allowed
BulletPhysicsEngine(BulletPhysicsEngine&&) = delete;
BulletPhysicsEngine& operator=(BulletPhysicsEngine&&) = delete;
void step() override;
void onEntityDeath(ecs::entity_t entity);
void onEntityDeath(entity::EntityID 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;
std::vector<ContactPoint> scanCollisions(entity::EntityID entity) override;
entity::EntityID raycast(math::vec3 from, math::vec3 to) override;
std::vector<entity::EntityID> raycastAll(math::vec3 from, math::vec3 to) override;
std::vector<entity::EntityID> sweep(PhyShape& shape, math::Transform& from, math::Transform& to) override;
/**
* Add a rigid body to the physics world.
*
* The body should not currently be in the simulation. Physics position will be synced with the motion
* state prior to it being added to the simulation. This method should not need to be called manually, it
* is called when the entity is decoded.
*
* @param entity the entity the BulletBody is attached to.
* @param body the pre-configured bullet body.
*/
void addBody(entity::EntityID entity, BulletBody& body);
bool debugDraw = false;
void setDebugDraw(bool enabled) override;
private:
ecs3::World* m_ecs;
entity::EntityManager* 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.
*/
void checkForPhys();
/**
* Sync the bullet world state back to the ECS.
* Force position and rotation in the ECS to match those from the physics world.
*
* As FGGL uses motion states, this should not be required. As it iterates all entities in a scene this is
* also a very expensive operation.
*/
void forceSyncToECS();
void syncTransformToECS();
/**
* Deal with physics collisions
......
/*
* This file is part of FGGL.
*
* FGGL 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.
*
* FGGL 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 FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 31/07/22.
//
#include "fggl/phys/bullet/module.hpp"
#include "fggl/phys/bullet/service.hpp"
namespace fggl::phys::bullet {
bool Bullet::factory(modules::ModuleService service, modules::Services& services) {
if ( service == phys::PhysicsProvider::service ) {
services.bind<phys::PhysicsProvider, BulletProvider>();
}
return false;
}
} // namespace fggl::phys::bullet
\ No newline at end of file
/*
* This file is part of FGGL.
*
* FGGL 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.
*
* FGGL 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 FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
// Created by webpigeon on 01/08/22.
//
#include "fggl/phys/bullet/service.hpp"
#include "fggl/phys/bullet/types.hpp"
#include "fggl/phys/bullet/motion.hpp"
namespace fggl::phys::bullet {
constexpr float UNIT_EXTENT = 0.5F;
btCollisionShape* create_bullet_shape(const YAML::Node& node) {
auto type = node["type"].as<std::string>("MISSING");
if ( type == "sphere" ) {
auto radius = node["radius"].as<float>(UNIT_EXTENT);
return new btSphereShape(radius);
} else if ( type == "box" ) {
auto extents = node["extents"].as<math::vec3>(math::vec3(UNIT_EXTENT));
return new btBoxShape(btVector3(extents.x, extents.y, extents.z));
} else {
debug::warning("unknown shape type: {}, assuming unit sphere", type);
return new btSphereShape(0.5F);
}
}
void setup_body(BodyType type, btRigidBody& body) {
if ( type != BodyType::STATIC ) {
body.setRestitution(0.5F);
body.setRollingFriction(0.0F);
// kinematic bodies cannot sleep
if (type == BodyType::KINEMATIC) {
auto flags = (body.getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT);
body.setCollisionFlags(flags);
body.setActivationState(DISABLE_DEACTIVATION);
}
}
}
void add_bt_body(const entity::ComponentSpec& spec, entity::EntityManager& manager, const entity::EntityID& id) {
auto& body = manager.add<BulletBody>(id);
body.motion = new FgglMotionState(&manager, id);
auto type = spec.get<BodyType>("type", BodyType::DYNAMIC);
auto mass = 0.0F;
// non-static objects can have mass
if ( type != BodyType::STATIC ) {
mass = spec.get<float>("mass", 1.0F);
}
// calculate inertia
btCollisionShape* shape = create_bullet_shape(spec.config["shape"]);
btVector3 inertia(0, 0, 0);
if ( type != BodyType::STATIC ) {
shape->calculateLocalInertia(mass, inertia);
}
// construction information for the simulation
btRigidBody::btRigidBodyConstructionInfo constructionInfo {
mass,
body.motion,
shape,
inertia
};
body.body = new btRigidBody(constructionInfo);
setup_body(type, *body.body);
// add the body to the simulation
if ( type == BodyType::DYNAMIC ) {
manager.add<phys::Dynamics>(id);
}
}
PhysicsEngine* BulletProvider::create(entity::EntityManager* entityManager, entity::EntityFactory* factory) {
auto* engine = new BulletPhysicsEngine(entityManager);
// FIXME: this won't work across multiple scenes - reactive approach might be better...
factory->bind(util::make_guid("phys::Body"), add_bt_body, [engine](auto& manager, const auto& entity) {
debug::trace("adding entity {} to physics scene...", (uint64_t)entity);
auto& physComp = manager.template get<BulletBody>(entity);
engine->addBody(entity, physComp);
});
return engine;
}
} // namespace fggl::phys::bullet
\ No newline at end of file
......@@ -35,7 +35,7 @@ namespace fggl::phys::bullet {
return {fgglVec.x, fgglVec.y, fgglVec.z};
}
BulletPhysicsEngine::BulletPhysicsEngine(ecs3::World* world) : m_ecs(world), m_config(), m_world(nullptr) {
BulletPhysicsEngine::BulletPhysicsEngine(entity::EntityManager* world) : m_ecs(world), m_config(), m_world(nullptr), m_debug(nullptr) {
m_config.broadphase = new btDbvtBroadphase();
m_config.collisionConfiguration = new btDefaultCollisionConfiguration();
m_config.dispatcher = new btCollisionDispatcher(m_config.collisionConfiguration);
......@@ -47,153 +47,101 @@ namespace fggl::phys::bullet {
m_config.solver,
m_config.collisionConfiguration);
m_debug = std::make_unique<debug::BulletDebugDrawList>();
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);} );
//m_ecs->addDeathListener( [this](auto entity) { this->onEntityDeath(entity);} );
}
void BulletPhysicsEngine::step() {
checkForPhys();
// this is now handled on creation
//checkForPhys();
auto dynamicEnts = m_ecs->findMatching<phys::Dynamics>();
auto dynamicEnts = m_ecs->find<phys::Dynamics, phys::bullet::BulletBody>();
for (auto& ent : dynamicEnts) {
auto* dynamicComp = m_ecs->get<phys::Dynamics>(ent);
auto* bulletProxy = m_ecs->get<phys::bullet::BulletBody>(ent);
if ( bulletProxy == nullptr ) {
std::cerr << "entity has dynamics but isn't a physics object!" << std::endl;
continue;
}
auto& dynamicComp = m_ecs->get<phys::Dynamics>(ent);
auto& bulletProxy = m_ecs->get<phys::bullet::BulletBody>(ent);
const auto forceVec = dynamicComp->force;
const auto forceVec = dynamicComp.force;
if ( glm::length(forceVec) != 0.F ) {
bulletProxy->body->applyCentralForce({forceVec.x, forceVec.y, forceVec.z});
bulletProxy->body->activate();
dynamicComp->force = math::VEC3_ZERO;
bulletProxy.body->applyCentralForce({forceVec.x, forceVec.y, forceVec.z});
bulletProxy.body->activate();
dynamicComp.force = math::VEC3_ZERO;
}
}
m_world->stepSimulation(60.0f);
m_world->stepSimulation(60.0F);
//syncToECS();
pollCollisions();
if ( debugDraw ) {
// if debug draw is enabled, trigger a draw call
if ( m_debug != nullptr ) {
m_world->debugDrawWorld();
}
}
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);
}
void BulletPhysicsEngine::addBody(entity::EntityID /*entity*/, BulletBody &body) {
// ensure static objects are placed correctly by setting their transforms to the correct value
btTransform transform;
body.body->getMotionState()->getWorldTransform(transform);
body.body->setWorldTransform(transform);
switch (fgglBody->shape->type) {
case phys::ShapeType::BOX: {
auto extents = ((phys::Box*)fgglBody->shape)->extents;
return new btBoxShape({extents.x, extents.y, extents.z});
}
case phys::ShapeType::SPHERE: {
float radius = ((phys::Sphere*)fgglBody->shape)->radius;
return new btSphereShape(radius);
}
default:
return new btSphereShape(0.5f);
}
m_world->addRigidBody(body.body);
}
void BulletPhysicsEngine::checkForPhys() {
// FIXME without reactive-based approaches this is very slow
auto entsWantPhys = m_ecs->findMatching<phys::RigidBody>();
for (auto ent : entsWantPhys) {
auto* btComp = m_ecs->tryGet<BulletBody>(ent);
if ( btComp != nullptr ) {
// 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);
// collisions
btCollisionShape* colShape = shape_to_bullet(fgBody);
btVector3 localInt(0, 0, 0);
if ( !fgBody->isStatic() ) {
colShape->calculateLocalInertia(fgBody->mass, localInt);
}
// setup the construction information
btRigidBody::btRigidBodyConstructionInfo bodyCI(
fgBody->mass,
btComp->motion,
colShape,
localInt
);
btComp->body = new btRigidBody(bodyCI);
btComp->body->setUserIndex( static_cast<int>(ent) );
if (!fgBody->isStatic()) {
btComp->body->setRestitution(0.5f);
btComp->body->setRollingFriction(0.0f);
}
if ( fgBody->type == BodyType::KINEMATIC ) {
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 );
void BulletPhysicsEngine::setDebugDraw(bool active) {
if ( active ) {
m_debug = std::make_unique<debug::BulletDebugDrawList>();
m_world->setDebugDrawer(m_debug.get());
m_debug->setDebugMode(btIDebugDraw::DBG_DrawWireframe);
} else {
m_debug = nullptr;
}
}
void BulletPhysicsEngine::forceSyncToECS() {
const auto physEnts = m_ecs->findMatching<BulletBody>();
void BulletPhysicsEngine::syncTransformToECS() {
const auto physEnts = m_ecs->find<math::Transform, BulletBody>();
for (const auto& ent : physEnts) {
auto* transform = m_ecs->get<math::Transform>(ent);
auto* physBody = m_ecs->get<BulletBody>(ent);
auto& physBody = m_ecs->get<BulletBody>(ent);
// don't bother syncing things with motion states, it's not necessary
if ( physBody.motion != nullptr ) {
continue;
}
if ( physBody->body->isKinematicObject() ) {
auto& transform = m_ecs->get<math::Transform>(ent);
if ( physBody.body->isKinematicObject() ) {
continue;
}
btTransform bTransform;
physBody->motion->getWorldTransform(bTransform);
physBody.motion->getWorldTransform(bTransform);
// set calculate and set ecs position
auto& btPos = bTransform.getOrigin();
math::vec3 pos{btPos.x(), btPos.y(), btPos.z()};
transform->origin( pos );
transform.origin( pos );
// set the rotation
const auto& btRot = bTransform.getRotation();
math::vec3 rot;
btRot.getEulerZYX(rot.x, rot.y, rot.z);
transform->euler(rot);
transform.euler(rot);
}
}
BulletPhysicsEngine::~BulletPhysicsEngine() {
// clean up the rigid bodies
auto entRB = m_ecs->findMatching<BulletBody>();
for (auto& ent : entRB) {
auto* bulletBody = m_ecs->get<BulletBody>(ent);
m_world->removeCollisionObject(bulletBody->body);
auto entRB = m_ecs->find<BulletBody>();
for (const auto& ent : entRB) {
auto& bulletBody = m_ecs->get<BulletBody>(ent);
if ( bulletBody.body != nullptr ) {
m_world->removeCollisionObject(bulletBody.body);
}
// release resources and delete
bulletBody->release();
m_ecs->remove<BulletBody>(ent);
bulletBody.release();
}
// delete the world
......@@ -206,7 +154,7 @@ namespace fggl::phys::bullet {
delete m_config.collisionConfiguration;
}
static void handleCollisionCallbacks(ecs3::World* world, ecs3::entity_t owner, ecs3::entity_t other) {
static void handleCollisionCallbacks(entity::EntityManager* world, entity::EntityID owner, entity::EntityID other) {
if ( !world->has<CollisionCallbacks>(owner) ) {
return;
}
......@@ -230,11 +178,11 @@ namespace fggl::phys::bullet {
void BulletPhysicsEngine::pollCollisions() {
// flush collision caches
auto caches = m_ecs->findMatching<CollisionCache>();
for( auto& ent : caches) {
auto* cache = m_ecs->get<CollisionCache>(ent);
std::swap(cache->collisions, cache->lastFrame);
cache->collisions.clear();
auto caches = m_ecs->find<CollisionCache>();
for( const auto& ent : caches) {
auto& cache = m_ecs->get<CollisionCache>(ent);
std::swap(cache.collisions, cache.lastFrame);
cache.collisions.clear();
}
// deal with the number of manifolds
......@@ -245,15 +193,6 @@ namespace fggl::phys::bullet {
const auto* body0 = contactManifold->getBody0();
const auto* body1 = contactManifold->getBody1();
// FIXME rigid body didn't die according to plan!
if ( body0->getUserIndex() == ecs::NULL_ENTITY || body1->getUserIndex() == ecs::NULL_ENTITY) {
continue;
}
if ( !m_ecs->alive( body0->getUserIndex()) || !m_ecs->alive(body1->getUserIndex()) ) {
continue;
}
int numContacts = contactManifold->getNumContacts();
for ( auto contactIdx = 0; contactIdx < numContacts; ++contactIdx ) {
auto& point = contactManifold->getContactPoint(contactIdx);
......@@ -264,14 +203,14 @@ namespace fggl::phys::bullet {
}
}
handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex());
handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex());
//handleCollisionCallbacks(m_ecs, body0->getUserIndex(), body1->getUserIndex());
//handleCollisionCallbacks(m_ecs, body1->getUserIndex(), body0->getUserIndex());
}
// 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);
caches = m_ecs->find<CollisionCache>(); // re-fetch, entities can (and probably do) die in handlers.
for( const auto& ent : caches) {
auto& cache = m_ecs->get<CollisionCache>(ent);
// if we don't have an exit callback, it's pointless checking the cache
auto* callbacks = m_ecs->tryGet<CollisionCallbacks>(ent);
......@@ -280,20 +219,20 @@ namespace fggl::phys::bullet {
}
// check if a collision has ended
for (const auto& other : cache->lastFrame) {
auto itr = cache->collisions.find(other);
if (itr == cache->collisions.end()) {
for (const auto& other : cache.lastFrame) {
auto itr = cache.collisions.find(other);
if (itr == cache.collisions.end()) {
callbacks->onExit( ent,other);
}
}
}
}
void BulletPhysicsEngine::onEntityDeath(ecs::entity_t entity) {
void BulletPhysicsEngine::onEntityDeath(entity::EntityID entity) {
auto* btPhysics = m_ecs->tryGet<BulletBody>(entity);
if ( btPhysics != nullptr) {
// decouple physics from entity
btPhysics->body->setUserIndex( ecs::NULL_ENTITY );
//btPhysics->body->setUserIndex( entity::INVALID );
m_world->removeRigidBody( btPhysics->body );
btPhysics->release();
}
......@@ -311,7 +250,7 @@ namespace fggl::phys::bullet {
m_contactCache.removed.push_back(point);
}
ecs3::entity_t BulletPhysicsEngine::raycast(math::vec3 from, math::vec3 to) {
entity::EntityID BulletPhysicsEngine::raycast(math::vec3 from, math::vec3 to) {
const auto btFrom = to_bullet(from);
const auto btTo = to_bullet(to);
......@@ -319,18 +258,19 @@ namespace fggl::phys::bullet {
m_world->rayTest(btFrom, btTo, rayTestResult);
if ( !rayTestResult.hasHit() ) {
return ecs3::NULL_ENTITY;
return entity::INVALID;
}
// tell the user what it hit
return rayTestResult.m_collisionObject->getUserIndex();
return entity::INVALID;
// return rayTestResult.m_collisionObject->getUserIndex();
}
std::vector<ContactPoint> BulletPhysicsEngine::scanCollisions(ecs3::entity_t entity) {
std::vector<ContactPoint> BulletPhysicsEngine::scanCollisions(entity::EntityID entity) {
return std::vector<ContactPoint>();
}
std::vector<ecs3::entity_t> BulletPhysicsEngine::raycastAll(math::vec3 fromPos, math::vec3 toPos) {
std::vector<entity::EntityID> BulletPhysicsEngine::raycastAll(math::vec3 fromPos, math::vec3 toPos) {
const auto btFrom = to_bullet(fromPos);
const auto btTo = to_bullet(toPos);
......@@ -345,21 +285,21 @@ namespace fggl::phys::bullet {
// hit processing
const auto hits = rayTestResult.m_collisionObjects.size();
std::vector<ecs3::entity_t> hitVec;
std::vector<entity::EntityID> hitVec;
hitVec.reserve(hits);
for ( auto i = 0; i < hits; ++i) {
ecs3::entity_t entity = rayTestResult.m_collisionObjects[i]->getUserIndex();
hitVec.push_back(entity);
auto entity = rayTestResult.m_collisionObjects[i]->getUserIndex();
//hitVec.push_back(entity);
}
return hitVec;
}
std::vector<ecs3::entity_t> BulletPhysicsEngine::sweep(PhyShape &shape,
std::vector<entity::EntityID> BulletPhysicsEngine::sweep(PhyShape &shape,
math::Transform &from,
math::Transform &to) {
return std::vector<ecs3::entity_t>();
return std::vector<entity::EntityID>();
}
} // namespace fggl::phys::bullet
\ No newline at end of file