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 2857 additions and 0 deletions
/**
* OpenGL RedBook Shader.
* Examples 7.8, 7.9 and 7.10.
*/
#version 330 core
struct LightProperties {
bool isEnabled;
bool isLocal;
bool isSpot;
vec3 ambient;
vec3 colour;
vec3 position;
vec3 halfVector;
vec3 coneDirection;
float spotCosCustoff;
float spotExponent;
float constantAttenuation;
float linearAttenuation;
float quadraticAttenuation;
};
// example 7.9
struct MaterialProperties {
vec3 emission;
vec3 ambient;
vec3 diffuse;
vec3 specular;
float shininess;
};
const int MaxLights = 8;
uniform LightProperties lights[MaxLights];
const int MaxMaterials = 1;
uniform MaterialProperties materials[MaxMaterials];
uniform float Strength;
uniform vec3 EyeDirection;
in vec4 Position;
in vec3 Normal;
in vec4 Colour;
flat in int MatIndex;
out vec4 FragColour;
void main() {
vec3 scatteredLight = vec3(0.0);
vec3 reflectedLight = vec3(0.0);
for(int light = 0; light < MaxLights; ++light) {
if ( !lights[light].isEnabled ) continue;
vec3 halfVector;
vec3 lightDirection = lights[light].position;
float attenuation = 1.0;
float specular = 0;
if ( lights[light].isLocal ) {
lightDirection = lightDirection - vec3(Position);
float lightDistance = length(lightDirection);
lightDirection = lightDirection / lightDistance;
attenuation = 1.0 /
lights[light].constantAttenuation +
lights[light].linearAttenuation * lightDistance +
lights[light].quadraticAttenuation * lightDistance * lightDistance;
if ( lights[light].isSpot ) {
float spotCos = dot(lightDirection, -lights[light].coneDirection);
if ( spotCos < lights[light].spotCosCustoff )
attenuation = 0.0;
else
attenuation *= pow(spotCos, lights[light].spotExponent);
}
halfVector = normalize(lightDirection + EyeDirection);
specular = max(0.0, dot(Normal, halfVector));
} else {
//halfVector = lights[light].halfVector;
halfVector = reflect(-lightDirection, Normal);
specular = max(0.0, dot(EyeDirection, halfVector));
}
float diffuse = max(0.0, dot(Normal, lightDirection));
if (diffuse == 0.0)
specular = 0.0;
else
specular = pow(specular, materials[MatIndex].shininess) * Strength;
scatteredLight += lights[light].ambient * materials[MatIndex].ambient * attenuation +
lights[light].colour * materials[MatIndex].diffuse * diffuse * attenuation;
reflectedLight += lights[light].colour * materials[MatIndex].specular *
specular * attenuation;
}
vec3 rgb = min( materials[MatIndex].emission + Colour.rgb * scatteredLight + reflectedLight, vec3(1.0));
FragColour = vec4(rgb, 1.0);
}
\ No newline at end of file
/**
* OpenGL RedBook Shader.
* Example 7.8
*/
#version 330 core
uniform mat4 MVPMatrix;
uniform mat4 MVMatrix;
uniform mat3 NormalMatrix;
layout (location = 0) in vec3 VertexPosition;
layout (location = 1) in vec3 VertexNormal;
layout (location = 2) in vec3 VertexColour;
out vec4 Position;
out vec3 Normal;
out vec4 Colour;
out int matIndex;
void main() {
Colour = vec4(1.0, 1.0, 1.0, 1.0f);
Normal = NormalMatrix * VertexNormal;
Position = MVMatrix * vec4(VertexPosition, 1.0);
matIndex = 0;
gl_Position = MVPMatrix * vec4(VertexPosition, 1.0);
}
\ No newline at end of file
print("File has been loaded!")
-- when the scene loads, switch to topdown to show state integration
--switch_scene(state, "topdown");
\ No newline at end of file
---
prefabs:
- name: "rb_environment"
components:
gfx::material:
ambient: [0.0215, 0.1754, 0.0215]
diffuse: [1, 1, 1]
specular: [0.0633, 0.727811, 0.633]
shininess: 16
- name: "rb_wallX"
parent: "rb_environment"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: "mesh_rb_wall_x"
shape:
type: box
scale: [1.0, 5.0, 41]
phys::Body:
type: static
shape:
type: box
extents: [0.5, 2.5, 20.5]
# Wall Z shorter to avoid z-fighting
- name: "rb_wallZ"
parent: "rb_environment"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: "mesh_rb_wall_z"
shape:
type: box
scale: [39, 5, 1]
phys::Body:
type: static
shape:
type: box
extents: [ 19.5, 2.5, 0.5 ]
- name: "rb_floor"
parent: "rb_environment"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: "mesh_rb_floor"
shape:
type: box # we don't (currently) support planes...
scale: [39, 0.5, 39]
phys::Body:
type: static
shape:
type: box # we don't (currently) support planes...
extents: [19.5, 0.25, 19.5]
- name: rb_player
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: "mesh_rb_player"
shape:
type: sphere
gfx::material:
ambient: [0.25, 0.25, 0.25]
diffuse: [0.4, 0.4, 0.4]
specular: [0.774597,0.774597,0.774597]
shininess: 16
phys::Body:
shape:
type: sphere
radius: 1
- name: rb_collectable
tags:
- "collectable"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: "mesh_rb_collect"
shape:
type: box
gfx::material:
ambient: [0.0215, 0.1754, 0.0215]
diffuse: [1, 1, 1]
specular: [0.0633, 0.727811, 0.633]
shininess: 16
phys::Body:
type: kinematic
shape:
type: box
- name: rb_light
components:
Transform:
gfx::phong::directional:
direction: [10, 5, 0]
scene:
- prefab: rb_wallX
components:
Transform:
origin: [20, 0, 0]
- prefab: rb_wallX
components:
Transform:
origin: [-20, 0, 0]
- prefab: rb_wallZ
components:
Transform:
origin: [0, 0, -20]
- prefab: rb_wallZ
components:
Transform:
origin: [0, 0, 20]
- prefab: rb_floor
components:
Transform:
origin: [0, -2.5, 0]
- prefab: rb_collectable
components:
Transform:
origin: [-5, -0.5, 12]
- prefab: rb_collectable
components:
Transform:
origin: [15, -0.5, 0.5]
- prefab: rb_collectable
components:
Transform:
origin: [6, -0.5, -15]
- prefab: rb_player
name: "player"
- prefab: rb_light
scripts:
- "rollball.lua"
\ No newline at end of file
#version 330 core
uniform sampler2D tex;
in vec3 colour;
in vec2 texPos;
out vec4 FragColor;
void main()
{
FragColor = vec4(colour.xyz, texture(tex, texPos).r);
}
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColour;
layout (location = 2) in vec2 aTexPos;
uniform mat4 projection;
out vec3 colour;
out vec2 texPos;
void main()
{
gl_Position = projection * vec4(aPos, 0.0f, 1.0f);
colour = aColour;
texPos = aTexPos;
}
---
floors:
ground:
visible: true
walls:
none:
visible: false
solid:
visible: true
\ No newline at end of file
---
prefabs:
- name: "wallX"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: td_wall_x
shape:
type: box
scale: [1.0, 5.0, 41]
gfx::material:
ambient: [0.25, 0.25, 0.25]
diffuse: [0.4, 0.4, 0.4]
specular: [0.774597,0.774597,0.774597]
shininess: 0.6
# phys::Body:
# type: static
# shape:
# type: box
# extents: [0.5, 2.5, 20.5]
# Wall Z shorter to avoid z-fighting
- name: "wallZ"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: td_wall_y
shape:
type: box
scale: [39, 5, 1]
gfx::material:
ambient: [0.25, 0.25, 0.25]
diffuse: [0.4, 0.4, 0.4]
specular: [0.774597,0.774597,0.774597]
shininess: 0.6
# phys::Body:
# type: static
# shape:
# type: box
# extents: [ 19.5, 2.5, 0.5 ]
- name: "floor"
components:
Transform:
StaticMesh:
pipeline: redbook/debug
shape_id: td_floor
shape:
type: box # we don't (currently) support planes...
scale: [39, 0.5, 39]
gfx::material:
ambient: [0.25, 0.25, 0.25]
diffuse: [0.4, 0.4, 0.4]
specular: [0.774597,0.774597,0.774597]
shininess: 0.6
# phys::Body:
# type: static
# shape:
# type: box # we don't (currently) support planes...
# extents: [19.5, 0.25, 19.5]
- name: player
components:
Transform:
StaticMesh:
pipeline: redbook/lighting
shape_id: td_player
shape:
type: sphere
gfx::material:
ambient: [0.25, 0.25, 0.25]
diffuse: [0.4, 0.4, 0.4]
specular: [0.774597,0.774597,0.774597]
shininess: 0.6
# phys::Body:
# shape:
# type: sphere
# radius: 1
- name: collectable
components:
Transform:
StaticMesh:
pipeline: redbook/lighting
shape_id: td_collect
shape:
type: box
gfx::material:
ambient: [0.0215, 0.1754, 0.0215]
diffuse: [1, 1, 1]
specular: [0.0633, 0.727811, 0.633]
shininess: 0.6
# phys::Body:
# type: kinematic
# shape:
# type: box
# phys::Callbacks:
# phys::Cache:
\ No newline at end of file
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//
// Created by webpigeon on 22/04/22.
//
#include "GameScene.h"
#include "fggl/entity/loader/loader.hpp"
#include "fggl/mesh/components.hpp"
camera_type cam_mode = cam_free;
static void placeObject(fggl::entity::EntityManager& world, fggl::entity::EntityID floor, fggl::entity::EntityFactory* factory, fggl::util::GUID prototype, glm::vec3 targetPos) {
#ifndef NDEBUG
fggl::debug::trace("Creating object from prototype: {}", fggl::util::guid_to_string(prototype));
#endif
auto obj = factory->create(prototype, world);
auto& result = world.get<fggl::math::Transform>(obj);
int xPos = (int)targetPos.x;
int zPos = (int)targetPos.z * -1;
// figure out the floor height
auto heightMap = world.get<fggl::data::HeightMap>(floor);
targetPos.y = heightMap.getValue(xPos, zPos); // TODO should really be the gradient at the required point
result.origin( targetPos );
}
static void process_camera(fggl::entity::EntityManager& ecs, const fggl::input::Input& input) {
auto cameras = ecs.find<fggl::gfx::Camera>();
auto cam = cameras[0];
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 );
// scroll wheel
glm::vec3 motion(0.0f);
float delta = input.mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
motion -= (forward * delta);
camTransform.origin( camTransform.origin() + motion );
if ( cam_mode == cam_arcball || input.mouse.down( fggl::input::MouseButton::MIDDLE ) ) {
fggl::input::process_arcball(ecs, input, cam);
} else if ( cam_mode == cam_free ) {
fggl::input::process_freecam(ecs, input, cam);
}
fggl::input::process_edgescroll( ecs, input, cam );
}
static void setupCamera(fggl::entity::EntityManager& world) {
auto prototype = world.create();
// setup camera position/transform
auto& transform = world.add<fggl::math::Transform>(prototype);
transform.origin(glm::vec3(10.0f, 3.0f, 10.0f));
// setup camera components
world.add<fggl::gfx::Camera>(prototype);
auto& cameraKeys = world.add<fggl::input::FreeCamKeys>(prototype);
cameraKeys.forward = glfwGetKeyScancode(GLFW_KEY_W);
cameraKeys.backward = glfwGetKeyScancode(GLFW_KEY_S);
cameraKeys.left = glfwGetKeyScancode(GLFW_KEY_A);
cameraKeys.right = glfwGetKeyScancode(GLFW_KEY_D);
cameraKeys.rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
cameraKeys.rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
}
static fggl::entity::EntityID setupTerrain(fggl::entity::EntityManager& world) {
fggl::entity::EntityID terrain;
{
terrain = world.create();
auto& camTf = world.add<fggl::math::Transform>(terrain);
camTf.origin( glm::vec3(-128.0f, 0.0f, 128.0f) );
//auto terrainData = m_world.get<fggl::data::HeightMap>(terrain);
fggl::data::HeightMap terrainData{};
terrainData.clear();
const siv::PerlinNoise::seed_type seed = 123456U;
const siv::PerlinNoise perlin{ seed };
for (std::size_t z = 0; z < fggl::data::heightMaxZ; ++z) {
for (std::size_t x = 0; x < fggl::data::heightMaxX; ++x) {
const double noise = perlin.octave2D_11( (x * 0.01), (z * 0.01) , 4) * 10.f;
terrainData.setValue(x, z, (float)noise);
}
}
world.add<fggl::data::HeightMap>(terrain, terrainData);
}
return terrain;
}
static fggl::entity::EntityID setup_environment(fggl::entity::EntityManager& world) {
setupCamera(world);
return setupTerrain(world);
}
static auto BUNKER_PROTOTYPE = "bunker"_fid;
static void setupBunkerPrototype(fggl::entity::EntityFactory* factory) {
{
auto bunkerSpec = fggl::entity::EntitySpec{};
bunkerSpec.addComp(fggl::math::Transform::guid, {});
// mesh
int nSections = 2;
fggl::mesh::MultiMesh3D mesh;
for (int j=-(nSections/2); j<=nSections/2; j++) {
const auto shapeOffset = glm::vec3( 0.0f, 0.5f, (float)j * 1.0f );
const auto cubeMat = glm::translate( fggl::math::mat4( 1.0f ) , shapeOffset );
const auto leftSlope = fggl::math::modelMatrix(
glm::vec3(-1.0f, 0.0f, 0.0f) + shapeOffset,
glm::vec3( 0.0f, -fggl::math::HALF_PI, 0.0f) );
const auto rightSlope = fggl::math::modelMatrix(
glm::vec3( 1.0f, 0.0f, 0.0f) + shapeOffset,
glm::vec3( 0.0f, fggl::math::HALF_PI, 0.0f) );
fggl::data::make_cube( mesh.generate(), cubeMat);
fggl::data::make_slope( mesh.generate(), leftSlope );
fggl::data::make_slope( mesh.generate(), rightSlope );
}
//mesh.removeDups();
// generate mesh component data
// FIXME: find a better way to do this, avoid re-uploading the whole mesh.
fggl::entity::ComponentSpec procMesh{};
procMesh.set<std::string>("pipeline", "redbook/debug");
YAML::Node modelNode;
for (auto& submesh : mesh.meshes) {
YAML::Node vertexData;
for (auto& vertex : submesh.data) {
YAML::Node vertexNode;
vertexNode["position"] = vertex.position;
vertexNode["normal"] = vertex.normal;
vertexNode["colour"] = vertex.colour;
vertexNode["texPos"] = vertex.texPos;
vertexData.push_back(vertexNode);
}
YAML::Node indexData;
for (auto& index : submesh.indices) {
indexData.push_back(index);
}
YAML::Node meshNode;
meshNode["vertex"] = vertexData;
meshNode["index"] = indexData;
modelNode.push_back( meshNode );
}
procMesh.set("model", modelNode);
bunkerSpec.addComp(fggl::mesh::StaticMultiMesh3D::guid, procMesh);
factory->define(BUNKER_PROTOTYPE, bunkerSpec);
}
}
void GameScene::setup() {
m_canvas.size( fggl::math::vec2(0,0), fggl::math::vec2(100, 100));
auto* entityFactory = m_owner.service<fggl::entity::EntityFactory>();
auto terrain = setup_environment(world());
setupBunkerPrototype(entityFactory);
// create building prototype
int nCubes = 3;
for ( int i=0; i<nCubes; i++ ) {
glm::vec3 location;
location.x = i * 6.f + 1.0f;
location.z = -5.0f + 1.0f;
placeObject(world(), terrain, entityFactory, BUNKER_PROTOTYPE, location);
}
}
void GameScene::update(float dt) {
Game::update(dt);
process_camera(world(), input());
}
void GameScene::render(fggl::gfx::Graphics &gfx) {
Game::render(gfx);
const fggl::math::vec2 panelSize { 250.0F, 250.0F };
const auto canvasY = gfx.canvasBounds().bottom - panelSize.y;
m_canvas.size( {0.0F, canvasY}, panelSize);
// now the 2D scene
fggl::gfx::Paint paint;
m_canvas.render(paint);
gfx.draw2D(paint);
}
\ 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 27/08/22.
//
#include "grid.hpp"
#include "fggl/assets/loader.hpp"
#include "fggl/entity/gridworld/zone.hpp"
#include "fggl/gui/model/parser.hpp"
#include "fggl/gui/renderer/renderer.hpp"
using namespace fggl::gfx::colours;
namespace demo {
using namespace fggl::entity::grid;
static void build_tileset(TileSet& tiles) {
tiles.m_floors.push_back(FloorTile{FloorTile::IMPOSSIBLE, BLACK});
tiles.m_floors.push_back(FloorTile{1, GREEN});
tiles.m_floors.push_back(FloorTile{1, YELLOW_GREEN});
fggl::entity::grid::WallTile noWall{};
tiles.m_walls.push_back(noWall);
fggl::entity::grid::WallTile solidWall{
.render = true,
.colour = DARK_SLATE_GRAY
};
tiles.m_walls.push_back(solidWall);
}
static void build_room(DemoGrid* area, fggl::math::vec2i center, fggl::math::vec2i size) {
for (int yOffset = -size.y; yOffset <= size.y; ++yOffset) {
auto yPos = yOffset + center.y;
area->setWallAt(center.x - size.x, yPos, false, 1);
area->setWallAt(center.x + size.x + 1, yPos, false, 1);
}
for (int xOffset = -size.x; xOffset <= size.x; ++xOffset) {
auto xPos = center.x + xOffset;
area->setWallAt(xPos, center.y - size.y, true, 1);
area->setWallAt(xPos, center.y + size.y + 1, true, 1);
for (int yOffset = -size.y; yOffset <= size.y; ++yOffset) {
auto yPos = yOffset + center.y;
area->setFloorAt(xPos, yPos, 1);
}
}
}
void build_doorway(DemoGrid* area, fggl::math::vec2i position, bool north, int size = 1) {
for ( auto offset = 0; offset < size; offset++) {
if ( north ) {
area->setWallAt(position.x + offset, position.y, north, 0);
} else {
area->setWallAt(position.x, position.y + offset, north, 0);
}
}
}
static fggl::entity::EntityID build_test_env(DemoGrid* area, LevelRules& rules) {
area->clear();
build_room(area, {5, 5}, {4,4});
build_room(area, {11, 5}, {1,1});
build_room(area, {17, 5}, {4,4});
build_doorway(area, {10, 5}, false, 1);
build_doorway(area, {13, 5}, false, 1);
// set goal
area->setFloorAt(17, 5, 2);
// level rules
rules.startingPower = 20;
rules.startingPos = {5, 5};
rules.startingDirection = 1;
// player
fggl::entity::EntityID player = fggl::entity::INVALID;
auto& manager = area->entities();
{
player = manager.create();
manager.add<CellPos>(player);
manager.add<RobotState>(player);
}
return player;
}
struct Action {
const char* name;
std::function<void(void)> callback;
};
GridScene::GridScene(fggl::App &app) : GameBase(app), m_tiles(), m_animator(15.0F), m_grid(nullptr), m_canvas() {
m_animator.add([this](){this->tickPlayer();});
auto btnGrid = std::make_unique<fggl::gui::GridBox>(0, 2);
std::array<Action, 4> actions{{
{"<", [this]() { this->rotate(true); }},
{">", [this]() { this->rotate(false); }},
{"^", [this]() { this->forward(); }},
{"Z", [this]() { } }
}};
fggl::math::vec2i pos{0, 0};
for (auto& action : actions) {
fggl::math::vec2i size{32, 32};
auto btn = std::make_unique<fggl::gui::Button>(pos, size);
btn->label(action.name);
btn->addCallback([=, this](){
this->m_program.m_instructions.push_back({action.name, action.callback});
});
btnGrid->add(std::move(btn));
}
// control buttons
{
fggl::math::vec2i size{64, 32};
auto btn = std::make_unique<fggl::gui::Button>(pos, size);
btn->label("go");
btn->addCallback([this](){
if ( !this->m_program.playing ) {
resetPuzzle();
this->m_program.m_currInstruction = 0;
this->m_program.playing = true;
}
});
btnGrid->add(std::move(btn));
}
{
fggl::math::vec2i size{64, 64};
auto btn = std::make_unique<fggl::gui::Button>(pos, size);
btn->label("Del");
btn->addCallback([this](){
if ( !this->m_program.playing ) {
if ( !m_program.m_instructions.empty() ) {
m_program.m_instructions.pop_back();
}
}
});
btnGrid->add(std::move(btn));
}
btnGrid->layout();
m_canvas.add(std::move(btnGrid));
// create a timeline panel
std::unique_ptr<robot::Timeline> timeline = std::make_unique<robot::Timeline>(m_program);
timeline->size({50,700}, {250, 250});
m_canvas.add(std::move(timeline));
m_canvas.layout();
}
void GridScene::activate() {
GameBase::activate();
fggl::debug::log(fggl::debug::Level::info, "GridScene::activate()");
m_animator.reset();
// fake loading the tileset
if ( m_tiles.m_floors.empty() ) {
build_tileset(m_tiles);
}
// create the grid world
m_grid = std::make_unique<DemoGrid>(m_tiles);
m_player = build_test_env(m_grid.get(), m_levelRules);
resetPuzzle();
}
void GridScene::deactivate() {
m_grid = nullptr;
}
constexpr float DRAW_SIZE = 64.0F;
constexpr float DRAW_HALF = DRAW_SIZE / 2.0F;
constexpr float WALL_HALF = 2.5F;
static void render_grid(fggl::gfx::Paint& paint, fggl::entity::grid::Area2D<255, 255>& grid, fggl::App& app) {
const fggl::math::vec2 wallOffsetNorth {0, -DRAW_HALF};
const fggl::math::vec2 wallOffsetWest {-DRAW_HALF, 0};
for (int i=0; i <= 31; ++i) {
for (int j=0; j <= 31; ++j) {
const fggl::math::vec2 drawPos{i * DRAW_SIZE, j*DRAW_SIZE};
// draw floor
{
auto &floor = grid.floorAt(i, j);
fggl::gfx::Path2D tileGfx = fggl::gfx::make_rect(drawPos, {DRAW_HALF, DRAW_HALF}, floor.colour);
paint.fill(tileGfx);
}
auto& wallNorth = grid.wallAt(i, j, true);
if ( wallNorth.render ) {
fggl::gfx::Path2D tileGfx = fggl::gfx::make_rect(drawPos + wallOffsetNorth, {DRAW_HALF, WALL_HALF}, wallNorth.colour);
paint.fill(tileGfx);
}
auto& wallWest = grid.wallAt(i, j, false);
if ( wallWest.render ) {
fggl::gfx::Path2D tileGfx = fggl::gfx::make_rect(drawPos + wallOffsetWest, {WALL_HALF, DRAW_HALF}, wallWest.colour);
paint.fill(tileGfx);
}
}
}
// UI test
auto widgetFactory = app.service<fggl::gui::model::WidgetFactory>();
auto widget = widgetFactory->buildEmpty();
widget->set("position", fggl::math::vec2{200.0F, 100.F});
widget->set("size", fggl::math::vec2{500.0F, 300.F});
widget->set("colour", fggl::gfx::colours::BLANCHED_ALMOND);
fggl::gui::model::attr_box_set(*widget, "padding", 5.0F);
auto handle = widgetFactory->buildEmpty();
handle->set("border::bottom",5.0F);
handle->set("position", fggl::math::vec2{0.0F, 0.0F});
//handle->set("size", fggl::math::vec2{INFINITY, 50.0F});
handle->set("text", "hello, world!");
handle->set("colour", fggl::gfx::colours::ORANGE);
fggl::gui::model::attr_box_set(*handle, "padding", 5.0F);
widget->addChild(*handle);
delete handle;
auto content = widgetFactory->buildEmpty();
content->set("position", fggl::math::vec2{0.0F, 50.0F});
//content->set("size", fggl::gui::model::UNDEFINED_SIZE);
content->set("colour", fggl::gfx::colours::BURLYWOOD);
widget->addChild(*content);
delete content;
fggl::gui::renderer::layout(*widget);
fggl::gui::renderer::visit(*widget, paint);
}
static void render_objects(fggl::gfx::Paint& paint, DemoGrid& grid) {
auto& manager = grid.entities();
auto entities = manager.find<CellPos>();
for (const auto& entity : entities ) {
//auto& sprite = manager.get<Sprite>(entity);
auto& cellPos = manager.get<CellPos>(entity);
// convert grid pos to world pos
fggl::math::vec2f drawPos = cellPos.pos;
drawPos *= DRAW_SIZE;
drawPos += cellPos.drawOffset;
fggl::gfx::ShapeOpts opts;
opts.angleOffset = (float)cellPos.direction * fggl::math::HALF_PI + cellPos.rotationOffset;
auto shape = fggl::gfx::make_shape(drawPos, DRAW_HALF, 3, NAVAJO_WHITE, opts);
paint.fill(shape);
}
}
static void update_canvas(fggl::input::Input& inputs, fggl::gui::Container& canvas) {
fggl::math::vec2f cursorPos {
inputs.mouse.axis(fggl::input::MouseAxis::X),
inputs.mouse.axis(fggl::input::MouseAxis::Y)
};
// in canvas space
fggl::math::vec2 projected {
fggl::math::rescale_ndc(cursorPos.x, 0, 1920.f),
fggl::math::rescale_ndc(cursorPos.y, 0, 1080.0f)
};
canvas.onMouseOver(projected);
// detect clicks
if (inputs.mouse.pressed(fggl::input::MouseButton::LEFT)) {
auto* widget = canvas.getChildAt(projected);
if (widget != nullptr) {
fggl::debug::info("Button clicked");
widget->activate();
}
}
}
void GridScene::update(float deltaTime) {
GameBase::update(deltaTime);
m_animator.update(deltaTime);
m_canvas.update(deltaTime);
update_canvas(input(), m_canvas);
}
void GridScene::tickPlayer() {
if ( !m_program.playing ){
return;
}
if ( m_program.m_currInstruction < m_program.m_instructions.size() ) {
auto& robotState = m_grid->entities().get<RobotState>(m_player);
if (robotState.power == 0) {
// a looser is you
resetPuzzle();
return;
}
robotState.power--;
m_program.step();
} else {
m_program.stop();
checkVictory();
}
}
void GridScene::resetPuzzle() {
// reset instruction panel
m_program.stop();
// reset robot state
auto& robotPos = m_grid->entities().get<CellPos>(m_player);
auto& robotState = m_grid->entities().get<RobotState>(m_player);
robotPos.pos = m_levelRules.startingPos;
robotPos.direction = m_levelRules.startingDirection;
robotState.power = m_levelRules.startingPower;
}
void GridScene::checkVictory() {
if ( !m_program.playing ) {
auto& botPos = m_grid->entities().get<CellPos>(m_player).pos;
auto gridCell = m_grid->floorAt(botPos.x, botPos.y);
if ( gridCell.colour == YELLOW_GREEN ) {
// a winner is you!
returnToMenu();
}
}
}
void GridScene::render(fggl::gfx::Graphics &gfx) {
fggl::gfx::Paint paint;
render_grid(paint, *m_grid, owner());
render_objects(paint, *m_grid);
m_canvas.render(paint);
gfx.draw2D(paint);
}
} // namespace demo
/*
* 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 "hexboard/scene.hpp"
namespace demo::hexboard {
constexpr float SCROLL_SPEED = 0.01F;
constexpr float HEX_SIZE = 64.0F;
constexpr std::array<fggl::grid::IntHex, 4> ISLAND_CENTERS {{
{2, 3},
{6, 7},
{9, 10},
{6, 3}
}};
Scene::Scene(fggl::App &app) : GameBase(app), m_board(nullptr), m_screen(1920.F, 1080.F) {
}
void Scene::activate() {
m_board = std::make_unique<fggl::grid::HexGrid>();
m_layout = std::make_unique<fggl::grid::Layout>( fggl::grid::Orientation::make_pointy(), HEX_SIZE );
//m_layout->m_origin = { 1920.0F * -0.5F, 1080.0F * -0.5F};
m_selections = std::make_unique<SelectionModel>();
const fggl::grid::TerrainType grass{
.data = std::make_shared<fggl::grid::MaterialData>()
};
grass.data->name = "grass";
grass.data->colour = {0.0F, 1.0F, 0.0};
for (auto islandPoint : ISLAND_CENTERS){
auto island = islandPoint.hexesInRange(2);
for (auto &hex : island) {
m_board->setTerrain(hex, grass);
}
}
m_camera = std::make_unique<Camera2D>();
}
void Scene::deactivate() {
m_board = nullptr;
m_selections = nullptr;
m_layout = nullptr;
}
void Scene::update(float delta) {
GameBase::update(delta);
// if the board is not set, abort
if ( m_board == nullptr ){
return;
}
// check if a button was pressed
auto& input = this->input();
{
const auto mouseNdc = fggl::input::mouse_axis(input.mouse);
const auto screenPos = ndc_to_screen(mouseNdc, m_screen);
// calculate what the user clicked on
auto worldPx = m_camera->unproject(screenPos);
m_selections->hover = fggl::grid::round2( m_layout->toGrid(worldPx) );
if (input.mouse.pressed(fggl::input::MouseButton::LEFT)) {
m_selections->selected = m_selections->hover;
m_camera->moveBy(mouseNdc * (m_screen * 0.5F) );
}
if ( input.mouse.down(fggl::input::MouseButton::RIGHT) ) {
if (input.mouse.pressed( fggl::input::MouseButton::RIGHT )) {
m_dragging = screenPos;
}
auto offset = screenPos - m_dragging.value();
m_camera->teleportBy(offset * SCROLL_SPEED);
} else if ( input.mouse.released(fggl::input::MouseButton::RIGHT) ) {
m_dragging = {};
}
}
m_camera->update(delta);
// flip y, because reasons
//auto offset = m_camera->getFocusLocation();
//m_layout->m_origin = -offset;
}
void Scene::drawGrid(fggl::gfx::Paint& paint) {
auto tiles = m_board->getAllTiles();
for ( const auto& tile : tiles ) {
auto terrain = m_board->getTerrain(tile);
if ( terrain.has_value() ) {
const auto& terrainData = terrain.value();
m_layout->paintHex(paint, tile, terrainData.colour, m_camera->getFocusLocation());
}
}
}
void Scene::drawSelections(fggl::gfx::Paint& paint) {
if ( m_selections == nullptr || m_dragging.has_value() ) {
return;
}
if ( m_selections->selected.has_value() ) {
m_layout->paintHex( paint, m_selections->selected.value(), fggl::gfx::colours::YELLOW, m_camera->getFocusLocation());
}
if ( m_selections->hover.has_value() ) {
m_layout->paintHex( paint, m_selections->hover.value(), fggl::gfx::colours::BLANCHED_ALMOND, m_camera->getFocusLocation());
}
}
void Scene::render(fggl::gfx::Graphics &gfx) {
// if the board is not set, abort
if ( m_board == nullptr ){
return;
}
// draw the grid
// FIXME don't hard-code the screen size
fggl::gfx::Paint paint;
drawGrid(paint);
drawSelections(paint);
gfx.draw2D(paint);
}
} // namespace demo::hexboard
\ 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 02/01/23.
//
#include "hexboard/camera.hpp"
#include "fggl/math/fmath.hpp"
#include <cmath>
namespace demo::hexboard {
void Camera2D::update(float delta) {
m_location = fggl::math::smooth_add( m_location, m_target, m_scale );
if ( m_trauma > 0 ) {
m_trauma = std::max( m_trauma - TRAUMA_DECAY, 0.0F );
}
}
}
\ No newline at end of file
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <filesystem>
#include <iostream>
#if __has_include("fggl/phys/bullet/bullet.hpp")
#include "fggl/phys/bullet/bullet.hpp"
#else
#include "fggl/phys/null.hpp"
#endif
#include "fggl/fggl.hpp"
#include "fggl/entity/module.hpp"
#include "fggl/audio/openal/audio.hpp"
#include "fggl/display/glfw/window.hpp"
#include "fggl/scenes/menu.hpp"
#include "fggl/modules/manager.hpp"
#include "fggl/data/assimp/module.hpp"
#include "fggl/assets/module.hpp"
#include "fggl/assets/packed/module.hpp"
#if __has_include("fggl/script/lua/module.hpp")
#include "fggl/script/lua/module.hpp"
#endif
#include "GameScene.h"
#include "rollball.hpp"
#include "topdown.hpp"
#include "grid.hpp"
#include "models/viewer.hpp"
#include "hexboard/scene.hpp"
static void setup_menu(fggl::App& app) {
auto *menu = app.addState<fggl::scenes::BasicMenu>("menu");
// add some menu items for the game states
const std::array labels = {"terrain", "rollball", "Top Down", "Grid World", "Viewer", "gridworld"};
const std::array scenes = {"game", "rollball", "topdown", "gridworld", "viewer", "hexboard"};
for (std::size_t i = 0; i < labels.size(); ++i) {
std::string sceneName = scenes.at(i);
menu->add(labels.at(i), [&app, sceneName]() {
auto* audio = app.service<fggl::audio::AudioService>();
audio->play("ui/click.ogg", false);
app.change_state(sceneName);
});
}
menu->add("quit", [&app]() {
//auto* audio = app.service<fggl::audio::AudioService>();
//audio->play("click.ogg", false);
app.running(false);
});
}
int main(int argc, const char* argv[]) {
// DAG initialisation test
fggl::modules::Manager moduleManager;
moduleManager.use<fggl::input::Generic>();
moduleManager.use<fggl::data::LocalStorage>();
moduleManager.use<fggl::gui::FreeType>();
moduleManager.use<fggl::audio::OpenAL>();
moduleManager.use<fggl::gfx::OpenGL4>();
moduleManager.use<fggl::display::GLFW>();
moduleManager.use<fggl::assets::AssetFolders>();
moduleManager.use<fggl::assets::PackedAssets>();
moduleManager.use<fggl::entity::ECS>();
#ifdef FGGL_HAS_LUA
moduleManager.use<fggl::script::Lua>();
#endif
// debug/testing use
moduleManager.use<fggl::data::AssimpLoader>();
#ifdef FGGL_MODULE_BULLET
moduleManager.use<fggl::phys::Bullet3>();
#else
moduleManager.use<fggl::phys::NullPhysics>();
#endif
moduleManager.resolve();
// create the application
fggl::App app( &moduleManager, "fggl-demo" );
// force asset loading
{
auto* assetFinder = app.service<fggl::assets::CheckinAdapted>();
assetFinder->discover("core");
auto* assets = app.service<fggl::assets::AssetManager>();
auto* loader = app.service<fggl::assets::Loader>();
loader->load("ui/click.ogg", fggl::audio::ASSET_CLIP_SHORT, assets);
}
auto* windowing = app.service<fggl::display::WindowService>();
// make a window for our application
auto* window = windowing->create();
window->setTitle( "fggl-demo" );
window->setFullscreen( true );
app.setWindow(window);
// our test states
setup_menu(app);
app.addState<GameScene>("game");
app.addState<demo::RollBall>("rollball");
app.addState<demo::TopDown>("topdown");
app.addState<demo::GridScene>("gridworld");
app.addState<demo::Viewer>("viewer");
app.addState<demo::hexboard::Scene>("hexboard");
return app.run(argc, argv);
}
/*
* 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 "models/viewer.hpp"
#include "fggl/assets/module.hpp"
#include "fggl/data/assimp/module.hpp"
#include "fggl/mesh/mesh.hpp"
#include "fggl/mesh/components.hpp"
#include "fggl/input/camera_input.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/gfx/camera.hpp"
#include "fggl/gfx/paint.hpp"
namespace demo {
static fggl::entity::EntityID build_model(fggl::entity::EntityManager& manager, fggl::assets::AssetManager *assets, fggl::assets::AssetID assetRef){
auto model = manager.create();
manager.add<fggl::math::Transform>(model);
auto& mesh = manager.add<fggl::mesh::StaticMultiMesh3D>(model);
auto* meshData = assets->get<fggl::mesh::MultiMesh3D>( assetRef );
if ( meshData == nullptr ) {
fggl::debug::warning("loading model did not work!");
} else {
mesh.mesh = *meshData;
mesh.pipeline = "redbook/debug";
}
manager.add<fggl::gfx::PhongMaterial>(model);
return model;
}
static void process_camera(fggl::entity::EntityManager& ecs, const fggl::input::Input& input) {
auto cameras = ecs.find<fggl::gfx::Camera>();
auto cam = cameras[0];
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 );
// scroll wheel
glm::vec3 motion(0.0f);
float delta = input.mouse.axis( fggl::input::MouseAxis::SCROLL_Y );
if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
motion -= (forward * delta);
camTransform.origin( camTransform.origin() + motion );
fggl::input::process_arcball(ecs, input, cam);
}
static void setup_camera(fggl::entity::EntityManager& world) {
auto prototype = world.create();
// setup camera position/transform
auto& transform = world.add<fggl::math::Transform>(prototype);
transform.origin(glm::vec3(10.0f, 3.0f, 10.0f));
// setup camera components
world.add<fggl::gfx::Camera>(prototype);
// interactive camera
auto& cameraKeys = world.add<fggl::input::FreeCamKeys>(prototype);
cameraKeys.forward = glfwGetKeyScancode(GLFW_KEY_W);
cameraKeys.backward = glfwGetKeyScancode(GLFW_KEY_S);
cameraKeys.left = glfwGetKeyScancode(GLFW_KEY_A);
cameraKeys.right = glfwGetKeyScancode(GLFW_KEY_D);
cameraKeys.rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
cameraKeys.rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
}
static void setup_lighting(fggl::entity::EntityManager& ecs) {
auto light = ecs.create();
ecs.add<fggl::math::Transform>(light);
auto& lightComp = ecs.add<fggl::gfx::DirectionalLight>(light);
lightComp.position = fggl::math::vec3( 10.0F, 5.0F, 0.0F );
lightComp.diffuse = fggl::gfx::colours::CORNSILK;
lightComp.ambient = fggl::gfx::colours::MIDNIGHT_BLUE;
lightComp.specular = fggl::gfx::colours::MIDNIGHT_BLUE;
}
Viewer::Viewer(fggl::App &app) : fggl::scenes::Game(app), m_model(fggl::entity::INVALID) {
}
void Viewer::activate() {
Game::activate();
auto* assetFinder = m_owner.service<fggl::assets::CheckinAdapted>();
assetFinder->discover("viewer");
// setup the assets we can select between
// TODO some form of introspection to automatically find declared/discovered assets of a given type
m_assets.clear();
m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "backpack/backpack.obj") );
m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "lowpoly_scifi/wallDoor_double.FBX") );
m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "lowpoly_scifi/wallDoor_double_end.FBX") );
m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "newell_teaset/teapot.obj") );
m_assets.push_back( fggl::assets::make_asset_id_rt("viewer", "humansanimatedpack/Paladin/Paladin.fbx") );
// create camera
setup_camera(world());
setup_lighting(world());
// setup model
m_model = fggl::entity::INVALID;
cycleAsset(0);
}
void Viewer::deactivate() {
Game::deactivate();
}
void Viewer::update(float dt) {
Game::update(dt);
process_camera(world(), input());
if ( input().keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F2)) ) {
m_debug = !m_debug;
}
if ( input().keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_F3)) ) {
// trigger the asset cycle
m_lastAsset = (m_lastAsset + 1) % m_assets.size();
cycleAsset(m_lastAsset);
}
}
void Viewer::cycleAsset(uint64_t /*idx*/) {
auto *loader = owner().service<fggl::assets::Loader>();
auto *manager = owner().service<fggl::assets::AssetManager>();
auto nextAsset = m_assets[ m_lastAsset ];
loader->loadChain(nextAsset, manager);
if ( m_model != fggl::entity::INVALID) {
world().destroy(m_model);
}
m_model = build_model(world(), manager, nextAsset);
}
void Viewer::render(fggl::gfx::Graphics &gfx) {
Game::render(gfx);
gfx.drawScene(world(), m_debug);
}
}
/*
* 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 04/09/22.
//
#include "robot/programmer.hpp"
namespace demo::robot {
Timeline::Timeline(Program& program) {
m_tracks.push_back(std::ref(program));
}
void Timeline::update(float deltaTime) {
auto currSize = size();
float trackHeight = m_tracks.size() * 32.0F;
std::size_t widestTrack = 0;
for ( auto& track : m_tracks ) {
widestTrack = std::max(widestTrack, track.get().size());
}
float instructionWidth = 32 * widestTrack;
if ( currSize.x < instructionWidth || currSize.y < trackHeight ) {
size( topLeft(), {instructionWidth, trackHeight} );
}
}
void Timeline::render(fggl::gfx::Paint &paint) {
fggl::gui::Panel::render(paint);
renderInstructions(paint);
}
void Timeline::renderInstructions(fggl::gfx::Paint& paint) {
const fggl::math::vec2f barExtents{16, 16};
for ( auto track=0U; track < m_tracks.size(); ++track) {
auto& trackRef = m_tracks[track].get();
for (auto i = 0U; i < trackRef.size(); ++i) {
auto barCenter = this->topLeft();
barCenter.x += (i * (barExtents.x * 2) ) + barExtents.x;
barCenter.y += (track * barExtents.y * 2) + barExtents.y;
// bar background
auto colour = fggl::gfx::colours::LIGHT_GRAY;
auto textColour = fggl::gfx::colours::DARK_SLATE_GRAY;
if (i % 2 == 0) {
colour = fggl::gfx::colours::WHITE;
}
if (i == trackRef.m_currInstruction && trackRef.playing) {
colour = fggl::gfx::colours::MIDNIGHT_BLUE;
textColour = fggl::gfx::colours::LIGHT_GRAY;
}
auto rect = fggl::gfx::make_rect(barCenter, barExtents, colour);
paint.fill(rect);
// bar instruction
auto& instruction = trackRef.m_instructions[i];
paint.text(instruction.name, barCenter, textColour);
}
}
}
}
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "rollball.hpp"
#include "fggl/gfx/camera.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/input/camera_input.hpp"
#include "fggl/entity/entity.hpp"
#include "fggl/entity/loader/loader.hpp"
#include "fggl/debug/draw.hpp"
#include <array>
static const fggl::util::GUID playerPrefab = "rb_player"_fid;
static const fggl::util::GUID collectablePrefab = "rb_collectable"_fid;
static const fggl::util::GUID WallNPrefab = "rb_wallX"_fid;
static const fggl::util::GUID WallEPrefab = "rb_wallZ"_fid;
static const fggl::util::GUID floorPrefab = "rb_floor"_fid;
static void setup_camera(fggl::entity::EntityManager& world) {
auto prototype = world.create();
// setup camera position/transform
auto& transform = world.add<fggl::math::Transform>(prototype);
transform.origin(glm::vec3(10.0f, 3.0f, 10.0f));
// setup camera components
world.add<fggl::gfx::Camera>(prototype);
}
static fggl::entity::EntityID setup_environment(fggl::entity::EntityManager& world, fggl::entity::EntityFactory* /*factory*/, const fggl::math::vec2& /*size*/, demo::RollState& state) {
// ensure the state is clean
state.closestPickup = fggl::entity::INVALID;
state.mode = demo::DebugMode::NORMAL;
state.time = 0.0F;
state.player = world.findByName("player");
state.collectables = world.findByTag("collectable");
return state.player;
}
namespace demo {
constexpr fggl::math::vec2 WORLD_SIZE{40.0F, 40.0F };
constexpr fggl::math::vec3 COLLECTABLE_ROTATION{15, 30, 45};
constexpr fggl::math::vec3 COLOUR_WHITE{1.0F, 1.0F, 1.0F};
constexpr fggl::math::vec3 COLOUR_BLUE{0.0F, 0.0F, 1.0F};
constexpr float MOVE_FORCE{3.0F};
RollBall::RollBall(fggl::App &app) : Game(app), m_phys(nullptr) {
}
void RollBall::activate() {
Game::activate();
fggl::debug::log(fggl::debug::Level::info, "RollBall::activate()");
// attach physics
auto* physService = m_owner.service<fggl::phys::PhysicsProvider>();
auto* entFactory = m_owner.service<fggl::entity::EntityFactory>();
m_phys = physService->create(&world(), entFactory);
auto* scriptProvider = m_owner.service<fggl::script::ScriptProvider>();
if ( scriptProvider != nullptr ) {
m_scripts = scriptProvider->create();
m_scripts->setGlobal("state", this);
}
// asset loader
auto* assetLoader = m_owner.service<fggl::assets::Loader>();
assetLoader->load("rollball.yml", fggl::entity::ENTITY_PROTOTYPE, entFactory);
assetLoader->loadChain( fggl::assets::make_asset_id_rt("core", "rollerball/rollball.yml"), this);
// collectable callbacks
/*auto* collectableCallbacks = world().get<fggl::phys::CollisionCallbacks>(prefabs.collectable);
collectableCallbacks->onEnter = [this](auto ourEntity, auto theirEntity) {
if ( theirEntity == state.player) {
//if ( ourEntity == state.closestPickup ) {
// // we're the closest pickup and we're about to get killed, so need to not be.
// state.closestPickup = fggl::ecs::NULL_ENTITY;
//}
world().destroy(ourEntity);
}
};*/
// actual scene objects
setup_camera(world());
// create a 20x20 grid
setup_environment(world(), m_owner.service<fggl::entity::EntityFactory>(), WORLD_SIZE, state);
// activate scripts
if ( m_scripts != nullptr ) {
m_scripts->onActivate();
m_scripts->load("rollball.lua");
}
}
void RollBall::deactivate() {
// deactivate scripts
if ( m_scripts != nullptr ) {
m_scripts->onActivate();
}
// we need to clean up physics
if ( m_phys != nullptr ) {
delete m_phys;
m_phys = nullptr;
}
// now let the rest of the scene do its bit
Game::deactivate();
}
fggl::math::vec3 calc_move_vector(const fggl::input::Input& input) {
constexpr fggl::math::vec3 forward = fggl::math::FORWARD;
constexpr fggl::math::vec3 right = fggl::math::RIGHT;
fggl::math::vec3 force = fggl::math::VEC3_ZERO;
if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_W))) {
force += forward;
}
if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_S))) {
force -= forward;
}
if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_A))) {
force -= right;
}
if (input.keyboard.down(glfwGetKeyScancode(GLFW_KEY_D))) {
force += right;
}
return force;
}
void RollBall::update(float deltaTime) {
Game::update(deltaTime);
m_phys->step();
auto& input = this->input();
if ( state.player != fggl::entity::INVALID ) {
auto &world = this->world();
// mode selection
if (input.keyboard.pressed(glfwGetKeyScancode(GLFW_KEY_SPACE))) {
state.mode = (DebugMode)( ((int)state.mode + 1) % 3 );
}
// setup dynamic force
auto force = calc_move_vector(input);
if ( force != fggl::math::VEC3_ZERO ) {
force = glm::normalize(force) * MOVE_FORCE;
auto& dynamics = world.get<fggl::phys::Dynamics>(state.player);
dynamics.force += force;
}
// track player position with camera
{
auto cameras = world.find<fggl::gfx::Camera>();
auto cam = cameras[0];
auto& camComp = world.get<fggl::gfx::Camera>(cam);
camComp.target = world.get<fggl::math::Transform>(state.player).origin();
auto& camTransform = world.get<fggl::math::Transform>(cam);
camTransform.origin( camComp.target + cameraOffset );
}
// distance mode
if ( state.mode == DebugMode::DISTANCE ) {
closestPickup(world);
} else {
if ( state.closestPickup != fggl::entity::INVALID ) {
if ( world.alive(state.closestPickup) ){
auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup);
if (renderer != nullptr) {
renderer->diffuse = COLOUR_WHITE;
}
}
state.closestPickup = fggl::entity::INVALID;
}
}
// make the cubes spin
spinCubes(world, deltaTime);
state.time += deltaTime;
}
// tick scripts
if ( m_scripts != nullptr ) {
m_scripts->onUpdate();
}
}
void RollBall::closestPickup(fggl::entity::EntityManager &world) {
float closestDistance = FLT_MAX;
auto closestEntity = fggl::entity::INVALID;
auto playerPos = world.get<fggl::math::Transform>(state.player).origin();
for ( const auto& pickup : state.collectables ) {
if ( !world.alive(pickup) ) {
continue;
}
auto& transform = world.get<fggl::math::Transform>(pickup);
auto distance = glm::distance(transform.origin(), playerPos);
if ( distance < closestDistance) {
closestDistance = distance;
closestEntity = pickup;
}
}
if ( state.closestPickup != closestEntity ) {
if ( world.alive(state.closestPickup) ){
// deal with the previous closest pickup
auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup);
if (renderer != nullptr) {
renderer->diffuse = COLOUR_WHITE;
}
}
// now, deal with the new closest
state.closestPickup = closestEntity;
if (closestEntity != fggl::entity::INVALID) {
auto *renderer = world.tryGet<fggl::gfx::PhongMaterial>(state.closestPickup);
if (renderer != nullptr) {
renderer->diffuse = COLOUR_BLUE;
}
}
}
// closest pickup should face the player
if ( state.closestPickup != fggl::entity::INVALID) {
auto& transform = world.get<fggl::math::Transform>(state.closestPickup);
auto& playerTransform = world.get<fggl::math::Transform>( state.player );
transform.lookAt(playerTransform.origin());
// lazy, so using the debug draw line for this bit
dd::line( &playerTransform.origin()[0], &transform.origin()[0], dd::colors::White );
}
}
void RollBall::spinCubes(fggl::entity::EntityManager& world, float deltaTime) {
// rotation
for ( const auto& entity : state.collectables ) {
if ( !world.alive(entity) || entity == state.closestPickup ) {
continue;
}
// rotate the cubes
auto& transform = world.get<fggl::math::Transform>(entity);
transform.rotateEuler( COLLECTABLE_ROTATION * deltaTime );
}
}
void RollBall::render(fggl::gfx::Graphics &gfx) {
Game::render(gfx);
}
} // namespace demo
/*
* 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 03/07/22.
//
#include "topdown.hpp"
#include "fggl/data/storage.hpp"
#include "fggl/gfx/camera.hpp"
#include "fggl/gfx/phong.hpp"
#include "fggl/input/camera_input.hpp"
#include "fggl/entity/loader/loader.hpp"
static const fggl::util::GUID collectablePrefab = "collectable"_fid;
static const fggl::util::GUID WallNPrefab = "wallX"_fid;
static const fggl::util::GUID WallEPrefab = "wallZ"_fid;
static const fggl::util::GUID floorPrefab = "floor"_fid;
namespace demo {
static void create_topdown_camera(fggl::entity::EntityManager& world) {
auto prototype = world.create();
// setup camera position/transform
auto& transform = world.add<fggl::math::Transform>(prototype);
transform.origin(glm::vec3(10.0f, 50.0f, 10.0f));
// setup camera components
auto& camera = world.add<fggl::gfx::Camera>(prototype);
camera.target = glm::vec3(0.0f, 0.0f, 0.0f);
auto& cameraKeys = world.add<fggl::input::FreeCamKeys>(prototype);
cameraKeys.forward = glfwGetKeyScancode(GLFW_KEY_W);
cameraKeys.backward = glfwGetKeyScancode(GLFW_KEY_S);
cameraKeys.left = glfwGetKeyScancode(GLFW_KEY_A);
cameraKeys.right = glfwGetKeyScancode(GLFW_KEY_D);
cameraKeys.rotate_cw = glfwGetKeyScancode(GLFW_KEY_Q);
cameraKeys.rotate_ccw = glfwGetKeyScancode(GLFW_KEY_E);
}
static void setup_lighting(fggl::entity::EntityManager& ecs) {
auto light = ecs.create();
ecs.add<fggl::math::Transform>(light);
auto& lightComp = ecs.add<fggl::gfx::DirectionalLight>(light);
lightComp.position = fggl::math::vec3( 10.0F, 5.0F, 0.0F );
lightComp.diffuse = fggl::gfx::colours::CORNSILK;
lightComp.ambient = fggl::gfx::colours::MIDNIGHT_BLUE;
lightComp.specular = fggl::gfx::colours::MIDNIGHT_BLUE;
}
static void place_cover_boxes(fggl::entity::EntityFactory* factory, fggl::entity::EntityManager& world) {
std::array<fggl::math::vec3,8> boxPos = {{
{-10.0F, 0.0F, -10.0F},
{-10.0F, 0.0F, 10.0F},
{ 10.0F, 0.0F, -10.0F},
{ 10.0F, 0.0F, 10.0F},
{-10.0F, 0.0F, 0.0F},
{ 0.0F, 0.0F, -10.0F},
{ 10.0F, 0.0F, 0.0F},
{ 0.0F, 0.0F, 10.0F},
}};
for (auto pos : boxPos) {
auto box = factory->create(collectablePrefab, world);
auto& transform = world.get<fggl::math::Transform>(box);
transform.origin(pos);
}
}
static void build_arena(fggl::entity::EntityFactory* factory, fggl::entity::EntityManager& world) {
{
auto floor = factory->create(floorPrefab, world);
auto& transform = world.get<fggl::math::Transform>(floor);
transform.origin({0.0F, -2.5F, 0.0F});
//fggl::debug::log("created floor: {}", floor);
}
fggl::math::vec2 size{40.0F, 40.0F};
for (auto side : {-1.0F, 1.0F})
{
{
auto obj = factory->create(WallNPrefab, world);
auto& transform = world.get<fggl::math::Transform>(obj);
transform.origin({size.x / 2 * side, 0.0F, 0.0F});
}
{
auto obj = factory->create(WallEPrefab, world);
auto& transform = world.get<fggl::math::Transform>(obj);
transform.origin({0.0F, 0.0F, size.y/2 * side });
}
}
setup_lighting(world);
place_cover_boxes(factory, world);
}
static void process_camera(fggl::entity::EntityManager& ecs, const fggl::input::Input& input) {
auto cameras = ecs.find<fggl::gfx::Camera>();
if ( !cameras.empty() ) {
auto cam = cameras[0];
fggl::input::process_scroll(ecs, input, cam);
fggl::input::process_freecam(ecs, input, cam);
fggl::input::process_edgescroll(ecs, input, cam);
}
}
static void populate_sample_level(fggl::entity::EntityFactory* factory, fggl::entity::EntityManager& world) {
create_topdown_camera(world);
build_arena(factory, world);
}
TopDown::TopDown(fggl::App& app) : fggl::scenes::Game(app) {
}
void TopDown::activate() {
Game::activate();
fggl::debug::log(fggl::debug::Level::info, "TopDown::activate()");
auto* assetLoader = m_owner.service<fggl::assets::Loader>();
assetLoader->load("topdown.yml", fggl::entity::ENTITY_PROTOTYPE);
auto* factory = m_owner.service<fggl::entity::EntityFactory>();
//fggl::ecs3::load_prototype_file(world(), *storage, "topdown.yml");
// create a sample level
populate_sample_level(factory, world());
}
void TopDown::update(float dt) {
Game::update(dt);
process_camera(world(), input());
if ( input().mouse.pressed(fggl::input::MouseButton::LEFT) ) {
pick_object();
}
}
void TopDown::pick_object() {
if (!hasPhys()) {
return;
}
auto cameras = world().find<fggl::gfx::Camera>();
if ( cameras.empty() ) {
return;
}
auto cam = cameras[0];
fggl::math::vec2 position {
input().mouse.axis(fggl::input::MouseAxis::X),
input().mouse.axis(fggl::input::MouseAxis::Y),
};
auto ray = fggl::gfx::get_camera_ray(world(), cam, position);
auto hit = phys().raycast(ray);
if ( hit != fggl::entity::INVALID) {
fggl::debug::info("hit: {}", (int)hit);
} else {
fggl::debug::info("no hit");
}
}
void TopDown::render(fggl::gfx::Graphics& gfx) {
Game::render(gfx);
}
}
\ No newline at end of file
/*
* ${license.title}
* Copyright (C) 2022 ${license.owner}
* ${license.mailto}
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//
// Created by webpigeon on 22/04/22.
//
#ifndef FGGL_DEMO_GAMESCENE_H
#define FGGL_DEMO_GAMESCENE_H
#include "fggl/app.hpp"
#include "fggl/gui/gui.hpp"
#include "fggl/scenes/game.hpp"
#include "fggl/data/heightmap.hpp"
#include "fggl/data/procedural.hpp"
#include "fggl/gfx/camera.hpp"
#include "fggl/input/input.hpp"
#include "fggl/input/camera_input.hpp"
#include "fggl/util/chrono.hpp"
#include "PerlinNoise.hpp"
enum camera_type { cam_free, cam_arcball };
class GameScene : public fggl::scenes::Game {
public:
explicit GameScene(fggl::App& app) : Game(app), m_sceneTime(nullptr) { };
//~GameScene() override = default;
void activate() override {
Game::activate();
m_sceneTime = std::make_unique<fggl::util::Timer>();
m_sceneTime->frequency( glfwGetTimerFrequency() );
m_sceneTime->setup( glfwGetTimerValue() );
setup();
}
void update(float dt) override;
void render(fggl::gfx::Graphics& gfx) override;
private:
std::unique_ptr<fggl::util::Timer> m_sceneTime;
fggl::gui::Panel m_canvas;
void setup();
};
#endif //FGGL_DEMO_GAMESCENE_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/>.
*/
# pragma once
# include <cstdint>
# include <algorithm>
# include <array>
# include <iterator>
# include <numeric>
# include <random>
# include <type_traits>
# if __has_include(<concepts>) && defined(__cpp_concepts)
# include <concepts>
# endif
// Library major version
# define SIVPERLIN_VERSION_MAJOR 3
// Library minor version
# define SIVPERLIN_VERSION_MINOR 0
// Library revision version
# define SIVPERLIN_VERSION_REVISION 0
// Library version
# define SIVPERLIN_VERSION ((SIVPERLIN_VERSION_MAJOR * 100 * 100) + (SIVPERLIN_VERSION_MINOR * 100) + (SIVPERLIN_VERSION_REVISION))
// [[nodiscard]] for constructors
# if (201907L <= __has_cpp_attribute(nodiscard))
# define SIVPERLIN_NODISCARD_CXX20 [[nodiscard]]
# else
# define SIVPERLIN_NODISCARD_CXX20
# endif
// std::uniform_random_bit_generator concept
# if __cpp_lib_concepts
# define SIVPERLIN_CONCEPT_URBG template <std::uniform_random_bit_generator URBG>
# define SIVPERLIN_CONCEPT_URBG_ template <std::uniform_random_bit_generator URBG>
# else
# define SIVPERLIN_CONCEPT_URBG template <class URBG, std::enable_if_t<std::conjunction_v<std::is_invocable<URBG&>, std::is_unsigned<std::invoke_result_t<URBG&>>>>* = nullptr>
# define SIVPERLIN_CONCEPT_URBG_ template <class URBG, std::enable_if_t<std::conjunction_v<std::is_invocable<URBG&>, std::is_unsigned<std::invoke_result_t<URBG&>>>>*>
# endif
// arbitrary value for increasing entropy
# ifndef SIVPERLIN_DEFAULT_Y
# define SIVPERLIN_DEFAULT_Y (0.12345)
# endif
// arbitrary value for increasing entropy
# ifndef SIVPERLIN_DEFAULT_Z
# define SIVPERLIN_DEFAULT_Z (0.34567)
# endif
namespace siv
{
template <class Float>
class BasicPerlinNoise
{
public:
static_assert(std::is_floating_point_v<Float>);
///////////////////////////////////////
//
// Typedefs
//
using state_type = std::array<std::uint8_t, 256>;
using value_type = Float;
using default_random_engine = std::mt19937;
using seed_type = typename default_random_engine::result_type;
///////////////////////////////////////
//
// Constructors
//
SIVPERLIN_NODISCARD_CXX20
constexpr BasicPerlinNoise() noexcept;
SIVPERLIN_NODISCARD_CXX20
explicit BasicPerlinNoise(seed_type seed);
SIVPERLIN_CONCEPT_URBG
SIVPERLIN_NODISCARD_CXX20
explicit BasicPerlinNoise(URBG&& urbg);
///////////////////////////////////////
//
// Reseed
//
void reseed(seed_type seed);
SIVPERLIN_CONCEPT_URBG
void reseed(URBG&& urbg);
///////////////////////////////////////
//
// Serialization
//
[[nodiscard]]
constexpr const state_type& serialize() const noexcept;
constexpr void deserialize(const state_type& state) noexcept;
///////////////////////////////////////
//
// Noise (The result is in the range [-1, 1])
//
[[nodiscard]]
value_type noise1D(value_type x) const noexcept;
[[nodiscard]]
value_type noise2D(value_type x, value_type y) const noexcept;
[[nodiscard]]
value_type noise3D(value_type x, value_type y, value_type z) const noexcept;
///////////////////////////////////////
//
// Noise (The result is remapped to the range [0, 1])
//
[[nodiscard]]
value_type noise1D_01(value_type x) const noexcept;
[[nodiscard]]
value_type noise2D_01(value_type x, value_type y) const noexcept;
[[nodiscard]]
value_type noise3D_01(value_type x, value_type y, value_type z) const noexcept;
///////////////////////////////////////
//
// Octave noise (The result can be out of the range [-1, 1])
//
[[nodiscard]]
value_type octave1D(value_type x, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave2D(value_type x, value_type y, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave3D(value_type x, value_type y, value_type z, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
///////////////////////////////////////
//
// Octave noise (The result is clamped to the range [-1, 1])
//
[[nodiscard]]
value_type octave1D_11(value_type x, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave2D_11(value_type x, value_type y, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave3D_11(value_type x, value_type y, value_type z, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
///////////////////////////////////////
//
// Octave noise (The result is clamped and remapped to the range [0, 1])
//
[[nodiscard]]
value_type octave1D_01(value_type x, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave2D_01(value_type x, value_type y, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type octave3D_01(value_type x, value_type y, value_type z, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
///////////////////////////////////////
//
// Octave noise (The result is normalized to the range [-1, 1])
//
[[nodiscard]]
value_type normalizedOctave1D(value_type x, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type normalizedOctave2D(value_type x, value_type y, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type normalizedOctave3D(value_type x, value_type y, value_type z, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
///////////////////////////////////////
//
// Octave noise (The result is normalized and remapped to the range [0, 1])
//
[[nodiscard]]
value_type normalizedOctave1D_01(value_type x, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type normalizedOctave2D_01(value_type x, value_type y, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
[[nodiscard]]
value_type normalizedOctave3D_01(value_type x, value_type y, value_type z, std::int32_t octaves, value_type persistence = value_type(0.5)) const noexcept;
private:
state_type m_permutation;
};
using PerlinNoise = BasicPerlinNoise<double>;
namespace perlin_detail
{
////////////////////////////////////////////////
//
// These functions are provided for consistency.
// You may get different results from std::shuffle() with different standard library implementations.
//
SIVPERLIN_CONCEPT_URBG
[[nodiscard]]
inline std::uint64_t Random(const std::uint64_t max, URBG&& urbg)
{
return (urbg() % (max + 1));
}
template <class RandomIt, class URBG>
inline void Shuffle(RandomIt first, RandomIt last, URBG&& urbg)
{
if (first == last)
{
return;
}
using difference_type = typename std::iterator_traits<RandomIt>::difference_type;
for (RandomIt it = first + 1; it < last; ++it)
{
const std::uint64_t n = static_cast<std::uint64_t>(it - first);
std::iter_swap(it, first + static_cast<difference_type>(Random(n, std::forward<URBG>(urbg))));
}
}
//
////////////////////////////////////////////////
template <class Float>
[[nodiscard]]
inline constexpr Float Fade(const Float t) noexcept
{
return t * t * t * (t * (t * 6 - 15) + 10);
}
template <class Float>
[[nodiscard]]
inline constexpr Float Lerp(const Float a, const Float b, const Float t) noexcept
{
return (a + (b - a) * t);
}
template <class Float>
[[nodiscard]]
inline constexpr Float Grad(const std::uint8_t hash, const Float x, const Float y, const Float z) noexcept
{
const std::uint8_t h = hash & 15;
const Float u = h < 8 ? x : y;
const Float v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);
}
template <class Float>
[[nodiscard]]
inline constexpr Float Remap_01(const Float x) noexcept
{
return (x * Float(0.5) + Float(0.5));
}
template <class Float>
[[nodiscard]]
inline constexpr Float Clamp_11(const Float x) noexcept
{
return std::clamp(x, Float(-1.0), Float(1.0));
}
template <class Float>
[[nodiscard]]
inline constexpr Float RemapClamp_01(const Float x) noexcept
{
if (x <= Float(-1.0))
{
return Float(0.0);
}
else if (Float(1.0) <= x)
{
return Float(1.0);
}
return (x * Float(0.5) + Float(0.5));
}
template <class Noise, class Float>
[[nodiscard]]
inline auto Octave1D(const Noise& noise, Float x, const std::int32_t octaves, const Float persistence) noexcept
{
using value_type = Float;
value_type result = 0;
value_type amplitude = 1;
for (std::int32_t i = 0; i < octaves; ++i)
{
result += (noise.noise1D(x) * amplitude);
x *= 2;
amplitude *= persistence;
}
return result;
}
template <class Noise, class Float>
[[nodiscard]]
inline auto Octave2D(const Noise& noise, Float x, Float y, const std::int32_t octaves, const Float persistence) noexcept
{
using value_type = Float;
value_type result = 0;
value_type amplitude = 1;
for (std::int32_t i = 0; i < octaves; ++i)
{
result += (noise.noise2D(x, y) * amplitude);
x *= 2;
y *= 2;
amplitude *= persistence;
}
return result;
}
template <class Noise, class Float>
[[nodiscard]]
inline auto Octave3D(const Noise& noise, Float x, Float y, Float z, const std::int32_t octaves, const Float persistence) noexcept
{
using value_type = Float;
value_type result = 0;
value_type amplitude = 1;
for (std::int32_t i = 0; i < octaves; ++i)
{
result += (noise.noise3D(x, y, z) * amplitude);
x *= 2;
y *= 2;
z *= 2;
amplitude *= persistence;
}
return result;
}
template <class Float>
[[nodiscard]]
inline constexpr Float MaxAmplitude(const std::int32_t octaves, const Float persistence) noexcept
{
using value_type = Float;
value_type result = 0;
value_type amplitude = 1;
for (std::int32_t i = 0; i < octaves; ++i)
{
result += amplitude;
amplitude *= persistence;
}
return result;
}
}
///////////////////////////////////////
template <class Float>
inline constexpr BasicPerlinNoise<Float>::BasicPerlinNoise() noexcept
: m_permutation{ 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 } {}
template <class Float>
inline BasicPerlinNoise<Float>::BasicPerlinNoise(const seed_type seed)
{
reseed(seed);
}
template <class Float>
SIVPERLIN_CONCEPT_URBG_
inline BasicPerlinNoise<Float>::BasicPerlinNoise(URBG&& urbg)
{
reseed(std::forward<URBG>(urbg));
}
///////////////////////////////////////
template <class Float>
inline void BasicPerlinNoise<Float>::reseed(const seed_type seed)
{
reseed(default_random_engine{ seed });
}
template <class Float>
SIVPERLIN_CONCEPT_URBG_
inline void BasicPerlinNoise<Float>::reseed(URBG&& urbg)
{
std::iota(m_permutation.begin(), m_permutation.end(), uint8_t{ 0 });
perlin_detail::Shuffle(m_permutation.begin(), m_permutation.end(), std::forward<URBG>(urbg));
}
///////////////////////////////////////
template <class Float>
inline constexpr const typename BasicPerlinNoise<Float>::state_type& BasicPerlinNoise<Float>::serialize() const noexcept
{
return m_permutation;
}
template <class Float>
inline constexpr void BasicPerlinNoise<Float>::deserialize(const state_type& state) noexcept
{
m_permutation = state;
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise1D(const value_type x) const noexcept
{
return noise3D(x,
static_cast<value_type>(SIVPERLIN_DEFAULT_Y),
static_cast<value_type>(SIVPERLIN_DEFAULT_Z));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise2D(const value_type x, const value_type y) const noexcept
{
return noise3D(x,
y,
static_cast<value_type>(SIVPERLIN_DEFAULT_Z));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise3D(const value_type x, const value_type y, const value_type z) const noexcept
{
const value_type _x = std::floor(x);
const value_type _y = std::floor(y);
const value_type _z = std::floor(z);
const std::int32_t ix = static_cast<std::int32_t>(_x) & 255;
const std::int32_t iy = static_cast<std::int32_t>(_y) & 255;
const std::int32_t iz = static_cast<std::int32_t>(_z) & 255;
const value_type fx = (x - _x);
const value_type fy = (y - _y);
const value_type fz = (z - _z);
const value_type u = perlin_detail::Fade(fx);
const value_type v = perlin_detail::Fade(fy);
const value_type w = perlin_detail::Fade(fz);
const std::uint8_t A = (m_permutation[ix & 255] + iy) & 255;
const std::uint8_t B = (m_permutation[(ix + 1) & 255] + iy) & 255;
const std::uint8_t AA = (m_permutation[A] + iz) & 255;
const std::uint8_t AB = (m_permutation[(A + 1) & 255] + iz) & 255;
const std::uint8_t BA = (m_permutation[B] + iz) & 255;
const std::uint8_t BB = (m_permutation[(B + 1) & 255] + iz) & 255;
const value_type p0 = perlin_detail::Grad(m_permutation[AA], fx, fy, fz);
const value_type p1 = perlin_detail::Grad(m_permutation[BA], fx - 1, fy, fz);
const value_type p2 = perlin_detail::Grad(m_permutation[AB], fx, fy - 1, fz);
const value_type p3 = perlin_detail::Grad(m_permutation[BB], fx - 1, fy - 1, fz);
const value_type p4 = perlin_detail::Grad(m_permutation[(AA + 1) & 255], fx, fy, fz - 1);
const value_type p5 = perlin_detail::Grad(m_permutation[(BA + 1) & 255], fx - 1, fy, fz - 1);
const value_type p6 = perlin_detail::Grad(m_permutation[(AB + 1) & 255], fx, fy - 1, fz - 1);
const value_type p7 = perlin_detail::Grad(m_permutation[(BB + 1) & 255], fx - 1, fy - 1, fz - 1);
const value_type q0 = perlin_detail::Lerp(p0, p1, u);
const value_type q1 = perlin_detail::Lerp(p2, p3, u);
const value_type q2 = perlin_detail::Lerp(p4, p5, u);
const value_type q3 = perlin_detail::Lerp(p6, p7, u);
const value_type r0 = perlin_detail::Lerp(q0, q1, v);
const value_type r1 = perlin_detail::Lerp(q2, q3, v);
return perlin_detail::Lerp(r0, r1, w);
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise1D_01(const value_type x) const noexcept
{
return perlin_detail::Remap_01(noise1D(x));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise2D_01(const value_type x, const value_type y) const noexcept
{
return perlin_detail::Remap_01(noise2D(x, y));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::noise3D_01(const value_type x, const value_type y, const value_type z) const noexcept
{
return perlin_detail::Remap_01(noise3D(x, y, z));
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave1D(const value_type x, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Octave1D(*this, x, octaves, persistence);
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave2D(const value_type x, const value_type y, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Octave2D(*this, x, y, octaves, persistence);
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave3D(const value_type x, const value_type y, const value_type z, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Octave3D(*this, x, y, z, octaves, persistence);
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave1D_11(const value_type x, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Clamp_11(octave1D(x, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave2D_11(const value_type x, const value_type y, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Clamp_11(octave2D(x, y, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave3D_11(const value_type x, const value_type y, const value_type z, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Clamp_11(octave3D(x, y, z, octaves, persistence));
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave1D_01(const value_type x, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::RemapClamp_01(octave1D(x, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave2D_01(const value_type x, const value_type y, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::RemapClamp_01(octave2D(x, y, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::octave3D_01(const value_type x, const value_type y, const value_type z, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::RemapClamp_01(octave3D(x, y, z, octaves, persistence));
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave1D(const value_type x, const std::int32_t octaves, const value_type persistence) const noexcept
{
return (octave1D(x, octaves, persistence) / perlin_detail::MaxAmplitude(octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave2D(const value_type x, const value_type y, const std::int32_t octaves, const value_type persistence) const noexcept
{
return (octave2D(x, y, octaves, persistence) / perlin_detail::MaxAmplitude(octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave3D(const value_type x, const value_type y, const value_type z, const std::int32_t octaves, const value_type persistence) const noexcept
{
return (octave3D(x, y, z, octaves, persistence) / perlin_detail::MaxAmplitude(octaves, persistence));
}
///////////////////////////////////////
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave1D_01(const value_type x, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Remap_01(normalizedOctave1D(x, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave2D_01(const value_type x, const value_type y, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Remap_01(normalizedOctave2D(x, y, octaves, persistence));
}
template <class Float>
inline typename BasicPerlinNoise<Float>::value_type BasicPerlinNoise<Float>::normalizedOctave3D_01(const value_type x, const value_type y, const value_type z, const std::int32_t octaves, const value_type persistence) const noexcept
{
return perlin_detail::Remap_01(normalizedOctave3D(x, y, z, octaves, persistence));
}
}
# undef SIVPERLIN_NODISCARD_CXX20
# undef SIVPERLIN_CONCEPT_URBG
# undef SIVPERLIN_CONCEPT_URBG_
\ 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 27/08/22.
//
#ifndef FGGL_DEMO_INCLUDE_GRID_HPP
#define FGGL_DEMO_INCLUDE_GRID_HPP
#include <memory>
#include "fggl/scenes/game.hpp"
#include "fggl/entity/gridworld/zone.hpp"
#include "fggl/animation/animator.hpp"
#include "fggl/gui/gui.hpp"
#include "robot/programmer.hpp"
namespace demo {
constexpr int GRID_SIZE = 255;
using DemoGrid = fggl::entity::grid::Area2D<GRID_SIZE, GRID_SIZE>;
struct LevelRules {
fggl::math::vec2i startingPos;
uint32_t startingDirection;
uint32_t startingPower;
};
struct CellPos {
fggl::math::vec2i pos;
uint8_t direction;
fggl::math::vec2f drawOffset{0.0F, 0.0F};
float rotationOffset{0.0F};
};
struct RobotState {
uint32_t power = 64;
};
class GridScene : public fggl::scenes::GameBase {
public:
explicit GridScene(fggl::App& app);
void activate() override;
void deactivate() override;
void update(float dt) override;
void render(fggl::gfx::Graphics& gfx) override;
private:
// level
LevelRules m_levelRules;
fggl::entity::grid::TileSet m_tiles;
std::unique_ptr<DemoGrid> m_grid;
// control
fggl::entity::EntityID m_player = fggl::entity::INVALID;
robot::Program m_program;
// UI
fggl::gui::Container m_canvas;
fggl::animation::FrameAnimator m_animator;
void resetPuzzle();
void tickPlayer();
void checkVictory();
inline void forward() {
auto& cell = m_grid->entities().get<CellPos>(m_player);
fggl::math::vec2i moveDir{0,0};
if ( cell.direction == 0) {
moveDir.y = 1;
} else if (cell.direction == 1) {
moveDir.x = 1;
} else if (cell.direction == 2) {
moveDir.y = -1;
} else if (cell.direction == 3) {
moveDir.x = -1;
}
if ( m_grid->canMove(cell.pos, moveDir) ) {
cell.pos += moveDir;
}
}
inline void rotate(bool clockwise) {
auto& cell = m_grid->entities().get<CellPos>(m_player);
int direction = clockwise ? +1 : -1;
cell.direction = (cell.direction + 4 + direction) % 4;
}
};
}
#endif //FGGL_DEMO_INCLUDE_GRID_HPP