#include #include #include #include #include #include #include #include #include #include #include #include #include #include // ctx #include #include #include // components #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 particle_count {1u, 1u}; // particles data std::string p_type {}; MM::ScalarRange2 p_pos_x {0.f, 0.f}; MM::ScalarRange2 p_pos_y {0.f, 0.f}; MM::ScalarRange2 p_dir {0.f, 0.f}; // 0-1, not rad MM::ScalarRange2 p_dir_force {0.f, 0.f}; MM::ScalarRange2 p_age {0.f, 0.f}; }; } // Components namespace Systems { void lite_particles2d_emit( entt::view> 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(); 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::ref(); auto [w, h] = engine.getService().getWindowSize(); // we dont have a gbuffer in this example { // gbuffer // depth #ifdef MM_OPENGL_3_GLES rm_t.reload( "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( "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( "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(); auto& rm_t = MM::ResourceManager::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::ref(); lpt_rm.load("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::fire1", engine, "/particles/lite_particles2d/fire1.json"); } TEST(lite_particles2d, it) { MM::Engine engine; auto& sdl_ss = engine.addService(); ASSERT_TRUE(engine.enableService()); sdl_ss.createGLWindow("lite_particles2d", 1280, 720); engine.addService(); ASSERT_TRUE(engine.enableService()); bool provide_ret = engine.provide(); ASSERT_TRUE(provide_ret); auto& scene = engine.tryService()->getScene(); engine.addService(argv0, "lite_particles2d"); ASSERT_TRUE(engine.enableService()); auto& rs = engine.addService(); ASSERT_TRUE(engine.enableService()); // load particle types // before addRenderTask // 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(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(engine); //bsrr_rend.target_fbo = "game_view"; auto& lprt = rs.addRenderTask(engine); lprt.target_fbo = "game_view"; } // rn does rt too MM::OpenGL::setup_bloom(engine); // not part of setup_bloom auto& comp = rs.addRenderTask(engine); comp.color_tex = "hdr_color"; comp.bloom_tex = "blur_tmp1"; comp.target_fbo = "display"; } // setup scene auto& cam = scene.ctx().emplace(); cam.horizontalViewPortSize = 30.f; cam.setOrthographic(); cam.updateView(); scene.ctx().emplace(); scene.ctx().emplace(42u); // setup v system auto& org = scene.ctx().emplace(); org.emplace("lite_particles2d_emit"); // HACK: instead you would switch to this scene engine.getService().updateOrganizerVertices(scene); { // default auto e = scene.create(); auto& p = scene.emplace(e); p.pos.x = -10.f; auto& lpe = scene.emplace(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(e); p.pos.x = 0.f; auto& lpe = scene.emplace(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(e); p.pos.x = 10.f; auto& lpe = scene.emplace(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(); }