├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── genie.lua └── src ├── editor ├── maps_plugins.cpp └── pugixml │ ├── pugiconfig.hpp │ ├── pugixml.cpp │ └── pugixml.hpp └── maps.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | 3 | AlignAfterOpenBracket : false 4 | AlignEscapedNewlinesLeft : true 5 | AlignConsecutiveAssignments : false 6 | AllowAllParametersOfDeclarationOnNextLine : false 7 | AccessModifierOffset : -4 8 | AllowShortCaseLabelsOnASingleLine : true 9 | AllowShortFunctionsOnASingleLine : Inline 10 | AllowShortIfStatementsOnASingleLine : true 11 | AllowShortLoopsOnASingleLine : true 12 | AlwaysBreakAfterDefinitionReturnType : None 13 | BinPackArguments : false 14 | BinPackParameters : false 15 | BreakBeforeBraces : Allman 16 | BreakConstructorInitializersBeforeComma : true 17 | ColumnLimit : 120 18 | ConstructorInitializerIndentWidth : 4 19 | ConstructorInitializerAllOnOneLineOrOnePerLine : false 20 | ContinuationIndentWidth : 4 21 | IndentCaseLabels : true 22 | IndentWidth : 4 23 | KeepEmptyLinesAtTheStartOfBlocks : true 24 | MaxEmptyLinesToKeep : 2 25 | NamespaceIndentation : None 26 | PenaltyBreakBeforeFirstCallParameter : 0 27 | PenaltyReturnTypeOnItsOwnLine : 1000 28 | PointerAlignment : Left 29 | SpaceAfterCStyleCast : false 30 | SpaceBeforeAssignmentOperators : true 31 | SpaceBeforeParens : true 32 | SpaceInEmptyParentheses : false 33 | SpacesBeforeTrailingComments : 1 34 | SpacesInAngles : false 35 | SpacesInCStyleCastParentheses : false 36 | SpacesInParentheses : false 37 | SpacesInSquareBrackets : false 38 | Standard : Cpp11 39 | TabWidth : 4 40 | UseTab : true 41 | 42 | 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | *.obj 6 | 7 | # Precompiled Headers 8 | *.gch 9 | *.pch 10 | 11 | # Compiled Dynamic libraries 12 | *.so 13 | *.dylib 14 | *.dll 15 | 16 | # Fortran module files 17 | *.mod 18 | *.smod 19 | 20 | # Compiled Static libraries 21 | *.lai 22 | *.la 23 | *.a 24 | *.lib 25 | 26 | # Executables 27 | *.exe 28 | *.out 29 | *.app 30 | 31 | tmp/ 32 | projects/tmp 33 | !projects/genie.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mikulas Florek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maps plugin for [Lumix Engine](https://github.com/nem0/LumixEngine) 2 | 3 | [![Discord Chat](https://img.shields.io/discord/480318777943392266.svg)](https://discord.gg/RgFybs6) 4 | [![License](http://img.shields.io/:license-mit-blue.svg)](http://doge.mit-license.org) 5 | [![Twitter](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/mikulasflorek) 6 | 7 | ![Screenshot](https://user-images.githubusercontent.com/153526/118119303-72877380-b3ee-11eb-80d2-da3288b0cdd6.png) 8 | 9 | [Getting started](https://www.youtube.com/watch?v=SaZPVgnGyKo) 10 | 11 | You can use this plugin to easily create real-world terrains in Lumix Engine. It downloads height maps from [Amazon](https://aws.amazon.com/public-datasets/terrain/) and satellite maps from [EOX](https://maps.eox.at/). It can read [OSM](https://www.openstreetmap.org/#map=14/48.6627/17.9527) data and use them to place roads, buildings and other objects. Read their licenses to find out what can you use this data for. 12 | 13 | You can use [itch.io](https://mikulasflorek.itch.io/lumix-engine) to download the engine with maps plugin already installed. It's used to develop [On the hunt](https://mikulasflorek.itch.io/on-the-hunt) game. 14 | 15 | [How to download and open OSM data](https://www.youtube.com/watch?v=8FMh-N8l_cA) 16 | -------------------------------------------------------------------------------- /genie.lua: -------------------------------------------------------------------------------- 1 | project "maps" 2 | libType() 3 | files { 4 | "src/**.c", 5 | "src/**.cpp", 6 | "src/**.h", 7 | "genie.lua" 8 | } 9 | defines { "BUILDING_MAPS" } 10 | links { "engine", "core", "renderer" } 11 | if build_studio then 12 | links { "editor" } 13 | end 14 | defaultConfigurations() 15 | 16 | linkPlugin("maps") -------------------------------------------------------------------------------- /src/editor/maps_plugins.cpp: -------------------------------------------------------------------------------- 1 | #define LUMIX_NO_CUSTOM_CRT 2 | #include 3 | #include "core/atomic.h" 4 | #include "core/geometry.h" 5 | #include "core/hash_map.h" 6 | #include "core/log.h" 7 | #include "core/math.h" 8 | #include "core/os.h" 9 | #include "core/path.h" 10 | #include "core/profiler.h" 11 | #include "core/stack_array.h" 12 | #include "core/sync.h" 13 | #include "core/thread.h" 14 | #include "editor/asset_browser.h" 15 | #include "editor/prefab_system.h" 16 | #include "editor/render_interface.h" 17 | #include "editor/spline_editor.h" 18 | #include "editor/studio_app.h" 19 | #include "editor/utils.h" 20 | #include "editor/world_editor.h" 21 | #include "editor/settings.h" 22 | #include "engine/core.h" 23 | #include "engine/engine.h" 24 | #include "engine/prefab.h" 25 | #include "engine/reflection.h" 26 | #include "engine/resource_manager.h" 27 | #include "imgui/imgui.h" 28 | #include "renderer/editor/composite_texture.h" 29 | #include "renderer/editor/terrain_editor.h" 30 | #include "renderer/material.h" 31 | #include "renderer/model.h" 32 | #include "renderer/render_module.h" 33 | #include "renderer/terrain.h" 34 | #include "renderer/texture.h" 35 | #ifndef STATIC_PLUGINS 36 | #define STB_IMAGE_IMPLEMENTATION 37 | #endif 38 | #include "stb/stb_image.h" 39 | #include "pugixml/pugixml.hpp" 40 | 41 | #ifdef _WIN32 42 | #define NOGDI 43 | #define WIN32_LEAN_AND_MEAN 44 | #include 45 | #include 46 | #include 47 | #pragma comment(lib, "urlmon.lib") 48 | #endif 49 | 50 | #include 51 | #include 52 | #pragma warning(disable : 4996) 53 | 54 | using namespace Lumix; 55 | 56 | namespace 57 | { 58 | 59 | bool download(const char* url, OutputMemoryStream& blob, u32& downloaded_bytes) { 60 | IStream* stream = nullptr; 61 | if (S_OK != URLOpenBlockingStream(nullptr, url, &stream, 0, nullptr)) { 62 | return false; 63 | } 64 | char buffer[4096]; 65 | ULONG read = 0; 66 | HRESULT hr; 67 | downloaded_bytes = 0; 68 | do { 69 | DWORD bytesRead = 0; 70 | hr = stream->Read(buffer, sizeof(buffer), &bytesRead); 71 | 72 | if (bytesRead > 0) 73 | { 74 | downloaded_bytes += bytesRead; 75 | blob.write(buffer, bytesRead); 76 | } 77 | } while (SUCCEEDED(hr) && hr != S_FALSE); 78 | 79 | return true; 80 | } 81 | 82 | static const ComponentType SPLINE_TYPE = reflection::getComponentType("spline"); 83 | static const ComponentType MODEL_INSTANCE_TYPE = reflection::getComponentType("model_instance"); 84 | static const ComponentType TERRAIN_TYPE = reflection::getComponentType("terrain"); 85 | static const ComponentType INSTANCED_MODEL_TYPE = reflection::getComponentType("instanced_model"); 86 | static const ComponentType CURVE_DECAL_TYPE = reflection::getComponentType("curve_decal"); 87 | 88 | enum class NodeType : u32 { 89 | MASK_POLYGONS, 90 | MASK_POLYLINES, 91 | PAINT_GROUND, 92 | INVERT, 93 | GRASS, 94 | NOISE, 95 | MASK_TEXTURE, 96 | MERGE_MASKS, 97 | MERGE_DISTANCE_FIELDS, 98 | DISTANCE_FIELD, 99 | PLACE_INSTANCES, 100 | ADJUST_HEIGHT, 101 | PLACE_SPLINES, 102 | FLATTEN_POLYLINES, 103 | MASK_DISTANCE 104 | }; 105 | 106 | static const struct { 107 | char key; 108 | const char* label; 109 | NodeType type; 110 | } TYPES[] = { 111 | { 'H', "Adjust height", NodeType::ADJUST_HEIGHT }, 112 | { 'D', "Distance field", NodeType::DISTANCE_FIELD }, 113 | { 'F', "Flatten polylines", NodeType::FLATTEN_POLYLINES }, 114 | { 'R', "Grass", NodeType::GRASS }, 115 | { 'I', "Invert", NodeType::INVERT }, 116 | { 0, "Mask distance", NodeType::MASK_DISTANCE }, 117 | { 'P', "Mask polygons", NodeType::MASK_POLYGONS }, 118 | { 'L', "Mask polylines", NodeType::MASK_POLYLINES }, 119 | { 0, "Mask texture", NodeType::MASK_TEXTURE }, 120 | { 'M', "Merge distance ", NodeType::MERGE_DISTANCE_FIELDS }, 121 | { 0, "Merge masks", NodeType::MERGE_MASKS }, 122 | { 'N', "Noise", NodeType::NOISE }, 123 | { 'G', "Paint ground", NodeType::PAINT_GROUND }, 124 | { 0, "Place instances", NodeType::PLACE_INSTANCES }, 125 | { 'S', "Place splines", NodeType::PLACE_SPLINES }, 126 | }; 127 | 128 | static stbi_uc* loadTexture(const char* texture, u32& mask_w, u32& mask_h, StudioApp& app) { 129 | FileSystem& fs = app.getEngine().getFileSystem(); 130 | OutputMemoryStream blob(app.getAllocator()); 131 | if (!fs.getContentSync(Path(texture), blob)) { 132 | logError("Failed to read ", texture); 133 | return nullptr; 134 | } 135 | 136 | int w, h, ch; 137 | stbi_uc* rgba = stbi_load_from_memory(blob.data(), (int)blob.size(), &w, &h, &ch, 1); 138 | if (!rgba) { 139 | logError("Failed to parse ", texture); 140 | return nullptr; 141 | } 142 | mask_w = w; 143 | mask_h = h; 144 | return rgba; 145 | } 146 | 147 | template 148 | static u32 getRandomItem(float distance, const Array& probs) { 149 | float sum = 0; 150 | 151 | auto get = [](float distance, const T& prob){ 152 | if (distance < prob.distances.x) return 0.f; 153 | if (distance > prob.distances.w) return 0.f; 154 | 155 | if (distance < prob.distances.y) { 156 | return prob.multiplier * (distance - prob.distances.x) / (prob.distances.y - prob.distances.x); 157 | } 158 | else if (distance < prob.distances.z) { 159 | return prob.multiplier; 160 | } 161 | return prob.multiplier * (1 - (distance - prob.distances.z) / (prob.distances.w - prob.distances.z)); 162 | }; 163 | 164 | for (const T& prob : probs) { 165 | sum += get(distance, prob); 166 | } 167 | if (sum == 0) return 0; 168 | 169 | float r = randFloat() * sum; 170 | 171 | for (i32 i = 0; i < probs.size(); ++i) { 172 | const T& prob = probs[i]; 173 | float p = get(distance, prob); 174 | if (r < p) return i; 175 | r -= p; 176 | } 177 | 178 | ASSERT(false); 179 | return 0; 180 | } 181 | 182 | static bool tagInput(Span key, Span value, Span values, float width) { 183 | ImGui::TextUnformatted("Tag"); ImGui::SameLine(); 184 | const float w = (width - 20) * 0.5f; 185 | ImGui::SetNextItemWidth(w); 186 | bool res = ImGui::InputText("##tag_key", key.begin(), key.length()); 187 | ImGui::SameLine(); 188 | ImGui::SetNextItemWidth(w); 189 | res = ImGui::InputText("##tag_value", value.begin(), value.length()) || res; 190 | ImGui::SameLine(); 191 | if (ImGuiEx::IconButton(ICON_FA_ELLIPSIS_H, "Common values")) { 192 | ImGui::OpenPopup("tag_list"); 193 | } 194 | if (ImGui::BeginPopup("tag_list")) { 195 | for (const char* tag : values) { 196 | const char* tag2 = tag + stringLength(tag) + 1; 197 | if (ImGui::Selectable(tag[0] ? tag : tag2)) { 198 | copyString(value, tag); 199 | copyString(key, tag2); 200 | res = true; 201 | } 202 | } 203 | ImGui::EndPopup(); 204 | } 205 | return res; 206 | } 207 | 208 | static bool tagInput(Span key, Span value, float width) { 209 | const char* values[] = { 210 | "\0landuse", 211 | "forest\0landuse", 212 | "farmland\0landuse", 213 | "farmyard\0landuse", 214 | "meadow\0landuse", 215 | "residential\0landuse", 216 | "industrial\0landuse", 217 | "cemetery\0landuse", 218 | "reservoir\0landuse", 219 | "water\0natural", 220 | "\0building", 221 | "\0man_made", 222 | "\0natural", 223 | "\0leisure", 224 | "\0barrier", 225 | "\0tourism", 226 | "\0amenity", 227 | "\0highway", 228 | "footway\0highway", 229 | "track\0highway", 230 | "path\0highway", 231 | "tree_row\0natural", 232 | "stream\0waterway", 233 | }; 234 | return tagInput(key, value, Span(values), width); 235 | } 236 | 237 | enum class OutputType { 238 | MASK, 239 | DISTANCE_FIELD 240 | }; 241 | 242 | struct OutputValue { 243 | virtual ~OutputValue() {} 244 | virtual OutputType getType() = 0; 245 | }; 246 | 247 | struct DistanceFieldOutput : OutputValue { 248 | DistanceFieldOutput(IAllocator& allocator) : m_field(allocator) {} 249 | OutputType getType() override { return OutputType::DISTANCE_FIELD; } 250 | 251 | float sample(float u, float v) const { 252 | const float x = u * (m_size - 1); 253 | const float y = v * (m_size - 1); 254 | 255 | const i32 i = i32(x); 256 | const i32 j = i32(y); 257 | 258 | i32 idx = i + j * m_size; 259 | float rx = x - i; 260 | float ry = y - j; 261 | 262 | auto get = [this](i32 i, i32 j){ 263 | i32 idx = clamp(i, 0, m_size - 1) + clamp(j, 0, m_size - 1) * m_size; 264 | return m_field[idx]; 265 | }; 266 | 267 | const float v00 = get(i, j); 268 | const float v10 = get(i + 1, j); 269 | const float v01 = get(i, j + 1); 270 | const float v11 = get(i + 1, j + 1); 271 | 272 | return lerp( 273 | lerp(v00, v01, rx), 274 | lerp(v01, v11, rx), 275 | ry 276 | ); 277 | } 278 | 279 | u32 m_size; 280 | Array m_field; 281 | }; 282 | 283 | struct MaskOutput : OutputValue { 284 | MaskOutput(IAllocator& allocator) : m_bitmap(allocator) {} 285 | OutputType getType() override { return OutputType::MASK; } 286 | 287 | u32 m_size; 288 | Array m_bitmap; 289 | }; 290 | 291 | using Polygon = Array; 292 | using Polygon2D = Array; 293 | 294 | struct Multipolygon { 295 | Multipolygon(IAllocator& allocator) 296 | : outer_polygons(allocator) 297 | , inner_polygons(allocator) 298 | {} 299 | 300 | Array outer_polygons; 301 | Array inner_polygons; 302 | }; 303 | 304 | struct Multipolygon2D { 305 | Multipolygon2D(IAllocator& allocator) 306 | : outer_polygons(allocator) 307 | , inner_polygons(allocator) 308 | {} 309 | 310 | Array outer_polygons; 311 | Array inner_polygons; 312 | }; 313 | 314 | struct OSMParser { 315 | static bool samePoint(const DVec3& a, const DVec3& b) { 316 | if (abs(a.x - b.x) > 1e-5) return false; 317 | if (abs(a.y - b.y) > 1e-5) return false; 318 | if (abs(a.z - b.z) > 1e-5) return false; 319 | 320 | return true; 321 | } 322 | 323 | static bool samePoint(const DVec2& a, const DVec2& b) { 324 | if (abs(a.x - b.x) > 1e-5) return false; 325 | if (abs(a.y - b.y) > 1e-5) return false; 326 | 327 | return true; 328 | } 329 | 330 | static void mergePolylines(Array& polylines, Polygon& merged) { 331 | merged = polylines.back().move(); 332 | polylines.pop(); 333 | while (!polylines.empty()) { 334 | const DVec3 end = merged.back(); 335 | bool found = false; 336 | for (Polygon& i : polylines) { 337 | if (samePoint(i[0], end)) { 338 | for (int j = 1; j < i.size(); ++j) { 339 | merged.push(i[j]); 340 | } 341 | polylines.erase(u32(&i - polylines.begin())); 342 | found = true; 343 | break; 344 | } 345 | else if (samePoint(i.back(), end)) { 346 | for (int j = i.size() - 2; j >= 0; --j) { 347 | merged.push(i[j]); 348 | } 349 | polylines.erase(u32(&i - polylines.begin())); 350 | found = true; 351 | break; 352 | } 353 | } 354 | if (!found) return; 355 | } 356 | } 357 | 358 | static void mergePolylines(Array& polylines, Polygon2D& merged) { 359 | merged = polylines.back().move(); 360 | polylines.pop(); 361 | while (!polylines.empty()) { 362 | const DVec2 end = merged.back(); 363 | bool found = false; 364 | for (Polygon2D& i : polylines) { 365 | if (samePoint(i[0], end)) { 366 | for (int j = 1; j < i.size(); ++j) { 367 | merged.push(i[j]); 368 | } 369 | polylines.erase(u32(&i - polylines.begin())); 370 | found = true; 371 | break; 372 | } 373 | else if (samePoint(i.back(), end)) { 374 | for (int j = i.size() - 2; j >= 0; --j) { 375 | merged.push(i[j]); 376 | } 377 | polylines.erase(u32(&i - polylines.begin())); 378 | found = true; 379 | break; 380 | } 381 | } 382 | if (!found) return; 383 | } 384 | } 385 | 386 | static bool hasAttributeValue(pugi::xml_node n, const char* key, const char* value) { 387 | pugi::xml_attribute attr = n.attribute(key); 388 | if (attr.empty()) return false; 389 | 390 | const char* str = attr.value(); 391 | return equalStrings(str, value); 392 | } 393 | 394 | template 395 | static bool getAttributeValue(pugi::xml_node n, const char* key, T& out) { 396 | pugi::xml_attribute attr = n.attribute(key); 397 | if (attr.empty()) return false; 398 | const char* str = attr.value(); 399 | fromCString(str, out); 400 | return true; 401 | } 402 | 403 | static Color randomColor() { 404 | // HSV to RGB with S=1,V=1,H=random 405 | const i32 H = rand(0, 360); 406 | const u8 X = u8(clamp(255 * (1 - abs(fmodf(H / 60.0f, 2) - 1)), 0.f, 255.f) + 0.5); 407 | 408 | Color res(0); 409 | res.a = 0xff; 410 | 411 | if(H >= 0 && H < 60) { 412 | res.r = 255; 413 | res.g = X; 414 | } 415 | else if(H >= 60 && H < 120) { 416 | res.r = X; 417 | res.g = 255; 418 | } 419 | else if(H >= 120 && H < 180) { 420 | res.g = 255; 421 | res.b = X; 422 | } 423 | else if(H >= 180 && H < 240) { 424 | res.g = X; 425 | res.b = 255; 426 | } 427 | else if(H >= 240 && H < 300) { 428 | res.r = X; 429 | res.b = 255; 430 | } 431 | else { 432 | res.r = 255; 433 | res.b = X; 434 | } 435 | return res; 436 | } 437 | 438 | OSMParser(StudioApp& app) 439 | : m_app(app) 440 | , m_nodes(m_app.getAllocator()) 441 | , m_ways(m_app.getAllocator()) 442 | , m_relations(m_app.getAllocator()) 443 | { 444 | } 445 | 446 | bool getLatLon(pugi::xml_node nd_ref, DVec2& p) const { 447 | if (nd_ref.empty() || !equalStrings(nd_ref.name(), "nd")) return false; 448 | 449 | pugi::xml_attribute ref_attr = nd_ref.attribute("ref"); 450 | if (ref_attr.empty()) return false; 451 | const char* ref_str = ref_attr.value(); 452 | u64 node_id; 453 | fromCString(ref_str, node_id); 454 | 455 | auto iter = m_nodes.find(node_id); 456 | if (!iter.isValid()) return false; 457 | 458 | pugi::xml_node n = iter.value(); 459 | 460 | pugi::xml_attribute lat_attr = n.attribute("lat"); 461 | pugi::xml_attribute lon_attr = n.attribute("lon"); 462 | 463 | if (lat_attr.empty() || lon_attr.empty()) return false; 464 | 465 | const double lat = atof(lat_attr.value()); 466 | const double lon = atof(lon_attr.value()); 467 | 468 | p = {lat, lon}; 469 | return true; 470 | } 471 | 472 | template 473 | void forEachWayInRelation(pugi::xml_node relation, F&& f) const { 474 | for (pugi::xml_node n = relation.first_child(); !n.empty(); n = n.next_sibling()) { 475 | if (!equalStrings(n.name(), "member")) continue; 476 | if (!hasAttributeValue(n, "type", "way")) continue; 477 | 478 | u64 ref; 479 | if (!getAttributeValue(n, "ref", ref)) continue; 480 | 481 | auto iter = m_ways.find(ref); 482 | if (!iter.isValid()) continue; 483 | 484 | pugi::xml_attribute role_attr = n.attribute("role"); 485 | const char* role = role_attr.empty() ? "" : role_attr.value(); 486 | 487 | f(iter.value(), role); 488 | } 489 | } 490 | 491 | void getMultipolygon(pugi::xml_node relation, Multipolygon& multipolygon, EntityRef terrain) { 492 | Array polylines(m_app.getAllocator()); 493 | forEachWayInRelation(relation, [&](const pugi::xml_node& w, const char* role){ 494 | if (!equalStrings(role, "outer")) return; 495 | 496 | Polygon& polygon = polylines.emplace(m_app.getAllocator()); 497 | getWay(w, terrain, polygon); 498 | }); 499 | 500 | multipolygon.outer_polygons.clear(); 501 | while(!polylines.empty()) { 502 | mergePolylines(polylines, multipolygon.outer_polygons.emplace(m_app.getAllocator())); 503 | } 504 | 505 | polylines.clear(); 506 | forEachWayInRelation(relation, [&](const pugi::xml_node& w, const char* role){ 507 | if (!equalStrings(role, "inner")) return; 508 | 509 | Polygon& polygon = polylines.emplace(m_app.getAllocator()); 510 | getWay(w, terrain, polygon); 511 | }); 512 | 513 | multipolygon.inner_polygons.clear(); 514 | while(!polylines.empty()) { 515 | mergePolylines(polylines, multipolygon.inner_polygons.emplace(m_app.getAllocator())); 516 | } 517 | } 518 | 519 | void getMultipolygon(pugi::xml_node relation, Multipolygon2D& multipolygon) { 520 | Array polylines(m_app.getAllocator()); 521 | forEachWayInRelation(relation, [&](const pugi::xml_node& w, const char* role){ 522 | if (!equalStrings(role, "outer")) return; 523 | 524 | Polygon2D& polygon = polylines.emplace(m_app.getAllocator()); 525 | getWay(w, polygon); 526 | }); 527 | 528 | multipolygon.outer_polygons.clear(); 529 | while(!polylines.empty()) { 530 | Polygon2D& merged = multipolygon.outer_polygons.emplace(m_app.getAllocator()); 531 | mergePolylines(polylines, merged); 532 | if (!samePoint(merged[0], merged.back())) { 533 | merged.push(merged[0]); 534 | } 535 | } 536 | 537 | polylines.clear(); 538 | forEachWayInRelation(relation, [&](const pugi::xml_node& w, const char* role){ 539 | if (!equalStrings(role, "inner")) return; 540 | 541 | Polygon2D& polygon = polylines.emplace(m_app.getAllocator()); 542 | getWay(w, polygon); 543 | }); 544 | 545 | multipolygon.inner_polygons.clear(); 546 | while(!polylines.empty()) { 547 | mergePolylines(polylines, multipolygon.inner_polygons.emplace(m_app.getAllocator())); 548 | } 549 | } 550 | 551 | pugi::xml_node getNode(const pugi::xml_node& nd_ref) { 552 | if (nd_ref.empty() || !equalStrings(nd_ref.name(), "nd")) return pugi::xml_node(); 553 | 554 | pugi::xml_attribute ref_attr = nd_ref.attribute("ref"); 555 | if (ref_attr.empty()) return pugi::xml_node(); 556 | const char* ref_str = ref_attr.value(); 557 | u64 node_id; 558 | fromCString(ref_str, node_id); 559 | 560 | auto iter = m_nodes.find(node_id); 561 | if (!iter.isValid()) return pugi::xml_node(); 562 | 563 | return iter.value(); 564 | } 565 | 566 | static bool hasTag(const pugi::xml_node& w, const char* tag, const char* value = "") { 567 | pugi::xml_node building_tag = w.find_child([&](const pugi::xml_node& n){ 568 | if (!equalStrings(n.name(), "tag")) return false; 569 | pugi::xml_attribute key_attr = n.attribute("k"); 570 | if (key_attr.empty()) return false; 571 | if (equalStrings(key_attr.value(), tag)) { 572 | if (!value[0]) return true; 573 | 574 | pugi::xml_attribute value_attr = n.attribute("v"); 575 | if (value_attr.empty()) return false; 576 | 577 | return equalStrings(value_attr.value(), value); 578 | } 579 | return false; 580 | }); 581 | return !building_tag.empty(); 582 | } 583 | 584 | void getWay(const pugi::xml_node& way, EntityRef terrain, Array& out) const { 585 | WorldEditor& editor = m_app.getWorldEditor(); 586 | World* world = editor.getWorld(); 587 | RenderModule* module = (RenderModule*)world->getModule(TERRAIN_TYPE); 588 | const double y_base = world->getPosition(terrain).y; 589 | 590 | for (pugi::xml_node& c : way.children()) { 591 | if (equalStrings(c.name(), "nd")) { 592 | DVec2 lat_lon; 593 | getLatLon(c, lat_lon); 594 | DVec3 p; 595 | p.x = (lat_lon.y - m_min_lon) / m_lon_range * m_scale; 596 | p.z = (m_min_lat + m_lat_range - lat_lon.x) / m_lat_range * m_scale; 597 | p.y = module->getTerrainHeightAt(terrain, (float)p.x, (float)p.z) + y_base; 598 | p.x -= m_scale * 0.5f; 599 | p.z -= m_scale * 0.5f; 600 | out.push(p); 601 | } 602 | } 603 | } 604 | 605 | void getWay(const pugi::xml_node& way, Array& out) const { 606 | for (pugi::xml_node& c : way.children()) { 607 | if (equalStrings(c.name(), "nd")) { 608 | DVec2 lat_lon; 609 | getLatLon(c, lat_lon); 610 | DVec2 p; 611 | p.x = (lat_lon.y - m_min_lon) / m_lon_range * m_scale - m_scale * 0.5f; 612 | p.y = (m_min_lat + m_lat_range - lat_lon.x) / m_lat_range * m_scale - m_scale * 0.5f; 613 | out.push(p); 614 | } 615 | } 616 | } 617 | 618 | void createPolyline(const Array& points, u32 color, Array& out) { 619 | if (points.empty()) return; 620 | 621 | const float half_extents = m_scale * 0.5f; 622 | for(i32 i = 0; i < points.size() - 1; ++i) { 623 | Vec3 a = Vec3(points[i]) + Vec3(0, 1, 0); 624 | Vec3 b = Vec3(points[i + 1]) + Vec3(0, 1, 0); 625 | 626 | if (squaredLength(a-b) < 0.01f) continue; 627 | 628 | Vec3 norm = normalize(cross(a - b, Vec3(0, 1, 0))); 629 | out.push({a - norm * 2, color}); 630 | out.push({b - norm * 2, color}); 631 | out.push({b + norm * 2, color}); 632 | 633 | out.push({a - norm * 2, color}); 634 | out.push({b + norm * 2, color}); 635 | out.push({a + norm * 2, color}); 636 | } 637 | } 638 | 639 | [[nodiscard]] bool parseOSM(double left, double bottom, double right, double top, float scale, const OutputMemoryStream& data) { 640 | m_nodes.clear(); 641 | m_ways.clear(); 642 | m_relations.clear(); 643 | 644 | const pugi::xml_parse_result res = m_doc.load_buffer(data.data(), data.size()); 645 | if (pugi::status_ok != res.status) return false; 646 | 647 | pugi::xml_node osm_root = m_doc.root().first_child(); 648 | 649 | m_min_lon = left; 650 | m_min_lat = bottom; 651 | m_lat_range = top - bottom; 652 | m_lon_range = right - left; 653 | m_scale = scale; 654 | 655 | for (pugi::xml_node n = osm_root.first_child(); !n.empty(); n = n.next_sibling()) { 656 | if (equalStrings(n.name(), "node")) { 657 | pugi::xml_attribute id_attr = n.attribute("id"); 658 | if (id_attr.empty()) continue; 659 | 660 | const char* id_str = id_attr.value(); 661 | u64 id; 662 | fromCString(id_str, id); 663 | m_nodes.insert(id, n); 664 | } 665 | else if (equalStrings(n.name(), "way")) { 666 | pugi::xml_attribute id_attr = n.attribute("id"); 667 | if (id_attr.empty()) continue; 668 | 669 | const char* id_str = id_attr.value(); 670 | u64 id; 671 | fromCString(id_str, id); 672 | m_ways.insert(id, n); 673 | } 674 | else if (equalStrings(n.name(), "relation")) { 675 | pugi::xml_attribute id_attr = n.attribute("id"); 676 | if (id_attr.empty()) continue; 677 | 678 | const char* id_str = id_attr.value(); 679 | u64 id; 680 | fromCString(id_str, id); 681 | m_relations.insert(id, n); 682 | } 683 | } 684 | 685 | return true; 686 | } 687 | 688 | StudioApp& m_app; 689 | pugi::xml_document m_doc; 690 | HashMap m_nodes; 691 | HashMap m_ways; 692 | HashMap m_relations; 693 | double m_min_lat = 0; 694 | double m_min_lon = 0; 695 | double m_lat_range = 0.5f; 696 | double m_lon_range = 0.5f; 697 | float m_scale = 1; 698 | }; 699 | 700 | static double long2tilex(double long lon, int z) { 701 | return (lon + 180) * (1 << z) / 360.0; 702 | } 703 | 704 | static double tilex2long(double x, int z) { 705 | return x / pow(2.0, z) * 360.0 - 180; 706 | } 707 | 708 | static double lat2tiley(double lat, int z) { 709 | const double latrad = lat * PI / 180.0; 710 | return (1.0 - asinh(tan(latrad)) / PI) / 2.0 * (1 << z); 711 | } 712 | 713 | static double tiley2lat(double y, int z) { 714 | double n = PI - 2.0 * PI * y / pow(2.0, z); 715 | return 180.0 / PI * atan(0.5 * (exp(n) - exp(-n))); 716 | } 717 | 718 | struct OSMNodeEditor : NodeEditor { 719 | struct Node : NodeEditorNode { 720 | Node(OSMNodeEditor& editor) 721 | : m_editor(editor) 722 | , m_error(editor.m_allocator) 723 | {} 724 | 725 | virtual NodeType getType() const = 0; 726 | virtual void serialize(OutputMemoryStream& blob) = 0; 727 | virtual void deserialize(InputMemoryStream& blob) = 0; 728 | virtual bool gui() = 0; 729 | virtual UniquePtr getOutputValue(u16 output_idx) = 0; 730 | 731 | bool nodeGUI() override { 732 | ImGuiEx::BeginNode(m_id, m_pos, &m_selected); 733 | m_input_counter = 0; 734 | m_output_counter = 0; 735 | bool res = gui(); 736 | 737 | if (m_error.length() > 0) { 738 | ImGui::PushStyleColor(ImGuiCol_Border, IM_COL32(0xff, 0, 0, 0xff)); 739 | } 740 | ImGuiEx::EndNode(); 741 | if (m_error.length() > 0) { 742 | ImGui::PopStyleColor(); 743 | if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", m_error.c_str()); 744 | } 745 | 746 | return res; 747 | } 748 | 749 | ImGuiEx::PinShape toShape(OutputType type) { 750 | switch (type) { 751 | case OutputType::MASK: return ImGuiEx::PinShape::CIRCLE; 752 | case OutputType::DISTANCE_FIELD: return ImGuiEx::PinShape::SQUARE; 753 | } 754 | return ImGuiEx::PinShape::CIRCLE; 755 | } 756 | 757 | void inputSlot(OutputType type = OutputType::MASK) { 758 | ImGuiEx::Pin(m_id | ((u32)m_input_counter << 16), true, toShape(type)); 759 | ++m_input_counter; 760 | } 761 | 762 | void outputSlot(OutputType type = OutputType::MASK) { 763 | ImGuiEx::Pin(m_id | ((u32)m_output_counter << 16) | OUTPUT_FLAG, false, toShape(type)); 764 | ++m_output_counter; 765 | } 766 | 767 | struct Input { 768 | Node* node = nullptr; 769 | u16 output_idx; 770 | operator bool() const { return node; } 771 | }; 772 | 773 | void nodeTitle(const char* label) { 774 | ImGuiEx::BeginNodeTitleBar(); 775 | ImGui::TextUnformatted(label); 776 | previewButton(); 777 | ImGuiEx::EndNodeTitleBar(); 778 | } 779 | 780 | UniquePtr outputError(const char* msg) { m_error = msg; return {}; } 781 | void error(const char* msg) { m_error = msg; } 782 | 783 | template 784 | bool textureMaskInput(StaticString& texture) { 785 | char basename[MAX_PATH]; 786 | copyString(Span(basename), Path::getBasename(texture)); 787 | bool res = false; 788 | if (ImGui::Button(basename[0] ? basename : "Click to select", ImVec2(150, 0))) m_show_mask_open = true; 789 | ImGui::SameLine(); 790 | if (ImGuiEx::IconButton(ICON_FA_TIMES, "Clear")) { 791 | texture = ""; 792 | return true; 793 | } 794 | FileSelector& fs = m_editor.m_app.getFileSelector(); 795 | if (fs.gui("Select mask texture", &m_show_mask_open, "tga", false)) { 796 | texture = fs.getPath(); 797 | res = true; 798 | } 799 | return false; 800 | } 801 | 802 | void clearErrors() { 803 | for (Node* n : m_editor.m_nodes) { 804 | n->m_error = ""; 805 | } 806 | } 807 | 808 | void previewButton() { 809 | ImGui::SameLine(); 810 | if (ImGui::Button(ICON_FA_SEARCH) && ensureOSMData()) { 811 | clearErrors(); 812 | 813 | UniquePtr out = getOutputValue(0); 814 | if (out) { 815 | switch(out->getType()) { 816 | case OutputType::MASK: m_editor.visualize((MaskOutput*)out.get()); break; 817 | case OutputType::DISTANCE_FIELD: m_editor.visualize((DistanceFieldOutput*)out.get()); break; 818 | } 819 | } 820 | } 821 | } 822 | 823 | bool ensureOSMData() { 824 | if (m_editor.m_osm_parser.m_nodes.size() == 0 && m_editor.m_osm_parser.m_ways.size() == 0) { 825 | m_editor.m_show_osm_download_dialog = true; 826 | return false; 827 | } 828 | return true; 829 | } 830 | 831 | UniquePtr getInput(u16 input_idx); 832 | 833 | OSMNodeEditor& m_editor; 834 | String m_error; 835 | u32 m_input_counter; 836 | u32 m_output_counter; 837 | bool m_selected = false; 838 | bool m_show_mask_open = false; 839 | }; 840 | 841 | OSMNodeEditor(struct MapsPlugin& plugin, StudioApp& app) 842 | : NodeEditor(app.getAllocator()) 843 | , m_app(app) 844 | , m_allocator(app.getAllocator()) 845 | , m_links(app.getAllocator()) 846 | , m_nodes(app.getAllocator()) 847 | , m_recent_paths(app.getAllocator()) 848 | , m_osm_parser(app) 849 | { 850 | pushUndo(NO_MERGE_UNDO); 851 | } 852 | 853 | ~OSMNodeEditor() { 854 | destroyPreviewTexture(); 855 | } 856 | 857 | void run(); 858 | 859 | void colorLinks() { 860 | const ImU32 colors[] = { 861 | IM_COL32(0x20, 0x20, 0xA0, 0xFF), 862 | IM_COL32(0x20, 0xA0, 0x20, 0xFF), 863 | IM_COL32(0x20, 0xA0, 0xA0, 0xFF), 864 | IM_COL32(0xA0, 0x20, 0x20, 0xFF), 865 | IM_COL32(0xA0, 0x20, 0xA0, 0xFF), 866 | IM_COL32(0xA0, 0xA0, 0x20, 0xFF), 867 | IM_COL32(0xA0, 0xA0, 0xA0, 0xFF), 868 | }; 869 | 870 | for (i32 i = 0, c = m_links.size(); i < c; ++i) { 871 | NodeEditorLink& l = m_links[i]; 872 | l.color = colors[i % lengthOf(colors)]; 873 | } 874 | } 875 | 876 | void pushUndo(u32 tag) override { 877 | colorLinks(); 878 | SimpleUndoRedo::pushUndo(tag); 879 | } 880 | 881 | TerrainEditor* getTerrainEditor() { 882 | for (StudioApp::MousePlugin* p : m_app.getMousePlugins()) { 883 | if (equalStrings(p->getName(), "terrain_editor")) { 884 | return static_cast(p); 885 | } 886 | } 887 | return nullptr; 888 | } 889 | 890 | void deleteSelectedNodes() { 891 | if (m_is_any_item_active) return; 892 | for (i32 i = m_nodes.size() - 1; i >= 0; --i) { 893 | Node* node = m_nodes[i]; 894 | if (node->m_selected) { 895 | for (i32 j = m_links.size() - 1; j >= 0; --j) { 896 | if (m_links[j].getFromNode() == node->m_id || m_links[j].getToNode() == node->m_id) { 897 | m_links.erase(j); 898 | } 899 | } 900 | 901 | LUMIX_DELETE(m_allocator, node); 902 | m_nodes.swapAndPop(i); 903 | } 904 | } 905 | pushUndo(NO_MERGE_UNDO); 906 | } 907 | 908 | void destroyPreviewTexture() { 909 | RenderInterface* ri = m_app.getRenderInterface(); 910 | if (!ri) return; 911 | if (m_preview_texture == (ImTextureID)(intptr_t)0xffFFffFF) return; 912 | ri->destroyTexture(m_preview_texture); 913 | m_preview_texture = (ImTextureID)(intptr_t)0xffFFffFF; 914 | } 915 | 916 | void visualize(DistanceFieldOutput* df) { 917 | destroyPreviewTexture(); 918 | 919 | RenderInterface* ri = m_app.getRenderInterface(); 920 | Array tmp(m_app.getAllocator()); 921 | tmp.resize(df->m_field.size()); 922 | for (u32 i = 0, c = df->m_field.size(); i < c; ++i) { 923 | u8 vp = u8(clamp(df->m_field[i], 0.f, 255.f)); 924 | u8 vn = u8(clamp(-df->m_field[i], 0.f, 255.f)); 925 | tmp[i] = Color(vn, vp, 0, 0xff).abgr(); 926 | } 927 | m_preview_texture = ri->createTexture("maps_debug", tmp.begin(), df->m_size, df->m_size); 928 | m_preview_size = df->m_size; 929 | m_show_preview = true; 930 | } 931 | 932 | void visualize(MaskOutput* mask) { 933 | destroyPreviewTexture(); 934 | RenderInterface* ri = m_app.getRenderInterface(); 935 | Array tmp(m_app.getAllocator()); 936 | tmp.resize(mask->m_bitmap.size()); 937 | for (u32 i = 0, c = mask->m_bitmap.size(); i < c; ++i) { 938 | u32 v = mask->m_bitmap[i] > 0 ? 0xff : 0; 939 | tmp[i] = Color(v, v, v, 0xff).abgr(); 940 | } 941 | m_preview_texture = ri->createTexture("maps_debug", tmp.begin(), mask->m_size, mask->m_size); 942 | m_preview_size = mask->m_size; 943 | m_show_preview = true; 944 | } 945 | 946 | void pushRecent(const char* path) { 947 | Path p(path); 948 | m_recent_paths.eraseItems([&](const String& s) { return s == path; }); 949 | m_recent_paths.emplace(p.c_str(), m_app.getAllocator()); 950 | } 951 | 952 | Node* addNode(NodeType type, ImVec2 pos); 953 | 954 | struct Header { 955 | static const u32 MAGIC = 'LMAP'; 956 | u32 magic = MAGIC; 957 | u32 version = 0; 958 | }; 959 | 960 | void deserialize(InputMemoryStream& blob) override { 961 | for (Node* n : m_nodes) LUMIX_DELETE(m_app.getAllocator(), n); 962 | m_nodes.clear(); 963 | m_links.clear(); 964 | 965 | Header header; 966 | blob.read(header); 967 | u32 count; 968 | blob.read(count); 969 | m_links.resize(count); 970 | for (NodeEditorLink& link : m_links) { 971 | blob.read(link.from); 972 | blob.read(link.to); 973 | } 974 | blob.read(count); 975 | for (u32 i = 0; i < count; ++i) { 976 | NodeType type; 977 | blob.read(type); 978 | Node* n = addNode(type, Vec2(0, 0)); 979 | blob.read(n->m_id); 980 | blob.read(n->m_pos); 981 | n->deserialize(blob); 982 | } 983 | } 984 | 985 | void serialize(OutputMemoryStream& blob) override { 986 | Header header; 987 | blob.write(header); 988 | blob.write(m_links.size()); 989 | for (const NodeEditorLink& link : m_links) { 990 | blob.write(link.from); 991 | blob.write(link.to); 992 | } 993 | blob.write(m_nodes.size()); 994 | for (Node* node : m_nodes) { 995 | blob.write(node->getType()); 996 | blob.write(node->m_id); 997 | blob.write(node->m_pos); 998 | node->serialize(blob); 999 | } 1000 | } 1001 | 1002 | void onCanvasClicked(ImVec2 pos, i32 hovered_link) override { 1003 | for (const auto& t : TYPES) { 1004 | if (t.key && os::isKeyDown((os::Keycode)t.key)) { 1005 | Node* node = addNode(t.type, pos); 1006 | if (hovered_link >= 0) splitLink(m_nodes.back(), m_links, hovered_link); 1007 | pushUndo(NO_MERGE_UNDO); 1008 | break; 1009 | } 1010 | } 1011 | } 1012 | 1013 | void onLinkDoubleClicked(NodeEditorLink& link, ImVec2 pos) override {} 1014 | 1015 | void onContextMenu(ImVec2 pos) override { 1016 | static char filter[64] = ""; 1017 | ImGuiEx::Filter("Filter", filter, sizeof(filter), 150, ImGui::IsWindowAppearing()); 1018 | Node* new_node = nullptr; 1019 | for (const auto& t : TYPES) { 1020 | StaticString<64> label(t.label); 1021 | if (t.key) { 1022 | label.append(" (LMB + ", t.key, ")"); 1023 | } 1024 | if ((!filter[0] || findInsensitive(t.label, filter) != nullptr) && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::MenuItem(label))){ 1025 | new_node = addNode(t.type, pos); 1026 | pushUndo(NO_MERGE_UNDO); 1027 | filter[0] = '\0'; 1028 | ImGui::CloseCurrentPopup(); 1029 | break; 1030 | } 1031 | } 1032 | } 1033 | 1034 | void open(const char* path) { 1035 | m_show_welcome_screen = false; 1036 | FileSystem& fs = m_app.getEngine().getFileSystem(); 1037 | 1038 | OutputMemoryStream blob(m_app.getAllocator()); 1039 | if (!fs.getContentSync(Path(path), blob)) { 1040 | logError("Could not load ", path); 1041 | return; 1042 | } 1043 | InputMemoryStream tmp(blob); 1044 | deserialize(tmp); 1045 | pushUndo(NO_MERGE_UNDO); 1046 | m_path = path; 1047 | pushRecent(path); 1048 | } 1049 | 1050 | void saveAs(const char* path) { 1051 | OutputMemoryStream blob(m_app.getAllocator()); 1052 | serialize(blob); 1053 | 1054 | FileSystem& fs = m_app.getEngine().getFileSystem(); 1055 | if (!fs.saveContentSync(Path(path), blob)) { 1056 | logError("Could not save ", path); 1057 | return; 1058 | } 1059 | 1060 | m_path = path; 1061 | pushRecent(path); 1062 | } 1063 | 1064 | void save() { 1065 | if (m_path.isEmpty()) m_show_save_as = true; 1066 | else saveAs(m_path.c_str()); 1067 | } 1068 | 1069 | void gui(i32 x, i32 y, IVec2 pixel_offset, i32 zoom, float scale, i32 size) { 1070 | if (m_show_preview) { 1071 | ImGui::OpenPopup("Preview"); 1072 | m_show_preview = false; 1073 | m_preview_size = minimum(512, m_preview_size); 1074 | } 1075 | if (ImGui::BeginPopup("Preview")) { 1076 | ImGui::Image(m_preview_texture, ImVec2((float)m_preview_size, (float)m_preview_size)); 1077 | ImGui::EndPopup(); 1078 | } 1079 | ImGui::BeginChild("osm", ImVec2(0, 0), false, ImGuiWindowFlags_MenuBar); 1080 | 1081 | double dl_bottom = double(tiley2lat(double(y - (pixel_offset.y - m_area_edge) / 256.0), zoom)); 1082 | double dl_left = double(tilex2long(double(x - (pixel_offset.x - m_area_edge) / 256.0), zoom)); 1083 | double dl_top = double(tiley2lat(double(y - m_area_edge / 256.0 - pixel_offset.y / 256.0 + (1 << size)), zoom)); 1084 | double dl_right = double(tilex2long(double(x - m_area_edge / 256.0 - pixel_offset.x / 256.0 + (1 << size)), zoom)); 1085 | 1086 | double bottom = double(tiley2lat(double(y - pixel_offset.y / 256.0), zoom)); 1087 | double left = double(tilex2long(double(x - pixel_offset.x / 256.0), zoom)); 1088 | double top = double(tiley2lat(double(y - pixel_offset.y / 256.0 + (1 << size)), zoom)); 1089 | double right = double(tilex2long(double(x - pixel_offset.x / 256.0 + (1 << size)), zoom)); 1090 | if (bottom > top) swap(bottom, top); 1091 | if (left > right) swap(left, right); 1092 | if (dl_bottom > dl_top) swap(dl_bottom, dl_top); 1093 | if (dl_left > dl_right) swap(dl_left, dl_right); 1094 | 1095 | const float level_scale = scale * float(256 * (1 << size) * 156543.03 * cos(degreesToRadians(bottom)) / (1 << zoom)); 1096 | 1097 | auto downloadOSMData = [&](){ 1098 | const StaticString<1024> osm_download_path("https://api.openstreetmap.org/api/0.6/map?bbox=", dl_left, ",", dl_bottom, ",", dl_right, ",", dl_top); 1099 | const StableHash url_hash(osm_download_path.data, stringLength(osm_download_path.data)); 1100 | const Path cache_path(".lumix/maps_cache/osm_", url_hash.getHashValue()); 1101 | FileSystem& fs = m_app.getEngine().getFileSystem(); 1102 | OutputMemoryStream blob(m_allocator); 1103 | u32 downloaded_bytes; 1104 | if (fs.fileExists(cache_path) && fs.getContentSync(cache_path, blob)) { 1105 | if (!m_osm_parser.parseOSM(left, bottom, right, top, level_scale, blob)) { 1106 | logError("Failed to parse ", osm_download_path); 1107 | } 1108 | else { 1109 | logInfo(osm_download_path, " loaded from cache - ", cache_path); 1110 | } 1111 | } 1112 | else if (download(osm_download_path, blob, downloaded_bytes)) { 1113 | logInfo("Downloaded ", osm_download_path, " (", downloaded_bytes / 1024, "kB)"); 1114 | if (!fs.saveContentSync(cache_path, blob)) { 1115 | logWarning("Could not save ", cache_path); 1116 | } 1117 | if (!m_osm_parser.parseOSM(left, bottom, right, top, level_scale, blob)) { 1118 | logError("Failed to parse ", osm_download_path); 1119 | } 1120 | } 1121 | else { 1122 | logError("Failed to download ", osm_download_path); 1123 | } 1124 | }; 1125 | 1126 | if (ImGui::BeginMenuBar()) { 1127 | const CommonActions& actions = m_app.getCommonActions(); 1128 | if (ImGui::BeginMenu("File")) { 1129 | if (menuItem(actions.save, true)) save(); 1130 | if (ImGui::MenuItem("Save As")) m_show_save_as = true; 1131 | if (ImGui::MenuItem("Open")) m_show_open = true; 1132 | if (ImGui::BeginMenu("Recent", !m_recent_paths.empty())) { 1133 | for (const String& s : m_recent_paths) { 1134 | if (ImGui::MenuItem(s.c_str())) open(s.c_str()); 1135 | } 1136 | ImGui::EndMenu(); 1137 | } 1138 | if (menuItem(m_run_action, true)) run(); 1139 | ImGui::EndMenu(); 1140 | } 1141 | if (ImGui::BeginMenu("OSM data")) { 1142 | if (ImGui::MenuItem(ICON_FA_FILE_DOWNLOAD "Download")) { 1143 | downloadOSMData(); 1144 | } 1145 | ImGui::DragInt("Area edge", &m_area_edge); 1146 | ImGui::EndMenu(); 1147 | } 1148 | if (ImGui::BeginMenu("Edit")) { 1149 | if (menuItem(actions.undo, canUndo())) undo(); 1150 | if (menuItem(actions.redo, canRedo())) redo(); 1151 | ImGui::EndMenu(); 1152 | } 1153 | ImGui::EndMenuBar(); 1154 | } 1155 | 1156 | FileSelector& fs = m_app.getFileSelector(); 1157 | if (fs.gui("Open", &m_show_open, "mgr", false)) open(fs.getPath()); 1158 | if (fs.gui("Save As", &m_show_save_as, "mgr", true)) saveAs(fs.getPath()); 1159 | 1160 | if (m_show_welcome_screen) { 1161 | if (ImGui::Button("New graph")) m_show_welcome_screen = false; 1162 | ImGui::SameLine(); 1163 | if (ImGui::Button("Open")) { 1164 | m_show_open = true; 1165 | m_show_welcome_screen = false; 1166 | } 1167 | 1168 | if (!m_recent_paths.empty()) { 1169 | ImGui::TextUnformatted("Recent:"); 1170 | for (const String& s : m_recent_paths) { 1171 | if (ImGui::Selectable(s.c_str())) { 1172 | m_show_welcome_screen = false; 1173 | open(s.c_str()); 1174 | } 1175 | } 1176 | } 1177 | } 1178 | else { 1179 | nodeEditorGUI(m_nodes, m_links); 1180 | } 1181 | ImGui::EndChild(); 1182 | 1183 | if (m_show_osm_download_dialog) { 1184 | m_show_osm_download_dialog = false; 1185 | ImGui::OpenPopup("Download OSM data"); 1186 | } 1187 | if (ImGui::BeginPopupModal("Download OSM data")) { 1188 | ImGui::TextUnformatted("OSM data empty"); 1189 | if (ImGui::Button("Download")) { 1190 | downloadOSMData(); 1191 | ImGui::CloseCurrentPopup(); 1192 | } 1193 | ImGui::SameLine(); 1194 | if (ImGui::Button("Close")) ImGui::CloseCurrentPopup(); 1195 | ImGui::EndPopup(); 1196 | } 1197 | } 1198 | 1199 | OSMParser m_osm_parser; 1200 | StudioApp& m_app; 1201 | IAllocator& m_allocator; 1202 | Array m_links; 1203 | Array m_nodes; 1204 | u32 m_node_id_genereator = 1; 1205 | Array m_recent_paths; 1206 | Action m_run_action{"Run", "OSM editor - run", "maps_nodes_run", ICON_FA_PLAY}; 1207 | i32 m_area_edge = 0; 1208 | bool m_show_save_as = false; 1209 | bool m_show_open = false; 1210 | bool m_show_osm_download_dialog = false; 1211 | bool m_show_welcome_screen = true; 1212 | Path m_path; 1213 | ImTextureID m_preview_texture = (ImTextureID)(intptr_t)0xffFFffFF; 1214 | bool m_show_preview = false; 1215 | u32 m_preview_size = 0; 1216 | }; 1217 | 1218 | template 1219 | static void forEachInput(const OSMNodeEditor& resource, int node_id, const F& f) { 1220 | for (const NodeEditorLink& link : resource.m_links) { 1221 | if (link.getToNode() == node_id) { 1222 | const int iter = resource.m_nodes.find([&](const OSMNodeEditor::Node* node) { return node->m_id == link.getFromNode(); }); 1223 | OSMNodeEditor::Node* from = resource.m_nodes[iter]; 1224 | const u16 from_attr = link.getFromPin(); 1225 | const u16 to_attr = link.getToPin(); 1226 | f(from, from_attr, to_attr, u32(&link - resource.m_links.begin())); 1227 | } 1228 | } 1229 | } 1230 | 1231 | UniquePtr OSMNodeEditor::Node::getInput(u16 input_idx) { 1232 | Node* node = nullptr; 1233 | u16 output_idx = 0; 1234 | forEachInput(m_editor, m_id, [&](Node* from, u16 from_attr, u16 to_attr, u32 link_idx){ 1235 | if (to_attr == input_idx) { 1236 | output_idx = from_attr; 1237 | node = from; 1238 | } 1239 | }); 1240 | 1241 | if (!node) return {}; 1242 | return node->getOutputValue(output_idx); 1243 | } 1244 | 1245 | static void raster(u8 value, const IVec2& p0, const IVec2& p1, u32 size, Array& out) { 1246 | // naive line rasterization 1247 | IVec2 a = p0; 1248 | IVec2 b = p1; 1249 | 1250 | IVec2 d = b - a; 1251 | if (abs(d.x) > abs(d.y)) { 1252 | if (d.x < 0) swap(a, b); 1253 | d = b - a; 1254 | 1255 | for (i32 i = a.x; i <= b.x; ++i) { 1256 | i32 j = int(a.y + d.y * float(i - a.x) / d.x); 1257 | if (i < 1 || i >= (i32)size - 1) continue; 1258 | if (j < 1 || j >= (i32)size - 1) continue; 1259 | 1260 | for (i32 k = -1; k <= 1; ++k) { 1261 | for (i32 l = -1; l <= 1; ++l) { 1262 | out[i + k + (j + l) * size] = value; 1263 | } 1264 | } 1265 | } 1266 | } 1267 | else { 1268 | if (d.y < 0) swap(a, b); 1269 | d = b - a; 1270 | 1271 | for (i32 j = a.y; j <= b.y; ++j) { 1272 | i32 i = int(a.x + d.x * float(j - a.y) / d.y); 1273 | if (i < 1 || i >= (i32)size - 1) continue; 1274 | if (j < 1 || j >= (i32)size - 1) continue; 1275 | 1276 | for (i32 k = -1; k <= 1; ++k) { 1277 | for (i32 l = -1; l <= 1; ++l) { 1278 | out[i + k + (j + l) * size] = value; 1279 | } 1280 | } 1281 | } 1282 | } 1283 | } 1284 | 1285 | static void raster(Span points, u32 w, i32 change, Array& out) { 1286 | // naive polygon rasterization 1287 | if (points.length() == 0) return; 1288 | 1289 | float miny = FLT_MAX; 1290 | float maxy = -FLT_MAX; 1291 | for (Vec2 v : points) { 1292 | miny = minimum(v.y, miny); 1293 | maxy = maximum(v.y, maxy); 1294 | } 1295 | const i32 h = (i32)w; 1296 | const i32 from_y = clamp(i32(miny - 1), 0, h - 1); 1297 | const i32 to_y = clamp(i32(maxy + 1), 0, h - 1); 1298 | 1299 | for (i32 pixelY = from_y; pixelY < to_y; ++pixelY) { 1300 | float nodeX[256]; 1301 | u32 nodes = 0; 1302 | const float y = (float)pixelY; 1303 | for (i32 i = 0; i < (i32)points.length() - 1; i++) { 1304 | const float y0 = points[i].y; 1305 | const float y1 = points[i + 1].y; 1306 | if (y1 >= y && y0 < y || y1 < y && y0 >= y) { 1307 | ASSERT(nodes < lengthOf(nodeX)); 1308 | const float t = (y - y0) / (y1 - y0); 1309 | ASSERT(t >= 0); 1310 | nodeX[nodes] = lerp(points[i].x, points[i + 1].x, t); 1311 | ++nodes; 1312 | } 1313 | } 1314 | 1315 | if ((nodes & 1) != 0) { 1316 | ASSERT(false); 1317 | logError("nodes == 1 ", points[0].x, ", ", points[0].y, " - ", points[2].x, ", ", points[2].y); 1318 | continue; 1319 | } 1320 | 1321 | qsort(nodeX, nodes, sizeof(nodeX[0]), [](const void* a, const void* b){ 1322 | float m = *(float*)a; 1323 | float n = *(float*)b; 1324 | return m == n ? 0 : (m < n ? -1 : 1); 1325 | }); 1326 | 1327 | for (u32 i = 0; i < nodes; i += 2) { 1328 | const i32 from = i32(clamp(nodeX[i] - 1, 0.f, (float)w - 1)); 1329 | const i32 to = i32(clamp(nodeX[i + 1] + 1, 0.f, (float)w - 1)); 1330 | 1331 | for (i32 pixelX = from; pixelX < to; ++pixelX) { 1332 | if (pixelX < nodeX[i]) continue; 1333 | if (pixelX > nodeX[i + 1]) continue; 1334 | u8& v = out[pixelX + pixelY * w]; 1335 | v = (u8)clamp(i32(v) + change, 0, 255); 1336 | } 1337 | } 1338 | } 1339 | } 1340 | 1341 | static Terrain* getTerrain(StudioApp& app) { 1342 | WorldEditor& editor = app.getWorldEditor(); 1343 | World* world = app.getWorldEditor().getWorld(); 1344 | 1345 | RenderModule* module = (RenderModule*)world->getModule("renderer"); 1346 | EntityPtr entity = module->getFirstTerrain(); 1347 | if (!entity.isValid()) return nullptr; 1348 | if (module->getNextTerrain(*entity).isValid()) return nullptr; 1349 | 1350 | return module->getTerrain(*entity); 1351 | } 1352 | 1353 | static u32 getSplatmapSize(StudioApp& app) { 1354 | const Terrain* terrain = getTerrain(app); 1355 | if (!terrain) return 1024; 1356 | Texture* splatmap = terrain->getSplatmap(); 1357 | if (!splatmap || !splatmap->isReady()) return 1024; 1358 | return splatmap->width; 1359 | } 1360 | 1361 | struct GrassNode : OSMNodeEditor::Node { 1362 | GrassNode(OSMNodeEditor& editor) 1363 | : Node(editor) 1364 | {} 1365 | 1366 | NodeType getType() const override { return NodeType::GRASS; } 1367 | bool hasInputPins() const override { return true; } 1368 | bool hasOutputPins() const override { return false; } 1369 | void serialize(OutputMemoryStream& blob) override {} 1370 | void deserialize(InputMemoryStream& blob) override {} 1371 | 1372 | UniquePtr getOutputValue(u16 output_idx) override { ASSERT(false); return {}; } 1373 | 1374 | void run() { 1375 | clearErrors(); 1376 | if (!ensureOSMData()) return; 1377 | 1378 | UniquePtr input = getInput(0); 1379 | if (!input) return error("Invalid input"); 1380 | if (input->getType() != OutputType::MASK) return error("Invalid input"); 1381 | const MaskOutput* mask = (MaskOutput*)input.get(); 1382 | 1383 | TerrainEditor* terrain_editor = m_editor.getTerrainEditor(); 1384 | if (!terrain_editor) return error("Terrain editor not found"); 1385 | 1386 | Terrain* terrain = getTerrain(m_editor.m_app); 1387 | if (!terrain) return error("Terrain not found"); 1388 | 1389 | Texture* splatmap = terrain->getSplatmap(); 1390 | if (!splatmap) return error("Missing splatmap"); 1391 | if (!splatmap->isReady()) return error("Splatmap not ready"); 1392 | 1393 | auto is_masked = [&](u32 x, u32 y){ 1394 | u32 i = u32(x / float(splatmap->width) * mask->m_size + 0.5f); 1395 | u32 j = u32(y / float(splatmap->height) * mask->m_size + 0.5f); 1396 | i = clamp(i, 0, mask->m_size - 1); 1397 | j = clamp(j, 0, mask->m_size - 1); 1398 | 1399 | return mask->m_bitmap[i + j * mask->m_size] != 0; 1400 | }; 1401 | 1402 | OutputMemoryStream new_data(m_editor.m_allocator); 1403 | new_data.resize(splatmap->height * splatmap->width * 4); 1404 | u8* data = new_data.getMutableData(); 1405 | memcpy(data, splatmap->getData(), new_data.size()); 1406 | for (u32 y = 0; y < splatmap->height; ++y) { 1407 | for (u32 x = 0; x < splatmap->width; ++x) { 1408 | if (is_masked(x, y)) { 1409 | u8* pixel = data + (x + (splatmap->height - y - 1) * splatmap->width) * 4; 1410 | if (m_additive) { 1411 | u16* tmp = (u16*)(pixel + 2); 1412 | *tmp |= m_grass; 1413 | } 1414 | else { 1415 | memcpy(pixel + 2, &m_grass, sizeof(m_grass)); 1416 | } 1417 | } 1418 | } 1419 | } 1420 | 1421 | terrain_editor->updateSplatmap(terrain, static_cast(new_data), 0, 0, splatmap->width, splatmap->height, true); 1422 | } 1423 | 1424 | bool gui() override { 1425 | ImGuiEx::NodeTitle("Grass", ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 1426 | inputSlot(); 1427 | bool res = ImGui::Checkbox("Additive", &m_additive); 1428 | i32 g = m_grass; 1429 | if (ImGui::InputInt("Grass", &g)) { 1430 | m_grass = g; 1431 | res = true; 1432 | } 1433 | return res; 1434 | } 1435 | 1436 | u16 m_grass = 0; 1437 | bool m_additive = false; 1438 | }; 1439 | 1440 | struct MergeDistanceFieldsNode : OSMNodeEditor::Node { 1441 | MergeDistanceFieldsNode(OSMNodeEditor& editor) 1442 | : Node(editor) 1443 | {} 1444 | 1445 | NodeType getType() const override { return NodeType::MERGE_DISTANCE_FIELDS; } 1446 | bool hasInputPins() const override { return true; } 1447 | bool hasOutputPins() const override { return true; } 1448 | 1449 | void serialize(OutputMemoryStream& blob) override {} 1450 | void deserialize(InputMemoryStream& blob) override {} 1451 | 1452 | UniquePtr getOutputValue(u16 output_idx) override { 1453 | UniquePtr input0 = getInput(0); 1454 | UniquePtr input1 = getInput(1); 1455 | if (!input0) return outputError("Invalid input"); 1456 | if (!input1) return outputError("Invalid input"); 1457 | if (input0->getType() != OutputType::DISTANCE_FIELD) return outputError("Invalid input"); 1458 | if (input1->getType() != OutputType::DISTANCE_FIELD) return outputError("Invalid input"); 1459 | DistanceFieldOutput* df0 = (DistanceFieldOutput*)input0.get(); 1460 | DistanceFieldOutput* df1 = (DistanceFieldOutput*)input1.get(); 1461 | if (df0->m_size != df1->m_size) return outputError("Distance fields have different size"); 1462 | 1463 | for (u32 i = 0, c = df0->m_field.size(); i < c; ++i) { 1464 | df0->m_field[i] = minimum(df0->m_field[i], df1->m_field[i]); 1465 | } 1466 | 1467 | return input0.move(); 1468 | } 1469 | 1470 | bool gui() override { 1471 | nodeTitle("Merge"); 1472 | ImGui::BeginGroup(); 1473 | inputSlot(OutputType::DISTANCE_FIELD); ImGui::TextUnformatted("A"); 1474 | inputSlot(OutputType::DISTANCE_FIELD); ImGui::TextUnformatted("B"); 1475 | ImGui::EndGroup(); 1476 | ImGui::SameLine(); 1477 | outputSlot(OutputType::DISTANCE_FIELD); 1478 | return false; 1479 | } 1480 | }; 1481 | 1482 | struct MergeMasksNode : OSMNodeEditor::Node { 1483 | MergeMasksNode(OSMNodeEditor& editor) 1484 | : Node(editor) 1485 | {} 1486 | 1487 | NodeType getType() const override { return NodeType::MERGE_MASKS; } 1488 | bool hasInputPins() const override { return true; } 1489 | bool hasOutputPins() const override { return true; } 1490 | 1491 | void serialize(OutputMemoryStream& blob) override { 1492 | blob.write(m_mode); 1493 | } 1494 | 1495 | void deserialize(InputMemoryStream& blob) override { 1496 | blob.read(m_mode); 1497 | } 1498 | 1499 | UniquePtr getOutputValue(u16 output_idx) override { 1500 | UniquePtr input0 = getInput(0); 1501 | UniquePtr input1 = getInput(1); 1502 | if (!input0) return outputError("Invalid input"); 1503 | if (!input1) return outputError("Invalid input"); 1504 | if (input0->getType() != OutputType::MASK) return outputError("Invalid input"); 1505 | if (input1->getType() != OutputType::MASK) return outputError("Invalid input"); 1506 | MaskOutput* mask0 = (MaskOutput*)input0.get(); 1507 | MaskOutput* mask1 = (MaskOutput*)input1.get(); 1508 | 1509 | switch (m_mode) { 1510 | case DIFFERENECE: 1511 | for (u32 i = 0, c = mask0->m_bitmap.size(); i < c; ++i) { 1512 | mask0->m_bitmap[i] = mask0->m_bitmap[i] != 0 && mask1->m_bitmap[i] == 0; 1513 | } 1514 | break; 1515 | case INTERSECTION: 1516 | for (u32 i = 0, c = mask0->m_bitmap.size(); i < c; ++i) { 1517 | mask0->m_bitmap[i] = mask0->m_bitmap[i] != 0 && mask1->m_bitmap[i] != 0; 1518 | } 1519 | break; 1520 | case UNION: 1521 | for (u32 i = 0, c = mask0->m_bitmap.size(); i < c; ++i) { 1522 | mask0->m_bitmap[i] = mask0->m_bitmap[i] != 0 || mask1->m_bitmap[i] != 0; 1523 | } 1524 | break; 1525 | } 1526 | 1527 | return input0.move(); 1528 | } 1529 | 1530 | bool gui() override { 1531 | nodeTitle("Merge"); 1532 | ImGui::BeginGroup(); 1533 | inputSlot(); ImGui::TextUnformatted("A"); 1534 | inputSlot(); ImGui::TextUnformatted("B"); 1535 | ImGui::EndGroup(); 1536 | ImGui::SameLine(); 1537 | ImGui::Combo("Mode", (i32*)&m_mode, "Union\0Intersection\0Difference\0"); 1538 | ImGui::SameLine(); 1539 | outputSlot(); 1540 | return false; 1541 | } 1542 | 1543 | enum Mode : u32 { 1544 | UNION, 1545 | INTERSECTION, 1546 | DIFFERENECE 1547 | }; 1548 | 1549 | Mode m_mode = UNION; 1550 | }; 1551 | 1552 | struct DistanceFieldNode : OSMNodeEditor::Node { 1553 | DistanceFieldNode(OSMNodeEditor& editor) 1554 | : Node(editor) 1555 | {} 1556 | 1557 | UniquePtr getOutputValue(u16 output_idx) override { 1558 | UniquePtr input = getInput(0); 1559 | if (!input) return outputError("Invalid input"); 1560 | if (input->getType() != OutputType::MASK) return outputError("Invalid input"); 1561 | MaskOutput* mask = (MaskOutput*)input.get(); 1562 | 1563 | IAllocator& allocator = m_editor.m_app.getAllocator(); 1564 | UniquePtr df = UniquePtr::create(allocator, allocator); 1565 | df->m_size = mask->m_size; 1566 | df->m_field.resize(df->m_size * df->m_size); 1567 | 1568 | Array data_ar(m_editor.m_app.getAllocator()); 1569 | 1570 | const u32 w = df->m_size; 1571 | const u32 h = df->m_size; 1572 | data_ar.resize(w * h); 1573 | IVec2* data = data_ar.begin(); 1574 | 1575 | auto check_neighbour = [](const IVec2& p, IVec2* n, IVec2 ij) { 1576 | IVec2 a = p - ij; 1577 | IVec2 b = *n - ij; 1578 | if (a.x * a.x + a.y * a.y < b.x * b.x + b.y * b.y) { 1579 | *n = p; 1580 | } 1581 | }; 1582 | 1583 | const u8* bitmap = mask->m_bitmap.begin(); 1584 | for (i32 j = 0; j < (i32)h; ++j) { 1585 | for (i32 i = 0; i < (i32)w; ++i) { 1586 | if (bitmap[i + j * w] != 0) { 1587 | data[i + j * w] = IVec2(i, j); 1588 | } 1589 | else { 1590 | data[i + j * w] = IVec2(INT_MIN, INT_MIN); 1591 | } 1592 | } 1593 | } 1594 | 1595 | auto compute = [&](){ 1596 | for (i32 j = 0; j < (i32)h; ++j) { 1597 | const int j0 = maximum(j - 1, 0); 1598 | for (i32 i = 0; i < (i32)w; ++i) { 1599 | const int i0 = maximum(i - 1, 0); 1600 | const int i1 = minimum(i + 1, w - 1); 1601 | 1602 | IVec2* n = &data[i + j * w]; 1603 | IVec2 ij(i, j); 1604 | check_neighbour(data[i0 + j0 * w], n, ij); 1605 | check_neighbour(data[i + j0 * w], n, ij); 1606 | check_neighbour(data[i1 + j0 * w], n, ij); 1607 | check_neighbour(data[i0 + j * w], n, ij); 1608 | } 1609 | 1610 | for (int i = w - 1; i >= 0; --i) { 1611 | const int i1 = minimum(i + 1, w - 1); 1612 | 1613 | IVec2* n = &data[j * w + i]; 1614 | IVec2 ij(i, j); 1615 | check_neighbour(data[j * w + i1], n, ij); 1616 | } 1617 | } 1618 | 1619 | for (int j = h - 1; j >= 0; --j) { 1620 | const int j0 = minimum(j + 1, h - 1); 1621 | for (int i = w - 1; i >= 0; --i) { 1622 | const int i0 = maximum(i - 1, 0); 1623 | const int i1 = minimum(i + 1, w - 1); 1624 | 1625 | IVec2* n = &data[i + j * w]; 1626 | IVec2 ij(i, j); 1627 | check_neighbour(data[i1 + j0 * w], n, ij); 1628 | check_neighbour(data[i + j0 * w], n, ij); 1629 | check_neighbour(data[i0 + j0 * w], n, ij); 1630 | check_neighbour(data[i1 + j * w], n, ij); 1631 | } 1632 | 1633 | for (int i = w - 1; i >= 0; --i) { 1634 | const int i0 = maximum(i - 1, 0); 1635 | 1636 | IVec2* n = &data[j * w + i]; 1637 | IVec2 ij(i, j); 1638 | check_neighbour(data[j * w + i0], n, ij); 1639 | } 1640 | } 1641 | }; 1642 | compute(); 1643 | 1644 | for (u32 j = 0; j < h; ++j) { 1645 | for (u32 i = 0; i < w; ++i) { 1646 | const float d = length(Vec2(data[i + j * w] - IVec2(i, j))); 1647 | df->m_field[i + (h - j - 1) * w] = d; 1648 | } 1649 | } 1650 | 1651 | // negative distance 1652 | for (i32 j = 0; j < (i32)h; ++j) { 1653 | for (i32 i = 0; i < (i32)w; ++i) { 1654 | if (bitmap[i + j * w] == 0) { 1655 | data[i + j * w] = IVec2(i, j); 1656 | } 1657 | else { 1658 | data[i + j * w] = IVec2(INT_MIN, INT_MIN); 1659 | } 1660 | } 1661 | } 1662 | 1663 | compute(); 1664 | 1665 | for (u32 j = 0; j < h; ++j) { 1666 | for (u32 i = 0; i < w; ++i) { 1667 | if (bitmap[i + j * w] != 0) { 1668 | const float d = length(Vec2(data[i + j * w] - IVec2(i, j))); 1669 | df->m_field[i + (h - j - 1) * w] = -d; 1670 | } 1671 | } 1672 | } 1673 | return df.move(); 1674 | } 1675 | 1676 | NodeType getType() const override { return NodeType::DISTANCE_FIELD; } 1677 | bool hasInputPins() const override { return true; } 1678 | bool hasOutputPins() const override { return true; } 1679 | void serialize(OutputMemoryStream& blob) override {} 1680 | void deserialize(InputMemoryStream& blob) override {} 1681 | 1682 | bool gui() override { 1683 | nodeTitle("Distance field"); 1684 | inputSlot(); 1685 | outputSlot(OutputType::DISTANCE_FIELD); 1686 | ImGui::TextUnformatted("Input mask"); 1687 | return false; 1688 | } 1689 | }; 1690 | 1691 | struct AdjustHeightNode : OSMNodeEditor::Node { 1692 | AdjustHeightNode(OSMNodeEditor& editor) 1693 | : Node(editor) 1694 | {} 1695 | 1696 | NodeType getType() const override { return NodeType::ADJUST_HEIGHT; } 1697 | bool hasInputPins() const override { return true; } 1698 | bool hasOutputPins() const override { return false; } 1699 | 1700 | void serialize(OutputMemoryStream& blob) override { 1701 | blob.writeString(m_texture); 1702 | blob.write(m_texture_scale); 1703 | blob.write(m_multiplier); 1704 | blob.write(m_distance_range); 1705 | } 1706 | 1707 | void deserialize(InputMemoryStream& blob) override { 1708 | m_texture = blob.readString(); 1709 | blob.read(m_texture_scale); 1710 | blob.read(m_multiplier); 1711 | blob.read(m_distance_range); 1712 | } 1713 | 1714 | UniquePtr getOutputValue(u16 output_idx) override { return {}; } 1715 | 1716 | bool gui() override { 1717 | ImGuiEx::BeginNodeTitleBar(ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 1718 | ImGui::AlignTextToFramePadding(); 1719 | ImGui::TextUnformatted("Adjust height"); 1720 | ImGui::SameLine(); 1721 | if (ImGui::Button(ICON_FA_PLAY)) run(); 1722 | ImGuiEx::EndNodeTitleBar(); 1723 | inputSlot(OutputType::DISTANCE_FIELD); 1724 | 1725 | bool res = textureMaskInput(m_texture); 1726 | res = ImGui::DragFloat("Texture scale", &m_texture_scale) || res; 1727 | res = ImGui::DragFloat("Multiplier", &m_multiplier) || res; 1728 | res = ImGui::DragFloat("Minimal distance", &m_distance_range.x, 1.f, -FLT_MAX, FLT_MAX) || res; 1729 | res = ImGui::DragFloat("Maximal distance", &m_distance_range.y, 1.f, -FLT_MAX, FLT_MAX) || res; 1730 | ImGui::TextUnformatted("(?)"); 1731 | if (ImGui::IsItemHovered()) { 1732 | ImGui::SetTooltip( 1733 | "Everything below minimal distance is not affected.\n" 1734 | "Everything above maximal distance is fully affected.\n" 1735 | "Quadratic interpolation between min and max." 1736 | ); 1737 | } 1738 | return false; 1739 | } 1740 | 1741 | void run() { 1742 | clearErrors(); 1743 | if (!ensureOSMData()) return; 1744 | 1745 | UniquePtr input = getInput(0); 1746 | if (!input) return error("Invalid input"); 1747 | if (input->getType() != OutputType::DISTANCE_FIELD) return error("Invalid input"); 1748 | 1749 | Terrain* terrain = getTerrain(m_editor.m_app); 1750 | if (!terrain) return error("Terrain not found"); 1751 | 1752 | TerrainEditor* terrain_editor = m_editor.getTerrainEditor(); 1753 | if (!terrain_editor) return error("Terrain editor not found"); 1754 | 1755 | DistanceFieldOutput* df = (DistanceFieldOutput*)input.get(); 1756 | 1757 | Texture* hm = terrain->getHeightmap(); 1758 | if (!hm) return error("Missing heightmap"); 1759 | if (!hm->isReady()) return error("Heightmap not ready"); 1760 | if (hm->format != gpu::TextureFormat::R16) return error("Heightmap format not supported - it has to be 16bit"); 1761 | 1762 | const u16* src_data = (const u16*)hm->getData(); 1763 | if (!src_data) return error("Could not read heightmap data"); 1764 | 1765 | u32 w, h; 1766 | stbi_uc* rgba = nullptr; 1767 | if (m_texture.data[0]) { 1768 | rgba = loadTexture(m_texture, w, h, m_editor.m_app); 1769 | if (!rgba) return error("Failed to load the texture"); 1770 | } 1771 | 1772 | OutputMemoryStream new_data(m_editor.m_allocator); 1773 | new_data.resize(hm->width * hm->height * 2); 1774 | u16* hm_data = (u16*)new_data.getMutableData(); 1775 | memcpy(hm_data, src_data, new_data.size()); 1776 | 1777 | auto mix = [](float a, float b, float t) { 1778 | return a * (1 - t) + b * t; 1779 | }; 1780 | 1781 | auto sample = [&](u32 i, u32 j) { 1782 | if (!rgba) return 1.f; 1783 | 1784 | const float x = i * float(m_texture_scale / hm->width); 1785 | const float y = j * float(m_texture_scale / hm->height); 1786 | const float a = x * w; 1787 | const float b = y * h; 1788 | 1789 | u32 a0 = u32(a); 1790 | u32 b0 = u32(b); 1791 | const float tx = a - a0; 1792 | const float ty = b - b0; 1793 | a0 = a0 % w; 1794 | b0 = b0 % w; 1795 | 1796 | const float v00 = rgba[a0 + b0 * w] / float(0xff); 1797 | const float v10 = rgba[a0 + 1 + b0 * w] / float(0xff); 1798 | const float v11 = rgba[a0 + 1 + b0 * w + w] / float(0xff); 1799 | const float v01 = rgba[a0 + b0 * w + w] / float(0xff); 1800 | 1801 | return mix( 1802 | mix(v00, v10, tx), 1803 | mix(v01, v11, tx), 1804 | ty 1805 | ); 1806 | }; 1807 | 1808 | const float range = m_distance_range.y - m_distance_range.x; 1809 | 1810 | for (u32 j = 0; j < hm->height; ++j) { 1811 | for (u32 i = 0; i < hm->width; ++i) { 1812 | const float u = float(i) / (hm->width - 1); 1813 | const float v = float(j) / (hm->height - 1); 1814 | const float dist = df->sample(u, 1 - v); 1815 | if (dist > m_distance_range.x) { 1816 | float distance_weight = clamp((dist - m_distance_range.x) / range, 0.f, 1.f); 1817 | distance_weight *= distance_weight; 1818 | const u32 idx = i + (hm->height - j - 1) * hm->width; 1819 | float height = (hm_data[idx] / float(0xffFF)); 1820 | height += sample(i, j) * m_multiplier * distance_weight; 1821 | hm_data[idx] = (u16)clamp(height * float(0xffFF), 0.f, (float)0xffFF); 1822 | } 1823 | } 1824 | } 1825 | 1826 | terrain_editor->updateHeightmap(terrain, static_cast(new_data), 0, 0, hm->width, hm->height); 1827 | stbi_image_free(rgba); 1828 | } 1829 | 1830 | StaticString m_texture; 1831 | float m_texture_scale = 1.f; 1832 | float m_multiplier = 1.f; 1833 | Vec2 m_distance_range = Vec2(0, 1); 1834 | }; 1835 | 1836 | struct FlattenPolylinesNode : OSMNodeEditor::Node { 1837 | FlattenPolylinesNode(OSMNodeEditor& editor) 1838 | : Node(editor) 1839 | , m_height(editor.m_allocator) 1840 | , m_weight_sum(editor.m_allocator) 1841 | , m_max_weight(editor.m_allocator) 1842 | {} 1843 | 1844 | NodeType getType() const override { return NodeType::FLATTEN_POLYLINES; } 1845 | bool hasInputPins() const override { return false; } 1846 | bool hasOutputPins() const override { return false; } 1847 | 1848 | void serialize(OutputMemoryStream& blob) override { 1849 | blob.write(m_line_width); 1850 | blob.write(m_boundary_width); 1851 | blob.writeString(m_key); 1852 | blob.writeString(m_value); 1853 | } 1854 | 1855 | void deserialize(InputMemoryStream& blob) override { 1856 | blob.read(m_line_width); 1857 | blob.read(m_boundary_width); 1858 | m_key = blob.readString(); 1859 | m_value = blob.readString(); 1860 | } 1861 | 1862 | UniquePtr getOutputValue(u16 output_idx) override { return {}; } 1863 | 1864 | bool gui() override { 1865 | ImGuiEx::BeginNodeTitleBar(ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 1866 | ImGui::TextUnformatted("Flatten polylines"); 1867 | ImGui::SameLine(); 1868 | if (ImGui::Button(ICON_FA_PLAY)) run(); 1869 | ImGuiEx::EndNodeTitleBar(); 1870 | bool res = ImGui::DragFloat("Width", &m_line_width); 1871 | res = ImGui::DragFloat("Boundary", &m_boundary_width) || res; 1872 | res = tagInput(Span(m_key.data), Span(m_value.data), 150) || res; 1873 | return res; 1874 | } 1875 | 1876 | Vec2 toHeightmap(const Vec2& p) const { 1877 | Vec2 tmp; 1878 | tmp = p; 1879 | const Terrain* terrain = getTerrain(m_editor.m_app); 1880 | const Vec2 s = terrain->getSize(); 1881 | const u32 size = terrain->getHeightmap()->width; 1882 | tmp.x += s.x * 0.5f; 1883 | tmp.y += s.y * 0.5f; 1884 | 1885 | tmp.x = tmp.x / s.x * (float)size; 1886 | tmp.y = (1 - tmp.y / s.y) * (float)size; 1887 | return tmp; 1888 | } 1889 | 1890 | void flattenQuad(const Vec2* points, float h0, float h1, const Terrain& terrain, float line_width, float boundary_width) { 1891 | u16* ptr = (u16*)terrain.m_heightmap->data.getMutableData(); 1892 | const u32 pixw = terrain.m_heightmap->width; 1893 | const u32 pixh = terrain.m_heightmap->height; 1894 | 1895 | const Vec2 c0 = (points[0] + points[1]) * 0.5f; 1896 | const Vec2 c1 = (points[2] + points[3]) * 0.5f; 1897 | const Vec2 dir = normalize(c1 - c0); 1898 | const Vec2 n(-dir.y, dir.x); 1899 | 1900 | const Vec2 min = minimum(points[0], minimum(points[1], minimum(points[2], points[3]))); 1901 | const Vec2 max = maximum(points[0], maximum(points[1], maximum(points[2], points[3]))); 1902 | const float heights[] = {h0, h0, h1, h1}; 1903 | 1904 | const i32 from_y = clamp(i32(min.y - 1), 0, pixh); 1905 | const i32 to_y = clamp(i32(max.y + 1), 0, pixh); 1906 | for (i32 pixelY = from_y; pixelY < to_y; ++pixelY) { 1907 | Vec2 nodeXY[4]; 1908 | u32 nodes = 0; 1909 | const float y = (float)pixelY; 1910 | for (i32 i = 0; i < 4; i++) { 1911 | const float y0 = points[i].y; 1912 | const float y1 = points[(i + 1) % 4].y; 1913 | if (y0 < y && y1 >= y || y1 < y && y0 >= y) { 1914 | const float t = (y - y0) / (y1 - y0); 1915 | nodeXY[nodes].x = lerp(points[i].x, points[(i + 1) % 4].x, t); 1916 | nodeXY[nodes].y = lerp(heights[i], heights[(i + 1) % 4], t); 1917 | ++nodes; 1918 | } 1919 | } 1920 | 1921 | if ((nodes & 1) != 0) { 1922 | ASSERT(false); 1923 | logError("nodes == 1 ", points[0].x, ", ", points[0].y, " - ", points[2].x, ", ", points[2].y); 1924 | continue; 1925 | } 1926 | 1927 | qsort(nodeXY, nodes, sizeof(nodeXY[0]), [](const void* a, const void* b){ 1928 | const Vec2 m = *(const Vec2*)a; 1929 | const Vec2 n = *(const Vec2*)b; 1930 | return m.x == n.x ? 0 : (m.x < n.x ? -1 : 1); 1931 | }); 1932 | 1933 | for (u32 i = 0; i < nodes; i += 2) { 1934 | const i32 from = clamp(i32(nodeXY[i].x), 0, pixw); 1935 | const i32 to = clamp(i32(nodeXY[i + 1].x), 0, pixw); 1936 | const float rcp_xd = 1.f / (nodeXY[i + 1].x - nodeXY[i].x); 1937 | for (i32 pixelX = from; pixelX < to; ++pixelX) { 1938 | const float x = (float)pixelX; 1939 | const u32 idx = u32(pixelX + (pixh - pixelY - 1) * pixw); 1940 | const float t = (x - nodeXY[i].x) * rcp_xd; 1941 | const float h = lerp(nodeXY[i].y, nodeXY[i + 1].y, t); 1942 | const Vec2 p(x, y); 1943 | const float center_dist = abs(dot(p - c0, n)); 1944 | float weight = boundary_width < 0.001f 1945 | ? 1.f 1946 | : 1.f - clamp((center_dist - line_width * 0.5f) / boundary_width, 0.f, 1.f); 1947 | weight *= weight; 1948 | 1949 | if (weight > 0.001f) { 1950 | m_height[idx] = (m_weight_sum[idx] * m_height[idx] + h * weight) / (m_weight_sum[idx] + weight); 1951 | m_weight_sum[idx] += weight; 1952 | m_max_weight[idx] = maximum(m_max_weight[idx], weight); 1953 | } 1954 | } 1955 | } 1956 | } 1957 | } 1958 | 1959 | void flattenLine(const DVec3& prev, const DVec3& a, const DVec3& b, const DVec3& next, const Terrain& terrain, float line_width, float boundary_width) { 1960 | ASSERT(terrain.m_heightmap->format == gpu::TextureFormat::R16); 1961 | ASSERT(terrain.m_splatmap); 1962 | 1963 | const float base_y = (float)m_editor.m_app.getWorldEditor().getWorld()->getPosition(terrain.m_entity).y; 1964 | 1965 | const Vec2 a2d = Vec3(a).xz(); 1966 | const Vec2 b2d = Vec3(b).xz(); 1967 | const Vec2 prev2d = Vec3(prev).xz(); 1968 | const Vec2 next2d = Vec3(next).xz(); 1969 | const Vec2 dir = b2d - a2d; 1970 | const Vec2 n0 = normalize(Vec2(dir.y, -dir.x)); 1971 | 1972 | Vec2 n1 = next2d - b2d; 1973 | if (squaredLength(n1) < 1e-3) { 1974 | n1 = dir; 1975 | } 1976 | n1 = normalize(Vec2(n1.y, -n1.x)); 1977 | 1978 | const float half_size = line_width * 0.5f + boundary_width; 1979 | 1980 | Vec2 points[] = { a2d - n0 * half_size 1981 | , a2d + n0 * half_size 1982 | , b2d + n1 * half_size 1983 | , b2d - n1 * half_size 1984 | }; 1985 | 1986 | ASSERT(terrain.m_heightmap->width == terrain.m_heightmap->height); 1987 | const float s = terrain.m_heightmap->height / terrain.getSize().x; 1988 | for (int i = 0; i < 4; ++i) { 1989 | points[i] = toHeightmap(points[i]) - Vec2(0.5f); 1990 | } 1991 | 1992 | flattenQuad(points, (float)a.y - base_y, (float)b.y - base_y, terrain, line_width * s, boundary_width * s); 1993 | } 1994 | 1995 | void run() { 1996 | clearErrors(); 1997 | if (!ensureOSMData()) return; 1998 | 1999 | Terrain* terrain = getTerrain(m_editor.m_app); 2000 | if (!terrain) return error("Terrain not found"); 2001 | 2002 | Texture* hm = terrain->m_heightmap; 2003 | if (!hm) return error("Missing heightmap"); 2004 | if (!hm->isReady()) return error("Heightmap not ready"); 2005 | 2006 | TerrainEditor* terrain_editor = m_editor.getTerrainEditor(); 2007 | if (!terrain_editor) return error("Terrain editor not found"); 2008 | 2009 | m_height.resize(hm->height * hm->width); 2010 | m_weight_sum.resize(m_height.size()); 2011 | m_max_weight.resize(m_height.size()); 2012 | memset(m_max_weight.begin(), 0, m_max_weight.byte_size()); 2013 | memset(m_weight_sum.begin(), 0, m_weight_sum.byte_size()); 2014 | Array polyline(m_editor.m_app.getAllocator()); 2015 | 2016 | for (pugi::xml_node& w : m_editor.m_osm_parser.m_ways) { 2017 | if (!OSMParser::hasTag(w, m_key, m_value)) continue; 2018 | 2019 | polyline.clear(); 2020 | m_editor.m_osm_parser.getWay(w, terrain->m_entity, polyline); 2021 | 2022 | for (i32 i = 0; i < polyline.size() - 1; ++i) { 2023 | flattenLine(polyline[i > 0 ? i - 1 : 0], 2024 | polyline[i], 2025 | polyline[i + 1], 2026 | polyline[i + 2 < polyline.size() ? i + 2 : i + 1], 2027 | *terrain, 2028 | m_line_width, 2029 | m_boundary_width); 2030 | } 2031 | } 2032 | 2033 | OutputMemoryStream new_data(m_editor.m_allocator); 2034 | new_data.resize(hm->width * hm->height * 2); 2035 | u16* hm_data = (u16*)new_data.getMutableData(); 2036 | memcpy(hm_data, hm->getData(), new_data.size()); 2037 | 2038 | for (u32 j = 0; j < hm->height; ++j) { 2039 | for (u32 i = 0; i < hm->width; ++i) { 2040 | const u32 idx = i + j * hm->width; 2041 | if (m_max_weight[idx] > 0.01f) { 2042 | float h = float(hm_data[idx]) * terrain->m_scale.y / 0xffFF; 2043 | h = lerp(h, m_height[idx], m_max_weight[idx]); 2044 | hm_data[idx] = u16(clamp(h * 0xffFF / terrain->m_scale.y, 0.f, 65535.f) + 0.5f); 2045 | } 2046 | } 2047 | } 2048 | 2049 | terrain_editor->updateHeightmap(terrain, static_cast(new_data), 0, 0, hm->width, hm->height); 2050 | } 2051 | 2052 | StaticString<128> m_key; 2053 | StaticString<128> m_value; 2054 | Array m_weight_sum; 2055 | Array m_max_weight; 2056 | Array m_height; // weighted average 2057 | float m_line_width = 3.f; 2058 | float m_boundary_width = 3.f; 2059 | }; 2060 | 2061 | struct PlaceSplinesNode : OSMNodeEditor::Node { 2062 | PlaceSplinesNode(OSMNodeEditor& editor) 2063 | : Node(editor) 2064 | {} 2065 | 2066 | NodeType getType() const override { return NodeType::PLACE_SPLINES; } 2067 | bool hasInputPins() const override { return false; } 2068 | bool hasOutputPins() const override { return false; } 2069 | void serialize(OutputMemoryStream& blob) override {} 2070 | void deserialize(InputMemoryStream& blob) override {} 2071 | UniquePtr getOutputValue(u16 output_idx) override { return {}; } 2072 | 2073 | bool gui() override { 2074 | ImGuiEx::BeginNodeTitleBar(ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 2075 | ImGui::TextUnformatted("Place splines"); 2076 | ImGui::SameLine(); 2077 | if (ImGui::Button(ICON_FA_PLAY)) run(); 2078 | ImGuiEx::EndNodeTitleBar(); 2079 | return tagInput(Span(m_key.data), Span(m_value.data), 150); 2080 | } 2081 | 2082 | void run() { 2083 | clearErrors(); 2084 | if (!ensureOSMData()) return; 2085 | 2086 | StudioApp& app = m_editor.m_app; 2087 | 2088 | Array polyline(app.getAllocator()); 2089 | 2090 | const Terrain* terrain = getTerrain(app); 2091 | if (!terrain) return error("Terrain not found"); 2092 | if (!terrain->m_heightmap) return error("Missing heightmap"); 2093 | if (!terrain->m_heightmap->isReady()) return error("Heightmap not ready"); 2094 | 2095 | WorldEditor& editor = app.getWorldEditor(); 2096 | 2097 | editor.beginCommandGroup("place_splines"); 2098 | SplineEditor* spline_editor = static_cast(app.getIPlugin("spline_editor")); 2099 | Array points(app.getAllocator()); 2100 | for (pugi::xml_node& w : m_editor.m_osm_parser.m_ways) { 2101 | if (!OSMParser::hasTag(w, m_key, m_value)) continue; 2102 | 2103 | polyline.clear(); 2104 | m_editor.m_osm_parser.getWay(w, terrain->m_entity, polyline); 2105 | if (polyline.size() < 3) continue; 2106 | 2107 | const EntityRef entity = editor.addEntityAt(polyline[0]); 2108 | editor.makeParent(terrain->m_entity, entity); 2109 | editor.addComponent(Span(&entity, 1), SPLINE_TYPE); 2110 | 2111 | points.clear(); 2112 | for (const DVec3& p : polyline) { 2113 | points.push(Vec3(p - polyline[0])); 2114 | } 2115 | 2116 | spline_editor->setSplinePoints(entity, points); 2117 | } 2118 | editor.endCommandGroup(); 2119 | editor.selectEntities(Span(&terrain->m_entity, 1), false); 2120 | } 2121 | 2122 | StaticString<128> m_key; 2123 | StaticString<128> m_value; 2124 | float m_width = 1.f; 2125 | }; 2126 | 2127 | struct PlaceInstancesNode : OSMNodeEditor::Node { 2128 | PlaceInstancesNode(OSMNodeEditor& editor) 2129 | : Node(editor) 2130 | , m_models(editor.m_allocator) 2131 | {} 2132 | 2133 | ~PlaceInstancesNode() { 2134 | for (const ModelProbability& mp : m_models) { 2135 | if (mp.resource) mp.resource->decRefCount(); 2136 | } 2137 | } 2138 | 2139 | NodeType getType() const override { return NodeType::PLACE_INSTANCES; } 2140 | bool hasInputPins() const override { return true; } 2141 | bool hasOutputPins() const override { return false; } 2142 | 2143 | void serialize(OutputMemoryStream& blob) override { 2144 | blob.write(m_spacing); 2145 | blob.write(m_models.size()); 2146 | for (const ModelProbability& mp : m_models) { 2147 | blob.writeString(mp.resource ? mp.resource->getPath().c_str() : ""); 2148 | blob.write(mp.distances); 2149 | blob.write(mp.scale); 2150 | blob.write(mp.y_offset); 2151 | blob.write(mp.multiplier); 2152 | } 2153 | } 2154 | 2155 | void deserialize(InputMemoryStream& blob) override { 2156 | ResourceManagerHub& rm = m_editor.m_app.getEngine().getResourceManager(); 2157 | blob.read(m_spacing); 2158 | u32 count; 2159 | blob.read(count); 2160 | m_models.resize(count); 2161 | for (ModelProbability& mp : m_models) { 2162 | const char* path = blob.readString(); 2163 | if (path[0]) { 2164 | mp.resource = rm.load(Path(path)); 2165 | } 2166 | else { 2167 | mp.resource = nullptr; 2168 | } 2169 | blob.read(mp.distances); 2170 | blob.read(mp.scale); 2171 | blob.read(mp.y_offset); 2172 | blob.read(mp.multiplier); 2173 | } 2174 | } 2175 | 2176 | void run() { 2177 | clearErrors(); 2178 | if (!ensureOSMData()) return; 2179 | 2180 | UniquePtr input = getInput(0); 2181 | if (!input) return error("Invalid input"); 2182 | 2183 | DistanceFieldOutput* df = (DistanceFieldOutput*)input.get(); 2184 | 2185 | WorldEditor& editor = m_editor.m_app.getWorldEditor(); 2186 | World* world = editor.getWorld(); 2187 | RenderModule* render_module = (RenderModule*)world->getModule(TERRAIN_TYPE); 2188 | Terrain* terrain = getTerrain(m_editor.m_app); 2189 | if (!terrain) return error("Terrain not found"); 2190 | EntityRef terrain_entity = terrain->m_entity; 2191 | 2192 | const DVec3 terrain_pos = world->getPosition(terrain_entity); 2193 | 2194 | struct Group { 2195 | EntityRef e; 2196 | InstancedModel* im; 2197 | }; 2198 | 2199 | StackArray groups(m_editor.m_app.getAllocator()); 2200 | editor.beginCommandGroup("maps_place_instances"); 2201 | for (const ModelProbability& m : m_models) { 2202 | const EntityRef e = editor.addEntity(); 2203 | groups.emplace().e = e; 2204 | editor.makeParent(terrain_entity, e); 2205 | editor.addComponent(Span(&e, 1), INSTANCED_MODEL_TYPE); 2206 | editor.setProperty(INSTANCED_MODEL_TYPE, "", 0, "Model", Span(&e, 1), m.resource->getPath()); 2207 | editor.setEntitiesPositions(&e, &terrain_pos, 1); 2208 | } 2209 | 2210 | for (Group& g : groups) { 2211 | g.im = &render_module->beginInstancedModelEditing(g.e); 2212 | } 2213 | 2214 | const double y_base = terrain_pos.y; 2215 | const Vec2 size = render_module->getTerrainSize(terrain_entity); 2216 | const Vec2 df_to_terrain = size / Vec2((float)df->m_size, (float)df->m_size); 2217 | const Vec2 terrain_to_df = Vec2((float)df->m_size, (float)df->m_size) / size; 2218 | 2219 | float min_dist = FLT_MAX; 2220 | float max_dist = -FLT_MAX; 2221 | 2222 | for (const ModelProbability& prob : m_models) { 2223 | min_dist = minimum(min_dist, prob.distances.x); 2224 | max_dist = maximum(max_dist, prob.distances.w); 2225 | } 2226 | 2227 | for (float y = 0; y < df->m_size; y += m_spacing * terrain_to_df.y) { 2228 | for (float x = 0; x < df->m_size; x += m_spacing * terrain_to_df.x) { 2229 | float fx = x; 2230 | float fy = y; 2231 | fx = clamp(fx + (randFloat() * 0.9f - 0.45f) * m_spacing * terrain_to_df.x, 0.f, (float)df->m_size - 1); 2232 | fy = clamp(fy + (randFloat() * 0.9f - 0.45f) * m_spacing * terrain_to_df.y, 0.f, (float)df->m_size - 1); 2233 | 2234 | DVec3 pos; 2235 | pos.x = fx * df_to_terrain.x; 2236 | pos.z = fy * df_to_terrain.y; 2237 | 2238 | const i32 idx = i32(fx) + i32(fy) * df->m_size; 2239 | const float distance = df->m_field[idx]; 2240 | if (distance >= min_dist && distance <= max_dist) { 2241 | pos.y = render_module->getTerrainHeightAt(terrain_entity, (float)pos.x, (float)pos.z); 2242 | 2243 | pos.x -= size.x * 0.5f; 2244 | pos.y += y_base; 2245 | pos.z -= size.y * 0.5f; 2246 | 2247 | const u32 r = getRandomItem(distance, m_models); 2248 | if (r != 0xffFFffFF) { 2249 | const Vec2& yoffset_range = m_models[r].y_offset; 2250 | pos.y += lerp(yoffset_range.x, yoffset_range.y, randFloat()); 2251 | 2252 | const Vec2& scale_range = m_models[r].scale; 2253 | const float scale = lerp(scale_range.x, scale_range.y, randFloat()); 2254 | 2255 | InstancedModel::InstanceData& id = groups[r].im->instances.emplace(); 2256 | const Quat rot(Vec3(0, 1, 0), randFloat() * 2 * PI); 2257 | id.pos = Vec3(pos - terrain_pos); 2258 | id.scale = scale; 2259 | id.rot_quat = rot.w < 0 ? -Vec3(rot.x, rot.y, rot.z) : Vec3(rot.x, rot.y, rot.z); 2260 | id.lod = 3; 2261 | } 2262 | } 2263 | } 2264 | } 2265 | 2266 | for (Group& g : groups) { 2267 | render_module->endInstancedModelEditing(g.e); 2268 | } 2269 | 2270 | editor.endCommandGroup(); 2271 | } 2272 | 2273 | UniquePtr getOutputValue(u16 output_idx) override { return {}; } 2274 | 2275 | bool gui() override { 2276 | ImGuiEx::BeginNodeTitleBar(ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 2277 | ImGui::TextUnformatted("Place instances"); 2278 | ImGui::SameLine(); 2279 | if (ImGui::Button(ICON_FA_PLAY)) run(); 2280 | ImGuiEx::EndNodeTitleBar(); 2281 | 2282 | inputSlot(OutputType::DISTANCE_FIELD); 2283 | bool res = ImGui::DragFloat("Spacing", &m_spacing); 2284 | if (ImGui::Button("Edit models")) ImGui::OpenPopup("Edit models"); 2285 | if (ImGui::BeginPopup("Edit models")) { 2286 | for (ModelProbability& mp : m_models) { 2287 | if (ImGui::TreeNode(&mp, "%d", u32(&mp - m_models.begin()))) { 2288 | Path path = mp.resource ? mp.resource->getPath() : Path(""); 2289 | if (m_editor.m_app.getAssetBrowser().resourceInput("model", path, Model::TYPE, 150)) { 2290 | res = true; 2291 | if (mp.resource) mp.resource->decRefCount(); 2292 | ResourceManagerHub& rm = m_editor.m_app.getEngine().getResourceManager(); 2293 | mp.resource = rm.load(path); 2294 | } 2295 | res = ImGui::DragFloat4("Distances", &mp.distances.x) || res; 2296 | res = ImGui::DragFloat2("Scale", &mp.scale.x) || res; 2297 | res = ImGui::DragFloat2("Y Offset", &mp.y_offset.x) || res; 2298 | res = ImGui::DragFloat("Multiplier", &mp.multiplier) || res; 2299 | ImGui::TreePop(); 2300 | } 2301 | } 2302 | if (ImGui::Button("Add")) { 2303 | m_models.emplace(); 2304 | } 2305 | ImGui::EndPopup(); 2306 | } 2307 | return res; 2308 | } 2309 | 2310 | struct ModelProbability { 2311 | Model* resource = nullptr; 2312 | Vec4 distances = Vec4(10, 20, 30, 40); 2313 | Vec2 scale = Vec2(1, 1); 2314 | Vec2 y_offset = Vec2(0, 0); 2315 | float multiplier = 1.f; 2316 | }; 2317 | 2318 | Array m_models; 2319 | float m_spacing = 5.f; 2320 | }; 2321 | 2322 | struct NoiseNode : OSMNodeEditor::Node { 2323 | NoiseNode(OSMNodeEditor& editor) 2324 | : Node(editor) 2325 | {} 2326 | 2327 | NodeType getType() const override { return NodeType::NOISE; } 2328 | bool hasInputPins() const override { return false; } 2329 | bool hasOutputPins() const override { return true; } 2330 | 2331 | void serialize(OutputMemoryStream& blob) override { 2332 | blob.write(m_value); 2333 | blob.write(m_probability); 2334 | } 2335 | 2336 | void deserialize(InputMemoryStream& blob) override { 2337 | blob.read(m_value); 2338 | blob.read(m_probability); 2339 | } 2340 | 2341 | UniquePtr getOutputValue(u16 output_idx) override { 2342 | if (!getTerrain(m_editor.m_app)) return outputError("Terrain not found"); 2343 | 2344 | IAllocator& allocator = m_editor.m_app.getAllocator(); 2345 | UniquePtr output = UniquePtr::create(allocator, allocator); 2346 | output->m_size = getSplatmapSize(m_editor.m_app); 2347 | output->m_bitmap.resize(output->m_size * output->m_size); 2348 | memset(output->m_bitmap.begin(), 0, output->m_bitmap.byte_size()); 2349 | MaskOutput* mask = (MaskOutput*)output.get(); 2350 | 2351 | for (u8& v : mask->m_bitmap) { 2352 | if (randFloat(0, 1) < m_probability) v = m_value; 2353 | } 2354 | return output.move(); 2355 | } 2356 | 2357 | bool gui() override { 2358 | nodeTitle("Noise"); 2359 | outputSlot(); 2360 | bool res = ImGui::DragFloat("Probability", &m_probability, 0.01f, 0.f, 1.f); 2361 | i32 v = m_value; 2362 | if (ImGui::DragInt("Value", &v, 1, 0, 255)) { 2363 | m_value = v; 2364 | res = true; 2365 | } 2366 | return res; 2367 | } 2368 | 2369 | float m_probability = 0.5f; 2370 | u8 m_value = 1; 2371 | }; 2372 | 2373 | struct MaskDistanceNode : OSMNodeEditor::Node { 2374 | MaskDistanceNode(OSMNodeEditor& editor) 2375 | : Node(editor) 2376 | {} 2377 | 2378 | NodeType getType() const override { return NodeType::MASK_DISTANCE; } 2379 | bool hasInputPins() const override { return true; } 2380 | bool hasOutputPins() const override { return true; } 2381 | void serialize(OutputMemoryStream& blob) override {} 2382 | void deserialize(InputMemoryStream& blob) override {} 2383 | 2384 | bool gui() override { 2385 | nodeTitle("Mask distance"); 2386 | inputSlot(OutputType::DISTANCE_FIELD); 2387 | outputSlot(); 2388 | return ImGui::DragFloatRange2("Distance", &m_from, &m_to, 1.f, -FLT_MAX, FLT_MAX, "%.1f", nullptr, ImGuiSliderFlags_AlwaysClamp); 2389 | } 2390 | 2391 | UniquePtr getOutputValue(u16 output_idx) override { 2392 | const UniquePtr input = getInput(output_idx); 2393 | if (!input) return outputError("Invalid input"); 2394 | if (input->getType() != OutputType::DISTANCE_FIELD) return outputError("Invalid input"); 2395 | 2396 | DistanceFieldOutput* df = (DistanceFieldOutput*)input.get(); 2397 | 2398 | IAllocator& allocator = m_editor.m_app.getAllocator(); 2399 | UniquePtr output = UniquePtr::create(allocator, allocator); 2400 | output->m_size = df->m_size; 2401 | output->m_bitmap.resize(output->m_size * output->m_size); 2402 | 2403 | for (u32 j = 0; j < df->m_size; ++j) { 2404 | for (u32 i = 0; i < df->m_size; ++i) { 2405 | const float dist = df->m_field[i + j * df->m_size]; 2406 | output->m_bitmap[i + (df->m_size - j - 1) * df->m_size] = dist >= m_from && dist <= m_to ? 1 : 0; 2407 | } 2408 | } 2409 | 2410 | return output.move(); 2411 | } 2412 | 2413 | float m_from = 0.f; 2414 | float m_to = 1.f; 2415 | }; 2416 | 2417 | struct MaskTextureNode : OSMNodeEditor::Node { 2418 | MaskTextureNode(OSMNodeEditor& editor) 2419 | : Node(editor) 2420 | {} 2421 | 2422 | NodeType getType() const override { return NodeType::MASK_TEXTURE; } 2423 | bool hasInputPins() const override { return false; } 2424 | bool hasOutputPins() const override { return true; } 2425 | 2426 | void serialize(OutputMemoryStream& blob) override { 2427 | blob.write(m_ref); 2428 | blob.write(m_mask_scale); 2429 | blob.writeString(m_texture); 2430 | } 2431 | 2432 | void deserialize(InputMemoryStream& blob) override { 2433 | blob.read(m_ref); 2434 | blob.read(m_mask_scale); 2435 | m_texture = blob.readString(); 2436 | } 2437 | 2438 | static float sampleMask(const u8* mask, Vec2 uv, IVec2 size) { 2439 | const u32 w = size.x; 2440 | const u32 h = size.y; 2441 | const float a = fmodf(uv.x, float(w - 1)); 2442 | const float b = fmodf(uv.y, float(h - 1)); 2443 | 2444 | u32 a0 = u32(a); 2445 | u32 b0 = u32(b); 2446 | const float tx = a - a0; 2447 | const float ty = b - b0; 2448 | a0 = a0 % w; 2449 | b0 = b0 % h; 2450 | 2451 | const float v00 = mask[a0 + b0 * w] / float(0xff); 2452 | const float v10 = mask[a0 + 1 + b0 * w] / float(0xff); 2453 | const float v11 = mask[a0 + 1 + b0 * w + w] / float(0xff); 2454 | const float v01 = mask[a0 + b0 * w + w] / float(0xff); 2455 | 2456 | return lerp( 2457 | lerp(v00, v10, tx), 2458 | lerp(v01, v11, tx), 2459 | ty 2460 | ); 2461 | }; 2462 | 2463 | bool gui() override { 2464 | nodeTitle("Mask texture"); 2465 | outputSlot(); 2466 | bool res = textureMaskInput(m_texture); 2467 | res = ImGui::DragFloat("Ref value", &m_ref, 0.01f, 0.f, 1.f) || res; 2468 | res = ImGui::DragFloat("Mask scale", &m_mask_scale, 0.01f, FLT_MIN, FLT_MAX) || res; 2469 | return res; 2470 | } 2471 | 2472 | UniquePtr getOutputValue(u16 output_idx) override { 2473 | IAllocator& allocator = m_editor.m_app.getAllocator(); 2474 | UniquePtr output = UniquePtr::create(allocator, allocator); 2475 | output->m_size = getSplatmapSize(m_editor.m_app); 2476 | output->m_bitmap.resize(output->m_size * output->m_size); 2477 | u32 mask_w, mask_h; 2478 | stbi_uc* mask = loadTexture(m_texture, mask_w, mask_h, m_editor.m_app); 2479 | if (!mask) return outputError("Failed to load the texture"); 2480 | 2481 | u8* out = output->m_bitmap.begin(); 2482 | const Vec2 rand_offset(randFloat(0, float(mask_w)), randFloat(0, float(mask_h))); 2483 | for (u32 j = 0; j < output->m_size; ++j) { 2484 | for (u32 i = 0; i < output->m_size; ++i) { 2485 | const float k = fmodf(rand_offset.x + i * m_mask_scale, (float)mask_w); 2486 | const float l = fmodf(rand_offset.y + j * m_mask_scale, (float)mask_h); 2487 | u8& m = out[i + j * output->m_size]; 2488 | m = sampleMask(mask, Vec2(k, l), IVec2(mask_w, mask_h)) > m_ref ? 0xff : m; 2489 | } 2490 | } 2491 | stbi_image_free(mask); 2492 | return output.move(); 2493 | } 2494 | 2495 | StaticString m_texture; 2496 | float m_ref = 0.f; 2497 | float m_mask_scale = 1.f; 2498 | }; 2499 | 2500 | struct InvertNode : OSMNodeEditor::Node { 2501 | InvertNode(OSMNodeEditor& editor) 2502 | : Node(editor) 2503 | {} 2504 | 2505 | NodeType getType() const override { return NodeType::INVERT; } 2506 | bool hasInputPins() const override { return true; } 2507 | bool hasOutputPins() const override { return true; } 2508 | void serialize(OutputMemoryStream& blob) override {} 2509 | void deserialize(InputMemoryStream& blob) override {} 2510 | 2511 | UniquePtr getOutputValue(u16 output_idx) override { 2512 | UniquePtr input = getInput(0); 2513 | if (!input) return outputError("Invalid input"); 2514 | if (input->getType() != OutputType::MASK) return outputError("Invalid input"); 2515 | MaskOutput* mask = (MaskOutput*)input.get(); 2516 | 2517 | for (u8& v : mask->m_bitmap) v = v ? 0 : 1; 2518 | return input.move(); 2519 | } 2520 | 2521 | bool gui() override { 2522 | nodeTitle("Invert"); 2523 | inputSlot(); 2524 | outputSlot(); 2525 | ImGui::TextUnformatted(" "); 2526 | return false; 2527 | } 2528 | }; 2529 | 2530 | struct PaintGroundNode : OSMNodeEditor::Node { 2531 | PaintGroundNode(OSMNodeEditor& editor) 2532 | : Node(editor) 2533 | {} 2534 | 2535 | NodeType getType() const override { return NodeType::PAINT_GROUND; } 2536 | bool hasInputPins() const override { return true; } 2537 | bool hasOutputPins() const override { return false; } 2538 | 2539 | void serialize(OutputMemoryStream& blob) override { 2540 | blob.write(m_ground); 2541 | } 2542 | 2543 | void deserialize(InputMemoryStream& blob) override { 2544 | blob.read(m_ground); 2545 | } 2546 | 2547 | UniquePtr getOutputValue(u16 output_idx) override { ASSERT(false); return {nullptr, nullptr}; } 2548 | 2549 | void run() { 2550 | clearErrors(); 2551 | if (!ensureOSMData()) return; 2552 | 2553 | const UniquePtr val = getInput(0); 2554 | if (!val) return error("Invalid input"); 2555 | if (val->getType() != OutputType::MASK) return error("Invalid input"); 2556 | const MaskOutput* mask = (MaskOutput*)val.get(); 2557 | 2558 | Terrain* terrain = getTerrain(m_editor.m_app); 2559 | if (!terrain) return error("Terrain not found"); 2560 | 2561 | TerrainEditor* terrain_editor = m_editor.getTerrainEditor(); 2562 | if (!terrain_editor) return error("Terrain editor not found"); 2563 | 2564 | Texture* splatmap = terrain->getSplatmap(); 2565 | if (!splatmap) return error("Missing splatmap"); 2566 | if (!splatmap->isReady()) return error("Splatmap not ready"); 2567 | 2568 | auto is_masked = [&](u32 x, u32 y){ 2569 | u32 i = u32(x / float(splatmap->width) * mask->m_size); 2570 | u32 j = u32(y / float(splatmap->height) * mask->m_size); 2571 | i = clamp(i, 0, mask->m_size - 1); 2572 | j = clamp(j, 0, mask->m_size - 1); 2573 | 2574 | return mask->m_bitmap[i + j * mask->m_size] != 0; 2575 | }; 2576 | 2577 | OutputMemoryStream new_data(m_editor.m_allocator); 2578 | new_data.resize(splatmap->height * splatmap->width * 4); 2579 | u8* data = new_data.getMutableData(); 2580 | memcpy(data, splatmap->getData(), new_data.size()); 2581 | 2582 | for (u32 y = 0; y < splatmap->height; ++y) { 2583 | for (u32 x = 0; x < splatmap->width; ++x) { 2584 | if (is_masked(x, y)) { 2585 | u8* pixel = data + (x + (splatmap->height - y - 1) * splatmap->width) * 4; 2586 | pixel[0] = m_ground; 2587 | pixel[1] = m_ground; 2588 | } 2589 | } 2590 | } 2591 | terrain_editor->updateSplatmap(terrain, static_cast(new_data), 0, 0, splatmap->width, splatmap->height, false); 2592 | } 2593 | 2594 | bool gui() override { 2595 | ImGuiEx::BeginNodeTitleBar(ImGui::GetColorU32(ImGuiCol_PlotLinesHovered)); 2596 | ImGui::AlignTextToFramePadding(); 2597 | ImGui::TextUnformatted("Paint ground"); 2598 | ImGui::SameLine(); 2599 | if (ImGui::Button(ICON_FA_PLAY)) run(); 2600 | ImGuiEx::EndNodeTitleBar(); 2601 | inputSlot(); 2602 | return ImGui::DragInt("Ground", (i32*)&m_ground, 1, 0, 256); 2603 | } 2604 | 2605 | u32 m_ground = 0; 2606 | }; 2607 | 2608 | struct MaskBaseNode : OSMNodeEditor::Node { 2609 | MaskBaseNode(OSMNodeEditor& editor) 2610 | : Node(editor) 2611 | {} 2612 | 2613 | DVec2 toBitmap(const DVec2& p, u32 size) const { 2614 | DVec2 tmp; 2615 | tmp = p; 2616 | const Terrain* terrain = getTerrain(m_editor.m_app); 2617 | const Vec2 s = terrain->getSize(); 2618 | 2619 | tmp.x += s.x * 0.5f; 2620 | tmp.y += s.y * 0.5f; 2621 | 2622 | tmp.x = tmp.x / s.x * (float)(size - 1); 2623 | tmp.y = (1 - tmp.y / s.y) * (float)(size - 1); 2624 | return tmp; 2625 | } 2626 | 2627 | Vec2 toBitmap(const Vec2& p, u32 size) const { 2628 | Vec2 tmp; 2629 | tmp = p; 2630 | const Terrain* terrain = getTerrain(m_editor.m_app); 2631 | const Vec2 s = terrain->getSize(); 2632 | tmp.x += s.x * 0.5f; 2633 | tmp.y += s.y * 0.5f; 2634 | 2635 | tmp.x = tmp.x / s.x * (float)size; 2636 | tmp.y = (1 - tmp.y / s.y) * (float)size; 2637 | return tmp; 2638 | } 2639 | 2640 | UniquePtr createOutput() { 2641 | if (!getTerrain(m_editor.m_app)) return outputError("Terrain not found"); 2642 | 2643 | IAllocator& allocator = m_editor.m_app.getAllocator(); 2644 | UniquePtr output = UniquePtr::create(allocator, allocator); 2645 | output->m_size = getSplatmapSize(m_editor.m_app); 2646 | output->m_bitmap.resize(output->m_size * output->m_size); 2647 | memset(output->m_bitmap.begin(), 0, output->m_bitmap.byte_size()); 2648 | return output.move(); 2649 | } 2650 | 2651 | bool commonGUI() { 2652 | outputSlot(); 2653 | return tagInput(Span(m_key.data), Span(m_value.data), 150); 2654 | } 2655 | 2656 | StaticString<128> m_key; 2657 | StaticString<128> m_value; 2658 | }; 2659 | 2660 | struct MaskPolylinesNode : MaskBaseNode { 2661 | MaskPolylinesNode(OSMNodeEditor& editor) 2662 | : MaskBaseNode(editor) 2663 | {} 2664 | 2665 | NodeType getType() const override { return NodeType::MASK_POLYLINES; } 2666 | bool hasInputPins() const override { return true; } 2667 | bool hasOutputPins() const override { return true; } 2668 | 2669 | void serialize(OutputMemoryStream& blob) override { 2670 | blob.write(m_width); 2671 | blob.write(m_key); 2672 | blob.write(m_value); 2673 | } 2674 | 2675 | void deserialize(InputMemoryStream& blob) override { 2676 | blob.read(m_width); 2677 | blob.read(m_key); 2678 | blob.read(m_value); 2679 | } 2680 | 2681 | UniquePtr getOutputValue(u16 output_idx) override { 2682 | StudioApp& app = m_editor.m_app; 2683 | Array polyline(app.getAllocator()); 2684 | UniquePtr output = createOutput(); 2685 | if (!output.get()) return output; 2686 | 2687 | for (pugi::xml_node& w : m_editor.m_osm_parser.m_ways) { 2688 | if (!OSMParser::hasTag(w, m_key, m_value)) continue; 2689 | 2690 | polyline.clear(); 2691 | m_editor.m_osm_parser.getWay(w, polyline); 2692 | for (i32 i = 0; i < polyline.size() - 1; ++i) { 2693 | const DVec2 dir = normalize(polyline[i + 1] - polyline[i]) * 0.5 * double(m_width); 2694 | const DVec2 n = DVec2(dir.y, -dir.x); 2695 | const DVec2 a = toBitmap(polyline[i] + n - dir, output->m_size); 2696 | const DVec2 b = toBitmap(polyline[i + 1] + n + dir, output->m_size); 2697 | const DVec2 c = toBitmap(polyline[i + 1] - n + dir, output->m_size); 2698 | const DVec2 d = toBitmap(polyline[i] - n - dir, output->m_size); 2699 | const Vec2 vecs[] = {Vec2(a), Vec2(b), Vec2(c), Vec2(d), Vec2(a)}; 2700 | raster(Span(vecs), output->m_size, 1, output->m_bitmap); 2701 | } 2702 | } 2703 | return output.move(); 2704 | } 2705 | 2706 | bool gui() override { 2707 | nodeTitle("Mask polylines"); 2708 | bool res = commonGUI(); 2709 | res = ImGui::DragFloat("Width", &m_width, 0.1f, FLT_MIN, FLT_MAX) || res; 2710 | return res; 2711 | } 2712 | 2713 | float m_width = 1; 2714 | }; 2715 | 2716 | struct MaskPolygonsNode : MaskBaseNode { 2717 | MaskPolygonsNode(OSMNodeEditor& editor) 2718 | : MaskBaseNode(editor) 2719 | {} 2720 | 2721 | NodeType getType() const override { return NodeType::MASK_POLYGONS; } 2722 | bool hasInputPins() const override { return false; } 2723 | bool hasOutputPins() const override { return true; } 2724 | 2725 | void serialize(OutputMemoryStream& blob) override { 2726 | blob.write(m_key); 2727 | blob.write(m_value); 2728 | } 2729 | 2730 | void deserialize(InputMemoryStream& blob) override { 2731 | blob.read(m_key); 2732 | blob.read(m_value); 2733 | } 2734 | 2735 | UniquePtr getOutputValue(u16 output_idx) override { 2736 | StudioApp& app = m_editor.m_app; 2737 | Array polygon(app.getAllocator()); 2738 | Array points(app.getAllocator()); 2739 | 2740 | UniquePtr output = createOutput(); 2741 | if (!output.get()) return output; 2742 | 2743 | Multipolygon2D multipolygon(app.getAllocator()); 2744 | for (pugi::xml_node& r : m_editor.m_osm_parser.m_relations) { 2745 | if (!OSMParser::hasTag(r,m_key, m_value)) continue; 2746 | 2747 | m_editor.m_osm_parser.getMultipolygon(r, multipolygon); 2748 | 2749 | for (const Polygon2D& poly : multipolygon.outer_polygons) { 2750 | points.clear(); 2751 | for (const DVec2 p : poly) { 2752 | const DVec2 tmp = toBitmap(p, output->m_size); 2753 | points.push(Vec2(tmp)); 2754 | } 2755 | raster(points, output->m_size, 1, output->m_bitmap); 2756 | } 2757 | 2758 | for (const Polygon2D& poly : multipolygon.inner_polygons) { 2759 | points.clear(); 2760 | for (const DVec2 p : poly) { 2761 | DVec2 tmp = toBitmap(p, output->m_size); 2762 | points.push(Vec2(tmp)); 2763 | } 2764 | raster(points, output->m_size, -1, output->m_bitmap); 2765 | } 2766 | } 2767 | 2768 | for (pugi::xml_node& w : m_editor.m_osm_parser.m_ways) { 2769 | if (!OSMParser::hasTag(w, m_key, m_value)) continue; 2770 | 2771 | polygon.clear(); 2772 | points.clear(); 2773 | m_editor.m_osm_parser.getWay(w, polygon); 2774 | 2775 | for (const DVec2 p : polygon) { 2776 | DVec2 tmp = toBitmap(p, output->m_size); 2777 | points.push(Vec2(tmp)); 2778 | } 2779 | raster(points, output->m_size, 1, output->m_bitmap); 2780 | } 2781 | 2782 | return output.move(); 2783 | } 2784 | 2785 | bool gui() override { 2786 | nodeTitle("Mask polygons"); 2787 | return commonGUI(); 2788 | } 2789 | 2790 | }; 2791 | 2792 | void OSMNodeEditor::run() { 2793 | for (Node* node : m_nodes) node->m_error = ""; 2794 | 2795 | if (!getTerrain(m_app)) return; 2796 | 2797 | for (Node* node : m_nodes) { 2798 | switch (node->getType()) { 2799 | case NodeType::GRASS: 2800 | ((GrassNode*)node)->run(); 2801 | break; 2802 | case NodeType::PLACE_SPLINES: 2803 | ((PlaceSplinesNode*)node)->run(); 2804 | break; 2805 | case NodeType::PLACE_INSTANCES: 2806 | ((PlaceInstancesNode*)node)->run(); 2807 | break; 2808 | case NodeType::ADJUST_HEIGHT: 2809 | ((AdjustHeightNode*)node)->run(); 2810 | break; 2811 | case NodeType::PAINT_GROUND: 2812 | ((PaintGroundNode*)node)->run(); 2813 | break; 2814 | default: 2815 | break; 2816 | } 2817 | } 2818 | } 2819 | 2820 | OSMNodeEditor::Node* OSMNodeEditor::addNode(NodeType type, ImVec2 pos) { 2821 | Node* node = nullptr; 2822 | switch(type) { 2823 | case NodeType::MASK_POLYLINES: node = LUMIX_NEW(m_allocator, MaskPolylinesNode)(*this); break; 2824 | case NodeType::MASK_POLYGONS: node = LUMIX_NEW(m_allocator, MaskPolygonsNode)(*this); break; 2825 | case NodeType::PAINT_GROUND: node = LUMIX_NEW(m_allocator, PaintGroundNode)(*this); break; 2826 | case NodeType::INVERT: node = LUMIX_NEW(m_allocator, InvertNode)(*this); break; 2827 | case NodeType::GRASS: node = LUMIX_NEW(m_allocator, GrassNode)(*this); break; 2828 | case NodeType::NOISE: node = LUMIX_NEW(m_allocator, NoiseNode)(*this); break; 2829 | case NodeType::MASK_TEXTURE: node = LUMIX_NEW(m_allocator, MaskTextureNode)(*this); break; 2830 | case NodeType::MERGE_MASKS: node = LUMIX_NEW(m_allocator, MergeMasksNode)(*this); break; 2831 | case NodeType::DISTANCE_FIELD: node = LUMIX_NEW(m_allocator, DistanceFieldNode)(*this); break; 2832 | case NodeType::PLACE_INSTANCES: node = LUMIX_NEW(m_allocator, PlaceInstancesNode)(*this); break; 2833 | case NodeType::PLACE_SPLINES: node = LUMIX_NEW(m_allocator, PlaceSplinesNode)(*this); break; 2834 | case NodeType::ADJUST_HEIGHT: node = LUMIX_NEW(m_allocator, AdjustHeightNode)(*this); break; 2835 | case NodeType::FLATTEN_POLYLINES: node = LUMIX_NEW(m_allocator, FlattenPolylinesNode)(*this); break; 2836 | case NodeType::MASK_DISTANCE: node = LUMIX_NEW(m_allocator, MaskDistanceNode)(*this); break; 2837 | case NodeType::MERGE_DISTANCE_FIELDS: node = LUMIX_NEW(m_allocator, MergeDistanceFieldsNode)(*this); break; 2838 | } 2839 | node->m_pos = pos; 2840 | node->m_id = ++m_node_id_genereator; 2841 | m_nodes.push(node); 2842 | return node; 2843 | } 2844 | 2845 | struct TileLoc { 2846 | TileLoc() {} 2847 | TileLoc(int _x, int _y, int z) 2848 | : z(z) 2849 | { 2850 | const int size = 1 << z; 2851 | x = (_x + size) % size; 2852 | y = (_y + size) % size; 2853 | } 2854 | bool operator==(const TileLoc& rhs) { 2855 | return x == rhs.x && y == rhs.y && z == rhs.z; 2856 | } 2857 | int x, y, z; 2858 | }; 2859 | 2860 | constexpr int TILE_SIZE = 256; 2861 | constexpr int MAX_ZOOM = 18; 2862 | constexpr float MAP_UI_SIZE = 512; 2863 | 2864 | struct MapsPlugin final : public StudioApp::GUIPlugin 2865 | { 2866 | struct MapsTask; 2867 | 2868 | struct TileData { 2869 | TileData(int x, int y, int zoom, IAllocator& allocator) 2870 | : imagery_data(allocator) 2871 | , hm_data(allocator) 2872 | { 2873 | const int size = 1 << zoom; 2874 | loc.x = (x + size) % size; 2875 | loc.y = (y + size) % size; 2876 | loc.z = zoom; 2877 | 2878 | imagery_data.resize(TILE_SIZE * TILE_SIZE); 2879 | hm_data.resize(TILE_SIZE * TILE_SIZE); 2880 | memset(imagery_data.begin(), 0xff, imagery_data.byte_size()); 2881 | memset(hm_data.begin(), 0xff, hm_data.byte_size()); 2882 | } 2883 | 2884 | TileLoc loc; 2885 | ImTextureID imagery = (ImTextureID)(intptr_t)0xffFFffFF; 2886 | ImTextureID hm = (ImTextureID)(intptr_t)0xffFFffFF; 2887 | Array imagery_data; 2888 | Array hm_data; 2889 | MapsTask* hm_task = nullptr; 2890 | MapsTask* imagery_task = nullptr; 2891 | }; 2892 | 2893 | struct Area { 2894 | Area(IAllocator& allocator) : prefabs(allocator) {} 2895 | 2896 | u16 grass = 0xffff; 2897 | u8 ground = 0xff; 2898 | bool inverted = false; 2899 | float spacing = 1; 2900 | StaticString<64> key = ""; 2901 | StaticString<64> value = ""; 2902 | Array prefabs; 2903 | }; 2904 | 2905 | struct MapsTask : public Thread 2906 | { 2907 | MapsTask(StudioApp& app, TileData* tile, IAllocator& _allocator) 2908 | : Thread(_allocator) 2909 | , app(&app) 2910 | , allocator(_allocator) 2911 | , tile(*tile) 2912 | { 2913 | } 2914 | 2915 | 2916 | static int getHTTPHeaderLength(Span data) 2917 | { 2918 | for (u32 i = 0; i < data.length() - 4; ++i) 2919 | { 2920 | if (data[i] == '\r' && data[i + 1] == '\n' && data[i + 2] == '\r' && data[i + 3] == '\n') 2921 | { 2922 | return i + 4; 2923 | } 2924 | } 2925 | return 0; 2926 | } 2927 | 2928 | bool parseImage(Span data) const 2929 | { 2930 | int header_size = getHTTPHeaderLength(data); 2931 | 2932 | int channels, w, h; 2933 | int image_size = data.length() - header_size; 2934 | stbi_uc* pixels = stbi_load_from_memory(&data[header_size], image_size, &w, &h, &channels, 4); 2935 | if (!pixels || canceled) return false; 2936 | 2937 | ASSERT(w == 256 && h == 256); 2938 | int row_size = w * sizeof(u32); 2939 | u8* out = is_heightmap ? (u8*)tile.hm_data.begin() : (u8*)tile.imagery_data.begin(); 2940 | for (int j = 0; j < h; ++j) { 2941 | memcpy(&out[j * row_size], &pixels[j * row_size], row_size); 2942 | } 2943 | stbi_image_free(pixels); 2944 | return true; 2945 | } 2946 | 2947 | 2948 | bool loadFromCache() { 2949 | FileSystem& fs = app->getWorldEditor().getEngine().getFileSystem(); 2950 | const StaticString path(".lumix/maps_cache", "/", is_heightmap ? "hm" : "im", tile.loc.z, "_", tile.loc.x, "_", tile.loc.y); 2951 | 2952 | os::InputFile file; 2953 | if (fs.open(path, file)) { 2954 | u8* out = is_heightmap ? (u8*)tile.hm_data.begin() : (u8*)tile.imagery_data.begin(); 2955 | const bool res = file.read(out, TILE_SIZE * TILE_SIZE * sizeof(u32)); 2956 | file.close(); 2957 | return res; 2958 | } 2959 | return false; 2960 | } 2961 | 2962 | 2963 | void saveToCache() { 2964 | FileSystem& fs = app->getWorldEditor().getEngine().getFileSystem(); 2965 | const StaticString dir(fs.getBasePath(), ".lumix/maps_cache"); 2966 | if (!os::makePath(dir)) logError("Could not create", dir); 2967 | 2968 | const StaticString path(dir, "/", is_heightmap ? "hm" : "im", tile.loc.z, "_", tile.loc.x, "_", tile.loc.y); 2969 | os::OutputFile file; 2970 | if (file.open(path)) { 2971 | u8* out = is_heightmap ? (u8*)tile.hm_data.begin() : (u8*)tile.imagery_data.begin(); 2972 | if (!file.write(out, TILE_SIZE * TILE_SIZE * sizeof(u32))) { 2973 | logError("Could not write ", path); 2974 | } 2975 | file.close(); 2976 | } 2977 | else { 2978 | logError("Fail to create ", path); 2979 | } 2980 | } 2981 | 2982 | 2983 | int task() override { 2984 | if (loadFromCache()) return 0; 2985 | 2986 | String url("https://", allocator); 2987 | url.append(host, path); 2988 | OutputMemoryStream data(allocator); 2989 | u32 local_downloaded_bytes; 2990 | if (!::download(url.c_str(), data, local_downloaded_bytes)) { 2991 | logError("Failed to download ", url); 2992 | return -1; 2993 | } 2994 | 2995 | downloaded_bytes->add(local_downloaded_bytes); 2996 | const bool res = parseImage(data); 2997 | if (res) saveToCache(); 2998 | return res ? 0 : -1; 2999 | } 3000 | 3001 | StaticString host; 3002 | StaticString<1024> path; 3003 | IAllocator& allocator; 3004 | AtomicI32* downloaded_bytes; 3005 | volatile bool canceled = false; 3006 | StudioApp* app; 3007 | TileData& tile; 3008 | bool is_heightmap; 3009 | }; 3010 | 3011 | MapsPlugin(StudioApp& app) 3012 | : m_app(app) 3013 | , m_open(false) 3014 | , m_tiles(app.getAllocator()) 3015 | , m_cache(app.getAllocator()) 3016 | , m_in_progress(app.getAllocator()) 3017 | , m_queue(app.getAllocator()) 3018 | , m_bitmap(app.getAllocator()) 3019 | , m_osm_editor(*this, app) 3020 | { 3021 | m_out_path[0] = '\0'; 3022 | Settings& settings = m_app.getSettings(); 3023 | settings.registerOption("maps_open", &m_open); 3024 | settings.registerOption("maps_x", &m_x); 3025 | settings.registerOption("maps_y", &m_y); 3026 | settings.registerOption("maps_scale", &m_scale); 3027 | settings.registerOption("maps_resample", &m_resample_hm); 3028 | settings.registerOption("maps_zoom", &m_zoom); 3029 | settings.registerOption("maps_offset_x", &m_pixel_offset.x); 3030 | settings.registerOption("maps_offset_y", &m_pixel_offset.y); 3031 | settings.registerOption("maps_size", &m_size); 3032 | settings.registerOption("maps_osm_area_edge", &m_osm_editor.m_area_edge); 3033 | } 3034 | 3035 | ~MapsPlugin() 3036 | { 3037 | finishAllTasks(); 3038 | clear(); 3039 | } 3040 | 3041 | void finishAllTasks() 3042 | { 3043 | IAllocator& allocator = m_app.getWorldEditor().getEngine().getAllocator(); 3044 | for (MapsTask* task : m_in_progress) { 3045 | task->canceled = true; 3046 | task->tile.hm_task = nullptr; 3047 | task->tile.imagery_task = nullptr; 3048 | 3049 | task->destroy(); 3050 | LUMIX_DELETE(allocator, task); 3051 | } 3052 | for (MapsTask* task : m_queue) { 3053 | task->tile.hm_task = nullptr; 3054 | task->tile.imagery_task = nullptr; 3055 | LUMIX_DELETE(allocator, task); 3056 | } 3057 | m_queue.clear(); 3058 | m_in_progress.clear(); 3059 | } 3060 | 3061 | const char* getName() const override { return "maps"; } 3062 | 3063 | DVec2 getCenter() { 3064 | int size = 1 << m_zoom; 3065 | DVec2 res; 3066 | 3067 | const double half = m_size == 0 ? 0.5 / double(size) : (1 << (m_size - 1)) / double(size); 3068 | 3069 | res.x = ((m_x + size) % size) / double(size); 3070 | res.y = ((m_y + size) % size) / double(size); 3071 | res.x += half; 3072 | res.y += half; 3073 | res.x -= m_pixel_offset.x * pixelToWorld(); 3074 | res.y -= m_pixel_offset.y * pixelToWorld(); 3075 | return res; 3076 | } 3077 | 3078 | void setCorner(const DVec2& p) { 3079 | const double worldToPixel = 1.0 / pixelToWorld(); 3080 | 3081 | const i32 size = 1 << m_zoom; 3082 | m_x = i32(p.x * size); 3083 | m_y = i32(p.y * size); 3084 | 3085 | const double dx = m_x / double(size) - p.x; 3086 | const double dy = m_y / double(size) - p.y; 3087 | 3088 | m_pixel_offset.x = i32(worldToPixel * dx); 3089 | m_pixel_offset.y = i32(worldToPixel * dy); 3090 | 3091 | download(); 3092 | } 3093 | 3094 | DVec2 getCorner() { 3095 | int size = 1 << m_zoom; 3096 | DVec2 res; 3097 | 3098 | res.x = ((m_x + size) % size) / double(size); 3099 | res.y = ((m_y + size) % size) / double(size); 3100 | res.x -= m_pixel_offset.x * pixelToWorld(); 3101 | res.y -= m_pixel_offset.y * pixelToWorld(); 3102 | return res; 3103 | } 3104 | 3105 | void clear() 3106 | { 3107 | IAllocator& allocator = m_app.getEngine().getAllocator(); 3108 | RenderInterface* ri = m_app.getRenderInterface(); 3109 | if (ri) { 3110 | for (TileData* tile : m_tiles) { 3111 | ri->destroyTexture(tile->imagery); 3112 | ri->destroyTexture(tile->hm); 3113 | 3114 | } 3115 | for (TileData* tile : m_cache) { 3116 | ri->destroyTexture(tile->imagery); 3117 | ri->destroyTexture(tile->hm); 3118 | LUMIX_DELETE(allocator, tile); 3119 | } 3120 | } 3121 | for (TileData* tile : m_tiles) LUMIX_DELETE(allocator, tile); 3122 | for (TileData* tile : m_cache) LUMIX_DELETE(allocator, tile); 3123 | m_tiles.clear(); 3124 | m_cache.clear(); 3125 | } 3126 | 3127 | static void getHeightmapPath(char* url, const TileLoc& loc) 3128 | { 3129 | int size = 1 << loc.z; 3130 | sprintf(url, 3131 | "/elevation-tiles-prod/terrarium/%d/%d/%d.png", 3132 | loc.z, 3133 | loc.x % size, 3134 | loc.y % size); 3135 | } 3136 | 3137 | static void getSatellitePath(char* url, const TileLoc& loc) 3138 | { 3139 | const int size = 1 << loc.z; 3140 | sprintf(url, 3141 | "/wmts/1.0.0/s2cloudless-2017_3857/default/g/%d/%d/%d.jpg", 3142 | loc.z, 3143 | loc.y % size, 3144 | loc.x % size); 3145 | } 3146 | 3147 | void createTile(const TileLoc& loc) { 3148 | for (TileData* tile : m_cache) { 3149 | if (tile->loc == loc) { 3150 | m_tiles.push(tile); 3151 | m_cache.eraseItem(tile); 3152 | return; 3153 | } 3154 | } 3155 | for (TileData* tile : m_tiles) { 3156 | if (tile->loc == loc) return; 3157 | } 3158 | 3159 | WorldEditor& editor = m_app.getWorldEditor(); 3160 | IAllocator& allocator = editor.getEngine().getAllocator(); 3161 | TileData& tile = *m_tiles.emplace(LUMIX_NEW(allocator, TileData)(loc.x, loc.y, loc.z, allocator)); 3162 | 3163 | const int map_size = TILE_SIZE * (1 << m_size); 3164 | 3165 | char url[1024]; 3166 | { 3167 | getSatellitePath(url, loc); 3168 | MapsTask* task = LUMIX_NEW(allocator, MapsTask)(m_app, &tile, allocator); 3169 | // https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2017_3857/default/g/2/1/1.jpg 3170 | task->host = "tiles.maps.eox.at"; 3171 | task->path = url; 3172 | task->downloaded_bytes = &m_downloaded_bytes; 3173 | task->is_heightmap = false; 3174 | tile.imagery_task = task; 3175 | m_queue.push(task); 3176 | } 3177 | 3178 | { 3179 | getHeightmapPath(url, loc); 3180 | MapsTask* task = LUMIX_NEW(allocator, MapsTask)(m_app, &tile, allocator); 3181 | task->host = "s3.amazonaws.com"; 3182 | task->path = url; 3183 | task->downloaded_bytes = &m_downloaded_bytes; 3184 | task->is_heightmap = true; 3185 | tile.hm_task = task; 3186 | m_queue.push(task); 3187 | } 3188 | } 3189 | 3190 | bool shouldLoad(const TileLoc& loc) const { 3191 | if (loc.z != m_zoom) return false; 3192 | 3193 | int x = m_x; 3194 | int y = m_y; 3195 | int size = 1 << m_zoom; 3196 | if (loc.x - x > size) x += size; 3197 | if (loc.y - y > size) y += size; 3198 | if (loc.x - x < -size) x -= size; 3199 | if (loc.y - y < -size) y -= size; 3200 | 3201 | const int right_edge = m_pixel_offset.x < 0; 3202 | const int left_edge = m_pixel_offset.x > 0; 3203 | const int bottom_edge = m_pixel_offset.y < 0; 3204 | const int top_edge = m_pixel_offset.y > 0; 3205 | if (loc.x < m_x - left_edge) return false; 3206 | if (loc.y < m_y - top_edge) return false; 3207 | if (loc.x > m_x + ((1 << m_size) - 1) + right_edge) return false; 3208 | if (loc.y > m_y + ((1 << m_size) - 1) + bottom_edge) return false; 3209 | return true; 3210 | } 3211 | 3212 | void download() 3213 | { 3214 | m_is_download_deferred = false; 3215 | 3216 | for (i32 i = m_tiles.size() - 1; i >= 0; --i) { 3217 | if (!shouldLoad(m_tiles[i]->loc)) { 3218 | m_cache.push(m_tiles[i]); 3219 | m_tiles.swapAndPop(i); 3220 | } 3221 | } 3222 | 3223 | for (int j = -1; j < (1 << m_size) + 1; ++j) { 3224 | for (int i = -1; i < (1 << m_size) + 1; ++i) { 3225 | const TileLoc loc(m_x + i, m_y + j, m_zoom); 3226 | if (shouldLoad(loc)) { 3227 | createTile(loc); 3228 | } 3229 | } 3230 | } 3231 | 3232 | } 3233 | 3234 | void createMapEntity() { 3235 | const double lat = double(tiley2lat(double(m_y + (1 << (m_size - 1))), m_zoom)); 3236 | const double width = m_scale * 256 * (1 << m_size) * 156543.03 * cos(degreesToRadians(lat)) / (1 << m_zoom); 3237 | WorldEditor& editor = m_app.getWorldEditor(); 3238 | const EntityRef e = editor.addEntityAt({-width * 0.5, m_last_saved_hm_range.x, -width * 0.5}); 3239 | editor.addComponent(Span(&e, 1), TERRAIN_TYPE); 3240 | const PathInfo file_info(m_out_path); 3241 | Path mat_path(file_info.dir, "/", file_info.basename, ".mat"); 3242 | 3243 | editor.setProperty(TERRAIN_TYPE, "", -1, "Material", Span(&e, 1), mat_path); 3244 | 3245 | const float scale = float(width / ((1 << m_size) * 256)); 3246 | editor.setProperty(TERRAIN_TYPE, "", -1, "XZ scale", Span(&e, 1), scale / m_resample_hm); 3247 | editor.setProperty(TERRAIN_TYPE, "", -1, "Height scale", Span(&e, 1), m_scale * (m_last_saved_hm_range.y - m_last_saved_hm_range.x)); 3248 | } 3249 | 3250 | void resample(Array& raw, i32 map_size, i32 scale) { 3251 | Array tmp(m_app.getAllocator()); 3252 | const i32 stride = map_size * scale; 3253 | tmp.resize(map_size * map_size * scale * scale); 3254 | for (i32 j = 0; j < map_size * scale; ++j) { 3255 | for (i32 i = 0; i < map_size * scale; ++i) { 3256 | const double u = i / double(map_size * scale - 1) * (map_size - 1); 3257 | const double v = j / double(map_size * scale - 1) * (map_size - 1); 3258 | 3259 | const i32 m = i32(u); 3260 | const i32 n = i32(v); 3261 | const i32 ma = minimum(m + 1, map_size - 1); 3262 | const i32 na = minimum(n + 1, map_size - 1); 3263 | const float tx = float(u - m); 3264 | const float ty = float(v - n); 3265 | 3266 | const u16 h00 = raw[m + n * map_size]; 3267 | const u16 h10 = raw[ma + n * map_size]; 3268 | const u16 h01 = raw[m + na * map_size]; 3269 | const u16 h11 = raw[ma + na * map_size]; 3270 | 3271 | const float h = lerp( 3272 | lerp((float)h00, (float)h10, tx), 3273 | lerp((float)h01, (float)h11, tx), 3274 | ty); 3275 | tmp[i + j * stride] = u16(h + 0.5f); 3276 | } 3277 | } 3278 | raw.resize(tmp.size()); 3279 | memcpy(raw.begin(), tmp.begin(), tmp.byte_size()); 3280 | } 3281 | 3282 | static float toFloatHeight(u32 height) { 3283 | return (height >> 16) * 256.f + ((height >> 8) & 0xff) * 1.f + (height & 0xff) / 256.f - 32768.f; 3284 | } 3285 | 3286 | void save() 3287 | { 3288 | ASSERT(m_out_path[0]); 3289 | union RGBA 3290 | { 3291 | struct { u8 r, g, b, a; }; 3292 | u32 rgba; 3293 | }; 3294 | 3295 | Array raw(m_app.getWorldEditor().getAllocator()); 3296 | int map_size = TILE_SIZE * (1 << m_size); 3297 | raw.resize(map_size * map_size); 3298 | 3299 | auto for_each = [&](auto f){ 3300 | for (TileData* tile : m_tiles) { 3301 | const RGBA* in = (const RGBA*)tile->hm_data.begin(); 3302 | 3303 | const ImVec2 p = getPos(*tile); 3304 | 3305 | for (u32 j = 0; j < TILE_SIZE; ++j) { 3306 | for (u32 i = 0; i < TILE_SIZE; ++i) { 3307 | const i32 x = i32(p.x) + i; 3308 | const i32 y = i32(p.y) + j; 3309 | 3310 | if (x < 0) continue; 3311 | if (y < 0) continue; 3312 | if (x >= map_size) continue; 3313 | if (y >= map_size) continue; 3314 | 3315 | const RGBA rgba = in[i + j * TILE_SIZE]; 3316 | f(x, y, rgba); 3317 | } 3318 | } 3319 | } 3320 | }; 3321 | 3322 | u32 min = 0xffffFFFF; 3323 | u32 max = 0; 3324 | 3325 | for_each([&](i32 x, i32 y, const RGBA rgba){ 3326 | const u32 p = u32((rgba.r << 16) + (rgba.g << 8) + rgba.b); 3327 | if (p != 0xffFFff) { 3328 | if (max < p) max = p; 3329 | if (min > p) min = p; 3330 | } 3331 | }); 3332 | 3333 | m_last_saved_hm_range.x = toFloatHeight(min); 3334 | m_last_saved_hm_range.y = toFloatHeight(max); 3335 | 3336 | double diff = max - min; 3337 | for_each([&](i32 x, i32 y, const RGBA rgba){ 3338 | const i32 o = x + y * map_size; 3339 | u32 p = u32((rgba.r << 16) + (rgba.g << 8) + rgba.b); 3340 | raw[o] = u16((double(p - min) / diff) * 0xffff); 3341 | }); 3342 | 3343 | Array imagery(m_app.getWorldEditor().getAllocator()); 3344 | imagery.resize(map_size * map_size); 3345 | for (TileData* tile : m_tiles) { 3346 | const u32* in = tile->imagery_data.begin(); 3347 | 3348 | const ImVec2 p = getPos(*tile); 3349 | 3350 | for (u32 j = 0; j < TILE_SIZE; ++j) { 3351 | for (u32 i = 0; i < TILE_SIZE; ++i) { 3352 | const i32 x = i32(p.x) + i; 3353 | const i32 y = i32(p.y) + j; 3354 | 3355 | if (x < 0) continue; 3356 | if (y < 0) continue; 3357 | if (x >= map_size) continue; 3358 | if (y >= map_size) continue; 3359 | 3360 | const i32 o = x + y * map_size; 3361 | imagery[o] = in[i + j * TILE_SIZE]; 3362 | } 3363 | } 3364 | } 3365 | 3366 | WorldEditor& editor = m_app.getWorldEditor(); 3367 | IAllocator& allocator = editor.getAllocator(); 3368 | os::OutputFile file; 3369 | FileSystem& fs = m_app.getEngine().getFileSystem(); 3370 | if (!fs.open(m_out_path, file)) { 3371 | logError("Failed to save ", m_out_path); 3372 | return; 3373 | } 3374 | RawTextureHeader header; 3375 | header.channels_count = 1; 3376 | header.channel_type = RawTextureHeader::ChannelType::U16; 3377 | header.depth = 1; 3378 | header.is_array = false; 3379 | header.width = map_size * m_resample_hm; 3380 | header.height = map_size * m_resample_hm; 3381 | bool success = file.write(&header, sizeof(header)); 3382 | if (m_resample_hm > 1) { 3383 | resample(raw, map_size, m_resample_hm); 3384 | } 3385 | success = file.write(&raw[0], raw.byte_size()) && success; 3386 | if (!success) { 3387 | logError("Could not write ", m_out_path); 3388 | } 3389 | file.close(); 3390 | 3391 | RenderInterface* ri = m_app.getRenderInterface(); 3392 | PathInfo file_info(m_out_path); 3393 | StaticString satellite_path(file_info.dir, "/", file_info.basename, ".tga"); 3394 | ri->saveTexture(editor.getEngine(), satellite_path, imagery.begin(), map_size, map_size, true); 3395 | 3396 | const StaticString albedo_path(file_info.dir, "albedo_detail.ltc"); 3397 | const StaticString normal_path(file_info.dir, "normal_detail.ltc"); 3398 | const StaticString splatmap_path(file_info.dir, "splatmap.tga"); 3399 | const StaticString splatmap_meta_path(file_info.dir, "splatmap.tga.meta"); 3400 | 3401 | if (!fs.open(splatmap_meta_path, file)) { 3402 | logError("Failed to create ", splatmap_meta_path); 3403 | } 3404 | else { 3405 | file << "compress = false\n"; 3406 | file << "mips = false\n"; 3407 | file.close(); 3408 | } 3409 | 3410 | if (!fs.fileExists(splatmap_path)) { 3411 | if (!fs.open(splatmap_path, file)) { 3412 | logError("Failed to create ", splatmap_path); 3413 | } 3414 | else { 3415 | OutputMemoryStream splatmap(m_app.getAllocator()); 3416 | splatmap.resize(header.width * header.height * 4); 3417 | memset(splatmap.getMutableData(), 0, splatmap.size()); 3418 | Texture::saveTGA(&file, header.width, header.height, gpu::TextureFormat::RGBA8, splatmap.data(), true, Path(splatmap_path), m_app.getAllocator()); 3419 | file.close(); 3420 | } 3421 | } 3422 | 3423 | if (!fs.fileExists(albedo_path)) { 3424 | if (!fs.open(albedo_path, file)) { 3425 | logError("Failed to create ", albedo_path); 3426 | } 3427 | else { 3428 | CompositeTexture ct(m_app, m_app.getAllocator()); 3429 | OutputMemoryStream blob(m_app.getAllocator()); 3430 | ct.initTerrainAlbedo(); 3431 | ct.serialize(blob); 3432 | if (!file.write(blob.data(), blob.size())) { 3433 | logError("Failed to write ", albedo_path); 3434 | } 3435 | file.close(); 3436 | } 3437 | } 3438 | 3439 | if (!fs.fileExists(normal_path)) { 3440 | if (!fs.open(normal_path, file)) { 3441 | logError("Failed to create ", normal_path); 3442 | } 3443 | else { 3444 | CompositeTexture ct(m_app, m_app.getAllocator()); 3445 | OutputMemoryStream blob(m_app.getAllocator()); 3446 | ct.initTerrainNormal(); 3447 | ct.serialize(blob); 3448 | if (!file.write(blob.data(), blob.size())) { 3449 | logError("Failed to write ", normal_path); 3450 | } 3451 | file.close(); 3452 | } 3453 | } 3454 | 3455 | StaticString mat_path(file_info.dir, "/", file_info.basename, ".mat"); 3456 | os::OutputFile mat_file; 3457 | if (!fs.fileExists(mat_path)) { 3458 | if (fs.open(mat_path, mat_file)) { 3459 | mat_file << R"#( 3460 | shader "shaders/terrain.hlsl" 3461 | texture ")#"; 3462 | mat_file << file_info.basename; 3463 | mat_file << R"#(.raw" 3464 | texture "albedo_detail.ltc" 3465 | texture "normal_detail.ltc" 3466 | texture "splatmap.tga" 3467 | texture ")#" << file_info.basename << R"#(.tga" 3468 | uniform "Detail distance", 50.000000 3469 | uniform "Detail scale", 1.000000 3470 | uniform "Noise UV scale", 0.200000 3471 | uniform "Detail diffusion", 0.500000 3472 | uniform "Detail power", 16.000000 3473 | )#"; 3474 | 3475 | mat_file.close(); 3476 | } 3477 | } 3478 | 3479 | StaticString tga_meta_path(file_info.dir, "/", file_info.basename, ".tga.meta"); 3480 | os::OutputFile tga_meta_file; 3481 | if (fs.open(tga_meta_path, tga_meta_file)) { 3482 | tga_meta_file << "srgb = true\n"; 3483 | tga_meta_file.close(); 3484 | } 3485 | } 3486 | 3487 | void checkTasks() 3488 | { 3489 | IAllocator& allocator = m_app.getWorldEditor().getEngine().getAllocator(); 3490 | RenderInterface* ri = m_app.getRenderInterface(); 3491 | 3492 | for (int i = m_in_progress.size() - 1; i >= 0; --i) { 3493 | MapsTask* task = m_in_progress[i]; 3494 | if (task->isFinished()) { 3495 | m_in_progress.swapAndPop(i); 3496 | 3497 | TileData& tile = task->tile; 3498 | const u8* data = task->is_heightmap ? (u8*)tile.hm_data.begin() : (u8*)tile.imagery_data.begin(); 3499 | ImTextureID& tex = task->is_heightmap ? tile.hm : tile.imagery; 3500 | tex = ri->createTexture("maps", data, TILE_SIZE, TILE_SIZE); 3501 | ASSERT(tex != (void*)(intptr_t)0xffFFffFF); 3502 | 3503 | task->destroy(); 3504 | LUMIX_DELETE(allocator, task); 3505 | 3506 | if (tile.imagery_task == task) tile.imagery_task = nullptr; 3507 | if (tile.hm_task == task) tile.hm_task = nullptr; 3508 | } 3509 | } 3510 | } 3511 | 3512 | void resize(const DVec2& corner, i32 old_size) 3513 | { 3514 | m_zoom += m_size - old_size; 3515 | setCorner(corner); 3516 | } 3517 | 3518 | void move(int dx, int dy) 3519 | { 3520 | m_x += dx; 3521 | m_y += dy; 3522 | download(); 3523 | } 3524 | 3525 | double pixelToWorld() const { 3526 | return 1.0 / ((1 << m_zoom) * 256); 3527 | } 3528 | 3529 | void zoom(int dz) 3530 | { 3531 | const DVec2 center = getCenter(); 3532 | 3533 | int new_zoom = clamp(m_zoom + dz, m_size, MAX_ZOOM); 3534 | dz = new_zoom - m_zoom; 3535 | m_zoom = new_zoom; 3536 | const int size = 1 << m_zoom; 3537 | m_x = int(center.x * size) - 1; 3538 | m_y = int(center.y * size) - 1; 3539 | IVec2 offset = m_pixel_offset; 3540 | m_pixel_offset = IVec2(0); 3541 | const DVec2 new_center = getCenter(); 3542 | m_pixel_offset.x += int((new_center.x - center.x) / pixelToWorld()); 3543 | m_pixel_offset.y += int((new_center.y - center.y) / pixelToWorld()); 3544 | const DVec2 check = getCenter(); 3545 | 3546 | download(); 3547 | } 3548 | 3549 | ImVec2 getPos(const TileData& tile) const { 3550 | ImVec2 res; 3551 | res.x = float(256 * (tile.loc.x - m_x)) + m_pixel_offset.x; 3552 | res.y = float(256 * (tile.loc.y - m_y)) + m_pixel_offset.y; 3553 | return res; 3554 | } 3555 | 3556 | void erosionGUI() { 3557 | static i32 iterations = 1; 3558 | static i32 drops_count = 1024 * 1024; 3559 | static float power = 0.01f; 3560 | 3561 | ImGuiEx::Label("Iterations"); 3562 | ImGui::InputInt("##iters", &iterations); 3563 | ImGuiEx::Label("Drops count"); 3564 | ImGui::InputInt("##drps_cnt", &drops_count); 3565 | ImGuiEx::Label("Power"); 3566 | ImGui::SliderFloat("##pwr", &power, 0.f, 1.f); 3567 | WorldEditor& editor = m_app.getWorldEditor(); 3568 | const Array& entities = editor.getSelectedEntities(); 3569 | if (entities.empty()) { 3570 | ImGui::TextUnformatted("No entity selected"); 3571 | return; 3572 | } 3573 | World* world = editor.getWorld(); 3574 | if (!world->hasComponent(entities[0], TERRAIN_TYPE)) { 3575 | ImGui::TextUnformatted("Selected entity does not have terrain component"); 3576 | return; 3577 | } 3578 | RenderModule* module = (RenderModule*)world->getModule("renderer"); 3579 | Material* mat = module->getTerrainMaterial(entities[0]); 3580 | if (!mat->isReady()) { 3581 | ImGui::Text("Material %s not ready", mat->getPath().c_str()); 3582 | return; 3583 | } 3584 | Texture* tex = mat->getTextureByName("Heightmap"); 3585 | if (!tex) { 3586 | ImGui::TextUnformatted("Missing heightmap"); 3587 | return; 3588 | } 3589 | if (!tex->isReady()) { 3590 | ImGui::TextUnformatted("Heightmap not ready"); 3591 | return; 3592 | } 3593 | 3594 | if (ImGui::Button("Erode")) { 3595 | Array drops(m_app.getAllocator()); 3596 | Array drops2(m_app.getAllocator()); 3597 | Array hmf(m_app.getAllocator()); 3598 | 3599 | drops2.reserve(drops_count); 3600 | const u32 w = tex->width; 3601 | const u32 h = tex->height; 3602 | hmf.resize(w * h); 3603 | u16* hm = (u16*)tex->data.getMutableData(); 3604 | ASSERT(tex->data.size() == w * h * 2); 3605 | for (u32 i = 0; i < w * h; ++i) { 3606 | hmf[i] = hm[i]; 3607 | } 3608 | 3609 | for (i32 iter = 0; iter < iterations; ++iter) { 3610 | drops.resize(drops_count); 3611 | drops2.clear(); 3612 | for (IVec2& drop : drops) { 3613 | drop.x = rand(0, w - 1); 3614 | drop.y = rand(0, h - 1); 3615 | } 3616 | 3617 | Array* from = &drops; 3618 | Array* to = &drops2; 3619 | 3620 | auto find_low = [&](const IVec2 p){ 3621 | const IVec2 offsets[] = { 3622 | IVec2(-1, -1), 3623 | IVec2(-1, 0), 3624 | IVec2(-1, 1), 3625 | IVec2(0, -1), 3626 | IVec2(0, 1), 3627 | IVec2(1, -1), 3628 | IVec2(1, 0), 3629 | IVec2(1, 1) 3630 | }; 3631 | 3632 | float min = hmf[p.x + p.y * w]; 3633 | IVec2 min_p = p; 3634 | for (const IVec2& o : offsets){ 3635 | IVec2 tmp = p + o; 3636 | if (tmp.x < 0 || tmp.y < 0 || tmp.x >= (i32)w || tmp.y >= (i32)h) continue; 3637 | 3638 | if (min > hmf[tmp.x + tmp.y * w]) { 3639 | min = hmf[tmp.x + tmp.y * w]; 3640 | min_p = tmp; 3641 | } 3642 | } 3643 | return min_p; 3644 | }; 3645 | 3646 | while (!from->empty()) { 3647 | const IVec2 drop = from->back(); 3648 | from->pop(); 3649 | const IVec2 low = find_low(drop); 3650 | if (low != drop) { 3651 | const u32 id = drop.x + drop.y * w; 3652 | const u32 il = low.x + low.y * w; 3653 | const float d = hmf[id] - hmf[il]; 3654 | const float steep = clamp(d * d, 0.f, 1.f); 3655 | hmf[id] -= d * power * steep; 3656 | hmf[il] += d * power * steep; 3657 | to->push(low); 3658 | } 3659 | } 3660 | } 3661 | for (u32 i = 0; i < w * h; ++i) { 3662 | hm[i] = (u16)hmf[i]; 3663 | } 3664 | 3665 | tex->onDataUpdated(0, 0, w, h); 3666 | } 3667 | } 3668 | 3669 | void onGUI() override { 3670 | if (m_app.checkShortcut(m_toggle_ui, true)) m_open = !m_open; 3671 | 3672 | while (!m_queue.empty() && m_in_progress.size() < 8) { 3673 | MapsTask* task = m_queue.back(); 3674 | m_queue.pop(); 3675 | task->create("download_maps_task", true); 3676 | m_in_progress.push(task); 3677 | } 3678 | checkTasks(); 3679 | 3680 | if (!m_open) return; 3681 | if (!ImGui::Begin(ICON_FA_MAP "Maps##maps", &m_open)) 3682 | { 3683 | ImGui::End(); 3684 | return; 3685 | } 3686 | 3687 | CommonActions& actions = m_app.getCommonActions(); 3688 | if (m_app.checkShortcut(actions.del, false)) m_osm_editor.deleteSelectedNodes(); 3689 | else if (m_app.checkShortcut(m_osm_editor.m_run_action, false)) m_osm_editor.run(); 3690 | else if (m_app.checkShortcut(actions.save, false)) m_osm_editor.save(); 3691 | else if (m_app.checkShortcut(actions.undo, false)) m_osm_editor.undo(); 3692 | else if (m_app.checkShortcut(actions.redo, false)) m_osm_editor.redo(); 3693 | 3694 | if (ImGui::BeginTabBar("tabs")) { 3695 | if (ImGui::BeginTabItem("Map")) { 3696 | mapGUI(); 3697 | ImGui::EndTabItem(); 3698 | } 3699 | if (ImGui::BeginTabItem("OSM")) { 3700 | m_osm_editor.gui(m_x, m_y, m_pixel_offset, m_zoom, m_scale, m_size); 3701 | ImGui::EndTabItem(); 3702 | } 3703 | if (ImGui::BeginTabItem("Erosion")) { 3704 | erosionGUI(); 3705 | ImGui::EndTabItem(); 3706 | } 3707 | ImGui::EndTabBar(); 3708 | } 3709 | ImGui::End(); 3710 | } 3711 | 3712 | Terrain* getSelectedTerrain() const { 3713 | WorldEditor& editor = m_app.getWorldEditor(); 3714 | World* world = m_app.getWorldEditor().getWorld(); 3715 | const Array& selected_entities = editor.getSelectedEntities(); 3716 | 3717 | if (selected_entities.size() != 1) return nullptr; 3718 | if (!world->hasComponent(selected_entities[0], TERRAIN_TYPE)) return nullptr; 3719 | 3720 | RenderModule* module = (RenderModule*)world->getModule("renderer"); 3721 | return module->getTerrain(selected_entities[0]); 3722 | } 3723 | 3724 | void mapGUI() { 3725 | if (m_is_download_deferred) download(); 3726 | 3727 | ImGuiEx::Label("Size"); 3728 | const DVec2 corner = getCorner(); 3729 | const i32 old_size = m_size; 3730 | if (ImGui::Combo("##size", &m_size, "256\0" "512\0" "1024\0" "2048\0" "4096\0")) resize(corner, old_size); 3731 | 3732 | int current_zoom = m_zoom; 3733 | ImGuiEx::Label("Zoom"); 3734 | ImGui::SetNextItemWidth(-50); 3735 | if (ImGui::SliderInt("##zoom", ¤t_zoom, m_size, MAX_ZOOM)) zoom(current_zoom - m_zoom); 3736 | ImGui::SameLine(); 3737 | if (ImGui::Button("+")) zoom(1); 3738 | ImGui::SameLine(); 3739 | if (ImGui::Button("-")) zoom(-1); 3740 | 3741 | 3742 | if (m_zoom > 12) { 3743 | ImGui::Text("Heightmap might have artifacts at this level."); 3744 | } 3745 | 3746 | ImGuiEx::Label("Scale"); 3747 | ImGui::DragFloat("##objscale", &m_scale); 3748 | 3749 | ImGuiEx::Label("Resample"); 3750 | ImGui::InputInt("##hmresample", &m_resample_hm); 3751 | ImGuiEx::Label("Output"); 3752 | if (ImGui::Button(StaticString(m_out_path[0] ? m_out_path : "Click to set"), ImVec2(-1, 0))) { 3753 | m_show_save_raw = true; 3754 | } 3755 | 3756 | FileSelector& fs = m_app.getFileSelector(); 3757 | if (fs.gui("Save As", &m_show_save_raw, "raw", true)) copyString(m_out_path, fs.getPath()); 3758 | 3759 | if (m_out_path[0]) { 3760 | if (ImGui::Button(ICON_FA_SAVE "Save textures")) save(); 3761 | ImGui::SameLine(); 3762 | if (ImGui::Button("Create entity")) createMapEntity(); 3763 | } 3764 | 3765 | ImVec2 cursor_pos = ImGui::GetCursorScreenPos(); 3766 | ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); 3767 | bool hovered = false; 3768 | if (ImGui::BeginChild("img", ImVec2(512, 512), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { 3769 | const ImVec2 cp = ImGui::GetCursorPos(); 3770 | 3771 | for (TileData* tile : m_tiles) { 3772 | ImGui::PushID(tile); 3773 | ImVec2 p = getPos(*tile); 3774 | float scale = 2.f / (1 << m_size); 3775 | p = p * ImVec2(scale, scale); 3776 | ImGui::SetCursorPos(p + cp); 3777 | if (tile->hm != (void*)(intptr_t)0xffFFffFF && m_show_hm) ImGui::ImageButton("hm", tile->hm, ImVec2(TILE_SIZE * scale, TILE_SIZE* scale)); 3778 | if (tile->imagery != (void*)(intptr_t)0xffFFffFF && !m_show_hm) ImGui::ImageButton("img", tile->imagery, ImVec2(TILE_SIZE* scale, TILE_SIZE* scale)); 3779 | hovered = hovered || ImGui::IsItemHovered(); 3780 | ImGui::PopID(); 3781 | } 3782 | ImGui::SetCursorPos(cp); 3783 | ImGui::Dummy(ImVec2(512, 512)); 3784 | } 3785 | ImGui::PopStyleVar(); 3786 | ImGui::EndChild(); 3787 | 3788 | if(ImGui::IsMouseDragging(0) && m_is_dragging) { 3789 | m_pixel_offset.x = m_drag_start_offset.x + int(ImGui::GetMouseDragDelta().x) * (1 << (m_size - 1)); 3790 | m_pixel_offset.y = m_drag_start_offset.y + int(ImGui::GetMouseDragDelta().y) * (1 << (m_size - 1)); 3791 | 3792 | const int size = 1 << m_zoom; 3793 | if (m_pixel_offset.x > 256) { 3794 | m_drag_start_offset.x -= 256; 3795 | m_pixel_offset.x -= 256; 3796 | --m_x; 3797 | } 3798 | if (m_pixel_offset.x < -256) { 3799 | m_drag_start_offset.x += 256; 3800 | m_pixel_offset.x += 256; 3801 | ++m_x; 3802 | } 3803 | if (m_pixel_offset.y > 256) { 3804 | m_drag_start_offset.y -= 256; 3805 | m_pixel_offset.y -= 256; 3806 | --m_y; 3807 | } 3808 | if (m_pixel_offset.y < -256) { 3809 | m_drag_start_offset.y += 256; 3810 | m_pixel_offset.y += 256; 3811 | ++m_y; 3812 | } 3813 | download(); 3814 | } 3815 | 3816 | if (ImGui::GetIO().MouseWheel && hovered) { 3817 | zoom(ImGui::GetIO().MouseWheel > 0 ? 1 : -1); 3818 | } 3819 | 3820 | if (ImGui::IsMouseClicked(0) && hovered) { 3821 | m_is_dragging = true; 3822 | m_drag_start_offset = m_pixel_offset; 3823 | } 3824 | 3825 | if (ImGui::IsMouseReleased(0) && m_is_dragging) { 3826 | m_is_dragging = false; 3827 | } 3828 | 3829 | static double go_lat_lon[2] = {}; 3830 | ImGuiEx::Label("Lat, Lon"); 3831 | ImGui::SetNextItemWidth(-30); 3832 | ImGui::InputScalarN("##nav", ImGuiDataType_Double, &go_lat_lon, 2); 3833 | ImGui::SameLine(); 3834 | if (ImGui::Button("Go")) { 3835 | double y = lat2tiley(go_lat_lon[0], m_zoom); 3836 | double x = long2tilex(go_lat_lon[1], m_zoom); 3837 | m_x = (int)x; 3838 | m_y = (int)y; 3839 | download(); 3840 | } 3841 | 3842 | ImGuiEx::Label("Location"); 3843 | static char loc[256] = "9005;5653;14;38;-158"; 3844 | if (ImGuiEx::IconButton(ICON_FA_MAP_MARKER_ALT, "Get current location")) { 3845 | StaticString tmp(m_x, ";", m_y, ";", m_zoom, ";", m_pixel_offset.x, ";", m_pixel_offset.y); 3846 | copyString(loc, tmp); 3847 | } 3848 | ImGui::SameLine(); 3849 | if (ImGuiEx::IconButton(ICON_FA_COPY, "Copy to clipboard")) { 3850 | os::copyToClipboard(loc); 3851 | } 3852 | ImGui::SameLine(); 3853 | if (ImGuiEx::IconButton(ICON_FA_BULLSEYE, "View")) { 3854 | sscanf(loc, "%d;%d;%d;%d;%d", &m_x, &m_y, &m_zoom, &m_pixel_offset.x, &m_pixel_offset.y); 3855 | download(); 3856 | } 3857 | ImGui::SameLine(); 3858 | ImGui::InputText("##loc", loc, sizeof(loc)); 3859 | 3860 | ImGui::Checkbox("Show HeightMap", &m_show_hm); 3861 | 3862 | ImGui::Text("Running tasks: %d, Downloaded: %dkB", m_queue.size() + m_in_progress.size(), m_downloaded_bytes / 1024); 3863 | ImGui::Text("Uses https://aws.amazon.com/public-datasets/terrain/"); 3864 | ImGui::Text("http://s3.amazonaws.com/elevation-tiles-prod/terrarium/%d/%d/%d.png", m_zoom, m_x, m_y); 3865 | ImGui::Text("Sentinel-2 cloudless - https://s2maps.eu by EOX IT Services GmbH (Contains modified Copernicus Sentinel data 2016 & 2017)"); 3866 | } 3867 | 3868 | bool m_show_hm = false; 3869 | StudioApp& m_app; 3870 | Array m_bitmap; 3871 | u32 m_bitmap_size = 0; 3872 | Array m_tiles; 3873 | Array m_cache; 3874 | Array m_queue; 3875 | Array m_in_progress; 3876 | AtomicI32 m_downloaded_bytes = 0; 3877 | bool m_open = false; 3878 | bool m_is_download_deferred = true; 3879 | i32 m_zoom = 1; 3880 | float m_scale = 1.f; 3881 | i32 m_resample_hm = 1; 3882 | i32 m_x = 0; 3883 | i32 m_y = 0; 3884 | IVec2 m_pixel_offset{0, 0}; 3885 | i32 m_size = 1; 3886 | char m_out_path[MAX_PATH]; 3887 | IVec2 m_drag_start_offset; 3888 | bool m_is_dragging = false; 3889 | bool m_show_save_raw = false; 3890 | Action m_toggle_ui{"Maps", "Maps - toggle ui", "maps_toggle_ui", nullptr, Action::WINDOW}; 3891 | Vec2 m_last_saved_hm_range = Vec2(0, 1000); 3892 | OSMNodeEditor m_osm_editor; 3893 | }; 3894 | 3895 | 3896 | } // anonoymous namespace 3897 | 3898 | 3899 | LUMIX_STUDIO_ENTRY(maps) { 3900 | PROFILE_FUNCTION(); 3901 | WorldEditor& editor = app.getWorldEditor(); 3902 | 3903 | auto* plugin = LUMIX_NEW(editor.getAllocator(), MapsPlugin)(app); 3904 | app.addPlugin(*plugin); 3905 | return nullptr; 3906 | } 3907 | -------------------------------------------------------------------------------- /src/editor/pugixml/pugiconfig.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * pugixml parser - version 1.10 3 | * -------------------------------------------------------- 4 | * Copyright (C) 2006-2019, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) 5 | * Report bugs and download new versions at https://pugixml.org/ 6 | * 7 | * This library is distributed under the MIT License. See notice at the end 8 | * of this file. 9 | * 10 | * This work is based on the pugxml parser, which is: 11 | * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) 12 | */ 13 | 14 | #ifndef HEADER_PUGICONFIG_HPP 15 | #define HEADER_PUGICONFIG_HPP 16 | 17 | // Uncomment this to enable wchar_t mode 18 | // #define PUGIXML_WCHAR_MODE 19 | 20 | // Uncomment this to enable compact mode 21 | // #define PUGIXML_COMPACT 22 | 23 | // Uncomment this to disable XPath 24 | // #define PUGIXML_NO_XPATH 25 | 26 | // Uncomment this to disable STL 27 | // #define PUGIXML_NO_STL 28 | 29 | // Uncomment this to disable exceptions 30 | // #define PUGIXML_NO_EXCEPTIONS 31 | 32 | // Set this to control attributes for public classes/functions, i.e.: 33 | // #define PUGIXML_API __declspec(dllexport) // to export all public symbols from DLL 34 | // #define PUGIXML_CLASS __declspec(dllimport) // to import all classes from DLL 35 | // #define PUGIXML_FUNCTION __fastcall // to set calling conventions to all public functions to fastcall 36 | // In absence of PUGIXML_CLASS/PUGIXML_FUNCTION definitions PUGIXML_API is used instead 37 | 38 | // Tune these constants to adjust memory-related behavior 39 | // #define PUGIXML_MEMORY_PAGE_SIZE 32768 40 | // #define PUGIXML_MEMORY_OUTPUT_STACK 10240 41 | // #define PUGIXML_MEMORY_XPATH_PAGE_SIZE 4096 42 | 43 | // Uncomment this to switch to header-only version 44 | // #define PUGIXML_HEADER_ONLY 45 | 46 | // Uncomment this to enable long long support 47 | // #define PUGIXML_HAS_LONG_LONG 48 | 49 | #endif 50 | 51 | /** 52 | * Copyright (c) 2006-2019 Arseny Kapoulkine 53 | * 54 | * Permission is hereby granted, free of charge, to any person 55 | * obtaining a copy of this software and associated documentation 56 | * files (the "Software"), to deal in the Software without 57 | * restriction, including without limitation the rights to use, 58 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 59 | * copies of the Software, and to permit persons to whom the 60 | * Software is furnished to do so, subject to the following 61 | * conditions: 62 | * 63 | * The above copyright notice and this permission notice shall be 64 | * included in all copies or substantial portions of the Software. 65 | * 66 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 67 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 68 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 69 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 70 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 71 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 72 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 73 | * OTHER DEALINGS IN THE SOFTWARE. 74 | */ 75 | -------------------------------------------------------------------------------- /src/editor/pugixml/pugixml.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * pugixml parser - version 1.10 3 | * -------------------------------------------------------- 4 | * Copyright (C) 2006-2019, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) 5 | * Report bugs and download new versions at https://pugixml.org/ 6 | * 7 | * This library is distributed under the MIT License. See notice at the end 8 | * of this file. 9 | * 10 | * This work is based on the pugxml parser, which is: 11 | * Copyright (C) 2003, by Kristen Wegner (kristen@tima.net) 12 | */ 13 | 14 | #ifndef PUGIXML_VERSION 15 | // Define version macro; evaluates to major * 1000 + minor * 10 + patch so that it's safe to use in less-than comparisons 16 | // Note: pugixml used major * 100 + minor * 10 + patch format up until 1.9 (which had version identifier 190); starting from pugixml 1.10, the minor version number is two digits 17 | # define PUGIXML_VERSION 1100 18 | #endif 19 | 20 | // Include user configuration file (this can define various configuration macros) 21 | #include "pugiconfig.hpp" 22 | 23 | #ifndef HEADER_PUGIXML_HPP 24 | #define HEADER_PUGIXML_HPP 25 | 26 | // Include stddef.h for size_t and ptrdiff_t 27 | #include 28 | 29 | // Include exception header for XPath 30 | #if !defined(PUGIXML_NO_XPATH) && !defined(PUGIXML_NO_EXCEPTIONS) 31 | # include 32 | #endif 33 | 34 | // Include STL headers 35 | #ifndef PUGIXML_NO_STL 36 | # include 37 | # include 38 | # include 39 | #endif 40 | 41 | // Macro for deprecated features 42 | #ifndef PUGIXML_DEPRECATED 43 | # if defined(__GNUC__) 44 | # define PUGIXML_DEPRECATED __attribute__((deprecated)) 45 | # elif defined(_MSC_VER) && _MSC_VER >= 1300 46 | # define PUGIXML_DEPRECATED __declspec(deprecated) 47 | # else 48 | # define PUGIXML_DEPRECATED 49 | # endif 50 | #endif 51 | 52 | // If no API is defined, assume default 53 | #ifndef PUGIXML_API 54 | # define PUGIXML_API 55 | #endif 56 | 57 | // If no API for classes is defined, assume default 58 | #ifndef PUGIXML_CLASS 59 | # define PUGIXML_CLASS PUGIXML_API 60 | #endif 61 | 62 | // If no API for functions is defined, assume default 63 | #ifndef PUGIXML_FUNCTION 64 | # define PUGIXML_FUNCTION PUGIXML_API 65 | #endif 66 | 67 | // If the platform is known to have long long support, enable long long functions 68 | #ifndef PUGIXML_HAS_LONG_LONG 69 | # if __cplusplus >= 201103 70 | # define PUGIXML_HAS_LONG_LONG 71 | # elif defined(_MSC_VER) && _MSC_VER >= 1400 72 | # define PUGIXML_HAS_LONG_LONG 73 | # endif 74 | #endif 75 | 76 | // If the platform is known to have move semantics support, compile move ctor/operator implementation 77 | #ifndef PUGIXML_HAS_MOVE 78 | # if __cplusplus >= 201103 79 | # define PUGIXML_HAS_MOVE 80 | # elif defined(_MSC_VER) && _MSC_VER >= 1600 81 | # define PUGIXML_HAS_MOVE 82 | # endif 83 | #endif 84 | 85 | // If C++ is 2011 or higher, add 'noexcept' specifiers 86 | #ifndef PUGIXML_NOEXCEPT 87 | # if __cplusplus >= 201103 88 | # define PUGIXML_NOEXCEPT noexcept 89 | # elif defined(_MSC_VER) && _MSC_VER >= 1900 90 | # define PUGIXML_NOEXCEPT noexcept 91 | # else 92 | # define PUGIXML_NOEXCEPT 93 | # endif 94 | #endif 95 | 96 | // Some functions can not be noexcept in compact mode 97 | #ifdef PUGIXML_COMPACT 98 | # define PUGIXML_NOEXCEPT_IF_NOT_COMPACT 99 | #else 100 | # define PUGIXML_NOEXCEPT_IF_NOT_COMPACT PUGIXML_NOEXCEPT 101 | #endif 102 | 103 | // If C++ is 2011 or higher, add 'override' qualifiers 104 | #ifndef PUGIXML_OVERRIDE 105 | # if __cplusplus >= 201103 106 | # define PUGIXML_OVERRIDE override 107 | # elif defined(_MSC_VER) && _MSC_VER >= 1700 108 | # define PUGIXML_OVERRIDE override 109 | # else 110 | # define PUGIXML_OVERRIDE 111 | # endif 112 | #endif 113 | 114 | // Character interface macros 115 | #ifdef PUGIXML_WCHAR_MODE 116 | # define PUGIXML_TEXT(t) L ## t 117 | # define PUGIXML_CHAR wchar_t 118 | #else 119 | # define PUGIXML_TEXT(t) t 120 | # define PUGIXML_CHAR char 121 | #endif 122 | 123 | namespace pugi 124 | { 125 | // Character type used for all internal storage and operations; depends on PUGIXML_WCHAR_MODE 126 | typedef PUGIXML_CHAR char_t; 127 | 128 | #ifndef PUGIXML_NO_STL 129 | // String type used for operations that work with STL string; depends on PUGIXML_WCHAR_MODE 130 | typedef std::basic_string, std::allocator > string_t; 131 | #endif 132 | } 133 | 134 | // The PugiXML namespace 135 | namespace pugi 136 | { 137 | // Tree node types 138 | enum xml_node_type 139 | { 140 | node_null, // Empty (null) node handle 141 | node_document, // A document tree's absolute root 142 | node_element, // Element tag, i.e. '' 143 | node_pcdata, // Plain character data, i.e. 'text' 144 | node_cdata, // Character data, i.e. '' 145 | node_comment, // Comment tag, i.e. '' 146 | node_pi, // Processing instruction, i.e. '' 147 | node_declaration, // Document declaration, i.e. '' 148 | node_doctype // Document type declaration, i.e. '' 149 | }; 150 | 151 | // Parsing options 152 | 153 | // Minimal parsing mode (equivalent to turning all other flags off). 154 | // Only elements and PCDATA sections are added to the DOM tree, no text conversions are performed. 155 | const unsigned int parse_minimal = 0x0000; 156 | 157 | // This flag determines if processing instructions (node_pi) are added to the DOM tree. This flag is off by default. 158 | const unsigned int parse_pi = 0x0001; 159 | 160 | // This flag determines if comments (node_comment) are added to the DOM tree. This flag is off by default. 161 | const unsigned int parse_comments = 0x0002; 162 | 163 | // This flag determines if CDATA sections (node_cdata) are added to the DOM tree. This flag is on by default. 164 | const unsigned int parse_cdata = 0x0004; 165 | 166 | // This flag determines if plain character data (node_pcdata) that consist only of whitespace are added to the DOM tree. 167 | // This flag is off by default; turning it on usually results in slower parsing and more memory consumption. 168 | const unsigned int parse_ws_pcdata = 0x0008; 169 | 170 | // This flag determines if character and entity references are expanded during parsing. This flag is on by default. 171 | const unsigned int parse_escapes = 0x0010; 172 | 173 | // This flag determines if EOL characters are normalized (converted to #xA) during parsing. This flag is on by default. 174 | const unsigned int parse_eol = 0x0020; 175 | 176 | // This flag determines if attribute values are normalized using CDATA normalization rules during parsing. This flag is on by default. 177 | const unsigned int parse_wconv_attribute = 0x0040; 178 | 179 | // This flag determines if attribute values are normalized using NMTOKENS normalization rules during parsing. This flag is off by default. 180 | const unsigned int parse_wnorm_attribute = 0x0080; 181 | 182 | // This flag determines if document declaration (node_declaration) is added to the DOM tree. This flag is off by default. 183 | const unsigned int parse_declaration = 0x0100; 184 | 185 | // This flag determines if document type declaration (node_doctype) is added to the DOM tree. This flag is off by default. 186 | const unsigned int parse_doctype = 0x0200; 187 | 188 | // This flag determines if plain character data (node_pcdata) that is the only child of the parent node and that consists only 189 | // of whitespace is added to the DOM tree. 190 | // This flag is off by default; turning it on may result in slower parsing and more memory consumption. 191 | const unsigned int parse_ws_pcdata_single = 0x0400; 192 | 193 | // This flag determines if leading and trailing whitespace is to be removed from plain character data. This flag is off by default. 194 | const unsigned int parse_trim_pcdata = 0x0800; 195 | 196 | // This flag determines if plain character data that does not have a parent node is added to the DOM tree, and if an empty document 197 | // is a valid document. This flag is off by default. 198 | const unsigned int parse_fragment = 0x1000; 199 | 200 | // This flag determines if plain character data is be stored in the parent element's value. This significantly changes the structure of 201 | // the document; this flag is only recommended for parsing documents with many PCDATA nodes in memory-constrained environments. 202 | // This flag is off by default. 203 | const unsigned int parse_embed_pcdata = 0x2000; 204 | 205 | // The default parsing mode. 206 | // Elements, PCDATA and CDATA sections are added to the DOM tree, character/reference entities are expanded, 207 | // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. 208 | const unsigned int parse_default = parse_cdata | parse_escapes | parse_wconv_attribute | parse_eol; 209 | 210 | // The full parsing mode. 211 | // Nodes of all types are added to the DOM tree, character/reference entities are expanded, 212 | // End-of-Line characters are normalized, attribute values are normalized using CDATA normalization rules. 213 | const unsigned int parse_full = parse_default | parse_pi | parse_comments | parse_declaration | parse_doctype; 214 | 215 | // These flags determine the encoding of input data for XML document 216 | enum xml_encoding 217 | { 218 | encoding_auto, // Auto-detect input encoding using BOM or < / class xml_object_range 294 | { 295 | public: 296 | typedef It const_iterator; 297 | typedef It iterator; 298 | 299 | xml_object_range(It b, It e): _begin(b), _end(e) 300 | { 301 | } 302 | 303 | It begin() const { return _begin; } 304 | It end() const { return _end; } 305 | 306 | private: 307 | It _begin, _end; 308 | }; 309 | 310 | // Writer interface for node printing (see xml_node::print) 311 | class PUGIXML_CLASS xml_writer 312 | { 313 | public: 314 | virtual ~xml_writer() {} 315 | 316 | // Write memory chunk into stream/file/whatever 317 | virtual void write(const void* data, size_t size) = 0; 318 | }; 319 | 320 | // xml_writer implementation for FILE* 321 | class PUGIXML_CLASS xml_writer_file: public xml_writer 322 | { 323 | public: 324 | // Construct writer from a FILE* object; void* is used to avoid header dependencies on stdio 325 | xml_writer_file(void* file); 326 | 327 | virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; 328 | 329 | private: 330 | void* file; 331 | }; 332 | 333 | #ifndef PUGIXML_NO_STL 334 | // xml_writer implementation for streams 335 | class PUGIXML_CLASS xml_writer_stream: public xml_writer 336 | { 337 | public: 338 | // Construct writer from an output stream object 339 | xml_writer_stream(std::basic_ostream >& stream); 340 | xml_writer_stream(std::basic_ostream >& stream); 341 | 342 | virtual void write(const void* data, size_t size) PUGIXML_OVERRIDE; 343 | 344 | private: 345 | std::basic_ostream >* narrow_stream; 346 | std::basic_ostream >* wide_stream; 347 | }; 348 | #endif 349 | 350 | // A light-weight handle for manipulating attributes in DOM tree 351 | class PUGIXML_CLASS xml_attribute 352 | { 353 | friend class xml_attribute_iterator; 354 | friend class xml_node; 355 | 356 | private: 357 | xml_attribute_struct* _attr; 358 | 359 | typedef void (*unspecified_bool_type)(xml_attribute***); 360 | 361 | public: 362 | // Default constructor. Constructs an empty attribute. 363 | xml_attribute(); 364 | 365 | // Constructs attribute from internal pointer 366 | explicit xml_attribute(xml_attribute_struct* attr); 367 | 368 | // Safe bool conversion operator 369 | operator unspecified_bool_type() const; 370 | 371 | // Borland C++ workaround 372 | bool operator!() const; 373 | 374 | // Comparison operators (compares wrapped attribute pointers) 375 | bool operator==(const xml_attribute& r) const; 376 | bool operator!=(const xml_attribute& r) const; 377 | bool operator<(const xml_attribute& r) const; 378 | bool operator>(const xml_attribute& r) const; 379 | bool operator<=(const xml_attribute& r) const; 380 | bool operator>=(const xml_attribute& r) const; 381 | 382 | // Check if attribute is empty 383 | bool empty() const; 384 | 385 | // Get attribute name/value, or "" if attribute is empty 386 | const char_t* name() const; 387 | const char_t* value() const; 388 | 389 | // Get attribute value, or the default value if attribute is empty 390 | const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; 391 | 392 | // Get attribute value as a number, or the default value if conversion did not succeed or attribute is empty 393 | int as_int(int def = 0) const; 394 | unsigned int as_uint(unsigned int def = 0) const; 395 | double as_double(double def = 0) const; 396 | float as_float(float def = 0) const; 397 | 398 | #ifdef PUGIXML_HAS_LONG_LONG 399 | long long as_llong(long long def = 0) const; 400 | unsigned long long as_ullong(unsigned long long def = 0) const; 401 | #endif 402 | 403 | // Get attribute value as bool (returns true if first character is in '1tTyY' set), or the default value if attribute is empty 404 | bool as_bool(bool def = false) const; 405 | 406 | // Set attribute name/value (returns false if attribute is empty or there is not enough memory) 407 | bool set_name(const char_t* rhs); 408 | bool set_value(const char_t* rhs); 409 | 410 | // Set attribute value with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") 411 | bool set_value(int rhs); 412 | bool set_value(unsigned int rhs); 413 | bool set_value(long rhs); 414 | bool set_value(unsigned long rhs); 415 | bool set_value(double rhs); 416 | bool set_value(double rhs, int precision); 417 | bool set_value(float rhs); 418 | bool set_value(float rhs, int precision); 419 | bool set_value(bool rhs); 420 | 421 | #ifdef PUGIXML_HAS_LONG_LONG 422 | bool set_value(long long rhs); 423 | bool set_value(unsigned long long rhs); 424 | #endif 425 | 426 | // Set attribute value (equivalent to set_value without error checking) 427 | xml_attribute& operator=(const char_t* rhs); 428 | xml_attribute& operator=(int rhs); 429 | xml_attribute& operator=(unsigned int rhs); 430 | xml_attribute& operator=(long rhs); 431 | xml_attribute& operator=(unsigned long rhs); 432 | xml_attribute& operator=(double rhs); 433 | xml_attribute& operator=(float rhs); 434 | xml_attribute& operator=(bool rhs); 435 | 436 | #ifdef PUGIXML_HAS_LONG_LONG 437 | xml_attribute& operator=(long long rhs); 438 | xml_attribute& operator=(unsigned long long rhs); 439 | #endif 440 | 441 | // Get next/previous attribute in the attribute list of the parent node 442 | xml_attribute next_attribute() const; 443 | xml_attribute previous_attribute() const; 444 | 445 | // Get hash value (unique for handles to the same object) 446 | size_t hash_value() const; 447 | 448 | // Get internal pointer 449 | xml_attribute_struct* internal_object() const; 450 | }; 451 | 452 | #ifdef __BORLANDC__ 453 | // Borland C++ workaround 454 | bool PUGIXML_FUNCTION operator&&(const xml_attribute& lhs, bool rhs); 455 | bool PUGIXML_FUNCTION operator||(const xml_attribute& lhs, bool rhs); 456 | #endif 457 | 458 | // A light-weight handle for manipulating nodes in DOM tree 459 | class PUGIXML_CLASS xml_node 460 | { 461 | friend class xml_attribute_iterator; 462 | friend class xml_node_iterator; 463 | friend class xml_named_node_iterator; 464 | 465 | protected: 466 | xml_node_struct* _root; 467 | 468 | typedef void (*unspecified_bool_type)(xml_node***); 469 | 470 | public: 471 | // Default constructor. Constructs an empty node. 472 | xml_node(); 473 | 474 | // Constructs node from internal pointer 475 | explicit xml_node(xml_node_struct* p); 476 | 477 | // Safe bool conversion operator 478 | operator unspecified_bool_type() const; 479 | 480 | // Borland C++ workaround 481 | bool operator!() const; 482 | 483 | // Comparison operators (compares wrapped node pointers) 484 | bool operator==(const xml_node& r) const; 485 | bool operator!=(const xml_node& r) const; 486 | bool operator<(const xml_node& r) const; 487 | bool operator>(const xml_node& r) const; 488 | bool operator<=(const xml_node& r) const; 489 | bool operator>=(const xml_node& r) const; 490 | 491 | // Check if node is empty. 492 | bool empty() const; 493 | 494 | // Get node type 495 | xml_node_type type() const; 496 | 497 | // Get node name, or "" if node is empty or it has no name 498 | const char_t* name() const; 499 | 500 | // Get node value, or "" if node is empty or it has no value 501 | // Note: For text node.value() does not return "text"! Use child_value() or text() methods to access text inside nodes. 502 | const char_t* value() const; 503 | 504 | // Get attribute list 505 | xml_attribute first_attribute() const; 506 | xml_attribute last_attribute() const; 507 | 508 | // Get children list 509 | xml_node first_child() const; 510 | xml_node last_child() const; 511 | 512 | // Get next/previous sibling in the children list of the parent node 513 | xml_node next_sibling() const; 514 | xml_node previous_sibling() const; 515 | 516 | // Get parent node 517 | xml_node parent() const; 518 | 519 | // Get root of DOM tree this node belongs to 520 | xml_node root() const; 521 | 522 | // Get text object for the current node 523 | xml_text text() const; 524 | 525 | // Get child, attribute or next/previous sibling with the specified name 526 | xml_node child(const char_t* name) const; 527 | xml_attribute attribute(const char_t* name) const; 528 | xml_node next_sibling(const char_t* name) const; 529 | xml_node previous_sibling(const char_t* name) const; 530 | 531 | // Get attribute, starting the search from a hint (and updating hint so that searching for a sequence of attributes is fast) 532 | xml_attribute attribute(const char_t* name, xml_attribute& hint) const; 533 | 534 | // Get child value of current node; that is, value of the first child node of type PCDATA/CDATA 535 | const char_t* child_value() const; 536 | 537 | // Get child value of child with specified name. Equivalent to child(name).child_value(). 538 | const char_t* child_value(const char_t* name) const; 539 | 540 | // Set node name/value (returns false if node is empty, there is not enough memory, or node can not have name/value) 541 | bool set_name(const char_t* rhs); 542 | bool set_value(const char_t* rhs); 543 | 544 | // Add attribute with specified name. Returns added attribute, or empty attribute on errors. 545 | xml_attribute append_attribute(const char_t* name); 546 | xml_attribute prepend_attribute(const char_t* name); 547 | xml_attribute insert_attribute_after(const char_t* name, const xml_attribute& attr); 548 | xml_attribute insert_attribute_before(const char_t* name, const xml_attribute& attr); 549 | 550 | // Add a copy of the specified attribute. Returns added attribute, or empty attribute on errors. 551 | xml_attribute append_copy(const xml_attribute& proto); 552 | xml_attribute prepend_copy(const xml_attribute& proto); 553 | xml_attribute insert_copy_after(const xml_attribute& proto, const xml_attribute& attr); 554 | xml_attribute insert_copy_before(const xml_attribute& proto, const xml_attribute& attr); 555 | 556 | // Add child node with specified type. Returns added node, or empty node on errors. 557 | xml_node append_child(xml_node_type type = node_element); 558 | xml_node prepend_child(xml_node_type type = node_element); 559 | xml_node insert_child_after(xml_node_type type, const xml_node& node); 560 | xml_node insert_child_before(xml_node_type type, const xml_node& node); 561 | 562 | // Add child element with specified name. Returns added node, or empty node on errors. 563 | xml_node append_child(const char_t* name); 564 | xml_node prepend_child(const char_t* name); 565 | xml_node insert_child_after(const char_t* name, const xml_node& node); 566 | xml_node insert_child_before(const char_t* name, const xml_node& node); 567 | 568 | // Add a copy of the specified node as a child. Returns added node, or empty node on errors. 569 | xml_node append_copy(const xml_node& proto); 570 | xml_node prepend_copy(const xml_node& proto); 571 | xml_node insert_copy_after(const xml_node& proto, const xml_node& node); 572 | xml_node insert_copy_before(const xml_node& proto, const xml_node& node); 573 | 574 | // Move the specified node to become a child of this node. Returns moved node, or empty node on errors. 575 | xml_node append_move(const xml_node& moved); 576 | xml_node prepend_move(const xml_node& moved); 577 | xml_node insert_move_after(const xml_node& moved, const xml_node& node); 578 | xml_node insert_move_before(const xml_node& moved, const xml_node& node); 579 | 580 | // Remove specified attribute 581 | bool remove_attribute(const xml_attribute& a); 582 | bool remove_attribute(const char_t* name); 583 | 584 | // Remove all attributes 585 | bool remove_attributes(); 586 | 587 | // Remove specified child 588 | bool remove_child(const xml_node& n); 589 | bool remove_child(const char_t* name); 590 | 591 | // Remove all children 592 | bool remove_children(); 593 | 594 | // Parses buffer as an XML document fragment and appends all nodes as children of the current node. 595 | // Copies/converts the buffer, so it may be deleted or changed after the function returns. 596 | // Note: append_buffer allocates memory that has the lifetime of the owning document; removing the appended nodes does not immediately reclaim that memory. 597 | xml_parse_result append_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 598 | 599 | // Find attribute using predicate. Returns first attribute for which predicate returned true. 600 | template xml_attribute find_attribute(Predicate pred) const 601 | { 602 | if (!_root) return xml_attribute(); 603 | 604 | for (xml_attribute attrib = first_attribute(); attrib; attrib = attrib.next_attribute()) 605 | if (pred(attrib)) 606 | return attrib; 607 | 608 | return xml_attribute(); 609 | } 610 | 611 | // Find child node using predicate. Returns first child for which predicate returned true. 612 | template xml_node find_child(Predicate pred) const 613 | { 614 | if (!_root) return xml_node(); 615 | 616 | for (xml_node node = first_child(); node; node = node.next_sibling()) 617 | if (pred(node)) 618 | return node; 619 | 620 | return xml_node(); 621 | } 622 | 623 | // Find node from subtree using predicate. Returns first node from subtree (depth-first), for which predicate returned true. 624 | template xml_node find_node(Predicate pred) const 625 | { 626 | if (!_root) return xml_node(); 627 | 628 | xml_node cur = first_child(); 629 | 630 | while (cur._root && cur._root != _root) 631 | { 632 | if (pred(cur)) return cur; 633 | 634 | if (cur.first_child()) cur = cur.first_child(); 635 | else if (cur.next_sibling()) cur = cur.next_sibling(); 636 | else 637 | { 638 | while (!cur.next_sibling() && cur._root != _root) cur = cur.parent(); 639 | 640 | if (cur._root != _root) cur = cur.next_sibling(); 641 | } 642 | } 643 | 644 | return xml_node(); 645 | } 646 | 647 | // Find child node by attribute name/value 648 | xml_node find_child_by_attribute(const char_t* name, const char_t* attr_name, const char_t* attr_value) const; 649 | xml_node find_child_by_attribute(const char_t* attr_name, const char_t* attr_value) const; 650 | 651 | #ifndef PUGIXML_NO_STL 652 | // Get the absolute node path from root as a text string. 653 | string_t path(char_t delimiter = '/') const; 654 | #endif 655 | 656 | // Search for a node by path consisting of node names and . or .. elements. 657 | xml_node first_element_by_path(const char_t* path, char_t delimiter = '/') const; 658 | 659 | // Recursively traverse subtree with xml_tree_walker 660 | bool traverse(xml_tree_walker& walker); 661 | 662 | #ifndef PUGIXML_NO_XPATH 663 | // Select single node by evaluating XPath query. Returns first node from the resulting node set. 664 | xpath_node select_node(const char_t* query, xpath_variable_set* variables = 0) const; 665 | xpath_node select_node(const xpath_query& query) const; 666 | 667 | // Select node set by evaluating XPath query 668 | xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const; 669 | xpath_node_set select_nodes(const xpath_query& query) const; 670 | 671 | // (deprecated: use select_node instead) Select single node by evaluating XPath query. 672 | PUGIXML_DEPRECATED xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const; 673 | PUGIXML_DEPRECATED xpath_node select_single_node(const xpath_query& query) const; 674 | 675 | #endif 676 | 677 | // Print subtree using a writer object 678 | void print(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; 679 | 680 | #ifndef PUGIXML_NO_STL 681 | // Print subtree to stream 682 | void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto, unsigned int depth = 0) const; 683 | void print(std::basic_ostream >& os, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, unsigned int depth = 0) const; 684 | #endif 685 | 686 | // Child nodes iterators 687 | typedef xml_node_iterator iterator; 688 | 689 | iterator begin() const; 690 | iterator end() const; 691 | 692 | // Attribute iterators 693 | typedef xml_attribute_iterator attribute_iterator; 694 | 695 | attribute_iterator attributes_begin() const; 696 | attribute_iterator attributes_end() const; 697 | 698 | // Range-based for support 699 | xml_object_range children() const; 700 | xml_object_range children(const char_t* name) const; 701 | xml_object_range attributes() const; 702 | 703 | // Get node offset in parsed file/string (in char_t units) for debugging purposes 704 | ptrdiff_t offset_debug() const; 705 | 706 | // Get hash value (unique for handles to the same object) 707 | size_t hash_value() const; 708 | 709 | // Get internal pointer 710 | xml_node_struct* internal_object() const; 711 | }; 712 | 713 | #ifdef __BORLANDC__ 714 | // Borland C++ workaround 715 | bool PUGIXML_FUNCTION operator&&(const xml_node& lhs, bool rhs); 716 | bool PUGIXML_FUNCTION operator||(const xml_node& lhs, bool rhs); 717 | #endif 718 | 719 | // A helper for working with text inside PCDATA nodes 720 | class PUGIXML_CLASS xml_text 721 | { 722 | friend class xml_node; 723 | 724 | xml_node_struct* _root; 725 | 726 | typedef void (*unspecified_bool_type)(xml_text***); 727 | 728 | explicit xml_text(xml_node_struct* root); 729 | 730 | xml_node_struct* _data_new(); 731 | xml_node_struct* _data() const; 732 | 733 | public: 734 | // Default constructor. Constructs an empty object. 735 | xml_text(); 736 | 737 | // Safe bool conversion operator 738 | operator unspecified_bool_type() const; 739 | 740 | // Borland C++ workaround 741 | bool operator!() const; 742 | 743 | // Check if text object is empty 744 | bool empty() const; 745 | 746 | // Get text, or "" if object is empty 747 | const char_t* get() const; 748 | 749 | // Get text, or the default value if object is empty 750 | const char_t* as_string(const char_t* def = PUGIXML_TEXT("")) const; 751 | 752 | // Get text as a number, or the default value if conversion did not succeed or object is empty 753 | int as_int(int def = 0) const; 754 | unsigned int as_uint(unsigned int def = 0) const; 755 | double as_double(double def = 0) const; 756 | float as_float(float def = 0) const; 757 | 758 | #ifdef PUGIXML_HAS_LONG_LONG 759 | long long as_llong(long long def = 0) const; 760 | unsigned long long as_ullong(unsigned long long def = 0) const; 761 | #endif 762 | 763 | // Get text as bool (returns true if first character is in '1tTyY' set), or the default value if object is empty 764 | bool as_bool(bool def = false) const; 765 | 766 | // Set text (returns false if object is empty or there is not enough memory) 767 | bool set(const char_t* rhs); 768 | 769 | // Set text with type conversion (numbers are converted to strings, boolean is converted to "true"/"false") 770 | bool set(int rhs); 771 | bool set(unsigned int rhs); 772 | bool set(long rhs); 773 | bool set(unsigned long rhs); 774 | bool set(double rhs); 775 | bool set(double rhs, int precision); 776 | bool set(float rhs); 777 | bool set(float rhs, int precision); 778 | bool set(bool rhs); 779 | 780 | #ifdef PUGIXML_HAS_LONG_LONG 781 | bool set(long long rhs); 782 | bool set(unsigned long long rhs); 783 | #endif 784 | 785 | // Set text (equivalent to set without error checking) 786 | xml_text& operator=(const char_t* rhs); 787 | xml_text& operator=(int rhs); 788 | xml_text& operator=(unsigned int rhs); 789 | xml_text& operator=(long rhs); 790 | xml_text& operator=(unsigned long rhs); 791 | xml_text& operator=(double rhs); 792 | xml_text& operator=(float rhs); 793 | xml_text& operator=(bool rhs); 794 | 795 | #ifdef PUGIXML_HAS_LONG_LONG 796 | xml_text& operator=(long long rhs); 797 | xml_text& operator=(unsigned long long rhs); 798 | #endif 799 | 800 | // Get the data node (node_pcdata or node_cdata) for this object 801 | xml_node data() const; 802 | }; 803 | 804 | #ifdef __BORLANDC__ 805 | // Borland C++ workaround 806 | bool PUGIXML_FUNCTION operator&&(const xml_text& lhs, bool rhs); 807 | bool PUGIXML_FUNCTION operator||(const xml_text& lhs, bool rhs); 808 | #endif 809 | 810 | // Child node iterator (a bidirectional iterator over a collection of xml_node) 811 | class PUGIXML_CLASS xml_node_iterator 812 | { 813 | friend class xml_node; 814 | 815 | private: 816 | mutable xml_node _wrap; 817 | xml_node _parent; 818 | 819 | xml_node_iterator(xml_node_struct* ref, xml_node_struct* parent); 820 | 821 | public: 822 | // Iterator traits 823 | typedef ptrdiff_t difference_type; 824 | typedef xml_node value_type; 825 | typedef xml_node* pointer; 826 | typedef xml_node& reference; 827 | 828 | #ifndef PUGIXML_NO_STL 829 | typedef std::bidirectional_iterator_tag iterator_category; 830 | #endif 831 | 832 | // Default constructor 833 | xml_node_iterator(); 834 | 835 | // Construct an iterator which points to the specified node 836 | xml_node_iterator(const xml_node& node); 837 | 838 | // Iterator operators 839 | bool operator==(const xml_node_iterator& rhs) const; 840 | bool operator!=(const xml_node_iterator& rhs) const; 841 | 842 | xml_node& operator*() const; 843 | xml_node* operator->() const; 844 | 845 | const xml_node_iterator& operator++(); 846 | xml_node_iterator operator++(int); 847 | 848 | const xml_node_iterator& operator--(); 849 | xml_node_iterator operator--(int); 850 | }; 851 | 852 | // Attribute iterator (a bidirectional iterator over a collection of xml_attribute) 853 | class PUGIXML_CLASS xml_attribute_iterator 854 | { 855 | friend class xml_node; 856 | 857 | private: 858 | mutable xml_attribute _wrap; 859 | xml_node _parent; 860 | 861 | xml_attribute_iterator(xml_attribute_struct* ref, xml_node_struct* parent); 862 | 863 | public: 864 | // Iterator traits 865 | typedef ptrdiff_t difference_type; 866 | typedef xml_attribute value_type; 867 | typedef xml_attribute* pointer; 868 | typedef xml_attribute& reference; 869 | 870 | #ifndef PUGIXML_NO_STL 871 | typedef std::bidirectional_iterator_tag iterator_category; 872 | #endif 873 | 874 | // Default constructor 875 | xml_attribute_iterator(); 876 | 877 | // Construct an iterator which points to the specified attribute 878 | xml_attribute_iterator(const xml_attribute& attr, const xml_node& parent); 879 | 880 | // Iterator operators 881 | bool operator==(const xml_attribute_iterator& rhs) const; 882 | bool operator!=(const xml_attribute_iterator& rhs) const; 883 | 884 | xml_attribute& operator*() const; 885 | xml_attribute* operator->() const; 886 | 887 | const xml_attribute_iterator& operator++(); 888 | xml_attribute_iterator operator++(int); 889 | 890 | const xml_attribute_iterator& operator--(); 891 | xml_attribute_iterator operator--(int); 892 | }; 893 | 894 | // Named node range helper 895 | class PUGIXML_CLASS xml_named_node_iterator 896 | { 897 | friend class xml_node; 898 | 899 | public: 900 | // Iterator traits 901 | typedef ptrdiff_t difference_type; 902 | typedef xml_node value_type; 903 | typedef xml_node* pointer; 904 | typedef xml_node& reference; 905 | 906 | #ifndef PUGIXML_NO_STL 907 | typedef std::bidirectional_iterator_tag iterator_category; 908 | #endif 909 | 910 | // Default constructor 911 | xml_named_node_iterator(); 912 | 913 | // Construct an iterator which points to the specified node 914 | xml_named_node_iterator(const xml_node& node, const char_t* name); 915 | 916 | // Iterator operators 917 | bool operator==(const xml_named_node_iterator& rhs) const; 918 | bool operator!=(const xml_named_node_iterator& rhs) const; 919 | 920 | xml_node& operator*() const; 921 | xml_node* operator->() const; 922 | 923 | const xml_named_node_iterator& operator++(); 924 | xml_named_node_iterator operator++(int); 925 | 926 | const xml_named_node_iterator& operator--(); 927 | xml_named_node_iterator operator--(int); 928 | 929 | private: 930 | mutable xml_node _wrap; 931 | xml_node _parent; 932 | const char_t* _name; 933 | 934 | xml_named_node_iterator(xml_node_struct* ref, xml_node_struct* parent, const char_t* name); 935 | }; 936 | 937 | // Abstract tree walker class (see xml_node::traverse) 938 | class PUGIXML_CLASS xml_tree_walker 939 | { 940 | friend class xml_node; 941 | 942 | private: 943 | int _depth; 944 | 945 | protected: 946 | // Get current traversal depth 947 | int depth() const; 948 | 949 | public: 950 | xml_tree_walker(); 951 | virtual ~xml_tree_walker(); 952 | 953 | // Callback that is called when traversal begins 954 | virtual bool begin(xml_node& node); 955 | 956 | // Callback that is called for each node traversed 957 | virtual bool for_each(xml_node& node) = 0; 958 | 959 | // Callback that is called when traversal ends 960 | virtual bool end(xml_node& node); 961 | }; 962 | 963 | // Parsing status, returned as part of xml_parse_result object 964 | enum xml_parse_status 965 | { 966 | status_ok = 0, // No error 967 | 968 | status_file_not_found, // File was not found during load_file() 969 | status_io_error, // Error reading from file/stream 970 | status_out_of_memory, // Could not allocate memory 971 | status_internal_error, // Internal error occurred 972 | 973 | status_unrecognized_tag, // Parser could not determine tag type 974 | 975 | status_bad_pi, // Parsing error occurred while parsing document declaration/processing instruction 976 | status_bad_comment, // Parsing error occurred while parsing comment 977 | status_bad_cdata, // Parsing error occurred while parsing CDATA section 978 | status_bad_doctype, // Parsing error occurred while parsing document type declaration 979 | status_bad_pcdata, // Parsing error occurred while parsing PCDATA section 980 | status_bad_start_element, // Parsing error occurred while parsing start element tag 981 | status_bad_attribute, // Parsing error occurred while parsing element attribute 982 | status_bad_end_element, // Parsing error occurred while parsing end element tag 983 | status_end_element_mismatch,// There was a mismatch of start-end tags (closing tag had incorrect name, some tag was not closed or there was an excessive closing tag) 984 | 985 | status_append_invalid_root, // Unable to append nodes since root type is not node_element or node_document (exclusive to xml_node::append_buffer) 986 | 987 | status_no_document_element // Parsing resulted in a document without element nodes 988 | }; 989 | 990 | // Parsing result 991 | struct PUGIXML_CLASS xml_parse_result 992 | { 993 | // Parsing status (see xml_parse_status) 994 | xml_parse_status status; 995 | 996 | // Last parsed offset (in char_t units from start of input data) 997 | ptrdiff_t offset; 998 | 999 | // Source document encoding 1000 | xml_encoding encoding; 1001 | 1002 | // Default constructor, initializes object to failed state 1003 | xml_parse_result(); 1004 | 1005 | // Cast to bool operator 1006 | operator bool() const; 1007 | 1008 | // Get error description 1009 | const char* description() const; 1010 | }; 1011 | 1012 | // Document class (DOM tree root) 1013 | class PUGIXML_CLASS xml_document: public xml_node 1014 | { 1015 | private: 1016 | char_t* _buffer; 1017 | 1018 | char _memory[192]; 1019 | 1020 | // Non-copyable semantics 1021 | xml_document(const xml_document&); 1022 | xml_document& operator=(const xml_document&); 1023 | 1024 | void _create(); 1025 | void _destroy(); 1026 | void _move(xml_document& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; 1027 | 1028 | public: 1029 | // Default constructor, makes empty document 1030 | xml_document(); 1031 | 1032 | // Destructor, invalidates all node/attribute handles to this document 1033 | ~xml_document(); 1034 | 1035 | #ifdef PUGIXML_HAS_MOVE 1036 | // Move semantics support 1037 | xml_document(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; 1038 | xml_document& operator=(xml_document&& rhs) PUGIXML_NOEXCEPT_IF_NOT_COMPACT; 1039 | #endif 1040 | 1041 | // Removes all nodes, leaving the empty document 1042 | void reset(); 1043 | 1044 | // Removes all nodes, then copies the entire contents of the specified document 1045 | void reset(const xml_document& proto); 1046 | 1047 | #ifndef PUGIXML_NO_STL 1048 | // Load document from stream. 1049 | xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1050 | xml_parse_result load(std::basic_istream >& stream, unsigned int options = parse_default); 1051 | #endif 1052 | 1053 | // (deprecated: use load_string instead) Load document from zero-terminated string. No encoding conversions are applied. 1054 | PUGIXML_DEPRECATED xml_parse_result load(const char_t* contents, unsigned int options = parse_default); 1055 | 1056 | // Load document from zero-terminated string. No encoding conversions are applied. 1057 | xml_parse_result load_string(const char_t* contents, unsigned int options = parse_default); 1058 | 1059 | // Load document from file 1060 | xml_parse_result load_file(const char* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1061 | xml_parse_result load_file(const wchar_t* path, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1062 | 1063 | // Load document from buffer. Copies/converts the buffer, so it may be deleted or changed after the function returns. 1064 | xml_parse_result load_buffer(const void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1065 | 1066 | // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). 1067 | // You should ensure that buffer data will persist throughout the document's lifetime, and free the buffer memory manually once document is destroyed. 1068 | xml_parse_result load_buffer_inplace(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1069 | 1070 | // Load document from buffer, using the buffer for in-place parsing (the buffer is modified and used for storage of document data). 1071 | // You should allocate the buffer with pugixml allocation function; document will free the buffer when it is no longer needed (you can't use it anymore). 1072 | xml_parse_result load_buffer_inplace_own(void* contents, size_t size, unsigned int options = parse_default, xml_encoding encoding = encoding_auto); 1073 | 1074 | // Save XML document to writer (semantics is slightly different from xml_node::print, see documentation for details). 1075 | void save(xml_writer& writer, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; 1076 | 1077 | #ifndef PUGIXML_NO_STL 1078 | // Save XML document to stream (semantics is slightly different from xml_node::print, see documentation for details). 1079 | void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; 1080 | void save(std::basic_ostream >& stream, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default) const; 1081 | #endif 1082 | 1083 | // Save XML to file 1084 | bool save_file(const char* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; 1085 | bool save_file(const wchar_t* path, const char_t* indent = PUGIXML_TEXT("\t"), unsigned int flags = format_default, xml_encoding encoding = encoding_auto) const; 1086 | 1087 | // Get document element 1088 | xml_node document_element() const; 1089 | }; 1090 | 1091 | #ifndef PUGIXML_NO_XPATH 1092 | // XPath query return type 1093 | enum xpath_value_type 1094 | { 1095 | xpath_type_none, // Unknown type (query failed to compile) 1096 | xpath_type_node_set, // Node set (xpath_node_set) 1097 | xpath_type_number, // Number 1098 | xpath_type_string, // String 1099 | xpath_type_boolean // Boolean 1100 | }; 1101 | 1102 | // XPath parsing result 1103 | struct PUGIXML_CLASS xpath_parse_result 1104 | { 1105 | // Error message (0 if no error) 1106 | const char* error; 1107 | 1108 | // Last parsed offset (in char_t units from string start) 1109 | ptrdiff_t offset; 1110 | 1111 | // Default constructor, initializes object to failed state 1112 | xpath_parse_result(); 1113 | 1114 | // Cast to bool operator 1115 | operator bool() const; 1116 | 1117 | // Get error description 1118 | const char* description() const; 1119 | }; 1120 | 1121 | // A single XPath variable 1122 | class PUGIXML_CLASS xpath_variable 1123 | { 1124 | friend class xpath_variable_set; 1125 | 1126 | protected: 1127 | xpath_value_type _type; 1128 | xpath_variable* _next; 1129 | 1130 | xpath_variable(xpath_value_type type); 1131 | 1132 | // Non-copyable semantics 1133 | xpath_variable(const xpath_variable&); 1134 | xpath_variable& operator=(const xpath_variable&); 1135 | 1136 | public: 1137 | // Get variable name 1138 | const char_t* name() const; 1139 | 1140 | // Get variable type 1141 | xpath_value_type type() const; 1142 | 1143 | // Get variable value; no type conversion is performed, default value (false, NaN, empty string, empty node set) is returned on type mismatch error 1144 | bool get_boolean() const; 1145 | double get_number() const; 1146 | const char_t* get_string() const; 1147 | const xpath_node_set& get_node_set() const; 1148 | 1149 | // Set variable value; no type conversion is performed, false is returned on type mismatch error 1150 | bool set(bool value); 1151 | bool set(double value); 1152 | bool set(const char_t* value); 1153 | bool set(const xpath_node_set& value); 1154 | }; 1155 | 1156 | // A set of XPath variables 1157 | class PUGIXML_CLASS xpath_variable_set 1158 | { 1159 | private: 1160 | xpath_variable* _data[64]; 1161 | 1162 | void _assign(const xpath_variable_set& rhs); 1163 | void _swap(xpath_variable_set& rhs); 1164 | 1165 | xpath_variable* _find(const char_t* name) const; 1166 | 1167 | static bool _clone(xpath_variable* var, xpath_variable** out_result); 1168 | static void _destroy(xpath_variable* var); 1169 | 1170 | public: 1171 | // Default constructor/destructor 1172 | xpath_variable_set(); 1173 | ~xpath_variable_set(); 1174 | 1175 | // Copy constructor/assignment operator 1176 | xpath_variable_set(const xpath_variable_set& rhs); 1177 | xpath_variable_set& operator=(const xpath_variable_set& rhs); 1178 | 1179 | #ifdef PUGIXML_HAS_MOVE 1180 | // Move semantics support 1181 | xpath_variable_set(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; 1182 | xpath_variable_set& operator=(xpath_variable_set&& rhs) PUGIXML_NOEXCEPT; 1183 | #endif 1184 | 1185 | // Add a new variable or get the existing one, if the types match 1186 | xpath_variable* add(const char_t* name, xpath_value_type type); 1187 | 1188 | // Set value of an existing variable; no type conversion is performed, false is returned if there is no such variable or if types mismatch 1189 | bool set(const char_t* name, bool value); 1190 | bool set(const char_t* name, double value); 1191 | bool set(const char_t* name, const char_t* value); 1192 | bool set(const char_t* name, const xpath_node_set& value); 1193 | 1194 | // Get existing variable by name 1195 | xpath_variable* get(const char_t* name); 1196 | const xpath_variable* get(const char_t* name) const; 1197 | }; 1198 | 1199 | // A compiled XPath query object 1200 | class PUGIXML_CLASS xpath_query 1201 | { 1202 | private: 1203 | void* _impl; 1204 | xpath_parse_result _result; 1205 | 1206 | typedef void (*unspecified_bool_type)(xpath_query***); 1207 | 1208 | // Non-copyable semantics 1209 | xpath_query(const xpath_query&); 1210 | xpath_query& operator=(const xpath_query&); 1211 | 1212 | public: 1213 | // Construct a compiled object from XPath expression. 1214 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on compilation errors. 1215 | explicit xpath_query(const char_t* query, xpath_variable_set* variables = 0); 1216 | 1217 | // Constructor 1218 | xpath_query(); 1219 | 1220 | // Destructor 1221 | ~xpath_query(); 1222 | 1223 | #ifdef PUGIXML_HAS_MOVE 1224 | // Move semantics support 1225 | xpath_query(xpath_query&& rhs) PUGIXML_NOEXCEPT; 1226 | xpath_query& operator=(xpath_query&& rhs) PUGIXML_NOEXCEPT; 1227 | #endif 1228 | 1229 | // Get query expression return type 1230 | xpath_value_type return_type() const; 1231 | 1232 | // Evaluate expression as boolean value in the specified context; performs type conversion if necessary. 1233 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. 1234 | bool evaluate_boolean(const xpath_node& n) const; 1235 | 1236 | // Evaluate expression as double value in the specified context; performs type conversion if necessary. 1237 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. 1238 | double evaluate_number(const xpath_node& n) const; 1239 | 1240 | #ifndef PUGIXML_NO_STL 1241 | // Evaluate expression as string value in the specified context; performs type conversion if necessary. 1242 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. 1243 | string_t evaluate_string(const xpath_node& n) const; 1244 | #endif 1245 | 1246 | // Evaluate expression as string value in the specified context; performs type conversion if necessary. 1247 | // At most capacity characters are written to the destination buffer, full result size is returned (includes terminating zero). 1248 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws std::bad_alloc on out of memory errors. 1249 | // If PUGIXML_NO_EXCEPTIONS is defined, returns empty set instead. 1250 | size_t evaluate_string(char_t* buffer, size_t capacity, const xpath_node& n) const; 1251 | 1252 | // Evaluate expression as node set in the specified context. 1253 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. 1254 | // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead. 1255 | xpath_node_set evaluate_node_set(const xpath_node& n) const; 1256 | 1257 | // Evaluate expression as node set in the specified context. 1258 | // Return first node in document order, or empty node if node set is empty. 1259 | // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors. 1260 | // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead. 1261 | xpath_node evaluate_node(const xpath_node& n) const; 1262 | 1263 | // Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode) 1264 | const xpath_parse_result& result() const; 1265 | 1266 | // Safe bool conversion operator 1267 | operator unspecified_bool_type() const; 1268 | 1269 | // Borland C++ workaround 1270 | bool operator!() const; 1271 | }; 1272 | 1273 | #ifndef PUGIXML_NO_EXCEPTIONS 1274 | #if defined(_MSC_VER) 1275 | // C4275 can be ignored in Visual C++ if you are deriving 1276 | // from a type in the Standard C++ Library 1277 | #pragma warning(push) 1278 | #pragma warning(disable: 4275) 1279 | #endif 1280 | // XPath exception class 1281 | class PUGIXML_CLASS xpath_exception: public std::exception 1282 | { 1283 | private: 1284 | xpath_parse_result _result; 1285 | 1286 | public: 1287 | // Construct exception from parse result 1288 | explicit xpath_exception(const xpath_parse_result& result); 1289 | 1290 | // Get error message 1291 | virtual const char* what() const throw() PUGIXML_OVERRIDE; 1292 | 1293 | // Get parse result 1294 | const xpath_parse_result& result() const; 1295 | }; 1296 | #if defined(_MSC_VER) 1297 | #pragma warning(pop) 1298 | #endif 1299 | #endif 1300 | 1301 | // XPath node class (either xml_node or xml_attribute) 1302 | class PUGIXML_CLASS xpath_node 1303 | { 1304 | private: 1305 | xml_node _node; 1306 | xml_attribute _attribute; 1307 | 1308 | typedef void (*unspecified_bool_type)(xpath_node***); 1309 | 1310 | public: 1311 | // Default constructor; constructs empty XPath node 1312 | xpath_node(); 1313 | 1314 | // Construct XPath node from XML node/attribute 1315 | xpath_node(const xml_node& node); 1316 | xpath_node(const xml_attribute& attribute, const xml_node& parent); 1317 | 1318 | // Get node/attribute, if any 1319 | xml_node node() const; 1320 | xml_attribute attribute() const; 1321 | 1322 | // Get parent of contained node/attribute 1323 | xml_node parent() const; 1324 | 1325 | // Safe bool conversion operator 1326 | operator unspecified_bool_type() const; 1327 | 1328 | // Borland C++ workaround 1329 | bool operator!() const; 1330 | 1331 | // Comparison operators 1332 | bool operator==(const xpath_node& n) const; 1333 | bool operator!=(const xpath_node& n) const; 1334 | }; 1335 | 1336 | #ifdef __BORLANDC__ 1337 | // Borland C++ workaround 1338 | bool PUGIXML_FUNCTION operator&&(const xpath_node& lhs, bool rhs); 1339 | bool PUGIXML_FUNCTION operator||(const xpath_node& lhs, bool rhs); 1340 | #endif 1341 | 1342 | // A fixed-size collection of XPath nodes 1343 | class PUGIXML_CLASS xpath_node_set 1344 | { 1345 | public: 1346 | // Collection type 1347 | enum type_t 1348 | { 1349 | type_unsorted, // Not ordered 1350 | type_sorted, // Sorted by document order (ascending) 1351 | type_sorted_reverse // Sorted by document order (descending) 1352 | }; 1353 | 1354 | // Constant iterator type 1355 | typedef const xpath_node* const_iterator; 1356 | 1357 | // We define non-constant iterator to be the same as constant iterator so that various generic algorithms (i.e. boost foreach) work 1358 | typedef const xpath_node* iterator; 1359 | 1360 | // Default constructor. Constructs empty set. 1361 | xpath_node_set(); 1362 | 1363 | // Constructs a set from iterator range; data is not checked for duplicates and is not sorted according to provided type, so be careful 1364 | xpath_node_set(const_iterator begin, const_iterator end, type_t type = type_unsorted); 1365 | 1366 | // Destructor 1367 | ~xpath_node_set(); 1368 | 1369 | // Copy constructor/assignment operator 1370 | xpath_node_set(const xpath_node_set& ns); 1371 | xpath_node_set& operator=(const xpath_node_set& ns); 1372 | 1373 | #ifdef PUGIXML_HAS_MOVE 1374 | // Move semantics support 1375 | xpath_node_set(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; 1376 | xpath_node_set& operator=(xpath_node_set&& rhs) PUGIXML_NOEXCEPT; 1377 | #endif 1378 | 1379 | // Get collection type 1380 | type_t type() const; 1381 | 1382 | // Get collection size 1383 | size_t size() const; 1384 | 1385 | // Indexing operator 1386 | const xpath_node& operator[](size_t index) const; 1387 | 1388 | // Collection iterators 1389 | const_iterator begin() const; 1390 | const_iterator end() const; 1391 | 1392 | // Sort the collection in ascending/descending order by document order 1393 | void sort(bool reverse = false); 1394 | 1395 | // Get first node in the collection by document order 1396 | xpath_node first() const; 1397 | 1398 | // Check if collection is empty 1399 | bool empty() const; 1400 | 1401 | private: 1402 | type_t _type; 1403 | 1404 | xpath_node _storage[1]; 1405 | 1406 | xpath_node* _begin; 1407 | xpath_node* _end; 1408 | 1409 | void _assign(const_iterator begin, const_iterator end, type_t type); 1410 | void _move(xpath_node_set& rhs) PUGIXML_NOEXCEPT; 1411 | }; 1412 | #endif 1413 | 1414 | #ifndef PUGIXML_NO_STL 1415 | // Convert wide string to UTF8 1416 | std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const wchar_t* str); 1417 | std::basic_string, std::allocator > PUGIXML_FUNCTION as_utf8(const std::basic_string, std::allocator >& str); 1418 | 1419 | // Convert UTF8 to wide string 1420 | std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const char* str); 1421 | std::basic_string, std::allocator > PUGIXML_FUNCTION as_wide(const std::basic_string, std::allocator >& str); 1422 | #endif 1423 | 1424 | // Memory allocation function interface; returns pointer to allocated memory or NULL on failure 1425 | typedef void* (*allocation_function)(size_t size); 1426 | 1427 | // Memory deallocation function interface 1428 | typedef void (*deallocation_function)(void* ptr); 1429 | 1430 | // Override default memory management functions. All subsequent allocations/deallocations will be performed via supplied functions. 1431 | void PUGIXML_FUNCTION set_memory_management_functions(allocation_function allocate, deallocation_function deallocate); 1432 | 1433 | // Get current memory management functions 1434 | allocation_function PUGIXML_FUNCTION get_memory_allocation_function(); 1435 | deallocation_function PUGIXML_FUNCTION get_memory_deallocation_function(); 1436 | } 1437 | 1438 | #if !defined(PUGIXML_NO_STL) && (defined(_MSC_VER) || defined(__ICC)) 1439 | namespace std 1440 | { 1441 | // Workarounds for (non-standard) iterator category detection for older versions (MSVC7/IC8 and earlier) 1442 | std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_node_iterator&); 1443 | std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_attribute_iterator&); 1444 | std::bidirectional_iterator_tag PUGIXML_FUNCTION _Iter_cat(const pugi::xml_named_node_iterator&); 1445 | } 1446 | #endif 1447 | 1448 | #if !defined(PUGIXML_NO_STL) && defined(__SUNPRO_CC) 1449 | namespace std 1450 | { 1451 | // Workarounds for (non-standard) iterator category detection 1452 | std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_node_iterator&); 1453 | std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_attribute_iterator&); 1454 | std::bidirectional_iterator_tag PUGIXML_FUNCTION __iterator_category(const pugi::xml_named_node_iterator&); 1455 | } 1456 | #endif 1457 | 1458 | #endif 1459 | 1460 | // Make sure implementation is included in header-only mode 1461 | // Use macro expansion in #include to work around QMake (QTBUG-11923) 1462 | #if defined(PUGIXML_HEADER_ONLY) && !defined(PUGIXML_SOURCE) 1463 | # define PUGIXML_SOURCE "pugixml.cpp" 1464 | # include PUGIXML_SOURCE 1465 | #endif 1466 | 1467 | /** 1468 | * Copyright (c) 2006-2019 Arseny Kapoulkine 1469 | * 1470 | * Permission is hereby granted, free of charge, to any person 1471 | * obtaining a copy of this software and associated documentation 1472 | * files (the "Software"), to deal in the Software without 1473 | * restriction, including without limitation the rights to use, 1474 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 1475 | * copies of the Software, and to permit persons to whom the 1476 | * Software is furnished to do so, subject to the following 1477 | * conditions: 1478 | * 1479 | * The above copyright notice and this permission notice shall be 1480 | * included in all copies or substantial portions of the Software. 1481 | * 1482 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 1483 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 1484 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 1485 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 1486 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 1487 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 1488 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 1489 | * OTHER DEALINGS IN THE SOFTWARE. 1490 | */ 1491 | -------------------------------------------------------------------------------- /src/maps.cpp: -------------------------------------------------------------------------------- 1 | #include "engine/plugin.h" 2 | #include "imgui/imgui.h" 3 | 4 | 5 | namespace Lumix 6 | { 7 | 8 | 9 | LUMIX_PLUGIN_ENTRY(maps) 10 | { 11 | return nullptr; 12 | } 13 | 14 | 15 | } --------------------------------------------------------------------------------