gunkiss/src/Pudding.hpp

312 lines
10 KiB
C++

/* _______________ ,----------------------------------------------------------------.
//`````````````\\ \ \
//~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
//=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
// \\ \ \
// \\ \ code released under the zlib license [git.nugget.fun/pudding] \
// ☆ GUNKISS ☆ \\ \ \
//_________________________\\ `---------------------------------------------------------------*/
#ifndef Pudding_h_
#define Pudding_h_
#include <stdlib.h>
#include <string>
#include <iostream>
#include <algorithm>
#include <thread>
#include <memory>
#include <stdexcept>
#include <functional>
#include <curl/curl.h>
#include "SDL.h"
#include "SDL_image.h"
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
#include "json/json.hpp"
#include "glm/glm.hpp"
#include "opencv2/core.hpp"
#include "opencv2/videoio.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "zbar.h"
#include "Game.hpp"
#include "Color.hpp"
#include "extension.hpp"
#include "filesystem.hpp"
#include "Animation.hpp"
#include "Texture.hpp"
#include "GLObject.hpp"
#include "Log.hpp"
#include "Attributes.hpp"
#include "VBO.hpp"
#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
* same return type, number of arguments, and argument types determined by the template arguments.
*
* Original test code:
*
* Connection<> connection_d(std::bind(&Game::print_frame_length_history, this));
* connection_d.toggle();
* Connection<int, int, int> connection_f {
* std::function<int(int, int)>(&sb::mod), std::function<int(int, int)>(&sb::mod) };
* Connection<> connection_g = connection_d;
* connection_g.toggle();
* connection_g.disconnect();
* int result;
* result = connection_f.connect(3, 5);
* std::cout << result << " ";
* std::cout << connection_f.disconnect(20, 6) << " ";
* result = connection_f.toggle(800, 120);
* std::cout << result << std::endl;
* result = connection_f.connect(111, 44);
* std::cout << result << std::endl;
*/
template<typename return_type = void, typename... arguments>
class Connection
{
private:
enum State : bool
{
STATE_OFF,
STATE_ON
};
using callback = std::function<return_type(arguments...)>;
State connection_state = STATE_OFF;
callback on_connect_callback, on_disconnect_callback;
public:
/* Without any arguments, the connection object will be in the disconnected state with empty functions. Otherwise,
* the supplied functions will be added to the connection object. The first function argument will be run on a
* connection, and the second function argument will be run on a disconnection. */
Connection(callback on_connect_callback = callback(), callback on_disconnect_callback = callback())
{
if (on_connect_callback)
{
on_connect(on_connect_callback);
if (on_disconnect_callback)
{
on_disconnect(on_disconnect_callback);
}
}
}
/* Set the function that will run when a connection is made. */
void on_connect(callback on_connect)
{
on_connect_callback = on_connect;
}
/* Set the function that will run when a disconnection happens. */
void on_disconnect(callback on_disconnect)
{
on_disconnect_callback = on_disconnect;
}
/* Set state to Connection::STATE_ON and run response function. If return_type is non-void and the
* connection is already connected, the function will not run and the return value will be a default
* constructed value of the type return_type. Therefore, return_type must be default constructible. */
return_type connect(arguments... args)
{
if (!*this)
{
connection_state = STATE_ON;
if (on_connect_callback)
{
return on_connect_callback(args...);
}
}
return return_type();
}
/* Set state to Connection::STATE_OFF and run response function. If return_type is non-void and the
* connection is already disconnected, the function will not run and the return value will be a default
* constructed value of the type return_type. Therefore, return_type must be default constructible. */
return_type disconnect(arguments... args)
{
if (*this)
{
connection_state = STATE_OFF;
if (on_disconnect_callback)
{
return on_disconnect_callback(args...);
}
}
return return_type();
}
/* Set state to the opposite of current state, causing the appropriate response function to run. */
return_type toggle(arguments... args)
{
if (*this)
{
return disconnect(args...);
}
else
{
return connect(args...);
}
}
/* Return true if state is Connection::STATE_ON, false otherwise. */
bool connected()
{
return connection_state;
}
/* When called as a boolean, return the connection state. */
operator bool()
{
return connected();
}
};
/* 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<void()>;
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
{
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 */
enum Effect
{
EFFECT_NONE,
EFFECT_SNAKE,
EFFECT_WOBBLE,
EFFECT_COUNT
};
/* Defines for UV transformations available in the fragment shader program */
enum UVTransformation
{
UV_NONE,
UV_SQUIRCLE
};
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};
std::string current_barcode, previous_barcode, current_config_barcode, current_camera_barcode;
std::vector<Item> items;
Carousel item_carousel;
int effect_id = EFFECT_NONE, pudding_triangle_vertex_count = 0, pudding_fan_vertex_count = 0;
cv::VideoCapture capture;
zbar::ImageScanner image_scanner;
std::map<std::string, std::map<std::string, GLuint>> uniform;
GLuint flat_program, mvp_program;
glm::mat4 projection, model {1.0f}, mvp;
Model pudding_model;
Plane plane;
Background background;
CameraView camera_view;
bool show_item = false, reading_capture_frame = false;
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.0f, float = 1.0f, float = 0.3f);
void load_gl_context();
void load_tiles();
void initialize_camera();
void incorporate_open_api(Item&, const std::string&);
void incorporate_nutronix_api(Item&);
void incorporate_edamam_api(Item&);
void incorporate_best_buy_api(Item&);
void incorporate_google_books_api(Item&);
void save_item_json(const nlohmann::json&, const Item&, const std::string&) const;
nlohmann::json json_from_url(const std::string&, const std::vector<std::string>& = {});
void curl_get_bytes(const std::string& url, std::vector<std::uint8_t>&, const std::vector<std::string>& = {}) const;
static size_t curl_write_response(std::uint8_t*, size_t, size_t, std::vector<std::uint8_t>*);
sb::Texture texture_from_image_url(const std::string&) const;
static void destroy_texture(GLuint*);
bool item_display_active() const;
static int capture_frame(void*);
/* Initialize camera on connection and release on disconnection. */
Connection<> camera_switch {
std::bind(&Pudding::initialize_camera, this),
[&] { capture.release(); }
};
public:
Pudding();
void respond(SDL_Event&);
void add_item(const std::string&);
Item& current_item();
void update();
virtual std::string class_name() const { return "Pudding"; }
};
/* 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<typename return_type, typename ...arguments>
class Button
{
private:
Connection<return_type, arguments...> connection;
/* float threshold = 1.0f */
/* float force = 0.0f */
/* apply() */
/* remove() */
/* float weighted depression rate */
};
#endif