diff --git a/config.json b/config.json index f987fb1..bd8e60b 100644 --- a/config.json +++ b/config.json @@ -79,6 +79,19 @@ }, "resource": { - "tile-path": "resource/tile" + "tile-path": "resource/tile", + "button-path": "resource/button" + }, + "interface": + { + "main-button-y": -0.75, + "main-button-single-x": 0.0, + "main-button-double-x": 0.65, + "main-button-scale": 0.25, + "camera-button-label": "scan", + "inventory-button-label": "inventory", + "arrow-button-location": [0.75, 0.0], + "arrow-button-scale": 0.1, + "arrow-button-label": "arrow" } } diff --git a/lib/sb b/lib/sb index 03d179e..54cf012 160000 --- a/lib/sb +++ b/lib/sb @@ -1 +1 @@ -Subproject commit 03d179eed4c8323576157f806806b214e42d07c7 +Subproject commit 54cf01246b0e5ec81ba5b9158248bca7492823db diff --git a/resource/button/arrow.png b/resource/button/arrow.png new file mode 100644 index 0000000..fbecee3 Binary files /dev/null and b/resource/button/arrow.png differ diff --git a/resource/button/home.png b/resource/button/home.png new file mode 100644 index 0000000..7672dc3 Binary files /dev/null and b/resource/button/home.png differ diff --git a/resource/button/inventory.png b/resource/button/inventory.png new file mode 100644 index 0000000..19e4a12 Binary files /dev/null and b/resource/button/inventory.png differ diff --git a/resource/button/scan.png b/resource/button/scan.png new file mode 100644 index 0000000..8ad9217 Binary files /dev/null and b/resource/button/scan.png differ diff --git a/src/Pudding.cpp b/src/Pudding.cpp index ef47122..0211be3 100644 --- a/src/Pudding.cpp +++ b/src/Pudding.cpp @@ -29,33 +29,24 @@ Pudding::Pudding() { /* subscribe to command events */ get_delegate().subscribe(&Pudding::respond, this); + get_delegate().subscribe(&Pudding::respond, this, SDL_MOUSEMOTION); 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"], + load_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"]); /* 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()); + load_pads(); + /* Load a pointer cursor from the system library that will be freed automatically */ + poke = std::shared_ptr(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND), SDL_FreeCursor); } /* Assign vertices, colors and texture UV coordinates to the pudding model */ -void Pudding::set_pudding_model( +void Pudding::load_pudding_model( float top_radius, float base_radius, int ring_vertex_count, int layer_count, float min_y, float max_y, float gradient_position) { size_t ii; @@ -231,17 +222,31 @@ void Pudding::load_gl_context() sb::Log::gl_errors("after uniform locations"); } -/* Read every resource/tile/.*.jpg into a GL texture, storing the texture pointer and file name in a std::map */ +/* Read every jpg in the folder at tile path into a GL texture and associate with the background object. */ void Pudding::load_tiles() { for (fs::path path : sb::glob(get_configuration()["resource"]["tile-path"].get() / ".*.jpg")) { - sb::Texture texture = sb::Texture(path); + sb::Texture texture {path}; texture.load(); background.texture(texture, path); } } +/* Load every png in the button path as a Texture and add to a map. */ +void Pudding::load_pads() +{ + for (fs::path path : sb::glob(get_configuration()["resource"]["button-path"].get() / ".*.png")) + { + labels[path.stem()] = sb::Texture(path); + labels[path.stem()].load(); + } + nlohmann::json interface = get_configuration()["interface"]; + camera_button.texture(labels["scan"]); + camera_button.transform({interface["main-button-single-x"], interface["main-button-y"]}, + interface["main-button-scale"], window_box().aspect()); +} + /* Try to create cv::VideoCapture object using device ID #0. If successful, this will create GL texture IDs and storage * for the camera frames, so it must be called after GL context has been created. Two textures will be created, so they * can be used as a double buffer. @@ -312,15 +317,31 @@ void Pudding::respond(SDL_Event& event) { background.next(); } - else if (event.type == SDL_MOUSEBUTTONDOWN) + /* Mouse interface */ + else if (event.type == SDL_MOUSEMOTION || 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 + glm::vec2 mouse = event.type == SDL_MOUSEBUTTONDOWN ? glm::vec2{event.button.x, event.button.y} : + glm::vec2{event.motion.x, event.motion.y}; + glm::vec2 ndc { + float(mouse.x) / window_box().width() * 2.0f - 1.0f, (1.0f - float(mouse.y) / window_box().height()) * 2.0f - 1.0f }; - if (pad.collide(gl_coordinates)) + /* Check for collision with the camera button if camera view is open, otherwise check for collision with main viewport. */ + if ((!capture.isOpened() && camera_button.collide(ndc)) || + (capture.isOpened() && get_display().ndc_subsection(main_viewport).collide(ndc))) { - camera_switch.toggle(); + if (SDL_GetCursor() != poke.get()) + { + SDL_SetCursor(poke.get()); + } + if (event.type == SDL_MOUSEBUTTONDOWN) + { + SDL_SetCursor(SDL_GetDefaultCursor()); + camera_switch.toggle(); + } + } + else if (SDL_GetCursor() == poke.get()) + { + SDL_SetCursor(SDL_GetDefaultCursor()); } } } @@ -793,13 +814,15 @@ void Pudding::update() sb::Log::log(message); } /* viewport box will be used to tell GL where to draw */ - Box viewport_box = window_box(true); + viewport = window_box(true); /* shrink viewport if item texture or camera will be displayed */ if (item_display_active() || capture.isOpened()) { - viewport_box.drag_bottom(0.3f * viewport_box.height()); + viewport.drag_bottom(0.3f * viewport.height()); } - glViewport(viewport_box); + /* Save the main viewport dimensions */ + main_viewport = viewport; + glViewport(viewport); glDisable(GL_DEPTH_TEST); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -826,7 +849,7 @@ void Pudding::update() /* 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); + glm::radians(40.0f * 1 / viewport.aspect()), viewport.aspect(), 0.1f, 100.0f); mvp = projection * VIEW_MATRIX * model; /* uniforms */ glUniform1f(uniform["mvp"]["time"], time_seconds); @@ -875,8 +898,8 @@ void Pudding::update() glUniform1i(uniform["flat"]["texture"], 0); glActiveTexture(GL_TEXTURE0); /* move viewport to the bottom of screen */ - viewport_box.top(viewport_box.bottom(), true); - viewport_box.bottom(window_box(true).bottom(), true); + viewport.top(viewport.bottom(), true); + viewport.bottom(window_box(true).bottom(), true); /* reset blend to display the original texture colors */ glUniform3f(uniform["flat"]["blend"], 0.0f, 0.0f, 1.0f); /* draw the current item image if we're supposed to */ @@ -885,9 +908,9 @@ void Pudding::update() /* shrink viewport to half size if camera will also be displayed */ if (capture.isOpened()) { - viewport_box.left(viewport_box.cx(), true); + viewport.left(viewport.cx(), true); } - glViewport(viewport_box); + glViewport(viewport); current_item().current_texture().bind(); plane.enable(); /* draws rectangle vertices and rectangle texture using UV coords */ @@ -896,8 +919,8 @@ void Pudding::update() /* draw the camera if the camera has been opened */ if (capture.isOpened()) { - viewport_box.left(window_box(true).left()); - glViewport(viewport_box); + viewport.left(window_box(true).left()); + glViewport(viewport); /* bind texture for drawing */ camera_view.current().bind(); camera_view.enable(); @@ -905,16 +928,17 @@ 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()); + else + { + /* Draw the camera button if neither the camera or inventory is displayed */ + glUseProgram(flat_program); + glUniformMatrix4fv(uniform["flat"]["transformation"], 1, GL_FALSE, &camera_button.transformation()[0][0]); + camera_button.texture().bind(); + plane.enable(); + glDrawArrays(GL_TRIANGLES, 0, camera_button.attributes("position")->count()); + } SDL_GL_SwapWindow(get_window()); - sb::Log::gl_errors("after test pad"); + sb::Log::gl_errors("at end of update"); /* add a new item if a new barcode was scanned or entered */ if (current_barcode != previous_barcode) { @@ -928,21 +952,22 @@ void Pudding::update() * 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) +Pad::Pad(sb::Texture texture, glm::vec2 offset, float scale, float ratio, std::function on_connect, float rotation) { this->texture(texture); - transform(offset, scale, ratio); + transform(offset, scale, ratio, rotation); this->on_connect(on_connect); - box.gl(true); + box.invert_y(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) +/* Set the Pad's transformation matrix based on an offset, a scale, an aspect ratio, and a rotation. The offset is the + * amount it will be shifted in the (x, y) plane. The scale is a value relative to the (x, y) plane, and 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. The rotation is an angle in radians to rotate the pad object around + * its center after it has been offset and scaled. */ +void Pad::transform(glm::vec2 offset, float scale, float ratio, float rotation) { glm::vec3 scale_components { scale, scale, 1 }; if (ratio > 1.0f) @@ -956,7 +981,8 @@ void Pad::transform(glm::vec2 offset, float scale, float 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)); + Model::transform(glm::translate(glm::vec3{offset.x, offset.y, 0}) * glm::scale(scale_components) * + glm::rotate(rotation, glm::vec3{0.0f, 0.0f, 1.0f})); } /* Set the function that will run when a pad object is clicked. */ diff --git a/src/Pudding.hpp b/src/Pudding.hpp index 4d2cce7..f831d63 100644 --- a/src/Pudding.hpp +++ b/src/Pudding.hpp @@ -44,8 +44,6 @@ #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 * same return type, number of arguments, and argument types determined by the template arguments. @@ -180,6 +178,18 @@ public: * - Has its own response to click * - Shares mouse collision code * - Has its own translate + scale transformation + * + * Example: + * + * 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})); */ class Pad : public Plane { @@ -193,8 +203,8 @@ private: public: Pad() {}; - Pad(sb::Texture, glm::vec2, float, float, callback); - void transform(glm::vec2, float, float); + Pad(sb::Texture, glm::vec2, float, float, callback, float = 0.0f); + void transform(glm::vec2, float, float = 1.0f, float = 0.0f); void on_connect(callback); bool collide(const glm::vec2&) const; @@ -206,7 +216,7 @@ class Pudding : public Game private: /* Defines for effect IDs that will be passed to the shader program. Since COUNT is last and every value - * is the default integer, it will define the number of effects available */ + * is the default integer, it will be set to the number of effects available. */ enum Effect { EFFECT_NONE, @@ -223,20 +233,21 @@ private: }; typedef Game super; - const std::string OPEN_FOOD_API_URL = "https://world.openfoodfacts.org/api/v0/product/"; - const std::string OPEN_PRODUCTS_API_URL = "https://world.openproductsfacts.org/api/v0/product/"; - const std::string NUTRONIX_API_URL = "https://trackapi.nutritionix.com/v2/search/item?upc="; - const std::string BARCODE_MONSTER_API_URL = "https://barcode.monster/api/"; - const std::string BEST_BUY_API_URL_1 = "https://api.bestbuy.com/v1/products(upc="; - const std::string BEST_BUY_API_URL_2 = ")?format=json&apiKey="; - 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 {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}; + inline static const std::string OPEN_FOOD_API_URL = "https://world.openfoodfacts.org/api/v0/product/"; + inline static const std::string OPEN_PRODUCTS_API_URL = "https://world.openproductsfacts.org/api/v0/product/"; + inline static const std::string NUTRONIX_API_URL = "https://trackapi.nutritionix.com/v2/search/item?upc="; + inline static const std::string BARCODE_MONSTER_API_URL = "https://barcode.monster/api/"; + inline static const std::string BEST_BUY_API_URL_1 = "https://api.bestbuy.com/v1/products(upc="; + inline static const std::string BEST_BUY_API_URL_2 = ")?format=json&apiKey="; + inline static const std::string NUTRONIX_NOT_FOUND = "resource not found"; + inline static const std::string GOOGLE_BOOKS_API_URL = "https://www.googleapis.com/books/v1/volumes?q=isbn:"; + inline static const std::string GIANTBOMB_API_URL = "https://www.giantbomb.com/api/release/?api_key="; + inline static const glm::vec3 ZERO_VECTOR_3D {0, 0, 0}; + inline static const glm::vec3 Y_UNIT_NORMAL_3D {0, 1, 0}; + inline static const glm::mat4 VIEW_MATRIX = glm::lookAt({4.0f, 2.0f, 1.0f}, {0.0f, -0.325f, 0.0f}, Y_UNIT_NORMAL_3D); + inline static const glm::vec3 PUDDING_BROWN {0.713f, 0.359f, 0.224f}; + inline static const glm::vec3 PUDDING_YELLOW {0.878f, 0.859f, 0.122f}; + std::shared_ptr poke; std::string current_barcode, previous_barcode, current_config_barcode, current_camera_barcode; std::vector items; Carousel item_carousel; @@ -254,11 +265,14 @@ private: SDL_GLContext capture_frame_thread_context = nullptr; sb::VAO vao; sb::VBO vbo; - Pad pad; + std::map labels; + Pad camera_button, previous_button, next_button, inventory_button; + Box viewport, main_viewport; - void set_pudding_model(float, float, int, int = 1, float = -1.0f, float = 1.0f, float = 0.3f); + void load_pudding_model(float, float, int, int = 1, float = -1.0f, float = 1.0f, float = 0.3f); void load_gl_context(); void load_tiles(); + void load_pads(); void initialize_camera(); void incorporate_open_api(Item&, const std::string&); void incorporate_nutronix_api(Item&); @@ -308,4 +322,7 @@ private: /* float weighted depression rate */ }; +/* Allow a box object to be passed to glViewport instead of four vertices. */ +void glViewport(Box); + #endif