diff --git a/all/native/graphics/BitmapCanvas.cpp b/all/native/graphics/BitmapCanvas.cpp index 35a083e9a..732e4ede9 100644 --- a/all/native/graphics/BitmapCanvas.cpp +++ b/all/native/graphics/BitmapCanvas.cpp @@ -9,6 +9,9 @@ #elif defined(__ANDROID__) #define CARTO_BITMAP_CANVAS_IMPL AndroidImpl #include "graphics/BitmapCanvasAndroidImpl.h" +#elif defined(__EMSCRIPTEN__) +#define CARTO_BITMAP_CANVAS_IMPL EmscriptenImpl +#include "graphics/BitmapCanvasEmscriptenImpl.h" #else #error "Unsupported platform" #endif diff --git a/all/native/graphics/BitmapCanvas.h b/all/native/graphics/BitmapCanvas.h index 7231fb4e4..28fc58d4f 100644 --- a/all/native/graphics/BitmapCanvas.h +++ b/all/native/graphics/BitmapCanvas.h @@ -71,6 +71,7 @@ namespace carto { class AndroidImpl; class IOSImpl; class UWPImpl; + class EmscriptenImpl; std::unique_ptr _impl; }; diff --git a/all/native/network/HTTPClient.cpp b/all/native/network/HTTPClient.cpp index 63c3be74d..0c2adfd07 100644 --- a/all/native/network/HTTPClient.cpp +++ b/all/native/network/HTTPClient.cpp @@ -19,6 +19,9 @@ #elif defined(__ANDROID__) #define CARTO_HTTP_SOCKET_IMPL AndroidImpl #include "network/HTTPClientAndroidImpl.h" +#elif defined(__EMSCRIPTEN__) +#define CARTO_HTTP_SOCKET_IMPL EmscriptenImpl +#include "network/HTTPClientEmscriptenImpl.h" #else #define CARTO_HTTP_SOCKET_IMPL PionImpl #include "HTTPClientPionImpl.h" diff --git a/all/native/network/HTTPClient.h b/all/native/network/HTTPClient.h index 881e1ca5b..dc754bba2 100644 --- a/all/native/network/HTTPClient.h +++ b/all/native/network/HTTPClient.h @@ -78,6 +78,7 @@ namespace carto { class AndroidImpl; class IOSImpl; class WinSockImpl; + class EmscriptenImpl; int makeRequest(Request request, Response& response, HandlerFunc handlerFn, std::uint64_t offset) const; diff --git a/all/native/utils/Log.cpp b/all/native/utils/Log.cpp index 553e51811..59ca6ba6b 100644 --- a/all/native/utils/Log.cpp +++ b/all/native/utils/Log.cpp @@ -15,6 +15,10 @@ #include #endif +#ifdef __EMSCRIPTEN__ +#include +#endif + namespace carto { #ifdef __ANDROID__ @@ -39,6 +43,13 @@ namespace carto { OutputDebugStringA("\n"); } #endif +#ifdef __EMSCRIPTEN__ + enum LogType { LOG_TYPE_FATAL = EM_LOG_ERROR, LOG_TYPE_ERROR = EM_LOG_ERROR, LOG_TYPE_WARNING = EM_LOG_WARN, LOG_TYPE_INFO = EM_LOG_INFO, LOG_TYPE_DEBUG = EM_LOG_DEBUG }; + + static void OutputLog(LogType logType, const std::string& tag, const char* text) { + emscripten_log(static_cast(logType), text); + } +#endif bool Log::IsShowError() { std::lock_guard lock(_Mutex); diff --git a/all/native/utils/PlatformUtils.h b/all/native/utils/PlatformUtils.h index 205fa5cd8..9da121169 100644 --- a/all/native/utils/PlatformUtils.h +++ b/all/native/utils/PlatformUtils.h @@ -20,7 +20,8 @@ namespace carto { PLATFORM_TYPE_WINDOWS, PLATFORM_TYPE_WINDOWS_PHONE, PLATFORM_TYPE_XAMARIN_IOS, - PLATFORM_TYPE_XAMARIN_ANDROID + PLATFORM_TYPE_XAMARIN_ANDROID, + PLATFORM_TYPE_WEB }; } diff --git a/emscripten/modules/ui/MapView.i b/emscripten/modules/ui/MapView.i new file mode 100644 index 000000000..9d07ab789 --- /dev/null +++ b/emscripten/modules/ui/MapView.i @@ -0,0 +1,29 @@ +#ifndef _MAPVIEW_I +#define _MAPVIEW_I + +%module MAPVIEW + +!proxy_imports(carto::MapView, ui.MapView) + +%{ +#include "ui/MapView.h" +#include "ui/BaseMapView.h" +#include "ui/MapEventListener.h" +#include "ui/MapRedrawRequestListener.h" +#include "ui/EmscriptenInput.h" +#include "components/Options.h" +#include "components/Layers.h" +#include "core/MapBounds.h" +#include "core/MapPos.h" +#include "core/MapVec.h" +#include "core/ScreenPos.h" +#include "core/ScreenBounds.h" +#include "utils/Const.h" +#include "renderers/MapRenderer.h" +%} + +%include + +%include "ui/MapView.h" + +#endif diff --git a/emscripten/modules/utils/AssetUtils.i b/emscripten/modules/utils/AssetUtils.i new file mode 100644 index 000000000..1f14df3e1 --- /dev/null +++ b/emscripten/modules/utils/AssetUtils.i @@ -0,0 +1,18 @@ +#ifndef _ASSETUTILS_I +#define _ASSETUTILS_I + +%module AssetUtils + +!proxy_imports(carto::AssetUtils, core.BinaryData) + +%{ +#include "utils/AssetUtils.h" +%} + +%include + +%import "core/BinaryData.i" + +%include "utils/AssetUtils.h" + +#endif diff --git a/emscripten/modules/utils/BitmapUtils.i b/emscripten/modules/utils/BitmapUtils.i new file mode 100644 index 000000000..4c208621c --- /dev/null +++ b/emscripten/modules/utils/BitmapUtils.i @@ -0,0 +1,24 @@ +#ifndef _BITMAPUTILS_I +#define _BITMAPUTILS_I + +%module BitmapUtils + +!proxy_imports(carto::BitmapUtils, graphics.Bitmap) + +%{ +#include "utils/BitmapUtils.h" +#include "components/Exceptions.h" +%} + +%include +%include +%include + +%import "graphics/Bitmap.i" + +%std_exceptions(carto::BitmapUtils::CreateBitmapFromUIImage) +%std_exceptions(carto::BitmapUtils::CreateUIImageFromBitmap) + +%include "utils/BitmapUtils.h" + +#endif diff --git a/emscripten/native/components/Task.cpp b/emscripten/native/components/Task.cpp new file mode 100644 index 000000000..128ca6303 --- /dev/null +++ b/emscripten/native/components/Task.cpp @@ -0,0 +1,9 @@ +#include "components/Task.h" + +namespace carto { + + void Task::operator()() { + run(); + } + +} diff --git a/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.cpp b/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.cpp new file mode 100644 index 000000000..97b829a4d --- /dev/null +++ b/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.cpp @@ -0,0 +1,583 @@ +#include +#include +#include +#include + +#include +#include +#include + +#include "ui/MapView.h" + +#define protected friend class EmRunOnMainThread; protected + +#include "graphics/BitmapCanvasEmscriptenImpl.h" +#include "components/Exceptions.h" +#include "utils/BitmapUtils.h" +#include "utils/Log.h" + + +typedef union em_variant_val { + int i; + int64_t i64; + float f; + double d; + void *vp; + char *cp; +} em_variant_val; + +#define EM_QUEUED_CALL_MAX_ARGS 11 +typedef struct em_queued_call { + int functionEnum; + void *functionPtr; + _Atomic uint32_t operationDone; + em_variant_val args[EM_QUEUED_JS_CALL_MAX_ARGS]; + em_variant_val returnValue; + void *satelliteData; + int calleeDelete; +} em_queued_call; + +namespace carto { + class EmRunOnMainThread { + public: + static void destructor(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + self->terminate(); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void constructor(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + const emscripten::val document = emscripten::val::global("document"); + emscripten::val canvas = document.call("createElement", std::string("canvas")); + canvas.set("width", q->args[1].i); + canvas.set("height", q->args[2].i); + self->setCanvas(canvas); + self->setContext(canvas.call("getContext", std::string("2d"))); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void pushClipRect(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + const carto::ScreenBounds clipRect = *((const carto::ScreenBounds*) q->args[1].vp); + self->pushClipRect(clipRect); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void popClipRect(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + self->popClipRect(); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void drawText(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + std::string text = *((std::string*) q->args[1].vp); + const carto::ScreenPos pos = *((const carto::ScreenPos*) q->args[2].vp); + int maxWidth = q->args[3].i; + bool breakLines = q->args[4].i == 1; + self->drawText(text, pos, maxWidth, breakLines); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void drawPolygon(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + const std::vector poses = *((const std::vector*) q->args[1].vp); + self->drawPolygon(poses); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void drawRoundRect(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + const carto::ScreenBounds rect = *((const carto::ScreenBounds*) q->args[1].vp); + float radius = q->args[1].f; + self->drawRoundRect(rect, radius); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void drawBitmap(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + const carto::ScreenBounds rect = *((const carto::ScreenBounds*) q->args[1].vp); + const std::shared_ptr bitmap = *((const std::shared_ptr*) q->args[2].vp); + self->drawBitmap(rect, bitmap); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void measureTextSize(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + std::string text = *((std::string*) q->args[1].vp); + int maxWidth = q->args[2].i; + bool breakLines = q->args[3].i == 1; + carto::ScreenBounds bounds = self->measureTextSize(text, maxWidth, breakLines); + q->returnValue.vp = (void*) new carto::ScreenBounds(bounds.getMin(), bounds.getMax()); + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + + static void buildBitmap(void *arg) { + em_queued_call* q = (em_queued_call*)arg; + carto::BitmapCanvas::EmscriptenImpl* self = (carto::BitmapCanvas::EmscriptenImpl*) q->args[0].vp; + std::shared_ptr sharedBitmap = self->buildBitmap(); + carto::Bitmap* ptrBitmap = new Bitmap( + sharedBitmap->getPixelDataPtr(), sharedBitmap->getWidth(), sharedBitmap->getHeight(), + sharedBitmap->getColorFormat(), sharedBitmap->getWidth() * -4 + ); + q->returnValue.vp = (void *) ptrBitmap; + q->operationDone = 1; + emscripten_futex_wake(&q->operationDone, INT_MAX); + } + }; + + BitmapCanvas::EmscriptenImpl::EmscriptenImpl(int width, int height) : + _width(width), + _height(height), + _drawMode(FILL), + _strokeWidth(0), + _color(), + _font(), + _fontSize(0), + _lineHeight(1.3) + { + if (MapView::getRunOnMainThread()) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].i = width; + call.args[2].i = height; + + auto target_thread = emscripten_main_browser_thread_id(); + + if (pthread_equal(target_thread, pthread_self())) { + EmRunOnMainThread::constructor(&call); + } else { + emscripten_proxy_async(emscripten_proxy_get_system_queue(), target_thread, EmRunOnMainThread::constructor, &call); + emscripten_wait_for_call_v(&call, INFINITY); + } + + return; + } + + emscripten::val offscreenClass = emscripten::val::global("OffscreenCanvas"); + this->_canvas = offscreenClass.new_(width, height); + this->_context = this->_canvas.call("getContext", std::string("2d")); + } + + BitmapCanvas::EmscriptenImpl::~EmscriptenImpl() { + if (MapView::getRunOnMainThread()) { + auto target_thread = emscripten_main_browser_thread_id(); + + if (!pthread_equal(target_thread, pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), target_thread, EmRunOnMainThread::destructor, &call); + emscripten_wait_for_call_v(&call, INFINITY); + } + } + } + + void BitmapCanvas::EmscriptenImpl::setDrawMode(DrawMode mode) { + _drawMode = mode; + } + + void BitmapCanvas::EmscriptenImpl::setColor(const Color& color) { + _color = "rgba(" + std::to_string(color.getR()) + "," + std::to_string(color.getG()) + "," + std::to_string(color.getB()) + "," + std::to_string(color.getA()/255) + ")"; + } + + void BitmapCanvas::EmscriptenImpl::setStrokeWidth(float width) { + _strokeWidth = width; + } + + void BitmapCanvas::EmscriptenImpl::setFont(const std::string& name, float size) { + _font = name; + _fontSize = static_cast(size); + } + + void BitmapCanvas::EmscriptenImpl::pushClipRect(const ScreenBounds& clipRect) { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)&clipRect; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::pushClipRect, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + this->_context.call("save"); + + auto rect = emscripten::val::global("Path2D").new_(); + rect.call("rect", clipRect.getMin().getX(), clipRect.getMin().getY(), clipRect.getWidth(), clipRect.getHeight()); + _context.call("clip", rect); + } + + void BitmapCanvas::EmscriptenImpl::popClipRect() { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::popClipRect, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + _context.call("restore"); + } + + void BitmapCanvas::EmscriptenImpl::drawText(std::string text, const ScreenPos& pos, int maxWidth, bool breakLines) { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)&text; + call.args[2].vp = (void *)&pos; + call.args[3].i = maxWidth; + call.args[4].i = breakLines ? 1 : 0; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::drawText, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + if (text.empty()) { + return; + } + + if (maxWidth < 0) { + maxWidth = 65536; + } + + std::vector lines; + std::vector maxWidths; + int maxAscent = 0; + int maxDescent = 0; + int baseHeight = 0; + + measureTextSizeAndEllipsizeText(text, maxWidth, breakLines, lines, maxWidths, maxAscent, maxDescent); + + switch (_drawMode) { + case STROKE: + _context.set("font", std::to_string(_fontSize) + "px " + _font); + _context.set("lineWidth", _strokeWidth); + _context.set("strokeStyle", _color); + + for (int i = 0; i < lines.size(); i += 1) { + _context.call("strokeText", lines[i], pos.getX(), pos.getY() + baseHeight + maxAscent); + baseHeight += maxAscent + maxDescent; + } + break; + case FILL: + _context.set("font", std::to_string(_fontSize) + "px " + _font); + _context.set("fillStyle", _color); + for (int i = 0; i < lines.size(); i += 1) { + _context.call("fillText", lines[i], pos.getX(), pos.getY() + baseHeight + maxAscent); + baseHeight += maxAscent + maxDescent; + } + break; + } + } + + void BitmapCanvas::EmscriptenImpl::drawRoundRect(const ScreenBounds& rect, float radius) { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)▭ + call.args[2].f = radius; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::drawRoundRect, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + float minX = round(rect.getMin().getX()), minY = round(rect.getMin().getY()); + float maxX = round(rect.getMax().getX()), maxY = round(rect.getMax().getY()); + float midX = round((minX + maxX) * 0.5f), midY = round((minY + maxY) * 0.5f); + + minX = std::max(minX, 0.0f); + minY = std::max(minY, 0.0f); + maxX = std::min(maxX, (float)_width - 1); + maxY = std::min(maxY, (float)_height - 1); + + _context.call("moveTo", minX, midY); + _context.call("arcTo", minX, minY, midX, minY, radius); + _context.call("arcTo", maxX, minY, maxX, midY, radius); + _context.call("arcTo", maxX, maxY, midX, maxY, radius); + _context.call("arcTo", minX, maxY, minX, midY, radius); + + _context.call("closePath"); + + switch (_drawMode) { + case STROKE: + _context.set("lineWidth", _strokeWidth); + _context.set("strokeStyle", _color); + _context.call("stroke"); + break; + case FILL: + _context.set("fillStyle", _color); + _context.call("fill"); + break; + } + } + + void BitmapCanvas::EmscriptenImpl::drawPolygon(const std::vector& poses) { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)&poses; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::drawPolygon, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + if (poses.empty()) { + return; + } + + auto path = emscripten::val::global("Path2D").new_(); + path.call("moveTo", poses[0].getX(), poses[0].getY()); + for (size_t i = 1; i < poses.size(); i++) { + path.call("lineTo", poses[i].getX(), poses[i].getY()); + } + path.call("closePath"); + + switch (_drawMode) { + case STROKE: + _context.set("lineWidth", _strokeWidth); + _context.set("strokeStyle", _color); + _context.call("stroke", path); + break; + case FILL: + _context.set("fillStyle", _color); + _context.call("fill", path); + break; + } + } + + void BitmapCanvas::EmscriptenImpl::drawBitmap(const ScreenBounds& rect, const std::shared_ptr& bitmap) { + if (!bitmap) { + return; + } + + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)▭ + call.args[2].vp = (void *)&bitmap; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::drawBitmap, &call); + emscripten_wait_for_call_v(&call, INFINITY); + return; + } + } + + std::shared_ptr rgbaBitmap = bitmap->getRGBABitmap(); + auto jsArray = emscripten::val::array(rgbaBitmap->getPixelData()); + auto jsUint8ClampedArray = emscripten::val::global("Uint8ClampedArray").new_(jsArray); + auto imageData = emscripten::val::global("ImageData").new_(jsUint8ClampedArray, rgbaBitmap->getWidth(), rgbaBitmap->getHeight()); + emscripten::val imgCanvas; + if (!MapView::getRunOnMainThread()) { + imgCanvas = emscripten::val::global("OffscreenCanvas").new_(rgbaBitmap->getWidth(), rgbaBitmap->getHeight()); + } else { + const emscripten::val document = emscripten::val::global("document"); + imgCanvas = document.call("createElement", std::string("canvas")); + imgCanvas.set("width", rgbaBitmap->getWidth()); + imgCanvas.set("height", rgbaBitmap->getHeight()); + } + auto imgContext = imgCanvas.call("getContext", "2d"); + imgContext.call("putImageData", imageData, 0, 0); + + _context.call("save"); + _context.call("scale", 1, -1); + _context.call("drawImage", imgCanvas, rect.getMin().getX(), -rect.getMin().getY() - rect.getHeight(), rect.getWidth(), rect.getHeight()); + _context.call("restore"); + } + + ScreenBounds BitmapCanvas::EmscriptenImpl::measureTextSize(std::string text, int maxWidth, bool breakLines) const { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + call.args[1].vp = (void *)&text; + call.args[2].i = maxWidth; + call.args[3].i = breakLines ? 1 : 0; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::measureTextSize, &call); + emscripten_wait_for_call_v(&call, INFINITY); + std::shared_ptr temp((ScreenBounds*) call.returnValue.vp); + ScreenBounds output(temp->getMin(), temp->getMax()); + return output; + } + } + + std::vector lines; + std::vector maxWidths; + int maxAscent = 0; + int maxDescent = 0; + + return measureTextSizeAndEllipsizeText(text, maxWidth, breakLines, lines, maxWidths, maxAscent, maxDescent); + } + + ScreenBounds BitmapCanvas::EmscriptenImpl::measureTextSizeAndEllipsizeText(std::string &text, int maxWidth, bool breakLines, std::vector &lines, std::vector &maxWidths, int &maxAscent, int &maxDescent) const { + if (text.empty()) { + return ScreenBounds(ScreenPos(0, 0), ScreenPos(0, 0)); + } + + if (maxWidth < 0) { + maxWidth = 65536; + } + + emscripten::val offscreenClass = emscripten::val::global("OffscreenCanvas"); + auto canvas = MapView::getRunOnMainThread() + ? emscripten::val::global("document").call("createElement", std::string("canvas")) + : offscreenClass.new_(0, 0); + auto context = canvas.call("getContext", std::string("2d")); + + context.set("font", std::to_string(_fontSize) + "px " + _font); + if (_drawMode == STROKE) context.set("lineWidth", _strokeWidth); + + std::istringstream ss(text); + std::string word; + std::string line; + std::vector maxAscents; + std::vector maxDescents; + + while (ss >> word) { + if (!lines.empty()) line = lines.back() + " " + word; + else line = word; + + auto metrics = context.call("measureText", line); + auto width = metrics["actualBoundingBoxRight"].as() + metrics["actualBoundingBoxLeft"].as(); + auto ascent = metrics["actualBoundingBoxAscent"].as(); + auto descent = metrics["actualBoundingBoxDescent"].as(); + + if (width <= maxWidth) { + if (!lines.empty()) { + lines[lines.size() - 1] = line; + maxWidths[maxWidths.size() - 1] = std::max(maxWidths.back(), width); + maxAscents[maxAscents.size() - 1] = std::max(maxAscents.back(), ascent); + maxDescents[maxDescents.size() - 1] = std::max(maxDescents.back(), descent); + } + else { + lines.emplace_back(line); + maxWidths.push_back(width); + maxAscents.push_back(ascent); + maxDescents.push_back(descent); + } + } else if (!breakLines) { + for (int i = line.size() - 2; i >= 0; i -= 1) { + auto truncatedLine = line.substr(0, i) + "..."; + auto metrics = context.call("measureText", truncatedLine); + auto width = metrics["actualBoundingBoxRight"].as() + metrics["actualBoundingBoxLeft"].as(); + auto ascent = metrics["actualBoundingBoxAscent"].as(); + auto descent = metrics["actualBoundingBoxDescent"].as(); + if (width <= maxWidth) { + if (!lines.empty()) { + lines[lines.size() - 1] = truncatedLine; + maxWidths[maxWidths.size() - 1] = std::max(maxWidths.back(), width); + maxAscents[maxAscents.size() - 1] = std::max(maxAscents.back(), ascent); + maxDescents[maxDescents.size() - 1] = std::max(maxDescents.back(), descent); + } + else { + lines.emplace_back(truncatedLine); + maxWidths.push_back(width); + maxAscents.push_back(ascent); + maxDescents.push_back(descent); + } + break; + } + } + break; + } else { + lines.emplace_back(word); + + auto metrics = context.call("measureText", word); + auto width = metrics["actualBoundingBoxRight"].as() + metrics["actualBoundingBoxLeft"].as(); + auto ascent = metrics["actualBoundingBoxAscent"].as(); + auto descent = metrics["actualBoundingBoxDescent"].as(); + + maxWidths.push_back(width); + maxAscents.push_back(ascent); + maxDescents.push_back(descent); + } + } + + int lineMaxWidth = 0; + for (const auto &val : maxWidths) { + lineMaxWidth = std::max(lineMaxWidth, val); + } + + maxAscent = 0; + maxDescent = 0; + for (const auto &val : maxAscents) { + maxAscent = std::max(maxAscent, val); + } + for (const auto &val : maxDescents) { + maxDescent = std::max(maxDescent, val); + } + + maxAscent *= _lineHeight; + maxDescent *= _lineHeight; + int lineMaxHeight = maxAscent + maxDescent; + + return ScreenBounds(ScreenPos(0, 0), ScreenPos(lineMaxWidth, lineMaxHeight * lines.size())); + } + + std::shared_ptr BitmapCanvas::EmscriptenImpl::buildBitmap() const { + if (MapView::getRunOnMainThread()) { + if (!pthread_equal(emscripten_main_browser_thread_id(), pthread_self())) { + em_queued_call call = {EM_FUNC_SIG_V}; + call.args[0].vp = (void *)this; + + emscripten_proxy_async(emscripten_proxy_get_system_queue(), emscripten_main_browser_thread_id(), EmRunOnMainThread::buildBitmap, &call); + emscripten_wait_for_call_v(&call, INFINITY); + std::shared_ptr bitmap((Bitmap*) call.returnValue.vp); + return bitmap; + } + } + + emscripten::val imageDataJS = _context.call("getImageData", 0, 0, _width, _height)["data"]; + std::vector imageData = emscripten::convertJSArrayToNumberVector(imageDataJS); + auto bitmap = std::make_shared(&imageData[0], _width, _height, ColorFormat::COLOR_FORMAT_RGBA, (_width * 4)); + return bitmap; + } + + void BitmapCanvas::EmscriptenImpl::setCanvas(emscripten::val canvas) { + _canvas = canvas; + } + + void BitmapCanvas::EmscriptenImpl::setContext(emscripten::val context) { + _context = context; + } + + void BitmapCanvas::EmscriptenImpl::terminate() { + this->_canvas = emscripten::val::undefined(); + this->_context = emscripten::val::undefined(); + } +} diff --git a/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.h b/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.h new file mode 100644 index 000000000..43825a88c --- /dev/null +++ b/emscripten/native/graphics/BitmapCanvasEmscriptenImpl.h @@ -0,0 +1,54 @@ +#ifndef _CARTO_BITMAPCANVASEMSCRIPTENIMPL_H_ +#define _CARTO_BITMAPCANVASEMSCRIPTENIMPL_H_ + +#include "graphics/BitmapCanvas.h" +#include + +namespace carto { + + class BitmapCanvas::EmscriptenImpl : public BitmapCanvas::Impl { + public: + EmscriptenImpl(int width, int height); + virtual ~EmscriptenImpl(); + + virtual void setDrawMode(DrawMode mode); + virtual void setColor(const Color& color); + virtual void setStrokeWidth(float width); + virtual void setFont(const std::string& name, float size); + + virtual void pushClipRect(const ScreenBounds& clipRect); + virtual void popClipRect(); + + virtual void drawText(std::string text, const ScreenPos& pos, int maxWidth, bool breakLines); + virtual void drawPolygon(const std::vector& poses); + virtual void drawRoundRect(const ScreenBounds& rect, float radius); + virtual void drawBitmap(const ScreenBounds& rect, const std::shared_ptr& bitmap); + + virtual ScreenBounds measureTextSize(std::string text, int maxWidth, bool breakLines) const; + + virtual std::shared_ptr buildBitmap() const; + + protected: + void setCanvas(emscripten::val canvas); + void setContext(emscripten::val context); + void terminate(); + + private: + ScreenBounds measureTextSizeAndEllipsizeText(std::string &text, int maxWidth, bool breakLines, std::vector &textLines, std::vector &textMaxWidths, int &maxAscent, int &maxDescent) const; + + emscripten::val _canvas; + emscripten::val _context; + + int _width; + int _height; + DrawMode _drawMode; + float _strokeWidth; + std::string _color; + std::string _font; + int _fontSize; + float _lineHeight; + }; + +} + +#endif diff --git a/emscripten/native/network/HTTPClientEmscriptenImpl.cpp b/emscripten/native/network/HTTPClientEmscriptenImpl.cpp new file mode 100644 index 000000000..2f3f559e9 --- /dev/null +++ b/emscripten/native/network/HTTPClientEmscriptenImpl.cpp @@ -0,0 +1,55 @@ +#include "HTTPClientEmscriptenImpl.h" + +#include +#include +#include +#include +#include + +namespace carto { + HTTPClient::EmscriptenImpl::EmscriptenImpl(bool log) : + _log(log), + _timeout(-1) + { + } + + void HTTPClient::EmscriptenImpl::setTimeout(int milliseconds) { + _timeout = milliseconds; + } + + bool HTTPClient::EmscriptenImpl::makeRequest(const HTTPClient::Request& request, HeadersFunc headersFn, DataFunc dataFn) const { + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, request.method.c_str()); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_SYNCHRONOUS; + attr.timeoutMSecs = _timeout; + + const char* headersArray[request.headers.size() * 2 + 1]; + int i = 0; + for (const auto& [key, value] : request.headers) { + headersArray[i++] = key.c_str(); + headersArray[i++] = value.c_str(); + } + headersArray[i] = NULL; + attr.requestHeaders = headersArray; + + emscripten_fetch_t *fetch = emscripten_fetch(&attr, request.url.c_str()); + + bool cancel = false; + std::map headers; + + if (!headersFn(fetch->status, headers)) { + cancel = true; + } + + if (!cancel) { + if(!dataFn((const unsigned char *)fetch->data, fetch->numBytes)) { + cancel = true; + } + } + + emscripten_fetch_close(fetch); + + return !cancel; + } +} diff --git a/emscripten/native/network/HTTPClientEmscriptenImpl.h b/emscripten/native/network/HTTPClientEmscriptenImpl.h new file mode 100644 index 000000000..9fd92ecfb --- /dev/null +++ b/emscripten/native/network/HTTPClientEmscriptenImpl.h @@ -0,0 +1,21 @@ +#ifndef _CARTO_HTTPCLIENTEMSCRIPTENImpl_H_ +#define _CARTO_HTTPCLIENTEMSCRIPTENImpl_H_ + +#include "network/HTTPClient.h" + +namespace carto { + class HTTPClient::EmscriptenImpl : public HTTPClient::Impl { + public: + explicit EmscriptenImpl(bool log); + + virtual void setTimeout(int milliseconds); + virtual bool makeRequest(const HTTPClient::Request& request, HeadersFunc headersFn, DataFunc dataFn) const; + + private: + bool _log; + int _timeout; + }; + +} + +#endif diff --git a/emscripten/native/ui/EmscriptenInput.cpp b/emscripten/native/ui/EmscriptenInput.cpp new file mode 100644 index 000000000..7b6a96696 --- /dev/null +++ b/emscripten/native/ui/EmscriptenInput.cpp @@ -0,0 +1,195 @@ +#include "EmscriptenInput.h" +#include "ui/MapView.h" +#include + +namespace carto { + namespace EmscriptenInput { + static const int NATIVE_ACTION_POINTER_1_DOWN = 0; + static const int NATIVE_ACTION_POINTER_2_DOWN = 1; + static const int NATIVE_ACTION_MOVE = 2; + static const int NATIVE_ACTION_CANCEL = 3; + static const int NATIVE_ACTION_POINTER_1_UP = 4; + static const int NATIVE_ACTION_POINTER_2_UP = 5; + static const int NATIVE_NO_COORDINATE = -1; + + struct Coord { + long x; + long y; + }; + + std::unordered_map touchList; + + long _pointer1 = -1; + long _pointer2 = -1; + bool isPressed = false; + + long lastX = 0; + long lastY = 0; + + long lastPressedX = 0; + long lastPressedY = 0; + + + EM_BOOL _emscripten_mouse_start(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + MapView* mapView = (MapView*)userData; + if (mouseEvent->buttons == 1) { + mapView->onInputEvent(NATIVE_ACTION_POINTER_1_DOWN, mouseEvent->targetX, mouseEvent->targetY, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + } + isPressed = true; + return true; + } + + EM_BOOL _emscripten_mouse_move(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + MapView* mapView = (MapView*)userData; + lastX = mouseEvent->targetX; + lastY = mouseEvent->targetY; + if (isPressed) { + if (mouseEvent->buttons == 2 || (mouseEvent->buttons == 1 && (mouseEvent->ctrlKey || mouseEvent->shiftKey || mouseEvent->altKey || mouseEvent->metaKey))) { + if (lastPressedX != 0 && lastPressedY != 0) { + long diffX = lastX - lastPressedX; + long diffY = lastY - lastPressedY; + + int x2 = mapView->getCanvasWidth() / 2; + int y2 = mapView->getCanvasHeight() / 2; + + if (lastY > y2) diffX *= -1; + + mapView->rotate(diffX / 1.5, 0); + mapView->tilt(diffY, 0); + } + + lastPressedX = lastX; + lastPressedY = lastY; + } else if (mouseEvent->buttons == 1) { + mapView->onInputEvent(NATIVE_ACTION_MOVE, mouseEvent->targetX, mouseEvent->targetY, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + } + } + + return true; + } + + EM_BOOL _emscripten_mouse_end(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData) { + MapView* mapView = (MapView*)userData; + + isPressed = false; + if (mouseEvent->buttons == 0) { + mapView->onInputEvent(NATIVE_ACTION_POINTER_1_UP, mouseEvent->targetX, mouseEvent->targetY, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + } + + lastPressedX = 0; + lastPressedY = 0; + + return true; + } + + + EM_BOOL _emscripten_mouse_wheel(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData) { + MapView* mapView = (MapView*)userData; + if (wheelEvent->deltaY < -1.0) { + mapView->onWheelEvent(1, lastX, lastY); + } else if (wheelEvent->deltaY > 1.0) { + mapView->onWheelEvent(-1, lastX, lastY); + } + + return true; + } + + + bool isContainEmscriptenEvent(const EmscriptenTouchEvent *touchEvent, long identifier) { + bool output = false; + for (int i = 0; i < touchEvent->numTouches; ++i) { + if (touchEvent->touches[i].identifier == identifier) { + output = true; + break; + } + } + return output; + } + + EM_BOOL _emscripten_touch_start(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { + MapView* mapView = (MapView*)userData; + // if (_pointer1 != -1) touchList.erase (_pointer1); + // if (_pointer1 != -2) touchList.erase (_pointer2); + // _pointer1 = -1; + // _pointer2 = -1; + for (int i = 0; i < touchEvent->numTouches; ++i) { + long pointer = touchEvent->touches[i].identifier; + if (pointer == _pointer1 || pointer == _pointer2) continue; + Coord c; + c.x = touchEvent->touches[i].targetX; + c.y = touchEvent->touches[i].targetY; + + if (_pointer1 == -1) { + _pointer1 = pointer; + touchList[pointer] = c; + + mapView->onInputEvent(NATIVE_ACTION_POINTER_1_DOWN, touchList[_pointer1].x, touchList[_pointer1].y, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + continue; + } + + if (_pointer2 == -1) { + _pointer2 = pointer; + touchList[pointer] = c; + + mapView->onInputEvent(NATIVE_ACTION_POINTER_2_DOWN, touchList[_pointer1].x, touchList[_pointer1].y, touchList[_pointer2].x, touchList[_pointer2].y); + break; + } + } + return true; + } + + EM_BOOL _emscripten_touch_move(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { + MapView* mapView = (MapView*)userData; + for (int i = 0; i < touchEvent->numTouches; ++i) { + auto &coord = touchList[touchEvent->touches[i].identifier]; + coord.x = touchEvent->touches[i].targetX; + coord.y = touchEvent->touches[i].targetY; + } + if (_pointer1 != -1) { + if (_pointer2 != -1) { + mapView->onInputEvent(NATIVE_ACTION_MOVE, touchList[_pointer1].x, touchList[_pointer1].y, touchList[_pointer2].x, touchList[_pointer2].y); + } else { + mapView->onInputEvent(NATIVE_ACTION_MOVE, touchList[_pointer1].x, touchList[_pointer1].y, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + } + } + return true; + } + + EM_BOOL _emscripten_touch_end(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { + MapView* mapView = (MapView*)userData; + if (_pointer2 != -1 && isContainEmscriptenEvent(touchEvent, _pointer2)) { + // Dual pointer, second pointer goes up first + mapView->onInputEvent(NATIVE_ACTION_POINTER_2_UP, touchList[_pointer1].x, touchList[_pointer1].y, touchList[_pointer2].x, touchList[_pointer2].y); + touchList.erase (_pointer2); + _pointer2 = -1; + } + + if (_pointer1 != -1 && isContainEmscriptenEvent(touchEvent, _pointer1)) { + // Single pointer, pointer goes up + if (_pointer2 != -1) { + mapView->onInputEvent(NATIVE_ACTION_POINTER_1_UP, touchList[_pointer1].x, touchList[_pointer1].y, touchList[_pointer2].x, touchList[_pointer2].y); + touchList.erase (_pointer1); + _pointer1 = _pointer2; + _pointer2 = -1; + } else { + mapView->onInputEvent(NATIVE_ACTION_POINTER_1_UP, touchList[_pointer1].x, touchList[_pointer1].y, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + touchList.erase (_pointer1); + _pointer1 = -1; + } + } + + return true; + } + + EM_BOOL _emscripten_touch_cancel(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData) { + MapView* mapView = (MapView*)userData; + mapView->onInputEvent(NATIVE_ACTION_CANCEL, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE, NATIVE_NO_COORDINATE); + touchList.erase (_pointer1); + touchList.erase (_pointer2); + _pointer1 = -1; + _pointer2 = -1; + + return true; + } + } +} diff --git a/emscripten/native/ui/EmscriptenInput.h b/emscripten/native/ui/EmscriptenInput.h new file mode 100644 index 000000000..f32124c66 --- /dev/null +++ b/emscripten/native/ui/EmscriptenInput.h @@ -0,0 +1,22 @@ +#ifndef _CARTO_EMSCRIPTENINPUT_H_ +#define _CARTO_EMSCRIPTENINPUT_H_ + +#include +#include + +namespace carto { + namespace EmscriptenInput { + EM_BOOL _emscripten_mouse_doubleClick(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + EM_BOOL _emscripten_mouse_start(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + EM_BOOL _emscripten_mouse_move(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + EM_BOOL _emscripten_mouse_end(int eventType, const EmscriptenMouseEvent *mouseEvent, void *userData); + EM_BOOL _emscripten_mouse_wheel(int eventType, const EmscriptenWheelEvent *wheelEvent, void *userData); + + EM_BOOL _emscripten_touch_start(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); + EM_BOOL _emscripten_touch_move(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); + EM_BOOL _emscripten_touch_end(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); + EM_BOOL _emscripten_touch_cancel(int eventType, const EmscriptenTouchEvent *touchEvent, void *userData); + } +} + +#endif diff --git a/emscripten/native/ui/MapLicenseManagerListener.cpp b/emscripten/native/ui/MapLicenseManagerListener.cpp new file mode 100644 index 000000000..bfd557ce0 --- /dev/null +++ b/emscripten/native/ui/MapLicenseManagerListener.cpp @@ -0,0 +1,5 @@ +#include "MapLicenseManagerListener.h" + +namespace carto { + void MapLicenseManagerListener::onLicenseUpdated(const std::string& licenseKey) {} +} diff --git a/emscripten/native/ui/MapLicenseManagerListener.h b/emscripten/native/ui/MapLicenseManagerListener.h new file mode 100644 index 000000000..7228f3e51 --- /dev/null +++ b/emscripten/native/ui/MapLicenseManagerListener.h @@ -0,0 +1,14 @@ +#ifndef _CARTO_EMSCRIPTENMAPLICENSEMANAGERLISTENER_H_ +#define _CARTO_EMSCRIPTENMAPLICENSEMANAGERLISTENER_H_ + +#include +#include "components/LicenseManagerListener.h" + +namespace carto { + class MapLicenseManagerListener : public LicenseManagerListener { + public: + void onLicenseUpdated(const std::string& licenseKey); + }; +} + +#endif diff --git a/emscripten/native/ui/MapRedrawRequestListener.cpp b/emscripten/native/ui/MapRedrawRequestListener.cpp new file mode 100644 index 000000000..3d90b7882 --- /dev/null +++ b/emscripten/native/ui/MapRedrawRequestListener.cpp @@ -0,0 +1,12 @@ +#include "ui/MapRedrawRequestListener.h" +#include "ui/MapView.h" + +namespace carto { + MapRedrawRequestListener::MapRedrawRequestListener(MapView* mapView) { + this->_mapView = mapView; + } + + void MapRedrawRequestListener::onRedrawRequested() const { + _mapView->requestRender(); + } +} \ No newline at end of file diff --git a/emscripten/native/ui/MapRedrawRequestListener.h b/emscripten/native/ui/MapRedrawRequestListener.h new file mode 100644 index 000000000..fc7c1cb47 --- /dev/null +++ b/emscripten/native/ui/MapRedrawRequestListener.h @@ -0,0 +1,19 @@ +#ifndef _CARTO_EMSCRIPTENMAPREDRAWREQUESTLISTENER_H_ +#define _CARTO_EMSCRIPTENMAPREDRAWREQUESTLISTENER_H_ + +#include "renderers/RedrawRequestListener.h" + +namespace carto { + class MapView; + + class MapRedrawRequestListener : public RedrawRequestListener { + public: + MapRedrawRequestListener(MapView* mapView); + void onRedrawRequested() const; + + private: + MapView* _mapView; + }; +} + +#endif diff --git a/emscripten/native/ui/MapView.cpp b/emscripten/native/ui/MapView.cpp new file mode 100644 index 000000000..db9793b87 --- /dev/null +++ b/emscripten/native/ui/MapView.cpp @@ -0,0 +1,294 @@ +#include "ui/MapView.h" +#include "ui/BaseMapView.h" +#include "ui/MapEventListener.h" +#include "ui/MapLicenseManagerListener.h" +#include "ui/MapRedrawRequestListener.h" +#include "ui/EmscriptenInput.h" +#include "components/Options.h" +#include "components/Layers.h" +#include "core/MapBounds.h" +#include "core/MapPos.h" +#include "core/MapVec.h" +#include "core/ScreenPos.h" +#include "core/ScreenBounds.h" +#include "utils/Const.h" +#include "utils/Log.h" +#include "renderers/MapRenderer.h" + +#include +#include +#include + +#include + +namespace carto { + std::vector cartoEmscriptenMapViews; + bool cartoEmscriptenMainLoopStarted = false; + bool waitResizeCount = 0; + bool _runOnMainThread = true; + + void _emscripten_main_loop() { + for (MapView* mapView : cartoEmscriptenMapViews) { + if (mapView->isRenderPaused()) continue; + if (mapView->needRedraw()) { + mapView->onDrawFrame(); + } + } + if (waitResizeCount != 0) waitResizeCount -= 1; + } + + EM_BOOL _emscripten_resize_callback(int eventType, const EmscriptenUiEvent *uiEvent, void *userData) { + for (MapView* mapView : cartoEmscriptenMapViews) { + mapView->onSurfaceChanged(); + } + + return true; + } + + void *_thread_on_surface_created(void *arg) { + bool runOnMainThread = MapView::getRunOnMainThread(); + MapView* mapView = (MapView*)arg; + EmscriptenWebGLContextAttributes attr; + emscripten_webgl_init_context_attributes(&attr); + attr.explicitSwapControl = runOnMainThread ? 0 : 1; + // attr.renderViaOffscreenBackBuffer = 1; + // attr.proxyContextToMainThread = EMSCRIPTEN_WEBGL_CONTEXT_PROXY_FALLBACK; + attr.alpha = 1; + attr.depth = 1; + attr.stencil = mapView->getStencil() ? 1 : 0; + attr.antialias = 1; + attr.preserveDrawingBuffer = 0; + attr.failIfMajorPerformanceCaveat = 0; + attr.premultipliedAlpha = 1; + attr.enableExtensionsByDefault = 1; + attr.majorVersion = 2; + attr.minorVersion = 0; + EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context(mapView->getCanvasId().c_str(), &attr); + emscripten_webgl_make_context_current(ctx); + + mapView->getBaseMapView()->onSurfaceCreated(); + mapView->onSurfaceChanged(); + + if (!cartoEmscriptenMainLoopStarted) { + cartoEmscriptenMainLoopStarted = true; + emscripten_set_main_loop(_emscripten_main_loop, runOnMainThread ? 0 : 60, runOnMainThread ? 0 : 1); + } + + return 0; + } + + MapView::MapView(std::string canvasId, bool runOnMainThread, bool stencil) { + this->setRunOnMainThread(runOnMainThread); + _stencil = stencil; + _canvasId = canvasId; + _baseMapView = std::make_shared(); + _scale = emscripten_get_device_pixel_ratio(); + + _baseMapView->getOptions()->setDPI(Const::UNSCALED_DPI * _scale); + + _redrawRequestListener = std::make_shared(this); + _baseMapView->setRedrawRequestListener(_redrawRequestListener); + } + + MapView::~MapView() { + cartoEmscriptenMapViews.erase( + std::remove(cartoEmscriptenMapViews.begin(), cartoEmscriptenMapViews.end(), this), + cartoEmscriptenMapViews.end() + ); + } + + bool MapView::registerLicense(std::string licenseKey) { + auto listener = std::make_shared(); + return BaseMapView::RegisterLicense(licenseKey, listener); + } + + void MapView::onSurfaceCreated() { + emscripten_set_touchstart_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_touch_start); + emscripten_set_touchmove_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_touch_move); + emscripten_set_touchend_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_touch_end); + emscripten_set_touchcancel_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_touch_cancel); + + emscripten_set_mousedown_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_mouse_start); + emscripten_set_mousemove_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_mouse_move); + emscripten_set_mouseup_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_mouse_end); + emscripten_set_wheel_callback(_canvasId.c_str(), this, true, &EmscriptenInput::_emscripten_mouse_wheel); + + if (!std::count(cartoEmscriptenMapViews.begin(), cartoEmscriptenMapViews.end(), this)) { + cartoEmscriptenMapViews.emplace_back(this); + } + + emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, true, _emscripten_resize_callback); + + if (!MapView::getRunOnMainThread()) { + pthread_attr_t attr; + pthread_attr_init(&attr); + + emscripten_pthread_attr_settransferredcanvases(&attr, _canvasId.substr(1).c_str()); + + pthread_t thread; + pthread_create(&thread, &attr, _thread_on_surface_created, this); + } else { + _thread_on_surface_created(this); + } + } + void MapView::onSurfaceChanged() { + double canvasWidthDouble, canvasHeightDouble; + emscripten_get_element_css_size(_canvasId.c_str(), &canvasWidthDouble, &canvasHeightDouble); + int canvasWidthInt = (int)round(canvasWidthDouble); + int canvasHeightInt = (int)round(canvasHeightDouble); + if (_canvasWidth != canvasWidthInt || _canvasHeight != canvasHeightInt) { + _canvasWidth = canvasWidthInt; + _canvasHeight = canvasHeightInt; + emscripten_set_canvas_element_size(_canvasId.c_str(), _canvasWidth, _canvasHeight); + _baseMapView->onSurfaceChanged(_canvasWidth, _canvasHeight); + _needRedraw = true; + } + } + void MapView::onDrawFrame() { + _baseMapView->onDrawFrame(); + // emscripten_webgl_commit_frame(); + } + void MapView::requestRender() { + _needRedraw = true; + } + void MapView::start() { + this->onSurfaceCreated(); + } + + bool MapView::isRenderPaused() { + return this->isPaused; + } + + void MapView::setRenderPaused(bool isPaused) { + this->isPaused = isPaused; + } + + bool MapView::getStencil() { + return _stencil; + } + + void MapView::onInputEvent(int event, float x1, float y1, float x2, float y2) { + _baseMapView->onInputEvent(event, x1, y1, x2, y2); + } + void MapView::onWheelEvent(int delta, float x, float y) { + _baseMapView->onWheelEvent(delta, x, y); + } + + float MapView::getScale() { + return _scale; + } + + int MapView::getCanvasWidth() { + return _canvasWidth; + } + int MapView::getCanvasHeight() { + return _canvasHeight; + } + + std::string MapView::getCanvasId() { + return _canvasId; + } + + bool MapView::getRunOnMainThread() { + return _runOnMainThread; + } + + void MapView::setRunOnMainThread(bool status) { + _runOnMainThread = status || !emscripten_supports_offscreencanvas(); + Log::Infof("runOnMainThread: %s", _runOnMainThread ? "true" : "false"); + } + + bool MapView::needRedraw() { + return std::atomic_exchange(&_needRedraw, false); + } + + std::shared_ptr MapView::getBaseMapView() { + return _baseMapView; + } + + const std::shared_ptr& MapView::getLayers() const { + return _baseMapView->getLayers(); + } + const std::shared_ptr& MapView::getOptions() const { + return _baseMapView->getOptions(); + } + const std::shared_ptr& MapView::getMapRenderer() const { + return _baseMapView->getMapRenderer(); + } + MapPos MapView::getFocusPos() const { + return _baseMapView->getFocusPos(); + } + float MapView::getRotation() const { + return _baseMapView->getRotation(); + } + float MapView::getTilt() const { + return _baseMapView->getTilt(); + } + float MapView::getZoom() const { + return _baseMapView->getZoom(); + } + void MapView::pan(MapVec deltaPos, float durationSeconds) { + _baseMapView->pan(deltaPos, durationSeconds); + } + void MapView::setFocusPos(MapPos pos, float durationSeconds) { + _baseMapView->setFocusPos(pos, durationSeconds); + } + void MapView::rotate(float deltaAngle, float durationSeconds) { + _baseMapView->rotate(deltaAngle, durationSeconds); + } + void MapView::rotate(float deltaAngle, MapPos targetPos, float durationSeconds) { + _baseMapView->rotate(deltaAngle, targetPos, durationSeconds); + } + void MapView::setRotation(float angle, float durationSeconds) { + _baseMapView->setRotation(angle, durationSeconds); + } + void MapView::setRotation(float angle, MapPos targetPos, float durationSeconds) { + _baseMapView->setRotation(angle, targetPos, durationSeconds); + } + void MapView::tilt(float deltaTilt, float durationSeconds) { + _baseMapView->tilt(deltaTilt, durationSeconds); + } + void MapView::setTilt(float tilt, float durationSeconds) { + _baseMapView->setTilt(tilt, durationSeconds); + } + void MapView::zoom(float deltaZoom, float durationSeconds) { + _baseMapView->zoom(deltaZoom, durationSeconds); + } + void MapView::zoom(float deltaZoom, MapPos targetPos, float durationSeconds) { + _baseMapView->zoom(deltaZoom, targetPos, durationSeconds); + } + void MapView::setZoom(float zoom, float durationSeconds) { + _baseMapView->setZoom(zoom, durationSeconds); + } + void MapView::setZoom(float zoom, MapPos targetPos, float durationSeconds) { + _baseMapView->setZoom(zoom, targetPos, durationSeconds); + } + void MapView::moveToFitBounds(MapBounds mapBounds, ScreenBounds screenBounds, bool integerZoom, float durationSeconds) { + _baseMapView->moveToFitBounds(mapBounds, screenBounds, integerZoom, durationSeconds); + } + void MapView::moveToFitBounds(MapBounds mapBounds, ScreenBounds screenBounds, bool integerZoom, bool resetRotation, + bool resetTilt, float durationSeconds) { + _baseMapView->moveToFitBounds(mapBounds, screenBounds, integerZoom, resetRotation, resetTilt, durationSeconds); + } + std::shared_ptr MapView::getMapEventListener() const { + return _baseMapView->getMapEventListener(); + } + void MapView::setMapEventListener(const std::shared_ptr& mapEventListener) { + _baseMapView->setMapEventListener(mapEventListener); + } + MapPos MapView::screenToMap(ScreenPos screenPos) { + return _baseMapView->screenToMap(screenPos); + } + ScreenPos MapView::mapToScreen(MapPos mapPos) { + return _baseMapView->mapToScreen(mapPos); + } + void MapView::cancelAllTasks() { + _baseMapView->cancelAllTasks(); + } + void MapView::clearPreloadingCaches() { + _baseMapView->clearPreloadingCaches(); + } + void MapView::clearAllCaches() { + _baseMapView->clearAllCaches(); + } +} diff --git a/emscripten/native/ui/MapView.h b/emscripten/native/ui/MapView.h new file mode 100644 index 000000000..5a733d871 --- /dev/null +++ b/emscripten/native/ui/MapView.h @@ -0,0 +1,95 @@ +#ifndef _CARTO_EMSCRIPTENMAPVIEW_H_ +#define _CARTO_EMSCRIPTENMAPVIEW_H_ + +#include +#include + +namespace carto { + class BaseMapView; + class RedrawRequestListener; + class Layers; + class MapBounds; + class MapPos; + class MapVec; + class ScreenPos; + class ScreenBounds; + class MapEventListener; + class MapRenderer; + class Options; + + class MapView { + public: + MapView(std::string canvasId, bool runOnMainThread = false, bool stencil = true); + ~MapView(); + + static bool registerLicense(std::string licenseKey); + + void onSurfaceCreated(); + void onSurfaceChanged(); + void onDrawFrame(); + void requestRender(); + void start(); + bool isRenderPaused(); + void setRenderPaused(bool isPaused); + bool getStencil(); + + void onInputEvent(int event, float x1, float y1, float x2, float y2); + void onWheelEvent(int delta, float x, float y); + float getScale(); + int getCanvasWidth(); + int getCanvasHeight(); + std::string getCanvasId(); + bool needRedraw(); + std::shared_ptr getBaseMapView(); + + const std::shared_ptr& getLayers() const; + const std::shared_ptr& getOptions() const; + const std::shared_ptr& getMapRenderer() const; + MapPos getFocusPos() const; + float getRotation() const; + float getTilt() const; + float getZoom() const; + void pan(MapVec deltaPos, float durationSeconds); + void setFocusPos(MapPos pos, float durationSeconds); + void rotate(float deltaAngle, float durationSeconds); + void rotate(float deltaAngle, MapPos targetPos, float durationSeconds); + void setRotation(float angle, float durationSeconds); + void setRotation(float angle, MapPos targetPos, float durationSeconds); + void tilt(float deltaTilt, float durationSeconds); + void setTilt(float tilt, float durationSeconds); + void zoom(float deltaZoom, float durationSeconds); + void zoom(float deltaZoom, MapPos targetPos, float durationSeconds); + void setZoom(float zoom, float durationSeconds); + void setZoom(float zoom, MapPos targetPos, float durationSeconds); + void moveToFitBounds(MapBounds mapBounds, ScreenBounds screenBounds, bool integerZoom, float durationSeconds); + void moveToFitBounds(MapBounds mapBounds, ScreenBounds screenBounds, bool integerZoom, bool resetRotation, + bool resetTilt, float durationSeconds); + std::shared_ptr getMapEventListener() const; + void setMapEventListener(const std::shared_ptr& mapEventListener); + MapPos screenToMap(ScreenPos screenPos); + ScreenPos mapToScreen(MapPos mapPos); + void cancelAllTasks(); + void clearPreloadingCaches(); + void clearAllCaches(); + + static bool getRunOnMainThread(); + + private: + static void setRunOnMainThread(bool status); + + std::shared_ptr _baseMapView; + std::shared_ptr _redrawRequestListener; + + std::string _canvasId; + float _scale = 1; + std::atomic _needRedraw = false; + + int _canvasWidth = 0; + int _canvasHeight = 0; + bool _stencil = true; + + bool isPaused = false; + }; +} + +#endif \ No newline at end of file diff --git a/emscripten/native/utils/AssetUtils.cpp b/emscripten/native/utils/AssetUtils.cpp new file mode 100644 index 000000000..052e4e7f5 --- /dev/null +++ b/emscripten/native/utils/AssetUtils.cpp @@ -0,0 +1,18 @@ +#include "AssetUtils.h" +#include "core/BinaryData.h" +#include +#include +#include + +namespace carto { + std::shared_ptr AssetUtils::LoadAsset(const std::string& path) { + std::ifstream input( "/assets/"+path, std::ios::binary ); + std::vector buffer(std::istreambuf_iterator(input), {}); + return std::make_shared(std::move(buffer)); + } + + AssetUtils::AssetUtils() { + } + + std::mutex AssetUtils::_Mutex; +} diff --git a/emscripten/native/utils/AssetUtils.h b/emscripten/native/utils/AssetUtils.h new file mode 100644 index 000000000..ae03f4932 --- /dev/null +++ b/emscripten/native/utils/AssetUtils.h @@ -0,0 +1,33 @@ +#ifndef _CARTO_ASSETUTILS_H_ +#define _CARTO_ASSETUTILS_H_ + +#include +#include +#include +#include + + +namespace carto { + class BinaryData; + + /** + * A helper class for managing application-bundled assets. + */ + class AssetUtils { + public: + + /** + * Loads the specified bundled asset. + * @param path The path of the asset to load. The path is relative to application root folder. + * @return The loaded asset as a byte vector or null if the asset was not found or could not be loaded. + */ + static std::shared_ptr LoadAsset(const std::string& path); + + private: + AssetUtils(); + static std::mutex _Mutex; + }; + +} + +#endif diff --git a/emscripten/native/utils/BitmapUtils.cpp b/emscripten/native/utils/BitmapUtils.cpp new file mode 100644 index 000000000..73a4692e4 --- /dev/null +++ b/emscripten/native/utils/BitmapUtils.cpp @@ -0,0 +1,43 @@ +#include "BitmapUtils.h" +#include "core/BinaryData.h" +#include "components/Exceptions.h" +#include "graphics/Bitmap.h" +#include "utils/AssetUtils.h" +#include "utils/Log.h" + +#include + +namespace carto { + + std::shared_ptr BitmapUtils::LoadBitmapFromAssets(const std::string& assetPath) { + std::shared_ptr data = AssetUtils::LoadAsset(assetPath); + if (!data) { + return std::shared_ptr(); + } + return Bitmap::CreateFromCompressed(data); + } + + std::shared_ptr BitmapUtils::LoadBitmapFromFile(const std::string& filePath) { + FILE* fpRaw = utf8_filesystem::fopen(filePath.c_str(), "rb"); + if (!fpRaw) { + Log::Errorf("BitmapUtils::LoadBitmapFromFile: Failed to load: %s", filePath.c_str()); + return std::shared_ptr(); + } + std::shared_ptr fp(fpRaw, fclose); + fseek(fp.get(), 0, SEEK_END); + long size = ftell(fp.get()); + fseek(fp.get(), 0, SEEK_SET); + if (size <= 0) { + Log::Errorf("BitmapUtils::LoadBitmapFromFile: Ignore load due to size %d: %s", size, filePath.c_str()); + return std::shared_ptr(); + } + std::vector data(static_cast(size)); + fread(data.data(), 1, size, fp.get()); + return Bitmap::CreateFromCompressed(data.data(), data.size()); + } + + + BitmapUtils::BitmapUtils() { + } + +} diff --git a/emscripten/native/utils/BitmapUtils.h b/emscripten/native/utils/BitmapUtils.h new file mode 100644 index 000000000..d352a6b60 --- /dev/null +++ b/emscripten/native/utils/BitmapUtils.h @@ -0,0 +1,35 @@ +#ifndef _CARTO_BITMAPUTILS_H_ +#define _CARTO_BITMAPUTILS_H_ + +#include +#include + +namespace carto { + class Bitmap; + + /** + * A helper class for loading bitmaps and converting Bitmaps to Android Bitmaps an vice versa. + */ + class BitmapUtils { + public: + /** + * Loads the specified bitmap asset bundled with the application. + * @param assetPath The asset path to the image to be loaded. + * @return The loaded bitmap. + */ + static std::shared_ptr LoadBitmapFromAssets(const std::string& assetPath); + + /** + * Loads bitmap from specified file. + * @param filePath The path to the image to be loaded. + * @return The loaded bitmap. + */ + static std::shared_ptr LoadBitmapFromFile(const std::string& filePath); + + protected: + BitmapUtils(); + }; + +} + +#endif diff --git a/emscripten/native/utils/PlatformUtils.cpp b/emscripten/native/utils/PlatformUtils.cpp new file mode 100644 index 000000000..180f0e20b --- /dev/null +++ b/emscripten/native/utils/PlatformUtils.cpp @@ -0,0 +1,36 @@ +#include "utils/PlatformUtils.h" + +namespace carto { + + PlatformType::PlatformType PlatformUtils::GetPlatformType() { + return PlatformType::PLATFORM_TYPE_WEB; + } + + std::string PlatformUtils::GetDeviceId() { + return "deviceId"; + } + + std::string PlatformUtils::GetDeviceType() { + return "web"; + } + + std::string PlatformUtils::GetDeviceOS() { + return "web"; + } + + std::string PlatformUtils::GetAppIdentifier() { + return "web"; + } + + std::string PlatformUtils::GetAppDeviceId() { + return "web:web"; + } + + bool PlatformUtils::ExcludeFolderFromBackup(const std::string &folder) { + return true; + } + + PlatformUtils::PlatformUtils() { + } + +} diff --git a/emscripten/native/utils/ThreadUtils.cpp b/emscripten/native/utils/ThreadUtils.cpp new file mode 100644 index 000000000..afbb91967 --- /dev/null +++ b/emscripten/native/utils/ThreadUtils.cpp @@ -0,0 +1,17 @@ +#include "utils/ThreadUtils.h" +#include "components/ThreadWorker.h" + +#include +#include + +#include + +namespace carto { + + void ThreadUtils::SetThreadPriority(ThreadPriority::ThreadPriority priority) { + } + + ThreadUtils::ThreadUtils() { + } + +} diff --git a/scripts/build-emscripten.py b/scripts/build-emscripten.py new file mode 100755 index 000000000..47528fe95 --- /dev/null +++ b/scripts/build-emscripten.py @@ -0,0 +1,72 @@ +import os +import sys +import shutil +import argparse +import string +from build.sdk_build_utils import * + +def build(args): + baseDir = getBaseDir() + buildDir = getBuildDir('emscripten') + distDir = getDistDir('emscripten') + defines = ["-D%s" % define for define in args.defines.split(';') if define] + defines.sort() + options = ["-D%s" % option for option in args.cmakeoptions.split(';') if option] + options.sort() + + if not cmake(args, buildDir, ["cmake"] + options + [ + "-DCMAKE_BUILD_TYPE=%s" % args.configuration, + #"-DWRAPPER_DIR=%s" % ('%s/generated/android-java/wrappers' % baseDir), + "-DSINGLE_LIBRARY:BOOL=ON", + "-DSDK_CPP_DEFINES=%s" % " ".join(defines), + "-DSDK_PLATFORM='Web'", + '%s/scripts/build' % baseDir + ]): + return False + if not execute(args.make, buildDir, "make", "-j4"): + return False + + return makedirs('%s' % (distDir)) and execute(args.cc, buildDir, + "-lembind", "-Wl,--whole-archive", + "%s/libcarto_mobile_sdk.a" % buildDir, "-o", "%s/CartoMobileSDK.js" % distDir, + "-O3", "-s", "ERROR_ON_UNDEFINED_SYMBOLS=0", "-s", "FORCE_FILESYSTEM=1", "-s", "FULL_ES2=1", + "-s", "FETCH=1", "-lworkerfs.js", "-s", "TOTAL_MEMORY=512MB", "-s", "DISABLE_EXCEPTION_CATCHING=0", + "-s", "WASM=1", "-s", "MODULARIZE=1", "-s", "EXPORT_NAME=\"CModule\"", + "-s", "RESERVED_FUNCTION_POINTERS=20", "-s", "USE_PTHREADS", "-s", "PTHREAD_POOL_SIZE=2", + "-s", "OFFSCREENCANVAS_SUPPORT", + # "-s", "OFFSCREEN_FRAMEBUFFER", + "-s", "EXPORTED_RUNTIME_METHODS=['FS']" + ) + +parser = argparse.ArgumentParser() +parser.add_argument('--profile', dest='profile', default=getDefaultProfileId(), type=validProfile, help='Build profile') +parser.add_argument('--defines', dest='defines', default='', help='Defines for compilation') +parser.add_argument('--make', dest='make', default='emmake', help='emmake executable') +parser.add_argument('--cmake', dest='cmake', default='emcmake', help='emcmake executable') +parser.add_argument('--cc', dest='cc', default='emcc', help='emcc executable') +parser.add_argument('--cmake-options', dest='cmakeoptions', default='', help='CMake options') +parser.add_argument('--configuration', dest='configuration', default='Release', choices=['Release', 'RelWithDebInfo', 'Debug'], help='Configuration') + +args = parser.parse_args() +args.defines += ';' + getProfile(args.profile).get('defines', '') +args.cmakeoptions += ';' + getProfile(args.profile).get('cmake-options', '') + +#if not os.path.exists("%s/generated/emscripten/proxies" % getBaseDir()): +# print("Proxies/wrappers not generated yet, run swigpp script first.") +# sys.exit(-1) + +if not checkExecutable(args.cmake, 'cmake', '--help'): + print(args.cmake) + print('Failed to find emcmake executable. Use --cmake to specify its location') + sys.exit(-1) + +if not checkExecutable(args.make, 'make', '--help'): + print('Failed to find emmake executable. Use --make to specify its location') + sys.exit(-1) + +if not checkExecutable(args.cc, '--help'): + print('Failed to find emcc executable. Use --cc to specify its location') + sys.exit(-1) + +if not build(args): + sys.exit(-1) \ No newline at end of file diff --git a/scripts/build/CMakeLists.txt b/scripts/build/CMakeLists.txt index 553cee2a4..54dfa928b 100644 --- a/scripts/build/CMakeLists.txt +++ b/scripts/build/CMakeLists.txt @@ -92,6 +92,13 @@ if(ANDROID) set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} -s -fuse-ld=lld -Wl,-plugin-opt=O3 -Wl,--gc-sections -Wl,-icf=all -Wl,-threads=4 -Wl,--as-needed -Wl,--build-id -Wl,--version-script=${PROJECT_SOURCE_DIR}/../android/version-script") endif(ANDROID) +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -O3 -fexceptions -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -ftemplate-depth=1024 -fexceptions -frtti -fvisibility=hidden -fvisibility-inlines-hidden") + set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3 -fexceptions -pthread -flto=full -fomit-frame-pointer") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -flto=full -fomit-frame-pointer") +endif() + # Make common libraries available to all subprojects include_directories("${SDK_EXTERNAL_LIBS_DIR}/boost") @@ -108,6 +115,10 @@ if(WIN32) include_directories("${SDK_EXTERNAL_LIBS_DIR}/angle-uwp/include" "${SDK_EXTERNAL_LIBS_DIR}/zlib/config" "${SDK_EXTERNAL_LIBS_DIR}/zlib/zlib") endif(WIN32) +if(EMSCRIPTEN) + include_directories("${SDK_EXTERNAL_LIBS_DIR}/zlib/config" "${SDK_EXTERNAL_LIBS_DIR}/zlib/zlib") +endif(EMSCRIPTEN) + # Subprojects set(SDK_CARTO_SUBPROJECTS vt mapnikvt mbvtbuilder cartocss nml) set(SDK_EXTERNAL_SUBPROJECTS botan pugixml bidi freetype harfbuzz brotli miniz libjpeg libpng libwebp tess2 pvrt rg_etc1) @@ -116,7 +127,11 @@ if(WIN32) list(APPEND SDK_EXTERNAL_SUBPROJECTS "zlib") endif(WIN32) -if(NOT (WIN32 OR APPLE OR ANDROID)) +if(EMSCRIPTEN) + list(APPEND SDK_EXTERNAL_SUBPROJECTS "zlib") +endif(EMSCRIPTEN) + +if(NOT (WIN32 OR APPLE OR ANDROID OR EMSCRIPTEN)) list(APPEND SDK_EXTERNAL_SUBPROJECTS "pion") endif() @@ -180,7 +195,7 @@ file(GLOB SDK_SRC_FILES ) set_source_files_properties("${SDK_SRC_DIR}/utils/PlatformUtils.cpp" PROPERTIES COMPILE_OPTIONS "-D_CARTO_MOBILE_SDK_PLATFORM=\"${SDK_PLATFORM}\";-D_CARTO_MOBILE_SDK_VERSION=\"${SDK_VERSION}\"") -if(WIN32 OR APPLE OR ANDROID) +if(WIN32 OR APPLE OR ANDROID OR EMSCRIPTEN) list(REMOVE_ITEM SDK_SRC_FILES "${SDK_SRC_DIR}/network/HTTPClientPionImpl.cpp") endif() @@ -227,6 +242,19 @@ elseif(WIN32) foreach(WINRT_SRC_FILE IN ITEMS "utils/AssetUtils.cpp" "utils/PlatformUtils.cpp" "utils/EGLContextWrapper.cpp" "components/Task.cpp") set_source_files_properties("${SDK_BASE_DIR}/winphone/native/${WINRT_SRC_FILE}" PROPERTIES COMPILE_OPTIONS "/ZW") endforeach() +elseif(EMSCRIPTEN) + file(GLOB SDK_EMSCRIPTEN_SRC_FILES + "${WRAPPER_DIR}/*.cpp" + "${WRAPPER_DIR}/*.h" + "${SDK_BASE_DIR}/emscripten/native/*.cpp" + "${SDK_BASE_DIR}/emscripten/native/*.h" + "${SDK_BASE_DIR}/emscripten/native/*/*.cpp" + "${SDK_BASE_DIR}/emscripten/native/*/*.h" + "${SDK_BASE_DIR}/emscripten/native/*/*/*.cpp" + "${SDK_BASE_DIR}/emscripten/native/*/*/*.h" + "${SDK_BASE_DIR}/generated/emscripten-js/wrappers/*.cpp" + ) + set(SDK_SRC_FILES "${SDK_SRC_FILES}" "${SDK_EMSCRIPTEN_SRC_FILES}") endif() # Group wrapper files into different groups, to reduce clutter @@ -258,6 +286,8 @@ elseif(APPLE) endif(INCLUDE_OBJC) elseif(WIN32) include_directories("${SDK_BASE_DIR}/winphone/native") +elseif(EMSCRIPTEN) + include_directories("${SDK_BASE_DIR}/emscripten/native") endif() # Linking diff --git a/scripts/emscripten-dev/assets/arrow@1.png b/scripts/emscripten-dev/assets/arrow@1.png new file mode 100644 index 000000000..0b4db1726 Binary files /dev/null and b/scripts/emscripten-dev/assets/arrow@1.png differ diff --git a/scripts/emscripten-dev/assets/arrow@2.png b/scripts/emscripten-dev/assets/arrow@2.png new file mode 100644 index 000000000..69d00c24c Binary files /dev/null and b/scripts/emscripten-dev/assets/arrow@2.png differ diff --git a/scripts/emscripten-dev/assets/arrow@3.png b/scripts/emscripten-dev/assets/arrow@3.png new file mode 100644 index 000000000..2595df660 Binary files /dev/null and b/scripts/emscripten-dev/assets/arrow@3.png differ diff --git a/scripts/emscripten-dev/assets/arrow@4.png b/scripts/emscripten-dev/assets/arrow@4.png new file mode 100644 index 000000000..66661b49f Binary files /dev/null and b/scripts/emscripten-dev/assets/arrow@4.png differ diff --git a/scripts/emscripten-dev/assets/info@1.png b/scripts/emscripten-dev/assets/info@1.png new file mode 100644 index 000000000..ac5a440a1 Binary files /dev/null and b/scripts/emscripten-dev/assets/info@1.png differ diff --git a/scripts/emscripten-dev/assets/info@2.png b/scripts/emscripten-dev/assets/info@2.png new file mode 100644 index 000000000..8324c252e Binary files /dev/null and b/scripts/emscripten-dev/assets/info@2.png differ diff --git a/scripts/emscripten-dev/assets/info@3.png b/scripts/emscripten-dev/assets/info@3.png new file mode 100644 index 000000000..bad66858d Binary files /dev/null and b/scripts/emscripten-dev/assets/info@3.png differ diff --git a/scripts/emscripten-dev/assets/info@4.png b/scripts/emscripten-dev/assets/info@4.png new file mode 100644 index 000000000..8db368135 Binary files /dev/null and b/scripts/emscripten-dev/assets/info@4.png differ diff --git a/scripts/emscripten-dev/assets/norwegian_737_800.nml b/scripts/emscripten-dev/assets/norwegian_737_800.nml new file mode 100644 index 000000000..b1a14596e Binary files /dev/null and b/scripts/emscripten-dev/assets/norwegian_737_800.nml differ diff --git a/scripts/emscripten-dev/assets/plane.wav b/scripts/emscripten-dev/assets/plane.wav new file mode 100644 index 000000000..0d4925039 Binary files /dev/null and b/scripts/emscripten-dev/assets/plane.wav differ diff --git a/scripts/emscripten-dev/assets/sample.geojson b/scripts/emscripten-dev/assets/sample.geojson new file mode 100644 index 000000000..148625e5e --- /dev/null +++ b/scripts/emscripten-dev/assets/sample.geojson @@ -0,0 +1,653 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 4" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.007753, + 40.716774 + ], + [ + -74.006068, + 40.716774 + ], + [ + -74.006068, + 40.717619 + ], + [ + -74.006733, + 40.717619 + ], + [ + -74.006722, + 40.717523 + ], + [ + -74.006197, + 40.717522 + ], + [ + -74.006191, + 40.716887 + ], + [ + -74.007618, + 40.716879 + ], + [ + -74.007608, + 40.717526 + ], + [ + -74.007098, + 40.717525 + ], + [ + -74.007109, + 40.717619 + ], + [ + -74.007753, + 40.717619 + ], + [ + -74.007753, + 40.716774 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 5" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.009539, + 40.716774 + ], + [ + -74.007753, + 40.716774 + ], + [ + -74.007753, + 40.717619 + ], + [ + -74.008461, + 40.717619 + ], + [ + -74.008455, + 40.717496 + ], + [ + -74.007913, + 40.717493 + ], + [ + -74.007913, + 40.716891 + ], + [ + -74.009373, + 40.716891 + ], + [ + -74.009373, + 40.717501 + ], + [ + -74.008938, + 40.717499 + ], + [ + -74.008938, + 40.717619 + ], + [ + -74.009539, + 40.717619 + ], + [ + -74.009539, + 40.716774 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 6" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.010703, + 40.716774 + ], + [ + -74.009539, + 40.716774 + ], + [ + -74.009539, + 40.71801 + ], + [ + -74.009649, + 40.71801 + ], + [ + -74.009652, + 40.716859 + ], + [ + -74.010585, + 40.716855 + ], + [ + -74.01059, + 40.718904 + ], + [ + -74.009646, + 40.718908 + ], + [ + -74.009648, + 40.718392 + ], + [ + -74.009539, + 40.718392 + ], + [ + -74.009539, + 40.718985 + ], + [ + -74.010703, + 40.718985 + ], + [ + -74.010703, + 40.716774 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 1" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.010703, + 40.718985 + ], + [ + -74.009099, + 40.718985 + ], + [ + -74.009099, + 40.719075 + ], + [ + -74.01058, + 40.719071 + ], + [ + -74.010574, + 40.719742 + ], + [ + -74.006191, + 40.71975 + ], + [ + -74.006197, + 40.719083 + ], + [ + -74.007061, + 40.719081 + ], + [ + -74.00705, + 40.718985 + ], + [ + -74.006068, + 40.718985 + ], + [ + -74.006068, + 40.719835 + ], + [ + -74.010703, + 40.719835 + ], + [ + -74.010703, + 40.718985 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 1" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.008724, + 40.719076 + ], + [ + -74.008713, + 40.718985 + ], + [ + -74.007474, + 40.718985 + ], + [ + -74.007474, + 40.719079 + ], + [ + -74.008724, + 40.719076 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 3" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.006068, + 40.716774 + ], + [ + -74.004754, + 40.716774 + ], + [ + -74.004754, + 40.718197 + ], + [ + -74.006068, + 40.718197 + ], + [ + -74.006068, + 40.718107 + ], + [ + -74.005961, + 40.718107 + ], + [ + -74.004866, + 40.718107 + ], + [ + -74.004866, + 40.716859 + ], + [ + -74.005961, + 40.716859 + ], + [ + -74.005961, + 40.717871 + ], + [ + -74.006068, + 40.717871 + ], + [ + -74.006068, + 40.716774 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "wall" }, + "properties": { "name": "Room 2" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.006068, + 40.718567 + ], + [ + -74.004754, + 40.718567 + ], + [ + -74.004754, + 40.719835 + ], + [ + -74.006068, + 40.719835 + ], + [ + -74.006068, + 40.718908 + ], + [ + -74.005938, + 40.718916 + ], + [ + -74.005934, + 40.71975 + ], + [ + -74.004861, + 40.719746 + ], + [ + -74.004861, + 40.718656 + ], + [ + -74.005939, + 40.718648 + ], + [ + -74.006068, + 40.718652 + ], + [ + -74.006068, + 40.718567 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 5" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.009373, + 40.717501 + ], + [ + -74.009373, + 40.716891 + ], + [ + -74.007913, + 40.716891 + ], + [ + -74.007913, + 40.717493 + ], + [ + -74.008455, + 40.717496 + ], + [ + -74.008938, + 40.717499 + ], + [ + -74.009373, + 40.717501 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 6" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.009646, + 40.718908 + ], + [ + -74.01059, + 40.718904 + ], + [ + -74.010585, + 40.716855 + ], + [ + -74.009652, + 40.716859 + ], + [ + -74.009649, + 40.71801 + ], + [ + -74.009648, + 40.718392 + ], + [ + -74.009646, + 40.718908 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 1" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.01058, + 40.719071 + ], + [ + -74.009099, + 40.719075 + ], + [ + -74.008724, + 40.719076 + ], + [ + -74.007474, + 40.719079 + ], + [ + -74.007061, + 40.719081 + ], + [ + -74.006197, + 40.719083 + ], + [ + -74.006191, + 40.71975 + ], + [ + -74.010574, + 40.719742 + ], + [ + -74.01058, + 40.719071 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 4" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.007608, + 40.717526 + ], + [ + -74.007618, + 40.716879 + ], + [ + -74.006191, + 40.716887 + ], + [ + -74.006197, + 40.717522 + ], + [ + -74.006722, + 40.717523 + ], + [ + -74.007098, + 40.717525 + ], + [ + -74.007608, + 40.717526 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 3" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.005961, + 40.717871 + ], + [ + -74.005961, + 40.716859 + ], + [ + -74.004866, + 40.716859 + ], + [ + -74.004866, + 40.718107 + ], + [ + -74.005961, + 40.718107 + ], + [ + -74.005961, + 40.717871 + ] + ] + ] + } + }, + { + "type": "Feature", + "tippecanoe": { "layer": "floor" }, + "properties": { "name": "Room 2" }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [ + -74.005938, + 40.718916 + ], + [ + -74.005939, + 40.718648 + ], + [ + -74.004861, + 40.718656 + ], + [ + -74.004861, + 40.719746 + ], + [ + -74.005934, + 40.71975 + ], + [ + -74.005938, + 40.718916 + ] + ] + ] + } + } + ] + } \ No newline at end of file diff --git a/scripts/emscripten-dev/assets/sample.mbtiles b/scripts/emscripten-dev/assets/sample.mbtiles new file mode 100644 index 000000000..92a5ae28c Binary files /dev/null and b/scripts/emscripten-dev/assets/sample.mbtiles differ diff --git a/scripts/emscripten-dev/assets/style.zip b/scripts/emscripten-dev/assets/style.zip new file mode 100644 index 000000000..0ec522960 Binary files /dev/null and b/scripts/emscripten-dev/assets/style.zip differ diff --git a/scripts/emscripten-dev/index.html b/scripts/emscripten-dev/index.html new file mode 100644 index 000000000..3ca9d8e41 --- /dev/null +++ b/scripts/emscripten-dev/index.html @@ -0,0 +1,80 @@ + + + + + Mobile SDK Web View + + + + + + + + + + diff --git a/scripts/emscripten-dev/main.js b/scripts/emscripten-dev/main.js new file mode 100644 index 000000000..2cbfe8980 --- /dev/null +++ b/scripts/emscripten-dev/main.js @@ -0,0 +1,214 @@ +const CartoMobileSDK = { + onRuntimeInitialized: function onRuntimeInitialized() { + CartoMobileSDK.FS.mkdir('assets'); + startMap(); + }, +}; +CModule(CartoMobileSDK); + +const imgScale = window.devicePixelRatio > 4 ? 4 : window.devicePixelRatio; + +function startMap() { + CartoMobileSDK.Log.SetShowInfo(false); + const stencil = !(navigator.vendor === 'Google Inc.' && navigator.userAgentData.mobile); + const runOnMainThread = false; + mapView = new CartoMobileSDK.MapView('#canvas', runOnMainThread, stencil); + const options = mapView.getOptions(); + options.setTiltRange(new CartoMobileSDK.MapRange(40, 90)); + options.setZoomGestures(true); + // options.setRenderProjectionMode(CartoMobileSDK.RenderProjectionMode.SPHERICAL); + // setMapListener(mapView); + mapView.start(); + + addRasterLayer(mapView); + addMBTiles(mapView); + + const pos = new CartoMobileSDK.MapPos(-8238512, 4970883); + mapView.setZoom(18.0 - window.devicePixelRatio, pos, 2.0); + mapView.setTilt(60, 2.0); + + localDataSource = createVectorLayer(mapView, new CartoMobileSDK.EPSG3857()); + addMarker(localDataSource); + addLine(localDataSource); + addText(localDataSource); + addPopup(localDataSource); + setTimeout(() => addPlane(localDataSource), 2500); +} + +function addRasterLayer(mapView) { + const mapUrl = 'https://{s}.basemaps.cartocdn.com/light_all/{zoom}/{x}/{y}@1x.png'; + const httpDataSource = new CartoMobileSDK.HTTPTileDataSource(0.0, 24.0, mapUrl); + // console.log(httpDataSource, httpDataSource.getBaseURL(), httpDataSource.getMinZoom(), httpDataSource.getMaxZoom(), httpDataSource.getSubdomains()); + + const cacheDataSource = new CartoMobileSDK.MemoryCacheTileDataSource(httpDataSource); + const rasterTileLayer = new CartoMobileSDK.RasterTileLayer(cacheDataSource); + mapView.getLayers().add(rasterTileLayer); +} + +function addMBTiles(mapView) { + const fetches = [ + fetch('assets/sample.mbtiles').then(response => response.arrayBuffer()), + fetch('assets/style.zip').then(response => response.arrayBuffer()) + ]; + Promise.all(fetches).then(([mbTilesData, styleData]) => { + CartoMobileSDK.FS.writeFile('/assets/sample.mbtiles', new Int8Array(mbTilesData)); + CartoMobileSDK.FS.writeFile('/assets/style.zip', new Int8Array(styleData)); + }).then(() => { + const mbDataSource = new CartoMobileSDK.MBTilesTileDataSource(0, 24, "/assets/sample.mbtiles"); + const styleAsset = CartoMobileSDK.AssetUtils.LoadAsset("style.zip"); + // console.log('styleAsset.size(): ', styleAsset.size()); + const assetPackage = new CartoMobileSDK.ZippedAssetPackage(styleAsset); + const styleset = new CartoMobileSDK.CompiledStyleSet(assetPackage, "style"); + const tileDecoder = new CartoMobileSDK.MBVectorTileDecoder(styleset); + const layer = new CartoMobileSDK.VectorTileLayer(mbDataSource, tileDecoder); + layer.setLabelRenderOrder(CartoMobileSDK.VectorTileRenderOrder.VECTOR_TILE_RENDER_ORDER_LAST); + setVectorTileListener(layer, tileDecoder); + + mapView.getLayers().add(layer); + }); +} + +function createVectorLayer(mapView, proj = new CartoMobileSDK.EPSG3857()) { + const localDataSource = new CartoMobileSDK.LocalVectorDataSource(proj); + const vectorLayer = new CartoMobileSDK.VectorLayer(localDataSource); + vectorLayer.setOpacity(1); + mapView.getLayers().add(vectorLayer); + return localDataSource; +} + +function addMarker(localDataSource) { + const pos = new CartoMobileSDK.MapPos(-8237821, 4970805); + + const markerStyleBuilder = new CartoMobileSDK.MarkerStyleBuilder(); + markerStyleBuilder.setSize(20.0); + + const marker = new CartoMobileSDK.Marker(pos, markerStyleBuilder.buildStyle()); + localDataSource.add(marker); +} + +function addLine(localDataSource) { + const poses = new CartoMobileSDK.MapPosVector; + poses.push_back(new CartoMobileSDK.MapPos(-8237821, 4970805)); + poses.push_back(new CartoMobileSDK.MapPos(-8238832, 4970287)); + + const lineStyleBuilder = new CartoMobileSDK.LineStyleBuilder(); + const color = new CartoMobileSDK.Color(255, 0, 0, 125); + lineStyleBuilder.setColor(color); + + const line = new CartoMobileSDK.Line(poses, lineStyleBuilder.buildStyle()); + localDataSource.add(line); +} + +function addText(localDataSource) { + const builder = new CartoMobileSDK.TextStyleBuilder(); + builder.setOrientationMode(CartoMobileSDK.BillboardOrientation.BILLBOARD_ORIENTATION_FACE_CAMERA_GROUND); + + const text = new CartoMobileSDK.Text(new CartoMobileSDK.MapPos(-8238832, 4970287), builder.buildStyle(), "Face camera ground text"); + localDataSource.add(text); +} + +function addPopup(localDataSource) { + const fetches = [ + fetch(`assets/info@${imgScale}.png`).then(response => response.arrayBuffer()), + fetch(`assets/arrow@${imgScale}.png`).then(response => response.arrayBuffer()) + ]; + Promise.all(fetches).then(([infoPNG, arrowPNG]) => { + CartoMobileSDK.FS.writeFile(`/assets/info@${imgScale}.png`, new Int8Array(infoPNG)); + CartoMobileSDK.FS.writeFile(`/assets/arrow@${imgScale}.png`, new Int8Array(arrowPNG)); + }).then(() => { + const infoPNG = CartoMobileSDK.BitmapUtils.LoadBitmapFromAssets(`info@${imgScale}.png`); + const arrowPNG = CartoMobileSDK.BitmapUtils.LoadBitmapFromAssets(`arrow@${imgScale}.png`); + + const builder = new CartoMobileSDK.BalloonPopupStyleBuilder(); + builder.setDescriptionWrap(false); + builder.setPlacementPriority(1); + builder.setLeftImage(infoPNG); + builder.setRightImage(arrowPNG); + builder.setRightMargins(new CartoMobileSDK.BalloonPopupMargins(2, 6, 12, 6)); + // builder.setTitleColor(new CartoMobileSDK.Color(255, 0, 0, 255)); + // builder.setTitleFontSize(24); + // builder.setDescriptionFontSize(24); + + const popup = new CartoMobileSDK.BalloonPopup(new CartoMobileSDK.MapPos(-8237821, 4970287), builder.buildStyle(), "This title will be wrapped if there's not enough space on the screen.", "ActivityData is set to be truncated with three dots, unless the screen is really really big."); + localDataSource.add(popup); + + }); +} + +function setMapListener(mapView) { + const Listener = CartoMobileSDK.MapEventListener.extend("MapEventListener", { + onMapIdle: function() { + console.log('onMapIdle'); + }, + onMapMoved: function() { + console.log('onMapMoved'); + }, + onMapStable: function() { + console.log('onMapStable'); + }, + onMapInteraction: function(mapInteractionInfo) { + console.log('onMapInteraction', mapInteractionInfo.isTiltAction()); + }, + onMapClicked: function(mapClickInfo) { + console.log('onMapClicked'); + }, + }); + const listener = new Listener; + mapView.setMapEventListener(listener); +} + +function setVectorTileListener(vectorTileLayer, tileDecoder) { + const Listener = CartoMobileSDK.VectorTileEventListener.extend("VectorTileEventListener", { + onVectorTileClicked: function(c) { + const name = c.getFeature().getProperties().getObjectElement("name").getString(); + tileDecoder.setStyleParameter("selectedRoomName", name); + console.log('onVectorTileClicked', name, c.getClickPos().getX()); + }, + }); + const listener = new Listener; + vectorTileLayer.setVectorTileEventListener(listener); +} + +function addPlane(localDataSource) { + const modelPath = 'norwegian_737_800.nml'; + fetch('assets/' + modelPath).then(response => response.arrayBuffer()).then(data => { + CartoMobileSDK.FS.writeFile('/assets/'+modelPath, new Int8Array(data)); + }).then(() => { + const modelStyleBuilder = new CartoMobileSDK.NMLModelStyleBuilder(); + modelStyleBuilder.setModelAsset(CartoMobileSDK.AssetUtils.LoadAsset(modelPath)); + + const pos = new CartoMobileSDK.MapPos(3649495, 4852002, 100); + const model = new CartoMobileSDK.NMLModel(pos, modelStyleBuilder.buildStyle()); + model.setScale(10); + localDataSource.add(model); + setTimeout(() => animate(model, -8237016, 4969476, 100, -8239763, 4971937, 6000), 2000); + var audio = new Audio('assets/plane.wav'); + setTimeout(() => audio.play(), 4000); + }) +} + +function animate(model, posX, posY, posZ, pos2X, pos2Y, timeInMs) { + let id; + let startTime = 0; + let func; + func = (timestamp) => { + if (!startTime) { + startTime = timestamp; + requestAnimationFrame(func); + return; + } + let totalTime = timestamp - startTime; + if (totalTime > timeInMs) totalTime = timeInMs; + + const x = (pos2X - posX) * (totalTime / timeInMs) + posX; + const y = (pos2Y - posY) * (totalTime / timeInMs) + posY; + + model.setPos(new CartoMobileSDK.MapPos(x, y, posZ)); + + if (totalTime < timeInMs) { + requestAnimationFrame(func); + } + }; + + requestAnimationFrame(func); +} \ No newline at end of file diff --git a/scripts/emscripten-dev/server/server-ip b/scripts/emscripten-dev/server/server-ip new file mode 100755 index 000000000..f391dfb3d --- /dev/null +++ b/scripts/emscripten-dev/server/server-ip @@ -0,0 +1,26 @@ +#!/usr/bin/env python +import ssl +from http import server + +class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_my_headers() + + server.SimpleHTTPRequestHandler.end_headers(self) + + def send_my_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + + def do_GET(self): + # cut off a query string + if '/dist/' not in self.path: + self.path = "scripts/emscripten-dev" + self.path + server.SimpleHTTPRequestHandler.do_GET(self) + + +if __name__ == '__main__': + httpd = server.HTTPServer(('0.0.0.0', 4443), MyHTTPRequestHandler) + httpd.socket = ssl.wrap_socket (httpd.socket, certfile='./scripts/emscripten-dev/server/server.pem', server_side=True) + httpd.serve_forever() diff --git a/scripts/emscripten-dev/server/server-localhost b/scripts/emscripten-dev/server/server-localhost new file mode 100755 index 000000000..d075a6a81 --- /dev/null +++ b/scripts/emscripten-dev/server/server-localhost @@ -0,0 +1,26 @@ +#!/usr/bin/env python +try: + from http import server # Python 3 +except ImportError: + import SimpleHTTPServer as server # Python 2 + +class MyHTTPRequestHandler(server.SimpleHTTPRequestHandler): + def end_headers(self): + self.send_my_headers() + + server.SimpleHTTPRequestHandler.end_headers(self) + + def send_my_headers(self): + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + + def do_GET(self): + # cut off a query string + if '/dist/' not in self.path: + self.path = "scripts/emscripten-dev" + self.path + server.SimpleHTTPRequestHandler.do_GET(self) + + +if __name__ == '__main__': + server.test(HandlerClass=MyHTTPRequestHandler) \ No newline at end of file diff --git a/scripts/emscripten-dev/server/server.pem b/scripts/emscripten-dev/server/server.pem new file mode 100644 index 000000000..2e26f0893 --- /dev/null +++ b/scripts/emscripten-dev/server/server.pem @@ -0,0 +1,49 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQCwDoLsIedf5GLe +A+k+8hKIgg1KTn0gctpUZDz/qav4hlvqZ80x15eTCyC4LXQinfD6wV4WAwePmFj4 +8R0tBlLEvr4q0ycnIziByk882a9+VRi+iLcu7MAF6xBE57r6x/+PiEN9cPrGg4yK +HYVEnbKoS3d4s/Qzo0Mn+i4nwEg3Ygpn6XD5fJ3kT2EgVbH4d5CvensWHe6izA0z +4GAlz2zuzbr59YILvwaWxcRRcljGQoNs72x4KPXko13JZDYJz7HSTx/0jcaj6Pcq +d2g9NgxRK/bWDOryuuUIL9/RvajRq1OLqycRev+41xmWV65/hcs+4f16u+Q4mDwt +s3BYoYv3AgMBAAECggEBAIpNbAXJgHQw+oXD9sgO7AY2e/BChkMmVmLzN4PzqveZ +vL0AfA5KeXT2yeZFNrU286ZYnuuVx6vDFynAZ5YGIKET4lnL9DbnkS1MW0DHFPie +KskKpzSCoTpE9Uvb6mO1FwqPFc2GIkot7wpUtGs5oq5YUmsMmqqx90CBZR6vC0wN +N1adR4615gQQYS+jTqXmCOcjo8EN537Afyhk28xaEptI01O8LBZQJdX/FvYHYvLF +WZNbO8MZ0+epdkyNetjkvWoglAShRcLleM7M0CyVOwHS5pJylIqsh+bocjaFHEA5 +OrtLZhCg5N1ELt4Qj7Rg28HKEpfQpfUBX4QbocMnHNkCgYEA4Rc0VTrgJrDd20b+ +1OseEs3lskedzTfyKry9sV1j8xbwBsq/oGgpuHIkCzpz9MDD3frwZEwkYXlyp5jB +CIK10fnne7UakKl8RuH6PITn5XozRhYMWxZAda75/EDLGycz/PHVIgfrTMRJeuO7 +mn1xvrpeKmytQDIHH/RVCGy7lVsCgYEAyDuTf5AZ2ptP9kwfVYAXiYp1xDGUXvQ3 +hcNop6+h6k4JJAS6ULZuz/Cf42uVnCfIoCk1oSolMcgOHjrXlMj+ccjKi1CLSnG6 +nUbf8KJxr/YnkHoF8n6qmxbqSorMwii4jZRUrsn30Os6wJWLDvJHIDr9/0p8qeoy +blPvqehnOpUCgYEArQza9h+yjuxxVJELgARW9ZwIWP0IS+sUKlk6+W5UVmKvHHmp +w5fxY1WVcnB5D5VXwISZZnLHkYz+Ye1lkrZzNMCNgCUnDErROemoKfkAFAPUAbEX +xLuhyRhCJvZlcDW+mVwPQP+XnkhWOb110PtahIjhtnog5HbR1XZSHY0C1nsCgYEA +x0CBxq7fC2sCZ1XY2Hxe77+Y8WNlAhiEJ9gMjNF5jm+ZKP6LaUYhf0sz0Ft+xaXR +P4/Cjl4iJSeu5OYpISXiDdiBtaCw+6Ow0FF3Bz6fYzrQpDTJNkZV/38OJp94or1i +6YBxxH223W83R+2IM5dkr+4EU8cLxwoGpUNhevKJxwECgYEA19OW57sBJqPUN0zO +hQMTcmz67RfGlF9fkk4pmr6hZGEoM6tg6TEQWwTLNZC77UEBUiM/oFiUWduke5/k +IIPLnE++5xJ5IQOW3har2h+QP7NySS+Kh5LuaRQ1IQgWj6feoVzU7brLbrMa2313 +yqtdRocXB7VMz2M5aBTq1P2eOC4= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDazCCAlOgAwIBAgIUJeDDNBkYHf7+XbyRBLMjZcgsXI0wDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCVFIxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMTExMDcxODI0MTRaFw0yMjEx +MDcxODI0MTRaMEUxCzAJBgNVBAYTAlRSMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCwDoLsIedf5GLeA+k+8hKIgg1KTn0gctpUZDz/qav4 +hlvqZ80x15eTCyC4LXQinfD6wV4WAwePmFj48R0tBlLEvr4q0ycnIziByk882a9+ +VRi+iLcu7MAF6xBE57r6x/+PiEN9cPrGg4yKHYVEnbKoS3d4s/Qzo0Mn+i4nwEg3 +Ygpn6XD5fJ3kT2EgVbH4d5CvensWHe6izA0z4GAlz2zuzbr59YILvwaWxcRRcljG +QoNs72x4KPXko13JZDYJz7HSTx/0jcaj6Pcqd2g9NgxRK/bWDOryuuUIL9/RvajR +q1OLqycRev+41xmWV65/hcs+4f16u+Q4mDwts3BYoYv3AgMBAAGjUzBRMB0GA1Ud +DgQWBBSZMpZT/7C+PZHWW+Cbdk74kdO5xTAfBgNVHSMEGDAWgBSZMpZT/7C+PZHW +W+Cbdk74kdO5xTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAq +HL9qQwlEUjk1/Zu8wbIlT8UM3b75zJ5CCgU8EmtQ9NJ/HOsTWS2QItaKWoVHf5sY +Y8SSDwB8LHONezL6exfwd1K48fC06Q0I+vOmQRKOMFBj8SNKnRgXburXDX9jFXtQ +QWLCZHGVQ5OU9RfKefGIGTTkseQ9j8VwiZ4ViwtfUNwV96TkPNus8oIYoT19hvtB +p1mY/FQ6phtH3fzMDcfBISwk1iDphgSPGpWJfG1jRNmm8SOzayh93NGxS7O7o88/ +/oNyydpjNpn0nCTyjD7H4+fq5zOek5HwNOE9RBVW5DbW8z/QkK754t4KcMm9PPzW +fxO9i7Wv+/jACbeKAtd+ +-----END CERTIFICATE----- diff --git a/scripts/swigpp-emscripten.py b/scripts/swigpp-emscripten.py new file mode 100644 index 000000000..e855d9b49 --- /dev/null +++ b/scripts/swigpp-emscripten.py @@ -0,0 +1,348 @@ +import os +import re +import sys +import argparse +import subprocess +import shutil +from build.sdk_build_utils import * + +ENUM_TEMPLATE = """ +""" + +VALUE_TYPE_TEMPLATE = """ +""" + +SHARED_PTR_TEMPLATE = """ +""" + +SHARED_PTR_CODE_TEMPLATE = """ +%feature("shared_ptr"); +""" + +POLYMORPHIC_SHARED_PTR_TEMPLATE = SHARED_PTR_TEMPLATE + """ +""" + +POLYMORPHIC_SHARED_PTR_CODE_TEMPLATE = SHARED_PTR_CODE_TEMPLATE + """ +%feature("polymorphic_shared_ptr"); +""" + +VALUE_TEMPLATE_TEMPLATE = """ + %template($TYPE$) $CLASSNAME$; +""" + +STANDARD_EQUALS_TEMPLATE = """ +""" + +STANDARD_EQUALS_CODE_TEMPLATE = """ +""" + +CUSTOM_EQUALS_TEMPLATE = """ +""" + +CUSTOM_EQUALS_CODE_TEMPLATE = """ +""" + +def fixProxyCode(fileName, className): + if not os.path.isfile(fileName): + return + + lines_in = readLines(fileName) + + lines_out = [] + for line in lines_in: + # Add '@hidden' comment above the special SWIG-wrapper lines + hide = line.strip() in [] + if line.find('swigCreatePolymorphicInstance') != -1: + hide = True + if hide: + numSpaces = len(line) - len(line.lstrip()) + lines_out.append(line[:numSpaces] + '/** @hidden */\n') + + lines_out.append(line) + + with open(fileName, 'w') as f: + f.writelines(lines_out) + +def transformSwigFile(sourcePath, outPath, headerDirs): + lines_in = [line.rstrip('\n') for line in readUncommentedLines(sourcePath)] + lines_out = [] + class_imports = {} + class_code = {} + imports_linenum = None + include_linenum = None + stl_wrapper = False + directors_module = False + for line in lines_in: + # Rename module + match = re.search('^\s*(%module(?:[(].*[)]|)\s+)([^\s]*)\s*$', line) + if match: + if match.group(1): + directors_module = 'directors' in match.group(1) + line = '%s%sModule' % (match.group(1), match.group(2)) + + # Language-specific method modifiers + match = re.search('^\s*%(java|cs|objc)methodmodifiers.*$', line) + if match: + lang = match.group(1) + if lang != 'java': + continue + + # Language-specific rename declarations + match = re.search('^\s*!(java|cs|objc)_rename(.*)$', line) + if match: + lang = match.group(1) + if lang != 'java': + continue + line = '%%rename%s' % match.group(2) + + # Attributes + match = re.search('^\s*(%|!)(static|)attribute.*$', line) + if match: + continue + + # Detect enum directive + match = re.search('^\s*!enum\s*[(]([^)]*)[)].*$', line) + if match: + enumName = match.group(1).strip() + args = { 'ENUMNAME': match.group(1).strip() } + lines_out += applyTemplate(ENUM_TEMPLATE, args) + continue + + # Detect value_type directive + match = re.search('^\s*!value_type\s*[(]([^)]*),([^)]*)[)].*$', line) + if match: + className = match.group(1).strip() + javaClass = match.group(2).strip().split(".")[-1] + javaDescriptor = "Lcom/carto/%s;" % match.group(2).strip().replace('.', '/') + args = { 'CLASSNAME': match.group(1).strip(), 'TYPE': javaClass, 'DESCRIPTOR': javaDescriptor } + lines_out += applyTemplate(VALUE_TYPE_TEMPLATE, args) + continue + + # Detect shared_ptr directive + match = re.search('^\s*!shared_ptr\s*[(]([^)]*),([^)]*)[)].*$', line) + if match: + className = match.group(1).strip() + javaClass = match.group(2).strip().split(".")[-1] + javaDescriptor = "Lcom/carto/%s;" % match.group(2).strip().replace('.', '/') + args = { 'CLASSNAME': match.group(1).strip(), 'TYPE': javaClass, 'DESCRIPTOR': javaDescriptor } + code = class_code.get(className, []) + code += applyTemplate(SHARED_PTR_CODE_TEMPLATE, args) + class_code[className] = code + lines_out += applyTemplate(SHARED_PTR_TEMPLATE, args) + continue + + # Polymorphic shared_ptr + match = re.search('^\s*!polymorphic_shared_ptr\s*[(]([^,]*),([^)]*)[)].*', line) + if match: + className = match.group(1).strip() + javaPackage = 'com.carto.%s' % '.'.join(match.group(2).strip().split(".")[:-1]) + javaClass = match.group(2).strip().split(".")[-1] + javaDescriptor = "Lcom/carto/%s;" % match.group(2).strip().replace('.', '/') + args = { 'CLASSNAME': match.group(1).strip(), 'TYPE': javaClass, 'PACKAGE': javaPackage, 'DESCRIPTOR': javaDescriptor } + code = class_code.get(className, []) + code += applyTemplate(POLYMORPHIC_SHARED_PTR_CODE_TEMPLATE, args) + class_code[className] = code + lines_out += applyTemplate(POLYMORPHIC_SHARED_PTR_TEMPLATE, args) + continue + + # Value-template + match = re.search('^\s*!value_template\s*[(]([^)]*),([^)]*)[)].*$', line) + if match: + className = match.group(1).strip() + javaClass = match.group(2).strip().split(".")[-1] + args = { 'CLASSNAME': match.group(1).strip(), 'TYPE': javaClass } + lines_out += applyTemplate(VALUE_TEMPLATE_TEMPLATE, args) + continue + + # Standard equals + match = re.search('^\s*!standard_equals\s*[(]([^)]*)[)].*', line) + if match: + className = match.group(1).strip() + args = { 'CLASSNAME': match.group(1).strip() } + code = class_code.get(className, []) + code += applyTemplate(STANDARD_EQUALS_CODE_TEMPLATE, args) + class_code[className] = code + lines_out += applyTemplate(STANDARD_EQUALS_TEMPLATE, args) + continue + + # Custom equals + match = re.search('^\s*!custom_equals\s*[(]([^)]*)[)].*', line) + if match: + className = match.group(1).strip() + args = { 'CLASSNAME': match.group(1).strip() } + code = class_code.get(className, []) + code += applyTemplate(CUSTOM_EQUALS_CODE_TEMPLATE, args) + class_code[className] = code + lines_out += applyTemplate(CUSTOM_EQUALS_TEMPLATE, args) + continue + + # Custom toString + match = re.search('^\s*!custom_tostring\s*[(]([^)]*)[)].*', line) + if match: + continue + + # Imports + match = re.search('^\s*!(proxy|java|cs|objc)_imports\s*[(]([^)]*)[)](.*)$', line) + if match: + lang = match.group(1) + parts = [part.strip() for part in match.group(2).split(",")] + className = parts[0] + if lang == 'proxy': + class_imports[className] = class_imports.get(className, []) + ['import com.carto.%s;' % part for part in parts[1:]] + elif lang == 'java': + class_imports[className] = class_imports.get(className, []) + ['import %s;' % part for part in parts[1:]] + else: + continue + line = match.group(3) + if imports_linenum is None: + imports_linenum = len(lines_out) + + # Check for STL templates + match = re.search('^\s*%template\(.*\)\s*std::.*$', line) + if match: + stl_wrapper = True + + # javacode import + match = re.search('^\s*%typemap[(]javacode[)]\s*(.*)\s*%{(.*)%}', line) + if match: + className = match.group(1).strip() + code = class_code.get(className, []) + code.append(match.group(2)) + class_code[className] = code + continue + + # Includes + match = re.search('^\s*%include\s+(.*)$', line) + if match: + include_linenum = len(lines_out) + + # Rename all methods to nonCapCase, add this before including C++ code + match = re.search('^\s*%include\s+"(.*)".*$', line) + if match: + includeName = match.group(1) + if not stl_wrapper and includeName != "NutiSwig.i": + # This is a huge hack: we will capitalize all method names starting with lower case letters. But we will do this only for carto:: classes + for n in range(ord('a'), ord('z') + 1): + c = chr(n) + lines_out.append('%%rename("%%(regex:/::%s([^:]*)$/%s\\\\1/)s", fullname=1, regextarget=1, %%$isfunction) "^carto::.+::%s[^:]*$";' % (c.upper(), c, c.upper())) + lines_out.append('') + + lines_out.append(line) + + # Add imports + if imports_linenum is not None: + lines_import = [] + for className, imports in class_imports.items(): + lines_import.append("%%typemap(javaimports) %s %%{" % className) + lines_import += imports + lines_import.append("%}") + lines_import.append("") + lines_import.append("%pragma(java) jniclassimports=%{") + for className, imports in class_imports.items(): + lines_import += imports + lines_import.append("%}") + lines_out = lines_out[:imports_linenum] + lines_import + lines_out[imports_linenum:] + if include_linenum > imports_linenum: + include_linenum += len(lines_import) + + # Add typemap code + if include_linenum is not None: + lines_code = [] + for className, code in class_code.items(): + lines_code += code + lines_out = lines_out[:include_linenum] + lines_code + lines_out[include_linenum:] + + # Write processed module + with open(outPath, 'w') as f: + f.writelines([line + '\n' for line in lines_out]) + +def transformSwigPackage(args, sourceDir, outDir, packageName): + for fileName in os.listdir(sourceDir): + if fileName == 'NutiSwig.i': + continue + sourcePath = os.path.join(sourceDir, fileName) + if not os.path.isfile(sourcePath): + continue + if not os.path.isdir(outDir): + os.makedirs(outDir) + outPath = os.path.join(outDir, fileName) + headerDirs = args.cppDir.split(";") + transformSwigFile(sourcePath, outPath, headerDirs) + return True + +def transformSwigPackages(args, sourceDir, outDir, basePackageName): + for dirName in os.listdir(sourceDir): + sourcePath = os.path.join(sourceDir, dirName) + if not os.path.isdir(sourcePath) or dirName.startswith("."): + continue + outPath = os.path.join(outDir, dirName) + packageName = (basePackageName + '.' if basePackageName else '') + dirName + transformSwigPackages(args, sourcePath, outPath, packageName) + if not transformSwigPackage(args, sourcePath, outPath, packageName): + return False + return True + +def buildSwigPackage(args, sourceDir, packageName): + for fileName in os.listdir(sourceDir): + if fileName == 'NutiSwig.i' or fileName == 'BaseMapView.i': + continue + fileNameWithoutExt = fileName.split(".")[0] + sourcePath = os.path.join(sourceDir, fileName) + outPath = os.path.join(args.wrapperDir, fileNameWithoutExt) + "_wrap.cpp" + if not os.path.isfile(sourcePath): + continue + if not os.path.isdir(args.wrapperDir): + os.makedirs(args.wrapperDir) + + includes = ["-I%s" % dir for dir in ["../scripts/swig/java", "../scripts/swig", args.moduleDir] + args.sourceDir.split(";") + [args.wrapperDir] + args.cppDir.split(";")] + swigPath = os.path.dirname(args.swig) + if swigPath: + includes += ["-I%s/Lib/emscripten" % swigPath, "-I%s/Lib" % swigPath] + defines = ["-D%s" % define for define in args.defines.split(';') if define] + cmd = [args.swig, "-c++", "-emscripten", "-o", outPath] + defines + includes + [sourcePath] + if subprocess.call(cmd) != 0: + print("Error in %s" % fileName) + return False + + for line in [line.rstrip('\n') for line in readUncommentedLines(sourcePath)]: + match = re.search('^\s*%template\((.*)\).*$', line) + if match: + templateFileNameWithoutExt = match.group(1) + return True + +def buildSwigPackages(args, sourceDir, basePackageName): + for dirName in os.listdir(sourceDir): + sourcePath = os.path.join(sourceDir, dirName) + if not os.path.isdir(sourcePath) or dirName.startswith("."): + continue + packageName = (basePackageName + '.' if basePackageName else '') + dirName + buildSwigPackages(args, sourcePath, packageName) + if not buildSwigPackage(args, sourcePath, packageName): + return False + return True + +parser = argparse.ArgumentParser() +parser.add_argument('--profile', dest='profile', default=getDefaultProfileId(), type=validProfile, help='Build profile') +parser.add_argument('--swig', dest='swig', default='swig', help='path to Swig executable') +parser.add_argument('--defines', dest='defines', default='', help='Defines for Swig') +parser.add_argument('--cppdir', dest='cppDir', default='../all/native;../emscripten/native', help='directories containing C++ headers') +parser.add_argument('--wrapperdir', dest='wrapperDir', default='../generated/emscripten-js/wrappers', help='output directory for C++ wrappers') +parser.add_argument('--moduledir', dest='moduleDir', default='../generated/emscripten-js/modules', help='output directory containing preprocessed Swig modules') +parser.add_argument('--sourcedir', dest='sourceDir', default='../all/modules;../emscripten/modules', help='input directories containing subdirectories of Swig wrappers') + +args = parser.parse_args() +args.defines += ';' + getProfile(args.profile).get('defines', '') + +if not checkExecutable(args.swig, '-help'): + print('Unable to find SWIG executable. Use --swig argument to specify its location. The supported version is available from https://github.com/cartodb/mobile-swig') + sys.exit(-1) + +if os.path.isdir(args.wrapperDir): + shutil.rmtree(args.wrapperDir) +if os.path.isdir(args.moduleDir): + shutil.rmtree(args.moduleDir) +for sourceDir in args.sourceDir.split(";"): + if os.path.exists(sourceDir) and not transformSwigPackages(args, sourceDir, args.moduleDir, ""): + sys.exit(-1) +if not buildSwigPackages(args, args.moduleDir, ""): + sys.exit(-1)