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 1034 additions and 252 deletions
......@@ -5,12 +5,14 @@ file(GLOB_RECURSE HEADER_LIST CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/include/fgg
# Should be shared linkage for legal reasons (LGPL)
add_library(fggl ${HEADER_LIST})
target_link_libraries(fggl PUBLIC entt)
# we need to tell people using the library about our headers
target_include_directories(fggl
PUBLIC
PUBLIC
$<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
)
# users of this library need at least C++20
target_compile_features(fggl PUBLIC cxx_std_20)
......@@ -31,19 +33,22 @@ if (CLANG_TIDY_FOUND)
set(CMAKE_CXX_CLANG_TIDY clang-tidy -checks=*,-llvmlibc-*,-fuchsia-*,-cppcoreguidelines-*,-android-*,-llvm-*,-altera-*,-modernize-use-trailing-return-type)
endif ()
# the important stuff everything else uses
add_subdirectory(math)
add_subdirectory(util)
add_subdirectory(grid)
add_subdirectory(assets)
add_subdirectory(phys)
target_sources(${PROJECT_NAME}
PRIVATE
PRIVATE
app.cpp
data/model.cpp
data/procedural.cpp
data/heightmap.cpp
ecs/ecs.cpp
ecs3/module/module.cpp
ecs3/fast/Container.cpp
ecs3/prototype/loader.cpp
ecs3/prototype/world.cpp
data/module.cpp
scenes/menu.cpp
scenes/game.cpp
......@@ -51,29 +56,17 @@ target_sources(${PROJECT_NAME}
input/input.cpp
input/mouse.cpp
input/camera_input.cpp
gui/widget.cpp
gui/widgets.cpp
gui/containers.cpp
gui/fonts.cpp
)
add_subdirectory(math)
# GUI support
add_subdirectory(gui)
# yaml-cpp for configs and storage
find_package(yaml-cpp)
target_link_libraries(fggl PUBLIC yaml-cpp)
# model loading
find_package(assimp CONFIG)
if ( MSVC )
target_link_libraries(${PROJECT_NAME} PUBLIC assimp::assimp)
else()
target_link_libraries(${PROJECT_NAME} PUBLIC assimp)
endif()
find_package(Freetype)
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
add_subdirectory(data/assimp)
# Graphics backend
add_subdirectory(gfx)
......@@ -86,11 +79,12 @@ add_subdirectory(debug)
# platform integrations
add_subdirectory(platform)
add_subdirectory(entity)
##
# begin windows support
##
if(MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC "/ZI")
target_link_options(${PROJECT_NAME} PUBLIC "/INCREMENTAL")
endif()
if (MSVC)
target_compile_options(${PROJECT_NAME} PUBLIC "/ZI")
target_link_options(${PROJECT_NAME} PUBLIC "/INCREMENTAL")
endif ()
......@@ -13,80 +13,79 @@
*/
#include <cstdlib>
#include <memory>
#include <spdlog/spdlog.h>
#include <fggl/app.hpp>
#include <fggl/ecs3/types.hpp>
#include "fggl/ecs3/module/module.hpp"
#include <fggl/util/service.hpp>
namespace fggl {
App::App(modules::Manager* services, const Identifer& name) : App::App( services, name, name ) {
}
App::App(modules::Manager *services, const Identifer &name) : App::App(services, name, name) {
}
App::App(modules::Manager* services, const Identifer& name, const Identifer& folder ) :
m_running(true),
m_types(std::make_unique<ecs3::TypeRegistry>()),
m_modules(std::make_unique<ecs3::ModuleManager>(*m_types)),
App::App(modules::Manager *services, const Identifer & /*name*/, const Identifer & /*folder*/ ) :
m_running(true),
m_window(nullptr),
m_states(),
m_subsystems(services){}
m_states(),
m_subsystems(services) {}
int App::run(int argc, const char** argv) {
auto* windowing = m_subsystems->get<display::WindowService>();
auto App::run(int /*argc*/, const char **/*argv*/) -> int {
auto *windowing = m_subsystems->get<display::WindowService>();
{
// activate the first state
auto& state = m_states.active();
{
// activate the first state
auto &state = m_states.active();
m_expectedScene = m_states.activeID();
state.activate();
}
state.activate();
}
auto lastTime = glfwGetTime();
while (m_running) {
auto currTime = glfwGetTime();
auto delta = currTime - lastTime;
lastTime = currTime;
while ( m_running ) {
// trigger a state change if expected
if ( m_expectedScene != m_states.activeID() ) {
if (m_expectedScene != m_states.activeID()) {
auto result = m_states.change(m_expectedScene);
if ( !result ) {
if (!result) {
m_expectedScene = m_states.activeID();
}
}
// process window events
if ( windowing != nullptr) {
if (windowing != nullptr) {
windowing->pollEvents();
}
m_modules->onUpdate();
//m_modules->onUpdate();
auto& state = m_states.active();
state.update();
auto &state = m_states.active();
state.update((float)delta);
// window rendering to frame buffer
if ( m_window != nullptr ) {
m_modules->onFrameStart();
// window rendering to frame buffer
if (m_window != nullptr) {
// m_modules->onFrameStart();
m_window->frameStart();
// get draw instructions
auto& graphics = m_window->graphics();
state.render(graphics);
// get draw instructions
auto &graphics = m_window->graphics();
state.render(graphics);
m_window->frameEnd();
m_modules->onFrameEnd();
m_window->frameEnd();
// m_modules->onFrameEnd();
m_running = m_running && !m_window->wantClose();
}
}
}
}
{
// shutdown last state
auto& state = m_states.active();
state.deactivate();
}
{
// shutdown last state
auto &state = m_states.active();
state.deactivate();
}
return EXIT_SUCCESS;
}
return EXIT_SUCCESS;
}
} //namespace fggl
target_sources(fggl PRIVATE
module.cpp
types.cpp
packed/module.cpp
)
\ 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 20/08/22.
//
#include "fggl/assets/module.hpp"
namespace fggl::assets {
auto AssetFolders::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == Loader::service) {
auto *storage = services.get<data::Storage>();
auto *checkin = services.get<CheckinAdapted>();
services.create<Loader>(storage, checkin);
return true;
}
if (service == AssetManager::service) {
services.create<AssetManager>();
return true;
}
return false;
}
} // namespace fggl::assets
\ No newline at end of file
......@@ -12,39 +12,26 @@
* If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef FGGL_ECS3_UTILS_HPP
#define FGGL_ECS3_UTILS_HPP
//
// Created by webpigeon on 20/08/22.
//
#include <cstddef>
#include "fggl/assets/packed/module.hpp"
namespace fggl::utils {
namespace fggl::assets {
template<typename T>
std::size_t search(const T *data, const std::size_t size, const T &v) {
// empty list == not found
if (size == 0) {
return size;
auto PackedAssets::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == RawCheckin::service) {
services.create<RawCheckin>();
return true;
}
std::size_t left = 0;
std::size_t right = size - 1;
while (left <= right) {
std::size_t m = (left + right) / 2;
if (data[m] == v) {
return m;
} else if (v < data[m]) {
if (m == 0) {
return size;
}
right = m - 1;
} else {
left = m + 1;
}
if (service == CheckinAdapted::service) {
auto* storage = services.get<data::Storage>();
auto* rawCheckin = services.get<RawCheckin>();
services.create<CheckinAdapted>(storage, rawCheckin);
return true;
}
return size;
return false;
}
}
#endif
} // namespace fggl::assets
\ 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 19/11/22.
//
#include <stack>
#include <vector>
#include <memory>
#include <set>
#include <map>
#include <concepts>
#include "fggl/util/guid.hpp"
namespace fggl::assets {
using AssetName = util::OpaqueName<uint64_t, struct AssetRefStruct>;
/**
* The base class representing an asset.
*
* This can be combined with the templated version.
*/
class Asset {
public:
Asset(AssetName name) : m_name(name) {}
virtual ~Asset() = default;
inline void release() {
releaseImpl();
}
bool operator==(const Asset& asset) const {
return m_name == asset.m_name;
}
bool operator!=(const Asset& asset) const {
return m_name != asset.m_name;
}
private:
AssetName m_name;
virtual void releaseImpl() = 0;
};
/**
* Wrapper for types that cannot extend Asset themselves.
*
* @tparam T the asset to wrap
*/
template<typename T>
class AssetBox : public Asset {
public:
AssetBox(T* ptr) : m_ptr(ptr) {}
~AssetBox() override {
release();
}
T* ptr() {
return m_ptr;
}
private:
T* m_ptr;
void releaseImpl() override {
delete m_ptr;
m_ptr = nullptr;
}
};
/**
* Asset Library.
*
* The currently usable set of assets loaded into the engine.
*/
class AssetLibrary {
public:
void load(AssetName name);
void unload(AssetName name);
template<typename T>
requires std::derived_from<T, Asset>
void store(AssetName name, T* ptr) {
m_assets[name] = std::make_unique<T>(ptr);
}
template<typename T>
requires std::derived_from<T, Asset>
void get(AssetName name) const {
Asset* asset = m_assets.at(name).get();
return dynamic_cast<T>(asset);
}
inline Asset* get(AssetName name) const {
return m_assets.at( name ).get();
}
private:
std::map<AssetName, std::unique_ptr<Asset>> m_assets;
};
struct AssetRecord {
AssetName id;
std::vector<AssetName> dependencies;
bool hasDepends(AssetName name) const;
void addDepend(AssetName name);
};
class AssetGraph {
public:
AssetGraph(AssetLibrary* loader);
/**
*
*
* @param name
*/
void require(AssetName& name) {
if ( m_loaded.find(name) != m_loaded.end() ) {
return;
}
m_required.push(name);
}
/**
*
*
* @return
*/
[[nodiscard]]
inline bool isLoadComplete() const {
return m_required.empty();
}
/**
*
*
* @param name
* @return
*/
[[nodiscard]]
inline bool isLoaded(AssetName name) const {
return m_loaded.find(name) != m_loaded.end();
}
/**
*
* @param name
* @param description
*/
void addDependency(AssetName& name, AssetName& description) {
auto& assets = m_assets.at(name);
assets.addDepend(description);
}
/**
*
* @param name
* @param dependency
* @return
*/
[[nodiscard]]
bool hasDependency(AssetName& name , AssetName& dependency) const {
try {
auto &assets = m_assets.at(name);
return assets.hasDepends(dependency);
} catch ( std::out_of_range& ) {
return false;
}
}
/**
*
*/
void process() {
auto asset = m_required.top();
m_loader->load( asset );
m_required.pop();
}
/**
*
*/
void finishLoading() {
while ( !m_loaded.empty() ) {
process();
}
}
private:
std::stack<AssetName> m_required;
std::set<AssetName> m_loaded;
std::map<AssetName, AssetRecord> m_assets;
AssetLibrary* m_loader;
};
} // namespace fggl::assets
\ 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 20/11/22.
//
#include "fggl/assets/types.hpp"
namespace fggl::assets {
auto make_asset_id_rt(const std::string &pack, const std::string &path, const std::string &view) -> AssetID {
auto fullPath = pack + ":" + path;
if (!view.empty()) {
fullPath += "[" + view + "]";
}
#ifndef NDEBUG
util::intern_string(fullPath.c_str());
#endif
auto hash = util::hash_fnv1a_64(fullPath.c_str());
return AssetID::make(hash);
}
auto asset_from_user(const std::string &input, const std::string &pack) -> AssetID {
if (input.find(':') != std::string::npos ) {
// probably fully qualified
#ifndef NDEBUG
util::intern_string(input.c_str());
#endif
auto hash = util::hash_fnv1a_64(input.c_str());
return AssetID::make(hash);
}
// probably local
return make_asset_id_rt(pack, input);
}
}
\ No newline at end of file
target_sources(fggl
PRIVATE
types.cpp
)
types.cpp
)
add_subdirectory(openal)
\ No newline at end of file
add_subdirectory(openal)
add_subdirectory(fallback)
\ No newline at end of file
target_sources(fggl
PRIVATE
audio.cpp
)
/*
* 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 05/11/22.
//
#include "fggl/audio/null_audio.hpp"
namespace fggl::audio {
void NullAudioService::play(const std::string &, bool) {
}
void NullAudioService::play(const fggl::audio::AudioClipShort &, bool) {
}
auto NullAudio::factory(modules::ServiceName service, modules::Services &services) -> bool{
if (service == SERVICE_AUDIO_PLAYBACK) {
services.bind<audio::AudioService, audio::NullAudioService>();
return true;
}
return false;
}
} // namespace fggl::audio
\ No newline at end of file
find_package( OpenAL CONFIG )
if ( NOT OpenAL_FOUND )
find_package(OpenAL CONFIG)
if (NOT OpenAL_FOUND)
# ubuntu/debian openal-soft package doesn't seem to have a config file
# it's probably falling back to findOpenAL.cmake
message( STATUS "OpenAL-Soft config missing - falling back" )
find_package( OpenAL REQUIRED )
target_link_libraries( fggl PUBLIC ${OPENAL_LIBRARY} )
target_include_directories( fggl PUBLIC ${OPENAL_INCLUDE_DIR} )
else()
message(STATUS "OpenAL-Soft config missing - falling back")
find_package(OpenAL REQUIRED)
target_link_libraries(fggl PUBLIC ${OPENAL_LIBRARY})
target_include_directories(fggl PUBLIC ${OPENAL_INCLUDE_DIR})
else ()
# we're using target-based
message( STATUS "Using OpenAL-Soft config file" )
if ( TARGET OpenAL::OpenAL )
target_link_libraries( fggl PUBLIC OpenAL::OpenAL )
else()
target_link_libraries( fggl PUBLIC OpenAL )
endif()
message(STATUS "Using OpenAL-Soft config file")
if (TARGET OpenAL::OpenAL)
target_link_libraries(fggl PUBLIC OpenAL::OpenAL)
else ()
target_link_libraries(fggl PUBLIC OpenAL)
endif ()
endif()
endif ()
target_sources(fggl
PRIVATE
audio.cpp
module.cpp
)
......@@ -27,30 +27,98 @@
*/
#include "fggl/audio/openal/audio.hpp"
#include "fggl/assets/types.hpp"
#include "fggl/assets/manager.hpp"
#include "fggl/util/service.hpp"
#include "fggl/data/storage.hpp"
#include "../../stb/stb_vorbis.h"
namespace fggl::audio::openal {
auto load_vorbis(assets::Loader* /*loader*/, const assets::AssetID &guid, const assets::LoaderContext &data, void* userPtr) -> assets::AssetRefRaw {
auto *manager = static_cast<assets::AssetManager*>(userPtr);
auto filePath = data.assetPath;
// vorbis
auto* clip = new AudioClipShort();
clip->sampleCount = stb_vorbis_decode_filename( filePath.c_str(), &clip->channels, &clip->sampleRate, &clip->data);
debug::info("clip loaded: channels={}, sampleRate={}, sampleCount={}", clip->channels, clip->sampleRate, clip->sampleCount);
if ( clip->sampleCount == -1 ) {
return nullptr;
}
manager->set(guid, clip);
return nullptr;
}
auto load_vorbis_short(std::filesystem::path path, assets::MemoryBlock& /*block*/) -> bool {
// vorbis
auto* clip = new AudioClipShort();
clip->sampleCount = stb_vorbis_decode_filename( path.c_str(), &clip->channels, &clip->sampleRate, &clip->data);
debug::info("clip loaded: channels={}, sampleRate={}, sampleCount={}", clip->channels, clip->sampleRate, clip->sampleCount);
return clip->sampleCount != 1;
}
auto check_vorbis(const std::filesystem::path& path ) -> assets::AssetTypeID {
if ( path.extension() != ".ogg" ) {
return assets::INVALID_ASSET_TYPE;
}
auto* clip = new AudioClipShort();
clip->sampleCount = stb_vorbis_decode_filename( path.c_str(), &clip->channels, &clip->sampleRate, &clip->data);
if ( clip->sampleCount == -1 ) {
return assets::INVALID_ASSET_TYPE;
}
delete clip;
return ASSET_CLIP_SHORT;
}
void AudioSource::play(const AudioClipShort &clip, bool looping) {
check_error("pre play");
AudioType format = clip.channels == 1 ? AudioType::MONO_16 : AudioType::STEREO_16;
m_splat.setData(format, clip.data, clip.size(), clip.sampleRate);
check_error("saving to buffer");
play(m_splat, looping);
check_error("post play");
}
void AudioServiceOAL::play(const std::string &filename, bool looping) {
debug::info("beginning audio: {}", filename);
// load audio clip into temp storage
AudioClip clip;
bool result = m_storage->load(data::StorageType::Data, filename, &clip);
if ( !result ) {
std::cerr << "error: can't load audio data" << std::endl;
auto assetRef = assets::make_asset_id_rt("core", filename);
auto* clip = m_assets->get<AudioClipShort>(assetRef);
if ( clip == nullptr ) {
debug::warning("audio asset requested, but not loaded: {}", filename);
return;
}
play(clip, looping);
play(*clip, looping);
debug::info("played audio: {}", filename);
}
void AudioServiceOAL::play(AudioClip &clip, bool looping) {
// play the audio on the default (non-positioned) source
if ( m_defaultSource != nullptr ) {
m_defaultSource->play(clip, looping);
void AudioServiceOAL::play(const AudioClipShort &clip, bool looping) {
if ( m_defaultSource == nullptr ){
return;
}
// play the audio on the default (non-positioned) source
m_defaultSource->play(clip, looping);
}
void AudioServiceOAL::release() {
m_defaultSource = nullptr;
alcMakeContextCurrent(nullptr);
alcDestroyContext(m_context);
alcCloseDevice(m_device);
m_context = nullptr;
m_device = nullptr;
}
}
\ 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 05/11/22.
//
#include "fggl/audio/openal/module.hpp"
namespace fggl::audio {
auto OpenAL::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == SERVICE_AUDIO_PLAYBACK) {
auto* assets = services.get<assets::AssetManager>();
{
auto *assetLoader = services.get<assets::Loader>();
assetLoader->setFactory( ASSET_CLIP_SHORT, openal::load_vorbis, assets::LoadType::PATH);
}
{
auto *checkin = services.get<assets::CheckinAdapted>();
checkin->setLoader( RES_OGG_VORBIS, openal::load_vorbis_short, openal::check_vorbis );
}
services.bind<audio::AudioService, openal::AudioServiceOAL>(assets);
return true;
}
return false;
}
}
\ No newline at end of file
......@@ -15,22 +15,11 @@
#include "fggl/data/storage.hpp"
#include "fggl/audio/audio.hpp"
#include "../stb/stb_vorbis.h"
namespace fggl::audio {
} // namespace fggl::audio
namespace fggl::data {
template<>
bool fggl_deserialize<audio::AudioClip>(std::filesystem::path &data, audio::AudioClip* out) {
out->sampleCount = stb_vorbis_decode_filename(data.string().c_str(),
&out->channels,
&out->sampleRate,
&out->data);
return out->sampleCount != -1;
}
} // namespace fggl::data
find_package(assimp CONFIG)
if (MSVC)
target_link_libraries(${PROJECT_NAME} PUBLIC assimp::assimp)
else ()
target_link_libraries(${PROJECT_NAME} PUBLIC assimp)
endif ()
target_sources(fggl
PRIVATE
module.cpp
image.cpp
)
\ No newline at end of file
......@@ -13,7 +13,8 @@
*/
//
// Created by webpigeon on 23/10/2021.
// Created by webpigeon on 22/10/22.
//
#include <fggl/ecs3/prototype/world.hpp>
#define STB_IMAGE_IMPLEMENTATION
#include "../../stb/stb_image.h"
/*
* 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 18/10/22.
//
#include "fggl/data/assimp/module.hpp"
#include "fggl/debug/logging.hpp"
#include "fggl/assets/manager.hpp"
#include "fggl/mesh/mesh.hpp"
#include "../../stb/stb_image.h"
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <filesystem>
namespace fggl::data::models {
constexpr auto convert(aiVector3D& vec) -> math::vec3 {
return {vec.x, vec.y, vec.z};
}
constexpr auto convert2(aiVector2D& vec) -> math::vec2 {
return {vec.x, vec.y};
}
constexpr auto convert2(aiVector3D& vec) -> math::vec2 {
return {vec.x, vec.y};
}
constexpr auto convert(aiColor3D& col) -> math::vec3 {
return {col.r, col.g, col.b};
}
static void process_mesh(mesh::Mesh3D& mesh, aiMesh* assimpMesh, const aiScene* scene, const std::vector<assets::AssetID>& assets) {
assert( assimpMesh != nullptr );
assert( scene != nullptr );
// process vertex data
for ( auto idx = 0U; idx < assimpMesh->mNumVertices; ++idx ) {
mesh::Vertex3D vertex{
.position = convert(assimpMesh->mVertices[idx]),
.normal = convert(assimpMesh->mNormals[idx])
};
if ( assimpMesh->mTextureCoords[0] != nullptr ) {
vertex.texPos = convert2( assimpMesh->mTextureCoords[0][idx] );
}
mesh.data.push_back(vertex);
}
// process faces (indexes)
for ( auto idx = 0U; idx < assimpMesh->mNumFaces; ++idx ) {
auto face = assimpMesh->mFaces[idx];
assert( face.mNumIndices == 3);
for ( auto vid = 0U; vid < face.mNumIndices; ++vid) {
mesh.indices.push_back( face.mIndices[vid] );
}
}
// process material
if ( assimpMesh->mMaterialIndex >= 0) {
mesh.material = assets[assimpMesh->mMaterialIndex];
}
}
static void process_node(mesh::MultiMesh3D& mesh, aiNode* node, const aiScene* scene, const std::vector<assets::AssetID>& assets) {
for ( auto idx = 0U; idx < node->mNumMeshes; ++idx ) {
auto *assimpMesh = scene->mMeshes[ node->mMeshes[idx] ];
// process assimp submesh
mesh::Mesh3D meshData;
process_mesh(meshData, assimpMesh, scene, assets);
mesh.meshes.push_back(meshData);
}
for ( auto idx = 0U; idx < node->mNumChildren; ++idx) {
process_node(mesh, node->mChildren[idx], scene, assets);
}
}
struct AssetStuff {
assets::Loader* loader;
assets::AssetManager* manager;
inline void checkLoaded(assets::AssetID asset) {
if (!manager->has(asset)) {
debug::info("triggered JIT upload for {}, use the chain loader", asset);
loader->load(asset);
}
}
inline auto isLoaded(assets::AssetID asset) const -> bool {
return manager->has(asset);
}
inline void set(assets::AssetID asset, auto* assetPtr) {
assert(assetPtr != nullptr);
manager->set(asset, assetPtr);
}
};
static auto process_texture( const aiTextureType type, const aiMaterial* assimpMat, AssetStuff& stuff, const assets::LoaderContext& config) -> std::vector<assets::AssetID> {
std::vector<assets::AssetID> matRefs;
matRefs.reserve( assimpMat->GetTextureCount(type) );
// iterate through things
for ( auto i = 0U ; i < assimpMat->GetTextureCount(type); ++i ) {
aiString texName;
assimpMat->GetTexture( type, i, &texName );
const auto texID = config.makeRef(texName.C_Str());
// trigger asset loading
stuff.checkLoaded( texID );
// assets
matRefs.push_back( texID );
}
return matRefs;
}
static auto process_mat_colour(const aiMaterial* mat, const char* name, int a1, int a2) -> math::vec3 {
aiColor3D col{0.0F, 0.0F, 0.0F};
mat->Get( name, a1, a2, col );
debug::info("read colour: {}, {}, {}, {}", name, col.r, col.g, col.b);
return convert(col);
}
static void process_material(const assets::AssetID& guid, aiMaterial* assimpMat, AssetStuff stuff, const assets::LoaderContext& config) {
auto* material = new mesh::Material();
debug::info("processing: {}", guid);
// for each material, calculate what it's name would be then request it
material->diffuseTextures = process_texture( aiTextureType_DIFFUSE, assimpMat, stuff, config );
material->normalTextures = process_texture( aiTextureType_NORMALS, assimpMat, stuff, config );
material->specularTextures = process_texture( aiTextureType_SPECULAR, assimpMat, stuff, config );
material->ambient = process_mat_colour( assimpMat, AI_MATKEY_COLOR_AMBIENT );
material->diffuse = process_mat_colour( assimpMat, AI_MATKEY_COLOR_DIFFUSE );
material->specular = process_mat_colour( assimpMat, AI_MATKEY_COLOR_SPECULAR );
stuff.set( guid, material );
}
auto load_assimp_model(assets::Loader* loader, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr) -> assets::AssetRefRaw {
// auto *filePath = std::get<assets::AssetPath *>(data);
auto filePath = data.assetPath;
AssetStuff stuff {
.loader = loader,
.manager = (assets::AssetManager*)userPtr
};
if ( stuff.isLoaded(guid) ) {
// asset in DB, what do you want me to do?
return nullptr;
}
// assimp stuff
Assimp::Importer importer;
// import the scene from disk
const aiScene *scene = importer.ReadFile(filePath.c_str(), aiProcessPreset_TargetRealtime_Fast | aiProcess_FlipUVs);
if ( scene == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0 || scene->mRootNode == nullptr ) {
debug::warning("unable to load required model asset");
return nullptr;
}
debug::debug("Processing assimp mesh, {} meshes, {} materials, {} textures", scene->mNumMeshes, scene->mNumMaterials, scene->mNumTextures);
// calculate the mapping from material => asset mappings
auto parentRel = std::filesystem::relative( filePath.parent_path(), filePath.parent_path().parent_path() );
std::vector<assets::AssetID> matAssets;
for ( auto idx = 0U; idx < scene->mNumMaterials; ++idx ) {
aiString matName = scene->mMaterials[idx]->GetName();
auto matRel = parentRel / matName.C_Str();
matAssets.push_back( assets::make_asset_id_rt( data.pack, matRel ) );
}
// now we can try importing the mesh data
auto* packedMesh = new mesh::MultiMesh3D();
process_node( *packedMesh, scene->mRootNode, scene, matAssets);
// now we try importing the materials
for (auto idx = 0U; idx < scene->mNumMaterials; ++idx ) {
process_material( matAssets[idx], scene->mMaterials[idx], stuff, data );
}
// FIXME asset loading system needs rework, this is bonkers.
stuff.set(guid, packedMesh);
return nullptr;
}
auto load_assimp_texture(assets::Loader* /*loader*/, const assets::AssetID& guid, const assets::LoaderContext& data, void* userPtr) -> assets::AssetRefRaw {
auto* manager = (assets::AssetManager*)userPtr;
if ( manager->has(guid) ) {
// already loaded.
return nullptr;
}
auto filePath = data.assetPath;
stbi_set_flip_vertically_on_load(1);
//load the texture data into memory
auto* image = new data::Texture2D();
image->data = stbi_load(filePath.c_str(), &image->size.x, &image->size.y, &image->channels, 3);
if ( image->data == nullptr ) {
debug::warning("error reading texture: {}", stbi_failure_reason());
return nullptr;
} else {
debug::info("image reports it loaded correctly, adding {} to database", guid);
manager->set(guid, image );
}
return nullptr;
}
auto load_tex_stb(const std::filesystem::path& filePath, assets::MemoryBlock& block) -> bool {
stbi_set_flip_vertically_on_load(1);
//load the texture data into memory
auto* image = new data::Texture2D();
image->data = stbi_load(filePath.c_str(), &image->size.x, &image->size.y, &image->channels, 3);
if ( image->data == nullptr ) {
debug::warning("error reading texture: {}", stbi_failure_reason());
delete image;
return false;
} else {
// TODO pass metadata to loader in a sensible way
block.size = image->channels * image->size.x * image->size.y;
block.data = (std::byte*)image->data;
delete image;
return true;
}
}
auto is_tex_stb(const std::filesystem::path& filePath) -> assets::AssetTypeID {
// detect jpgs
if ( filePath.extension() == ".jpg" || filePath.extension() == ".jpeg" ) {
return TEXTURE_RGBA;
}
// detect png
if ( filePath.extension() == ".png" ) {
return TEXTURE_RGBA;
}
return assets::INVALID_ASSET_TYPE;
}
auto is_model_assimp(const std::filesystem::path& filePath) -> assets::AssetTypeID {
auto ext = filePath.extension().string();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
if ( ext == ".obj" || ext == ".fbx" ) {
return MODEL_MULTI3D;
}
return assets::INVALID_ASSET_TYPE;
}
auto extract_requirements(const std::string& packName, const std::filesystem::path& packRoot, assets::ResourceRecord& rr) -> bool {
Assimp::Importer importer;
const aiScene *scene = importer.ReadFile( rr.m_path, aiProcess_Triangulate | aiProcess_FlipUVs);
if ( !scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode ) {
debug::warning("unable to load required model asset");
return false;
}
// we want to find out about dependencies
auto baseDir = std::filesystem::relative( rr.m_path.parent_path(), packRoot );
for ( auto idx = 0U; idx < scene->mNumMaterials; ++idx) {
const auto *mat = scene->mMaterials[idx];
std::array matTypes { aiTextureType_DIFFUSE, aiTextureType_NORMALS, aiTextureType_SPECULAR };
for ( auto& matType : matTypes ) {
const auto diffCount = mat->GetTextureCount(matType);
for (auto idx2 = 0U; idx2 < diffCount; ++idx2) {
aiString texName;
mat->GetTexture(matType, idx2, &texName);
auto assetPath = baseDir / texName.C_Str();
auto assetRequired = assets::make_asset_id_rt(packName, assetPath);
rr.m_requires.push_back(assetRequired);
}
}
}
return true;
}
auto AssimpModule::factory(modules::ServiceName service, modules::Services &serviceManager) -> bool {
if ( service == MODEL_PROVIDER ) {
auto* assetLoader = serviceManager.get<assets::Loader>();
assetLoader->setFactory( MODEL_MULTI3D, load_assimp_model, assets::LoadType::PATH );
assetLoader->setFactory( TEXTURE_RGBA, load_assimp_texture, assets::LoadType::PATH );
// new loading system
auto* checkin = serviceManager.get<assets::CheckinAdapted>();
checkin->setLoader( MIME_JPG, load_tex_stb, is_tex_stb );
checkin->setLoader( MIME_PNG, load_tex_stb, is_tex_stb );
checkin->setLoader( MIME_OBJ, assets::NEEDS_CHECKIN, is_model_assimp );
checkin->setLoader( MIME_FBX, assets::NEEDS_CHECKIN, is_model_assimp );
checkin->setProcessor( MIME_OBJ, extract_requirements);
checkin->setProcessor( MIME_FBX, extract_requirements );
return false;
}
return false;
}
} // namespace fggl::data
......@@ -23,103 +23,104 @@
namespace fggl::data {
void gridVertexNormals(data::Vertex *locations) {
const int sizeX = data::heightMaxX;
const int sizeY = data::heightMaxZ;
const int gridOffset = sizeX * sizeY;
// calculate normals for each triangle
math::vec3* triNormals = new math::vec3[sizeX * sizeY * 2];
for (int i = 0; i < sizeX - 1; i++) {
for (int j = 0; j < sizeY - 1; j++) {
// calculate vertex
const auto &a = locations[i * sizeY + j].posititon;
const auto &b = locations[(i + 1) * sizeY + j].posititon;
const auto &c = locations[i * sizeY + (j + 1)].posititon;
const auto &d = locations[(i + 1) * sizeY + (j + 1)].posititon;
const auto normalA = glm::cross(b - a, a - d);
const auto normalB = glm::cross(d - c, c - b);
// store the normals
int idx1 = idx(i, j, sizeY);
int idx2 = idx1 + gridOffset;
triNormals[idx1] = glm::normalize(normalA);
triNormals[idx2] = glm::normalize(normalB);
}
}
// calculate normals for each vertex
for (int i = 0; i < sizeX; i++) {
for (int j = 0; j < sizeY; j++) {
const auto firstRow = (i == 0);
const auto firstCol = (j == 0);
const auto lastRow = (i == sizeX - 1);
const auto lastCol = (i == sizeY - 1);
auto finalNormal = glm::vec3(0.0f, 0.0f, 0.0f);
if (!firstRow && !firstCol) {
finalNormal += triNormals[idx(i - 1, j - 1, sizeY)];
}
if (!lastRow && !lastCol) {
finalNormal += triNormals[idx(i, j, sizeY)];
}
if (!firstRow && lastCol) {
finalNormal += triNormals[idx(i - 1, j, sizeY)];
finalNormal += triNormals[idx(i - 1, j, sizeY) + gridOffset];
}
if (!lastRow && !firstCol) {
finalNormal += triNormals[idx(i, j - 1, sizeY)];
finalNormal += triNormals[idx(i, j - 1, sizeY) + gridOffset];
}
locations[idx(i, j, sizeY)].normal = glm::normalize(finalNormal) * -1.0f; //FIXME the normals seem wrong.
}
}
delete[] triNormals;
}
void generateHeightMesh(const data::HeightMap *heights, data::Mesh &mesh) {
// step 1: convert height data into vertex locations
const int numElms = data::heightMaxX * data::heightMaxZ;
void gridVertexNormals(data::Vertex *locations) {
const int sizeX = data::heightMaxX;
const int sizeY = data::heightMaxZ;
const int gridOffset = sizeX * sizeY;
// calculate normals for each triangle
auto *triNormals = new math::vec3[sizeX * sizeY * 2];
for (int i = 0; i < sizeX - 1; i++) {
for (int j = 0; j < sizeY - 1; j++) {
// calculate vertex
const auto &a = locations[i * sizeY + j].posititon;
const auto &b = locations[(i + 1) * sizeY + j].posititon;
const auto &c = locations[i * sizeY + (j + 1)].posititon;
const auto &d = locations[(i + 1) * sizeY + (j + 1)].posititon;
const auto normalA = glm::cross(b - a, a - d);
const auto normalB = glm::cross(d - c, c - b);
// store the normals
int idx1 = idx(i, j, sizeY);
int idx2 = idx1 + gridOffset;
triNormals[idx1] = glm::normalize(normalA);
triNormals[idx2] = glm::normalize(normalB);
}
}
// calculate normals for each vertex
for (int i = 0; i < sizeX; i++) {
for (int j = 0; j < sizeY; j++) {
const auto firstRow = (i == 0);
const auto firstCol = (j == 0);
const auto lastRow = (i == sizeX - 1);
const auto lastCol = (i == sizeY - 1);
auto finalNormal = glm::vec3(0.0F, 0.0F, 0.0F);
if (!firstRow && !firstCol) {
finalNormal += triNormals[idx(i - 1, j - 1, sizeY)];
}
if (!lastRow && !lastCol) {
finalNormal += triNormals[idx(i, j, sizeY)];
}
if (!firstRow && lastCol) {
finalNormal += triNormals[idx(i - 1, j, sizeY)];
finalNormal += triNormals[idx(i - 1, j, sizeY) + gridOffset];
}
if (!lastRow && !firstCol) {
finalNormal += triNormals[idx(i, j - 1, sizeY)];
finalNormal += triNormals[idx(i, j - 1, sizeY) + gridOffset];
}
locations[idx(i, j, sizeY)].normal =
glm::normalize(finalNormal) * -1.0F; //FIXME the normals seem wrong.
}
}
delete[] triNormals;
}
void generateHeightMesh(const data::HeightMap &heights, data::Mesh &mesh) {
// step 1: convert height data into vertex locations
const int numElms = data::heightMaxX * data::heightMaxZ;
std::array<data::Vertex, numElms> locations{};
// iterate the
for (std::size_t x = 0; x < data::heightMaxX; x++) {
for (std::size_t z = 0; z < data::heightMaxZ; z++) {
float level = heights->getValue(x, z);
auto xPos = float(x);
auto zPos = float(z);
std::size_t idx1 = idx(x, z, data::heightMaxZ);
locations[idx1].colour = fggl::math::vec3(1.0f, 1.0f, 1.0f);
locations[idx1].posititon = math::vec3(-0.5f + xPos, level, -0.5f - zPos);
}
}
gridVertexNormals(locations.data());
mesh.restartVertex = data::heightMaxZ * data::heightMaxX;
// populate mesh
for (auto i = 0; i < numElms; i++) {
mesh.pushVertex(locations[i]);
}
for (std::size_t x = 0; x < data::heightMaxX - 1; x++) {
for (std::size_t z = 0; z < data::heightMaxZ; z++) {
for (int k=0; k < 2; k++) {
auto idx = (x+k) * data::heightMaxZ + z;
mesh.pushIndex(idx);
}
}
mesh.pushIndex(mesh.restartVertex);
}
}
for (std::size_t x = 0; x < data::heightMaxX; x++) {
for (std::size_t z = 0; z < data::heightMaxZ; z++) {
float level = heights.getValue(x, z);
auto xPos = float(x);
auto zPos = float(z);
std::size_t idx1 = idx(x, z, data::heightMaxZ);
locations[idx1].colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
locations[idx1].posititon = math::vec3(-0.5F + xPos, level, -0.5F - zPos);
}
}
gridVertexNormals(locations.data());
mesh.restartVertex = data::heightMaxZ * data::heightMaxX;
// populate mesh
for (auto i = 0; i < numElms; i++) {
mesh.pushVertex(locations[i]);
}
for (std::size_t x = 0; x < data::heightMaxX - 1; x++) {
for (std::size_t z = 0; z < data::heightMaxZ; z++) {
for (int k = 0; k < 2; k++) {
auto idx = (x + k) * data::heightMaxZ + z;
mesh.pushIndex(idx);
}
}
mesh.pushIndex(mesh.restartVertex);
}
}
}
......@@ -24,19 +24,19 @@ Mesh::Mesh() : restartVertex(Mesh::INVALID_IDX), m_verts(0), m_index(0) {
}
void Mesh::pushIndex(unsigned int idx) {
assert( idx < m_verts.size() || idx == this->restartVertex );
assert(idx < m_verts.size() || idx == this->restartVertex);
m_index.push_back(idx);
}
Mesh::IndexType Mesh::pushVertex(Vertex vert) {
auto Mesh::pushVertex(Vertex vert) -> Mesh::IndexType {
auto idx = m_verts.size();
m_verts.push_back( vert );
m_verts.push_back(vert);
return idx;
}
Mesh::IndexType Mesh::indexOf(Vertex term) {
auto Mesh::indexOf(Vertex term) -> Mesh::IndexType {
auto itr = std::find(m_verts.begin(), m_verts.end(), term);
if ( itr == m_verts.end() ){
if (itr == m_verts.end()) {
return INVALID_IDX;
}
return itr - m_verts.begin();
......@@ -47,24 +47,24 @@ void Mesh::removeDups() {
std::map<Vertex, unsigned int> mapping;
// new data
std::vector< Vertex > newVerts;
std::vector< unsigned int > newIndexes;
newIndexes.reserve( m_index.size() ); // newIndex will be same size as oldIndex
std::vector<Vertex> newVerts;
std::vector<unsigned int> newIndexes;
newIndexes.reserve(m_index.size()); // newIndex will be same size as oldIndex
unsigned int nextID = 0;
for ( auto& idx : m_index ) {
auto& vertex = m_verts[idx];
for (auto &idx : m_index) {
auto &vertex = m_verts[idx];
auto newID = nextID;
// if the vertex is known, use the existing ID, else allocate a new ID
try {
newID = mapping.at( vertex );
} catch ( std::out_of_range& e ){
mapping[ vertex ] = newID;
newVerts.push_back( vertex );
newID = mapping.at(vertex);
} catch (std::out_of_range &e) {
mapping[vertex] = newID;
newVerts.push_back(vertex);
++nextID;
}
newIndexes.push_back( newID );
newIndexes.push_back(newID);
}
// assign the replacement lists
......