From 80aebaf8de1104e664ecd26be1cc1a736de4f208 Mon Sep 17 00:00:00 2001 From: frank <420@shampoo.ooo> Date: Tue, 9 Nov 2021 23:32:48 -0500 Subject: [PATCH] pad class with example that runs camera toggle on click --- config.json | 3 +- lib/sb | 2 +- src/Item.cpp | 9 ++-- src/Model.cpp | 45 ++++++++++++++----- src/Model.hpp | 14 +++--- src/Pudding.cpp | 116 ++++++++++++++++++++++++++++++++++++++++++++---- src/Pudding.hpp | 77 +++++++++++++++++++------------- src/flat.frag | 3 +- src/flat.vert | 3 +- 9 files changed, 205 insertions(+), 67 deletions(-) diff --git a/config.json b/config.json index d6d28a0..f987fb1 100644 --- a/config.json +++ b/config.json @@ -5,7 +5,8 @@ "framerate": 60, "title": "Pudding", "debug": false, - "render driver": "opengl" + "render driver": "opengl", + "show-cursor": true }, "configuration": { diff --git a/lib/sb b/lib/sb index 863db54..03d179e 160000 --- a/lib/sb +++ b/lib/sb @@ -1 +1 @@ -Subproject commit 863db5467bfc625df916ac9a04e9d14aa13d7e55 +Subproject commit 03d179eed4c8323576157f806806b214e42d07c7 diff --git a/src/Item.cpp b/src/Item.cpp index 678a1fc..b50ed44 100644 --- a/src/Item.cpp +++ b/src/Item.cpp @@ -79,21 +79,20 @@ void Item::texture(sb::Texture& texture, const std::string& name) sb::Texture& Item::current_texture() { - // return image.texture().begin()->second; - return carousel.current(image.texture())->second; + return carousel.current(image.textures())->second; } void Item::next_texture() { - carousel.next(image.texture()); + carousel.next(image.textures()); } void Item::previous_texture() { - carousel.previous(image.texture()); + carousel.previous(image.textures()); } std::size_t Item::texture_count() { - return image.texture().size(); + return image.textures().size(); } diff --git a/src/Model.cpp b/src/Model.cpp index df9c1f5..c34fc65 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -56,7 +56,7 @@ std::map>& Model::attributes() * access to the public interface of the attributes. */ std::shared_ptr& Model::attributes(const std::string& name) { - return model_attributes.at(name); + return attributes().at(name); } /* Get the attributes under name, wrapped in the shared pointer held by this object. This @@ -64,12 +64,13 @@ std::shared_ptr& Model::attributes(const std::string& name) * object if they are wrapped in a shared pointer. */ std::shared_ptr& Model::operator[](const std::string& name) { - auto element = model_attributes.find(name); - if (element == model_attributes.end()) + auto element = attributes().find(name); + /* add an empty Attributes at name if it doesn't exist yet */ + if (element == attributes().end()) { attributes(sb::Attributes{}, name); } - return model_attributes[name]; + return attributes()[name]; } /* Assign name to attributes, copy and wrap in a shared pointer. The model can share @@ -82,7 +83,7 @@ void Model::attributes(const sb::Attributes& attributes, const std::string& name /* Assign name to attributes and share ownership. */ void Model::attributes(const std::shared_ptr& attributes, const std::string& name) { - model_attributes[name] = attributes; + this->attributes()[name] = attributes; } /* Enable all attributes. */ @@ -104,26 +105,46 @@ void Model::disable() } /* Return a reference to the texture container. */ -std::map& Model::texture() +std::map& Model::textures() { - return model_texture; + return model_textures; } /* Get the texture at name. This can be used to read the texture memory, share ownership of it, or * anything else a Texture object can be used for with direct calls to GL functions. */ sb::Texture& Model::texture(const std::string& name) { - return model_texture.at(name); + return textures().at(name); +} + +/* Get the default texture. The default texture must have previously been set with the default key as + * the name, which can be done using Model::texture(sb::Texture). */ +sb::Texture& Model::texture() +{ + return texture(DEFAULT_TEXTURE_NAME); } /* Assign name to texture and share ownership. */ void Model::texture(const sb::Texture& texture, const std::string& name) { - model_texture[name] = texture; + textures()[name] = texture; +} + +/* If no name is specified, use the default texture. This can be used to conveniently setup a model + * with only one texture. */ +void Model::texture(const sb::Texture& texture) +{ + this->texture(texture, DEFAULT_TEXTURE_NAME); } /* Set the transformation matrix. */ -void Model::transformation(const glm::mat4& transformation) +const glm::mat4& Model::transformation() const +{ + return model_transformation; +} + +/* Set the transformation matrix. */ +void Model::transform(const glm::mat4& transformation) { model_transformation = transformation; } @@ -149,13 +170,13 @@ Model::operator glm::mat4() const * start over from the beginning. */ void Background::next() { - carousel.next(model_texture); + carousel.next(textures()); } /* Return the currently active texture. */ sb::Texture& Background::current() { - return carousel.current(model_texture)->second; + return carousel.current(textures())->second; } CameraView::CameraView() : Plane() diff --git a/src/Model.hpp b/src/Model.hpp index d7b5a23..c0bcc0b 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -30,10 +30,11 @@ class Model { -protected: +private: + inline static const std::string DEFAULT_TEXTURE_NAME = "default"; + std::map model_textures; std::map> model_attributes; - std::map model_texture; glm::mat4 model_transformation = glm::mat4(1); public: @@ -49,10 +50,13 @@ public: std::shared_ptr& operator[](const std::string&); void enable(); void disable(); - std::map& texture(); + std::map& textures(); sb::Texture& texture(const std::string&); + sb::Texture& texture(); void texture(const sb::Texture&, const std::string&); - void transformation(const glm::mat4&); + void texture(const sb::Texture&); + const glm::mat4& transformation() const; + void transform(const glm::mat4&); std::size_t size(); operator glm::mat4() const; @@ -64,7 +68,7 @@ class Plane : public Model public: inline const static std::shared_ptr position = std::make_shared(sb::Attributes{ - {-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f}, + {-1.0f, 1.0f}, {1.0f, 1.0f}, {-1.0f, -1.0f}, {1.0f, 1.0f}, {1.0f, -1.0f}, {-1.0f, -1.0f} }); inline const static std::shared_ptr uv = std::make_shared(sb::Attributes{ diff --git a/src/Pudding.cpp b/src/Pudding.cpp index 1edb2c1..ef47122 100644 --- a/src/Pudding.cpp +++ b/src/Pudding.cpp @@ -29,15 +29,29 @@ Pudding::Pudding() { /* subscribe to command events */ get_delegate().subscribe(&Pudding::respond, this); + get_delegate().subscribe(&Pudding::respond, this, SDL_MOUSEBUTTONDOWN); /* initialize a zbar image scanner for reading barcodes of any format */ image_scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1); /* set up pudding model */ nlohmann::json pudding = get_configuration()["pudding"]; set_pudding_model(pudding["top-radius"], pudding["base-radius"], pudding["ring-vertex-count"], pudding["layer-count"], pudding["y-range"][0], pudding["y-range"][1], pudding["gradient-position"]); - /* use gl context so we can draw 3D */ + /* loading GL context instead of SDL context for 3D */ load_gl_context(); load_tiles(); + glm::vec3 w = glm::mat3({{1, 0, 0}, {0, 1, 0}, {-0.6739, -0.74, 1}}) * glm::mat3({{.1, 0, 0}, {0, .1 * (460.0 / 768.0), 0}, {0, 0, 1}}) * + glm::vec3({-1, -1, 1}); + std::cout << w << std::endl << glm::translate(glm::vec3{-0.6739, -0.74, 0}) * + glm::scale(glm::vec3{.1, .1 * (460.0 / 768.0), 1}) * glm::vec4{-1, -1, 0, 1} << std::endl; + Pad p {background.current(), {-0.6739f, -0.74f}, 0.1f, get_display().window_box().aspect(), std::function()}; + const std::vector& p_position = *p.attributes("position"); + glm::vec4 final_position = p.transformation() * glm::vec4{p_position[2].x, p_position[2].y, 0, 1}; + std::cout << p.transformation() << std::endl << final_position << std::endl; + assert(final_position == glm::vec4({w.x, w.y, 0, 1})); + sb::Texture label {"local/button/scan.png"}; + label.load(); + pad.texture(label); + pad.transform({-0.6739f, -0.74f}, 0.25f, get_display().window_box().aspect()); } /* Assign vertices, colors and texture UV coordinates to the pudding model */ @@ -204,12 +218,16 @@ void Pudding::load_gl_context() uniform["flat"]["time"] = glGetUniformLocation(flat_program, "time"); uniform["flat"]["scroll"] = glGetUniformLocation(flat_program, "scroll"); uniform["flat"]["blend"] = glGetUniformLocation(flat_program, "blend_min_hsv"); + uniform["flat"]["transformation"] = glGetUniformLocation(flat_program, "transformation"); uniform["mvp"]["mvp"] = glGetUniformLocation(mvp_program, "mvp"); uniform["mvp"]["time"] = glGetUniformLocation(mvp_program, "time"); uniform["mvp"]["effect"] = glGetUniformLocation(mvp_program, "effect"); uniform["mvp"]["uv transformation"] = glGetUniformLocation(mvp_program, "uv_transformation"); uniform["mvp"]["coordinate bound"] = glGetUniformLocation(mvp_program, "coordinate_bound"); uniform["mvp"]["pudding texture"] = glGetUniformLocation(mvp_program, "pudding_texture"); + /* enable alpha rendering */ + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable( GL_BLEND ); sb::Log::gl_errors("after uniform locations"); } @@ -294,6 +312,17 @@ void Pudding::respond(SDL_Event& event) { background.next(); } + else if (event.type == SDL_MOUSEBUTTONDOWN) + { + glm::vec2 gl_coordinates { + float(event.button.x) / window_box().width() * 2.0f - 1.0f, + (1.0f - float(event.button.y) / window_box().height()) * 2.0f - 1.0f + }; + if (pad.collide(gl_coordinates)) + { + camera_switch.toggle(); + } + } } /* Build an Item object by submitting the upc parameter to multiple APIs and taking @@ -729,6 +758,7 @@ int Pudding::capture_frame(void* game) frame.release(); } SDL_GL_MakeCurrent(pudding->window, nullptr); + sb::Log::gl_errors("in capture thread, after capturing frame"); } pudding->reading_capture_frame = false; return 0; @@ -752,6 +782,7 @@ void Pudding::update() reading_capture_frame = true; } } + sb::Log::gl_errors("in main thread, after capturing frame"); /* if the config is set to refresh automatically, there may be a new barcode available */ if (current_config_barcode != get_configuration()["scan"]["barcode"]) { @@ -768,7 +799,7 @@ void Pudding::update() { viewport_box.drag_bottom(0.3f * viewport_box.height()); } - glViewport(viewport_box.left(), viewport_box.bottom(), viewport_box.width(), viewport_box.height()); + glViewport(viewport_box); glDisable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -780,22 +811,25 @@ void Pudding::update() glUniform1i(uniform["flat"]["texture"], 0); glUniform3f(uniform["flat"]["blend"], 0.0f, 0.0f, 1.0f); glUniform1i(uniform["flat"]["scroll"], true); + glUniformMatrix4fv(uniform["flat"]["transformation"], 1, GL_FALSE, &glm::mat4(1)[0][0]); /* disable pudding attributes and enable background attributes */ pudding_model.disable(); background.enable(); background.current().bind(); /* draws bg vertices and texture */ glDrawArrays(GL_TRIANGLES, 0, background.attributes("position")->count()); + /* turn off scrolling */ glUniform1i(uniform["flat"]["scroll"], false); + sb::Log::gl_errors("after background, before pudding"); /* draw pudding model using MVP shader */ glUseProgram(mvp_program); - glUniform1f(uniform["mvp"]["time"], time_seconds); /* calculate the transformation matrix for displaying pudding in viewport */ model = glm::rotate(model, weight(get_configuration()["pudding"]["rotation-speed"].get()), Y_UNIT_NORMAL_3D); projection = glm::perspective( glm::radians(40.0f * 1 / viewport_box.aspect()), viewport_box.aspect(), 0.1f, 100.0f); mvp = projection * VIEW_MATRIX * model; - /* pass the mvp matrix to the shader */ + /* uniforms */ + glUniform1f(uniform["mvp"]["time"], time_seconds); glUniformMatrix4fv(uniform["mvp"]["mvp"], 1, GL_FALSE, &mvp[0][0]); /* disable bg attributes and enable pudding attributes */ background.disable(); @@ -803,12 +837,12 @@ void Pudding::update() if (items.size() == 0) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - pudding_model.attributes("color")->enable(); + // pudding_model.attributes("color")->enable(); } else { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - pudding_model.attributes("color")->enable(); + // pudding_model.attributes("color")->enable(); pudding_model.attributes("uv")->enable(); glUniform1i(uniform["mvp"]["pudding texture"], 0); glActiveTexture(GL_TEXTURE0); @@ -818,6 +852,7 @@ void Pudding::update() glEnable(GL_DEPTH_TEST); /* draw the sides of the pudding */ glDrawArrays(GL_TRIANGLES, 0, pudding_triangle_vertex_count); + sb::Log::gl_errors("after pudding sides, before pudding top/bottom"); /* enable squircling and draw the top and bottom of pudding */ glUniform1i(uniform["mvp"]["uv transformation"], UV_SQUIRCLE); glUniform1f(uniform["mvp"]["coordinate bound"], get_configuration()["pudding"]["top-radius"]); @@ -828,6 +863,7 @@ void Pudding::update() glUniform1i(uniform["mvp"]["uv transformation"], UV_NONE); /* regular fill mode enabled for all other drawing */ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + sb::Log::gl_errors("after pudding, before item or camera view"); /* only do more drawing if items are downloaded or camera is enabled */ if (item_display_active() || capture.isOpened()) { @@ -851,7 +887,7 @@ void Pudding::update() { viewport_box.left(viewport_box.cx(), true); } - glViewport(viewport_box.left(), viewport_box.bottom(), viewport_box.width(), viewport_box.height()); + glViewport(viewport_box); current_item().current_texture().bind(); plane.enable(); /* draws rectangle vertices and rectangle texture using UV coords */ @@ -861,7 +897,7 @@ void Pudding::update() if (capture.isOpened()) { viewport_box.left(window_box(true).left()); - glViewport(viewport_box.left(), viewport_box.bottom(), viewport_box.width(), viewport_box.height()); + glViewport(viewport_box); /* bind texture for drawing */ camera_view.current().bind(); camera_view.enable(); @@ -869,8 +905,16 @@ void Pudding::update() glDrawArrays(GL_TRIANGLES, 0, camera_view.attributes("position")->count()); } } + sb::Log::gl_errors("after capture, before test pad"); + /* Test Pad */ + glUseProgram(flat_program); + glUniformMatrix4fv(uniform["flat"]["transformation"], 1, GL_FALSE, &pad.transformation()[0][0]); + pad.texture().bind(); + plane.enable(); + glViewport(window_box(true)); + glDrawArrays(GL_TRIANGLES, 0, pad.attributes("position")->count()); SDL_GL_SwapWindow(get_window()); - sb::Log::gl_errors("after update loop"); + sb::Log::gl_errors("after test pad"); /* add a new item if a new barcode was scanned or entered */ if (current_barcode != previous_barcode) { @@ -878,3 +922,57 @@ void Pudding::update() previous_barcode = current_barcode; } } + +/* Construct a Pad using a texture, an offset, a scale, and a callback function. A Pad is a Plane which can be clicked + * to launch an arbitrary user function. It can be sized and placed by setting the offset and scale values. The offset + * is relative to (0.0, 0.0), and the scale is relative to the Plane, which has opposite corners at (-1.0, -1.0) and + * (1.0, 1.0). * The texture is the graphic that displays in the Pad location. The callback must be a function that + * doesn't return a value or accept any arguments. */ +Pad::Pad(sb::Texture texture, glm::vec2 offset, float scale, float ratio, std::function on_connect) +{ + this->texture(texture); + transform(offset, scale, ratio); + this->on_connect(on_connect); + box.gl(true); +} + +/* Set the Pad's transformation matrix based on an offset, a scale, and an aspect ratio. The offset is amount it will + * be shifted in the (x, y) plane. The scale is a value relative to the (x, y) plane, but it is a single value because + * the aspect ratio will determine how much each axis is scaled. If the aspect ratio is above one, the x-axis's scale + * will be divided by the ratio. If the aspect ratio is below one, the y-axis's scale will be multiplied by the aspect + * ratio. If the aspect ratio of the window is given, this will force the Pad to display as a square, and the ratio + * will be relative to the shorter axis. */ +void Pad::transform(glm::vec2 offset, float scale, float ratio) +{ + glm::vec3 scale_components { scale, scale, 1 }; + if (ratio > 1.0f) + { + scale_components.x /= ratio; + } + else if (ratio < 1.0f) + { + scale_components.y *= ratio; + } + box.size({scale_components.x * 2, scale_components.y * 2}); + box.center(offset); + std::cout << "pad box is " << box << std::endl; + Model::transform(glm::translate(glm::vec3{offset.x, offset.y, 0}) * glm::scale(scale_components)); +} + +/* Set the function that will run when a pad object is clicked. */ +void Pad::on_connect(std::function on_connect) +{ + connection.on_connect(on_connect); +} + +/* Returns true if the point at position collides with the box containing the pad object, which is the box the pad + * object fits inside after the transform is applied. */ +bool Pad::collide(const glm::vec2& position) const +{ + return box.collide(position); +} + +void glViewport(Box box) +{ + glViewport(box.left(), box.bottom(), box.width(), box.height()); +} diff --git a/src/Pudding.hpp b/src/Pudding.hpp index 4b9f791..4d2cce7 100644 --- a/src/Pudding.hpp +++ b/src/Pudding.hpp @@ -42,6 +42,9 @@ #include "Item.hpp" #include "Model.hpp" #include "utility.hpp" +#include "Box.hpp" + +void glViewport(Box); /* A connection is an object containing a binary state of either on (connected) or off (not connected) * and user supplied functions that run automatically on each state change. The functions each have the @@ -170,6 +173,33 @@ public: }; +/* Drawable class that is a plane containing a connection. Each instance: + * + * - Shares vertices and UV in VBO + * - Has its own Texture representing the button on-screen + * - Has its own response to click + * - Shares mouse collision code + * - Has its own translate + scale transformation +*/ +class Pad : public Plane +{ + +private: + + using callback = std::function; + Connection<> connection; + Box box; + +public: + + Pad() {}; + Pad(sb::Texture, glm::vec2, float, float, callback); + void transform(glm::vec2, float, float); + void on_connect(callback); + bool collide(const glm::vec2&) const; + +}; + class Pudding : public Game { @@ -202,11 +232,11 @@ private: const std::string NUTRONIX_NOT_FOUND = "resource not found"; const std::string GOOGLE_BOOKS_API_URL = "https://www.googleapis.com/books/v1/volumes?q=isbn:"; const std::string GIANTBOMB_API_URL = "https://www.giantbomb.com/api/release/?api_key="; - const glm::vec3 ZERO_VECTOR_3D = glm::vec3(0, 0, 0); - const glm::vec3 Y_UNIT_NORMAL_3D = glm::vec3(0, 1, 0); - const glm::mat4 VIEW_MATRIX = glm::lookAt(glm::vec3(4, 2, 1), glm::vec3(0, -0.325, 0), Y_UNIT_NORMAL_3D); - const glm::vec3 PUDDING_BROWN = glm::vec3(0.713f, 0.359f, 0.224f); - const glm::vec3 PUDDING_YELLOW = glm::vec3(0.878f, 0.859f, 0.122f); + const glm::vec3 ZERO_VECTOR_3D {0, 0, 0}; + const glm::vec3 Y_UNIT_NORMAL_3D {0, 1, 0}; + const glm::mat4 VIEW_MATRIX = glm::lookAt({4.0f, 2.0f, 1.0f}, {0.0f, -0.325f, 0.0f}, Y_UNIT_NORMAL_3D); + const glm::vec3 PUDDING_BROWN {0.713f, 0.359f, 0.224f}; + const glm::vec3 PUDDING_YELLOW {0.878f, 0.859f, 0.122f}; std::string current_barcode, previous_barcode, current_config_barcode, current_camera_barcode; std::vector items; Carousel item_carousel; @@ -215,7 +245,7 @@ private: zbar::ImageScanner image_scanner; std::map> uniform; GLuint flat_program, mvp_program; - glm::mat4 projection, model = glm::mat4(1.0f), mvp; + glm::mat4 projection, model {1.0f}, mvp; Model pudding_model; Plane plane; Background background; @@ -224,8 +254,9 @@ private: SDL_GLContext capture_frame_thread_context = nullptr; sb::VAO vao; sb::VBO vbo; + Pad pad; - void set_pudding_model(float, float, int, int = 1, float = -1, float = 1, float = 0.3f); + void set_pudding_model(float, float, int, int = 1, float = -1.0f, float = 1.0f, float = 0.3f); void load_gl_context(); void load_tiles(); void initialize_camera(); @@ -262,37 +293,19 @@ public: /* Apply force until reaching a threshold. Use a connection object to run user functions * when force reaches threshold and when force goes below threshold. */ +template class Button { private: - Connection<> connection; - - /* threshold */ - /* force */ - /* close */ - /* open */ - /* apply */ - /* remove */ - /* weighted depression rate */ -}; - -/* Drawable class that is a plane containing a button. Each instance: - * - * - Shares vertices and UV in VBO - * - Has its own Texture representing the button on-screen - * - Has its own response to click - * - Shares mouse collision code - * - Has its own translate + scale transformation -*/ -class Pad : public Plane -{ - -private: - - Button button; + Connection connection; + /* float threshold = 1.0f */ + /* float force = 0.0f */ + /* apply() */ + /* remove() */ + /* float weighted depression rate */ }; #endif diff --git a/src/flat.frag b/src/flat.frag index 40d9470..2982ec1 100644 --- a/src/flat.frag +++ b/src/flat.frag @@ -35,5 +35,6 @@ void main(void) { gl_FragColor = texture(base_texture, uv); } - gl_FragColor = min(gl_FragColor, vec4(hsv2rgb(blend_min_hsv), 1)); + /* apply blending, leaving alpha unchanged */ + gl_FragColor.xyz = min(gl_FragColor.xyz, hsv2rgb(blend_min_hsv)); } diff --git a/src/flat.vert b/src/flat.vert index 7fcbb68..265464e 100644 --- a/src/flat.vert +++ b/src/flat.vert @@ -12,9 +12,10 @@ in vec2 in_position; in vec2 vertex_uv; out vec2 uv; +uniform mat4 transformation; void main(void) { - gl_Position = vec4(in_position, 0, 1); + gl_Position = transformation * vec4(in_position, 0, 1); uv = vertex_uv; }