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 1450 additions and 487 deletions
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 ()
# 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 ()
endif ()
target_sources(fggl
PRIVATE
audio.cpp
module.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/>.
*/
/*
* 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/>.
*/
#include "fggl/audio/openal/audio.hpp"
#include "fggl/assets/types.hpp"
#include "fggl/assets/manager.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
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);
debug::info("played audio: {}", filename);
}
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
/*
* 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/>.
*/
#include "fggl/data/storage.hpp"
#include "fggl/audio/audio.hpp"
namespace fggl::audio {
} // namespace fggl::audio
namespace fggl::data {
} // 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
/*
* 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 22/10/22.
//
#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
/*
* 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/2021.
//
#include <fggl/data/model.hpp>
#include <fggl/data/heightmap.h>
#include <fggl/data/heightmap.hpp>
// adapted from https://www.mbsoftworks.sk/tutorials/opengl4/016-heightmap-pt1-random-terrain/
namespace fggl::data {
void gridVertexNormals(data::Vertex *locations) {
int sizeX = data::heightMaxX;
int sizeY = data::heightMaxZ;
const int gridOffset = sizeX * sizeY;
// calculate normals for each triangle
math::vec3 triNormals[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.
}
}
}
void generateHeightMesh(const data::HeightMap *heights, data::Mesh &mesh) {
// step 1: convert height data into vertex locations
data::Vertex locations[data::heightMaxZ * data::heightMaxZ];
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);
mesh.restartVertex = data::heightMaxZ * data::heightMaxX;
// populate mesh
for (auto & location : locations) {
mesh.pushVertex(location);
}
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++) {
int idx = (x+k) * data::heightMaxZ + z;
mesh.pushIndex(idx);
}
}
mesh.pushIndex(mesh.restartVertex);
}
}
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);
}
}
}
//
// Created by webpigeon on 20/11/2021.
//
#ifndef FGGL_HEIGHTMAP_H
#define FGGL_HEIGHTMAP_H
#include <cstdint>
namespace fggl::data {
constexpr std::size_t heightMaxX = 255;
constexpr std::size_t heightMaxZ = 255;
constexpr float heightSeaLevel = 0.0f;
struct HeightMap {
constexpr static const char name[] = "Heightmap";
float heightValues[heightMaxX * heightMaxZ];
void clear() {
for (float & heightValue : heightValues){
heightValue = heightSeaLevel;
}
}
[[nodiscard]]
inline float getValue(std::size_t x, std::size_t z) const {
return heightValues[x * heightMaxZ + z];
}
inline void setValue(std::size_t x, std::size_t z, float value) {
heightValues[x * heightMaxZ + z] = value;
}
};
inline int idx(int x, int z, int zMax) {
return x * zMax + z;
}
void generateHeightMesh(const data::HeightMap* heights, data::Mesh &mesh);
}
#endif //FGGL_HEIGHTMAP_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/>.
*/
#include <fggl/data/model.hpp>
#include <map>
#include <stdexcept>
#include <algorithm>
using namespace fggl::data;
Mesh::Mesh() : m_verts(0), m_index(0) {
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);
}
unsigned int Mesh::pushVertex(Vertex vert) {
unsigned int idx = m_verts.size();
m_verts.push_back( vert );
auto Mesh::pushVertex(Vertex vert) -> Mesh::IndexType {
auto idx = m_verts.size();
m_verts.push_back(vert);
return idx;
}
int Mesh::indexOf(Vertex vert) {
int i = 0;
for ( auto& vert_v : m_verts ) {
if ( vert.posititon == vert_v.posititon ) {
return i;
}
++i;
auto Mesh::indexOf(Vertex term) -> Mesh::IndexType {
auto itr = std::find(m_verts.begin(), m_verts.end(), term);
if (itr == m_verts.end()) {
return INVALID_IDX;
}
return -1;
return itr - m_verts.begin();
}
void Mesh::removeDups() {
// lookups to make detecting duplicates easier
std::map<Vertex, unsigned int> mapping;
unsigned int nextID = 0;
// 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
for ( auto& idx : m_index ) {
auto& vertex = m_verts[idx];
unsigned int nextID = 0;
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 );
}
// FIXME must be faster way to do this...
m_verts.clear();
m_index.clear();
for ( auto& vert : newVerts ) {
m_verts.push_back( vert );
}
for ( auto& idx : newIndexes ) {
m_index.push_back( idx );
newIndexes.push_back(newID);
}
// ensure we're not wasting space
m_verts.shrink_to_fit();
m_index.shrink_to_fit();
// assign the replacement lists
m_verts = newVerts;
m_index = newIndexes;
}
/*
* 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/data/module.hpp"
namespace fggl::data {
auto LocalStorage::factory(modules::ServiceName service, modules::Services &data) -> bool {
if (service == SERVICE_STORAGE) {
// FIXME: no easy way to set the application name
auto pathConfig = fggl::platform::calc_engine_paths("fggl-demo");
data.create<Storage>(pathConfig);
return true;
}
return false;
}
} // namespace fggl::data
\ No newline at end of file
#include "procedural.hpp"
#include "model.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/>.
*/
#include <cstddef>
#include <fggl/data/procedural.hpp>
#include <fggl/data/model.hpp>
#include <glm/ext/matrix_transform.hpp>
#include <glm/gtc/quaternion.hpp>
#include <iostream>
#include <array>
#include <glm/geometric.hpp>
#include "fggl/mesh/mesh.hpp"
using namespace fggl::data;
// from https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
static glm::vec3 calcSurfaceNormal(glm::vec3 a, glm::vec3 b, glm::vec3 c) {
glm::vec3 u = b - a;
glm::vec3 v = c - a;
return glm::normalize( glm::cross( u, v ) );
static auto calcSurfaceNormal(glm::vec3 vert1, glm::vec3 vert2, glm::vec3 vert3) -> glm::vec3 {
const glm::vec3 edge1 = vert2 - vert1;
const glm::vec3 edge2 = vert3 - vert1;
return glm::normalize(glm::cross(edge1, edge2));
}
static void computeNormals( fggl::data::Mesh& mesh, const int* idx, const int* colIdx, int nPoints) {
static void computeNormalsDirect(fggl::mesh::Mesh3D &mesh, const fggl::data::Mesh::IndexType *colIdx, int nPoints) {
// we're assuming all the normals are zero...
// FIXME this will touch any vertex that appears in idx more than once mutliple times...
for ( int i=0; i<nPoints; i++ ) {
auto& vertex = mesh.vertex( colIdx[ idx[i] ] );
for (int i = 0; i < nPoints; i++) {
auto &vertex = mesh.data[colIdx[i]];
vertex.normal = glm::vec3(0.0F);
}
// each vertex normal should be the sum of the triangles it's part of.
for (int i=0; i<nPoints; i += 3) {
auto& v1 = mesh.vertex( colIdx[ idx[i] ] );
auto& v2 = mesh.vertex( colIdx[ idx[i + 1] ] );
auto& v3 = mesh.vertex( colIdx[ idx[i + 2] ] );
// We're assuming each vertex appears only once (because we're not indexed)
for (int i = 0; i < nPoints; i += 3) {
auto &v1 = mesh.data[colIdx[i]];
auto &v2 = mesh.data[colIdx[i + 1]];
auto &v3 = mesh.data[colIdx[i + 2]];
const glm::vec3 normal = calcSurfaceNormal( v1.posititon, v2.posititon, v3.posititon );
// v2.normal += calcSurfaceNormal( v2.posititon, v3.posititon, v1.posititon );
// v3.normal += calcSurfaceNormal( v3.posititon, v1.posititon, v2.posititon );
v1.normal += normal;
v2.normal += normal;
v3.normal += normal;
const glm::vec3 normal = glm::normalize(calcSurfaceNormal(v1.position, v2.position, v3.position));
v1.normal = normal;
v2.normal = normal;
v3.normal = normal;
}
}
static void compute_normals(fggl::mesh::Mesh3D &mesh,
const std::vector<Mesh::IndexType> &idxList, // source index
const std::vector<Mesh::IndexType> &idxMapping // source-to-mesh lookup
) {
// clear the normals, so the summation below works correctly
for (auto vertexIndex : idxMapping) {
auto &vertex = mesh.data[vertexIndex];
vertex.normal = ILLEGAL_NORMAL;
}
// we need to calculate the contribution for each vertex
// this assumes IDXList describes a raw triangle list (ie, not quads and not a strip)
for (std::size_t i = 0; i < idxList.size(); i += 3) {
auto &v1 = mesh.data[ idxMapping[idxList[i]] ];
auto &v2 = mesh.data[ idxMapping[idxList[i + 1]] ];
auto &v3 = mesh.data[ idxMapping[idxList[i + 2]] ];
// calculate the normal and area (formula for area the math textbook)
float area = glm::length(glm::cross(v3.position - v2.position, v1.position - v3.position)) / 2;
auto faceNormal = calcSurfaceNormal(v1.position, v2.position, v3.position);
// weight the normal according to the area of the surface (bigger area = more impact)
v1.normal += area * faceNormal;
v2.normal += area * faceNormal;
v3.normal += area * faceNormal;
}
// each vertex should currently be the sum of every triangle it's part of
// so we need to normalize them
for (auto& vertex : mesh.vertexList() ) {
vertex.normal = glm::normalize( vertex.normal );
// re-normalise the normals
for (unsigned int vertexIndex : idxMapping) {
auto &vertex = mesh.data[vertexIndex];
vertex.normal = glm::normalize(vertex.normal);
}
}
static void computeNormalsDirect( fggl::data::Mesh& mesh, const int* colIdx, int nPoints) {
static void populateMesh(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 transform,
const int nIdx, const fggl::math::vec3 *pos, const Mesh::IndexType *idx) {
// we're assuming all the normals are zero...
for ( int i=0; i<nPoints; i++ ) {
auto& vertex = mesh.vertex( colIdx[i] );
vertex.normal = glm::vec3(0.0F);
auto *colIdx = new fggl::data::Mesh::IndexType[nIdx];
// generate mesh
for (int i = 0; i < nIdx; i++) {
glm::vec3 rawPos = transform * glm::vec4(pos[idx[i]], 1.0);
colIdx[i] = mesh.append(fggl::mesh::Vertex3D::from_pos(rawPos));
mesh.indices.push_back(colIdx[i]);
}
// We're assuming each vertex appears only once (because we're not indexed)
for (int i=0; i<nPoints; i += 3) {
auto& v1 = mesh.vertex( colIdx[ i ] );
auto& v2 = mesh.vertex( colIdx[ i + 1 ] );
auto& v3 = mesh.vertex( colIdx[ i + 2 ] );
computeNormalsDirect(mesh, colIdx, nIdx);
const glm::vec3 normal = glm::normalize( calcSurfaceNormal( v1.posititon, v2.posititon, v3.posititon ) );
v1.normal = normal;
v2.normal = normal;
v3.normal = normal;
}
delete[] colIdx;
}
fggl::data::Mesh fggl::data::make_triangle() {
constexpr fggl::math::vec3 pos[]{
{-0.5F, -0.5F, 0.0F},
{0.5F, -0.5F, 0.0F},
{0.0F, 0.5F, 0.0F}
};
// add points
fggl::data::Mesh mesh;
for (auto po : pos) {
Vertex vert{};
vert.posititon = po;
vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
mesh.push(vert);
}
// mesh
for (int i = 0; i < 3; ++i) {
mesh.pushIndex(i);
}
return mesh;
static void populateMesh(fggl::mesh::Mesh3D &mesh,
const fggl::math::mat4 transform,
const std::vector<fggl::math::vec3> &posList,
const std::vector<fggl::data::Mesh::IndexType> &idxList) {
// tmp store the resulting mesh indexes (incase the mesh has multiple primatives)
std::vector<unsigned int> colIdx;
colIdx.reserve(posList.size());
// clion this thinks this loop is infinite, my assumption is it's gone bananas
for (std::size_t i = 0; i < posList.size(); ++i) {
glm::vec3 position = transform * fggl::math::vec4(posList[i], 1.0F);
auto vert = fggl::mesh::Vertex3D::from_pos(position);
colIdx[i] = mesh.append(vert);
}
// use the remapped indexes for the mesh
for (auto idx : idxList) {
mesh.indices.push_back(colIdx[idx]);
}
compute_normals(mesh, idxList, colIdx);
}
fggl::data::Mesh fggl::data::make_quad_xy() {
constexpr fggl::math::vec3 pos[] {
namespace fggl::data {
static void quads2Tris(std::vector<Mesh::IndexType> &indexList, uint32_t stacks, uint32_t slices) {
const auto HORZ_SIZE = slices + 1;
for (uint32_t vertical = 0; vertical < stacks; vertical++) {
for (uint32_t horz = 0; horz < slices; horz++) {
const auto topLeft = vertical * HORZ_SIZE + horz;
const auto topRight = vertical * HORZ_SIZE + (horz + 1);
const auto bottomLeft = (vertical + 1) * HORZ_SIZE + horz;
const auto bottomRight = (vertical + 1) * HORZ_SIZE + (horz + 1);
indexList.push_back(topLeft);
indexList.push_back(bottomLeft);
indexList.push_back(topRight);
indexList.push_back(topRight);
indexList.push_back(bottomLeft);
indexList.push_back(bottomRight);
}
}
}
void make_sphere(fggl::mesh::Mesh3D &mesh, const math::mat4 &offset, uint32_t slices, uint32_t stacks) {
std::vector<math::vec3> positions;
float verticalAngularStride = math::PI / (float) stacks;
float horizontalAngularStride = math::TAU / (float) slices;
// calculate vertex positions
for (uint32_t vertical = 0; vertical < (stacks + 1); vertical++) {
float theta = (math::HALF_PI) - verticalAngularStride * (float) vertical;
for (uint32_t horz = 0; horz < (slices + 1); horz++) {
float phi = horizontalAngularStride * (float) horz;
math::vec3 position{
cosf(theta) * cosf(phi),
cosf(theta) * sinf(phi),
sinf(theta)
};
positions.push_back(position);
}
}
// combine the vertices into triangles
std::vector<Mesh::IndexType> indexList;
quads2Tris(indexList, stacks, slices);
populateMesh(mesh, offset, positions, indexList);
}
} // namespace fggl::data
auto fggl::data::make_triangle() -> fggl::data::Mesh {
constexpr fggl::math::vec3 pos[]{
{-0.5F, -0.5F, 0.0F},
{ 0.5F, -0.5F, 0.0F},
{ 0.5F, 0.5F, 0.0F},
{-0.5F, 0.5F, 0.0F}
};
constexpr int idx[] {
0, 1, 3,
3, 1, 2
{0.5F, -0.5F, 0.0F},
{0.0F, 0.5F, 0.0F}
};
// add points
fggl::data::Mesh mesh;
std::array<int,4> colIdx;
for (int i = 0; i < 4; ++i){
Vertex vert{};
vert.posititon = pos[i];
vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
colIdx[ i ] = mesh.pushVertex(vert);
for (auto po : pos) {
mesh.push(Vertex::from_pos(po));
}
for( auto i : idx ) {
mesh.pushIndex( colIdx[ i ] );
// mesh
for (int i = 0; i < 3; ++i) {
mesh.pushIndex(i);
}
return mesh;
}
fggl::data::Mesh fggl::data::make_quad_xz() {
constexpr fggl::math::vec3 pos[] {
{-0.5F, 0.0F, -0.5F},
{ 0.5F, 0.0F, -0.5F},
{ 0.5F, 0.0F, 0.5F},
{-0.5F, 0.0F, 0.5F}
auto fggl::data::make_quad_xy() -> fggl::data::Mesh {
constexpr fggl::math::vec3 pos[]{
{-0.5F, -0.5F, 0.0F},
{0.5F, -0.5F, 0.0F},
{0.5F, 0.5F, 0.0F},
{-0.5F, 0.5F, 0.0F}
};
constexpr int idx[] {
constexpr int idx[]{
0, 1, 3,
3, 1, 2
};
fggl::data::Mesh mesh;
int colIdx[4];
for (int i = 0; i < 4; ++i){
Vertex vert{};
vert.posititon = pos[i];
vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
colIdx[ i ] = mesh.pushVertex(vert);
std::array<int, 4> colIdx{};
for (int i = 0; i < 4; ++i) {
colIdx[i] = mesh.pushVertex(Vertex::from_pos(pos[i]));
}
for( auto i : idx ) {
mesh.pushIndex( colIdx[ i ] );
for (auto i : idx) {
mesh.pushIndex(colIdx[i]);
}
return mesh;
}
auto fggl::data::make_quad_xz() -> fggl::data::Mesh {
constexpr std::array<fggl::math::vec3, 4> pos{{
{-0.5F, 0.0F, -0.5F},
{0.5F, 0.0F, -0.5F},
{0.5F, 0.0F, 0.5F},
{-0.5F, 0.0F, 0.5F}
}};
constexpr std::array<int, 6> idx{{
0, 1, 3,
3, 1, 2
}};
constexpr float HALF_PI = M_PI / 2.0F;
static void populateMesh(fggl::data::Mesh& mesh, const fggl::math::mat4 transform,
int nIdx, const fggl::math::vec3* pos, const int* idx ) {
int colIdx[nIdx];
fggl::data::Mesh mesh;
std::array<int, 4> colIdx{};
for (int i = 0; i < 4; ++i) {
colIdx[i] = mesh.pushVertex(Vertex::from_pos(pos[i]));
}
// generate mesh
for (int i=0; i<nIdx; i++) {
glm::vec3 rawPos = transform * glm::vec4( pos[ idx[i] ], 1.0 );
Vertex vert{};
vert.posititon = rawPos;
vert.normal = glm::vec3(0.0F, 0.0F, 0.0F);
vert.colour = fggl::math::vec3(1.0F, 1.0F, 1.0F);
colIdx[ i ] = mesh.pushVertex( vert );
mesh.pushIndex( colIdx[i] );
for (auto i : idx) {
mesh.pushIndex(colIdx.at(i));
}
computeNormalsDirect( mesh, colIdx, nIdx );
return mesh;
}
fggl::data::Mesh fggl::data::make_cube(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
auto fggl::data::make_cube(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
constexpr fggl::math::vec3 pos[] {
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{ 0.5, 0.5, -0.5}, // 1
{ 0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 4 BOTTOM LOOP
{ 0.5, -0.5, -0.5}, // 5
{ 0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
};
constexpr int idx[] {
0, 3, 1, // top
3, 2, 1,
0, 1, 4, // side 0 - 1
5, 4, 1,
1, 2, 5, // side 1 - 2
2, 6, 5,
3, 7, 2, // side 2 - 3
2, 7, 6,
0, 4, 3, // side 3 - 0
4, 7, 3,
4, 5, 7, // bottom
7, 5, 6,
};
int nIdx = sizeof(idx) / sizeof(int);
populateMesh(mesh, transform, nIdx, pos, idx);
constexpr std::array<fggl::math::vec3, 8> pos{{
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{0.5, 0.5, -0.5}, // 1
{0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 4 BOTTOM LOOP
{0.5, -0.5, -0.5}, // 5
{0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
}};
constexpr std::array<Mesh::IndexType, 36> idx{{
0, 3, 1, // top
3, 2, 1,
0, 1, 4, // side 0 - 1
5, 4, 1,
1, 2, 5, // side 1 - 2
2, 6, 5,
3, 7, 2, // side 2 - 3
2, 7, 6,
0, 4, 3, // side 3 - 0
4, 7, 3,
4, 5, 7, // bottom
7, 5, 6,
}};
populateMesh(mesh, transform, idx.size(), pos.data(), idx.data());
return mesh;
}
fggl::data::Mesh fggl::data::make_slope(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
auto fggl::data::make_slope(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
// FIXME remove 2 and 3 and renumber the index list accordingly
constexpr fggl::math::vec3 pos[] {
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{ 0.5, 0.5, -0.5}, // 1
{ 0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
constexpr fggl::math::vec3 pos[]{
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{0.5, 0.5, -0.5}, // 1
{0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 4 BOTTOM LOOP
{ 0.5, -0.5, -0.5}, // 5
{ 0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
{0.5, -0.5, -0.5}, // 5
{0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
};
constexpr int idx[] {
constexpr Mesh::IndexType idx[]{
0, 7, 1, // ramp
7, 6, 1,
0, 1, 4, // side 0 - 1
......@@ -239,22 +316,22 @@ fggl::data::Mesh fggl::data::make_slope(fggl::data::Mesh& mesh, const fggl::math
return mesh;
}
fggl::data::Mesh fggl::data::make_ditch(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
auto fggl::data::make_ditch(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
// FIXME remove 2 and renumber the index list accordingly
constexpr fggl::math::vec3 pos[] {
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{ 0.5, 0.5, -0.5}, // 1
{ 0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
constexpr fggl::math::vec3 pos[]{
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{0.5, 0.5, -0.5}, // 1
{0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 4 BOTTOM LOOP
{ 0.5, -0.5, -0.5}, // 5
{ 0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
{0.5, -0.5, -0.5}, // 5
{0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
};
constexpr int idx[] {
constexpr Mesh::IndexType idx[]{
0, 3, 1, // top
3, 6, 1,
0, 1, 4, // side 0 - 1
......@@ -272,21 +349,21 @@ fggl::data::Mesh fggl::data::make_ditch(fggl::data::Mesh& mesh, const fggl::math
return mesh;
}
fggl::data::Mesh fggl::data::make_point(fggl::data::Mesh& mesh, const fggl::math::mat4& transform) {
auto fggl::data::make_point(fggl::mesh::Mesh3D &mesh, const fggl::math::mat4 &transform) -> fggl::mesh::Mesh3D {
// done as two loops, top loop is 0,1,2,3, bottom loop is 4,5,6,7
constexpr fggl::math::vec3 pos[] {
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{ 0.5, 0.5, -0.5}, // 1
{ 0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
constexpr fggl::math::vec3 pos[]{
{-0.5, 0.5, -0.5}, // 0 TOP LOOP
{0.5, 0.5, -0.5}, // 1
{0.5, 0.5, 0.5}, // 2
{-0.5, 0.5, 0.5}, // 3
{-0.5, -0.5, -0.5}, // 4 BOTTOM LOOP
{ 0.5, -0.5, -0.5}, // 5
{ 0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
{0.5, -0.5, -0.5}, // 5
{0.5, -0.5, 0.5}, // 6
{-0.5, -0.5, 0.5} // 7
};
constexpr int idx[] {
constexpr Mesh::IndexType idx[]{
0, 7, 5, // top
7, 6, 5,
0, 5, 4, // side 0 - 1
......@@ -394,7 +471,7 @@ Mesh fggl::data::make_cube() {
// triagulate the quads
quadsToTris(cube, vertIndex, cubeIndexes, nQuads);
computeNormals(cube);
compute_normals(cube);
return cube;
}*/
......
#include "model.hpp"
namespace fggl::data {
constexpr math::mat4 OFFSET_NONE(1.0f);
Mesh make_triangle();
// quads
Mesh make_quad_xy();
Mesh make_quad_xz();
// simple shapes
Mesh make_cube(Mesh& mesh, const math::mat4& offset);
inline Mesh make_cube(Mesh& mesh) {
return make_cube(mesh, OFFSET_NONE);
}
// blockout shapes
Mesh make_slope(Mesh& mesh, const math::mat4& offset);
inline Mesh make_slope(Mesh& mesh) {
return make_slope(mesh, OFFSET_NONE);
}
Mesh make_ditch(Mesh& mesh, const math::mat4& offset);
inline Mesh make_ditch(Mesh& mesh) {
return make_ditch(mesh, OFFSET_NONE);
}
Mesh make_point(Mesh& mesh, const math::mat4& offset);
inline Mesh make_point(Mesh& mesh) {
return make_point(mesh, OFFSET_NONE);
}
}
#ifndef FGGL_DATA_FS_H
#define FGGL_DATA_FS_H
namespace fggl::data {
class DataRegistry {
public:
DataRegistry();
~DataRegistry();
};
}
#endif
#ifndef FGGL_DATA_STORAGE_H
#define FGGL_DATA_STORAGE_H
#include <iostream>
#include <string>
#include <filesystem>
#include <spdlog/spdlog.h>
namespace fggl::data {
template<typename T>
bool fggl_serialize(std::filesystem::path& data, const T* out);
template<typename T>
bool fggl_deserialize(std::filesystem::path& data, T* out);
enum StorageType {Data, User, Cache};
class Storage {
public:
template<typename T>
bool load(StorageType pool, const std::string& name, T* out) {
auto path = resolvePath(pool, name);
if ( !std::filesystem::exists(path) ) {
spdlog::warn("Path {} does not exist!", path.c_str());
return false;
}
return fggl_deserialize<T>(path, out);
}
template<typename T>
void save(StorageType pool, const std::string& name, const T* out) {
auto path = resolvePath(pool, name);
fggl_serialize<T>(path, out);
}
inline std::filesystem::path resolvePath(StorageType pool, const std::string& name) {
std::filesystem::path path;
switch ( pool ) {
case Data:
path = std::filesystem::current_path() / "data";
break;
case User:
path = "./user-data/";
break;
case Cache:
path = "/tmp/fggl/";
break;
}
if ( !std::filesystem::exists(path) ) {
std::filesystem::create_directories(path);
}
return path / name;
}
};
}
#endif
target_sources(fggl
PRIVATE
debug.cpp
)
PRIVATE
debug.cpp
debug_draw.cpp
logging.cpp
)
# spdlog for cleaner logging
find_package(spdlog)
target_link_libraries(fggl PRIVATE spdlog::spdlog)
target_link_libraries(fggl imgui)
add_subdirectory(imgui)
\ No newline at end of file
#include "debug.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/>.
*/
#include <fggl/debug/debug.h>
#include "imgui.h"
#include "imgui/include/imgui_impl_glfw.h"
#include "imgui/include/imgui_impl_opengl3.h"
#include <imgui.h>
#include <backends/imgui_impl_glfw.h>
#include <backends/imgui_impl_opengl3.h>
using fggl::gfx::Window;
using fggl::debug::DebugUI;
DebugUI::DebugUI(std::shared_ptr<Window>& win) : m_visible(false) {
DebugUI::DebugUI(std::shared_ptr<fggl::display::glfw::Window> &win) : m_visible(false) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGuiIO &io = ImGui::GetIO();
io.IniFilename = nullptr;
ImGui_ImplGlfw_InitForOpenGL(win->handle(), true);
......@@ -32,14 +45,14 @@ void DebugUI::frameStart() {
}
void DebugUI::draw() {
for ( auto& [name, data] : m_windows) {
if ( data.m_visible ) {
data.m_callback( &data.m_visible );
for (auto &[name, data] : m_windows) {
if (data.m_visible) {
data.m_callback(&data.m_visible);
}
}
ImGui::Render();
if ( m_visible ) {
if (m_visible) {
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
}
/*
* 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 30/05/22.
//
#define DEBUG_DRAW_IMPLEMENTATION
#include "fggl/debug/draw.hpp"
\ No newline at end of file
target_link_libraries(fggl PRIVATE imgui)
target_sources(fggl
PRIVATE
imgui_impl_glfw.cpp
imgui_impl_opengl3.cpp
)
target_include_directories(fggl
PRIVATE
include/
)
\ No newline at end of file
// dear imgui: Platform Backend for GLFW
// This needs to be used along with a Renderer (e.g. OpenGL3, Vulkan, WebGPU..)
// (Info: GLFW is a cross-platform general purpose library for handling windows, inputs, OpenGL/Vulkan graphics context creation, etc.)
// (Requires: GLFW 3.1+)
// Implemented features:
// [X] Platform: Clipboard support.
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility. Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange' (note: the resizing cursors requires GLFW 3.4+).
// [X] Platform: Keyboard arrays indexed using GLFW_KEY_* codes, e.g. ImGui::IsKeyPressed(GLFW_KEY_SPACE).
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// If you are new to Dear ImGui, read documentation from the docs/ folder + read the top of imgui.cpp.
// Read online: https://github.com/ocornut/imgui/tree/master/docs
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2020-01-17: Inputs: Disable error callback while assigning mouse cursors because some X11 setup don't have them and it generates errors.
// 2019-12-05: Inputs: Added support for new mouse cursors added in GLFW 3.4+ (resizing cursors, not allowed cursor).
// 2019-10-18: Misc: Previously installed user callbacks are now restored on shutdown.
// 2019-07-21: Inputs: Added mapping for ImGuiKey_KeyPadEnter.
// 2019-05-11: Inputs: Don't filter value from character callback before calling AddInputCharacter().
// 2019-03-12: Misc: Preserve DisplayFramebufferScale when main window is minimized.
// 2018-11-30: Misc: Setting up io.BackendPlatformName so it can be displayed in the About Window.
// 2018-11-07: Inputs: When installing our GLFW callbacks, we save user's previously installed ones - if any - and chain call them.
// 2018-08-01: Inputs: Workaround for Emscripten which doesn't seem to handle focus related calls.
// 2018-06-29: Inputs: Added support for the ImGuiMouseCursor_Hand cursor.
// 2018-06-08: Misc: Extracted imgui_impl_glfw.cpp/.h away from the old combined GLFW+OpenGL/Vulkan examples.
// 2018-03-20: Misc: Setup io.BackendFlags ImGuiBackendFlags_HasMouseCursors flag + honor ImGuiConfigFlags_NoMouseCursorChange flag.
// 2018-02-20: Inputs: Added support for mouse cursors (ImGui::GetMouseCursor() value, passed to glfwSetCursor()).
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves.
// 2018-02-06: Inputs: Added mapping for ImGuiKey_Space.
// 2018-01-25: Inputs: Added gamepad support if ImGuiConfigFlags_NavEnableGamepad is set.
// 2018-01-25: Inputs: Honoring the io.WantSetMousePos by repositioning the mouse (when using navigation and ImGuiConfigFlags_NavMoveMouse is set).
// 2018-01-20: Inputs: Added Horizontal Mouse Wheel support.
// 2018-01-18: Inputs: Added mapping for ImGuiKey_Insert.
// 2017-08-25: Inputs: MousePos set to -FLT_MAX,-FLT_MAX when mouse is unavailable/missing (instead of -1,-1).
// 2016-10-15: Misc: Added a void* user_data parameter to Clipboard function handlers.
#include "imgui.h"
#include "include/imgui_impl_glfw.h"
// GLFW
#include <GLFW/glfw3.h>
#ifdef _WIN32
#undef APIENTRY
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h> // for glfwGetWin32Window
#endif
#define GLFW_HAS_WINDOW_TOPMOST (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ GLFW_FLOATING
#define GLFW_HAS_WINDOW_HOVERED (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ GLFW_HOVERED
#define GLFW_HAS_WINDOW_ALPHA (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwSetWindowOpacity
#define GLFW_HAS_PER_MONITOR_DPI (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3300) // 3.3+ glfwGetMonitorContentScale
#define GLFW_HAS_VULKAN (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3200) // 3.2+ glfwCreateWindowSurface
#ifdef GLFW_RESIZE_NESW_CURSOR // let's be nice to people who pulled GLFW between 2019-04-16 (3.4 define) and 2019-11-29 (cursors defines) // FIXME: Remove when GLFW 3.4 is released?
#define GLFW_HAS_NEW_CURSORS (GLFW_VERSION_MAJOR * 1000 + GLFW_VERSION_MINOR * 100 >= 3400) // 3.4+ GLFW_RESIZE_ALL_CURSOR, GLFW_RESIZE_NESW_CURSOR, GLFW_RESIZE_NWSE_CURSOR, GLFW_NOT_ALLOWED_CURSOR
#else
#define GLFW_HAS_NEW_CURSORS (0)
#endif
// Data
enum GlfwClientApi {
GlfwClientApi_Unknown,
GlfwClientApi_OpenGL,
GlfwClientApi_Vulkan
};
static GLFWwindow *g_Window = NULL; // Main window
static GlfwClientApi g_ClientApi = GlfwClientApi_Unknown;
static double g_Time = 0.0;
static bool g_MouseJustPressed[ImGuiMouseButton_COUNT] = {};
static GLFWcursor *g_MouseCursors[ImGuiMouseCursor_COUNT] = {};
static bool g_InstalledCallbacks = false;
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
static GLFWmousebuttonfun g_PrevUserCallbackMousebutton = NULL;
static GLFWscrollfun g_PrevUserCallbackScroll = NULL;
static GLFWkeyfun g_PrevUserCallbackKey = NULL;
static GLFWcharfun g_PrevUserCallbackChar = NULL;
static const char *ImGui_ImplGlfw_GetClipboardText(void *user_data) {
return glfwGetClipboardString((GLFWwindow *) user_data);
}
static void ImGui_ImplGlfw_SetClipboardText(void *user_data, const char *text) {
glfwSetClipboardString((GLFWwindow *) user_data, text);
}
void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
if (g_PrevUserCallbackMousebutton != NULL)
g_PrevUserCallbackMousebutton(window, button, action, mods);
if (action == GLFW_PRESS && button >= 0 && button < IM_ARRAYSIZE(g_MouseJustPressed))
g_MouseJustPressed[button] = true;
}
void ImGui_ImplGlfw_ScrollCallback(GLFWwindow *window, double xoffset, double yoffset) {
if (g_PrevUserCallbackScroll != NULL)
g_PrevUserCallbackScroll(window, xoffset, yoffset);
ImGuiIO &io = ImGui::GetIO();
io.MouseWheelH += (float) xoffset;
io.MouseWheel += (float) yoffset;
}
void ImGui_ImplGlfw_KeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
ImGuiIO &io = ImGui::GetIO();
if (g_PrevUserCallbackKey != NULL && !io.WantCaptureKeyboard)
g_PrevUserCallbackKey(window, key, scancode, action, mods);
if (key >= 0 && key < IM_ARRAYSIZE(io.KeysDown)) {
if (action == GLFW_PRESS)
io.KeysDown[key] = true;
if (action == GLFW_RELEASE)
io.KeysDown[key] = false;
}
// Modifiers are not reliable across systems
io.KeyCtrl = io.KeysDown[GLFW_KEY_LEFT_CONTROL] || io.KeysDown[GLFW_KEY_RIGHT_CONTROL];
io.KeyShift = io.KeysDown[GLFW_KEY_LEFT_SHIFT] || io.KeysDown[GLFW_KEY_RIGHT_SHIFT];
io.KeyAlt = io.KeysDown[GLFW_KEY_LEFT_ALT] || io.KeysDown[GLFW_KEY_RIGHT_ALT];
#ifdef _WIN32
io.KeySuper = false;
#else
io.KeySuper = io.KeysDown[GLFW_KEY_LEFT_SUPER] || io.KeysDown[GLFW_KEY_RIGHT_SUPER];
#endif
}
void ImGui_ImplGlfw_CharCallback(GLFWwindow *window, unsigned int c) {
if (g_PrevUserCallbackChar != NULL)
g_PrevUserCallbackChar(window, c);
ImGuiIO &io = ImGui::GetIO();
io.AddInputCharacter(c);
}
static bool ImGui_ImplGlfw_Init(GLFWwindow *window, bool install_callbacks, GlfwClientApi client_api) {
g_Window = window;
g_Time = 0.0;
// Setup backend capabilities flags
ImGuiIO &io = ImGui::GetIO();
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |=
ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
io.BackendPlatformName = "imgui_impl_glfw";
// Keyboard mapping. Dear ImGui will use those indices to peek into the io.KeysDown[] array.
io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB;
io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;
io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT;
io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP;
io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN;
io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP;
io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN;
io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME;
io.KeyMap[ImGuiKey_End] = GLFW_KEY_END;
io.KeyMap[ImGuiKey_Insert] = GLFW_KEY_INSERT;
io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE;
io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE;
io.KeyMap[ImGuiKey_Space] = GLFW_KEY_SPACE;
io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER;
io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE;
io.KeyMap[ImGuiKey_KeyPadEnter] = GLFW_KEY_KP_ENTER;
io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
io.KeyMap[ImGuiKey_C] = GLFW_KEY_C;
io.KeyMap[ImGuiKey_V] = GLFW_KEY_V;
io.KeyMap[ImGuiKey_X] = GLFW_KEY_X;
io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;
io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
io.ClipboardUserData = g_Window;
#if defined(_WIN32)
io.ImeWindowHandle = (void*)glfwGetWin32Window(g_Window);
#endif
// Create mouse cursors
// (By design, on X11 cursors are user configurable and some cursors may be missing. When a cursor doesn't exist,
// GLFW will emit an error which will often be printed by the app, so we temporarily disable error reporting.
// Missing cursors will return NULL and our _UpdateMouseCursor() function will use the Arrow cursor instead.)
GLFWerrorfun prev_error_callback = glfwSetErrorCallback(NULL);
g_MouseCursors[ImGuiMouseCursor_Arrow] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_TextInput] = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNS] = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeEW] = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_Hand] = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
#if GLFW_HAS_NEW_CURSORS
g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_RESIZE_ALL_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_RESIZE_NESW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_RESIZE_NWSE_CURSOR);
g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_NOT_ALLOWED_CURSOR);
#else
g_MouseCursors[ImGuiMouseCursor_ResizeAll] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNESW] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_ResizeNWSE] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
g_MouseCursors[ImGuiMouseCursor_NotAllowed] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
#endif
glfwSetErrorCallback(prev_error_callback);
// Chain GLFW callbacks: our callbacks will call the user's previously installed callbacks, if any.
g_PrevUserCallbackMousebutton = NULL;
g_PrevUserCallbackScroll = NULL;
g_PrevUserCallbackKey = NULL;
g_PrevUserCallbackChar = NULL;
if (install_callbacks) {
g_InstalledCallbacks = true;
g_PrevUserCallbackMousebutton = glfwSetMouseButtonCallback(window, ImGui_ImplGlfw_MouseButtonCallback);
g_PrevUserCallbackScroll = glfwSetScrollCallback(window, ImGui_ImplGlfw_ScrollCallback);
g_PrevUserCallbackKey = glfwSetKeyCallback(window, ImGui_ImplGlfw_KeyCallback);
g_PrevUserCallbackChar = glfwSetCharCallback(window, ImGui_ImplGlfw_CharCallback);
}
g_ClientApi = client_api;
return true;
}
bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow *window, bool install_callbacks) {
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_OpenGL);
}
bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow *window, bool install_callbacks) {
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Vulkan);
}
bool ImGui_ImplGlfw_InitForOther(GLFWwindow *window, bool install_callbacks) {
return ImGui_ImplGlfw_Init(window, install_callbacks, GlfwClientApi_Unknown);
}
void ImGui_ImplGlfw_Shutdown() {
if (g_InstalledCallbacks) {
glfwSetMouseButtonCallback(g_Window, g_PrevUserCallbackMousebutton);
glfwSetScrollCallback(g_Window, g_PrevUserCallbackScroll);
glfwSetKeyCallback(g_Window, g_PrevUserCallbackKey);
glfwSetCharCallback(g_Window, g_PrevUserCallbackChar);
g_InstalledCallbacks = false;
}
for (ImGuiMouseCursor cursor_n = 0; cursor_n < ImGuiMouseCursor_COUNT; cursor_n++) {
glfwDestroyCursor(g_MouseCursors[cursor_n]);
g_MouseCursors[cursor_n] = NULL;
}
g_ClientApi = GlfwClientApi_Unknown;
}
static void ImGui_ImplGlfw_UpdateMousePosAndButtons() {
// Update buttons
ImGuiIO &io = ImGui::GetIO();
for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) {
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
io.MouseDown[i] = g_MouseJustPressed[i] || glfwGetMouseButton(g_Window, i) != 0;
g_MouseJustPressed[i] = false;
}
// Update mouse position
const ImVec2 mouse_pos_backup = io.MousePos;
io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX);
#ifdef __EMSCRIPTEN__
const bool focused = true; // Emscripten
#else
const bool focused = glfwGetWindowAttrib(g_Window, GLFW_FOCUSED) != 0;
#endif
if (focused) {
if (io.WantSetMousePos) {
glfwSetCursorPos(g_Window, (double) mouse_pos_backup.x, (double) mouse_pos_backup.y);
} else {
double mouse_x, mouse_y;
glfwGetCursorPos(g_Window, &mouse_x, &mouse_y);
io.MousePos = ImVec2((float) mouse_x, (float) mouse_y);
}
}
}
static void ImGui_ImplGlfw_UpdateMouseCursor() {
ImGuiIO &io = ImGui::GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
|| glfwGetInputMode(g_Window, GLFW_CURSOR) == GLFW_CURSOR_DISABLED)
return;
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (imgui_cursor == ImGuiMouseCursor_None || io.MouseDrawCursor) {
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
} else {
// Show OS mouse cursor
// FIXME-PLATFORM: Unfocused windows seems to fail changing the mouse cursor with GLFW 3.2, but 3.3 works here.
glfwSetCursor(g_Window,
g_MouseCursors[imgui_cursor] ? g_MouseCursors[imgui_cursor]
: g_MouseCursors[ImGuiMouseCursor_Arrow]);
glfwSetInputMode(g_Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
}
}
static void ImGui_ImplGlfw_UpdateGamepads() {
ImGuiIO &io = ImGui::GetIO();
memset(io.NavInputs, 0, sizeof(io.NavInputs));
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
// Update gamepad inputs
#define MAP_BUTTON(NAV_NO, BUTTON_NO) { if (buttons_count > BUTTON_NO && buttons[BUTTON_NO] == GLFW_PRESS) io.NavInputs[NAV_NO] = 1.0f; }
#define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float v = (axes_count > AXIS_NO) ? axes[AXIS_NO] : V0; v = (v - V0) / (V1 - V0); if (v > 1.0f) v = 1.0f; if (io.NavInputs[NAV_NO] < v) io.NavInputs[NAV_NO] = v; }
int axes_count = 0, buttons_count = 0;
const float *axes = glfwGetJoystickAxes(GLFW_JOYSTICK_1, &axes_count);
const unsigned char *buttons = glfwGetJoystickButtons(GLFW_JOYSTICK_1, &buttons_count);
MAP_BUTTON(ImGuiNavInput_Activate, 0); // Cross / A
MAP_BUTTON(ImGuiNavInput_Cancel, 1); // Circle / B
MAP_BUTTON(ImGuiNavInput_Menu, 2); // Square / X
MAP_BUTTON(ImGuiNavInput_Input, 3); // Triangle / Y
MAP_BUTTON(ImGuiNavInput_DpadLeft, 13); // D-Pad Left
MAP_BUTTON(ImGuiNavInput_DpadRight, 11); // D-Pad Right
MAP_BUTTON(ImGuiNavInput_DpadUp, 10); // D-Pad Up
MAP_BUTTON(ImGuiNavInput_DpadDown, 12); // D-Pad Down
MAP_BUTTON(ImGuiNavInput_FocusPrev, 4); // L1 / LB
MAP_BUTTON(ImGuiNavInput_FocusNext, 5); // R1 / RB
MAP_BUTTON(ImGuiNavInput_TweakSlow, 4); // L1 / LB
MAP_BUTTON(ImGuiNavInput_TweakFast, 5); // R1 / RB
MAP_ANALOG(ImGuiNavInput_LStickLeft, 0, -0.3f, -0.9f);
MAP_ANALOG(ImGuiNavInput_LStickRight, 0, +0.3f, +0.9f);
MAP_ANALOG(ImGuiNavInput_LStickUp, 1, +0.3f, +0.9f);
MAP_ANALOG(ImGuiNavInput_LStickDown, 1, -0.3f, -0.9f);
#undef MAP_BUTTON
#undef MAP_ANALOG
if (axes_count > 0 && buttons_count > 0)
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
else
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
}
void ImGui_ImplGlfw_NewFrame() {
ImGuiIO &io = ImGui::GetIO();
IM_ASSERT(io.Fonts->IsBuilt()
&& "Font atlas not built! It is generally built by the renderer backend. Missing call to renderer _NewFrame() function? e.g. ImGui_ImplOpenGL3_NewFrame().");
// Setup display size (every frame to accommodate for window resizing)
int w, h;
int display_w, display_h;
glfwGetWindowSize(g_Window, &w, &h);
glfwGetFramebufferSize(g_Window, &display_w, &display_h);
io.DisplaySize = ImVec2((float) w, (float) h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float) display_w / w, (float) display_h / h);
// Setup time step
double current_time = glfwGetTime();
io.DeltaTime = g_Time > 0.0 ? (float) (current_time - g_Time) : (float) (1.0f / 60.0f);
g_Time = current_time;
ImGui_ImplGlfw_UpdateMousePosAndButtons();
ImGui_ImplGlfw_UpdateMouseCursor();
// Update game controllers (if enabled and available)
ImGui_ImplGlfw_UpdateGamepads();
}