diff --git a/config.json b/config.json index bd8e60b..a122fe0 100644 --- a/config.json +++ b/config.json @@ -48,7 +48,7 @@ "enabled": true, "json-save": true, "json-save-directory": "local/scans", - "barcode": "", + "barcode": "014100085980", "capture-device": "/dev/video0" }, "api": @@ -86,12 +86,13 @@ { "main-button-y": -0.75, "main-button-single-x": 0.0, - "main-button-double-x": 0.65, + "main-button-double-x": 0.6, "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" + "arrow-button-scale": 0.2, + "arrow-button-label": "arrow", + "pop-up-viewport-height": 0.6 } } diff --git a/lib/sb b/lib/sb index 54cf012..660865b 160000 --- a/lib/sb +++ b/lib/sb @@ -1 +1 @@ -Subproject commit 54cf01246b0e5ec81ba5b9158248bca7492823db +Subproject commit 660865b2f21b61f145f351514e51b26e3f0b68e9 diff --git a/src/Carousel.hpp b/src/Carousel.hpp index d043af6..079ae46 100644 --- a/src/Carousel.hpp +++ b/src/Carousel.hpp @@ -24,7 +24,7 @@ private: public: template - auto current(Container& container) + auto current(Container& container) const { auto location = container.begin(); if (location != container.end()) @@ -39,35 +39,49 @@ public: } template - auto next(const Container& container) + void next(const Container& container) { offset = ++offset % container.size(); } template - auto previous(const Container& container) + void previous(const Container& container) { offset = sb::mod(--offset, container.size()); } template - auto increment(const Container& container, int amount) + void increment(const Container& container, int amount) { offset = sb::mod(offset + amount, container.size()); } - template - auto beginning() + void beginning() { offset = 0; } template - auto end(const Container& container) + void end(const Container& container) { offset = container.size() - 1; } + /* Return true if the carousel currently points to the location at the beginning of the container. */ + bool at_beginning() const + { + return offset == 0; + } + + /* Return true if the carousel currently points to the location at the end of the container. */ + template + bool at_end(const Container& container) const + { + auto location = container.begin(); + std::advance(location, offset); + return location == container.end(); + } + }; #endif diff --git a/src/Item.cpp b/src/Item.cpp index b50ed44..52dfe59 100644 --- a/src/Item.cpp +++ b/src/Item.cpp @@ -74,25 +74,42 @@ std::string Item::full_name() const void Item::texture(sb::Texture& texture, const std::string& name) { - image.texture(texture, name); + item_view.texture(texture, name); } sb::Texture& Item::current_texture() { - return carousel.current(image.textures())->second; + return carousel.current(item_view.textures())->second; } void Item::next_texture() { - carousel.next(image.textures()); + carousel.next(item_view.textures()); } void Item::previous_texture() { - carousel.previous(image.textures()); + carousel.previous(item_view.textures()); } std::size_t Item::texture_count() { - return image.textures().size(); + return item_view.textures().size(); +} + +Plane& Item::view() +{ + return item_view; +} + +/* Return true if the current texture is the first texture added. */ +bool Item::at_first() const +{ + return carousel.at_beginning(); +} + +/* Return true if the current texture is the last texture added. */ +bool Item::at_last() +{ + return carousel.at_end(item_view.textures()); } diff --git a/src/Item.hpp b/src/Item.hpp index b54c24e..efa593c 100644 --- a/src/Item.hpp +++ b/src/Item.hpp @@ -35,7 +35,7 @@ class Item private: nlohmann::json json = {}; - Plane image; + Plane item_view; Carousel carousel; std::string item_brand_name = "", item_product_name = "", item_upc = ""; void text_property(const std::string&, std::string&, const std::string&); @@ -60,6 +60,9 @@ public: void next_texture(); void previous_texture(); std::size_t texture_count(); + Plane& view(); + bool at_first() const; + bool at_last(); }; diff --git a/src/Model.cpp b/src/Model.cpp index c34fc65..129ca4e 100644 --- a/src/Model.cpp +++ b/src/Model.cpp @@ -137,14 +137,14 @@ void Model::texture(const sb::Texture& texture) this->texture(texture, DEFAULT_TEXTURE_NAME); } -/* Set the transformation matrix. */ +/* Get the model's transformation matrix. */ const glm::mat4& Model::transformation() const { return model_transformation; } -/* Set the transformation matrix. */ -void Model::transform(const glm::mat4& transformation) +/* Set the model's transformation matrix. */ +void Model::transformation(const glm::mat4& transformation) { model_transformation = transformation; } diff --git a/src/Model.hpp b/src/Model.hpp index c0bcc0b..a3d58e7 100644 --- a/src/Model.hpp +++ b/src/Model.hpp @@ -35,7 +35,7 @@ private: inline static const std::string DEFAULT_TEXTURE_NAME = "default"; std::map model_textures; std::map> model_attributes; - glm::mat4 model_transformation = glm::mat4(1); + glm::mat4 model_transformation {1.0f}; public: @@ -56,7 +56,7 @@ public: void texture(const sb::Texture&, const std::string&); void texture(const sb::Texture&); const glm::mat4& transformation() const; - void transform(const glm::mat4&); + void transformation(const glm::mat4&); std::size_t size(); operator glm::mat4() const; diff --git a/src/Pudding.cpp b/src/Pudding.cpp index 0211be3..3c8fd4e 100644 --- a/src/Pudding.cpp +++ b/src/Pudding.cpp @@ -243,8 +243,18 @@ void Pudding::load_pads() } 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()); + camera_button.translation({interface["main-button-single-x"], interface["main-button-y"]}); + camera_button.scale(interface["main-button-scale"], window_box().aspect()); + inventory_button.texture(labels["inventory"]); + inventory_button.translation({interface["main-button-double-x"], interface["main-button-y"]}); + inventory_button.scale(interface["main-button-scale"], window_box().aspect()); + previous_button.texture(labels["arrow"]); + previous_button.translation(glm::vec2({-1, 1}) * interface["arrow-button-location"].get()); + previous_button.scale(interface["arrow-button-scale"], window_box().aspect()); + next_button.texture(labels["arrow"]); + next_button.translation(interface["arrow-button-location"]); + next_button.scale(interface["arrow-button-scale"], window_box().aspect()); + next_button.rotation(glm::radians(180.0f)); } /* Try to create cv::VideoCapture object using device ID #0. If successful, this will create GL texture IDs and storage @@ -325,18 +335,45 @@ void Pudding::respond(SDL_Event& event) 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 }; - /* 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))) + bool over_camera_button = !capture.isOpened() && !item_display_active() && camera_button.collide(ndc), + over_inventory_button = items.size() > 0 && !item_display_active() && !capture.isOpened() && inventory_button.collide(ndc), + over_close_area = (capture.isOpened() || item_display_active()) && get_display().ndc_subsection(main_viewport).collide(ndc); + /* Check for collisions with anything clickable */ + if (over_camera_button || over_inventory_button || over_close_area) { + /* Set cursor to pokey finger */ if (SDL_GetCursor() != poke.get()) { SDL_SetCursor(poke.get()); } + /* Respond to a click */ if (event.type == SDL_MOUSEBUTTONDOWN) { + /* Reset cursor to default arrow */ SDL_SetCursor(SDL_GetDefaultCursor()); - camera_switch.toggle(); + if (over_camera_button) + { + camera_switch.connect(); + } + else if (over_inventory_button) + { + show_item = true; + Box viewport = sb::Display::ndc; + /* Drag viewport completely closed to the bottom of the screen */ + viewport.top(viewport.bottom(), true); + nlohmann::json interface = get_configuration()["interface"]; + /* Drag viewport back up the height of the pop-up window */ + viewport.drag_top(interface["pop-up-viewport-height"]); + /* Get the viewport in pixel resolution to size the buttons to be square inside the viewport */ + Box pixel_resolution = get_display().ndc_to_pixel(viewport); + next_button.scale(interface["arrow-button-scale"], pixel_resolution.aspect()); + previous_button.scale(interface["arrow-button-scale"], pixel_resolution.aspect()); + } + else if (over_close_area) + { + camera_switch.disconnect(); + show_item = false; + } } } else if (SDL_GetCursor() == poke.get()) @@ -381,8 +418,14 @@ void Pudding::add_item(const std::string& upc) if (item.texture_count() > 0) { items.push_back(item); - /* set item index to end so newest item will display */ + /* Set item index to end so newest item will display. */ item_carousel.end(items); + /* Move the camera button away from center to make room for inventory button if this is the first item added. */ + if (items.size() == 1) + { + const nlohmann::json& interface = get_configuration()["interface"]; + camera_button.translation({-1.0f * interface["main-button-double-x"].get(), interface["main-button-y"]}); + } } else { @@ -818,7 +861,7 @@ void Pudding::update() /* shrink viewport if item texture or camera will be displayed */ if (item_display_active() || capture.isOpened()) { - viewport.drag_bottom(0.3f * viewport.height()); + viewport.drag_bottom(0.5f * get_configuration()["interface"]["pop-up-viewport-height"].get() * viewport.height()); } /* Save the main viewport dimensions */ main_viewport = viewport; @@ -912,9 +955,16 @@ void Pudding::update() } glViewport(viewport); current_item().current_texture().bind(); - plane.enable(); + current_item().view().enable(); /* draws rectangle vertices and rectangle texture using UV coords */ - glDrawArrays(GL_TRIANGLES, 0, plane.attributes("position")->count()); + glDrawArrays(GL_TRIANGLES, 0, current_item().view().attributes("position")->count()); + current_item().view().disable(); + /* Draw arrows for cycling through items in inventory */ + if (items.size() > 1 || current_item().texture_count() > 1) + { + next_button.draw(uniform["flat"]["transformation"]); + previous_button.draw(uniform["flat"]["transformation"]); + } } /* draw the camera if the camera has been opened */ if (capture.isOpened()) @@ -932,10 +982,12 @@ void Pudding::update() { /* 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()); + camera_button.draw(uniform["flat"]["transformation"]); + /* And the inventory button if there are items scanned into the inventory */ + if (items.size() > 0) + { + inventory_button.draw(uniform["flat"]["transformation"]); + } } SDL_GL_SwapWindow(get_window()); sb::Log::gl_errors("at end of update"); @@ -947,42 +999,74 @@ void Pudding::update() } } -/* 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, float rotation) +/* Construct a Pad using a texture, a translation, 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 translation and scale values. The translation + * 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 translation, float scale, float ratio, std::function on_connect, float rotation) { this->texture(texture); - transform(offset, scale, ratio, rotation); + this->translation(translation); + this->scale(scale, ratio); + if (rotation) + { + this->rotation(rotation); + } this->on_connect(on_connect); - box.invert_y(true); + collision_box.invert_y(true); } -/* 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) +/* Set angle in radians the pad will be rotated. The pad will be rotated around its center. The collision box will not + * change, so the box will not contain the entire pad if the angle is not a multiple of pi/2. The pad's transformation + * matrix will automatically be set to incorporate this rotation transformation. */ +void Pad::rotation(float angle) { - glm::vec3 scale_components { scale, scale, 1 }; - if (ratio > 1.0f) + rotation_angle = angle; + transform(); +} + +/* Set the scale using a factor and ratio that will transform the pad in the X and Y dimensions. The ratio will determine + * how much each axis is scaled. If the ratio is above one, the X-axis's scale will be divided by the ratio. If the 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 collision box + * will be scaled by the same factors. The pad's transformation matrix will automatically be set to incorporate this + * scale transformation. */ +void Pad::scale(float factor, float ratio) +{ + scale_factor = factor; + scale_ratio = ratio; + transform(); +} + +/* Set a translation for the pad object in the X and Y dimension using a 2d vector. The collision box will be moved by the + * same translation. The pad's transformation matrix will automatically be set to incorporate this translation + * transformation. */ +void Pad::translation(const glm::vec2& translation) +{ + translation_vector = translation; + transform(); +} + +/* Set the transformation matrix for the pad object by applying the scale to the translation and the rotation to the + * resulting matrix, meaning the transformations will be applied to the pad in the order of: translate, scale, and + * rotate. The collision box will be scaled and moved to fit around the position coordinates that would result from + * applying this transformation to the position coordinates. */ +void Pad::transform() +{ + glm::vec3 scale { scale_factor, scale_factor, 1.0f }; + if (scale_ratio > 1.0f) { - scale_components.x /= ratio; + scale.x /= scale_ratio; } - else if (ratio < 1.0f) + else if (scale_ratio < 1.0f) { - scale_components.y *= ratio; + scale.y *= scale_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) * - glm::rotate(rotation, glm::vec3{0.0f, 0.0f, 1.0f})); + collision_box.size(2.0f * glm::vec2{scale.x, scale.y}, true); + collision_box.center(translation_vector); + Model::transformation(glm::translate(glm::vec3{translation_vector.x, translation_vector.y, 0.0f}) * + glm::scale(scale) * glm::rotate(rotation_angle, ROTATION_AXIS)); } /* Set the function that will run when a pad object is clicked. */ @@ -991,11 +1075,19 @@ 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. */ +/* Returns true if the point at position collides with the pad's collision box. */ bool Pad::collide(const glm::vec2& position) const { - return box.collide(position); + return collision_box.collide(position); +} + +void Pad::draw(GLuint uniform_id) +{ + glUniformMatrix4fv(uniform_id, 1, GL_FALSE, &transformation()[0][0]); + texture().bind(); + enable(); + glDrawArrays(GL_TRIANGLES, 0, attributes("position")->count()); + disable(); } void glViewport(Box box) diff --git a/src/Pudding.hpp b/src/Pudding.hpp index f831d63..90c486a 100644 --- a/src/Pudding.hpp +++ b/src/Pudding.hpp @@ -7,8 +7,11 @@ // ☆ GUNKISS ☆ \\ \ \ //_________________________\\ `---------------------------------------------------------------*/ -#ifndef Pudding_h_ -#define Pudding_h_ +#ifndef PUDDING_H_ +#define PUDDING_H_ + +/* Needed for functions in glm/gtx/ */ +#define GLM_ENABLE_EXPERIMENTAL #include #include @@ -24,6 +27,7 @@ #include "sdl2-gfx/SDL2_gfxPrimitives.h" #include "json/json.hpp" #include "glm/glm.hpp" +#include "glm/gtx/matrix_decompose.hpp" #include "opencv2/core.hpp" #include "opencv2/videoio.hpp" #include "opencv2/highgui.hpp" @@ -196,17 +200,25 @@ class Pad : public Plane private: + inline static const glm::vec3 ROTATION_AXIS {0.0f, 0.0f, 1.0f}; using callback = std::function; Connection<> connection; - Box box; + Box collision_box; + float rotation_angle = 0.0f, scale_factor = 0.0f, scale_ratio = 1.0f; + glm::vec2 translation_vector {0.0f, 0.0f}; + + void transform(); public: Pad() {}; Pad(sb::Texture, glm::vec2, float, float, callback, float = 0.0f); - void transform(glm::vec2, float, float = 1.0f, float = 0.0f); + void rotation(float); + void scale(float, float = 1.0f); + void translation(const glm::vec2&); void on_connect(callback); bool collide(const glm::vec2&) const; + void draw(GLuint); }; @@ -267,7 +279,7 @@ private: sb::VBO vbo; std::map labels; Pad camera_button, previous_button, next_button, inventory_button; - Box viewport, main_viewport; + Box viewport, main_viewport, pop_up_viewport; void load_pudding_model(float, float, int, int = 1, float = -1.0f, float = 1.0f, float = 0.3f); void load_gl_context();