610 lines
22 KiB
C++
610 lines
22 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_
|
|
|
|
/* Needed for functions in glm/gtx/ */
|
|
#define GLM_ENABLE_EXPERIMENTAL
|
|
|
|
/* cURL and cv::VideoCapture are not available for Emscripten, so use alternatives for Emscripten builds */
|
|
#if defined(__EMSCRIPTEN__)
|
|
#include <emscripten/fetch.h>
|
|
#include <emscripten/bind.h>
|
|
using namespace emscripten;
|
|
#else
|
|
#include <curl/curl.h>
|
|
#include "opencv2/videoio.hpp"
|
|
#include "opencv2/highgui.hpp"
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <algorithm>
|
|
#include <thread>
|
|
#include <mutex>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <functional>
|
|
#include <chrono>
|
|
#include "SDL.h"
|
|
#include "SDL_image.h"
|
|
#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/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"
|
|
|
|
/*!
|
|
* This class is used for printing the running time of a function or lambda. It can be
|
|
* inserted inline into existing code by wrapping the block to time in a lambda. It can also be
|
|
* given a function and optionally the function's arguments and will return a value of the type specified.
|
|
*
|
|
* The following code
|
|
*
|
|
* ~~~{.cpp}
|
|
* std::string fake(std::string x, std::string y)
|
|
* {
|
|
* return x + y;
|
|
* }
|
|
*
|
|
* // Needs SPACEBOX initialization for log call
|
|
* int ii = 0;
|
|
* time_it()([&] { while (ii++ < 1000); });
|
|
* ii = 0;
|
|
* int revolving = time_it<int, int>("for revolving")([&](int x) -> int { while (ii++ < 10000); return x; }, 1);
|
|
* ii = 0;
|
|
* std::cout << "Revolving is " << revolving << std::endl;
|
|
* std::string addition = time_it<std::string, std::string, std::string>("for combination")(fake, "Hello, ", "World!");
|
|
* std::cout << "Combination is " << addition << std::endl;
|
|
* time_it("for many combinations")([&] { while (ii++ < 100000) { fake("a", "b"); } });
|
|
* ~~~
|
|
*
|
|
* gives the expected output
|
|
*
|
|
* Elapsed time: 2.588e-06s
|
|
* Elapsed time for revolving: 2.1547e-05s
|
|
* Revolving is 1
|
|
* Elapsed time for combination: 7.27e-07s
|
|
* Combination is Hello, World!
|
|
* Elapsed time for many combinations: 0.0107351s
|
|
*/
|
|
template<typename return_type = void, typename... arguments>
|
|
class time_it
|
|
{
|
|
|
|
private:
|
|
|
|
std::string info = "";
|
|
|
|
public:
|
|
|
|
time_it(std::string info = "") : info(info) {};
|
|
|
|
return_type operator()(const std::function<return_type(arguments...)>&& block, arguments... args)
|
|
{
|
|
std::cout << "{Elapsed time";
|
|
if (!info.empty())
|
|
{
|
|
std::cout << " " << info;
|
|
}
|
|
std::cout << ": ";
|
|
auto start = std::chrono::steady_clock::now();
|
|
std::chrono::duration<float> elapsed;
|
|
if constexpr (std::is_same_v<return_type, void>)
|
|
{
|
|
block(args...);
|
|
elapsed = std::chrono::steady_clock::now() - start;
|
|
std::cout << elapsed.count() << "s}" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
return_type result = block(args...);
|
|
elapsed = std::chrono::steady_clock::now() - start;
|
|
std::cout << elapsed.count() << "s}" << std::endl;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
/* 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
|
|
*
|
|
* 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<void()>()};
|
|
* const std::vector<glm::vec2>& 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
|
|
{
|
|
|
|
private:
|
|
|
|
inline static const glm::vec3 ROTATION_AXIS {0.0f, 0.0f, 1.0f};
|
|
using callback = std::function<void()>;
|
|
Connection<> connection;
|
|
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 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);
|
|
|
|
};
|
|
|
|
/* These variables will be bound to JS. They are placed in the global scope, so they can be read and written by both
|
|
* C++ and JS. The associated functions are bound to JS so they can be used to write values to the variables. The
|
|
* first flag is used by both C++ and JS builds, so it is always included. */
|
|
bool new_frame_available = false;
|
|
#ifdef __EMSCRIPTEN__
|
|
unsigned int emscripten_heap_offset = 0;
|
|
void flag_frame();
|
|
void set_heap_offset(int offset);
|
|
#endif
|
|
|
|
/*!
|
|
* Type declaration of a function that will accept a vector of bytes for the response data from a web request and a string for the URL.
|
|
*/
|
|
using web_callback = std::function<void(const std::vector<std::uint8_t>&, const std::string&)>;
|
|
|
|
/*!
|
|
* Store the state, response data, and response function of a request for web data sent to either cURL or Emscripten.
|
|
*/
|
|
class Request
|
|
{
|
|
private:
|
|
|
|
web_callback callback = nullptr;
|
|
std::vector<std::uint8_t> response;
|
|
bool is_finished = false;
|
|
std::string request_url;
|
|
|
|
public:
|
|
|
|
/*!
|
|
* Construct a request object, specifying a callback that will be passed the complete data in bytes. The callback must therefore accept a
|
|
* vector of bytes. The URL of the request can be stored.
|
|
*
|
|
* @param callback A function object that accepts a vector of bytes.
|
|
* @param url URL of the request
|
|
*/
|
|
Request(const web_callback& callback, const std::string& url = "");
|
|
|
|
/*!
|
|
* Get the URL of the request if it has been specified.
|
|
*
|
|
* @return The URL of the request or an empty string if the URL was not set
|
|
*/
|
|
const std::string& url() const;
|
|
|
|
/*!
|
|
* Add the bytes pointed to by buffer to the storage vector.
|
|
*/
|
|
void store(const std::uint8_t* buffer, const std::size_t& size);
|
|
|
|
/*!
|
|
* Call the user supplied callback and set state to finished.
|
|
*/
|
|
void respond();
|
|
|
|
/*!
|
|
* Set the finished state to true.
|
|
*/
|
|
void mark_finished();
|
|
|
|
/*!
|
|
* Check if the request is complete, meaning the data has been stored in memory and the callback has run.
|
|
*
|
|
* @return true if complete, false otherwise
|
|
*/
|
|
const bool& finished() const;
|
|
|
|
};
|
|
|
|
/*!
|
|
* The main game object. There is currently only support for one of these to exist at a time.
|
|
*/
|
|
class Pudding : public Game
|
|
{
|
|
|
|
private:
|
|
|
|
/* Defines for effect IDs that will be passed to the shader program. Since EFFECT_COUNT is last and every value
|
|
* is the default integer, it will be set to 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
|
|
};
|
|
|
|
/* Convention for calling parent class in a consistent way across classes */
|
|
typedef Game super;
|
|
|
|
/* Constants */
|
|
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 NUTRITIONIX_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 NUTRITIONIX_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};
|
|
|
|
/* Member variables */
|
|
std::shared_ptr<SDL_Cursor> poke;
|
|
std::string current_barcode, previous_barcode, current_config_barcode, current_camera_barcode;
|
|
std::vector<Item> items;
|
|
Item incoming_item;
|
|
Carousel item_carousel;
|
|
int effect_id = EFFECT_NONE, pudding_triangle_vertex_count = 0, pudding_fan_vertex_count = 0;
|
|
#ifndef __EMSCRIPTEN__
|
|
cv::VideoCapture capture;
|
|
#endif
|
|
cv::Mat camera_frame;
|
|
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, camera_view;
|
|
Background background;
|
|
bool show_item = false;
|
|
sb::VAO vao;
|
|
sb::VBO vbo;
|
|
std::map<std::string, sb::Texture> labels;
|
|
Pad camera_button, previous_button, next_button, inventory_button;
|
|
Box viewport, main_viewport, pop_up_viewport;
|
|
std::vector<Request*> requests;
|
|
|
|
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();
|
|
|
|
/*!
|
|
* Try to create cv::VideoCapture object using device ID #0. If successful, this will also create a GL texture ID and
|
|
* storage for the camera frame on the GPU, so it must be called after GL context has been created. Create and detach
|
|
* a thread which will continuously read frame data.
|
|
*/
|
|
void open_camera();
|
|
|
|
/*!
|
|
* Release camera resources.
|
|
*/
|
|
void close_camera();
|
|
|
|
/*!
|
|
* Check the response from Open Food/Products API and use the result to fill out item properties if found. Request the image
|
|
* data if an image URL is found.
|
|
*
|
|
* @param storage JSON as raw bytes fetched from the web, written to a vector
|
|
* @param url URL of the request
|
|
*/
|
|
void incorporate_open_api(const std::vector<std::uint8_t>& json_bytes, const std::string& url);
|
|
|
|
/*!
|
|
* Check the response from Nutritionix API and use the result to fill out item properties if found. Request the image data if an
|
|
* image URL is found.
|
|
*
|
|
* @param storage JSON as raw bytes fetched from the web, written to a vector
|
|
* @param url URL of the request
|
|
*/
|
|
void incorporate_nutritionix_api(const std::vector<std::uint8_t>& json_bytes, const std::string& url);
|
|
|
|
/*!
|
|
* Check the response from Edamame API and use the result to fill out item properties if found. Request the image data if an
|
|
* image URL is found.
|
|
*
|
|
* @param storage JSON as raw bytes fetched from the web, written to a vector
|
|
* @param url URL of the request
|
|
*/
|
|
void incorporate_edamam_api(const std::vector<std::uint8_t>& json_bytes, const std::string& url);
|
|
|
|
/*!
|
|
* Check the response from Best Buy API and use the result to fill out item properties if found. Request image data if an
|
|
* image URL is found.
|
|
*
|
|
* @param storage JSON as raw bytes fetched from the web, written to a vector
|
|
* @param url URL of the request
|
|
*/
|
|
void incorporate_best_buy_api(const std::vector<std::uint8_t>& json_bytes, const std::string& url);
|
|
|
|
/*!
|
|
* Check the response from Google API and use the result to fill out item properties if found. Request image data if an
|
|
* image URL is found.
|
|
*
|
|
* @param storage JSON as raw bytes fetched from the web, written to a vector
|
|
* @param url URL of the request
|
|
*/
|
|
void incorporate_google_books_api(const std::vector<std::uint8_t>& json_bytes, const std::string& url);
|
|
|
|
void save_item_json(const nlohmann::json&, const Item&, const std::string&) const;
|
|
|
|
/*!
|
|
* Fetch data from `url` as raw bytes. The data will be copied into a vector which will be passed to a user supplied function. A request object
|
|
* will be added to `launched_requests`. That vector can be checked to determine when all requests are complete.
|
|
*
|
|
* The compiler will determine whether to use cURL or the Emscripten Fetch API to do the retrieval, depending on whether it is compiling for
|
|
* Emscripten.
|
|
*
|
|
* @param url URL containing data to be retrieved
|
|
* @param callback A function pointer for a function that accepts a reference to a vector of bytes and a reference to an Item. The bytes are the
|
|
* response data retrieved from `url`. The function can be any arbitrary code that uses the data.
|
|
* @param headers Request headers as a vector of strings formatted as ["name1", "value1", "name2", "value2", ...]
|
|
*/
|
|
void web_get_bytes(std::string url, const web_callback& callback, const std::vector<std::string>& headers = {});
|
|
|
|
static void destroy_texture(GLuint*);
|
|
bool item_display_active() const;
|
|
void capture_frame();
|
|
|
|
/*!
|
|
* Create a texture to store the image data and add it to the incoming item object.
|
|
*
|
|
* @param image image data as raw bytes fetched from the web, written to a vector
|
|
* @param url image URL which will will be the Texture's name
|
|
*/
|
|
void store_web_image(const std::vector<std::uint8_t>& image, const std::string& url);
|
|
|
|
/* Declare the appropriate callbacks for asynchronous web data loaders. Either cURL by default, or Fetch if compiling for Emscripten. */
|
|
#if defined(__EMSCRIPTEN__)
|
|
|
|
/*!
|
|
* This will be called automatically when request data is sucessfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
|
|
* Data will be written to a vector, and a user supplied callback will be called and passed the data. The callback requested by the
|
|
* caller is in fetch->userData.
|
|
*
|
|
* @param fetch an object created by Emscripten that stores parameters for accessing the downloaded data
|
|
*/
|
|
static void fetch_success(emscripten_fetch_t* fetch);
|
|
|
|
/*!
|
|
* This will be called automatically when request data is not successfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
|
|
*
|
|
* @param fetch an object created by Emscripten that stores parameters related to the request
|
|
*/
|
|
static void fetch_error(emscripten_fetch_t* fetch);
|
|
|
|
#else
|
|
|
|
/*!
|
|
* This will be called by cURL when it has received a buffer of data. The data will be stored by the object at `request`. This may
|
|
* be called multiple times before the entire data received from the originally submitted URL is received.
|
|
*
|
|
* @param buffer pointer to data cURL is transferring into memory
|
|
* @param size size in bytes of each value
|
|
* @param count number of values
|
|
* @param request pointer to a request object which will store the data which will be freed in the update loop
|
|
* @return number of bytes copied
|
|
*/
|
|
static std::size_t curl_write_response(std::uint8_t* buffer, std::size_t size, std::size_t count, Request* request);
|
|
|
|
#endif
|
|
|
|
/* Open camera on connection and close on disconnection. */
|
|
Connection<> camera_switch {
|
|
std::bind(&Pudding::open_camera, this),
|
|
std::bind(&Pudding::close_camera, this)
|
|
};
|
|
|
|
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 */
|
|
};
|
|
|
|
/* Allow a box object to be passed to glViewport instead of four vertices. */
|
|
void glViewport(Box);
|
|
|
|
#endif
|