MushMachine/framework/opengl_renderer/test/lite_particles2d.cpp

378 lines
9.7 KiB
C++

#include <gtest/gtest.h>
#include <mm/engine.hpp>
#include <mm/services/filesystem.hpp>
#include <mm/services/sdl_service.hpp>
#include <mm/services/organizer_scene.hpp>
#include <mm/services/opengl_renderer.hpp>
#include <mm/services/count_down.hpp>
#include <mm/fs_const_archiver.hpp>
#include <entt/entity/registry.hpp>
#include <entt/entity/organizer.hpp>
#include <entt/core/hashed_string.hpp>
#include <mm/opengl/render_tasks/clear.hpp>
#include <mm/opengl/render_tasks/lite_particles2d.hpp>
#include <mm/opengl/bloom.hpp>
#include <mm/opengl/render_tasks/composition.hpp>
// ctx
#include <mm/opengl/components/lite_particles2d.hpp>
#include <mm/components/time_delta.hpp>
#include <mm/opengl/camera_3d.hpp>
// components
#include <mm/components/position2d.hpp>
#include <mm/resource_manager.hpp>
#include <mm/opengl/texture_loader.hpp>
#include <mm/opengl/lite_particles2d_type_loader.hpp>
#include <mm/opengl/fbo_builder.hpp>
#include <mm/random/srng.hpp>
#include <glm/vec2.hpp>
#include <glm/vec4.hpp>
#include <glm/common.hpp>
#include <glm/gtc/constants.hpp>
#include <glm/trigonometric.hpp>
#include <mm/scalar_range2.hpp>
#include <vector>
#include <string>
using namespace entt::literals;
namespace Components {
// or what ever
struct LiteParticles2DEmitter {
float age {0.f}; // this changes each tick, if >=1, spawn and reset to 0
float age_delta {1.f}; // times per sec
MM::ScalarRange2<uint16_t> particle_count {1u, 1u};
// particles data
std::string p_type {};
MM::ScalarRange2<float> p_pos_x {0.f, 0.f};
MM::ScalarRange2<float> p_pos_y {0.f, 0.f};
MM::ScalarRange2<float> p_dir {0.f, 0.f}; // 0-1, not rad
MM::ScalarRange2<float> p_dir_force {0.f, 0.f};
MM::ScalarRange2<float> p_age {0.f, 0.f};
};
} // Components
namespace Systems {
void lite_particles2d_emit(
entt::view<entt::get_t<
const MM::Components::Position2D,
Components::LiteParticles2DEmitter
>> view,
const MM::Components::TimeDelta& td,
MM::Components::LiteParticles2DUploadQueue& lp_uq,
MM::Random::SRNG& rng
) {
view.each([&lp_uq, &td, &rng](const auto& pos, Components::LiteParticles2DEmitter& lpe) {
lpe.age += lpe.age_delta * td.tickDelta;
if (lpe.age < 1.f) {
return;
}
lpe.age = 0.f; // TODO: dont discard ?
const auto type = entt::hashed_string::value(lpe.p_type.c_str());
const size_t count = rng.range(lpe.particle_count);
for (size_t i = 0; i < count; i++) {
float dir = rng.range(lpe.p_dir) * glm::two_pi<float>();
lp_uq.queue.push(MM::Components::LiteParticle2D{
type, // type
//{rng.negOneToOne() * 30.f, rng.negOneToOne() * 30.f}, // pos
pos.pos + /*lpe.pos_offset +*/ glm::vec2{rng.range(lpe.p_pos_x), rng.range(lpe.p_pos_y)}, // pos
glm::vec2{glm::cos(dir), glm::sin(dir)} * rng.range(lpe.p_dir_force), // vel
rng.range(lpe.p_age) // age
});
}
});
}
} // Systems
const char* argv0;
static void setup_textures(MM::Engine& engine) {
auto& rm_t = MM::ResourceManager<MM::OpenGL::Texture>::ref();
auto [w, h] = engine.getService<MM::Services::SDLService>().getWindowSize();
// we dont have a gbuffer in this example
{ // gbuffer
// depth
#ifdef MM_OPENGL_3_GLES
rm_t.reload<MM::OpenGL::TextureLoaderEmpty>(
"depth",
GL_DEPTH_COMPONENT24, // d16 is the onlyone for gles 2 (TODO: test for 3)
w, h,
GL_DEPTH_COMPONENT, GL_UNSIGNED_INT
);
#else
rm_t.reload<MM::OpenGL::TextureLoaderEmpty>(
"depth",
GL_DEPTH_COMPONENT32F, // TODO: stencil?
w, h,
GL_DEPTH_COMPONENT, GL_FLOAT
);
#endif
}
const float render_scale = 1.f;
// hdr color gles3 / webgl2
rm_t.reload<MM::OpenGL::TextureLoaderEmpty>(
"hdr_color",
GL_RGBA16F,
w * render_scale, h * render_scale,
GL_RGBA,
GL_HALF_FLOAT
);
{ // filter
rm_t.get("hdr_color"_hs)->bind(0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
static void setup_fbos(MM::Engine& engine) {
auto& rs = engine.getService<MM::Services::OpenGLRenderer>();
auto& rm_t = MM::ResourceManager<MM::OpenGL::Texture>::ref();
const float render_scale = 1.f;
rs.targets["game_view"] = MM::OpenGL::FBOBuilder::start()
.attachTexture(rm_t.get("hdr_color"_hs), GL_COLOR_ATTACHMENT0)
.attachTexture(rm_t.get("depth"_hs), GL_DEPTH_ATTACHMENT)
.setResizeFactors(render_scale, render_scale)
.setResize(true)
.finish();
assert(rs.targets["game_view"]);
}
void setup_particles_types(MM::Engine& engine) {
auto& lpt_rm = MM::ResourceManager<MM::OpenGL::LiteParticles2DType>::ref();
lpt_rm.load<MM::OpenGL::LiteParticles2DTypeLoaderJson>("MM::spark1",
R"({
"compute": {
"age_delta": 2.0,
"force_vec": { "x": 0.0, "y": -5.0 },
"turbulence": 1.0,
"turbulence_individuality": 1.0,
"turbulence_noise_scale": 1.0,
"turbulence_time_scale": 1.0,
"dampening": 1.0
},
"render": {
"color_start": {
"x": 6.0,
"y": 6.0,
"z": 1.8,
"w": 1.0
},
"color_end": {
"x": 1.5,
"y": 1.0,
"z": 0.3,
"w": 1.0
},
"size_start": 0.01,
"size_end": 0.002
}
})"_json);
// mount into vfx
FS_CONST_MOUNT_FILE("/particles/lite_particles2d/fire1.json",
R"({
"compute": {
"age_delta": 1.0,
"force_vec": { "x": 0.0, "y": 5.0 },
"turbulence": 5.0,
"turbulence_individuality": 1.0,
"turbulence_noise_scale": 1.0,
"turbulence_time_scale": 1.0,
"dampening": 0.0
},
"render": {
"color_start": {
"x": 3.0,
"y": 2.1,
"z": 1.5,
"w": 0.8
},
"color_end": {
"x": 3.0,
"y": 1.1,
"z": 1.0,
"w": 0.0
},
"size_start": 0.15,
"size_end": 0.4
}
})");
// and load it
lpt_rm.load<MM::OpenGL::LiteParticles2DTypeLoaderFile>("MM::fire1", engine, "/particles/lite_particles2d/fire1.json");
}
TEST(lite_particles2d, it) {
MM::Engine engine;
auto& sdl_ss = engine.addService<MM::Services::SDLService>();
ASSERT_TRUE(engine.enableService<MM::Services::SDLService>());
sdl_ss.createGLWindow("lite_particles2d", 1280, 720);
engine.addService<MM::Services::OrganizerSceneService>();
ASSERT_TRUE(engine.enableService<MM::Services::OrganizerSceneService>());
bool provide_ret = engine.provide<MM::Services::SceneServiceInterface, MM::Services::OrganizerSceneService>();
ASSERT_TRUE(provide_ret);
auto& scene = engine.tryService<MM::Services::SceneServiceInterface>()->getScene();
engine.addService<MM::Services::FilesystemService>(argv0, "lite_particles2d");
ASSERT_TRUE(engine.enableService<MM::Services::FilesystemService>());
auto& rs = engine.addService<MM::Services::OpenGLRenderer>();
ASSERT_TRUE(engine.enableService<MM::Services::OpenGLRenderer>());
#ifdef MM_AUTOTEST
engine.addService<MM::Services::CountDown>(50); // 50 frames
ASSERT_TRUE(engine.enableService<MM::Services::CountDown>());
#endif
// load particle types
// before addRenderTask<LiteParticle2D>
// OR
// call LiteParticle2D::resetTypeBuffers()
setup_particles_types(engine);
{ // setup rendering
// TODO: split vertically
setup_textures(engine);
setup_fbos(engine);
// clear
auto& clear_opaque = rs.addRenderTask<MM::OpenGL::RenderTasks::Clear>(engine);
clear_opaque.target_fbo = "game_view";
// clears all color attachments
clear_opaque.r = 0.f;
clear_opaque.g = 0.f;
clear_opaque.b = 0.f;
clear_opaque.a = 1.f;
clear_opaque.mask = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT;
{ // render
//MM::OpenGL::RenderTasks::SimpleRect& bsrr_rend = rs.addRenderTask<MM::OpenGL::RenderTasks::SimpleRect>(engine);
//bsrr_rend.target_fbo = "game_view";
auto& lprt = rs.addRenderTask<MM::OpenGL::RenderTasks::LiteParticles2D>(engine);
lprt.target_fbo = "game_view";
}
// rn does rt too
MM::OpenGL::setup_bloom(engine);
// not part of setup_bloom
auto& comp = rs.addRenderTask<MM::OpenGL::RenderTasks::Composition>(engine);
comp.color_tex = "hdr_color";
comp.bloom_tex = "blur_tmp1";
comp.target_fbo = "display";
}
// setup scene
auto& cam = scene.ctx().emplace<MM::OpenGL::Camera3D>();
cam.horizontalViewPortSize = 30.f;
cam.setOrthographic();
cam.updateView();
scene.ctx().emplace<MM::Components::LiteParticles2DUploadQueue>();
scene.ctx().emplace<MM::Random::SRNG>(42u);
// setup v system
auto& org = scene.ctx().emplace<entt::organizer>();
org.emplace<Systems::lite_particles2d_emit>("lite_particles2d_emit");
// HACK: instead you would switch to this scene
engine.getService<MM::Services::OrganizerSceneService>().updateOrganizerVertices(scene);
{ // default
auto e = scene.create();
auto& p = scene.emplace<MM::Components::Position2D>(e);
p.pos.x = -10.f;
auto& lpe = scene.emplace<Components::LiteParticles2DEmitter>(e);
lpe.age_delta = 60.f;
lpe.particle_count = {1, 1};
lpe.p_type = "default";
lpe.p_dir = {-0.1f, +0.1f};
lpe.p_dir_force = {0.f, 8.f};
lpe.p_age = {0.f, 0.7f};
}
{ // sparks
auto e = scene.create();
auto& p = scene.emplace<MM::Components::Position2D>(e);
p.pos.x = 0.f;
auto& lpe = scene.emplace<Components::LiteParticles2DEmitter>(e);
lpe.age_delta = 1.f;
lpe.particle_count = {25, 40};
lpe.p_type = "MM::spark1";
lpe.p_dir = {0.f, 1.f};
lpe.p_dir_force = {0.f, 1.f}; // m/s
lpe.p_age = {0.f, 0.7f};
}
{ // fire
auto e = scene.create();
auto& p = scene.emplace<MM::Components::Position2D>(e);
p.pos.x = 10.f;
auto& lpe = scene.emplace<Components::LiteParticles2DEmitter>(e);
lpe.age_delta = 60.f;
lpe.particle_count = {1, 1};
lpe.p_type = "MM::fire1";
lpe.p_dir = {0.f, 1.f};
lpe.p_dir_force = {0.f, 1.f};
lpe.p_age = {0.f, 0.5f};
}
engine.run();
}
int main(int argc, char** argv) {
argv0 = argv[0];
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}