pad class with example that runs camera toggle on click

This commit is contained in:
frank 2021-11-09 23:32:48 -05:00
parent c9868db346
commit 80aebaf8de
9 changed files with 205 additions and 67 deletions

View File

@ -5,7 +5,8 @@
"framerate": 60,
"title": "Pudding",
"debug": false,
"render driver": "opengl"
"render driver": "opengl",
"show-cursor": true
},
"configuration":
{

2
lib/sb

@ -1 +1 @@
Subproject commit 863db5467bfc625df916ac9a04e9d14aa13d7e55
Subproject commit 03d179eed4c8323576157f806806b214e42d07c7

View File

@ -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();
}

View File

@ -56,7 +56,7 @@ std::map<std::string, std::shared_ptr<sb::Attributes>>& Model::attributes()
* access to the public interface of the attributes. */
std::shared_ptr<sb::Attributes>& 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<sb::Attributes>& Model::attributes(const std::string& name)
* object if they are wrapped in a shared pointer. */
std::shared_ptr<sb::Attributes>& 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<sb::Attributes>& 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<std::string, sb::Texture>& Model::texture()
std::map<std::string, sb::Texture>& 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()

View File

@ -30,10 +30,11 @@
class Model
{
protected:
private:
inline static const std::string DEFAULT_TEXTURE_NAME = "default";
std::map<std::string, sb::Texture> model_textures;
std::map<std::string, std::shared_ptr<sb::Attributes>> model_attributes;
std::map<std::string, sb::Texture> model_texture;
glm::mat4 model_transformation = glm::mat4(1);
public:
@ -49,10 +50,13 @@ public:
std::shared_ptr<sb::Attributes>& operator[](const std::string&);
void enable();
void disable();
std::map<std::string, sb::Texture>& texture();
std::map<std::string, sb::Texture>& 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<sb::Attributes> position = std::make_shared<sb::Attributes>(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<sb::Attributes> uv = std::make_shared<sb::Attributes>(sb::Attributes{

View File

@ -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<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}));
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<float>()), 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<void()> 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<void()> 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());
}

View File

@ -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<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
{
@ -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<Item> items;
Carousel item_carousel;
@ -215,7 +245,7 @@ private:
zbar::ImageScanner image_scanner;
std::map<std::string, std::map<std::string, GLuint>> 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<typename return_type, typename ...arguments>
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<return_type, arguments...> connection;
/* float threshold = 1.0f */
/* float force = 0.0f */
/* apply() */
/* remove() */
/* float weighted depression rate */
};
#endif

View File

@ -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));
}

View File

@ -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;
}