diff --git a/CMakeLists.txt b/CMakeLists.txt index e673fb7..a911884 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,5 @@ cmake_minimum_required(VERSION 3.7) - option(VERSION_STRING "Project version string" "0.0.0") project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX) diff --git a/src/animation/screen-transition.cpp b/src/animation/screen-transition.cpp index cd617d8..086dbce 100644 --- a/src/animation/screen-transition.cpp +++ b/src/animation/screen-transition.cpp @@ -24,7 +24,8 @@ screen_transition::screen_transition() { // Setup material - material.set_flags(MATERIAL_FLAG_TRANSLUCENT | MATERIAL_FLAG_X_RAY); + material.set_flags(MATERIAL_FLAG_X_RAY); + material.set_blend_mode(render::blend_mode::translucent); progress = material.add_property("progress"); // Setup billboard diff --git a/src/entity/commands.cpp b/src/entity/commands.cpp index 7c8fa0c..1d36063 100644 --- a/src/entity/commands.cpp +++ b/src/entity/commands.cpp @@ -20,7 +20,6 @@ #include "entity/commands.hpp" #include "game/component/model.hpp" #include "game/component/transform.hpp" -#include "game/component/parent.hpp" #include "game/component/celestial-body.hpp" #include "game/component/terrain.hpp" #include "math/quaternion.hpp" diff --git a/src/game/component/proteome.hpp b/src/game/ant/species.hpp similarity index 65% rename from src/game/component/proteome.hpp rename to src/game/ant/species.hpp index 771532c..4522af9 100644 --- a/src/game/component/proteome.hpp +++ b/src/game/ant/species.hpp @@ -17,23 +17,27 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GAME_COMPONENT_PROTEOME_HPP -#define ANTKEEPER_GAME_COMPONENT_PROTEOME_HPP +#ifndef ANTKEEPER_GAME_ANT_SPECIES_HPP +#define ANTKEEPER_GAME_ANT_SPECIES_HPP -#include -#include +#include "game/ant/caste.hpp" +#include "game/ant/phenome.hpp" +#include "render/model.hpp" +#include namespace game { -namespace component { +namespace ant { -/// Set of all proteins that can be expressed by an organism. -struct proteome +struct species { - /// Set of amino acid sequences of every protein in the proteome. - std::vector proteins; + /// Caste-specific phenomes + std::unordered_map phenomes; + + /// Caste-specific models + std::unordered_map models; }; -} // namespace component +} // namespace ant } // namespace game -#endif // ANTKEEPER_GAME_COMPONENT_PROTEOME_HPP +#endif // ANTKEEPER_GAME_ANT_SPECIES_HPP diff --git a/src/game/component/genome.hpp b/src/game/component/genome.hpp deleted file mode 100644 index d92d4b1..0000000 --- a/src/game/component/genome.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#ifndef ANTKEEPER_GAME_COMPONENT_GENOME_HPP -#define ANTKEEPER_GAME_COMPONENT_GENOME_HPP - -#include -#include - -namespace game { -namespace component { - -/// Complete set of genetic material in an organism. -struct genome -{ - /** - * Number of complete sets of chromosomes in a cell. - * - * A value of `1` indicates the organism is haploid, `2` is diploid, `3` is triploid, etc. - */ - unsigned int ploidy; - - /** - * Set of DNA base sequences for every chromosomes in the genome. - * - * A DNA base sequence is a string of IUPAC DNA base symbols. Homologous chromosomes should be stored consecutively, such that in a diploid organism, a chromosome with an even index is homologous to the following chromosome. - */ - std::vector chromosomes; -}; - -} // namespace component -} // namespace game - -#endif // ANTKEEPER_GAME_COMPONENT_GENOME_HPP diff --git a/src/game/component/parent.hpp b/src/game/component/portal.hpp similarity index 81% rename from src/game/component/parent.hpp rename to src/game/component/portal.hpp index 542eabf..64cf7f6 100644 --- a/src/game/component/parent.hpp +++ b/src/game/component/portal.hpp @@ -17,20 +17,18 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GAME_COMPONENT_PARENT_HPP -#define ANTKEEPER_GAME_COMPONENT_PARENT_HPP - -#include "entity/id.hpp" +#ifndef ANTKEEPER_GAME_COMPONENT_PORTAL_HPP +#define ANTKEEPER_GAME_COMPONENT_PORTAL_HPP namespace game { namespace component { -struct parent +struct portal { - entity::id parent; + }; } // namespace component } // namespace game -#endif // ANTKEEPER_GAME_COMPONENT_PARENT_HPP +#endif // ANTKEEPER_GAME_COMPONENT_PORTAL_HPP diff --git a/src/game/context.hpp b/src/game/context.hpp index b46e338..4a0ebc9 100644 --- a/src/game/context.hpp +++ b/src/game/context.hpp @@ -97,7 +97,6 @@ namespace game class camera; class nest; class render; - class proteome; class steering; class spring; } @@ -295,7 +294,6 @@ struct context game::system::atmosphere* atmosphere_system; game::system::astronomy* astronomy_system; game::system::orbit* orbit_system; - game::system::proteome* proteome_system; double3 rgb_wavelengths; diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp index e71de10..7d47028 100644 --- a/src/game/fonts.cpp +++ b/src/game/fonts.cpp @@ -61,7 +61,7 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const font_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear); // Create font material - material.set_flags(MATERIAL_FLAG_TRANSLUCENT); + material.set_blend_mode(render::blend_mode::translucent); material.add_property("font_bitmap")->set_value(font_texture); material.set_shader_program(shader_program); } diff --git a/src/game/spawn.cpp b/src/game/spawn.cpp new file mode 100644 index 0000000..a54ca93 --- /dev/null +++ b/src/game/spawn.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/spawn.hpp" +#include "game/component/transform.hpp" +#include "game/component/model.hpp" + +namespace game { + +entity::id spawn_ant_egg(game::context& ctx, const ant::genome& genome, bool fertilized, const float3& position) +{ + // Create entity + entity::id egg_eid = ctx.entity_registry->create(); + + // Construct transform component + component::transform transform_component; + transform_component.local = math::transform::identity; + transform_component.local.translation = position; + transform_component.world = transform_component.local; + transform_component.warp = true; + ctx.entity_registry->emplace(egg_eid, transform_component); + + // Construct model component + component::model model_component; + model_component.render_model = genome.egg->phene.model; + model_component.instance_count = 0; + model_component.layers = ~0; + ctx.entity_registry->emplace(egg_eid, model_component); + + return egg_eid; +} + +entity::id spawn_ant_larva(game::context& ctx, const ant::genome& genome, const float3& position) +{ + // Create entity + entity::id larva_eid = ctx.entity_registry->create(); + + // Construct transform component + component::transform transform_component; + transform_component.local = math::transform::identity; + transform_component.local.translation = position; + transform_component.world = transform_component.local; + transform_component.warp = true; + ctx.entity_registry->emplace(larva_eid, transform_component); + + // Construct model component + component::model model_component; + model_component.render_model = genome.larva->phene.model; + model_component.instance_count = 0; + model_component.layers = ~0; + ctx.entity_registry->emplace(larva_eid, model_component); + + return larva_eid; +} + +entity::id spawn_worker_ant(game::context& ctx, const ant::genome& genome, const float3& position) +{ + return entt::null; +} + +} // namespace game diff --git a/src/game/spawn.hpp b/src/game/spawn.hpp new file mode 100644 index 0000000..6d69ad3 --- /dev/null +++ b/src/game/spawn.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_GAME_SPAWN_HPP +#define ANTKEEPER_GAME_SPAWN_HPP + +#include "game/context.hpp" +#include "game/ant/genome.hpp" +#include "utility/fundamental-types.hpp" + +namespace game { + +/** + * Spawns an ant egg. + * + * @param ctx Game context. + * @param genome Ant genome. + * @param fertilized Whether the egg has been fertilized. + * @param position Position at which to spawn an egg. + * + * @return Entity ID of the spawned ant egg. + */ +entity::id spawn_ant_egg(game::context& ctx, const ant::genome& genome, bool fertilized, const float3& position); + +/** + * Spawns an ant larva. + * + * @param ctx Game context. + * @param genome Ant genome. + * @param position Position at which to spawn an larva. + * + * @return Entity ID of the spawned ant larva. + */ +entity::id spawn_ant_larva(game::context& ctx, const ant::genome& genome, const float3& position); + +/** + * Spawns a worker ant. + * + * @param ctx Game context. + * @param genome Ant genome. + * @param position Position at which to spawn a worker ant. + * + * @return Entity ID of the spawned worker ant. + */ +entity::id spawn_worker_ant(game::context& ctx, const ant::genome& genome, const float3& position); + +} // namespace game + +#endif // ANTKEEPER_GAME_SPAWN_HPP diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp index 07dfd80..26d7517 100644 --- a/src/game/state/boot.cpp +++ b/src/game/state/boot.cpp @@ -71,7 +71,6 @@ #include "game/system/blackbody.hpp" #include "game/system/atmosphere.hpp" #include "game/system/orbit.hpp" -#include "game/system/proteome.hpp" #include "game/system/steering.hpp" #include "game/system/spring.hpp" #include "entity/commands.hpp" @@ -497,8 +496,7 @@ void boot::setup_rendering() ctx.surface_shadow_map_clear_pass->set_cleared_buffers(false, true, false); ctx.surface_shadow_map_clear_pass->set_clear_depth(1.0f); - ctx.surface_shadow_map_pass = new render::shadow_map_pass(ctx.rasterizer, ctx.shadow_map_framebuffer, ctx.resource_manager); - ctx.surface_shadow_map_pass->set_split_scheme_weight(0.75f); + ctx.surface_shadow_map_pass = new render::shadow_map_pass(ctx.rasterizer, ctx.resource_manager); ctx.surface_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.hdr_framebuffer); ctx.surface_clear_pass->set_cleared_buffers(false, true, true); @@ -514,8 +512,6 @@ void boot::setup_rendering() ctx.surface_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager); ctx.surface_material_pass->set_fallback_material(ctx.fallback_material); - ctx.surface_material_pass->shadow_map_pass = ctx.surface_shadow_map_pass; - ctx.surface_material_pass->shadow_map = ctx.shadow_map_depth_texture; ctx.app->get_event_dispatcher()->subscribe(ctx.surface_material_pass); ctx.surface_outline_pass = new render::outline_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager); @@ -733,7 +729,7 @@ void boot::setup_scenes() menu_bg_material->set_shader_program(ctx.resource_manager->load("ui-element-untextured.glsl")); auto menu_bg_tint = menu_bg_material->add_property("tint"); menu_bg_tint->set_value(float4{0.0f, 0.0f, 0.0f, 0.5f}); - menu_bg_material->set_flags(MATERIAL_FLAG_TRANSLUCENT); + menu_bg_material->set_blend_mode(render::blend_mode::translucent); menu_bg_material->update_tweens(); ctx.menu_bg_billboard = new scene::billboard(); ctx.menu_bg_billboard->set_active(false); @@ -750,7 +746,7 @@ void boot::setup_scenes() flash_tint->set_value(float4{1, 1, 1, 1}); //flash_tint->set_tween_interpolator(ease::out_quad); - flash_material->set_flags(MATERIAL_FLAG_TRANSLUCENT); + flash_material->set_blend_mode(render::blend_mode::translucent); flash_material->update_tweens(); ctx.camera_flash_billboard = new scene::billboard(); @@ -966,9 +962,6 @@ void boot::setup_systems() ctx.astronomy_system->set_transmittance_samples(16); ctx.astronomy_system->set_sky_pass(ctx.sky_pass); - // Setup proteome system - ctx.proteome_system = new game::system::proteome(*ctx.entity_registry); - // Setup render system ctx.render_system = new game::system::render(*ctx.entity_registry); //ctx.render_system->add_layer(ctx.underground_scene); @@ -1203,7 +1196,6 @@ void boot::setup_loop() ctx.spatial_system->update(t, dt); ctx.constraint_system->update(t, dt); ctx.painting_system->update(t, dt); - ctx.proteome_system->update(t, dt); ctx.animator->animate(dt); ctx.render_system->update(t, dt); } diff --git a/src/game/state/nest-selection.cpp b/src/game/state/nest-selection.cpp index 46591bd..84cca8c 100644 --- a/src/game/state/nest-selection.cpp +++ b/src/game/state/nest-selection.cpp @@ -57,6 +57,7 @@ #include "game/ant/phenome.hpp" #include "game/ant/genome.hpp" #include "game/ant/cladogenesis.hpp" +#include "game/spawn.hpp" using namespace game::ant; @@ -68,25 +69,9 @@ nest_selection::nest_selection(game::context& ctx): { ctx.logger->push_task("Entering nest selection state"); - ctx.logger->push_task("Generating genome"); - std::random_device rng; ant::genome* genome = ant::cladogenesis(ctx.active_ecoregion->gene_pools[0], rng); - - // genome.antennae = ctx.resource_manager->load("pogonomyrmex-antennae.dna"); - // genome.eyes = ctx.resource_manager->load("pogonomyrmex-eyes.dna"); - // genome.gaster = ctx.resource_manager->load("pogonomyrmex-gaster.dna"); - // genome.head = ctx.resource_manager->load("pogonomyrmex-head.dna"); - // genome.legs = ctx.resource_manager->load("pogonomyrmex-legs.dna"); - // genome.mandibles = ctx.resource_manager->load("pogonomyrmex-mandibles.dna"); - // genome.mesosoma = ctx.resource_manager->load("pogonomyrmex-mesosoma.dna"); - // genome.ocelli = ctx.resource_manager->load("ocelli-absent.dna"); - // genome.pigmentation = ctx.resource_manager->load("rust-pigmentation.dna"); - // genome.sculpturing = ctx.resource_manager->load("politus-sculpturing.dna"); - // genome.sting = ctx.resource_manager->load("pogonomyrmex-sting.dna"); - // genome.waist = ctx.resource_manager->load("pogonomyrmex-waist.dna"); - // genome.wings = ctx.resource_manager->load("wings-absent.dna"); ctx.logger->pop_task(EXIT_SUCCESS); ctx.logger->push_task("Building worker phenome"); @@ -112,7 +97,6 @@ nest_selection::nest_selection(game::context& ctx): worker_model_component.layers = ~0; ctx.entity_registry->emplace(worker_eid, worker_model_component); - // Disable UI color clear ctx.ui_clear_pass->set_cleared_buffers(false, true, false); @@ -180,11 +164,37 @@ nest_selection::nest_selection(game::context& ctx): // Satisfy first person camera rig constraints satisfy_first_person_camera_rig_constraints(); - auto color_checker_archetype = ctx.resource_manager->load("color-checker.ent"); - color_checker_archetype->create(*ctx.entity_registry); - + // auto color_checker_archetype = ctx.resource_manager->load("color-checker.ent"); + // color_checker_archetype->create(*ctx.entity_registry); // auto ruler_archetype = ctx.resource_manager->load("ruler-10cm.ent"); // ruler_archetype->create(*ctx.entity_registry); + auto yucca_archetype = ctx.resource_manager->load("yucca-plant-l.ent"); + auto yucca_eid = yucca_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, yucca_eid, {0, 4, 30}); + + yucca_archetype = ctx.resource_manager->load("yucca-plant-m.ent"); + yucca_eid = yucca_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, yucca_eid, {400, 0, 200}); + + yucca_archetype = ctx.resource_manager->load("yucca-plant-s.ent"); + yucca_eid = yucca_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, yucca_eid, {-300, 3, -300}); + + auto cactus_plant_archetype = ctx.resource_manager->load("barrel-cactus-plant-l.ent"); + auto cactus_plant_eid = cactus_plant_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, cactus_plant_eid, {-100, 0, -200}); + + cactus_plant_archetype = ctx.resource_manager->load("barrel-cactus-plant-m.ent"); + cactus_plant_eid = cactus_plant_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, cactus_plant_eid, {100, -2, -70}); + + cactus_plant_archetype = ctx.resource_manager->load("barrel-cactus-plant-s.ent"); + cactus_plant_eid = cactus_plant_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, cactus_plant_eid, {50, 2, 80}); + + auto cactus_seed_archetype = ctx.resource_manager->load("barrel-cactus-seed.ent"); + auto cactus_seed_eid = cactus_seed_archetype->create(*ctx.entity_registry); + entity::command::warp_to(*ctx.entity_registry, cactus_seed_eid, {10, 5, 10}); // Queue control setup ctx.function_queue.push(std::bind(&nest_selection::enable_controls, this)); diff --git a/src/game/state/nest-selection.hpp b/src/game/state/nest-selection.hpp index d63c814..abbd5f4 100644 --- a/src/game/state/nest-selection.hpp +++ b/src/game/state/nest-selection.hpp @@ -36,11 +36,9 @@ public: private: void create_first_person_camera_rig(); void destroy_first_person_camera_rig(); - void set_first_person_camera_rig_pedestal(float pedestal); void move_first_person_camera_rig(const float2& direction, float factor); void satisfy_first_person_camera_rig_constraints(); - void enable_controls(); void disable_controls(); @@ -59,7 +57,6 @@ private: float first_person_camera_far_speed; float first_person_camera_rig_pedestal_speed; float first_person_camera_rig_pedestal; - bool mouse_look; }; diff --git a/src/game/state/nuptial-flight.cpp b/src/game/state/nuptial-flight.cpp index 907b4a9..fc63ecf 100644 --- a/src/game/state/nuptial-flight.cpp +++ b/src/game/state/nuptial-flight.cpp @@ -54,9 +54,6 @@ #include "color/color.hpp" #include "application.hpp" #include "input/mouse.hpp" -#include "geom/primitive/sphere.hpp" -#include "geom/primitive/rectangle.hpp" -#include "geom/primitive/hyperplane.hpp" #include namespace game { @@ -65,11 +62,6 @@ namespace state { nuptial_flight::nuptial_flight(game::context& ctx): game::state::base(ctx) { - geom::primitive::rectangle rect; - rect.intersects(rect); - geom::primitive::hyperplane plane; - plane.distance({0, 0, 0, 0}); - ctx.logger->push_task("Entering nuptial flight state"); // Init selected picking flag @@ -89,9 +81,6 @@ nuptial_flight::nuptial_flight(game::context& ctx): game::world::create_observer(ctx); } - // Load biome - //game::load::biome(ctx, "desert-scrub.bio"); - // Set world time game::world::set_time(ctx, 2022, 6, 21, 12, 0, 0.0); diff --git a/src/game/state/splash.cpp b/src/game/state/splash.cpp index 0ea4e7c..07ad937 100644 --- a/src/game/state/splash.cpp +++ b/src/game/state/splash.cpp @@ -49,7 +49,7 @@ splash::splash(game::context& ctx): auto splash_dimensions = splash_texture->get_dimensions(); // Construct splash billboard material - splash_billboard_material.set_flags(MATERIAL_FLAG_TRANSLUCENT); + splash_billboard_material.set_blend_mode(render::blend_mode::translucent); splash_billboard_material.set_shader_program(ctx.resource_manager->load("ui-element-textured.glsl")); splash_billboard_material.add_property("background")->set_value(splash_texture); render::material_property* splash_tint = splash_billboard_material.add_property("tint"); diff --git a/src/game/system/metamorphosis.cpp b/src/game/system/metamorphosis.cpp new file mode 100644 index 0000000..9bbb094 --- /dev/null +++ b/src/game/system/metamorphosis.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/system/metamorphosis.hpp" +#include "entity/id.hpp" + +namespace game { +namespace system { + +metamorphosis::metamorphosis(entity::registry& registry): + updatable(registry), + time_scale(1.0f) +{} + +void metamorphosis::update(double t, double dt) +{ + // registry.view().each( + // [&](entity::id entity_id, auto& component) + // { + // }); + + // registry.view().each( + // [&](entity::id entity_id, auto& component) + // { + // }); +} + +void metamorphosis::set_time_scale(float scale) +{ + time_scale = scale; +} + +} // namespace system +} // namespace game diff --git a/src/game/system/proteome.hpp b/src/game/system/metamorphosis.hpp similarity index 60% rename from src/game/system/proteome.hpp rename to src/game/system/metamorphosis.hpp index cbcc326..f96824e 100644 --- a/src/game/system/proteome.hpp +++ b/src/game/system/metamorphosis.hpp @@ -17,40 +17,33 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GAME_SYSTEM_PROTEOME_HPP -#define ANTKEEPER_GAME_SYSTEM_PROTEOME_HPP +#ifndef ANTKEEPER_GAME_SYSTEM_METAMORPHOSIS_HPP +#define ANTKEEPER_GAME_SYSTEM_METAMORPHOSIS_HPP #include "game/system/updatable.hpp" -#include "game/component/genome.hpp" -#include "entity/id.hpp" namespace game { namespace system { -/** - * Generates proteomes for every genome. - */ -class proteome: +class metamorphosis: public updatable { public: - proteome(entity::registry& registry); - ~proteome(); + metamorphosis(entity::registry& registry); + virtual void update(double t, double dt); /** - * Scales then adds the timestep `dt` to the current time, then recalculates the positions of proteomeing bodies. + * Sets the factor by which the timestep `dt` will be scaled. * - * @param t Time, in seconds. - * @param dt Delta time, in seconds. + * @param scale Factor by which to scale the timestep. */ - virtual void update(double t, double dt); + void set_time_scale(float scale); private: - void on_genome_construct(entity::registry& registry, entity::id entity_id); - void on_genome_update(entity::registry& registry, entity::id entity_id); + float time_scale; }; } // namespace system } // namespace game -#endif // ANTKEEPER_GAME_SYSTEM_PROTEOME_HPP +#endif // ANTKEEPER_GAME_SYSTEM_METAMORPHOSIS_HPP diff --git a/src/game/system/morphogenesis.hpp b/src/game/system/morphogenesis.hpp index 2980e2e..bc8d017 100644 --- a/src/game/system/morphogenesis.hpp +++ b/src/game/system/morphogenesis.hpp @@ -26,29 +26,13 @@ namespace game { namespace system { /** - * Constructs the forms of organisms from their genetic codes. - * - * Algorithm: - * - * 1. Consider inital egg cell as a sphere at the origin. - * 2. Use the marching cubes algorithm to generate a mesh from the isosurface formed by the sum of all cells. - * 3. Generate evenly-distributed points across surface area of the mesh (Poisson sample?). - * 4. For each point, evaluate morphogenic genes to determine type, direction, and color of the next cell. - * 5. Create new cells given the output of step 4. - * 6. Go to step 2 and repeat. + * Generates 3D models from genomes. */ class morphogenesis: public updatable { public: morphogenesis(entity::registry& registry); - - /** - * Scales then adds the timestep `dt` to the current time, then recalculates the positions of morphogenesising bodies. - * - * @param t Time, in seconds. - * @param dt Delta time, in seconds. - */ virtual void update(double t, double dt); private: diff --git a/src/game/system/proteome.cpp b/src/game/system/proteome.cpp deleted file mode 100644 index 93685a6..0000000 --- a/src/game/system/proteome.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2023 Christopher J. Howard - * - * This file is part of Antkeeper source code. - * - * Antkeeper source code is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Antkeeper source code 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Antkeeper source code. If not, see . - */ - -#include "game/system/proteome.hpp" -#include "game/component/proteome.hpp" -#include "genetics/sequence.hpp" -#include "genetics/standard-code.hpp" - -namespace game { -namespace system { - -proteome::proteome(entity::registry& registry): - updatable(registry) -{ - registry.on_construct().connect<&proteome::on_genome_construct>(this); - registry.on_update().connect<&proteome::on_genome_update>(this); -} - -proteome::~proteome() -{ - registry.on_construct().disconnect<&proteome::on_genome_construct>(this); - registry.on_update().disconnect<&proteome::on_genome_update>(this); -} - -void proteome::update(double t, double dt) -{} - -void proteome::on_genome_construct(entity::registry& registry, entity::id entity_id) -{ - on_genome_update(registry, entity_id); -} - -void proteome::on_genome_update(entity::registry& registry, entity::id entity_id) -{ - game::component::genome& genome = registry.get(entity_id); - - // Allocate a proteome component - game::component::proteome proteome_component; - - // For each chromosome in the genome - for (const std::string& chromosome: genome.chromosomes) - { - // Find the first ORF in the chromosome - auto orf = genetics::sequence::find_orf(chromosome.begin(), chromosome.end(), genetics::standard_code); - - // While the ORF is valid - while (orf.start != chromosome.end()) - { - // Translate the base sequence into an amino acid sequence (protein) - std::string protein; - genetics::sequence::translate(orf.start, orf.stop, std::back_inserter(protein), genetics::standard_code); - - // Append protein to the proteome - proteome_component.proteins.push_back(protein); - - // Find the next ORF - orf = genetics::sequence::find_orf(orf.stop, chromosome.end(), genetics::standard_code); - } - } - - // Assign or replace the entity's proteome component - registry.emplace_or_replace(entity_id, proteome_component); -} - -} // namespace system -} // namespace game diff --git a/src/game/world.cpp b/src/game/world.cpp index 71f3cdb..cc62594 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -418,6 +418,12 @@ void create_sun(game::context& ctx) // Create sun directional light scene object scene::directional_light* sun_light = new scene::directional_light(); sun_light->set_color({0, 0, 0}); + sun_light->set_shadow_caster(true); + sun_light->set_shadow_framebuffer(ctx.shadow_map_framebuffer); + sun_light->set_shadow_bias(1.0f); + sun_light->set_shadow_cascade_count(4); + sun_light->set_shadow_cascade_coverage(0.1f); + sun_light->set_shadow_cascade_distribution(0.8f); sun_light->update_tweens(); // Create sky ambient light scene object @@ -437,7 +443,6 @@ void create_sun(game::context& ctx) //ctx.surface_scene->add_object(bounce_light); // Pass direct sun light scene object to shadow map pass and astronomy system - ctx.surface_shadow_map_pass->set_light(sun_light); ctx.astronomy_system->set_sun_light(sun_light); ctx.astronomy_system->set_sky_light(sky_light); ctx.astronomy_system->set_bounce_light(bounce_light); diff --git a/src/render/blend-mode.hpp b/src/render/blend-mode.hpp new file mode 100644 index 0000000..10aae86 --- /dev/null +++ b/src/render/blend-mode.hpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEPER_RENDER_BLEND_MODE_HPP +#define ANTKEEPER_RENDER_BLEND_MODE_HPP + +namespace render { + +/** + * Material blend modes. + */ +enum class blend_mode +{ + /// Fully opaque. + opaque, + + /// Binary masked opacity. + masked, + + /// Translucent. + translucent +}; + +} // namespace render + +#endif // ANTKEEPER_RENDER_BLEND_MODE_HPP diff --git a/src/render/material-flags.hpp b/src/render/material-flags.hpp index 0c12260..c73d985 100644 --- a/src/render/material-flags.hpp +++ b/src/render/material-flags.hpp @@ -20,13 +20,6 @@ #ifndef ANTKEEPER_MATERIAL_FLAGS_HPP #define ANTKEEPER_MATERIAL_FLAGS_HPP -#define MATERIAL_FLAG_OPAQUE 0x00 -#define MATERIAL_FLAG_TRANSLUCENT 0x01 -#define MATERIAL_FLAG_FRONT_FACES 0x00 -#define MATERIAL_FLAG_BACK_FACES 0x02 -#define MATERIAL_FLAG_FRONT_AND_BACK_FACES 0x04 -#define MATERIAL_FLAG_SHADOW_CASTER 0x00 -#define MATERIAL_FLAG_NOT_SHADOW_CASTER 0x08 #define MATERIAL_FLAG_X_RAY 0x10 #define MATERIAL_FLAG_OUTLINE 0x20 #define MATERIAL_FLAG_VEGETATION 0x40 diff --git a/src/render/material.cpp b/src/render/material.cpp index 362cc3b..1d58fc5 100644 --- a/src/render/material.cpp +++ b/src/render/material.cpp @@ -23,7 +23,11 @@ namespace render { material::material(gl::shader_program* program): program(program), - flags(0) + flags(0), + blend_mode(blend_mode::opaque), + opacity_threshold(0.5f), + two_sided(false), + shadow_mode(shadow_mode::opaque) {} material::material(): @@ -55,6 +59,10 @@ material& material::operator=(const material& other) this->program = other.program; this->flags = other.flags; + this->blend_mode = other.blend_mode; + this->opacity_threshold = other.opacity_threshold; + this->two_sided = other.two_sided; + this->shadow_mode = other.shadow_mode; for (auto it = other.property_map.begin(); it != other.property_map.end(); ++it) { material_property_base* property = it->second->clone(); @@ -99,11 +107,31 @@ void material::set_shader_program(gl::shader_program* program) reconnect_properties(); } -void material::set_flags(std::uint32_t flags) +void material::set_flags(std::uint32_t flags) noexcept { this->flags = flags; } +void material::set_blend_mode(render::blend_mode mode) noexcept +{ + blend_mode = mode; +} + +void material::set_opacity_threshold(float threshold) noexcept +{ + opacity_threshold = threshold; +} + +void material::set_two_sided(bool two_sided) noexcept +{ + this->two_sided = two_sided; +} + +void material::set_shadow_mode(render::shadow_mode mode) noexcept +{ + shadow_mode = mode; +} + std::size_t material::reconnect_properties() { std::size_t disconnected_property_count = properties.size(); diff --git a/src/render/material.hpp b/src/render/material.hpp index 7881b82..e190de0 100644 --- a/src/render/material.hpp +++ b/src/render/material.hpp @@ -20,7 +20,9 @@ #ifndef ANTKEEPER_RENDER_MATERIAL_HPP #define ANTKEEPER_RENDER_MATERIAL_HPP +#include "render/blend-mode.hpp" #include "render/material-property.hpp" +#include "render/shadow-mode.hpp" #include "gl/shader-program.hpp" #include #include @@ -93,7 +95,37 @@ public: * * @param flags Material flags. */ - void set_flags(std::uint32_t flags); + void set_flags(std::uint32_t flags) noexcept; + + /** + * Sets the material blend mode. + * + * @param mode Blend mode. + */ + void set_blend_mode(blend_mode mode) noexcept; + + /** + * Sets the opacity mask threshold value for masked blend mode. + * + * @param threshold Opacity mask threshold value, above which the surface is considered opaque. + * + * @see render::blend_mode::masked + */ + void set_opacity_threshold(float threshold) noexcept; + + /** + * Enables or disables back-face culling of the material surface. + * + * @param two_sided `true` to disable back-face culling, or `false` to enable it. + */ + void set_two_sided(bool two_sided) noexcept; + + /** + * Sets the material shadow mode. + * + * @param mode Shadow mode. + */ + void set_shadow_mode(shadow_mode mode) noexcept; /** * Adds a material array property to the material. @@ -110,10 +142,20 @@ public: */ gl::shader_program* get_shader_program() const; - /** - * Returns the material flags. - */ - std::uint32_t get_flags() const; + /// Returns the material flags. + std::uint32_t get_flags() const noexcept; + + /// Returns the material blend mode. + blend_mode get_blend_mode() const noexcept; + + /// Returns the opacity mask threshold value. + float get_opacity_threshold() const noexcept; + + /// Returns `true` if the material surface is two-sided, and `false` otherwise. + bool is_two_sided() const noexcept; + + /// Returns the material shadow mode. + shadow_mode get_shadow_mode() const noexcept; /** * Returns the material property with the specified name, or `nullptr` if the material could not be found. @@ -135,6 +177,10 @@ private: gl::shader_program* program; std::uint32_t flags; + blend_mode blend_mode; + float opacity_threshold; + bool two_sided; + shadow_mode shadow_mode; std::list properties; std::unordered_map property_map; }; @@ -163,11 +209,31 @@ inline gl::shader_program* material::get_shader_program() const return program; } -inline std::uint32_t material::get_flags() const +inline std::uint32_t material::get_flags() const noexcept { return flags; } +inline blend_mode material::get_blend_mode() const noexcept +{ + return blend_mode; +} + +inline float material::get_opacity_threshold() const noexcept +{ + return opacity_threshold; +} + +inline bool material::is_two_sided() const noexcept +{ + return two_sided; +} + +inline shadow_mode material::get_shadow_mode() const noexcept +{ + return shadow_mode; +} + inline material_property_base* material::get_property(const std::string& name) const { if (auto it = property_map.find(name); it != property_map.end()) diff --git a/src/render/passes/material-pass.cpp b/src/render/passes/material-pass.cpp index 5bc1969..9aff24c 100644 --- a/src/render/passes/material-pass.cpp +++ b/src/render/passes/material-pass.cpp @@ -47,8 +47,6 @@ #include #include -#include "render/passes/shadow-map-pass.hpp" - namespace render { static bool operation_compare(const render::operation& a, const render::operation& b); @@ -56,9 +54,7 @@ static bool operation_compare(const render::operation& a, const render::operatio material_pass::material_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): pass(rasterizer, framebuffer), fallback_material(nullptr), - mouse_position({0.0f, 0.0f}), - shadow_map_pass(nullptr), - shadow_map(nullptr) + mouse_position({0.0f, 0.0f}) { max_ambient_light_count = MATERIAL_PASS_MAX_AMBIENT_LIGHT_COUNT; max_point_light_count = MATERIAL_PASS_MAX_POINT_LIGHT_COUNT; @@ -139,12 +135,18 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con const gl::shader_program* active_shader_program = nullptr; const render::material* active_material = nullptr; const parameter_set* parameters = nullptr; + blend_mode active_blend_mode = blend_mode::opaque; + bool active_two_sided = false; // Reset light counts ambient_light_count = 0; point_light_count = 0; directional_light_count = 0; spot_light_count = 0; + const gl::texture_2d* shadow_map_texture = nullptr; + unsigned int shadow_cascade_count = 0; + const float* shadow_splits_directional = nullptr; + const float4x4* shadow_matrices_directional = nullptr; // Collect lights const std::list* lights = ctx.collection->get_objects(scene::light::object_type_id); @@ -199,6 +201,14 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con float3 direction = static_cast(light)->get_direction_tween().interpolate(ctx.alpha); directional_light_directions[directional_light_count] = direction; + if (directional_light->is_shadow_caster()) + { + if (directional_light->get_shadow_framebuffer()) + shadow_map_texture = directional_light->get_shadow_framebuffer()->get_depth_attachment(); + shadow_cascade_count = directional_light->get_shadow_cascade_count(); + shadow_splits_directional = directional_light->get_shadow_cascade_distances(); + shadow_matrices_directional = directional_light->get_shadow_cascade_matrices(); + } if (directional_light->get_light_texture()) { @@ -255,19 +265,6 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con } } - float4x4 shadow_matrices_directional[4]; - float4 shadow_splits_directional; - - if (shadow_map_pass) - { - for (int i = 0; i < 4; ++i) - shadow_matrices_directional[i] = shadow_map_pass->get_shadow_matrices()[i]; - - // Calculate shadow map split distances - for (int i = 0; i < 4; ++i) - shadow_splits_directional[i] = shadow_map_pass->get_split_distances()[i + 1]; - } - // Sort render queue queue.sort(operation_compare); @@ -294,49 +291,43 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con { active_material = material; - // Change rasterizer state according to material flags - std::uint32_t material_flags = active_material->get_flags(); - if (active_material_flags != material_flags) + // Set blend mode + const blend_mode material_blend_mode = active_material->get_blend_mode(); + if (material_blend_mode != active_blend_mode) { - if ((material_flags & MATERIAL_FLAG_TRANSLUCENT) != (active_material_flags & MATERIAL_FLAG_TRANSLUCENT)) + if (material_blend_mode == blend_mode::translucent) { - if (material_flags & MATERIAL_FLAG_TRANSLUCENT) - { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - } - else - { - glDisable(GL_BLEND); - } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } - - if ((material_flags & MATERIAL_FLAG_BACK_FACES) != (active_material_flags & MATERIAL_FLAG_BACK_FACES)) + else if (active_blend_mode == blend_mode::translucent && (material_blend_mode == blend_mode::opaque || material_blend_mode == blend_mode::masked)) { - if (material_flags & MATERIAL_FLAG_BACK_FACES) - { - glEnable(GL_CULL_FACE); - glCullFace(GL_FRONT); - } - else - { - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - } + glDisable(GL_BLEND); } - else if ((material_flags & MATERIAL_FLAG_FRONT_AND_BACK_FACES) != (active_material_flags & MATERIAL_FLAG_FRONT_AND_BACK_FACES)) + + active_blend_mode = material_blend_mode; + } + + // Set back-face culling mode + const bool material_two_sided = active_material->is_two_sided(); + if (material_two_sided != active_two_sided) + { + if (material_two_sided) { - if (material_flags & MATERIAL_FLAG_FRONT_AND_BACK_FACES) - { - glDisable(GL_CULL_FACE); - } - else - { - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - } + glDisable(GL_CULL_FACE); } - + else + { + glEnable(GL_CULL_FACE); + } + + active_two_sided = material_two_sided; + } + + // Change rasterizer state according to material flags + std::uint32_t material_flags = active_material->get_flags(); + if (active_material_flags != material_flags) + { if ((material_flags & MATERIAL_FLAG_X_RAY) != (active_material_flags & MATERIAL_FLAG_X_RAY)) { if (material_flags & MATERIAL_FLAG_X_RAY) @@ -476,6 +467,13 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con if (parameters->directional_light_texture_opacities) parameters->directional_light_texture_opacities->upload(0, directional_light_texture_opacities, directional_light_count); + if (parameters->shadow_map_directional && shadow_map_texture) + parameters->shadow_map_directional->upload(shadow_map_texture); + if (parameters->shadow_matrices_directional) + parameters->shadow_matrices_directional->upload(0, shadow_matrices_directional, shadow_cascade_count); + if (parameters->shadow_splits_directional) + parameters->shadow_splits_directional->upload(0, shadow_splits_directional, shadow_cascade_count); + if (parameters->spot_light_count) parameters->spot_light_count->upload(spot_light_count); if (parameters->spot_light_colors) @@ -488,13 +486,6 @@ void material_pass::render(const render::context& ctx, render::queue& queue) con parameters->spot_light_attenuations->upload(0, spot_light_attenuations, spot_light_count); if (parameters->spot_light_cutoffs) parameters->spot_light_cutoffs->upload(0, spot_light_cutoffs, spot_light_count); - - if (parameters->shadow_map_directional && shadow_map) - parameters->shadow_map_directional->upload(shadow_map); - if (parameters->shadow_matrices_directional) - parameters->shadow_matrices_directional->upload(0, shadow_matrices_directional, 4); - if (parameters->shadow_splits_directional) - parameters->shadow_splits_directional->upload(shadow_splits_directional); } // Upload material properties to shader @@ -608,6 +599,9 @@ bool operation_compare(const render::operation& a, const render::operation& b) bool xray_a = a.material->get_flags() & MATERIAL_FLAG_X_RAY; bool xray_b = b.material->get_flags() & MATERIAL_FLAG_X_RAY; + const bool two_sided_a = (a.material) ? a.material->is_two_sided() : false; + const bool two_sided_b = (b.material) ? b.material->is_two_sided() : false; + if (xray_a) { if (xray_b) @@ -631,8 +625,8 @@ bool operation_compare(const render::operation& a, const render::operation& b) else { // Determine transparency - bool transparent_a = a.material->get_flags() & MATERIAL_FLAG_TRANSLUCENT; - bool transparent_b = b.material->get_flags() & MATERIAL_FLAG_TRANSLUCENT; + bool transparent_a = a.material->get_blend_mode() == blend_mode::translucent; + bool transparent_b = b.material->get_blend_mode() == blend_mode::translucent; if (transparent_a) { @@ -695,8 +689,33 @@ bool operation_compare(const render::operation& a, const render::operation& b) } else { - // Sort by VAO - return (a.vertex_array < b.vertex_array); + // A and B have different VAOs, sort by two-sided + if (two_sided_a) + { + if (two_sided_b) + { + // A and B are both two-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + else + { + // A is two-sided, B is one-sided. Render B first + return false; + } + } + else + { + if (two_sided_b) + { + // A is one-sided, B is two-sided. Render A first + return true; + } + else + { + // A and B are both one-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + } } } else diff --git a/src/render/passes/material-pass.hpp b/src/render/passes/material-pass.hpp index 42c11e3..2601bf6 100644 --- a/src/render/passes/material-pass.hpp +++ b/src/render/passes/material-pass.hpp @@ -34,8 +34,6 @@ class resource_manager; namespace render { -class shadow_map_pass; - /** * Renders scene objects using their material-specified shaders and properties. */ @@ -50,9 +48,6 @@ public: /// Sets the material to be used when a render operation is missing a material. If no fallback material is specified, render operations without materials will not be processed. void set_fallback_material(const material* fallback); - const render::shadow_map_pass* shadow_map_pass; - const gl::texture_2d* shadow_map; - private: virtual void handle_event(const mouse_moved_event& event); diff --git a/src/render/passes/shadow-map-pass.cpp b/src/render/passes/shadow-map-pass.cpp index 8cb9169..a6eaa6d 100644 --- a/src/render/passes/shadow-map-pass.cpp +++ b/src/render/passes/shadow-map-pass.cpp @@ -26,12 +26,13 @@ #include "gl/drawing-mode.hpp" #include "render/context.hpp" #include "render/material.hpp" -#include "render/material-flags.hpp" #include "scene/camera.hpp" +#include "scene/collection.hpp" #include "scene/light.hpp" #include "geom/view-frustum.hpp" #include "geom/aabb.hpp" #include "config.hpp" +#include "math/interpolation.hpp" #include "math/vector.hpp" #include "math/matrix.hpp" #include "math/quaternion.hpp" @@ -43,28 +44,8 @@ namespace render { static bool operation_compare(const render::operation& a, const render::operation& b); -void shadow_map_pass::distribute_frustum_splits(float* split_distances, std::size_t split_count, float split_scheme, float near, float far) -{ - // Calculate split distances - for (std::size_t i = 0; i < split_count; ++i) - { - float part = static_cast(i + 1) / static_cast(split_count + 1); - - // Calculate uniform split distance - float uniform_split_distance = near + (far - near) * part; - - // Calculate logarithmic split distance - float log_split_distance = near * std::pow(far / near, part); - - // Interpolate between uniform and logarithmic split distances - split_distances[i] = log_split_distance * split_scheme + uniform_split_distance * (1.0f - split_scheme); - } -} - -shadow_map_pass::shadow_map_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager): - pass(rasterizer, framebuffer), - split_scheme_weight(0.5f), - light(nullptr) +shadow_map_pass::shadow_map_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager): + pass(rasterizer, nullptr) { // Load skinned shader program unskinned_shader_program = resource_manager->load("depth-unskinned.glsl"); @@ -91,13 +72,36 @@ shadow_map_pass::~shadow_map_pass() void shadow_map_pass::render(const render::context& ctx, render::queue& queue) const { - // Abort if no directional light was set - if (!light) + // Collect lights + const std::list* lights = ctx.collection->get_objects(scene::light::object_type_id); + for (const scene::object_base* object: *lights) { - return; + // Ignore inactive lights + if (!object->is_active()) + continue; + + // Ignore non-directional lights + const scene::light* light = static_cast(object); + if (light->get_light_type() != scene::light_type::directional) + continue; + + // Ignore non-shadow casters + const scene::directional_light* directional_light = static_cast(light); + if (!directional_light->is_shadow_caster()) + continue; + + // Ignore improperly-configured lights + if (!directional_light->get_shadow_cascade_count() || !directional_light->get_shadow_framebuffer()) + continue; + + // Render cascaded shadow maps for light + render_csm(*directional_light, ctx, queue); } - - rasterizer->use_framebuffer(*framebuffer); +} + +void shadow_map_pass::render_csm(const scene::directional_light& light, const render::context& ctx, render::queue& queue) const +{ + rasterizer->use_framebuffer(*light.get_shadow_framebuffer()); // Disable blending glDisable(GL_BLEND); @@ -107,9 +111,10 @@ void shadow_map_pass::render(const render::context& ctx, render::queue& queue) c glDepthFunc(GL_LESS); glDepthMask(GL_TRUE); - // Disable face culling + // Enable back-face culling glEnable(GL_CULL_FACE); - glCullFace(GL_FRONT); + glCullFace(GL_BACK); + bool two_sided = false; // For half-z buffer //glDepthRange(-1.0f, 1.0f); @@ -117,30 +122,48 @@ void shadow_map_pass::render(const render::context& ctx, render::queue& queue) c // Get camera const scene::camera& camera = *ctx.camera; - // Calculate distances to the depth clipping planes of each frustum split - float clip_near = camera.get_clip_near_tween().interpolate(ctx.alpha); - float clip_far = camera.get_clip_far_tween().interpolate(ctx.alpha); - split_distances[0] = clip_near; - split_distances[4] = clip_far; - distribute_frustum_splits(&split_distances[1], 3, split_scheme_weight, clip_near, clip_far); + // Get distances to camera depth clipping planes + const float camera_clip_near = camera.get_clip_near_tween().interpolate(ctx.alpha); + const float camera_clip_far = camera.get_clip_far_tween().interpolate(ctx.alpha); + + // Calculate distance to shadow cascade depth clipping planes + const float shadow_clip_far = math::lerp(camera_clip_near, camera_clip_far, light.get_shadow_cascade_coverage()); + + const unsigned int cascade_count = light.get_shadow_cascade_count(); + float* cascade_distances = light.get_shadow_cascade_distances(); + float4x4* cascade_matrices = light.get_shadow_cascade_matrices(); + + // Calculate cascade far clipping plane distances + cascade_distances[cascade_count - 1] = shadow_clip_far; + for (unsigned int i = 0; i < cascade_count - 1; ++i) + { + const float weight = static_cast(i + 1) / static_cast(cascade_count); + + // Calculate linear and logarithmic distribution distances + const float linear_distance = math::lerp(camera_clip_near, shadow_clip_far, weight); + const float log_distance = math::log_lerp(camera_clip_near, shadow_clip_far, weight); + + // Interpolate between linear and logarithmic distribution distances + cascade_distances[i] = math::lerp(linear_distance, log_distance, light.get_shadow_cascade_distribution()); + } // Calculate viewports for each shadow map - const int shadow_map_resolution = std::get<0>(framebuffer->get_dimensions()) / 2; - float4 shadow_map_viewports[4]; + const int shadow_map_resolution = std::get<0>(light.get_shadow_framebuffer()->get_dimensions()) / 2; + int4 shadow_map_viewports[4]; for (int i = 0; i < 4; ++i) { int x = i % 2; int y = i / 2; - float4& viewport = shadow_map_viewports[i]; - viewport[0] = static_cast(x * shadow_map_resolution); - viewport[1] = static_cast(y * shadow_map_resolution); - viewport[2] = static_cast(shadow_map_resolution); - viewport[3] = static_cast(shadow_map_resolution); + int4& viewport = shadow_map_viewports[i]; + viewport[0] = x * shadow_map_resolution; + viewport[1] = y * shadow_map_resolution; + viewport[2] = shadow_map_resolution; + viewport[3] = shadow_map_resolution; } // Calculate a view-projection matrix from the directional light's transform - math::transform light_transform = light->get_transform_tween().interpolate(ctx.alpha); + math::transform light_transform = light.get_transform_tween().interpolate(ctx.alpha); float3 forward = light_transform.rotation * config::global_forward; float3 up = light_transform.rotation * config::global_up; float4x4 light_view = math::look_at(light_transform.translation, light_transform.translation + forward, up); @@ -159,15 +182,15 @@ void shadow_map_pass::render(const render::context& ctx, render::queue& queue) c gl::shader_program* active_shader_program = nullptr; - for (int i = 0; i < 4; ++i) + for (unsigned int i = 0; i < cascade_count; ++i) { // Set viewport for this shadow map - const float4& viewport = shadow_map_viewports[i]; + const int4& viewport = shadow_map_viewports[i]; rasterizer->set_viewport(viewport[0], viewport[1], viewport[2], viewport[3]); // Calculate projection matrix for view camera subfrustum - const float subfrustum_near = split_distances[i]; - const float subfrustum_far = split_distances[i + 1]; + const float subfrustum_near = (i) ? cascade_distances[i - 1] : camera_clip_near; + const float subfrustum_far = cascade_distances[i]; float4x4 subfrustum_projection = math::perspective_half_z(camera.get_fov(), camera.get_aspect_ratio(), subfrustum_near, subfrustum_far); // Calculate view camera subfrustum @@ -216,16 +239,31 @@ void shadow_map_pass::render(const render::context& ctx, render::queue& queue) c crop_matrix = math::translate(math::matrix4::identity(), offset) * math::scale(math::matrix4::identity(), scale); cropped_view_projection = crop_matrix * light_view_projection; - // Calculate shadow matrix - shadow_matrices[i] = bias_tile_matrices[i] * cropped_view_projection; + // Calculate world-space to cascade texture-space transformation matrix + cascade_matrices[i] = bias_tile_matrices[i] * cropped_view_projection; for (const render::operation& operation: queue) { - // Skip materials which don't cast shadows const render::material* material = operation.material; - if (material && (material->get_flags() & MATERIAL_FLAG_NOT_SHADOW_CASTER)) + if (material) { - continue; + // Skip materials which don't cast shadows + if (material->get_shadow_mode() == shadow_mode::none) + continue; + + if (material->is_two_sided() != two_sided) + { + if (material->is_two_sided()) + { + glDisable(GL_CULL_FACE); + } + else + { + glEnable(GL_CULL_FACE); + } + + two_sided = material->is_two_sided(); + } } // Switch shader programs if necessary @@ -255,28 +293,44 @@ void shadow_map_pass::render(const render::context& ctx, render::queue& queue) c } } -void shadow_map_pass::set_split_scheme_weight(float weight) -{ - split_scheme_weight = weight; -} - -void shadow_map_pass::set_light(const scene::directional_light* light) -{ - this->light = light; -} - bool operation_compare(const render::operation& a, const render::operation& b) { - // Determine transparency - bool skinned_a = (a.bone_count); - bool skinned_b = (b.bone_count); + const bool skinned_a = (a.bone_count); + const bool skinned_b = (b.bone_count); + const bool two_sided_a = (a.material) ? a.material->is_two_sided() : false; + const bool two_sided_b = (b.material) ? b.material->is_two_sided() : false; if (skinned_a) { if (skinned_b) { - // A and B are both skinned, sort by VAO - return (a.vertex_array < b.vertex_array); + // A and B are both skinned, sort by two-sided + if (two_sided_a) + { + if (two_sided_b) + { + // A and B are both two-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + else + { + // A is two-sided, B is one-sided. Render B first + return false; + } + } + else + { + if (two_sided_b) + { + // A is one-sided, B is two-sided. Render A first + return true; + } + else + { + // A and B are both one-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + } } else { @@ -293,8 +347,33 @@ bool operation_compare(const render::operation& a, const render::operation& b) } else { - // A and B are both unskinned, sort by VAO - return (a.vertex_array < b.vertex_array); + // A and B are both unskinned, sort by two-sided + if (two_sided_a) + { + if (two_sided_b) + { + // A and B are both two-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + else + { + // A is two-sided, B is one-sided. Render B first + return false; + } + } + else + { + if (two_sided_b) + { + // A is one-sided, B is two-sided. Render A first + return true; + } + else + { + // A and B are both one-sided, sort by VAO + return (a.vertex_array < b.vertex_array); + } + } } } } diff --git a/src/render/passes/shadow-map-pass.hpp b/src/render/passes/shadow-map-pass.hpp index a9d63f8..cd056ee 100644 --- a/src/render/passes/shadow-map-pass.hpp +++ b/src/render/passes/shadow-map-pass.hpp @@ -31,38 +31,42 @@ class resource_manager; namespace render { /** - * + * Renders shadow maps. */ class shadow_map_pass: public pass { public: - shadow_map_pass(gl::rasterizer* rasterizer, const gl::framebuffer* framebuffer, resource_manager* resource_manager); - virtual ~shadow_map_pass(); - virtual void render(const render::context& ctx, render::queue& queue) const final; - /** - * Sets the linear interpolation weight between uniform and logarithmic frustum-splitting schemes. + * Constructs a shadow map pass. * - * @param weight Linear interpolation weight between uniform and logarithmic frustum-splitting schemes. A value of `0.0` indicates a uniform split scheme, while `1.0` indicates a logarithmic split scheme. + * @param rasterizer Rasterizer. + * @param framebuffer Shadow map framebuffer. + * @param resource_manage Resource manager. */ - void set_split_scheme_weight(float weight); + shadow_map_pass(gl::rasterizer* rasterizer, resource_manager* resource_manager); - void set_light(const scene::directional_light* light); + /** + * Destructs a shadow map pass. + */ + virtual ~shadow_map_pass(); - const float4x4* get_shadow_matrices() const; - const float* get_split_distances() const; + /** + * Renders shadow maps for a single camera. + * + * @param ctx Render context. + * @param queue Render queue. + */ + virtual void render(const render::context& ctx, render::queue& queue) const final; private: /** - * Calculates the distances along the depth axis at which a view-frustum should be split, given a frustum-splitting scheme. + * Renders cascaded shadow maps for a single directional light. * - * @param[out] split_distances Array containing the distances to each split. - * @param split_count Number of times the frustum should be split. - * @param split_scheme Linear interpolation weight between uniform and logarithmic frustum-splitting schemes. A value of `0.0` indicates a uniform split scheme, while `1.0` indicates a logarithmic split scheme. - * @param near Distance to the near clipping plane of the frustum to be split. - * @param far Distance to the far clipping plane of the frustum to be split. + * @param light Shadow-casting directional light. + * @param ctx Render context. + * @param queue Render queue. */ - static void distribute_frustum_splits(float* split_distances, std::size_t split_count, float split_scheme, float near, float far); + void render_csm(const scene::directional_light& light, const render::context& ctx, render::queue& queue) const; gl::shader_program* unskinned_shader_program; const gl::shader_input* unskinned_model_view_projection_input; @@ -70,23 +74,9 @@ private: gl::shader_program* skinned_shader_program; const gl::shader_input* skinned_model_view_projection_input; - mutable float split_distances[5]; - mutable float4x4 shadow_matrices[4]; float4x4 bias_tile_matrices[4]; - float split_scheme_weight; - const scene::directional_light* light; }; -inline const float4x4* shadow_map_pass::get_shadow_matrices() const -{ - return shadow_matrices; -} - -inline const float* shadow_map_pass::get_split_distances() const -{ - return split_distances; -} - } // namespace render #endif // ANTKEEPER_RENDER_SHADOW_MAP_PASS_HPP diff --git a/src/game/component/select.hpp b/src/render/shadow-mode.hpp similarity index 71% rename from src/game/component/select.hpp rename to src/render/shadow-mode.hpp index 4f4a3bf..1a29709 100644 --- a/src/game/component/select.hpp +++ b/src/render/shadow-mode.hpp @@ -17,20 +17,23 @@ * along with Antkeeper source code. If not, see . */ -#ifndef ANTKEEPER_GAME_COMPONENT_SELECT_HPP -#define ANTKEEPER_GAME_COMPONENT_SELECT_HPP +#ifndef ANTKEEPER_RENDER_SHADOW_MODE_HPP +#define ANTKEEPER_RENDER_SHADOW_MODE_HPP -namespace game { -namespace component { +namespace render { -/// Allows an entity to be selected. -struct select +/** + * Material shadow casting modes. + */ +enum class shadow_mode { - /// Radius of selection bounds. - float radius; + /// Fully opaque shadow casting. + opaque, + + /// No shadows cast. + none }; -} // namespace component -} // namespace game +} // namespace render -#endif // ANTKEEPER_GAME_COMPONENT_SELECT_HPP +#endif // ANTKEEPER_RENDER_SHADOW_MODE_HPP diff --git a/src/resources/material-loader.cpp b/src/resources/material-loader.cpp index 895f422..882f65c 100644 --- a/src/resources/material-loader.cpp +++ b/src/resources/material-loader.cpp @@ -286,28 +286,42 @@ render::material* resource_loader::load(resource_manager* reso // Read blend mode std::string blend_mode; read_value(&blend_mode, json, "blend_mode"); - if (blend_mode == "alpha_blend") - flags |= MATERIAL_FLAG_TRANSLUCENT; - else - flags |= MATERIAL_FLAG_OPAQUE; + if (blend_mode == "opaque") + { + material->set_blend_mode(render::blend_mode::opaque); + } + else if (blend_mode == "masked") + { + material->set_blend_mode(render::blend_mode::masked); + } + else if (blend_mode == "translucent") + { + material->set_blend_mode(render::blend_mode::translucent); + } + + // Read opacity threshold + float opacity_threshold = 0.5f; + if (read_value(&opacity_threshold, json, "opacity_threshold")) + { + material->set_opacity_threshold(opacity_threshold); + } + + // Read two sided + bool two_sided = false; + read_value(&two_sided, json, "two_sided"); + material->set_two_sided(two_sided); // Read shadow mode std::string shadow_mode; read_value(&shadow_mode, json, "shadow_mode"); - if (shadow_mode == "none") - flags |= MATERIAL_FLAG_NOT_SHADOW_CASTER; - else - flags |= MATERIAL_FLAG_SHADOW_CASTER; - - // Read cull mode - std::string cull_mode; - read_value(&cull_mode, json, "cull_mode"); - if (cull_mode == "none") - flags |= MATERIAL_FLAG_FRONT_AND_BACK_FACES; - else if (cull_mode == "front") - flags |= MATERIAL_FLAG_BACK_FACES; - else - flags |= MATERIAL_FLAG_FRONT_FACES; + if (shadow_mode == "opaque") + { + material->set_shadow_mode(render::shadow_mode::opaque); + } + else if (shadow_mode == "none") + { + material->set_shadow_mode(render::shadow_mode::none); + } // Read depth mode std::string depth_mode; diff --git a/src/scene/directional-light.cpp b/src/scene/directional-light.cpp index 5cd8f51..1697e06 100644 --- a/src/scene/directional-light.cpp +++ b/src/scene/directional-light.cpp @@ -33,10 +33,51 @@ static float3 interpolate_direction(const float3& x, const float3& y, float a) directional_light::directional_light(): direction(config::global_forward, interpolate_direction), + shadow_caster(false), + shadow_framebuffer(nullptr), + shadow_bias(1.0f), + shadow_cascade_count(4), + shadow_cascade_coverage(1.0f), + shadow_cascade_distribution(0.8f), light_texture(nullptr), light_texture_opacity(1.0f, math::lerp), light_texture_scale({1.0f, 1.0f}, math::lerp) -{} +{ + shadow_cascade_distances.resize(shadow_cascade_count); + shadow_cascade_matrices.resize(shadow_cascade_count); +} + +void directional_light::set_shadow_caster(bool caster) noexcept +{ + shadow_caster = caster; +} + +void directional_light::set_shadow_framebuffer(const gl::framebuffer* framebuffer) noexcept +{ + shadow_framebuffer = framebuffer; +} + +void directional_light::set_shadow_bias(float bias) noexcept +{ + shadow_bias = bias; +} + +void directional_light::set_shadow_cascade_count(unsigned int count) noexcept +{ + shadow_cascade_count = count; + shadow_cascade_distances.resize(shadow_cascade_count); + shadow_cascade_matrices.resize(shadow_cascade_count); +} + +void directional_light::set_shadow_cascade_coverage(float factor) noexcept +{ + shadow_cascade_coverage = factor; +} + +void directional_light::set_shadow_cascade_distribution(float weight) noexcept +{ + shadow_cascade_distribution = weight; +} void directional_light::set_light_texture(const gl::texture_2d* texture) { diff --git a/src/scene/directional-light.hpp b/src/scene/directional-light.hpp index 2a70468..9968b8f 100644 --- a/src/scene/directional-light.hpp +++ b/src/scene/directional-light.hpp @@ -23,6 +23,7 @@ #include "scene/light.hpp" #include "gl/texture-2d.hpp" #include "utility/fundamental-types.hpp" +#include namespace scene { @@ -35,6 +36,48 @@ public: /// Creates a directional light. directional_light(); + /** + * Enables or disables shadow casting. + * + * @param caster `true` if the light should cast shadows, `false` otherwise. + */ + void set_shadow_caster(bool caster) noexcept; + + /** + * Sets the shadow map framebuffer. + * + * @param framebuffer Pointer to a shadow map framebuffer. + */ + void set_shadow_framebuffer(const gl::framebuffer* framebuffer) noexcept; + + /** + * Sets the shadow bias factor for reducing self-shadowing. + * + * @param bias Shadow bias factor. + */ + void set_shadow_bias(float bias) noexcept; + + /** + * Sets the number of shadow cascades. + * + * @param count Number of shadow cascades. + */ + void set_shadow_cascade_count(unsigned int count) noexcept; + + /** + * Sets the shadow cascade coverage factor. + * + * @param factor Percentage of the view frustum clipping range covered by shadow cascades. A value of `1.0` results in full coverage of the view frustum clipping range, `0.5` results in coverage of half of the clipping range, etc. + */ + void set_shadow_cascade_coverage(float factor) noexcept; + + /** + * Sets the shadow cascade distribution. + * + * @param weight Linear interpolation weight between uniform and logarithmic cascade distributions. A weight of `0.0` results in a uniform cascade distribution, while `1.0` results in a logarithmic distribution. + */ + void set_shadow_cascade_distribution(float weight) noexcept; + /** * Sets the light texture, also known as a gobo, cucoloris, or cookie. * @@ -62,6 +105,30 @@ public: /// Returns the normalized direction vector of the light. const float3& get_direction() const; + /// Returns `true` if the light casts shadows, `false` otherwise. + bool is_shadow_caster() const noexcept; + + /// Returns the shadow map framebuffer, of `nullptr` if no shadow map framebuffer is set. + const gl::framebuffer* get_shadow_framebuffer() const noexcept; + + /// Returns the shadow bias factor. + float get_shadow_bias() const noexcept; + + /// Returns the number of shadow cascades. + unsigned int get_shadow_cascade_count() const noexcept; + + /// Returns the shadow cascade coverage factor. + float get_shadow_cascade_coverage() const noexcept; + + /// Returns the shadow cascade distribution weight. + float get_shadow_cascade_distribution() const noexcept; + + /// Returns the array of shadow cascade far clipping plane distances. + float* get_shadow_cascade_distances() const noexcept; + + /// Returns the array of world-space to cascade texture-space transformation matrices. + float4x4* get_shadow_cascade_matrices() const noexcept; + /// Returns the light texture for this light, or `nullptr` if no light texture is set. const gl::texture_2d* get_light_texture() const; @@ -87,9 +154,20 @@ private: virtual void transformed(); tween direction; + + bool shadow_caster; + const gl::framebuffer* shadow_framebuffer; + float shadow_bias; + unsigned int shadow_cascade_count; + float shadow_cascade_coverage; + float shadow_cascade_distribution; + mutable std::vector shadow_cascade_distances; + mutable std::vector shadow_cascade_matrices; + const gl::texture_2d* light_texture; tween light_texture_opacity; tween light_texture_scale; + }; inline light_type directional_light::get_light_type() const @@ -102,6 +180,46 @@ inline const float3& directional_light::get_direction() const return direction[1]; } +inline bool directional_light::is_shadow_caster() const noexcept +{ + return shadow_caster; +} + +inline const gl::framebuffer* directional_light::get_shadow_framebuffer() const noexcept +{ + return shadow_framebuffer; +} + +inline float directional_light::get_shadow_bias() const noexcept +{ + return shadow_bias; +} + +inline unsigned int directional_light::get_shadow_cascade_count() const noexcept +{ + return shadow_cascade_count; +} + +inline float directional_light::get_shadow_cascade_coverage() const noexcept +{ + return shadow_cascade_coverage; +} + +inline float directional_light::get_shadow_cascade_distribution() const noexcept +{ + return shadow_cascade_distribution; +} + +inline float* directional_light::get_shadow_cascade_distances() const noexcept +{ + return shadow_cascade_distances.data(); +} + +inline float4x4* directional_light::get_shadow_cascade_matrices() const noexcept +{ + return shadow_cascade_matrices.data(); +} + inline const gl::texture_2d* directional_light::get_light_texture() const { return light_texture;