/* Pepy by @ohsqueezy [ohsqueezy.itch.io] */ #include "Pepy.hpp" /* Launch the Pepy instance's mainloop */ int main() { Pepy pepy = Pepy(); pepy.run(); pepy.quit(); return 0; } /* Initialize a Pepy instance */ Pepy::Pepy() { /* subscribe to command events */ get_delegate().subscribe(&Pepy::respond, this); get_delegate().subscribe(&Pepy::respond, this, SDL_MOUSEBUTTONDOWN); get_delegate().subscribe(&Pepy::respond, this, SDL_MOUSEBUTTONUP); /* create a glowing ring */ int point_count = configuration()["sim"]["cuckoo-wall-count"].get() + 1; std::vector outer_points = sb::points_on_circle(point_count, configuration()["sim"]["cuckoo-outer-radius"], {0.0f, 0.0f}); std::vector inner_points = sb::points_on_circle(point_count, configuration()["sim"]["cuckoo-inner-radius"], {0.0f, 0.0f}); float inner_saturation = 0.1f; float inner_value = 1.0f; float outer_saturation = 1.0f; float outer_value = 0.1f; int next; for (int ii = 0; ii <= point_count; ii++) { next = (ii + 1) % point_count; cuckoo["position"]->add(outer_points[ii]); cuckoo["position"]->add(outer_points[next]); cuckoo["position"]->add(inner_points[ii]); cuckoo["position"]->add(inner_points[ii]); cuckoo["position"]->add(inner_points[next]); cuckoo["position"]->add(outer_points[next]); cuckoo["color"]->add(glm::rgbColor(glm::vec3(ii / static_cast(point_count) * 255.0f, outer_saturation, outer_value))); cuckoo["color"]->add(glm::rgbColor(glm::vec3(next / static_cast(point_count) * 255.0f, outer_saturation, outer_value))); cuckoo["color"]->add(glm::rgbColor(glm::vec3(ii / static_cast(point_count) * 255.0f, inner_saturation, inner_value))); cuckoo["color"]->add(glm::rgbColor(glm::vec3(ii / static_cast(point_count) * 255.0f, inner_saturation, inner_value))); cuckoo["color"]->add(glm::rgbColor(glm::vec3(next / static_cast(point_count) * 255.0f, inner_saturation, inner_value))); cuckoo["color"]->add(glm::rgbColor(glm::vec3(next / static_cast(point_count) * 255.0f, outer_saturation, outer_value))); } /* Create the playing balls */ for (std::size_t ball_ii = 0; ball_ii < configuration()["sim"]["ball-count"]; ball_ii++) { Plane ball; wad.push_back(std::pair{ball, glm::vec2{0.0f, 0.0f}}); } situate_balls(); background.transformation(glm::scale(glm::vec3{5.0f, 5.0f, 1.0f})); /* load Open GL */ load_gl_context(); /* load wad textures */ sb::Texture wad_texture {"resource/wad.png"}; wad_texture.load(); /* Apply the wad texture to each wad */ for (std::size_t wad_ii = 0; wad_ii < wad.size(); wad_ii++) { wad[wad_ii].first.texture(wad_texture); } sb::Texture texture {"resource/space.png"}; texture.load(); background.texture(texture); } void Pepy::situate_balls() { float size = configuration()["sim"]["ball-scale"]; auto spawn_points = sb::points_on_circle(wad.size(), configuration()["sim"]["spawn-radius"]); int ii = 0; for (auto& ball : wad) { ball.first.transformation(glm::translate(glm::vec3{spawn_points[ii].x, spawn_points[ii++].y, 1.0f}) * glm::scale(glm::vec3{size, size, 1.0f})); } } void Pepy::load_gl_context() { super::load_gl_context(); cuckoo_vao.generate(); wad_vao.generate(); cuckoo_vao.bind(); /* Generate ID for the vertex buffer object that will hold vertex data. Because there is one buffer, data * will be copied in one after the other, offset to after the previous location. */ vbo.generate(); vbo.bind(); vbo.allocate(cuckoo.size() + wad[0].first.size(), GL_STATIC_DRAW); GLuint vertex_shader = load_shader("src/shader.vert", GL_VERTEX_SHADER); GLuint fragment_shader = load_shader("src/shader.frag", GL_FRAGMENT_SHADER); shader = glCreateProgram(); glAttachShader(shader, vertex_shader); glAttachShader(shader, fragment_shader); sb::Log::gl_errors("after attaching shaders"); cuckoo.attributes("position")->bind(0, shader, "vertex_position"); cuckoo.attributes("color")->bind(1, shader, "vertex_color"); vbo.add(*cuckoo.attributes("position")); vbo.add(*cuckoo.attributes("color")); wad_vao.bind(); wad[0].first.attributes("position")->bind(0, shader, "vertex_position"); wad[0].first.attributes("uv")->bind(2, shader, "vertex_uv"); vbo.add(*wad[0].first.attributes("position")); vbo.add(*wad[0].first.attributes("uv")); sb::Log::gl_errors("after VBO allocation"); link_shader(shader); uniform["blend"] = glGetUniformLocation(shader, "blend_min_hsv"); uniform["orthographic_projection"] = glGetUniformLocation(shader, "orthographic_projection"); uniform["model_transformation"] = glGetUniformLocation(shader, "model_transformation"); uniform["base_texture"] = glGetUniformLocation(shader, "base_texture"); uniform["textured"] = glGetUniformLocation(shader, "textured"); uniform["scroll"] = glGetUniformLocation(shader, "scroll"); uniform["time"] = glGetUniformLocation(shader, "time"); sb::Log::gl_errors("after uniforms"); /* enable alpha rendering */ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); sb::Log::gl_errors("at end of load context"); } void Pepy::respond(SDL_Event& event) { /* Will check if reset should be triggered */ bool reset = false; if (Delegate::compare(event, "toggle-boom")) { boom = !boom; } /* Check for any direction key when waiting to reset */ if (stopped && delegate.compare(event, {"up", "down", "left", "right"})) { reset = true; } /* Check mouse buttons to initiate grabbing, thrusting, or reset */ if (event.type == SDL_MOUSEBUTTONDOWN) { if (shaking) { /* In regular mode, a mouse click means grab the cuckoo */ if (!boom) { grabbed = true; } /* In BOOM mode, a mouse click means to thrust the cuckoo */ else { /* Point cuckoo in the direction of the mouse click relative to the center of the screen. Set the velocity to * maximum so the cuckoo immediately moves in the calculated direction. */ cuckoo_velocity.x = sb::angle_between({0.0f, 0.0f}, mouse_ndc()); cuckoo_velocity.y = 0.5f; } } /* Mouse button will trigger reset when game is waiting to reset */ else if (stopped) { reset = true; } } /* Ungrab is the only thing mouse up does */ else if (event.type == SDL_MOUSEBUTTONUP && shaking) { grabbed = false; } /* Reset to cuckoo time */ if (reset) { situate_balls(); stopped = false; shaking = true; } } glm::vec2 Pepy::mouse_ndc() { glm::ivec2 mouse_pixel; SDL_GetMouseState(&mouse_pixel.x, &mouse_pixel.y); return { static_cast(mouse_pixel.x) / window_box().width() * 2.0f - 1.0f, (1.0f - static_cast(mouse_pixel.y) / window_box().height()) * 2.0f - 1.0f }; } void Pepy::update() { /* number of seconds running */ time_seconds = SDL_GetTicks() / 1000.0f; /* Move countdown along during the shaking phase */ if (shaking) { countdown -= last_frame_length / 1000.0f; if (countdown < 0.0f) { shaking = false; flying = true; countdown = configuration()["sim"]["cuckoo-time"]; /* During a BOOM shake, freeze motion at the release point. */ if (boom) { for (auto& ball : wad) { ball.second.y = 0.0f; } } } } /* Move cuckoo with mouse */ if (grabbed && shaking) { glm::vec2 ndc = mouse_ndc(); cuckoo_offset.x += weight(ndc.x / 20.0f) * std::max(std::min(std::abs(1.0f - cuckoo_offset.x), 1.0f), 0.1f); cuckoo_offset.y += weight(ndc.y / 20.0f) * std::max(std::min(std::abs(1.0f - cuckoo_offset.y), 1.0f), 0.1f); } else { /* move cuckoo with keys */ const std::uint8_t* state = SDL_GetKeyboardState(nullptr); float motion = weight(1 / 25.0f), diagonal = motion * std::sin(glm::pi() * 0.25f); if (state[SDL_SCANCODE_UP]) { if (state[SDL_SCANCODE_RIGHT]) { cuckoo_offset.x += diagonal; cuckoo_offset.y += diagonal; } else if (state[SDL_SCANCODE_LEFT]) { cuckoo_offset.x -= diagonal; cuckoo_offset.y += diagonal; } else { cuckoo_offset.y += motion; } } else if (state[SDL_SCANCODE_RIGHT]) { if (state[SDL_SCANCODE_DOWN]) { cuckoo_offset.x += diagonal; cuckoo_offset.y -= diagonal; } else { cuckoo_offset.x += motion; } } else if (state[SDL_SCANCODE_DOWN]) { if (state[SDL_SCANCODE_LEFT]) { cuckoo_offset.x -= diagonal; cuckoo_offset.y -= diagonal; } else { cuckoo_offset.y -= motion; } } else if (state[SDL_SCANCODE_LEFT]) { cuckoo_offset.x -= motion; } } cuckoo_offset.x -= weight(glm::sign(cuckoo_offset).x * configuration()["sim"]["cuckoo-return-speed"].get()); cuckoo_offset.y -= weight(glm::sign(cuckoo_offset).y * configuration()["sim"]["cuckoo-return-speed"].get()); /* In regular mode, apply the cuckoo offset */ if (!boom) { cuckoo.transformation(glm::translate(cuckoo_offset)); } /* In boom mode, apply the cuckoo velocity */ else { /* Retract */ cuckoo_velocity.y = std::max(0.0f, cuckoo_velocity.y - weight(0.01f)); /* Change velocity to offset */ glm::vec2 delta = sb::velocity_to_delta(cuckoo_velocity); cuckoo.transformation(glm::translate(glm::vec3{delta.x, delta.y, 0.0f})); } hue_offset += weight(0.002f); glm::vec2 x_range = {-1.0f, 1.0f}; glm::vec2 y_range = {-1.0f, 1.0f}; /* move wad */ bool all_stopped = true; for (auto& ball : wad) { const glm::vec2& ball_center = {ball.first.transformation()[3].x, ball.first.transformation()[3].y}; const glm::vec2& cuckoo_center = {cuckoo.transformation()[3].x, cuckoo.transformation()[3].y}; float distance = glm::distance(ball_center, cuckoo_center); if (shaking) { /* The ball will bounce back toward the center of the cuckoo if it's a certain distance away from it. */ if (distance > configuration()["sim"]["cuckoo-inner-radius"]) { /* Calculate the angle of the ball and add speed. */ ball.second.x = sb::angle_between(ball_center, cuckoo_center); if (!boom) { ball.second.y += configuration()["sim"]["ball-bounce-speed"].get(); } else { ball.second.y += cuckoo_velocity.y * std::abs(sb::angle_ratio(ball.second.x, cuckoo_velocity.x)) * 0.1f; } } } glm::vec2 step = sb::velocity_to_delta(ball.second); ball.first.transformation(glm::translate(glm::vec3{step.x, step.y, 0.0f}) * ball.first.transformation()); if (ball.second.y > 0) { float friction; if (shaking) { friction = configuration()["sim"]["ball-shaking-friction"]; } else { friction = configuration()["sim"]["ball-not-shaking-friction"]; } ball.second.y = std::max(0.0f, ball.second.y - friction); } if (ball_center.x < x_range[0]) { x_range[0] = ball_center.x - 0.1f; } else if (ball_center.x > x_range[1]) { x_range[1] = ball_center.x + 0.1f; } if (ball_center.y < y_range[0]) { y_range[0] = ball_center.y - 0.1f; } else if (ball_center.y > y_range[1]) { y_range[1] = ball_center.y + 0.1f; } if (ball.second.y > 0.0f) { all_stopped = false; } } if (all_stopped && flying) { flying = false; stopped = true; glm::vec2 sum {0.0f, 0.0f}; std::vector centers; for (auto& ball : wad) { const glm::vec2& ball_center = {ball.first.transformation()[3].x, ball.first.transformation()[3].y}; centers.push_back(ball_center); sum += ball_center; } glm::vec2 centroid = sum / wad.size(); angle_sort sorter {centroid, centers[0]}; std::sort(centers.begin(), centers.end(), sorter); float area = 0.0f; for (std::size_t center_ii = 0; center_ii < centers.size() - 1; center_ii++) { area += centers[center_ii].x * centers[center_ii + 1].y - centers[center_ii + 1].x * centers[center_ii].y; } area += centers[centers.size() - 1].x * centers[0].y - centers[0].x * centers[centers.size() - 1].y; area = std::abs(area) / 2.0f; std::cout << "Your score (size) is " << area << ". Press arrow key to play again." << std::endl; } /* paint over screen */ glClearColor(0, 0, 0, 1); sb::Log::gl_errors("after setting clear color"); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); sb::Log::gl_errors("after clearing screen"); glUseProgram(shader); sb::Log::gl_errors("after using program"); /* set orthographic project for viewing entire scene at normalized screen ratio */ aspect_ratio = window_box().aspect(true); if (window_box().width() > window_box().height()) { orthographic_projection = glm::ortho(aspect_ratio * x_range[0], aspect_ratio * x_range[1], y_range[0], y_range[1]); } else { orthographic_projection = glm::ortho(x_range[0], x_range[1], aspect_ratio * y_range[0], aspect_ratio * y_range[1]); } glUniformMatrix4fv(uniform["orthographic_projection"], 1, GL_FALSE, &orthographic_projection[0][0]); sb::Log::gl_errors("after setting orthographic projection"); /* draw background */ wad_vao.bind(); glUniform3f(uniform["blend"], hue_offset, 0.0f, 1.0f); glUniform1i(uniform["textured"], true); glUniform1i(uniform["scroll"], true); glUniform1f(uniform["time"], time_seconds); glUniform1i(uniform["base_texture"], 0); glActiveTexture(GL_TEXTURE0); background.texture().bind(); glUniformMatrix4fv(uniform["model_transformation"], 1, GL_FALSE, reinterpret_cast(&background.transformation()[0][0])); background.enable(); glDrawArrays(GL_TRIANGLES, 0, background.attributes("position")->count()); background.disable(); /* draw wad */ wad_vao.bind(); glUniform3f(uniform["blend"], 0.0f, 0.0f, 1.0f); glUniform1i(uniform["textured"], true); glUniform1i(uniform["scroll"], false); sb::Log::gl_errors("after setting blending and textured flag"); for (std::pair& ball : wad) { glUniform1i(uniform["base_texture"], 0); sb::Log::gl_errors("after setting texture uniform"); glActiveTexture(GL_TEXTURE0); sb::Log::gl_errors("after activating texture"); ball.first.texture().bind(); sb::Log::gl_errors("after binding wad"); glUniformMatrix4fv(uniform["model_transformation"], 1, GL_FALSE, reinterpret_cast(&ball.first.transformation()[0][0])); ball.first.enable(); glDrawArrays(GL_TRIANGLES, 0, ball.first.attributes("position")->count()); ball.first.disable(); sb::Log::gl_errors("after drawing wad"); } /* draw cuckoo */ if (shaking || stopped) { cuckoo_vao.bind(); glUniform3f(uniform["blend"], hue_offset, 0.5f, 1.0f); glUniform1i(uniform["textured"], false); glUniformMatrix4fv(uniform["model_transformation"], 1, GL_FALSE, reinterpret_cast(&cuckoo.transformation()[0][0])); cuckoo.enable(); glDrawArrays(GL_TRIANGLES, 0, cuckoo.attributes("position")->count()); cuckoo.disable(); } SDL_GL_SwapWindow(window()); sb::Log::gl_errors("at end of update"); if (shaking) { std::cout << countdown << std::endl; } }