diff --git a/.gitmodules b/.gitmodules index 79803a6..ad738c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,3 @@ [submodule "third_party/rapidjson"] path = third_party/rapidjson url = https://github.com/Tencent/rapidjson -[submodule "third_party/libui"] - path = third_party/libui - url = https://github.com/andlabs/libui diff --git a/CMakeLists.txt b/CMakeLists.txt index bf77c3f..368ad92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,8 @@ set( "${CMAKE_CXX_FLAGS} -Wno-sign-compare -Wno-deprecated-declarations -Wno-unused-parameter -Wno-extern-initializer -Wno-deprecated-enum-enum-conversion" ) +add_definitions(-DRAPIDJSON_HAS_STDSTRING) + if(CMAKE_BUILD_TYPE EQUAL "DEBUG") add_link_options(-g -O0) endif() @@ -51,23 +53,6 @@ else() endif() execute_process(COMMAND ninja WORKING_DIRECTORY ${SKIA_BUILD_DIR}) -# LibUI for the renderer -set(LIBUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party/libui) -set(LIBUI_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/third_party/libui) -file(MAKE_DIRECTORY ${LIBUI_BUILD_DIR}) -execute_process( - COMMAND meson setup . ${LIBUI_DIR} - WORKING_DIRECTORY ${LIBUI_BUILD_DIR}) -execute_process( - COMMAND meson configure -Ddefault_library=static - WORKING_DIRECTORY ${LIBUI_BUILD_DIR}) -execute_process( - COMMAND meson compile - WORKING_DIRECTORY ${LIBUI_BUILD_DIR}) -add_library(libui STATIC IMPORTED) -set_property(TARGET libui PROPERTY - IMPORTED_LOCATION ${LIBUI_BUILD_DIR}/meson-out/libui.a) - # For OpenGL find_package(glfw3 3.3 REQUIRED) @@ -75,8 +60,10 @@ add_executable(streetview_client ${APPLICATION_TYPE} src/main.cpp src/parse.cpp src/extract.cpp - src/renderer.cpp + src/headers.cpp + src/interface.cpp src/download.cpp + src/preloader.cpp ) set_target_properties(streetview_client PROPERTIES POSITION_INDEPENDENT_CODE ON) @@ -88,5 +75,5 @@ set_target_properties(streetview_client PROPERTIES CXX_VISIBILITY_PRESET hidden POSITION_INDEPENDENT_CODE ON) -include_directories(streetview_client include fmt libcurl CLI11 glfw3 ${SKIA_DIR} ${SKIA_DIR}/include ${LIBUI_DIR} ${RAPIDJSON_INCLUDE_DIR}) -target_link_libraries(streetview_client PUBLIC fmt libcurl CLI11 skia libui "-framework OpenGl" "-framework CoreFoundation" "-framework CoreGraphics" "-framework CoreText" "-framework CoreServices" "-framework Cocoa" "-framework Metal" "-framework Foundation" "-framework QuartzCore" glfw) \ No newline at end of file +include_directories(streetview_client include fmt libcurl CLI11 glfw3 ${SKIA_DIR} ${SKIA_DIR}/include ${RAPIDJSON_INCLUDE_DIR}) +target_link_libraries(streetview_client PUBLIC fmt libcurl CLI11 skia "-framework OpenGl" "-framework CoreFoundation" "-framework CoreGraphics" "-framework CoreText" "-framework CoreServices" "-framework Cocoa" "-framework Metal" "-framework Foundation" "-framework QuartzCore" glfw) \ No newline at end of file diff --git a/src/download.cpp b/src/download.cpp index d905e13..fc0c1cd 100644 --- a/src/download.cpp +++ b/src/download.cpp @@ -81,6 +81,7 @@ rapidjson::Document download_preview_document( rapidjson::Document download_photometa( CURL* curl_handle, std::string client_id, std::string panorama_id) { + CURLcode res; auto photometa_url = fmt::format("https://www.google.com/maps/photometa/" "v1?authuser=0&hl=en&gl=us&pb=!1m4!1smaps_sv.tactile!11m2!" @@ -101,18 +102,21 @@ rapidjson::Document download_photometa( return photometa_document; } -sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, int streetview_zoom, +sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, int streetview_zoom, rapidjson::Document& photmeta_document) { auto [tiles_width, tiles_height] = extract_tiles_dimensions(photmeta_document, streetview_zoom); sk_sp tile_surface = SkSurface::MakeRasterN32Premul(tiles_width * 512, tiles_height * 512); + fmt::print("Downloading {} tiles\n", tiles_width * tiles_height); + // Start processing tile_surface->getCanvas()->clear(SK_ColorWHITE); for(int y = 0; y < tiles_height; y++) { for(int x = 0; x < tiles_width; x++) { // Download the specific tile + // Each tile takes around ~40ms to download CURLcode res; auto tile_url = fmt::format("https://streetviewpixels-pa.googleapis.com/v1/" "tile?cb_client=maps_sv.tactile&panoid={}&x={}&" @@ -126,6 +130,7 @@ sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, i curl_easy_getinfo(curl_handle, CURLINFO_RESPONSE_CODE, &http_code); if(http_code == 200) { // Construct an image from the data + // This is less than 1ms auto image = SkImage::MakeFromEncoded( SkData::MakeWithoutCopy(tile_download.data(), tile_download.size())); tile_surface->getCanvas()->drawImage(image, 512 * x, 512 * y); @@ -136,5 +141,5 @@ sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, i } } - return tile_surface; + return tile_surface->makeImageSnapshot(); } \ No newline at end of file diff --git a/src/download.hpp b/src/download.hpp index 2e53697..83cb7ce 100644 --- a/src/download.hpp +++ b/src/download.hpp @@ -1,7 +1,5 @@ #pragma once -#define RAPIDJSON_HAS_STDSTRING 1 - #include #include #include @@ -16,5 +14,5 @@ rapidjson::Document download_preview_document( CURL* curl_handle, std::string client_id, int num_previews, double lat, double lng, int range); rapidjson::Document download_photometa( CURL* curl_handle, std::string client_id, std::string panorama_id); -sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, int streetview_zoom, +sk_sp download_panorama(CURL* curl_handle, std::string panorama_id, int streetview_zoom, rapidjson::Document& photmeta_document); \ No newline at end of file diff --git a/src/extract.cpp b/src/extract.cpp index b0a6edd..70ac8b5 100644 --- a/src/extract.cpp +++ b/src/extract.cpp @@ -9,47 +9,38 @@ std::vector extract_panorama_ids(rapidjson::Document& preview_document) { std::vector ids; - if(preview_document.IsArray() && preview_document.Size() == 11) { - if(preview_document[0].IsArray() && preview_document[0].Size() > 0) { - // std::cout << preview_document[0].Size() << " previews" << std::endl; - for(auto& preview : preview_document[0].GetArray()) { - if(preview.IsArray() && preview.Size() == 32) { - if(preview[0].IsString()) { - auto panorama_id - = std::string(preview[0].GetString(), preview[0].GetStringLength()); - if(panorama_id.size() != 22) { - // Not a street view - continue; - } - ids.push_back(panorama_id); - } else { - std::cout << "Panorama id is not a string" << std::endl; - } - } else { - std::cout << "Panorama is not well formed" << std::endl; - } - } - } else { - std::cout << "No street view panoramas here" << std::endl; + // std::cout << preview_document[0].Size() << " previews" << std::endl; + for(auto& preview : preview_document[0].GetArray()) { + auto panorama_id = std::string(preview[0].GetString(), preview[0].GetStringLength()); + if(panorama_id.size() != 22) { + // Not a street view + continue; } - } else { - std::cout << "Is not well formed array" << std::endl; + ids.push_back(panorama_id); } return ids; } +Panorama extract_info(rapidjson::Document& photometa_document) { + auto& id = photometa_document[1][0][1][1]; + auto& lat_long = photometa_document[1][0][5][0][1][0]; + return Panorama { + .id = id.GetString(), + .lat = lat_long[2].GetDouble(), + .lng = lat_long[3].GetDouble(), + }; +} + Location extract_location(rapidjson::Document& photometa_document) { Location location; - auto& outer_location = photometa_document[1][0][3]; - if(outer_location.IsArray() && outer_location.Size() > 2 && outer_location[2].IsArray()) { - if(outer_location[2].Size() == 1) { - // No address, just city - location.city_and_state = std::string(outer_location[2][0].GetString()); - } else if(outer_location[2].Size() == 2) { - // Address and city - location.street = std::string(outer_location[2][0].GetString()); - location.city_and_state = std::string(outer_location[2][1].GetString()); - } + auto& outer_location = photometa_document[1][0][3][2]; + if(outer_location.Size() == 1) { + // No address, just city + location.city_and_state = std::string(outer_location[0].GetString()); + } else if(outer_location.Size() == 2) { + // Address and city + location.street = std::string(outer_location[0].GetString()); + location.city_and_state = std::string(outer_location[1].GetString()); } return location; @@ -57,24 +48,17 @@ Location extract_location(rapidjson::Document& photometa_document) { std::vector extract_adjacent_panoramas(rapidjson::Document& photometa_document) { std::vector panoramas; - auto& outer_adjacent = photometa_document[1][0][5]; - if(outer_adjacent.IsArray() && outer_adjacent.Size() == 1 && outer_adjacent[0].IsArray() - && outer_adjacent[0].Size() == 13 && outer_adjacent[0][3].IsArray() - && outer_adjacent[0][3].Size() == 1 && outer_adjacent[0][3][0].IsArray()) { - for(auto& adjacent : outer_adjacent[0][3][0].GetArray()) { - if(adjacent.IsArray() && adjacent.Size() == 3 && adjacent[0].IsArray() - && adjacent[0].Size() == 2) { - Panorama panorama; - auto adjacent_tile = adjacent[0][1].GetString(); - panorama.id = std::string(adjacent_tile); - if(adjacent[2].IsArray() && adjacent[2].Size() == 3 && adjacent[2][0].IsArray() - && adjacent[2][0].Size() == 4) { - panorama.lat = adjacent[2][0][2].GetDouble(); - panorama.lng = adjacent[2][0][3].GetDouble(); - } - panoramas.push_back(panorama); - } - } + auto current_panorama = extract_info(photometa_document); + auto& adjacent_list = photometa_document[1][0][5][0][3][0]; + for(auto& adjacent : adjacent_list.GetArray()) { + Panorama panorama = { + .id = adjacent[0][1].GetString(), + .lat = adjacent[2][0][2].GetDouble(), + .lng = adjacent[2][0][3].GetDouble(), + }; + if(panorama.id == current_panorama.id) + continue; + panoramas.push_back(panorama); } return panoramas; } diff --git a/src/extract.hpp b/src/extract.hpp index b7407dd..db1a659 100644 --- a/src/extract.hpp +++ b/src/extract.hpp @@ -18,6 +18,7 @@ struct Panorama { }; std::vector extract_panorama_ids(rapidjson::Document& preview_document); +Panorama extract_info(rapidjson::Document& photometa_document); Location extract_location(rapidjson::Document& photometa_document); std::vector extract_adjacent_panoramas(rapidjson::Document& photometa_document); std::pair extract_tiles_dimensions( diff --git a/src/headers.cpp b/src/headers.cpp new file mode 100644 index 0000000..945c50e --- /dev/null +++ b/src/headers.cpp @@ -0,0 +1,68 @@ +#include "headers.hpp" + +curl_slist* get_main_page_headers() { + curl_slist* main_page_headers = NULL; + main_page_headers + = curl_slist_append(main_page_headers, "Accept: " + "text/html,application/xhtml+xml,application/" + "xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); + main_page_headers = curl_slist_append(main_page_headers, "Accept-Encoding: en-US,en;q=0.5"); + main_page_headers = curl_slist_append(main_page_headers, "Accept-Language: en-US,en;q=0.5"); + main_page_headers = curl_slist_append(main_page_headers, "Connection: keep-alive"); + main_page_headers = curl_slist_append(main_page_headers, "Host: www.google.com"); + main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Dest: document"); + main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Mode: navigate"); + main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Site: none"); + main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-User: ?1"); + main_page_headers = curl_slist_append(main_page_headers, "TE: trailers"); + main_page_headers = curl_slist_append(main_page_headers, "Upgrade-Insecure-Requests: 1"); + main_page_headers = curl_slist_append(main_page_headers, + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " + "rv:109.0) Gecko/20100101 Firefox/111.0"); + return main_page_headers; +} + +curl_slist* get_panorama_headers() { + curl_slist* panorama_headers = NULL; + panorama_headers + = curl_slist_append(panorama_headers, "Accept: " + "text/html,application/xhtml+xml,application/" + "xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); + panorama_headers = curl_slist_append(panorama_headers, "Accept-Encoding: en-US,en;q=0.5"); + panorama_headers = curl_slist_append(panorama_headers, "Accept-Language: en-US,en;q=0.5"); + panorama_headers + = curl_slist_append(panorama_headers, "Alt-Used: streetviewpixels-pa.googleapis.com"); + panorama_headers = curl_slist_append(panorama_headers, "Connection: keep-alive"); + panorama_headers + = curl_slist_append(panorama_headers, "Host: streetviewpixels-pa.googleapis.com"); + panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Dest: document"); + panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Mode: navigate"); + panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Site: none"); + panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-User: ?1"); + panorama_headers = curl_slist_append(panorama_headers, "TE: trailers"); + panorama_headers = curl_slist_append(panorama_headers, "Upgrade-Insecure-Requests: 1"); + panorama_headers = curl_slist_append(panorama_headers, + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " + "rv:109.0) Gecko/20100101 Firefox/111.0"); + return panorama_headers; +} + +curl_slist* get_photometa_headers() { + curl_slist* photometa_headers = NULL; + photometa_headers = curl_slist_append(photometa_headers, "Accept: */*"); + photometa_headers = curl_slist_append(photometa_headers, "Accept-Encoding: en-US,en;q=0.5"); + photometa_headers = curl_slist_append(photometa_headers, "Accept-Language: en-US,en;q=0.5"); + photometa_headers = curl_slist_append(photometa_headers, "Connection: keep-alive"); + photometa_headers = curl_slist_append(photometa_headers, "Host: www.google.com"); + photometa_headers = curl_slist_append(photometa_headers, "Referer: https://www.google.com/"); + // TODO switch to cors + photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Dest: document"); + photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Mode: navigate"); + photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Site: none"); + photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-User: ?1"); + photometa_headers = curl_slist_append(photometa_headers, "TE: trailers"); + photometa_headers = curl_slist_append(photometa_headers, + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " + "rv:109.0) Gecko/20100101 Firefox/111.0"); + return photometa_headers; +} \ No newline at end of file diff --git a/src/headers.hpp b/src/headers.hpp index 85591d0..c3cc256 100644 --- a/src/headers.hpp +++ b/src/headers.hpp @@ -2,69 +2,6 @@ #include -static curl_slist* get_main_page_headers() { - curl_slist* main_page_headers = NULL; - main_page_headers - = curl_slist_append(main_page_headers, "Accept: " - "text/html,application/xhtml+xml,application/" - "xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); - main_page_headers = curl_slist_append(main_page_headers, "Accept-Encoding: en-US,en;q=0.5"); - main_page_headers = curl_slist_append(main_page_headers, "Accept-Language: en-US,en;q=0.5"); - main_page_headers = curl_slist_append(main_page_headers, "Connection: keep-alive"); - main_page_headers = curl_slist_append(main_page_headers, "Host: www.google.com"); - main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Dest: document"); - main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Mode: navigate"); - main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-Site: none"); - main_page_headers = curl_slist_append(main_page_headers, "Sec-Fetch-User: ?1"); - main_page_headers = curl_slist_append(main_page_headers, "TE: trailers"); - main_page_headers = curl_slist_append(main_page_headers, "Upgrade-Insecure-Requests: 1"); - main_page_headers = curl_slist_append(main_page_headers, - "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " - "rv:109.0) Gecko/20100101 Firefox/111.0"); - return main_page_headers; -} - -static curl_slist* get_panorama_headers() { - curl_slist* panorama_headers = NULL; - panorama_headers - = curl_slist_append(panorama_headers, "Accept: " - "text/html,application/xhtml+xml,application/" - "xml;q=0.9,image/avif,image/webp,*/*;q=0.8"); - panorama_headers = curl_slist_append(panorama_headers, "Accept-Encoding: en-US,en;q=0.5"); - panorama_headers = curl_slist_append(panorama_headers, "Accept-Language: en-US,en;q=0.5"); - panorama_headers - = curl_slist_append(panorama_headers, "Alt-Used: streetviewpixels-pa.googleapis.com"); - panorama_headers = curl_slist_append(panorama_headers, "Connection: keep-alive"); - panorama_headers - = curl_slist_append(panorama_headers, "Host: streetviewpixels-pa.googleapis.com"); - panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Dest: document"); - panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Mode: navigate"); - panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-Site: none"); - panorama_headers = curl_slist_append(panorama_headers, "Sec-Fetch-User: ?1"); - panorama_headers = curl_slist_append(panorama_headers, "TE: trailers"); - panorama_headers = curl_slist_append(panorama_headers, "Upgrade-Insecure-Requests: 1"); - panorama_headers = curl_slist_append(panorama_headers, - "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " - "rv:109.0) Gecko/20100101 Firefox/111.0"); - return panorama_headers; -} - -static curl_slist* get_photometa_headers() { - curl_slist* photometa_headers = NULL; - photometa_headers = curl_slist_append(photometa_headers, "Accept: */*"); - photometa_headers = curl_slist_append(photometa_headers, "Accept-Encoding: en-US,en;q=0.5"); - photometa_headers = curl_slist_append(photometa_headers, "Accept-Language: en-US,en;q=0.5"); - photometa_headers = curl_slist_append(photometa_headers, "Connection: keep-alive"); - photometa_headers = curl_slist_append(photometa_headers, "Host: www.google.com"); - photometa_headers = curl_slist_append(photometa_headers, "Referer: https://www.google.com/"); - // TODO switch to cors - photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Dest: document"); - photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Mode: navigate"); - photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-Site: none"); - photometa_headers = curl_slist_append(photometa_headers, "Sec-Fetch-User: ?1"); - photometa_headers = curl_slist_append(photometa_headers, "TE: trailers"); - photometa_headers = curl_slist_append(photometa_headers, - "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; " - "rv:109.0) Gecko/20100101 Firefox/111.0"); - return photometa_headers; -} \ No newline at end of file +curl_slist* get_main_page_headers(); +curl_slist* get_panorama_headers(); +curl_slist* get_photometa_headers(); \ No newline at end of file diff --git a/src/renderer.cpp b/src/interface.cpp similarity index 50% rename from src/renderer.cpp rename to src/interface.cpp index 363b28d..463c41a 100644 --- a/src/renderer.cpp +++ b/src/interface.cpp @@ -1,8 +1,9 @@ -#include "renderer.hpp" +#include "interface.hpp" #define SK_GL 1 #define SK_GANESH 1 #define SK_ENABLE_SKSL 1 +#define PI 3.14159265358979323846264f #include #include @@ -21,7 +22,19 @@ #include #include #include -#include + +#include "download.hpp" + +InterfaceWindow::InterfaceWindow(std::string initial_panorama_id, CURL* curl_handle) { + // Start preloader + preloader.SetClientId(download_client_id(curl_handle)); + preloader.SetZoom(1); + preloader.SetCurlHandle(curl_handle); + preloader.Start(5); + + // Set initial panorama + ChangePanorama(initial_panorama_id); +} bool InterfaceWindow::PrepareWindow() { if(!glfwInit()) { @@ -39,7 +52,7 @@ bool InterfaceWindow::PrepareWindow() { /* Create a windowed mode window and its OpenGL context */ glfwWindowHint(GLFW_MAXIMIZED, 1); - window = glfwCreateWindow(1, 1, "Hello World", NULL, NULL); + window = glfwCreateWindow(1, 1, "Streetview Client", NULL, NULL); if(!window) { glfwTerminate(); return false; @@ -85,15 +98,15 @@ bool InterfaceWindow::PrepareWindow() { window, *[](GLFWwindow* window, int key, int scancode, int action, int mods) { InterfaceWindow* renderer = (InterfaceWindow*)glfwGetWindowUserPointer(window); if(key == GLFW_KEY_UP && action == GLFW_PRESS) { - // Load another panorama - renderer->SetImage(SkImage::MakeFromEncoded( - SkData::MakeFromFileName("tiles/stitched-NRQ3LOFsRR15hQaPleVRug.png"))); - renderer->PrepareShader(); + // Load closest adjacent + renderer->ChangePanorama(renderer->GetClosestAdjacent().id); } }); // Make the window's context current glfwMakeContextCurrent(window); + // Force fast rendering, probably 120fps + glfwSwapInterval(0); // Set the user pointer glfwSetWindowUserPointer(window, this); @@ -115,11 +128,8 @@ bool InterfaceWindow::PrepareWindow() { 0, // stencil bits framebufferInfo); - //(replace line below with this one to enable correct color spaces) sSurface = - // SkSurface::MakeFromBackendRenderTarget(sContext, backendRenderTarget, - // kBottomLeft_GrSurfaceOrigin, colorType, SkColorSpace::MakeSRGB(), nullptr).release(); surface = SkSurface::MakeFromBackendRenderTarget(direct_context.get(), backendRenderTarget, - kBottomLeft_GrSurfaceOrigin, colorType, nullptr, nullptr); + kBottomLeft_GrSurfaceOrigin, colorType, SkColorSpace::MakeSRGB(), nullptr); timing_start = std::chrono::high_resolution_clock::now(); return true; @@ -127,6 +137,7 @@ bool InterfaceWindow::PrepareWindow() { void InterfaceWindow::DrawFrame() { RenderPanorama(); + RenderMap(); surface->getCanvas()->flush(); glfwSwapBuffers(window); glfwPollEvents(); @@ -137,15 +148,34 @@ void InterfaceWindow::DrawFrame() { // * 1000.0); } +void InterfaceWindow::ChangePanorama(std::string id) { + panorama_info = preloader.GetPanorama(id, true); + current_panorama = extract_info(panorama_info->photometa); + adjacent = extract_adjacent_panoramas(panorama_info->photometa); + PrepareShader(); + + for(auto& panorama : adjacent) { + preloader.QueuePanorama(panorama.id); + } +} + void InterfaceWindow::RenderPanorama() { if(shader_builder) { // Set view resolution shader_builder->uniform("u_viewResolution") = SkV2 { (float)surface->width(), (float)surface->height() }; - // Set mouse location from drag - shader_builder->uniform("u_mouse") - = SkV2 { (float)glfw_events.screen_offset_x, (float)glfw_events.screen_offset_y }; + // Calculate yaw and pitch in C++ + // This is important because it is needed later for streetview navigation + auto normalized_x + = (float)glfw_events.screen_offset_x / (float)surface->width() * 2.0f - 1.0f; + auto normalized_y + = (float)glfw_events.screen_offset_y / (float)surface->height() * 2.0f - 1.0f; + yaw = normalized_x * PI * 0.5f; + pitch = normalized_y * PI * 0.33f + -PI * 0.65f; + + // Set mouse rotation + shader_builder->uniform("u_rotation") = SkV2 { yaw, pitch }; // Correct FOV formula // Vertical FOV = Initial FOV / Zoom @@ -165,55 +195,87 @@ void InterfaceWindow::RenderPanorama() { } } -#define PI 3.14159265358979323846264 +void InterfaceWindow::RenderMap() { + constexpr int map_width = 800; + constexpr int map_height = 800; + constexpr double scale = 500000.0; + + // Render in bottom right, 200 by 200 + int start_x = surface->width() - map_width; + int start_y = surface->height() - map_height; + + // Draw the background + SkPaint background_paint; + background_paint.setColor(SkColorSetARGB(255, 255, 255, 255)); + surface->getCanvas()->drawRect( + SkRect::MakeXYWH(start_x, start_y, map_width, map_height), background_paint); + + // Get closest adjacent + auto closest_adjacent = GetClosestAdjacent(); + + // Draw the adjacent + SkPaint adjacent_panorama_paint; + for(auto& panorama : adjacent) { + if(panorama.id == closest_adjacent.id) { + adjacent_panorama_paint.setColor(SkColorSetARGB(255, 255, 0, 0)); + } else { + adjacent_panorama_paint.setColor(SkColorSetARGB(255, 0, 0, 255)); + } + + surface->getCanvas()->drawCircle( + start_x + map_width / 2 + (panorama.lat - current_panorama.lat) * scale, + start_y + map_height / 2 + (panorama.lng - current_panorama.lng) * scale, 8, + adjacent_panorama_paint); + } + + // Draw the current panorama + SkPaint current_panorama_paint; + current_panorama_paint.setColor(SkColorSetARGB(255, 0, 255, 0)); + surface->getCanvas()->drawCircle( + start_x + map_width / 2, start_y + map_height / 2, 12, current_panorama_paint); +} + void InterfaceWindow::PrepareShader() { const char* sksl_src = R"( -// Handle 8 images at once, each one max 2048x2048 -uniform shader image; - -uniform vec2 u_imageResolution; -uniform vec2 u_viewResolution; -uniform vec2 u_mouse; - -//uniform float u_pitch; -//uniform float u_yaw; -uniform float u_fovH; -uniform float u_fovV; - -const float PI = 3.14159265358979323846264; -const float PI2 = 3.14159265358979323846264 * 2.0; -const float PI_2 = 3.14159265358979323846264 * 0.5; - -//tools -vec3 rotateXY(vec3 p, vec2 angle) { - vec2 c = cos(angle), s = sin(angle); - p = vec3(p.x, c.x*p.y + s.x*p.z, -s.x*p.y + c.x*p.z); - return vec3(c.y*p.x + s.y*p.z, p.y, -s.y*p.x + c.y*p.z); -} + // Handle 8 images at once, each one max 2048x2048 + uniform shader image; + + uniform vec2 u_imageResolution; + uniform vec2 u_viewResolution; + uniform vec2 u_rotation; + + //uniform float u_pitch; + //uniform float u_yaw; + uniform float u_fovH; + uniform float u_fovV; + + const float PI = 3.14159265358979323846264; + const float PI2 = 3.14159265358979323846264 * 2.0; + const float PI_2 = 3.14159265358979323846264 * 0.5; + + vec3 rotateXY(vec3 p, vec2 angle) { + vec2 c = cos(angle), s = sin(angle); + p = vec3(p.x, c.x*p.y + s.x*p.z, -s.x*p.y + c.x*p.z); + return vec3(c.y*p.x + s.y*p.z, p.y, -s.y*p.x + c.y*p.z); + } + + float4 main(float2 fragCoord) { + // Place 0,0 in center from -1 to 1 ndc + vec2 uv = fragCoord * 2.0 / u_viewResolution - 1.0; -float4 main(float2 fragCoord) { - //place 0,0 in center from -1 to 1 ndc - vec2 uv = fragCoord * 2.0 / u_viewResolution - 1.0; + // Spherical + vec3 camDir = normalize(vec3(uv * vec2(tan(0.5 * u_fovH), tan(0.5 * u_fovV)), 1.0)); - //to spherical - vec3 camDir = normalize(vec3(uv * vec2(tan(0.5 * u_fovH), tan(0.5 * u_fovV)), 1.0)); - - //camRot is angle vec in rad - vec2 normalizedMouse = u_mouse / u_viewResolution * 2.0 - 1.0; - vec2 camRot = normalizedMouse * vec2(PI / 2.0, PI / 3.0) + vec2(0.0, -PI * 0.65); - //vec2 camRot = vec2((u_mouse.x / u_viewResolution.x - 0.5) * 2.0 * PI, (0.5 - u_mouse.y / u_viewResolution.y) * PI + PI * 0.5); - //vec2 camRot = vec2(u_yaw, u_pitch); - - //rotate - vec3 rd = normalize(rotateXY(camDir, camRot.yx)); - - //radial azmuth polar - vec2 texCoord = 1 - vec2(atan(rd.z, rd.x) + PI, acos(-rd.y)) / vec2(2.0 * PI, PI); - vec2 imageCoord = texCoord * u_imageResolution; - - return image.eval(imageCoord); - //return vec4(u_mouse.x / u_viewResolution.x, 0.0, 0.0, 1.0); -} + // Rotate + vec3 rd = normalize(rotateXY(camDir, u_rotation.yx)); + + // Radial azmuth polar + vec2 texCoord = vec2(atan(rd.z, rd.x) + PI, acos(-rd.y)) / vec2(2.0 * PI, PI); + // Y is flipped but X is not + vec2 imageCoord = vec2(texCoord.x, 1 - texCoord.y) * u_imageResolution; + + return image.eval(imageCoord); + } )"; auto [effect, errorText] = SkRuntimeEffect::MakeForShader(SkString(sksl_src)); @@ -230,11 +292,43 @@ float4 main(float2 fragCoord) { // Set one time image resolution shader_builder->uniform("u_imageResolution") - = SkV2 { (float)current_image->width(), (float)current_image->height() }; + = SkV2 { (float)panorama_info->image->width(), (float)panorama_info->image->height() }; - // Set one time image + // Set image shader_builder->child("image") - = current_image->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); + = panorama_info->image->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); } -void InterfaceWindow::UpdateCamera(double pitch, double yaw, double hfov) { } \ No newline at end of file +Panorama& InterfaceWindow::GetClosestAdjacent() { + auto current_yaw = std::fmod(std::fmod(yaw + PI, 2 * PI) + 2 * PI, 2 * PI); + // std::cout << "Current: " << current_yaw / PI << std::endl; + Panorama closest_panorama; + double closest_difference = 1000; + for(auto& panorama : adjacent) { + // Get angle of this adjacent panorama + // Angle is between 0 and 2PI + auto angle = std::atan2(-(panorama.lng - current_panorama.lng), + panorama.lat - current_panorama.lat) + + PI; + + /* + double smallest_arc; + if (fabs(angle-current_yaw) < M_PI) smallest_arc = angle-current_yaw; if + (angle>current_yaw) return angle-current_yaw-M_PI*2.0f; return + angle-current_yaw+M_PI*2.0f; + */ + + // std::cout << "This: " << angle / PI << std::endl; + + // 1.3 + // -1.8 + // 0.2 + // -2.8 + + if(std::abs(angle - current_yaw) < closest_difference) { + closest_difference = angle - current_yaw; + closest_panorama = panorama; + } + } + return closest_panorama; +} \ No newline at end of file diff --git a/src/renderer.hpp b/src/interface.hpp similarity index 67% rename from src/renderer.hpp rename to src/interface.hpp index 098a798..fea6692 100644 --- a/src/renderer.hpp +++ b/src/interface.hpp @@ -8,15 +8,22 @@ #define GLFW_INCLUDE_GLCOREARB #include +#include +#include +#include + +#include "extract.hpp" +#include "preloader.hpp" + class InterfaceWindow { public: + InterfaceWindow(std::string initial_panorama_id, CURL* curl_handle); + bool PrepareWindow(); - void SetImage(sk_sp image) { - current_image = image; - } void DrawFrame(); + void ChangePanorama(std::string id); void PrepareShader(); - void UpdateCamera(double pitch, double yaw, double hfov); + Panorama& GetClosestAdjacent(); bool ShouldClose() { return glfwWindowShouldClose(window); @@ -39,11 +46,12 @@ class InterfaceWindow { private: void RenderPanorama(); + void RenderMap(); + // Render variables sk_sp surface; GLFWwindow* window; sk_sp direct_context; - sk_sp current_image; SkRuntimeShaderBuilder* shader_builder = nullptr; sk_sp shader_effect = nullptr; sk_sp spherical_filter; @@ -51,4 +59,13 @@ class InterfaceWindow { int width; int height; int frame = 0; + + // Panorama variables + CURL* curl_handle; + PanoramaPreloader preloader; + std::shared_ptr panorama_info; + Panorama current_panorama; + std::vector adjacent; + float yaw; + float pitch; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index f1be4ae..43c787f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,3 @@ -#define RAPIDJSON_HAS_STDSTRING 1 #define SK_ENCODE_PNG #include @@ -40,8 +39,8 @@ #include "download.hpp" #include "extract.hpp" #include "headers.hpp" +#include "interface.hpp" #include "parse.hpp" -#include "renderer.hpp" int main(int argc, char** argv) { CLI::App app { "Street View custom client in C++" }; @@ -62,18 +61,18 @@ int main(int argc, char** argv) { "increase resolution"); auto& render_sub = *app.add_subcommand("render", "Render panoramas"); - std::string filename; - render_sub.add_option("-f,--filename", filename, "Equirectangular image filename")->required(); + std::string initial_id; + render_sub.add_option("-i,--id", initial_id, "Initial panorama ID")->required(); CLI11_PARSE(app, argc, argv); + curl_global_init(CURL_GLOBAL_ALL); if(download_sub) { std::chrono::time_point start; std::chrono::time_point stop; start = std::chrono::high_resolution_clock::now(); - curl_global_init(CURL_GLOBAL_ALL); auto curl_handle = curl_easy_init(); auto client_id = download_client_id(curl_handle); @@ -88,26 +87,24 @@ int main(int argc, char** argv) { start = std::chrono::high_resolution_clock::now(); // Get photometa - auto photmeta_document = download_photometa(curl_handle, client_id, panorama_id); + auto photometa_document = download_photometa(curl_handle, client_id, panorama_id); // Get panorama auto tile_surface - = download_panorama(curl_handle, panorama_id, streetview_zoom, photmeta_document); + = download_panorama(curl_handle, panorama_id, streetview_zoom, photometa_document); std::string filename = fmt::format("tiles/stitched-{}.png", panorama_id); - // std::cout << "Location: " - // << extract_location(photometa_document).city_and_state + // std::cout << "Location: " << extract_location(photometa_document).city_and_state // << std::endl; - // for(auto& adjacent : extract_adjacent_panoramas(photometa_document)) - // { std::cout << "Adjacent: " << adjacent.id << " " << adjacent.lat - // << " " << adjacent.lng << std::endl; + // for(auto& adjacent : extract_adjacent_panoramas(photometa_document)) { + // std::cout << "Adjacent: " << adjacent.id << " " << adjacent.lat << " " + // << adjacent.lng << std::endl; // } std::filesystem::create_directory("tiles"); - auto tile_data - = tile_surface->makeImageSnapshot()->encodeToData(SkEncodedImageFormat::kPNG, 95); + auto tile_data = tile_surface->encodeToData(SkEncodedImageFormat::kPNG, 95); std::ofstream outfile(filename, std::ios::out | std::ios::binary); outfile.write((const char*)tile_data->bytes(), tile_data->size()); outfile.close(); @@ -120,13 +117,13 @@ int main(int argc, char** argv) { curl_easy_cleanup(curl_handle); curl_global_cleanup(); } else if(render_sub) { - InterfaceWindow window; + auto curl_handle = curl_easy_init(); + InterfaceWindow window(initial_id, curl_handle); window.PrepareWindow(); - window.SetImage(SkImage::MakeFromEncoded(SkData::MakeFromFileName(filename.c_str()))); - window.PrepareShader(); while(!window.ShouldClose()) { window.DrawFrame(); } + curl_easy_cleanup(curl_handle); } return 0; diff --git a/src/preloader.cpp b/src/preloader.cpp new file mode 100644 index 0000000..ae869f3 --- /dev/null +++ b/src/preloader.cpp @@ -0,0 +1,88 @@ +#include "preloader.hpp" + +#include +#include + +#include "download.hpp" + +void PanoramaPreloader::Start(int num_threads) { + // Create all the threads + for(int i = 0; i < num_threads; i++) { + threads.push_back(std::thread(&PanoramaPreloader::PanoramaThread, this)); + } +} + +void PanoramaPreloader::QueuePanorama(std::string id) { + std::scoped_lock lock { queued_panoramas_m }; + queued_panoramas.emplace_back(id); +} + +std::shared_ptr PanoramaPreloader::GetPanorama(std::string id, bool force) { + panoramas_m.lock(); + bool have_panorama = panoramas.count(id); + + if(have_panorama) { + auto info = panoramas[id]; + panoramas_m.unlock(); + return info; + } else { + panoramas_m.unlock(); + if(force) { + // Download regardless on the current thread + auto info = DownloadPanorama(id, curl_handle); + return info; + } else { + // Don't force download + return nullptr; + } + } +} + +void PanoramaPreloader::PanoramaThread() { + CURL* curl_handle = curl_easy_init(); + + while(run_threads) { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + queued_panoramas_m.lock(); + if(queued_panoramas.empty()) { + queued_panoramas_m.unlock(); + continue; + } + std::string id = queued_panoramas.front(); + queued_panoramas.pop_front(); + queued_panoramas_m.unlock(); + + panoramas_m.lock(); + if(panoramas.count(id)) { + // Ignore this panorama + panoramas_m.unlock(); + continue; + } + panoramas_m.unlock(); + + auto info = DownloadPanorama(id, curl_handle); + + panoramas_m.lock(); + panoramas[id] = info; + panoramas_m.unlock(); + } + + curl_easy_cleanup(curl_handle); +} + +std::shared_ptr PanoramaPreloader::DownloadPanorama( + std::string id, CURL* handle) { + std::scoped_lock lock { info_m }; + + // Get photometa + auto photmeta_document = download_photometa(handle, client_id, id); + + // Get panorama + auto image = download_panorama(handle, id, streetview_zoom, photmeta_document); + + auto info = std::make_shared(); + info->id = id; + info->image = image; + info->photometa.Swap(photmeta_document); + return info; +} \ No newline at end of file diff --git a/src/preloader.hpp b/src/preloader.hpp new file mode 100644 index 0000000..47136a9 --- /dev/null +++ b/src/preloader.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +struct PanoramaDownload { + sk_sp image; + std::string id; + rapidjson::Document photometa; +}; + +class PanoramaPreloader { +public: + void Start(int num_threads); + + void QueuePanorama(std::string id); + void SetZoom(int zoom) { + std::scoped_lock lock { info_m }; + streetview_zoom = zoom; + } + void SetClientId(std::string id) { + std::scoped_lock lock { info_m }; + client_id = id; + } + void SetCurlHandle(CURL* handle) { + curl_handle = handle; + } + std::shared_ptr GetPanorama(std::string id, bool force); + +private: + void PanoramaThread(); + std::shared_ptr DownloadPanorama(std::string id, CURL* handle); + + CURL* curl_handle; + + std::deque queued_panoramas; + std::mutex queued_panoramas_m; + std::unordered_map> panoramas; + std::mutex panoramas_m; + + bool run_threads = true; + std::vector threads; + + int streetview_zoom = 2; + std::string client_id; + std::mutex info_m; +}; \ No newline at end of file diff --git a/third_party/libui b/third_party/libui deleted file mode 160000 index fea45b2..0000000 --- a/third_party/libui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit fea45b2d5b75839be0af9acc842a147c5cba9295