diff --git a/.gitignore b/.gitignore
index a3e509d..fdf0e27 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@ local/
BPmono.ttf
compile_commands.json
gunkiss
+*.data
+*.wasm
+gunkiss.js
diff --git a/Makefile b/Makefile
index 19db7f4..0f9ebfe 100644
--- a/Makefile
+++ b/Makefile
@@ -83,15 +83,15 @@ $(SRC_DIR)Model.o : $(addprefix $(SB_SRC_DIR),extension.hpp Attributes.hpp Textu
$(SRC_DIR)Item.o : $(addprefix $(SB_SRC_DIR),Texture.hpp Log.hpp utility.hpp) $(addprefix $(SRC_DIR),Model.hpp Carousel.hpp)
$(SRC_DIR)Pudding.o : $(SRC_H_FILES) $(SB_H_FILES)
%.o : %.cpp %.hpp
- $(CXX) $(CPP_FLAGS) $< -c -o $@
+ $(CXX) $(CXXFLAGS) $< -c -o $@
###############
# Linux build #
###############
linux : CFLAGS = -g -Wall -Wextra -O0 -c -I$(SB_LIB_DIR) -I$(SB_SRC_DIR) $(SDL_CFLAGS) -I$(HOME)/local/zbar/include \
- -I$(HOME)/local/opencv/include/opencv4
-linux : CPP_FLAGS = $(CFLAGS) --std=c++17
+ -I $(HOME)/local/opencv/include/opencv4 -I $(HOME)/ext/software/emsdk/upstream/emscripten/system/include
+linux : CXXFLAGS = $(CFLAGS) --std=c++17
linux : LFLAGS = $(SDL_LFLAGS) -Wl,--enable-new-dtags -lpthread -lGL -lGLESv2 -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -lcurl \
-L$(HOME)/local/opencv/lib -Wl,-rpath,$(HOME)/local/opencv/lib -lopencv_videoio -lopencv_core -lopencv_highgui -lopencv_imgproc \
-L$(HOME)/local/zbar/lib -Wl,-rpath,$(HOME)/local/zbar/lib -lzbar
@@ -107,29 +107,38 @@ linux : $(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPri
# Use Emscripten to output JavaScript and an HTML index page for running in the browser
EMSCRIPTENHOME = $(HOME)/ext/software/emsdk/upstream/emscripten
-EMSCRIPTEN_CFLAGS = -O3 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" \
- -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 -s MAX_WEBGL_VERSION=1 -s EXPORTED_FUNCTIONS="['_main']" \
- -s ALLOW_MEMORY_GROWTH=1 -s GL_PREINITIALIZED_CONTEXT=1 -s ENVIRONMENT=web --shell-file shell_minimal.html \
- --no-heap-copy -I$(SFW_LIB_DIR) -I$(SFW_SRC_DIR)
-EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "config.json" --preload-file "resource"
+EMSCRIPTEN_CFLAGS = -O1 -Wall -s USE_SDL=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS="['png', 'jpg']" -s USE_SDL_TTF=2 -s USE_SDL_MIXER=2 \
+ --no-heap-copy -I $(SB_LIB_DIR) -I $(SB_SRC_DIR) -I $(HOME)/local/zbar/include \
+ -I $(HOME)/ext/software/opencv-4.6.0/modules/videoio/include/ \
+ -I $(HOME)/ext/software/opencv-4.6.0/modules/core/include/ \
+ -I $(HOME)/ext/software/opencv-4.6.0/modules/highgui/include/ \
+ -I $(HOME)/ext/software/opencv-4.6.0/modules/imgproc/include/ \
+ -I $(HOME)/ext/software/opencv-4.6.0/modules/imgcodecs/include/ \
+ -I $(HOME)/ext/software/opencv-4.6.0/build_wasm/
+EMSCRIPTEN_LFLAGS = -s MIN_WEBGL_VERSION=2 -s EXPORTED_FUNCTIONS="['_main']" -s ALLOW_MEMORY_GROWTH=1 -s FULL_ES3=1 \
+ -sLLD_REPORT_UNDEFINED -s FETCH --bind $(wildcard $(addprefix $(HOME)/ext/software/opencv-4.6.0/build_wasm/lib/,*.a)) \
+ $(HOME)/ext/software/ZBar/zbar/.libs/libzbar.a
+EMSCRIPTEN_PRELOADS = --preload-file "BPmono.ttf"@/ --preload-file "config.json"@/ --preload-file "resource/"@/"resource/" \
+ --preload-file "src/shaders/"@/"src/shaders/"
emscripten : CC = $(EMSCRIPTENHOME)/emcc
emscripten : CXX = $(EMSCRIPTENHOME)/em++
emscripten : CFLAGS = $(EMSCRIPTEN_CFLAGS)
-emscripten : CPP_FLAGS = $(CFLAGS) --std=c++17
+emscripten : CXXFLAGS = $(CFLAGS) --std=c++17
emscripten : $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o) $(SB_O_FILES) $(SRC_O_FILES)
$(CREATE_FONT_SYMLINK)
- $(CXX) $(CPP_FLAGS) $(EMSCRIPTEN_PRELOADS) $^ -o "index.html"
+ $(CXX) $^ $(CXXFLAGS) $(EMSCRIPTEN_LFLAGS) $(EMSCRIPTEN_PRELOADS) -o gunkiss.js
#########################
# Clean up object files #
#########################
clean :
- -rm $(SRC_DIR)*.o
+ -find $(SRC_DIR) -iname "*.o" -delete
-clean-all :
- -find . -iname "*.o" -exec rm {} \;
+clean-all : clean
+ -find $(SB_SRC_DIR) -iname "*.o" -delete
+ -find $(SB_LIB_DIR) -iname "*.o" -delete
#############
# compiledb #
diff --git a/config.json b/config.json
index cbae764..9708afe 100644
--- a/config.json
+++ b/config.json
@@ -3,9 +3,9 @@
{
"dimensions": [460, 768],
"framerate": 60,
- "title": "Pudding",
+ "title": "Gunkiss",
"debug": false,
- "render driver": "opengl",
+ "render driver": "opengles2",
"show-cursor": true,
"camera-resolution": [1280, 720]
},
@@ -29,7 +29,7 @@
{
"screenshot-directory": "local/screenshots",
"video-directory": "local/video",
- "enabled": true,
+ "enabled": false,
"write-mp4": true,
"video-frame-length": 33.333,
"max-video-memory": 2000,
@@ -57,13 +57,13 @@
"enabled": true,
"json-save": true,
"json-save-directory": "local/scans",
- "barcode": "0140231056",
+ "barcode": "",
"capture-device": "/dev/video0"
},
"api":
{
- "user-agent": "Custom pudding creation game for http://nugget.fun",
+ "user-agent": "Custom pudding creation game under development for https://shampoo.ooo",
"nutronix-app-id": "ea0f2e7e",
"nutronix-app-key": "39218dde526dd3349daa028deda518ae",
"edamam-app-id": "c23b139f",
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..a996fc5
--- /dev/null
+++ b/index.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/sb b/lib/sb
index 0bf2e12..b1fb77b 160000
--- a/lib/sb
+++ b/lib/sb
@@ -1 +1 @@
-Subproject commit 0bf2e1293542da180a325455610a72df5697853d
+Subproject commit b1fb77b1c8a2902fde711ede1a45b459013dc876
diff --git a/src/Item.cpp b/src/Item.cpp
index 0c5a384..a9c2324 100644
--- a/src/Item.cpp
+++ b/src/Item.cpp
@@ -1,11 +1,11 @@
-/* _______________ ,----------------------------------------------------------------.
- //`````````````\\ \ \
- //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
- //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
- // \\ \ \
- // \\ \ code released under the zlib license [git.nugget.fun/pudding] \
- // ☆ GUNKISS ☆ \\ \ \
-//_________________________\\ `---------------------------------------------------------------*/
+/* _______________ ,-------------------------------------------------.
+ //`````````````\\ \ \
+ //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+ //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+ // \\ \ \
+ // \\ \ zlib licensed code at [git.nugget.fun/pudding] \
+ // ☆ GUNKISS ☆ \\ \ \
+//_________________________\\ `-------------------------------------------------*/
#include "Item.hpp"
diff --git a/src/Pudding.cpp b/src/Pudding.cpp
index ad0a2dc..0f4cd3a 100644
--- a/src/Pudding.cpp
+++ b/src/Pudding.cpp
@@ -24,6 +24,18 @@ int main()
return 0;
}
+#ifdef __EMSCRIPTEN__
+void flag_frame()
+{
+ new_frame_available = true;
+}
+
+void set_heap_offset(int offset)
+{
+ emscripten_heap_offset = offset;
+}
+#endif
+
/* Initialize a Pudding instance */
Pudding::Pudding()
{
@@ -35,9 +47,6 @@ Pudding::Pudding()
/* initialize a zbar image scanner for reading barcodes of any format */
image_scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
- /* Add a texture to the camera Plane for storing frame image data */
- camera_view.texture(sb::Texture());
-
/* set up pudding model */
nlohmann::json pudding = configuration()["pudding"];
load_pudding_model(pudding["top-radius"], pudding["base-radius"], pudding["ring-vertex-count"], pudding["layer-count"],
@@ -46,6 +55,12 @@ Pudding::Pudding()
/* loading GL context instead of SDL context for 3D */
load_gl_context();
+ /* Add a texture to the camera Plane for storing frame image data */
+ camera_view.texture(sb::Texture());
+ glm::mat4 flip = glm::mat4(1);
+ flip[1][1] = -1;
+ camera_view.transformation(flip);
+
/* Load background tiles */
load_tiles();
@@ -175,16 +190,15 @@ void Pudding::load_gl_context()
vao.generate();
vao.bind();
- /* Generate ID for the vertex buffer object that will hold all vertex data. Since we're using one buffer, data
- * will be copied in one after the other, offset to after the previous data location. The same buffer offset will
- * be passed to the vertex attributes for each data. */
+ /* Generate ID for the vertex buffer object that will hold all vertex data. Using one buffer for all attributes, data
+ * will be copied in one after the other. */
vbo.generate();
vbo.bind();
/* Load two shader programs, one for rendering the flat objects, and one for rendering the 3D model. Load and configure
* the flat shader program first. */
- GLuint vertex_shader = load_shader("src/flat.vert", GL_VERTEX_SHADER);
- GLuint fragment_shader = load_shader("src/flat.frag", GL_FRAGMENT_SHADER);
+ GLuint vertex_shader = load_shader("src/shaders/flat.vert", GL_VERTEX_SHADER);
+ GLuint fragment_shader = load_shader("src/shaders/flat.frag", GL_FRAGMENT_SHADER);
flat_program = glCreateProgram();
glAttachShader(flat_program, vertex_shader);
glAttachShader(flat_program, fragment_shader);
@@ -192,8 +206,8 @@ void Pudding::load_gl_context()
Plane::uv->bind(1, flat_program, "vertex_uv");
/* load, configure and link the 3D world program */
- vertex_shader = load_shader("src/mvp.vert", GL_VERTEX_SHADER);
- fragment_shader = load_shader("src/mvp.frag", GL_FRAGMENT_SHADER);
+ vertex_shader = load_shader("src/shaders/mvp.vert", GL_VERTEX_SHADER);
+ fragment_shader = load_shader("src/shaders/mvp.frag", GL_FRAGMENT_SHADER);
mvp_program = glCreateProgram();
glAttachShader(mvp_program, vertex_shader);
glAttachShader(mvp_program, fragment_shader);
@@ -275,8 +289,9 @@ void Pudding::load_pads()
* 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 Pudding::initialize_camera()
+void Pudding::open_camera()
{
+#ifndef __EMSCRIPTEN__
/* Open the OpenCV capture, using device ID #0 to get the default attached camera. */
int device_id = 0;
capture.open(device_id);
@@ -314,6 +329,18 @@ void Pudding::initialize_camera()
message << "failed to open video capture device ID #" << device_id;
}
sb::Log::log(message);
+#else
+ emscripten_run_script("open_camera()");
+#endif
+}
+
+void Pudding::close_camera()
+{
+#ifndef __EMSCRIPTEN__
+ capture.release();
+#else
+ emscripten_run_script("close_camera()");
+#endif
}
/* Respond to command events */
@@ -382,9 +409,9 @@ void Pudding::respond(SDL_Event& event)
glm::vec2 mouse_viewport_ndc {
mouse_ndc.x, (1.0f - (float(mouse_pixel.y) - float(viewport_pixel.top())) / viewport_pixel.height()) * 2.0f - 1.0f
};
- bool over_camera_button = !capture.isOpened() && !item_display_active() && camera_button.collide(mouse_ndc),
- over_inventory_button = items.size() > 0 && !item_display_active() && !capture.isOpened() && inventory_button.collide(mouse_ndc),
- over_close_area = (capture.isOpened() || item_display_active()) && get_display().ndc_subsection(main_viewport).collide(mouse_ndc),
+ bool over_camera_button = !camera_switch && !item_display_active() && camera_button.collide(mouse_ndc),
+ over_inventory_button = items.size() > 0 && !item_display_active() && !camera_switch && inventory_button.collide(mouse_ndc),
+ over_close_area = (camera_switch || item_display_active()) && get_display().ndc_subsection(main_viewport).collide(mouse_ndc),
over_previous_button = item_display_active() && previous_button.collide(mouse_viewport_ndc),
over_next_button = item_display_active() && next_button.collide(mouse_viewport_ndc);
/* Check for collisions with anything clickable */
@@ -721,7 +748,7 @@ void Pudding::save_item_json(const nlohmann::json& json, const Item& item, const
nlohmann::json Pudding::json_from_url(const std::string& url, const std::vector& headers)
{
std::vector storage;
- curl_get_bytes(url, storage, headers);
+ web_get_bytes(url, storage, headers);
nlohmann::json json = nlohmann::json::parse(storage);
std::stringstream json_formatted;
json_formatted << std::setw(4) << json << std::endl;
@@ -729,10 +756,35 @@ nlohmann::json Pudding::json_from_url(const std::string& url, const std::vector<
return json;
}
-/* Store the byte buffer from the submitted URL downloaded by cURL into the supplied storage vector
+/*!
+ * Store the bytes retrieved from `url` in the byte vector `storage`.
+ *
+ * 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.
+ *
+ * The optional `headers` parameter will be added to the request when using cURL, but not when using the Emscripten Fetch API.
+ *
+ * @param url URL containing data to be retrieved
+ * @param storage A reference to a vector of bytes which will be filled with the data retrieved from the URL
+ * @param headers A reference to a vector of strings that should be passed as headers with the request. It is only supported by the cURL version.
*/
-void Pudding::curl_get_bytes(const std::string& url, std::vector& storage, const std::vector& headers) const
+void Pudding::web_get_bytes(const std::string& url, std::vector& storage, const std::vector& headers) const
{
+#if defined(__EMSCRIPTEN__)
+
+ /* Create a fetch attributes object. Set a callback that will be called when response data is received. Pass along the user
+ * storage location to be filled by the callback. */
+ emscripten_fetch_attr_t attr;
+ emscripten_fetch_attr_init(&attr);
+ strcpy(attr.requestMethod, "GET");
+ attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
+ attr.onsuccess = fetch_success;
+ attr.onerror = fetch_error;
+ attr.userData = &storage;
+ emscripten_fetch(&attr, url.c_str());
+
+#else
+
CURL *curl;
CURLcode result;
result = curl_global_init(CURL_GLOBAL_DEFAULT);
@@ -747,7 +799,6 @@ void Pudding::curl_get_bytes(const std::string& url, std::vector&
{
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, Pudding::curl_write_response);
- std::vector food_barcode_response;
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &storage);
curl_easy_setopt(curl, CURLOPT_USERAGENT, configuration()["api"]["user-agent"].get().c_str());
struct curl_slist* list = nullptr;
@@ -773,10 +824,47 @@ void Pudding::curl_get_bytes(const std::string& url, std::vector&
curl_easy_cleanup(curl);
}
curl_global_cleanup();
+
+#endif
}
-/* This callback will be called by cURL when it has a response char buffer. The chars will be inserted into the storage
- * vector pointed to by the storage parameter.
+#if defined(__EMSCRIPTEN__)
+
+/*!
+ * This will be called automatically when request data is sucessfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
+ * Response bytes will be inserted into the user supplied `std::vector&` at `fetch->userData`.
+ */
+void Pudding::fetch_success(emscripten_fetch_t* fetch)
+{
+ std::vector* storage = reinterpret_cast*>(fetch->userData);
+ storage->insert(storage->end(), fetch->data, fetch->data + fetch->numBytes);
+ std::stringstream message;
+ message << "Stored " << (fetch->numBytes / 100) << "KB of image data in memory from " << fetch->url;
+ sb::Log::log(message.str());
+ emscripten_fetch_close(fetch);
+}
+
+/*!
+ * This will be called automatically when request data is not successfully fetched by `emscripten_fetch` from `Pudding::web_get_bytes`.
+ */
+void Pudding::fetch_error(emscripten_fetch_t* fetch)
+{
+ std::stringstream message;
+ message << "Downloading image from " << fetch->url << " failed with status code " << fetch->status;
+ sb::Log::log(message.str());
+ emscripten_fetch_close(fetch);
+}
+
+#else
+
+/*!
+ * This will be called by cURL when it has received a buffer of data. The data will be inserted into the vector at `storage`
+ *
+ * @param buffer pointer to data
+ * @param size size in bytes of each value
+ * @param count number of values
+ * @param storage pointer to a vector of unsigned 8-bit values where the data will be copied to
+ * @return number of bytes copied
*/
size_t Pudding::curl_write_response(std::uint8_t* buffer, size_t size, size_t count, std::vector* storage)
{
@@ -785,6 +873,8 @@ size_t Pudding::curl_write_response(std::uint8_t* buffer, size_t size, size_t co
return total_size;
}
+#endif
+
/* Allocate storage for a texture, copy the cURL response data into the storage, and return the ID that corresponds to the GL texture
*/
sb::Texture Pudding::texture_from_image_url(const std::string& url) const
@@ -793,7 +883,7 @@ sb::Texture Pudding::texture_from_image_url(const std::string& url) const
sb::Texture texture;
sb::Log::log("looking up image at " + url);
std::vector storage;
- curl_get_bytes(url, storage);
+ web_get_bytes(url, storage);
if (!storage.empty())
{
sb::Log::log("received image data", sb::Log::DEBUG);
@@ -844,15 +934,23 @@ bool Pudding::item_display_active() const
}
/*!
- * Read pixels from the camera into a `cv::Mat`. This function is meant to be launched in a separate thread,
- * where it will run continuously, setting `new_frame_available` to `false` before loading camera frame data
- * into the `cv::Mat` object at `camera_frame`, then setting `finished_loading_frame` to `true` to indicate
- * new frame data is available in `camera_frame`.
+ * Read pixels from the camera into a `cv::Mat`.
+ *
+ * For a Linux build: This function is meant to be launched in a separate thread, where it will run continuously. Set `new_frame_available`
+ * to `false` before loading camera frame data into the `cv::Mat` object at `camera_frame`, then set it back to `true` to indicate new frame
+ * data is available in `camera_frame`.
+ *
+ * For an Emscripten build: This will load pixel data off the Emscripten heap into a `cv::Mat`. It is intended to be called synchronously in the
+ * main thread.
*/
void Pudding::capture_frame()
{
- /* When capture is closed, this thread will automatically finish execution. */
- while (capture.isOpened())
+
+/* Emscripten builds will call this function from the main thread, so don't run continuously */
+#ifndef __EMSCRIPTEN__
+
+ /* When the camera button is switched off, this thread will automatically finish execution. */
+ while (camera_switch)
{
/* The frame data in the `cv::Mat` at `pudding->camera_frame` is about to be modified by the rest of
* this function, so even if there is data stored there that hasn't been read yet, it should not
@@ -864,24 +962,33 @@ void Pudding::capture_frame()
capture >> camera_frame;
});
+#else
+
+ /* Convert the address of frame RGBA pixel data on the Emscripten heap into an unsigned 8-bit pointer and read the data
+ * into a cv::Mat. */
+ std::uint8_t* emscripten_camera_pixels = reinterpret_cast(emscripten_heap_offset);
+ camera_frame = cv::Mat(320, 240, CV_8UC4, emscripten_camera_pixels);
+
+#endif
+
if (!camera_frame.empty())
{
- /* Rotate the frame 180 degrees to work with OpenGL coords */
- time_it("flip")([&]{
- cv::flip(camera_frame, camera_frame, -1);
- });
-
/* Finished loading into `cv::Mat`, so it is new data that is safe to read. */
new_frame_available = true;
}
- sb::Log::gl_errors("in capture thread, after capturing frame");
+ sb::Log::gl_errors("in capture, after capturing frame");
+
+#ifndef __EMSCRIPTEN__
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
+#endif
+
}
/* Update parameters and draw the screen */
void Pudding::update()
{
+ sb::Log::gl_errors("at beginning of update");
/* Time in seconds the game has running for */
float time_seconds = SDL_GetTicks() / 1000.0f;
@@ -895,13 +1002,21 @@ void Pudding::update()
sb::Log::log(message);
}
- /* If a new frame is finished loading in the detached thread, copy the frame data into a texture, process the frame
- * for scanning and scan it. */
+ /* If new frame data is available, copy it from a cv::Mat into a texture, process for scanning and scan it. */
if (new_frame_available)
{
+
+ sb::Log::log("Hello, World!");
+#ifdef __EMSCRIPTEN__
+ /* Emscripten builds load pixel data into cv::Mat synchronously */
+ capture_frame();
+#endif
+
camera_view.texture().bind();
/* Fill camera view texture memory with last frame's pixels */
- camera_view.texture().load(camera_frame.ptr(), {camera_frame.cols, camera_frame.rows}, GL_BGR, GL_UNSIGNED_BYTE);
+ // camera_view.texture().load(camera_frame.ptr(), {camera_frame.cols, camera_frame.rows}, GL_BGR, GL_UNSIGNED_BYTE);
+ // std::cout << camera_frame.size[0] << " " << camera_frame.size[1] << std::endl;
+ camera_view.texture().load(camera_frame.ptr(), {320, 240}, GL_RGBA, GL_UNSIGNED_BYTE);
/* Frame data has been loaded, so there is not a new frame available anymore. */
new_frame_available = false;
/* Convert to grayscale for ZBar */
@@ -932,13 +1047,14 @@ void Pudding::update()
viewport = window_box(true);
/* shrink viewport if item texture or camera will be displayed */
- if (item_display_active() || capture.isOpened())
+ if (item_display_active() || camera_switch)
{
viewport.drag_bottom(0.5f * configuration()["interface"]["pop-up-viewport-height"].get() * viewport.height());
}
/* Save the main viewport dimensions */
main_viewport = viewport;
+ sb::Log::gl_errors("before viewport");
glViewport(viewport);
glDisable(GL_DEPTH_TEST);
glClearColor(0, 0, 0, 1);
@@ -976,12 +1092,12 @@ void Pudding::update()
pudding_model.attributes("position")->enable();
if (items.size() == 0)
{
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// pudding_model.attributes("color")->enable();
}
else
{
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ // glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
// pudding_model.attributes("color")->enable();
pudding_model.attributes("uv")->enable();
glUniform1i(uniform["mvp"]["pudding texture"], 0);
@@ -1002,10 +1118,10 @@ void Pudding::update()
/* disable squircling for all other drawing */
glUniform1i(uniform["mvp"]["uv transformation"], UV_NONE);
/* regular fill mode enabled for all other drawing */
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ // 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())
+ if (item_display_active() || camera_switch)
{
/* switch to flat shader for item and camera */
glUseProgram(flat_program);
@@ -1023,7 +1139,7 @@ void Pudding::update()
if (item_display_active())
{
/* shrink viewport to half size if camera will also be displayed */
- if (capture.isOpened())
+ if (camera_switch)
{
viewport.left(viewport.cx(), true);
}
@@ -1040,8 +1156,8 @@ void Pudding::update()
previous_button.draw(uniform["flat"]["transformation"]);
}
}
- /* draw the camera if the camera has been opened */
- if (capture.isOpened())
+ /* draw the camera view if the camera button has been switched on */
+ if (camera_switch)
{
viewport.left(window_box(true).left());
glViewport(viewport);
@@ -1169,3 +1285,12 @@ void glViewport(Box box)
{
glViewport(box.left(), box.bottom(), box.width(), box.height());
}
+
+#ifdef __EMSCRIPTEN__
+/* This will bind the global functions to Emscripten so the camera pixel data can be transferred */
+EMSCRIPTEN_BINDINGS(my_module)
+{
+ function("flag_frame", &flag_frame);
+ function("set_heap_offset", &set_heap_offset);
+}
+#endif
diff --git a/src/Pudding.hpp b/src/Pudding.hpp
index 6bf035f..1fb4756 100644
--- a/src/Pudding.hpp
+++ b/src/Pudding.hpp
@@ -13,6 +13,17 @@
/* 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
+#include
+using namespace emscripten;
+#else
+#include
+#include "opencv2/videoio.hpp"
+#include "opencv2/highgui.hpp"
+#endif
+
#include
#include
#include
@@ -23,7 +34,6 @@
#include
#include
#include
-#include
#include "SDL.h"
#include "SDL_image.h"
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
@@ -31,8 +41,6 @@
#include "glm/glm.hpp"
#include "glm/gtx/matrix_decompose.hpp"
#include "opencv2/core.hpp"
-#include "opencv2/videoio.hpp"
-#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include "zbar.h"
#include "Game.hpp"
@@ -297,12 +305,22 @@ public:
};
+/* 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
+
class Pudding : public Game
{
private:
- /* Defines for effect IDs that will be passed to the shader program. Since COUNT is last and every value
+ /* 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
{
@@ -344,7 +362,9 @@ private:
std::vector- items;
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> uniform;
@@ -353,7 +373,7 @@ private:
Model pudding_model;
Plane plane, camera_view;
Background background;
- bool show_item = false, new_frame_available = false;
+ bool show_item = false;
sb::VAO vao;
sb::VBO vbo;
std::map labels;
@@ -365,25 +385,39 @@ private:
void load_gl_context();
void load_tiles();
void load_pads();
- void initialize_camera();
+ void open_camera();
+
+ /*!
+ * Release camera resources.
+ */
+ void close_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& = {});
- void curl_get_bytes(const std::string& url, std::vector&, const std::vector& = {}) const;
- static size_t curl_write_response(std::uint8_t*, size_t, size_t, std::vector*);
+ nlohmann::json json_from_url(const std::string& url, const std::vector& = {});
+ void web_get_bytes(const std::string& url, std::vector& storage, const std::vector& = {}) const;
sb::Texture texture_from_image_url(const std::string&) const;
static void destroy_texture(GLuint*);
bool item_display_active() const;
void capture_frame();
- /* Initialize camera on connection and release on disconnection. */
+ /* Define the appropriate callbacks for URL data loaders. Either cURL by default, or Fetch if compiling for Emscripten. */
+#if defined(__EMSCRIPTEN__)
+ static void fetch_success(emscripten_fetch_t* fetch);
+ static void fetch_error(emscripten_fetch_t* fetch);
+#else
+ static size_t curl_write_response(std::uint8_t*, size_t, size_t, std::vector*);
+#endif
+
+ /* Open camera on connection and close on disconnection. */
Connection<> camera_switch {
- std::bind(&Pudding::initialize_camera, this),
- [&] { capture.release(); }
+ std::bind(&Pudding::open_camera, this),
+ std::bind(&Pudding::close_camera, this)
+ // [&] { capture.release(); }
};
public:
diff --git a/src/flat.frag b/src/flat.frag
deleted file mode 100644
index 2982ec1..0000000
--- a/src/flat.frag
+++ /dev/null
@@ -1,40 +0,0 @@
-/* _______________ ,----------------------------------------------------------------.
- //`````````````\\ \ \
- //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
- //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
- // \\ \ \
- // \\ \ code released under the zlib license [git.nugget.fun/pudding] \
- // ☆ GUNKISS ☆ \\ \ \
-//_________________________\\ `---------------------------------------------------------------*/
-
-#version 130
-
-in vec2 uv;
-uniform sampler2D base_texture;
-uniform vec3 blend_min_hsv;
-uniform float time;
-uniform bool scroll = false;
-
-/* from http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl, licensed under WTFPL */
-vec3 hsv2rgb(vec3 c)
-{
- vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
- vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
- return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
-}
-
-void main(void)
-{
- if (scroll)
- {
- ivec2 texture_size = textureSize(base_texture, 0);
- float speed = time * 35.0;
- gl_FragColor = texelFetch(base_texture, ivec2(mod(vec2(gl_FragCoord.x + speed, gl_FragCoord.y - speed), texture_size)), 0);
- }
- else
- {
- gl_FragColor = texture(base_texture, uv);
- }
- /* apply blending, leaving alpha unchanged */
- gl_FragColor.xyz = min(gl_FragColor.xyz, hsv2rgb(blend_min_hsv));
-}
diff --git a/src/flat.vert b/src/flat.vert
deleted file mode 100644
index 265464e..0000000
--- a/src/flat.vert
+++ /dev/null
@@ -1,21 +0,0 @@
-/* _______________ ,----------------------------------------------------------------.
- //`````````````\\ \ \
- //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
- //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
- // \\ \ \
- // \\ \ code released under the zlib license [git.nugget.fun/pudding] \
- // ☆ GUNKISS ☆ \\ \ \
-//_________________________\\ `---------------------------------------------------------------*/
-
-#version 130
-
-in vec2 in_position;
-in vec2 vertex_uv;
-out vec2 uv;
-uniform mat4 transformation;
-
-void main(void)
-{
- gl_Position = transformation * vec4(in_position, 0, 1);
- uv = vertex_uv;
-}
diff --git a/src/mvp.frag b/src/mvp.frag
deleted file mode 100644
index 3f70823..0000000
--- a/src/mvp.frag
+++ /dev/null
@@ -1,78 +0,0 @@
-/* _______________ +---------------------------------------------------------------------------------------+
- //~~~~~~~~~~~~~\\ | a game by @ohsqueezy & @sleepin |
- //```````````````\\ | [ohsqueezy.itch.io] [instagram.com/sleepin] |
- //_0_0_0_0_0_0_0_0_\\ | |
- //_/_/_/_/___\_\_\_\_\\ | with code licensed for copy, modification and redistribution [git.nugget.fun/pudding] |
- //GGGUUUNNNKKKIIISSSSSS\\ | |
-//_/__/__/__/_\__\__\__\_\\ | 😀 Thank you for choosing Puddendo for your business 😀 |
- +---------------------------------------------------------------------------------------+ */
-#version 130
-
-#define TRANSFORMATION_NONE 0
-#define TRANSFORMATION_SQUIRCLE 1
-
-in vec2 fragment_uv;
-in vec3 ex_color;
-in float x_center_proximity;
-in vec3 original_coordinates;
-in vec3 clip_coordinates;
-uniform sampler2D pudding_texture;
-uniform int uv_transformation = TRANSFORMATION_NONE;
-uniform float coordinate_bound;
-
-/* [-coordinate_bound, coordinate_bound] arbitrary box coordinates to [-1, 1] normalized coordinates */
-vec2 normalize_coordinates(vec2 coordinates)
-{
- return coordinates / coordinate_bound;
-}
-
-/* [-1, 1] box coordinates to [0, 1] UV coordinates */
-vec2 coordinates_to_uv(vec2 coordinates)
-{
- return (coordinates + 1) / 2;
-}
-
-/* coordinates in circle with radius <= 1 to box coordinates in [-1, 1] */
-vec2 circle_to_box(vec2 circle)
-{
- float u = circle.x;
- float v = circle.y;
- float u_sq = pow(u, 2);
- float v_sq = pow(v, 2);
- float rt_2 = sqrt(2);
- float x = .5 * sqrt(2 + 2 * u * rt_2 + u_sq - v_sq) - .5 * sqrt(2 - 2 * u * rt_2 + u_sq - v_sq);
- float y = .5 * sqrt(2 + 2 * v * rt_2 - u_sq + v_sq) - .5 * sqrt(2 - 2 * v * rt_2 - u_sq + v_sq);
- return vec2(x, y);
-}
-
-/* Apply color passed in from the vertex shader, compressing to one of 16 colors. Add retro effect
- * by alternately darkening and lightening 2x2 pixel areas in a checker pattern. Add shadowing by
- * brightening the color based on how near it is to the center in the X-dimension */
-void retro()
-{
- vec3 shadowed = min(ex_color, 1.0);
- float dx = abs(floor(gl_FragCoord[0]) - 480) / 480.0;
- if (int(floor(gl_FragCoord[0] / 2) + floor(gl_FragCoord[1]) / 2) % 2 == 0)
- {
- gl_FragColor = vec4(shadowed * 1.2, 1);
- }
- else
- {
- gl_FragColor = vec4(shadowed * 0.7, 1);
- }
- gl_FragColor[0] = int(gl_FragColor[0] * 4) / 4.0;
- gl_FragColor[1] = int(gl_FragColor[1] * 4) / 4.0;
- gl_FragColor[2] = int(gl_FragColor[2] * 4) / 4.0;
- gl_FragColor *= x_center_proximity;
-}
-
-void main()
-{
- vec2 uv = fragment_uv;
- if (uv_transformation == TRANSFORMATION_SQUIRCLE)
- {
- vec2 normalized_circle_coordinates = normalize_coordinates(vec2(original_coordinates.x, original_coordinates.z));
- uv = coordinates_to_uv(circle_to_box(normalized_circle_coordinates));
- }
- gl_FragColor = texture(pudding_texture, uv);
-}
diff --git a/src/shaders/flat.frag b/src/shaders/flat.frag
new file mode 100644
index 0000000..2dc31bf
--- /dev/null
+++ b/src/shaders/flat.frag
@@ -0,0 +1,44 @@
+#version 300 es
+
+/* _______________ ,--------------------------------------------------------.
+ //`````````````\\ \ \
+ //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+ //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+ // \\ \ \
+ // \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
+ // ☆ GUNKISS ☆ \\ \ \
+ //_________________________\\ `--------------------------------------------------------*/
+
+/* The precision declaration is required by OpenGL ES */
+precision mediump float;
+
+in vec2 uv;
+uniform sampler2D base_texture;
+uniform vec3 blend_min_hsv;
+uniform float time;
+uniform bool scroll;
+out vec4 outputColor;
+
+/* from http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl, licensed under WTFPL */
+vec3 hsv2rgb(vec3 c)
+{
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main(void)
+{
+ if (scroll)
+ {
+ ivec2 texture_size = textureSize(base_texture, 0);
+ float speed = time * 35.0;
+ outputColor = texelFetch(base_texture, ivec2(mod(vec2(gl_FragCoord.x + speed, gl_FragCoord.y - speed), vec2(texture_size))), 0);
+ }
+ else
+ {
+ outputColor = texture(base_texture, uv);
+ }
+ /* apply blending, leaving alpha unchanged */
+ outputColor.xyz = min(outputColor.xyz, hsv2rgb(blend_min_hsv));
+}
diff --git a/src/shaders/flat.vert b/src/shaders/flat.vert
new file mode 100644
index 0000000..cb14e68
--- /dev/null
+++ b/src/shaders/flat.vert
@@ -0,0 +1,24 @@
+#version 300 es
+
+/* _______________ ,--------------------------------------------------------.
+ //`````````````\\ \ \
+ //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+ //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+ // \\ \ \
+ // \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
+ // ☆ GUNKISS ☆ \\ \ \
+ //_________________________\\ `--------------------------------------------------------*/
+
+/* The precision declaration is required by OpenGL ES */
+precision mediump float;
+
+in vec2 in_position;
+in vec2 vertex_uv;
+uniform mat4 transformation;
+out vec2 uv;
+
+void main(void)
+{
+ gl_Position = transformation * vec4(in_position, 0, 1);
+ uv = vertex_uv;
+}
diff --git a/src/shaders/mvp.frag b/src/shaders/mvp.frag
new file mode 100644
index 0000000..3688b39
--- /dev/null
+++ b/src/shaders/mvp.frag
@@ -0,0 +1,83 @@
+#version 300 es
+
+/* _______________ ,--------------------------------------------------------.
+ //`````````````\\ \ \
+ //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+ //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+ // \\ \ \
+ // \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
+ // ☆ GUNKISS ☆ \\ \ \
+ //_________________________\\ `--------------------------------------------------------*/
+
+/* The precision declaration is required by OpenGL ES */
+precision mediump float;
+
+#define TRANSFORMATION_NONE 0
+#define TRANSFORMATION_SQUIRCLE 1
+
+in vec2 fragment_uv;
+in vec3 ex_color;
+in float x_center_proximity;
+in vec3 original_coordinates;
+in vec3 clip_coordinates;
+uniform sampler2D pudding_texture;
+uniform int uv_transformation;
+uniform float coordinate_bound;
+out vec4 output_color;
+
+/* [-coordinate_bound, coordinate_bound] arbitrary box coordinates to [-1, 1] normalized coordinates */
+vec2 normalize_coordinates(vec2 coordinates)
+{
+ return coordinates / coordinate_bound;
+}
+
+/* [-1, 1] box coordinates to [0, 1] UV coordinates */
+vec2 coordinates_to_uv(vec2 coordinates)
+{
+ return (1.0 + coordinates) / 2.0;
+}
+
+/* coordinates in circle with radius <= 1 to box coordinates in [-1, 1] */
+vec2 circle_to_box(vec2 circle)
+{
+ float u = circle.x;
+ float v = circle.y;
+ float u_sq = pow(u, 2.0);
+ float v_sq = pow(v, 2.0);
+ float rt_2 = sqrt(2.0);
+ float x = 0.5 * sqrt(2.0 + 2.0 * u * rt_2 + u_sq - v_sq) - 0.5 * sqrt(2.0 - 2.0 * u * rt_2 + u_sq - v_sq);
+ float y = 0.5 * sqrt(2.0 + 2.0 * v * rt_2 - u_sq + v_sq) - 0.5 * sqrt(2.0 - 2.0 * v * rt_2 - u_sq + v_sq);
+ return vec2(x, y);
+}
+
+/* Apply color passed in from the vertex shader, compressing to one of 16 colors. Add retro effect
+ * by alternately darkening and lightening 2x2 pixel areas in a checker pattern. Add shadowing by
+ * brightening the color based on how near it is to the center in the X-dimension */
+void retro()
+{
+ vec3 shadowed = min(ex_color, 1.0);
+ float dx = abs(floor(gl_FragCoord[0]) - 480.0) / 480.0;
+ if (int(floor(gl_FragCoord[0] / 2.0) + floor(gl_FragCoord[1]) / 2.0) % 2 == 0)
+ {
+ output_color = vec4(shadowed * 1.2, 1);
+ }
+ else
+ {
+ output_color = vec4(shadowed * 0.7, 1);
+ }
+ output_color[0] = float(int(output_color[0] * 4.0)) / 4.0;
+ output_color[1] = float(int(output_color[1] * 4.0)) / 4.0;
+ output_color[2] = float(int(output_color[2] * 4.0)) / 4.0;
+ output_color *= x_center_proximity;
+}
+
+void main()
+{
+ vec2 uv = fragment_uv;
+ if (uv_transformation == TRANSFORMATION_SQUIRCLE)
+ {
+ vec2 normalized_circle_coordinates = normalize_coordinates(vec2(original_coordinates.x, original_coordinates.z));
+ uv = coordinates_to_uv(circle_to_box(normalized_circle_coordinates));
+ }
+ output_color = texture(pudding_texture, uv);
+}
diff --git a/src/mvp.vert b/src/shaders/mvp.vert
similarity index 68%
rename from src/mvp.vert
rename to src/shaders/mvp.vert
index cb3b188..2a4ba46 100644
--- a/src/mvp.vert
+++ b/src/shaders/mvp.vert
@@ -1,13 +1,16 @@
-/* _______________ ,----------------------------------------------------------------.
- //`````````````\\ \ \
- //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
- //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
- // \\ \ \
- // \\ \ code released under the zlib license [git.nugget.fun/pudding] \
- // ☆ GUNKISS ☆ \\ \ \
-//_________________________\\ `---------------------------------------------------------------*/
+#version 300 es
-#version 130
+/* _______________ ,--------------------------------------------------------.
+ //`````````````\\ \ \
+ //~~~~~~~~~~~~~~~\\ \ by @ohsqueezy & @sleepin \
+ //=================\\ \ [ohsqueezy.itch.io] [sleepin.itch.io] \
+ // \\ \ \
+ // \\ \ zlib licensed code at [git.nugget.fun/nugget/gunkiss] \
+ // ☆ GUNKISS ☆ \\ \ \
+ //_________________________\\ `--------------------------------------------------------*/
+
+/* The precision declaration is required by OpenGL ES */
+precision mediump float;
#define PI 3.1415926535897932384626433832795
#define AMPLITUDE 0.2
@@ -22,7 +25,7 @@ in vec3 vertex_color;
in vec2 vertex_uv;
uniform mat4 mvp;
uniform float time;
-uniform int effect = EFFECT_NONE;
+uniform int effect;
out vec3 ex_color;
out float x_center_proximity;
out vec2 fragment_uv;