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 1754 additions and 565 deletions
/*
* This file is part of FGGL.
*
* FGGL is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* FGGL is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
#include "fggl/gfx/ogl4/meshes.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/data/texture.hpp"
//
// Created by webpigeon on 22/10/22.
//
namespace fggl::gfx::ogl4 {
static auto setup_array_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<mesh::Vertex3D> &data) -> std::shared_ptr<ogl::ArrayBuffer> {
// upload the data to the GPU
auto buff = std::make_shared<ogl::ArrayBuffer>();
buff->write(data.size() * sizeof(mesh::Vertex3D), data.data(), ogl::BufUsage::STATIC_DRAW);
// set up the vertex attributes
auto posAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, position));
auto normalAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, normal));
auto colAttr = ogl::attribute<mesh::Vertex3D, math::vec3>(offsetof(mesh::Vertex3D, colour));
auto texAttr = ogl::attribute<mesh::Vertex3D, math::vec2>(offsetof(mesh::Vertex3D, texPos));
vao->setAttribute(*buff, 0, posAttr);
vao->setAttribute(*buff, 1, normalAttr);
vao->setAttribute(*buff, 2, colAttr);
vao->setAttribute(*buff, 3, texAttr);
return buff;
}
static auto setup_index_buffer(const std::vector<uint32_t>& data) -> std::shared_ptr<ogl::ElementBuffer> {
auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
elementBuffer->write(data.size() * sizeof(uint32_t),
data.data(), ogl::BufUsage::STATIC_DRAW);
return elementBuffer;
}
void setup_material(const std::shared_ptr<ogl::Shader>& shader, const PhongMaterial* material) {
if ( !shader->hasUniform("material.ambient") ) {
return;
}
// setup material block
shader->setUniformF(shader->uniform("material.emission"), material->emission);
shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
shader->setUniformF(shader->uniform("material.diffuse"), material->diffuse);
shader->setUniformF(shader->uniform("material.specular"), material->specular);
//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
}
void setup_material(const std::shared_ptr<ogl::Shader>& shader, const Material* material) {
if ( shader->hasUniform("material.diffuse") ) {
// setup material block
// shader->setUniformF(shader->uniform("material.emission"), material->emission);
// shader->setUniformF(shader->uniform("material.ambient"), material->ambient);
shader->setUniformF(shader->uniform("material.diffuse"), material->m_diffCol);
shader->setUniformF(shader->uniform("material.specular"), material->m_specCol);
}
// setup diffuse texture
if (material->m_diffuse != nullptr) {
material->m_diffuse->bind(0);
if (shader->hasUniform("diffuseTexture")) {
shader->setUniformI(shader->uniform("diffuseTexture"), 0);
}
}
// setup specular texture
if (material->m_specular != nullptr) {
material->m_specular->bind(1);
if (shader->hasUniform("specularTexture")) {
shader->setUniformI(shader->uniform("specularTexture"), 1);
}
}
//shader->setUniformF(shader->uniform("material.shininess"), material->shininess);
}
void MeshData::draw(std::shared_ptr<ogl::Shader> shader) const {
vao->bind();
vertexData->bind();
if ( material != nullptr ) {
setup_material(shader, material);
} else {
debug::info("no material is active, cannot bind textures!");
}
if ( drawInfo.restartIndex != ogl::NO_RESTART_IDX) {
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex( drawInfo.restartIndex );
}
vao->drawElements(*elements, drawInfo.mode, elementCount);
if ( drawInfo.restartIndex != ogl::NO_RESTART_IDX ) {
glDisable(GL_PRIMITIVE_RESTART);
}
}
void StaticMultiMesh::draw() const {
for ( const auto& mesh : meshes ) {
mesh.draw(pipeline);
}
}
void setup_lighting(const std::shared_ptr<ogl::Shader>& shader, const DirectionalLight* light) {
assert( light != nullptr );
if ( !shader->hasUniform("light.direction") ) {
fggl::debug::warning("asked for directional lighting, but shader does not support!");
return;
}
shader->setUniformF( shader->uniform("light.direction"), light->position);
shader->setUniformF( shader->uniform("light.ambient"), light->ambient);
shader->setUniformF( shader->uniform("light.diffuse"), light->diffuse);
shader->setUniformF( shader->uniform("light.specular"), light->specular);
}
void setup_lighting(const std::shared_ptr<ogl::Shader>& shader, const math::mat4& viewMatrix, const math::Transform& camTransform, const math::Transform& transform, math::vec3 lightPos) {
if (shader->hasUniform("lightPos")) {
shader->setUniformF(shader->uniform("lightPos"), lightPos);
}
if ( !shader->hasUniform("lights[0].isEnabled") ) {
return;
}
bool local = true;
shader->setUniformI(shader->uniform("lights[0].isEnabled"), 1);
shader->setUniformI(shader->uniform("lights[0].isLocal"), local);
shader->setUniformI(shader->uniform("lights[0].isSpot"), 0);
shader->setUniformF(shader->uniform("lights[0].constantAttenuation"), 5.0F);
shader->setUniformF(shader->uniform("lights[0].linearAttenuation"), 0.0F);
shader->setUniformF(shader->uniform("lights[0].quadraticAttenuation"), 0.0F);
shader->setUniformF(shader->uniform("Strength"), 0.6F);
if (!local) {
lightPos = glm::normalize(lightPos);
auto viewDir = glm::normalize(camTransform.origin() - transform.origin());
auto halfVector = glm::normalize(lightPos + viewDir);
shader->setUniformF(shader->uniform("lights[0].halfVector"), halfVector);
shader->setUniformF(shader->uniform("EyeDirection"), viewDir);
shader->setUniformF(shader->uniform("lights[0].position"), lightPos);
} else {
auto camModelView = (viewMatrix * camTransform.model() * math::vec4(0.0F, 0.0F, 0.0F, 1.0F));
auto modelModelView = (viewMatrix * transform.model() * math::vec4(0.0F, 0.0F, 0.0F, 1.0F));
math::vec3 viewDir = glm::normalize(camModelView - modelModelView);
shader->setUniformF(shader->uniform("EyeDirection"), viewDir);
shader->setUniformF(shader->uniform("lights[0].position"),
math::vec3(viewMatrix * math::vec4(lightPos, 1.0F)));
}
shader->setUniformF(shader->uniform("lights[0].ambient"), {0.0F, 0.5F, 0.0F});
shader->setUniformF(shader->uniform("lights[0].colour"), {0.5F, 0.5F, 0.5F});
}
static auto upload_texture( assets::AssetID name, assets::AssetManager* manager ) -> ogl::Texture* {
debug::info("loading texture: {}", name.get());
auto uploadedTex = assets::make_asset_id_rt("ogl", std::to_string(name.get()) );
auto* texture = manager->get<ogl::Texture>( uploadedTex );
if ( texture != nullptr ) {
return texture;
}
// get the texture data we plan to load
auto *textureData = manager->get<data::Texture2D>(name);
// create a texture
texture = new ogl::Texture(ogl::TextureType::Tex2D);
texture->setData( ogl::InternalImageFormat::RedGreenBlue, textureData);
return manager->set(uploadedTex, texture);
}
static auto get_fallback_material(assets::AssetManager* manager) -> Material* {
auto* fallback = manager->get<Material>(FALLBACK_MAT);
if ( fallback != nullptr ) {
return fallback;
}
auto* mat = new Material();
mat->m_diffCol = FALLBACK_COLOUR;
mat->m_specCol= FALLBACK_COLOUR;
mat->m_diffuse = manager->get<ogl::Texture>(FALLBACK_TEX);
mat->m_specular = manager->get<ogl::Texture>(FALLBACK_TEX);
mat->m_normals = manager->get<ogl::Texture>(SOLID_TEX);
return manager->set(FALLBACK_MAT, mat);
}
static auto upload_material( assets::AssetID name, assets::AssetManager* manager ) -> Material* {
auto* meshMaterial = manager->get<mesh::Material>(name);
if ( meshMaterial == nullptr ){
debug::error("attempted to load material {}, but did not exist!", name.get());
return get_fallback_material(manager);
}
auto uploadedMat = assets::make_asset_id_rt("ogl", std::to_string(name.get()) );
auto* material = manager->get<Material>( uploadedMat );
if ( material != nullptr ) {
return material;
}
material = new Material();
material->m_diffCol = meshMaterial->diffuse;
material->m_specCol = meshMaterial->specular;
// do we have a diffuse texture?
if ( !meshMaterial->diffuseTextures.empty() ) {
material->m_diffuse = upload_texture(meshMaterial->getPrimaryDiffuse(), manager);
} else {
material->m_diffuse = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
// do we have a normal Texture?
if ( !meshMaterial->normalTextures.empty() ) {
material->m_normals = upload_texture(meshMaterial->getPrimaryNormals(), manager);
} else {
material->m_normals = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
// do we have a specular texture?
if ( !meshMaterial->specularTextures.empty() ) {
material->m_specular = upload_texture(meshMaterial->getPrimarySpecular(), manager);
} else {
material->m_specular = manager->get<ogl::Texture>( ogl4::SOLID_TEX );
}
return manager->set( uploadedMat, material);
}
auto upload_mesh(const mesh::Mesh3D& rawMesh, assets::AssetManager* manager) -> MeshData {
auto vao = std::make_shared<ogl::VertexArray>();
return {
.vao = vao,
.elements = setup_index_buffer( rawMesh.indices ),
.vertexData = setup_array_buffer(vao, rawMesh.data ),
.elementCount = rawMesh.indices.size(),
.drawInfo = { .mode = ogl::Primitive::TRIANGLE },
.material = upload_material( rawMesh.material, manager )
};
}
auto upload_multi_mesh(const mesh::MultiMesh3D& rawMesh, assets::AssetManager* manager) -> std::vector<MeshData> {
std::vector<MeshData> gpuMeshes;
for (const auto& mesh : rawMesh.meshes) {
gpuMeshes.push_back( upload_mesh(mesh, manager) );
}
return gpuMeshes;
}
}
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
* This file is part of FGGL.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* You should have received a copy of the GNU Lesser General Public License along with FGGL.
* If not, see <https://www.gnu.org/licenses/>.
*/
//
......@@ -23,15 +17,19 @@
//
#include "fggl/gfx/ogl4/models.hpp"
#include "fggl/data/heightmap.h"
#include "fggl/gfx/ogl4/meshes.hpp"
#include "fggl/data/heightmap.hpp"
#include "fggl/data/texture.hpp"
#include "fggl/debug/logging.hpp"
#include "fggl/gfx/camera.hpp"
#include <spdlog/spdlog.h>
#include "fggl/gfx/phong.hpp"
namespace fggl::gfx::ogl4 {
static std::shared_ptr<ogl::ArrayBuffer> setupArrayBuffer(std::shared_ptr<ogl::VertexArray>& vao,
std::vector<data::Vertex>& data) {
static auto setup_array_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<data::Vertex> &data) -> std::shared_ptr<ogl::ArrayBuffer> {
auto buff = std::make_shared<ogl::ArrayBuffer>();
buff->write(data.size() * sizeof(data::Vertex), data.data(), ogl::BufUsage::STATIC_DRAW);
......@@ -40,148 +38,210 @@ namespace fggl::gfx::ogl4 {
auto normalAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, normal));
auto colAttr = ogl::attribute<data::Vertex, math::vec3>(offsetof(data::Vertex, colour));
vao->setAttribute( *buff.get(), 0, posAttr );
vao->setAttribute( *buff.get(), 1, normalAttr );
vao->setAttribute( *buff.get(), 3, colAttr );
vao->setAttribute(*buff, 0, posAttr);
vao->setAttribute(*buff, 1, normalAttr);
vao->setAttribute(*buff, 2, colAttr);
return buff;
}
static std::shared_ptr<ogl::ElementBuffer> setupIndexBuffer(std::shared_ptr<ogl::VertexArray>& vao,
std::vector<uint32_t>& data) {
static auto setup_index_buffer(std::shared_ptr<ogl::VertexArray> &vao,
const std::vector<uint32_t> &data) -> std::shared_ptr<ogl::ElementBuffer> {
auto elementBuffer = std::make_shared<ogl::ElementBuffer>();
elementBuffer->write(data.size() * sizeof(uint32_t),
data.data(), ogl::BufUsage::STATIC_DRAW);
return elementBuffer;
}
static void setupComponent(StaticModel* modelComp, std::shared_ptr<ogl::Shader>& shader, data::Mesh& mesh) {
auto vao = std::make_shared< ogl::VertexArray >();
auto meshBuffer = setupArrayBuffer(vao, mesh.vertexList());
auto elementBuffer = setupIndexBuffer(vao, mesh.indexList());
static void setup_component(StaticModel &modelComp, std::shared_ptr<ogl::Shader> &shader, data::Mesh &mesh) {
auto vao = std::make_shared<ogl::VertexArray>();
auto meshBuffer = setup_array_buffer(vao, mesh.vertexList());
auto elementBuffer = setup_index_buffer(vao, mesh.indexList());
// set up the element attributes
modelComp->vao = vao;
modelComp->vertexData = meshBuffer;
modelComp->elements = elementBuffer;
modelComp->pipeline = shader;
modelComp->elementCount = mesh.indexCount();
modelComp->drawType = ogl::Primative::TRIANGLE;
modelComp.vao = vao;
modelComp.vertexData = meshBuffer;
modelComp.elements = elementBuffer;
modelComp.pipeline = shader;
modelComp.elementCount = mesh.indexCount();
modelComp.drawType = ogl::Primitive::TRIANGLE;
}
void StaticModelRenderer::resolveModels(ecs3::World &world) {
// FIXME: this needs something reactive or performance will suck.
auto renderables = world.findMatching<data::StaticMesh>();
for (auto& renderable : renderables){
auto currModel = world.get<StaticModel>( renderable );
if ( currModel != nullptr ){
continue;
}
auto StaticModelRenderer::uploadMesh(assets::AssetID guid, const data::Mesh &mesh, bool allowCache) -> StaticModel* {
assert( m_assets != nullptr );
auto* meshComp = world.get<data::StaticMesh>(renderable);
auto* modelComp = world.add<StaticModel>(renderable);
setupComponent(modelComp, m_phong, meshComp->mesh);
// no active model, we need to resolve/load one.
spdlog::info("looks like {} needs a static mesh", renderable);
// if the asset has already been uploaded, we don't need to do anything
if ( allowCache && m_assets->has(guid) ) {
m_assets->require(guid);
return m_assets->get<StaticModel>(guid);
}
// terrain
auto terrain = world.findMatching<data::HeightMap>();
for (auto& renderable : terrain){
auto currModel = world.get<StaticModel>( renderable );
if ( currModel != nullptr ){
continue;
}
// the asset does not exist, we need to upload it
auto* modelAsset = new StaticModel();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertexData = setup_array_buffer(modelAsset->vao, mesh.vertexList());
modelAsset->elements = setup_index_buffer(modelAsset->vao, mesh.indexList());
modelAsset->elementCount = mesh.indexCount();
modelAsset->drawType = ogl::Primitive::TRIANGLE;
// if caching is enabled, then use the cache
if ( allowCache ) {
m_assets->set(guid, modelAsset);
}
auto* heightmap = world.get<data::HeightMap>(renderable);
data::Mesh heightMapMesh{};
data::generateHeightMesh(heightmap, heightMapMesh);
return modelAsset;
}
auto* modelComp = world.add<StaticModel>(renderable);
setupComponent(modelComp, m_phong, heightMapMesh);
/*MeshData* StaticModelRenderer::uploadMesh(assets::AssetGUID guid, const mesh::Mesh3D &mesh, bool allowCache) {
assert( m_assets != nullptr );
// we know this is a triangle strip with a restart vertex...
// FIXME the model should be telling us this...
modelComp->drawType = ogl::Primative::TRIANGLE_STRIP;
modelComp->restartIndex = heightMapMesh.restartVertex;
// if the asset has already been uploaded, we don't need to do anything
if ( allowCache && m_assets->has(guid) ) {
m_assets->require(guid);
return m_assets->get<MeshData>(guid);
}
// no active model, we need to resolve/load one.
spdlog::info("looks like heightmap, {} needs a static mesh", renderable);
// the asset does not exist, we need to upload it
auto* modelAsset = new MeshData();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertexData = setupArrayBuffer(modelAsset->vao, mesh.data);
modelAsset->elements = setupIndexBuffer(modelAsset->vao, mesh.indices);
modelAsset->elementCount = mesh.indices.size();
modelAsset->drawInfo.mode = ogl::Primitive::TRIANGLE;
// if caching is enabled, then use the cache
if ( allowCache ) {
m_assets->set(guid, modelAsset);
}
}
void StaticModelRenderer::renderModelsForward(const ecs3::World &world) {
return modelAsset;
}*/
// fetch cameras we will need to render with
auto cameras = world.findMatching<gfx::Camera>();
auto StaticModelRenderer::uploadMesh2(const assets::AssetID& meshName, const data::Mesh &mesh) -> StaticModelGPU* {
assert( m_assets != nullptr );
// if there are no cameras, we can't do anything...
if ( cameras.size() == 0) {
spdlog::warn("asked to render static models, but there were no cameras");
return;
if ( m_assets->has(meshName) ) {
// we've already uploaded it...
return m_assets->get<StaticModelGPU>(meshName);
}
// perform a rendering pass for each camera (will usually only be one...)
for ( auto& cameraEnt : cameras ){
//TODO should be cliping this to only visible objects
auto* modelAsset = new StaticModelGPU();
modelAsset->vao = std::make_shared<ogl::VertexArray>();
modelAsset->vertices = setup_array_buffer(modelAsset->vao, mesh.vertexList());
modelAsset->elements = setup_index_buffer(modelAsset->vao, mesh.indexList());
modelAsset->elementCount = mesh.indexCount();
modelAsset->drawType = ogl::Primitive::TRIANGLE;
return m_assets->set(meshName, modelAsset);
}
// enable required OpenGL state
glEnable( GL_CULL_FACE );
glCullFace( GL_BACK );
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
static void setup_meshes(entity::EntityManager& world, ShaderCache* shaders, assets::AssetManager* manager) {
auto entsWithModels = world.find<mesh::StaticMesh3D>();
for ( const auto& mesher : entsWithModels ) {
// enable depth testing
glEnable( GL_DEPTH_TEST );
// check if this entity already has a mesh...
const auto* currModel = world.tryGet<StaticMesh>(mesher);
if ( currModel != nullptr ) {
continue;
}
// set-up camera matrices
auto* const camTransform = world.get<math::Transform>(cameraEnt);
auto* const camComp = world.get<gfx::Camera>(cameraEnt);
// figure out the requirements
auto& rawMesh = world.get<mesh::StaticMesh3D>(mesher);
if ( rawMesh.guid != util::make_guid("__NO_CACHE__") ){
// TODO support fetching of loaded meshes
debug::warning("multiple entities sharing a mesh has not been implemented yet...");
}
const math::mat4 projectionMatrix = glm::perspective(camComp->fov, camComp->aspectRatio, camComp->nearPlane, camComp->farPlane);
const math::mat4 viewMatrix = glm::lookAt( camTransform->origin(), camComp->target, camTransform->up() );
auto& entityMesh = world.add<StaticMesh>(mesher);
entityMesh.pipeline = shaders->get( rawMesh.pipeline );
entityMesh.mesh = upload_mesh( rawMesh.mesh, manager );
// TODO lighting needs to not be this...
math::vec3 lightPos{20.0f, 20.0f, 15.0f};
}
}
auto renderables = world.findMatching<StaticModel>();
for ( const auto& entity : renderables ){
auto* transform = world.get<math::Transform>(entity);
StaticModel* model = world.get<StaticModel>(entity);
static void setup_multi_meshes(entity::EntityManager& world, ShaderCache* shaders, assets::AssetManager* manager) {
auto entsWithModels = world.find<mesh::StaticMultiMesh3D>();
for ( const auto& mesher : entsWithModels ) {
// grouping by shader would mean we only need to send the model matrix...
// TODO clean shader API
auto shader = model->pipeline;
if ( shader == nullptr ) {
spdlog::warn("shader was null, aborting render");
// check if this entity already has a mesh...
const auto* currModel = world.tryGet<StaticMultiMesh>(mesher);
if ( currModel != nullptr ) {
continue;
}
// setup shader uniforms
shader->use();
shader->setUniformMtx(shader->uniform("model"), transform->model());
shader->setUniformMtx(shader->uniform("view"), viewMatrix);
shader->setUniformMtx(shader->uniform("projection"), projectionMatrix);
auto lightPosIdx = shader->uniform("lightPos");
if ( lightPosIdx != -1 ) {
shader->setUniformF(lightPosIdx, lightPos);
// figure out the requirements
auto& multiMesh = world.get<mesh::StaticMultiMesh3D>(mesher);
if ( multiMesh.guid != util::make_guid("__NO_CACHE__") ){
// TODO support fetching of loaded meshes
debug::warning("multiple entities sharing a mesh has not been implemented yet...");
}
auto vao = model->vao;
vao->bind();
auto& entityMesh = world.add<StaticMultiMesh>(mesher);
entityMesh.pipeline = shaders->get( multiMesh.pipeline );
entityMesh.meshes = upload_multi_mesh(multiMesh.mesh, manager);
//entityMesh.material = setup_material( multiMesh.mesh.materials[0], manager);
}
}
model->vertexData->bind();
void StaticModelRenderer::resolveModels(entity::EntityManager &world) {
// new mesh formats
setup_meshes(world, m_shaders, m_assets);
setup_multi_meshes(world, m_shaders, m_assets);
if ( model->restartIndex != NO_RESTART_IDX) {
glEnable(GL_PRIMITIVE_RESTART);
glPrimitiveRestartIndex(model->restartIndex);
}
auto elements = model->elements.get();
vao->drawElements( *elements, model->drawType, model->elementCount);
if ( model->restartIndex != NO_RESTART_IDX) {
glDisable(GL_PRIMITIVE_RESTART);
// terrain
auto terrain = world.find<data::HeightMap>();
for (const auto &renderable : terrain) {
auto *currModel = world.tryGet<StaticModel>(renderable);
if (currModel != nullptr) {
continue;
}
auto &heightmap = world.get<data::HeightMap>(renderable);
data::Mesh heightMapMesh{};
data::generateHeightMesh(heightmap, heightMapMesh);
auto &modelComp = world.add<StaticModel>(renderable);
setup_component(modelComp, m_phong, heightMapMesh);
// we know this is a triangle strip with a restart vertex...
// FIXME the model should be telling us this...
modelComp.drawType = ogl::Primitive::TRIANGLE_STRIP;
modelComp.restartIndex = heightMapMesh.restartVertex;
// no active model, we need to resolve/load one.
debug::info("looks like {} needs a static mesh", (uint64_t)renderable);
}
}
#endif
void StaticModelRenderer::renderModelsForward(const entity::EntityManager &world, bool debugMode) {
// fetch cameras we will need to render with
auto cameras = world.find<gfx::Camera>();
// if there are no cameras, we can't do anything...
if (cameras.empty()) {
debug::warning("asked to render static models, but there were no cameras");
return;
}
// perform a rendering pass for each camera (will usually only be one...)
for (const auto &cameraEnt : cameras) {
//TODO should be clipping this to only visible objects
//forward_camera_pass(cameraEnt, world);
forward_pass<ogl4::StaticMesh>(cameraEnt, world, m_assets);
forward_pass<ogl4::StaticMultiMesh>(cameraEnt, world, m_assets);
// enable rendering normals
if ( debugMode ) {
auto normalShader = m_shaders->get("normals");
forward_pass_normals<StaticMesh>(cameraEnt, world, normalShader);
forward_pass_normals<StaticMultiMesh>(cameraEnt, world, normalShader);
//forward_normal_pass(cameraEnt, world, normalShader);
}
}
}
......
/*
* 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 24/07/22.
//
#include "fggl/gfx/ogl4/module.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/mesh/components.hpp"
#include "fggl/data/procedural.hpp"
#include "fggl/assets/loader.hpp"
#include <string>
namespace fggl::gfx {
constexpr uint32_t DEFAULT_STACKS = 16;
constexpr uint32_t DEFAULT_SLICES = 16;
constexpr const char *SHAPE_SPHERE{"sphere"};
constexpr const char *SHAPE_BOX{"box"};
namespace {
void process_shape(const YAML::Node &node, mesh::Mesh3D &mesh) {
auto transform = data::OFFSET_NONE;
auto offset = node["offset"].as<math::vec3>(math::VEC3_ZERO);
transform = glm::translate(transform, offset);
auto scale = node["scale"].as<math::vec3>(math::VEC3_ONES);
transform = glm::scale(transform, scale);
debug::debug("scale: {}, {}, {}", scale.x, scale.y, scale.z);
// now the shape itself
auto type = node["type"].as<std::string>();
if (type == SHAPE_BOX) {
data::make_cube(mesh, transform);
} else if (type == SHAPE_SPHERE) {
auto stacks = node["stacks"].as<uint32_t>(DEFAULT_STACKS);
auto slices = node["slices"].as<uint32_t>(DEFAULT_SLICES);
data::make_sphere(mesh, transform, stacks, slices);
} else {
debug::log(debug::Level::warning, "unknown shape type requested: {}", type);
}
}
}
void attach_mesh(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services &services) {
// check for the asset service
auto* assetService = services.get<assets::AssetManager>();
auto* assetLoader = services.get<assets::Loader>();
if ( assetService == nullptr || assetLoader == nullptr ) {
// no asset service, give up
return;
}
// asset is a procedural mesh description
if (spec.has("shape")) {
auto pipeline = spec.get<std::string>("pipeline", "");
// check if we had previously loaded this asset
/*const auto shapeName = spec.get<std::string>("shape_id", "");
if ( !shapeName.empty() ) {
meshAsset = assetService->get<data::Mesh>(shapeName);
}*/
// we've not loaded this before - generate mesh
if ( true ) {
// procedural meshes are build as static meshes first
if (spec.config["shape"].IsSequence()) {
mesh::MultiMesh3D* multiMesh;
for (const auto &node : spec.config["shape"]) {
auto* meshAsset = new mesh::Mesh3D();
process_shape(node, *meshAsset);
multiMesh->meshes.push_back(*meshAsset);
delete meshAsset;
}
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
// the graphics stack can detect static meshes without a rendering proxy at runtime and fix it but this
// requires loading the whole model into the ECS (and should be removed in the future). instead we should
// be triggering the upload at this point (if needed).
auto &entityMesh = manager.add<mesh::StaticMultiMesh3D>(id);
entityMesh.mesh = *multiMesh;
entityMesh.pipeline = pipeline;
debug::warning("HACKY: Triggered proc mesh - using deferred upload");
#endif
} else {
auto* meshAsset = new mesh::Mesh3D();
process_shape(spec.config["shape"], *meshAsset);
#ifdef FGGL_ALLOW_DEFERRED_UPLOAD
// the graphics stack can detect static meshes without a rendering proxy at runtime and fix it but this
// requires loading the whole model into the ECS (and should be removed in the future). instead we should
// be triggering the upload at this point (if needed).
auto &entityMesh = manager.add<mesh::StaticMesh3D>(id);
entityMesh.mesh = *meshAsset;
entityMesh.pipeline = pipeline;
debug::warning("HACKY: Triggered proc mesh - using deferred upload");
#endif
}
//assetService->set(shapeName, meshTmp);
}
// TODO we need to trigger an upload to the GPU (somehow)
return;
}
// asset is a model from the resource cache
if ( spec.has("model") ) {
// figure out what model we want
auto assetStr = spec.get<std::string>("model", "");
auto assetId = assets::asset_from_user( assetStr );
auto* asset = assetService->get<ogl4::StaticModel>(assetId);
if ( asset == nullptr ) {
// the asset is not loaded/does not exist
debug::error("requested model {} but it was not loaded.", assetStr);
return;
}
// copy the asset to the model
auto& model = manager.add<ogl4::StaticModel>(id);
model = *asset;
}
}
void attach_material(const entity::ComponentSpec &spec,
entity::EntityManager &manager,
const entity::EntityID &id,
modules::Services& /*services*/ ) {
auto &mat = manager.add<gfx::PhongMaterial>(id);
mat.ambient = spec.get<math::vec3>("ambient", gfx::DEFAULT_AMBIENT);
mat.diffuse = spec.get<math::vec3>("diffuse", gfx::DEFAULT_DIFFUSE);
mat.specular = spec.get<math::vec3>("ambient", gfx::DEFAULT_SPECULAR);
mat.shininess = spec.get<float>("ambient", gfx::DEFAULT_SHININESS);
}
void attach_light_directional(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services& /*services*/) {
auto &light = manager.add<gfx::DirectionalLight>(id);
light.position = spec.get<math::vec3>("direction", -math::UP);
light.ambient = spec.get<math::vec3>("ambient", gfx::colours::WHITE);
light.specular = spec.get<math::vec3>("specular", gfx::colours::WHITE);
light.diffuse = spec.get<math::vec3>("diffuse", gfx::colours::WHITE);
}
void attach_light_point(const entity::ComponentSpec &spec, entity::EntityManager &manager, const entity::EntityID &id, modules::Services& /*services*/) {
auto &light = manager.add<gfx::PointLight>(id);
light.position = spec.get<math::vec3>("position", math::VEC3_ZERO);
light.constant = spec.get<float>("constant", 1.0F);
light.linear = spec.get<float>("linear", 0.0014F);
light.quadratic = spec.get<float>("quadratic", 0.000007F);
}
auto OpenGL4::factory(modules::ServiceName service, modules::Services &services) -> bool {
if (service == WindowGraphics::service) {
// setup the thing responsible for graphics
auto *storage = services.get<data::Storage>();
auto *fontLibrary = services.get<gui::FontLibrary>();
auto *assets = services.get<assets::AssetManager>();
services.bind<WindowGraphics, ogl4::WindowGraphics>(storage, fontLibrary, assets);
// register as responsible for creating rendering components
auto *entityFactory = services.get<entity::EntityFactory>();
entityFactory->bind(data::StaticMesh::guid, attach_mesh);
entityFactory->bind(mesh::StaticMesh3D::guid, attach_mesh);
entityFactory->bind(mesh::StaticMultiMesh3D::guid, attach_mesh);
entityFactory->bind(gfx::PhongMaterial::guid, attach_material);
entityFactory->bind(gfx::DirectionalLight::guid, attach_light_directional);
entityFactory->bind(gfx::PointLight::guid, attach_light_point);
return true;
}
return false;
}
} // namespace fggl::gfX::ogl4
\ 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 24/07/22.
//
#include "fggl/gfx/ogl4/setup.hpp"
namespace fggl::gfx::ogl4 {
auto WindowGraphics::create(display::Window &window) -> Graphics * {
return new OpenGL4Backend(m_storage, m_fonts, m_assets, (GlFunctionLoader)glfwGetProcAddress);
}
}
\ 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 <utility>
#include <fggl/gfx/window.hpp>
#include <fggl/gfx/window_input.hpp>
#include <iostream>
#include "fggl/display/glfw/window.hpp"
#include "fggl/display/glfw/window_input.hpp"
#include "fggl/debug/logging.hpp"
#include <string>
#include <stdexcept>
#include <utility>
#include <GLFW/glfw3.h>
#include <spdlog/spdlog.h>
using namespace fggl::gfx;
namespace fggl::display::glfw {
static void glfw_error(int code, const char *description) {
debug::log(debug::Level::error, "[GLFW] {}: {}", code, description);
}
static void glfw_error(int code, const char* description) {
spdlog::warn("[GLFW error] {}: {}", code, description);
}
static void framebuffer_resize(GLFWwindow* window, int width, int height) {
auto *fgglWindow = static_cast<GlfwWindow*>(glfwGetWindowUserPointer( window ));
fgglWindow->framesize( width, height );
}
static void framebuffer_resize(GLFWwindow *window, int width, int height) {
auto *fgglWindow = static_cast<Window *>(glfwGetWindowUserPointer(window));
fgglWindow->framesize(width, height);
}
static void fggl_input_cursor(GLFWwindow* window, double x, double y) {
auto& input = GlfwInputManager::instance();
auto *fgglWin = static_cast<GlfwWindow*>(glfwGetWindowUserPointer(window));
static void fggl_input_cursor(GLFWwindow *window, double xPos, double yPos) {
auto &input = GlfwInputManager::instance();
auto *fgglWin = static_cast<Window *>(glfwGetWindowUserPointer(window));
#ifndef FGGL_INPUT_SCREEN_COORDS
#ifndef FGGL_INPUT_SCREEN_COORDS
// convert to nice ranges...
x = (x / fgglWin->width() * 2) - 1.0; // [-1, 1]
y = (y / fgglWin->height() * 2) - 1.0; // [-1, 1]
#endif
// inform the input system
input.onMouseMove(x, y);
}
static void fggl_input_scroll(GLFWwindow* window, double x, double y) {
auto& input = GlfwInputManager::instance();
input.onMouseScroll(x, y);
}
static void fggl_input_mouse_btn(GLFWwindow* window, int btn, int action, int mods) {
auto& input = GlfwInputManager::instance();
input.onMouseButton(btn, action == GLFW_PRESS);
}
static void fggl_input_keyboard(GLFWwindow* window, int key, int scancode, int action, int mods) {
auto& input = GlfwInputManager::instance();
input.onKeyEvent( scancode, action == GLFW_PRESS || action == GLFW_REPEAT);
}
static void fggl_update_joystick(fggl::input::GamepadInput& input, int jid) {
bool isGamepad = glfwJoystickIsGamepad(jid);
if ( isGamepad ) {
if( !input.present(jid) ) {
std::string name = glfwGetJoystickName(jid);
input.setActive(jid, true);
input.name(jid, name);
}
xPos = (xPos / fgglWin->width() * 2) - 1.0; // [-1, 1]
yPos = (yPos / fgglWin->height() * 2) - 1.0; // [-1, 1]
#endif
// inform the input system
input.onMouseMove(xPos, yPos);
}
static void fggl_input_scroll(GLFWwindow */*window*/, double xPos, double yPos) {
auto &input = GlfwInputManager::instance();
input.onMouseScroll(xPos, yPos);
}
static void fggl_input_mouse_btn(GLFWwindow */*window*/, int btn, int action, int /*mods*/) {
auto &input = GlfwInputManager::instance();
input.onMouseButton(btn, action == GLFW_PRESS);
}
static void fggl_input_keyboard(GLFWwindow */*window*/, int /*key*/, int scancode, int action, int /*mods*/) {
auto &input = GlfwInputManager::instance();
input.onKeyEvent(scancode, action == GLFW_PRESS || action == GLFW_REPEAT);
}
GLFWgamepadstate state;
glfwGetGamepadState(jid, &state);
static void fggl_update_joystick(fggl::input::GamepadInput &input, int jid) {
bool isGamepad = (glfwJoystickIsGamepad(jid) == GLFW_TRUE);
fggl::input::GamepadState gamepadState;
for (int i=0; i<=GLFW_GAMEPAD_BUTTON_LAST; i++) {
gamepadState.buttons[i] = state.buttons[i];
if (isGamepad) {
if (!input.present(jid)) {
std::string name = glfwGetJoystickName(jid);
input.setActive(jid, true);
input.name(jid, name);
}
GLFWgamepadstate state;
glfwGetGamepadState(jid, &state);
fggl::input::GamepadState gamepadState;
for (int i = 0; i <= GLFW_GAMEPAD_BUTTON_LAST; i++) {
gamepadState.buttons[i] = state.buttons[i];
}
for (int i = 0; i <= GLFW_GAMEPAD_AXIS_LAST; i++) {
gamepadState.axes[i] = state.axes[i];
}
input.update(jid, gamepadState);
} else {
input.setActive(jid, false);
}
}
for (int i=0; i<=GLFW_GAMEPAD_AXIS_LAST; i++) {
gamepadState.axes[i] = state.axes[i];
static void fggl_joystick_poll() {
auto &input = GlfwInputManager::instance();
if (!input.alive()) {
return;
}
input.update(jid, gamepadState);
} else {
input.setActive(jid, false);
auto &gamepadCtl = input.gamepads();
for (int jid = 0; jid < GLFW_JOYSTICK_LAST; jid++) {
if (glfwJoystickPresent(jid) == GLFW_TRUE) {
fggl_update_joystick(gamepadCtl, jid);
} else {
gamepadCtl.setActive(jid, false);
}
}
}
}
static void fggl_joystick_poll() {
auto& input = GlfwInputManager::instance();
if ( !input.alive() ) {
return;
}
static void fggl_joystick(int jid, int state) {
auto &input = GlfwInputManager::instance();
if (!input.alive()) {
return;
}
auto& gamepadCtl = input.gamepads();
for (int jid=0; jid < GLFW_JOYSTICK_LAST; jid++) {
if ( glfwJoystickPresent(jid) ) {
auto &gamepadCtl = input.gamepads();
if (state == GLFW_CONNECTED) {
fggl_update_joystick(gamepadCtl, jid);
} else {
gamepadCtl.setActive(jid, false);
}
}
}
static void fggl_joystick(int jid, int state) {
auto& input = GlfwInputManager::instance();
if ( !input.alive() ) {
return;
}
GlfwContext::GlfwContext(fggl::input::Input *input) {
debug::log(debug::Level::info, "[GLFW] Initializing...");
auto &glfwCallbacks = GlfwInputManager::instance();
glfwCallbacks.setup(input);
auto& gamepadCtl = input.gamepads();
if ( state == GLFW_CONNECTED ) {
fggl_update_joystick( gamepadCtl, jid );
} else {
gamepadCtl.setActive(jid, false);
}
}
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
glfwSetErrorCallback(glfw_error);
GlfwContext::GlfwContext( std::shared_ptr<fggl::input::Input> input ) {
spdlog::debug("[glfw] context creation stated");
auto& glfwCallbacks = GlfwInputManager::instance();
glfwCallbacks.setup( std::move(input) );
int state = glfwInit();
if (state == GLFW_FALSE) {
const char **error = nullptr;
glfwGetError(error);
throw std::runtime_error(*error);
}
glfwInitHint(GLFW_JOYSTICK_HAT_BUTTONS, GLFW_FALSE);
glfwSetErrorCallback(glfw_error);
// joysticks are global
fggl_joystick_poll();
glfwSetJoystickCallback(fggl_joystick);
debug::log(debug::Level::info, "[GLFW] Initialization complete.");
}
int state = glfwInit();
if ( state == GLFW_FALSE ) {
const char **error = nullptr;
glfwGetError(error);
throw std::runtime_error( *error );
GlfwContext::~GlfwContext() {
glfwTerminate();
debug::trace("[glfw] context terminated");
}
// joysticks are global
fggl_joystick_poll();
glfwSetJoystickCallback(fggl_joystick);
spdlog::debug("[glfw] context creation complete");
}
void GlfwContext::pollEvents() {
auto &glfwCallbacks = GlfwInputManager::instance();
glfwCallbacks.frame();
GlfwContext::~GlfwContext() {
glfwTerminate();
spdlog::debug("[glfw] context terminated");
}
glfwPollEvents();
fggl_joystick_poll();
}
void GlfwContext::pollEvents() {
auto& glfwCallbacks = GlfwInputManager::instance();
glfwCallbacks.frame();
Window::Window(std::shared_ptr<GlfwContext> context, gfx::WindowGraphics *graphics)
: m_context(std::move(context)), m_window(nullptr), m_framesize() {
glfwPollEvents();
fggl_joystick_poll();
}
// don't iconify when focus is lost.
glfwWindowHint( GLFW_AUTO_ICONIFY, GLFW_FALSE );
GlfwWindow::GlfwWindow(std::shared_ptr<GlfwContext> context) : m_context(std::move(context)), m_window(nullptr), m_framesize() {
spdlog::debug("[glfw] creating window");
// FIXME - this ties the graphics API before window creation
auto graphicsConfig = graphics->config();
if (graphicsConfig.api == gfx::GraphicsAPI::OpenGL) {
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, graphicsConfig.majorVersion);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, graphicsConfig.minorVersion);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, graphicsConfig.debug ? GLFW_TRUE : GLFW_FALSE);
}
// FIXME - this ties the graphics API before window creation
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE);
m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr);
if (m_window == nullptr) {
return;
}
m_window = glfwCreateWindow(1920, 1080, "main", nullptr, nullptr);
if ( m_window == nullptr ) {
return;
}
glfwSetWindowUserPointer(m_window, this);
glfwSetWindowUserPointer(m_window, this);
// setup callback wrappers (will invoke the methods via the user ptr above)
glfwSetFramebufferSizeCallback(m_window, framebuffer_resize);
glfwSetScrollCallback(m_window, fggl_input_scroll);
glfwSetCursorPosCallback(m_window, fggl_input_cursor);
glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn);
glfwSetKeyCallback(m_window, fggl_input_keyboard);
// setup callback wrappers (will invoke the methods via the user ptr above)
glfwSetFramebufferSizeCallback( m_window, framebuffer_resize );
glfwSetScrollCallback(m_window, fggl_input_scroll);
glfwSetCursorPosCallback(m_window, fggl_input_cursor);
glfwSetMouseButtonCallback(m_window, fggl_input_mouse_btn);
glfwSetKeyCallback(m_window, fggl_input_keyboard);
// bind the graphics API
glfwMakeContextCurrent(m_window);
m_graphics = graphics->createMain(*this);
}
spdlog::debug("[glfw] window creation complete");
}
Window::~Window() {
if ( m_graphics != nullptr ) {
delete m_graphics;
m_graphics = nullptr;
}
GlfwWindow::~GlfwWindow() {
if ( m_window != nullptr ) {
// prevent dangling pointers
glfwSetWindowUserPointer(m_window, nullptr);
glfwDestroyWindow(m_window);
m_window = nullptr;
if (m_window != nullptr) {
// prevent dangling pointers
glfwSetWindowUserPointer(m_window, nullptr);
glfwDestroyWindow(m_window);
m_window = nullptr;
}
}
}
void GlfwWindow::frameStart() {
assert( m_window != nullptr );
assert( m_graphics != nullptr );
void Window::frameStart() {
assert(m_window != nullptr);
assert(m_graphics != nullptr);
glfwMakeContextCurrent(m_window);
m_graphics->clear();
}
glfwMakeContextCurrent(m_window);
m_graphics->clear();
}
void GlfwWindow::frameEnd() {
glfwSwapBuffers(m_window);
}
void Window::frameEnd() {
glfwSwapBuffers(m_window);
}
void GlfwWindow::activate() const {
assert( m_window != nullptr );
glfwMakeContextCurrent(m_window);
}
void Window::activate() const {
assert(m_window != nullptr);
glfwMakeContextCurrent(m_window);
}
fggl::math::vec2i GlfwWindow::frameSize() const {
assert( m_window != nullptr );
math::vec2i size;
glfwGetFramebufferSize(m_window, &size.x, &size.y);
return size;
}
bool GlfwWindow::wantClose() const {
assert( m_window != nullptr );
return glfwWindowShouldClose(m_window) == GLFW_TRUE;
}
auto Window::frameSize() const -> fggl::math::vec2i {
assert(m_window != nullptr);
math::vec2i size;
glfwGetFramebufferSize(m_window, &size.x, &size.y);
return size;
}
auto Window::wantClose() const -> bool {
assert(m_window != nullptr);
return glfwWindowShouldClose(m_window) == GLFW_TRUE;
}
}
\ No newline at end of file
target_sources( fggl
PRIVATE
hexagon.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 10/12/22.
//
#include "fggl/grid/hexagon.hpp"
namespace fggl::grid {
std::vector<IntHex> lineTo(const IntHex& start, const IntHex& end) {
const int distance = start.distance(end);
std::vector<IntHex> line;
for (auto i=0; i < distance; ++i) {
line.push_back( round2(hexLerp(start, end, 1.0F/distance * i)) );
}
return line;
}
} // namespace fggl:grid
target_sources( ${PROJECT_NAME}
PRIVATE
widget.cpp
widgets.cpp
containers.cpp
fonts.cpp
model/parser.cpp
model/structure.cpp
renderer/renderer.cpp
)
find_package(Freetype)
target_link_libraries(${PROJECT_NAME} PUBLIC Freetype::Freetype)
/*
* 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/gui/containers.hpp>
#include <spdlog/spdlog.h>
namespace fggl::gui {
Widget *Container::getChildAt(const math::vec2 &point) {
for ( auto& child : m_children ){
if ( child->contains(point) ){
auto Container::getChildAt(const math::vec2 &point) -> Widget * {
for (auto &child : m_children) {
if (child->contains(point)) {
return child->getChildAt(point);
}
}
......@@ -12,21 +27,99 @@ namespace fggl::gui {
return nullptr;
}
bool Container::contains(const math::vec2 &point) {
auto Container::contains(const math::vec2 &point) -> bool {
return true;
}
void Container::render(gfx::Paint &paint) {
for( auto& child : m_children ){
child->render( paint );
for (auto &child : m_children) {
child->render(paint);
}
}
void Container::add(std::unique_ptr<Widget> widget) {
m_children.push_back( std::move(widget) );
m_children.push_back(std::move(widget));
m_dirty = true;
}
void Container::onMouseOver(math::vec2 pos) {
Widget::onMouseOver(pos);
auto* childHover = getChildAt(pos);
for ( auto& child : m_children) {
if ( child.get() != childHover ) {
child->onExit(pos);
}
}
if ( childHover != nullptr ) {
childHover->onMouseOver(pos);
}
}
void Container::onEnter(math::vec2i pos) {
Widget::onEnter(pos);
for ( auto& child : m_children ) {
if ( child->contains(pos)) {
child->onEnter(pos);
}
}
}
void Container::onExit(math::vec2i pos) {
Widget::onExit(pos);
for ( auto& child : m_children) {
child->onExit(pos);
}
}
GridBox::GridBox(uint32_t rows, uint32_t cols, uint32_t padx, uint32_t pady) : m_rows(rows), m_cols(cols), m_padding(padx, pady) {}
void GridBox::layout() {
assert( m_rows != 0 || m_cols != 0 );
if ( m_rows == 0 ) {
int rows = m_children.size() / m_cols;
// figure out the width and heights
auto* widths = new float[m_cols]{0.0F};
auto* heights = new float[rows]{0.0F};
for ( auto idx = 0U; idx < m_children.size(); ++idx) {
auto& child = m_children[idx];
int col = idx % m_cols;
int row = idx / m_cols;
widths[col] = std::max( child->size().x, widths[col] );
heights[row] = std::max( child->size().y, heights[row] );
}
// populate the grid
fggl::math::vec2i pos{0, 0};
unsigned int row = 0;
unsigned int col = 0;
for ( auto& child : m_children ) {
fggl::math::vec2i size{ widths[col], heights[row] };
child->size(pos, size);
child->layout();
// next iter
pos.x += size.x + m_padding.x;
col++;
if ( col == m_cols ) {
col = 0;
row++;
pos.x = 0;
pos.y += size.y + m_padding.y;
}
}
// cleanup variables
delete[] widths;
delete[] heights;
}
}
/*
Box::Box( LayoutAxis axis ) : m_axis( axis ) {}
......@@ -52,4 +145,14 @@ namespace fggl::gui {
setSize(biggestCross, lineSum);
}*/
void Panel::render(gfx::Paint &paint) {
// background painting time
gfx::Path2D background(topLeft());
background.colour(math::vec3(32.0F / 255.0F, 74.0F / 255.0F, 135.0F / 255.0F));
draw_box(background, topLeft(), bottomRight());
paint.fill(background);
// call parent's render method for child objects
Container::render(paint);
}
};
/*
* 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/gui/fonts.hpp"
namespace fggl::gui {
FontFace::FontFace(FT_Face face) : m_face(face) {
}
FontFace::~FontFace() {
FT_Done_Face(m_face);
}
FontLibrary::FontLibrary(data::Storage *storage) : m_context(nullptr), m_storage(storage) {
FT_Init_FreeType(&m_context);
m_defaultFont = getFont(DEFAULT_FONT_NAME);
}
FontLibrary::~FontLibrary() {
// free all fonts
m_defaultFont = nullptr;
m_cache.clear();
// shut the library down
FT_Done_FreeType(m_context);
}
auto FontFace::populateMetrics(char letter) -> GlyphMetrics & {
if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER)) {
// something bad happened
return m_metrics['?'];
}
GlyphMetrics metrics{
{m_face->glyph->bitmap.width, m_face->glyph->bitmap.rows},
{m_face->glyph->bitmap_left, m_face->glyph->bitmap_top},
m_face->glyph->advance.x
};
// return the new metrics data
auto it = m_metrics.emplace(letter, metrics);
return it.first->second;
}
void FontFace::texture(char letter, int &width, int &height, unsigned char **buff) {
if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER)) {
// something bad happened
return;
}
width = m_face->glyph->bitmap.width;
height = m_face->glyph->bitmap.rows;
*buff = m_face->glyph->bitmap.buffer;
}
} // namespace fggl::fonts
\ 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 11/03/23.
//
#include "fggl/gui/model/parser.hpp"
#include <yaml-cpp/yaml.h>
namespace fggl::gui::model {
Widget* YamlToWidgetTree(WidgetFactory& factory, const YAML::Node& config) {
Widget* root;
if ( config["template"] ) {
root = factory.build( config["template"].as<std::string>());
} else {
root = factory.buildEmpty();
}
// deal with attrs
for ( auto attr : config["attrs"] ) {
root->set(attr.first.as<std::string>(), attr.second.as<std::string>());
}
// deal with child nodes
for ( auto child : config["children"] ) {
Widget* childWidget = YamlToWidgetTree(factory, child);
root->addChild(*childWidget);
}
// are we a template definition?
if ( config["define"] ) {
factory.push( config["define"].as<std::string>(), std::move(*root) );
return factory.getTemplate( config["define"].as<std::string>() );
}
return root;
}
inline Widget* parseFile(WidgetFactory& factory, const std::string& path) {
YAML::Node root = YAML::LoadFile(path);
if ( !root ){
return nullptr;
}
return YamlToWidgetTree(factory, root);
}
}
\ 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 11/03/23.
//
#include "fggl/gui/model/structure.hpp"
#include "fggl/gui/fonts.hpp"
#include <algorithm>
#include <utility>
#include <string>
namespace fggl::gui::model {
math::vec2 calcTextBounds(const std::string& value, std::shared_ptr<FontFace> face) {
if ( face == nullptr ){
debug::warning("No preferred font sent, cowardly refusing to process text");
return {};
}
math::vec2 max{0, 0};
for (auto letter : value) {
auto metrics = face->metrics(letter);
max.x += metrics.size.x + (metrics.advance >> 6);
max.y = std::max(max.y, metrics.size.y);
}
return max;
}
inline math::vec2 calcBoxContrib(const Widget& widget, const std::string& name) {
return math::vec2{
widget.get_or_default<float>( name + "::left") + widget.get_or_default<float>(name + "::right"),
widget.get_or_default<float>( name + "::top" ) + widget.get_or_default<float>( name + "::bottom" )
};
}
void Widget::calcPrefSize(std::shared_ptr<FontFace> face) {
if ( !m_dirty ){
return;
}
auto padding = calcBoxContrib( *this, "padding");
auto border = calcBoxContrib( *this, "border");
auto content = math::vec2{0,0};
if (hasAttr("text")) {
content += calcTextBounds( get<std::string>("text"), std::move(face) );
}
m_cachedSize = padding + content + content;
debug::info("my preferred size is: ({}, {})", m_cachedSize.x, m_cachedSize.y);
m_dirty = 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/>.
*/
//
// Created by webpigeon on 11/03/23.
//
#include "fggl/gui/renderer/renderer.hpp"
namespace fggl::gui::renderer {
constexpr int PADDING = 15;
void draw_box(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 bottomRight) {
path.moveTo({topLeft.x, topLeft.y});
path.pathTo({bottomRight.x, topLeft.y});
path.pathTo({bottomRight.x, bottomRight.y});
path.pathTo({topLeft.x, bottomRight.y});
path.pathTo({topLeft.x, topLeft.y});
}
void draw_border_patch(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour) {
gfx::Path2D path({0,0});
path.colour(colour);
// draw edges
draw_box(path, {bounds.left + size.left, bounds.top}, {bounds.right - size.right, bounds.top + size.top} );
draw_box(path, {bounds.right - size.right, bounds.top + size.top}, {bounds.right, bounds.bottom - size.bottom} );
draw_box(path, {bounds.left + size.left, bounds.bottom - size.bottom}, {bounds.right - size.right, bounds.bottom} );
draw_box(path, {bounds.left, bounds.top + size.top}, {bounds.left + size.left, bounds.bottom - size.bottom} );
// draw-corners
draw_box(path, {bounds.left, bounds.top}, {bounds.left + size.left, bounds.top + size.top} );
draw_box(path, {bounds.right - size.right, bounds.top}, {bounds.right, bounds.top + size.top} );
draw_box(path, {bounds.left, bounds.bottom - size.bottom}, {bounds.left + size.left, bounds.bottom} );
draw_box(path, {bounds.right - size.right, bounds.bottom - size.bottom}, {bounds.right, bounds.bottom});
paint.fill(path);
}
void draw_border_solid(gfx::Paint& paint, Box& bounds, Box& size, math::vec3 colour) {
gfx::Path2D path({0,0});
path.colour(colour);
// draw edges
draw_box(path, {bounds.left, bounds.top}, {bounds.right, bounds.top + size.top} );
draw_box(path, {bounds.right - size.right, bounds.top + size.top}, {bounds.right, bounds.bottom - size.bottom} );
draw_box(path, {bounds.left, bounds.bottom - size.bottom}, {bounds.right, bounds.bottom} );
draw_box(path, {bounds.left, bounds.top + size.top}, {bounds.left + size.left, bounds.bottom - size.bottom} );
paint.fill(path);
}
void draw_background_solid(gfx::Paint& paint, Box& bounds, math::vec3 colour) {
gfx::Path2D path({0,0});
path.colour(colour);
draw_box(path, {bounds.left, bounds.top}, {bounds.right, bounds.bottom} );
paint.fill(path);
}
void layout(model::Widget& current) {
if ( current.isLeaf() ) {
// if the widget has a defined size, use that
if ( current.hasAttr("size") && !current.hasAttr("text") ) {
return;
}
// else, use the model's preferred size
auto preferred = current.preferredSize();
if ( preferred.has_value() ) {
current.set<math::vec2>("size", preferred.value() );
}
} else {
auto topPad = current.get_or_default<float>("border:top") + current.get_or_default<float>("padding::top");
auto leftPad = current.get_or_default<float>("border::left") + current.get_or_default<float>("padding::left");
math::vec2 size = {topPad, leftPad};
// layout all children
for ( auto& child : current ) {
layout(child);
auto childSize = child.get_or_default<math::vec2>("size");
size.x = std::max( childSize.x, size.x );
size.y += childSize.y;
child.set<math::vec2>("position", {leftPad, size.y});
}
// set our size based on that
current.set<math::vec2>("size", size );
}
}
void visit(const model::Widget& root, gfx::Paint& paint, Box offset) {
// get border size
auto border = get_box(root, "border");
// calculate box bounds
auto pos = get_vec2(root, "position");
auto size = get_vec2(root, "size");
auto bounds = getBounds(pos, size);
bounds.top += offset.top;
bounds.left += offset.left;
// deal with right hand size bounds
//bounds.right = std::min( size.x, offset.width() );
//bounds.right += offset.left;
// deal with bottom bounds
//bounds.bottom = std::min( size.y, offset.height() );
//bounds.bottom += offset.top;
auto background = bounds.trim(border);
draw_background_solid(paint, background, get_vec3_rgb(root, "colour"));
draw_border_patch(paint, bounds, border, get_vec3_rgb(root, "border::colour"));
auto padding = get_box(root, "padding");
background = background.trim(padding);
if ( root.hasAttr("text") ) {
auto text = root.get<std::string>("text");
paint.text(text, {background.left, background.top + PADDING});
}
for (const auto& child : root) {
visit(child, paint, background);
}
}
} // namespace fggl::gui:;renderer
\ 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 17/04/22.
//
......@@ -6,44 +20,44 @@
namespace fggl::gui {
void buttonBorder( gfx::Path2D& path, glm::vec2 pos, glm::vec2 size ) {
void button_border(gfx::Path2D &path, glm::vec2 pos, glm::vec2 size) {
// outer box
path.colour( {1.0f, 0.0f, 0.0f} );
path.pathTo( { pos.x + size.x, pos.y } );
path.pathTo( { pos.x + size.x, pos.y + size.y } );
path.pathTo( { pos.x, pos.y + size.y } );
path.colour({1.0F, 0.0F, 0.0F});
path.pathTo({pos.x + size.x, pos.y});
path.pathTo({pos.x + size.x, pos.y + size.y});
path.pathTo({pos.x, pos.y + size.y});
path.close();
// inner box
math::vec2 innerTop { pos.x + 5, pos.y + 5 };
math::vec2 innerBottom { pos.x + size.x - 5, pos.y + size.y - 5 };
path.colour( {1.0f, 1.0f, 0.0f} );
path.moveTo( { innerTop.x, innerTop.y } );
path.pathTo( { innerBottom.x, innerTop.y } );
path.pathTo( { innerBottom.x, innerBottom.y } );
path.pathTo( { innerTop.x, innerBottom.y } );
path.pathTo( { innerTop.x, innerTop.y } );
math::vec2 innerTop{pos.x + 5, pos.y + 5};
math::vec2 innerBottom{pos.x + size.x - 5, pos.y + size.y - 5};
path.colour({1.0F, 1.0F, 0.0F});
path.moveTo({innerTop.x, innerTop.y});
path.pathTo({innerBottom.x, innerTop.y});
path.pathTo({innerBottom.x, innerBottom.y});
path.pathTo({innerTop.x, innerBottom.y});
path.pathTo({innerTop.x, innerTop.y});
}
void makeBox( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 bottomRight ) {
path.moveTo( { topLeft.x, topLeft.y } );
path.pathTo( { bottomRight.x, topLeft.y } );
path.pathTo( { bottomRight.x, bottomRight.y } );
path.pathTo( { topLeft.x, bottomRight.y } );
path.pathTo( { topLeft.x, topLeft.y } );
void draw_box(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 bottomRight) {
path.moveTo({topLeft.x, topLeft.y});
path.pathTo({bottomRight.x, topLeft.y});
path.pathTo({bottomRight.x, bottomRight.y});
path.pathTo({topLeft.x, bottomRight.y});
path.pathTo({topLeft.x, topLeft.y});
}
void draw_progress( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 size, float value ) {
const auto bottomRight { topLeft + size };
void draw_progress(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 size, float value) {
const auto bottomRight{topLeft + size};
// background
path.colour( {0.5f, 0.5f, 0.5f} );
makeBox( path, topLeft, bottomRight );
path.colour({0.5F, 0.5F, 0.5F});
draw_box(path, topLeft, bottomRight);
// fill
math::vec2 innerTop { topLeft.x + 5, topLeft.y + 5 };
math::vec2 innerBottom { bottomRight.x - 5, bottomRight.y - 5 };
math::vec2 innerTop{topLeft.x + 5, topLeft.y + 5};
math::vec2 innerBottom{bottomRight.x - 5, bottomRight.y - 5};
// figure out how wide the bar should be
float barWidth = (innerBottom.x - innerTop.x) * value;
......@@ -51,56 +65,55 @@ namespace fggl::gui {
innerBottom.x = innerTop.x + barWidth;
// draw the bar
path.colour( {0.8f, 0.0f, 0.0f} );
makeBox( path, innerTop, innerBottom );
path.colour({0.8F, 0.0F, 0.0F});
draw_box(path, innerTop, innerBottom);
// part of the bar that's not filled in
math::vec2 emptyTop { innerBottom.x, innerTop.y };
math::vec2 emptyBottom { trueBottom, innerBottom.y };
path.colour( {0.4f, 0.0f, 0.0f} );
makeBox( path, emptyTop, emptyBottom );
math::vec2 emptyTop{innerBottom.x, innerTop.y};
math::vec2 emptyBottom{trueBottom, innerBottom.y};
path.colour({0.4F, 0.0F, 0.0F});
draw_box(path, emptyTop, emptyBottom);
}
void draw_slider( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 size, float value ) {
draw_progress( path, topLeft, size, value );
void draw_slider(gfx::Path2D &path, glm::vec2 topLeft, glm::vec2 size, float value) {
draw_progress(path, topLeft, size, value);
// dimensions
const auto bottomRight { topLeft + size };
const math::vec2 innerTop { topLeft.x + 5, topLeft.y + 5 };
const math::vec2 innerBottom { bottomRight.x - 5, bottomRight.y - 5 };
const auto bottomRight{topLeft + size};
const math::vec2 innerTop{topLeft.x + 5, topLeft.y + 5};
const math::vec2 innerBottom{bottomRight.x - 5, bottomRight.y - 5};
// selector bar
float trackWidth = innerBottom.x - innerTop.x;
float selectorValue = trackWidth * value;
float selectorWidth = 6;
math::vec2 selectorTop { innerTop.x + selectorValue - ( selectorWidth/2), topLeft.y };
math::vec2 selectorBottom { selectorTop.x + selectorWidth, bottomRight.y };
path.colour( {1.0f, 1.0f, 1.0f} );
makeBox( path, selectorTop, selectorBottom );
math::vec2 selectorTop{innerTop.x + selectorValue - (selectorWidth / 2), topLeft.y};
math::vec2 selectorBottom{selectorTop.x + selectorWidth, bottomRight.y};
path.colour({1.0F, 1.0F, 1.0F});
draw_box(path, selectorTop, selectorBottom);
}
void draw_button( gfx::Path2D& path, glm::vec2 pos, glm::vec2 size, bool active, bool pressed) {
void draw_button(gfx::Path2D &path, glm::vec2 pos, glm::vec2 size, bool active, bool pressed) {
// locations
math::vec2 outerTop { pos };
math::vec2 outerBottom { pos + size };
math::vec2 innerTop { pos.x + 5, pos.y + 5 };
math::vec2 innerBottom { pos.x + size.x - 5, pos.y + size.y - 5 };
math::vec2 outerTop{pos};
math::vec2 outerBottom{pos + size};
math::vec2 innerTop{pos.x + 5, pos.y + 5};
math::vec2 innerBottom{pos.x + size.x - 5, pos.y + size.y - 5};
math::vec3 baseColour{ 0.5f, 0.5f, 0.5f };
math::vec3 baseColour{0.5F, 0.5F, 0.5F};
if ( active ) {
baseColour *= 1.2f;
if (active) {
baseColour *= 1.2F;
}
if ( pressed ) {
baseColour *= 0.8f;
if (pressed) {
baseColour *= 0.8F;
}
math::vec3 lightColour{ baseColour * 1.2f };
math::vec3 darkColour{ baseColour * 0.8f };
if ( pressed ) {
math::vec3 lightColour{baseColour * 1.2F};
math::vec3 darkColour{baseColour * 0.8F};
if (pressed) {
// flip light and dark for selected buttons
auto tmp = darkColour;
darkColour = lightColour;
......@@ -108,39 +121,39 @@ namespace fggl::gui {
}
// bottom side
path.colour( darkColour );
path.moveTo( outerTop );
path.pathTo( innerTop );
path.pathTo( { innerBottom.x, innerTop.y } );
path.pathTo( { outerBottom.x, outerTop.y } );
path.pathTo( outerTop );
path.colour(lightColour);
path.moveTo(outerTop);
path.pathTo(innerTop);
path.pathTo({innerBottom.x, innerTop.y});
path.pathTo({outerBottom.x, outerTop.y});
path.pathTo(outerTop);
// left side
path.colour( darkColour );
path.moveTo( outerTop );
path.pathTo( innerTop );
path.pathTo( { innerTop.x, innerBottom.y } );
path.pathTo( { outerTop.x, outerBottom.y } );
path.pathTo( outerTop );
path.colour(lightColour);
path.moveTo(outerTop);
path.pathTo(innerTop);
path.pathTo({innerTop.x, innerBottom.y});
path.pathTo({outerTop.x, outerBottom.y});
path.pathTo(outerTop);
// top side
path.colour( lightColour );
path.moveTo( { outerTop.x, outerBottom.y} );
path.pathTo( { innerTop.x, innerBottom.y} );
path.pathTo( innerBottom );
path.pathTo( outerBottom );
path.pathTo( { outerTop.x, outerBottom.y} );
path.colour(darkColour);
path.moveTo({outerTop.x, outerBottom.y});
path.pathTo({innerTop.x, innerBottom.y});
path.pathTo(innerBottom);
path.pathTo(outerBottom);
path.pathTo({outerTop.x, outerBottom.y});
// right side
path.colour( lightColour );
path.moveTo( outerBottom );
path.pathTo( innerBottom );
path.pathTo( { innerBottom.x, innerTop.y } );
path.pathTo( { outerBottom.x, outerTop.y } );
path.pathTo( outerBottom );
path.colour(darkColour);
path.moveTo(outerBottom);
path.pathTo(innerBottom);
path.pathTo({innerBottom.x, innerTop.y});
path.pathTo({outerBottom.x, outerTop.y});
path.pathTo(outerBottom);
// inner box
path.colour( baseColour );
makeBox( path, innerTop, innerBottom );
path.colour(baseColour);
draw_box(path, innerTop, innerBottom);
}
}
\ 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 17/04/22.
//
......@@ -9,32 +23,58 @@
namespace fggl::gui {
Button::Button( math::vec2 pos, math::vec2 size) : Widget(pos, size), m_hover(false), m_active(false) {}
Button::Button(math::vec2 pos, math::vec2 size) : Widget(pos, size), m_label(pos, size), m_active(false) {}
void Button::render(gfx::Paint &paint) {
gfx::Path2D path{ topLeft() };
gfx::Path2D path{topLeft()};
draw_button(path, topLeft(), size(), m_hover, m_active);
paint.fill(path);
m_label.render(paint);
}
void Button::activate() {
m_active = !m_active;
if ( m_active ) {
for( auto& callback : m_callbacks ) {
if (m_active) {
for (auto &callback : m_callbacks) {
callback();
}
m_active = false;
}
}
void Button::onEnter() {
m_hover = true;
void Button::addCallback(Callback cb) {
m_callbacks.push_back(cb);
}
void Button::onExit() {
m_hover = false;
void Button::label(const std::string &value) {
m_label.text(value);
}
void Button::addCallback(Callback cb) {
m_callbacks.push_back( cb );
void Button::layout() {
m_label.size(topLeft(), size());
}
auto Button::label() const -> std::string {
return m_label.text();
}
void Label::layout() {
if (m_font == nullptr) {
return;
}
math::vec2 size;
for (const auto &letter : m_value) {
auto metrics = m_font->metrics(letter);
size.x += (metrics.advance << 6);
size.y = std::max(size.y, metrics.size.y);
}
m_naturalSize = size;
m_needsLayout = false;
}
Label::Label(math::vec2 pos, math::vec2 size) : Widget(pos, size) {
}
} // namespace fggl::gui
\ 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/2021.
//
#include <fggl/ecs3/ecs.hpp>
#include <fggl/input/input.hpp>
#include <fggl/gfx/camera.hpp>
#include <fggl/input/camera_input.h>
#include <fggl/input/camera_input.hpp>
namespace fggl::input {
void process_arcball(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam) {
// see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/
auto *camTransform = ecs.get<fggl::math::Transform>(cam);
auto *camComp = ecs.get<fggl::gfx::Camera>(cam);
auto &mouse = input.mouse;
glm::vec4 position(camTransform->origin(), 1.0f);
glm::vec4 pivot(camComp->target, 1.0f);
glm::mat4 view = glm::lookAt(camTransform->origin(), camComp->target, camTransform->up());
glm::vec3 viewDir = -glm::transpose(view)[2];
glm::vec3 rightDir = glm::transpose(view)[0];
float deltaAngleX = (2 * M_PI);
float deltaAngleY = (M_PI);
float xAngle = (-mouse.axisDelta(fggl::input::MouseAxis::X)) * deltaAngleX;
float yAngle = (-mouse.axisDelta(fggl::input::MouseAxis::Y)) * deltaAngleY;
// rotate the camera around the pivot on the first axis
glm::mat4x4 rotationMatrixX(1.0f);
rotationMatrixX = glm::rotate(rotationMatrixX, xAngle, fggl::math::UP);
position = (rotationMatrixX * (position - pivot)) + pivot;
// rotate the camera aroud the pivot on the second axis
glm::mat4x4 rotationMatrixY(1.0f);
rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir);
glm::vec3 finalPos = (rotationMatrixY * (position - pivot)) + pivot;
camTransform->origin(finalPos);
}
void process_freecam(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam) {
float rotationValue = 0.0f;
glm::vec3 translation(0.0f);
auto &keyboard = input.keyboard;
auto* settings = ecs.get<FreeCamKeys>(cam);
// calculate rotation (user input)
if (keyboard.down(settings->rotate_cw)) {
rotationValue = ROT_SPEED;
} else if (keyboard.down(settings->rotate_ccw)) {
rotationValue = -ROT_SPEED;
}
// calculate movement (user input)
if (keyboard.down(settings->forward)) {
translation -= fggl::math::RIGHT;
}
if (keyboard.down(settings->backward)) {
translation += fggl::math::RIGHT;
}
if (keyboard.down(settings->right)) {
translation += fggl::math::FORWARD;
}
if (keyboard.down(settings->left)) {
translation -= fggl::math::FORWARD;
}
// apply rotation/movement
auto camTransform = ecs.get<fggl::math::Transform>(cam);
auto camComp = ecs.get<fggl::gfx::Camera>(cam);
glm::vec4 position(camTransform->origin(), 1.0f);
glm::vec4 pivot(camComp->target, 1.0f);
// apply movement
if (translation != glm::vec3(0.0f)) {
const auto rotation = (position - pivot);
const float angle = atan2f(rotation.x, rotation.z);
const auto rotationMat = glm::rotate(MAT_IDENTITY, angle, fggl::math::UP);
auto deltaMove = (rotationMat * glm::vec4(translation, 1.0f)) * PAN_SPEED;
deltaMove.w = 0.0f;
position += deltaMove;
pivot += deltaMove;
}
// apply rotation
if (rotationValue != 0.0f) {
glm::mat4 rotation = glm::rotate(MAT_IDENTITY, rotationValue, fggl::math::UP);
position = (rotation * (position - pivot)) + pivot;
}
camTransform->origin(position);
camComp->target = pivot;
}
void process_edgescroll(fggl::ecs3::World &ecs, const Input &input, fggl::ecs::entity_t cam) {
glm::vec3 translation(0.0f);
auto &mouse = input.mouse;
// calculate movement (user input)
if (mouse.axis(MouseAxis::Y) < 0.9f) {
translation -= fggl::math::RIGHT;
}
if (mouse.axis(MouseAxis::Y) > -0.9f) {
translation += fggl::math::RIGHT;
}
if (mouse.axis(MouseAxis::X) > -0.9f) {
translation += fggl::math::FORWARD;
}
if (mouse.axis(MouseAxis::X) < 0.9f) {
translation -= fggl::math::FORWARD;
}
// apply rotation/movement
auto camTransform = ecs.get<fggl::math::Transform>(cam);
auto camComp = ecs.get<fggl::gfx::Camera>(cam);
glm::vec4 position(camTransform->origin(), 1.0f);
glm::vec4 pivot(camComp->target, 1.0f);
// apply movement
if (translation != glm::vec3(0.0f)) {
const auto rotation = (position - pivot);
const float angle = atan2f(rotation.x, rotation.z);
const auto rotationMat = glm::rotate(MAT_IDENTITY, angle, fggl::math::UP);
auto deltaMove = (rotationMat * glm::vec4(translation, 1.0f)) * PAN_SPEED;
deltaMove.w = 0.0f;
position += deltaMove;
pivot += deltaMove;
}
// move camera
camTransform->origin(position);
camComp->target = pivot;
}
void process_arcball(entity::EntityManager &ecs, const Input &input, entity::EntityID cam) {
// see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/
auto &camTransform = ecs.get<fggl::math::Transform>(cam);
auto &camComp = ecs.get<fggl::gfx::Camera>(cam);
const auto &mouse = input.mouse;
glm::vec4 position(camTransform.origin(), 1.0F);
glm::vec4 pivot(camComp.target, 1.0F);
glm::mat4 view = glm::lookAt(camTransform.origin(), camComp.target, camTransform.up());
glm::vec3 viewDir = -glm::transpose(view)[2];
glm::vec3 rightDir = glm::transpose(view)[0];
float deltaAngleX = (2 * M_PI);
float deltaAngleY = (M_PI);
float xAngle = (-mouse.axisDelta(fggl::input::MouseAxis::X)) * deltaAngleX;
float yAngle = (-mouse.axisDelta(fggl::input::MouseAxis::Y)) * deltaAngleY;
// rotate the camera around the pivot on the first axis
glm::mat4x4 rotationMatrixX(1.0F);
rotationMatrixX = glm::rotate(rotationMatrixX, xAngle, fggl::math::UP);
position = (rotationMatrixX * (position - pivot)) + pivot;
// rotate the camera around the pivot on the second axis
glm::mat4x4 rotationMatrixY(1.0F);
rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir);
glm::vec3 finalPos = (rotationMatrixY * (position - pivot)) + pivot;
camTransform.origin(finalPos);
}
void process_scroll(entity::EntityManager &ecs,
const Input &input,
entity::EntityID cam,
float minZoom,
float maxZoom) {
auto &camTransform = ecs.get<fggl::math::Transform>(cam);
auto &camComp = ecs.get<fggl::gfx::Camera>(cam);
const glm::vec3 dir = (camTransform.origin() - camComp.target);
const glm::vec3 forward = glm::normalize(dir);
glm::vec3 motion(0.0F);
float delta = input.mouse.axis(fggl::input::MouseAxis::SCROLL_Y);
if ((glm::length(dir) < maxZoom && delta < 0.0F) || (glm::length(dir) > minZoom && delta > 0.0F)) {
motion -= (forward * delta);
camTransform.origin(camTransform.origin() + motion);
}
}
void process_freecam(entity::EntityManager &ecs, const Input &input, entity::EntityID cam) {
float rotationValue = 0.0F;
glm::vec3 translation(0.0F);
const auto &keyboard = input.keyboard;
auto &settings = ecs.get<FreeCamKeys>(cam);
// calculate rotation (user input)
if (keyboard.down(settings.rotate_cw)) {
rotationValue = ROT_SPEED;
} else if (keyboard.down(settings.rotate_ccw)) {
rotationValue = -ROT_SPEED;
}
// calculate movement (user input)
if (keyboard.down(settings.forward)) {
translation -= fggl::math::RIGHT;
}
if (keyboard.down(settings.backward)) {
translation += fggl::math::RIGHT;
}
if (keyboard.down(settings.right)) {
translation += fggl::math::FORWARD;
}
if (keyboard.down(settings.left)) {
translation -= fggl::math::FORWARD;
}
// apply rotation/movement
auto camTransform = ecs.get<fggl::math::Transform>(cam);
auto camComp = ecs.get<fggl::gfx::Camera>(cam);
glm::vec4 position(camTransform.origin(), 1.0F);
glm::vec4 pivot(camComp.target, 1.0F);
// apply movement
if (translation != glm::vec3(0.0F)) {
const auto rotation = (position - pivot);
const float angle = atan2f(rotation.x, rotation.z);
const auto rotationMat = glm::rotate(MAT_IDENTITY, angle, fggl::math::UP);
auto deltaMove = (rotationMat * glm::vec4(translation, 1.0F)) * PAN_SPEED;
deltaMove.w = 0.0F;
position += deltaMove;
pivot += deltaMove;
}
// apply rotation
if (rotationValue != 0.0F) {
glm::mat4 rotation = glm::rotate(MAT_IDENTITY, rotationValue, fggl::math::UP);
position = (rotation * (position - pivot)) + pivot;
}
camTransform.origin(position);
camComp.target = pivot;
}
void process_edgescroll(entity::EntityManager &ecs, const Input &input, entity::EntityID cam) {
glm::vec3 translation(0.0F);
const auto &mouse = input.mouse;
// calculate movement (user input)
if (mouse.axis(MouseAxis::Y) < 0.9F) {
translation -= fggl::math::RIGHT;
}
if (mouse.axis(MouseAxis::Y) > -0.9F) {
translation += fggl::math::RIGHT;
}
if (mouse.axis(MouseAxis::X) > -0.9F) {
translation += fggl::math::FORWARD;
}
if (mouse.axis(MouseAxis::X) < 0.9F) {
translation -= fggl::math::FORWARD;
}
// apply rotation/movement
auto &camTransform = ecs.get<fggl::math::Transform>(cam);
auto &camComp = ecs.get<fggl::gfx::Camera>(cam);
glm::vec4 position(camTransform.origin(), 1.0F);
glm::vec4 pivot(camComp.target, 1.0F);
// apply movement
if (translation != glm::vec3(0.0F)) {
const auto rotation = (position - pivot);
const float angle = atan2f(rotation.x, rotation.z);
const auto rotationMat = glm::rotate(MAT_IDENTITY, angle, fggl::math::UP);
auto deltaMove = (rotationMat * glm::vec4(translation, 1.0F)) * PAN_SPEED;
deltaMove.w = 0.0F;
position += deltaMove;
pivot += deltaMove;
}
// move camera
camTransform.origin(position);
camComp.target = pivot;
}
}
/*
* 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/input/input.hpp>
namespace fggl::input {
......
/*
* 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/input/mouse.hpp>
namespace fggl::input {
void MouseState::operator=(const MouseState& rhs) {
for ( int i=0; i<4; i++ ) {
axis[i] = rhs.axis[i];
}
buttons = rhs.buttons;
}
void MouseState::operator=(const MouseState &rhs) {
for (int i = 0; i < 4; i++) {
axis[i] = rhs.axis[i];
}
buttons = rhs.buttons;
}
} // namespace fggl::input
# math
find_package(glm CONFIG REQUIRED)
target_link_libraries(fggl PUBLIC glm::glm)
target_sources(fggl
PRIVATE
shapes.cpp
triangulation.cpp
)
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
* This file is part of FGGL.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
* 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 this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* 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/math/shapes.hpp"
......@@ -25,21 +19,17 @@ namespace fggl::math::phs3d {
void AABB::emtpy() {
min = {FLT_MAX, FLT_MAX, FLT_MAX};
max = { - FLT_MAX, - FLT_MAX, -FLT_MAX};
max = {-FLT_MAX, -FLT_MAX, -FLT_MAX};
}
void AABB::add(const math::vec3 &p) {
if ( p.x < min.x ) min.x = p.x;
if ( p.x > max.x ) max.x = p.x;
if ( p.y < min.y ) min.y = p.y;
if ( p.y > min.y ) max.y = p.y;
if ( p.z < min.z ) min.z = p.z;
if ( p.z > max.z ) max.z = p.z;
void AABB::add(const math::vec3 &point) {
min = minElm(min, point);
max = maxElm(max, point);
}
AABB AABB::fromPoints(const std::vector<math::vec3> &points) {
auto AABB::fromPoints(const std::vector<math::vec3> &points) -> AABB {
AABB box;
for (const auto& point : points) {
for (const auto &point : points) {
box.add(point);
}
return box;
......@@ -50,9 +40,9 @@ namespace fggl::math::phs3d {
min = max = m[3]; // should be the translation component of the matrix
// this feels like something that should be vectorizable...
for ( int i = 0; i < 3; i++) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if ( m[i][j] > 0.0f ) {
if (m[i][j] > 0.0F) {
min[j] += m[i][j] * other.min[j];
max[j] += m[i][j] * other.max[j];
} else {
......@@ -63,7 +53,7 @@ namespace fggl::math::phs3d {
}
}
Plane Plane::fromPoints(const math::vec3 p1, const math::vec3 p2, const math::vec3 p3) {
auto Plane::fromPoints(const math::vec3 p1, const math::vec3 p2, const math::vec3 p3) -> Plane {
const auto e3 = p2 - p1;
const auto e1 = p3 - p2;
auto normal = glm::normalize(glm::cross(e3, e1));
......@@ -71,13 +61,13 @@ namespace fggl::math::phs3d {
return {normal, d};
}
static math::vec3 bestFitNormal(const std::vector<math::vec3>& points) {
static auto bestFitNormal(const std::vector<math::vec3> &points) -> math::vec3 {
assert(!points.empty());
math::vec3 result;
math::vec3 p = points.back();
for ( std::size_t i = 0; i < points.size(); ++i ){
for (std::size_t i = 0; i < points.size(); ++i) {
math::vec3 c = points[i];
result.x += (p.z + c.z) * (p.y - c.y);
......@@ -90,13 +80,13 @@ namespace fggl::math::phs3d {
return glm::normalize(result);
};
static float bestFitD(const std::vector<math::vec3>& points, glm::vec3 normal) {
static auto bestFitD(const std::vector<math::vec3> &points, glm::vec3 normal) -> float {
math::vec3 sum;
for (auto& point : points) {
for (const auto &point : points) {
sum += point;
}
sum *= 1.0F/points.size();
return glm::dot( sum, normal );
sum *= 1.0F / points.size();
return glm::dot(sum, normal);
}
const char X = 0;
......@@ -108,11 +98,11 @@ namespace fggl::math::phs3d {
const char b;
};
static bary_axis baryCalcAxis(const math::vec3& normal) {
if ( (fabs(normal.x) >= fabs(normal.y)) && (fabs(normal.x) >= fabs(normal.z))) {
static auto baryCalcAxis(const math::vec3 &normal) -> bary_axis {
if ((fabs(normal.x) >= fabs(normal.y)) && (fabs(normal.x) >= fabs(normal.z))) {
// discard x
return {Y, Z};
} else if ( fabs(normal.y) >= fabs(normal.z) ) {
} else if (fabs(normal.y) >= fabs(normal.z)) {
// discard y
return {Z, X};
} else {
......@@ -121,7 +111,7 @@ namespace fggl::math::phs3d {
}
}
bool Triangle::CartToBarycentric(const math::vec3& cart, Barycentric& outVal) {
auto Triangle::CartToBarycentric(const math::vec3 &cart, Barycentric &outVal) -> bool {
// everything is const because I'm paying the compiler is smarter than me...
const auto d1 = v[1] - v[0];
......@@ -143,20 +133,20 @@ namespace fggl::math::phs3d {
const float v3 = cart[ax.b] - v[0][ax.b];
const float v4 = cart[ax.b] - v[2][ax.b];
const float denom = v1*u2 - v2*u1;
if ( denom == 0.0f) {
const float denom = v1 * u2 - v2 * u1;
if (denom == 0.0F) {
return false;
}
// finally, we can work it out
const float oneOverDenom = 1.0f / denom;
outVal.b[0] = (v4*u2 - v2*u4) * oneOverDenom;
outVal.b[1] = (v1*u3 - v3*u1) * oneOverDenom;
outVal.b[2] = 1.0f - outVal.b[0] - outVal.b[1];
const float oneOverDenom = 1.0F / denom;
outVal.b[0] = (v4 * u2 - v2 * u4) * oneOverDenom;
outVal.b[1] = (v1 * u3 - v3 * u1) * oneOverDenom;
outVal.b[2] = 1.0F - outVal.b[0] - outVal.b[1];
return true;
}
bool Triangle::CartToBarycentric2(const math::vec3& cart, Barycentric& outVal) {
auto Triangle::CartToBarycentric2(const math::vec3 &cart, Barycentric &outVal) -> bool {
const auto e1 = v[2] - v[1];
const auto e2 = v[0] - v[2];
const auto e3 = v[1] - v[0];
......@@ -167,7 +157,7 @@ namespace fggl::math::phs3d {
const auto normal = glm::normalize(glm::cross(e1, e2));
const auto denom = glm::dot(glm::cross(e1, e2), normal);
assert( denom != 0.0f);
assert(denom != 0.0F);
outVal.b[0] = glm::dot(glm::cross(e1, d3), normal) / denom;
outVal.b[1] = glm::dot(glm::cross(e2, d1), normal) / denom;
......