Browse Source

Add outline pass

master
C. J. Howard 1 year ago
parent
commit
752991b0f5
14 changed files with 271 additions and 50 deletions
  1. +0
    -1
      CMakeLists.txt
  2. +8
    -2
      src/game/bootloader.cpp
  3. +2
    -0
      src/game/components/tool-component.hpp
  4. +13
    -0
      src/game/entity-commands.cpp
  5. +1
    -0
      src/game/entity-commands.hpp
  6. +2
    -0
      src/game/game-context.hpp
  7. +32
    -40
      src/game/states/play-state.cpp
  8. +9
    -3
      src/game/systems/tool-system.cpp
  9. +1
    -3
      src/renderer/passes/bloom-pass.cpp
  10. +1
    -1
      src/renderer/passes/clear-pass.cpp
  11. +18
    -0
      src/renderer/passes/material-pass.cpp
  12. +112
    -0
      src/renderer/passes/outline-pass.cpp
  13. +54
    -0
      src/renderer/passes/outline-pass.hpp
  14. +18
    -0
      src/resources/entity-archetype-loader.cpp

+ 0
- 1
CMakeLists.txt View File

@ -14,7 +14,6 @@ find_package(SDL2 REQUIRED COMPONENTS SDL2::SDL2-static SDL2::SDL2main CONFIG)
find_package(OpenAL REQUIRED CONFIG)
find_library(physfs REQUIRED NAMES physfs-static PATHS "${CMAKE_PREFIX_PATH}/lib")
# Determine dependencies
set(STATIC_LIBS
dr_wav

+ 8
- 2
src/game/bootloader.cpp View File

@ -43,6 +43,7 @@
#include "renderer/passes/clear-pass.hpp"
#include "renderer/passes/final-pass.hpp"
#include "renderer/passes/material-pass.hpp"
#include "renderer/passes/outline-pass.hpp"
#include "renderer/passes/shadow-map-pass.hpp"
#include "renderer/passes/sky-pass.hpp"
#include "renderer/simple-render-pass.hpp"
@ -439,13 +440,14 @@ void setup_rendering(game_context* ctx)
ctx->framebuffer_hdr_color->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
ctx->framebuffer_hdr_color->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
ctx->framebuffer_hdr_color->set_max_anisotropy(0.0f);
ctx->framebuffer_hdr_depth = new texture_2d(viewport_dimensions[0], viewport_dimensions[1], pixel_type::float_32, pixel_format::d);
ctx->framebuffer_hdr_depth = new texture_2d(viewport_dimensions[0], viewport_dimensions[1], pixel_type::uint_32, pixel_format::ds);
ctx->framebuffer_hdr_depth->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
ctx->framebuffer_hdr_depth->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
ctx->framebuffer_hdr_depth->set_max_anisotropy(0.0f);
ctx->framebuffer_hdr = new framebuffer(viewport_dimensions[0], viewport_dimensions[1]);
ctx->framebuffer_hdr->attach(framebuffer_attachment_type::color, ctx->framebuffer_hdr_color);
ctx->framebuffer_hdr->attach(framebuffer_attachment_type::depth, ctx->framebuffer_hdr_depth);
ctx->framebuffer_hdr->attach(framebuffer_attachment_type::stencil, ctx->framebuffer_hdr_depth);
// Create shadow map framebuffer
int shadow_map_resolution = ctx->config->get<int>("shadow_map_resolution");
@ -475,13 +477,16 @@ void setup_rendering(game_context* ctx)
ctx->overworld_shadow_map_pass = new shadow_map_pass(ctx->rasterizer, ctx->shadow_map_framebuffer, ctx->resource_manager);
ctx->overworld_shadow_map_pass->set_split_scheme_weight(0.75f);
ctx->overworld_clear_pass = new clear_pass(ctx->rasterizer, ctx->framebuffer_hdr);
ctx->overworld_clear_pass->set_cleared_buffers(true, true, false);
ctx->overworld_clear_pass->set_cleared_buffers(false, true, true);
ctx->overworld_sky_pass = new sky_pass(ctx->rasterizer, ctx->framebuffer_hdr, ctx->resource_manager);
ctx->overworld_sky_pass->set_enabled(false);
ctx->overworld_material_pass = new material_pass(ctx->rasterizer, ctx->framebuffer_hdr, ctx->resource_manager);
ctx->overworld_material_pass->set_fallback_material(ctx->fallback_material);
ctx->overworld_material_pass->shadow_map_pass = ctx->overworld_shadow_map_pass;
ctx->overworld_material_pass->shadow_map = ctx->shadow_map_depth_texture;
ctx->overworld_outline_pass = new outline_pass(ctx->rasterizer, ctx->framebuffer_hdr, ctx->resource_manager);
ctx->overworld_outline_pass->set_outline_width(0.25f);
ctx->overworld_outline_pass->set_outline_color(float4{1.0f, 1.0f, 1.0f, 1.0f});
ctx->overworld_bloom_pass = new bloom_pass(ctx->rasterizer, ctx->framebuffer_bloom, ctx->resource_manager);
ctx->overworld_bloom_pass->set_source_texture(ctx->framebuffer_hdr_color);
ctx->overworld_bloom_pass->set_brightness_threshold(1.0f);
@ -496,6 +501,7 @@ void setup_rendering(game_context* ctx)
ctx->overworld_compositor->add_pass(ctx->overworld_clear_pass);
ctx->overworld_compositor->add_pass(ctx->overworld_sky_pass);
ctx->overworld_compositor->add_pass(ctx->overworld_material_pass);
ctx->overworld_compositor->add_pass(ctx->overworld_outline_pass);
ctx->overworld_compositor->add_pass(ctx->overworld_bloom_pass);
ctx->overworld_compositor->add_pass(ctx->overworld_final_pass);

+ 2
- 0
src/game/components/tool-component.hpp View File

@ -25,6 +25,8 @@ namespace ecs {
struct tool_component
{
bool active;
float hover_distance;
bool heliotropic;
};
} // namespace ecs

+ 13
- 0
src/game/entity-commands.cpp View File

@ -21,6 +21,8 @@
#include "game/components/model-component.hpp"
#include "game/components/transform-component.hpp"
#include "game/components/copy-transform-component.hpp"
#include "game/components/snap-component.hpp"
#include <limits>
namespace ec {
@ -63,6 +65,17 @@ void set_transform(entt::registry& registry, entt::entity eid, const math::trans
}
}
void place(entt::registry& registry, entt::entity eid, const float2& translation)
{
snap_component component;
component.warp = true;
component.relative = false;
component.autoremove = true;
component.ray.origin = {translation[0], 10000.0f, translation[1]};
component.ray.direction = {0.0f, -1.0f, 0.0f};
registry.assign_or_replace<snap_component>(eid, component);
}
void assign_render_layers(entt::registry& registry, entt::entity eid, unsigned int layers)
{
if (registry.has<model_component>(eid))

+ 1
- 0
src/game/entity-commands.hpp View File

@ -30,6 +30,7 @@ void translate(entt::registry& registry, entt::entity eid, const float3& transla
void move_to(entt::registry& registry, entt::entity eid, const float3& position);
void warp_to(entt::registry& registry, entt::entity eid, const float3& position);
void set_transform(entt::registry& registry, entt::entity eid, const math::transform<float>& transform, bool warp = false);
void place(entt::registry& registry, entt::entity eid, const float2& translation);
void assign_render_layers(entt::registry& registry, entt::entity eid, unsigned int layers);
void bind_transform(entt::registry& registry, entt::entity source_eid, entt::entity target_eid);

+ 2
- 0
src/game/game-context.hpp View File

@ -81,6 +81,7 @@ class model_instance;
class input_event_router;
class input_mapper;
class cli;
class outline_pass;
template <typename T> class animation;
template <typename T> class material_property;
template <typename T> class tween;
@ -152,6 +153,7 @@ struct game_context
material_pass* overworld_material_pass;
material_pass* ui_material_pass;
material_pass* underworld_material_pass;
outline_pass* overworld_outline_pass;
shadow_map_pass* overworld_shadow_map_pass;
simple_render_pass* underworld_final_pass;
sky_pass* overworld_sky_pass;

+ 32
- 40
src/game/states/play-state.cpp View File

@ -62,6 +62,8 @@ void play_state_enter(game_context* ctx)
sky_pass->set_sun_color({2.0f, 2.0f, 2.0f});
sky_pass->set_horizon_color(to_linear(float3{81.0f, 162.0f, 219.0f} / 255.0f));
sky_pass->set_zenith_color(to_linear(float3{7.0f, 134.0f, 206.0f} / 255.0f));
//sky_pass->set_horizon_color(float3{0.002f, 0.158f, 0.250f});
//sky_pass->set_zenith_color(float3{0.002f, 0.158f, 0.250f});
resource_manager* resource_manager = ctx->resource_manager;
entt::registry& ecs_registry = *ctx->ecs_registry;
@ -71,36 +73,44 @@ void play_state_enter(game_context* ctx)
ecs::archetype* maple_tree_archetype = resource_manager->load<ecs::archetype>("maple-tree.ent");
ecs::archetype* nest_archetype = resource_manager->load<ecs::archetype>("harvester-nest.ent");
ecs::archetype* samara_archetype = resource_manager->load<ecs::archetype>("samara.ent");
ecs::archetype* forceps_archetype = resource_manager->load<ecs::archetype>("lens.ent");
ecs::archetype* forceps_archetype = resource_manager->load<ecs::archetype>("forceps.ent");
ecs::archetype* lens_archetype = resource_manager->load<ecs::archetype>("lens.ent");
ecs::archetype* brush_archetype = resource_manager->load<ecs::archetype>("brush.ent");
ecs::archetype* larva_archetype = resource_manager->load<ecs::archetype>("larva.ent");
ecs::archetype* pebble_archetype = resource_manager->load<ecs::archetype>("pebble.ent");
ecs::archetype* flashlight_archetype = resource_manager->load<ecs::archetype>("flashlight.ent");
ecs::archetype* flashlight_light_cone_archetype = resource_manager->load<ecs::archetype>("flashlight-light-cone.ent");
// Create flashlight + light cone compund entity
// Create tools
forceps_archetype->assign(ecs_registry, ctx->forceps_entity);
lens_archetype->assign(ecs_registry, ctx->lens_entity);
brush_archetype->assign(ecs_registry, ctx->brush_entity);
// Create flashlight and light cone, bind light cone to flashlight, and move both to underworld scene
flashlight_archetype->assign(ecs_registry, ctx->flashlight_entity);
auto flashlight_light_cone = flashlight_light_cone_archetype->create(ecs_registry);
ec::bind_transform(ecs_registry, flashlight_light_cone, ctx->flashlight_entity);
ec::assign_render_layers(ecs_registry, ctx->flashlight_entity, 2);
ec::assign_render_layers(ecs_registry, flashlight_light_cone, 2);
ecs::snap_component snap;
snap.warp = true;
snap.relative = false;
snap.autoremove = true;
auto ant_hill_entity = ant_hill_archetype->create(ecs_registry);
snap.ray.origin = {0, 10000, 0};
snap.ray.direction = {0, -1, 0};
ecs_registry.assign<ecs::snap_component>(ant_hill_entity, snap);
// Make lens tool's model instance unculled, so its shadow is always visible.
model_instance* lens_model_instance = ctx->render_system->get_model_instance(ctx->lens_entity);
if (lens_model_instance)
{
lens_model_instance->set_culling_mask(&ctx->no_cull);
}
// Activate brush tools
auto& active_tool_component = ecs_registry.get<ecs::tool_component>(ctx->lens_entity);
active_tool_component.active = true;
// Create ant-hill
auto ant_hill_entity = ant_hill_archetype->create(ecs_registry);
ec::place(ecs_registry, ant_hill_entity, {0, 0});
// Generate pebbles
float pebble_radius = 300.0f;
int pebble_count = 100;
for (int i = 0; i < pebble_count; ++i)
{
float x = math::random(-pebble_radius, pebble_radius);
@ -113,17 +123,17 @@ void play_state_enter(game_context* ctx)
transform.transform.rotation = math::angle_axis(math::random(0.0f, math::two_pi<float>), {0, 1, 0});
transform.transform.scale = float3{1, 1, 1} * math::random(0.75f, 1.25f);
snap.ray.origin = {x, 10000, z};
ecs_registry.assign<ecs::snap_component>(pebble_entity, snap);
ec::place(ecs_registry, pebble_entity, {x, z});
}
// Create maple tree
auto maple_tree_entity = maple_tree_archetype->create(ecs_registry);
snap.ray.origin = {300, 10000, 200};
snap.ray.direction = {0, -1, 0};
ecs_registry.assign<ecs::snap_component>(maple_tree_entity, snap);
ec::place(ecs_registry, maple_tree_entity, {300, 200});
// Creat nest
auto nest_entity = nest_archetype->create(ecs_registry);
// Create terrain
int terrain_radius = 2;
for (int x = -terrain_radius; x <= terrain_radius; ++x)
{
@ -138,6 +148,7 @@ void play_state_enter(game_context* ctx)
}
}
// Create samaras
for (int i = 0; i < 15; ++i)
{
auto samara_entity = samara_archetype->create(ecs_registry);
@ -156,13 +167,6 @@ void play_state_enter(game_context* ctx)
ecs_registry.assign_or_replace<ecs::samara_component>(samara_entity, samara_component);
}
/*
ecs::archetype* grass_archetype = resource_manager->load<ecs::archetype>("grassland-grass.ent");
auto grass_entity_1 = grass_archetype->create(ecs_registry);
auto grass_entity_2 = grass_archetype->create(ecs_registry);
ecs_registry.get<ecs::transform_component>(grass_entity_2).transform.rotation = math::angle_axis(math::radians(120.0f), float3{0, 1, 0});
*/
// Setup camera focal point
ecs::transform_component focal_point_transform;
@ -174,7 +178,6 @@ void play_state_enter(game_context* ctx)
focal_point_snap.warp = false;
focal_point_snap.relative = true;
focal_point_snap.autoremove = false;
ecs_registry.assign_or_replace<ecs::transform_component>(ctx->focal_point_entity, focal_point_transform);
ecs_registry.assign_or_replace<ecs::camera_follow_component>(ctx->focal_point_entity, focal_point_follow);
ecs_registry.assign_or_replace<ecs::snap_component>(ctx->focal_point_entity, focal_point_snap);
@ -185,17 +188,6 @@ void play_state_enter(game_context* ctx)
// Create forceps tool
auto forceps_entity = forceps_archetype->create(ecs_registry);
ecs::tool_component forceps_tool_component;
forceps_tool_component.active = true;
ecs_registry.assign<ecs::tool_component>(forceps_entity, forceps_tool_component);
model_instance* forceps_model_instance = ctx->render_system->get_model_instance(forceps_entity);
if (forceps_model_instance)
{
forceps_model_instance->set_culling_mask(&ctx->no_cull);
}
ctx->overworld_scene->update_tweens();
// Allocate a nest

+ 9
- 3
src/game/systems/tool-system.cpp View File

@ -102,16 +102,22 @@ void tool_system::update(double t, double dt)
registry.view<tool_component, transform_component>().each(
[&](auto entity, auto& tool, auto& transform)
{
/*
if (registry.has<model_component>(entity))
{
}*/
if (!tool.active)
return;
if (intersection)
{
transform.transform.translation = pick + float3{0, 15, 0};
transform.transform.translation = pick + float3{0, tool.hover_distance, 0};
}
math::quaternion<float> rotation = math::angle_axis(orbit_cam->get_azimuth() + pick_angle, float3{0, 1, 0});
transform.transform.rotation = rotation;
//math::quaternion<float> rotation = math::angle_axis(orbit_cam->get_azimuth() + pick_angle, float3{0, 1, 0});
//transform.transform.rotation = rotation;
});
was_pick_enabled = pick_enabled;

+ 1
- 3
src/renderer/passes/bloom-pass.cpp View File

@ -105,9 +105,7 @@ bloom_pass::~bloom_pass()
}
void bloom_pass::render(render_context* context) const
{
rasterizer->use_framebuffer(*framebuffer);
{
glDisable(GL_BLEND);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);

+ 1
- 1
src/renderer/passes/clear-pass.cpp View File

@ -42,7 +42,7 @@ void clear_pass::render(render_context* context) const
if (clear_depth_buffer)
glDepthMask(GL_TRUE);
if (clear_stencil_buffer)
glStencilMask(GL_TRUE);
glStencilMask(0xFF);
rasterizer->use_framebuffer(*framebuffer);

+ 18
- 0
src/renderer/passes/material-pass.cpp View File

@ -109,6 +109,8 @@ void material_pass::render(render_context* context) const
glDepthFunc(GL_LESS);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glDisable(GL_STENCIL_TEST);
glStencilMask(0x00);
auto viewport = framebuffer->get_dimensions();
rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport));
@ -324,6 +326,22 @@ void material_pass::render(render_context* context) const
glEnable(GL_DEPTH_TEST);
}
}
if ((material_flags & MATERIAL_FLAG_OUTLINE) != (active_material_flags & MATERIAL_FLAG_OUTLINE))
{
if (material_flags & MATERIAL_FLAG_OUTLINE)
{
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilMask(0xFF);
}
else
{
glDisable(GL_STENCIL_TEST);
glStencilMask(0x00);
}
}
active_material_flags = material_flags;

+ 112
- 0
src/renderer/passes/outline-pass.cpp View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
*/
#include "renderer/passes/outline-pass.hpp"
#include "resources/resource-manager.hpp"
#include "rasterizer/rasterizer.hpp"
#include "rasterizer/framebuffer.hpp"
#include "rasterizer/shader-program.hpp"
#include "rasterizer/shader-input.hpp"
#include "rasterizer/vertex-buffer.hpp"
#include "rasterizer/vertex-array.hpp"
#include "rasterizer/vertex-attribute-type.hpp"
#include "rasterizer/drawing-mode.hpp"
#include "renderer/vertex-attributes.hpp"
#include "renderer/render-context.hpp"
#include "renderer/material.hpp"
#include "renderer/material-flags.hpp"
#include "scene/camera.hpp"
#include "math/math.hpp"
#include <cmath>
#include <glad/glad.h>
outline_pass::outline_pass(::rasterizer* rasterizer, const ::framebuffer* framebuffer, resource_manager* resource_manager):
render_pass(rasterizer, framebuffer),
outline_shader(nullptr)
{
// Load outline shader
outline_shader = resource_manager->load<shader_program>("outline-unskinned.glsl");
model_view_projection_input = outline_shader->get_input("model_view_projection");
outline_width_input = outline_shader->get_input("outline_width");
outline_color_input = outline_shader->get_input("outline_color");
}
outline_pass::~outline_pass()
{}
void outline_pass::render(render_context* context) const
{
rasterizer->use_framebuffer(*framebuffer);
if (outline_color.w < 1.0f)
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
else
{
glDisable(GL_BLEND);
}
glDisable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilMask(0x00);
// Determine viewport based on framebuffer resolution
auto viewport = framebuffer->get_dimensions();
rasterizer->set_viewport(0, 0, std::get<0>(viewport), std::get<1>(viewport));
float4x4 view = context->camera->get_view_tween().interpolate(context->alpha);
float4x4 view_projection = context->camera->get_view_projection_tween().interpolate(context->alpha);
float4x4 model_view_projection;
// Perform iterative blur subpass
rasterizer->use_program(*outline_shader);
outline_width_input->upload(outline_width);
outline_color_input->upload(outline_color);
// Render outlines
for (const render_operation& operation: context->operations)
{
const ::material* material = operation.material;
if (!material || !(material->get_flags() & MATERIAL_FLAG_OUTLINE))
continue;
model_view_projection = view_projection * operation.transform;
model_view_projection_input->upload(model_view_projection);
rasterizer->draw_arrays(*operation.vertex_array, operation.drawing_mode, operation.start_index, operation.index_count);
}
glDisable(GL_STENCIL_TEST);
}
void outline_pass::set_outline_width(float width)
{
outline_width = width;
}
void outline_pass::set_outline_color(const float4& color)
{
outline_color = color;
}

+ 54
- 0
src/renderer/passes/outline-pass.hpp View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_OUTLINE_PASS_HPP
#define ANTKEEPER_OUTLINE_PASS_HPP
#include "renderer/render-pass.hpp"
#include "utility/fundamental-types.hpp"
class shader_program;
class shader_input;
class resource_manager;
/**
*
*/
class outline_pass: public render_pass
{
public:
outline_pass(::rasterizer* rasterizer, const ::framebuffer* framebuffer, resource_manager* resource_manager);
virtual ~outline_pass();
virtual void render(render_context* context) const final;
void set_outline_width(float width);
void set_outline_color(const float4& color);
private:
shader_program* outline_shader;
const shader_input* model_view_projection_input;
const shader_input* outline_width_input;
const shader_input* outline_color_input;
float outline_width;
float4 outline_color;
};
#endif // ANTKEEPER_OUTLINE_PASS_HPP

+ 18
- 0
src/resources/entity-archetype-loader.cpp View File

@ -27,6 +27,7 @@
#include "game/components/transform-component.hpp"
#include "game/components/model-component.hpp"
#include "game/components/nest-component.hpp"
#include "game/components/tool-component.hpp"
#include "entity/archetype.hpp"
#include "game/behavior/ebt.hpp"
#include <sstream>
@ -123,6 +124,22 @@ static bool load_terrain_component(archetype& archetype, const std::vector
return true;
}
static bool load_tool_component(archetype& archetype, const std::vector<std::string>& parameters)
{
if (parameters.size() != 4)
{
throw std::runtime_error("load_tool_component(): Invalid parameter count.");
}
tool_component component;
component.active = static_cast<bool>(std::stoi(parameters[1]));
component.hover_distance = std::stof(parameters[2]);
component.heliotropic = static_cast<bool>(std::stoi(parameters[3]));
archetype.set<tool_component>(component);
return true;
}
static bool load_transform_component(archetype& archetype, const std::vector<std::string>& parameters)
{
if (parameters.size() != 11)
@ -163,6 +180,7 @@ static bool load_component(archetype& archetype, resource_manager& resource_mana
if (parameters[0] == "model") return load_model_component(archetype, resource_manager, parameters);
if (parameters[0] == "nest") return load_nest_component(archetype, parameters);
if (parameters[0] == "terrain") return load_terrain_component(archetype, parameters);
if (parameters[0] == "tool") return load_tool_component(archetype, parameters);
if (parameters[0] == "transform") return load_transform_component(archetype, parameters);
std::string message = std::string("load_component(): Unknown component type \"") + parameters[0] + std::string("\"");

Loading…
Cancel
Save