94 Commits

Author SHA1 Message Date
Louis Erbkamm
a99dd0f907 Updated version 2024-12-30 00:28:03 +01:00
Louis Erbkamm
5b023fc9ce Updated version 2024-12-30 00:27:51 +01:00
Louis Erbkamm
ed57bf1b4e Update README.md 2024-12-30 00:16:13 +01:00
Louis Erbkamm
1f45100707 Merge pull request #100 from louis-e/groundlevel-parameter
Added groundlevel parameter to settings
2024-12-30 00:05:32 +01:00
Louis Erbkamm
aa1c8509a5 Merge branch 'main' into groundlevel-parameter 2024-12-29 23:54:04 +01:00
louis-e
6193a05be7 fmt (clippy): fixed redundant field names in struct initialization 2024-12-29 23:53:33 +01:00
louis-e
b9277e6a60 Added groundlevel parameter to settings 2024-12-29 23:48:17 +01:00
Louis Erbkamm
5ae8c27bfa Merge pull request #98 from louis-e/new-world-generation
Added support for generating new world
2024-12-29 23:28:04 +01:00
Louis Erbkamm
0f47fa1316 Update README.md 2024-12-29 23:20:23 +01:00
louis-e
04c0b5e6f8 fmt (clippy): Removed unneeded return statement required by clippy 2024-12-29 23:17:59 +01:00
louis-e
c339f02d1b fmt (clippy): Removed unneeded return statement required by clippy 2024-12-29 23:02:19 +01:00
louis-e
e2e69d119f fmt: Fixed cargo fmt required formatting 2024-12-29 22:58:05 +01:00
Louis Erbkamm
278c7a2cda Extended bug_report.md 2024-12-29 22:56:47 +01:00
louis-e
fb31cb5a21 Added LastPlayed unix timestamp modification 2024-12-29 22:52:15 +01:00
louis-e
712c7db03e Added support for generating new world 2024-12-29 22:42:53 +01:00
Louis Erbkamm
420852fcb0 Added rotate maps to TODO section 2024-12-29 15:15:23 +01:00
Louis Erbkamm
a3aae00ae0 Updated star history section 2024-12-29 01:23:01 +01:00
Louis Erbkamm
7401036675 Merge pull request #95 from louis-e/floodfill-settings
Add floodfill timeout support
2024-12-29 00:55:24 +01:00
Louis Erbkamm
46cf1bca63 FUNDING.yml 2024-12-29 00:53:10 +01:00
louis-e
ae3a91970f Add floodfill timeout support 2024-12-29 00:41:20 +01:00
Louis Erbkamm
b44b7995d1 Merge pull request #94 from louis-e/session-lock-check
Check session lock before selecting world
2024-12-28 23:55:38 +01:00
louis-e
f3ae449f02 Check session lock before selecting world 2024-12-28 23:41:45 +01:00
Louis Erbkamm
2e319d5ea0 Merge pull request #93 from louis-e/snow-mode
Added winter mode
2024-12-28 23:34:29 +01:00
louis-e
1a259d6dfc Added winter mode 2024-12-28 23:22:07 +01:00
Louis Erbkamm
036e54807e Merge pull request #92 from louis-e/force-dark-theme
Force dark theme
2024-12-28 16:24:13 +01:00
louis-e
a75cffd94b Force dark theme 2024-12-28 16:21:35 +01:00
Louis Erbkamm
135e15077a Merge pull request #91 from louis-e/settings-menu
Added settings menu including scale option and custom bbox input
2024-12-28 03:20:45 +01:00
louis-e
a284f77545 Added settings menu including scale option and custom bbox input 2024-12-28 03:07:47 +01:00
Louis Erbkamm
5cb749ba8f Added .gitattributes 2024-12-25 01:50:30 +01:00
louis-e
b13f8cb96a Added capabilities/ 2024-12-25 01:42:08 +01:00
Louis Erbkamm
5121f3b647 Merge pull request #89 from louis-e/gui-impl
Implemented GUI
2024-12-25 00:52:01 +01:00
louis-e
5a215c1eda ci: remove Ubuntu build from CI workflow due to https://github.com/tauri-apps/tauri/issues/11222 2024-12-25 00:40:56 +01:00
louis-e
fbe40a0611 fix(ci): install libsoup3 and set PKG_CONFIG_PATH to resolve build issues on Ubuntu 2024-12-25 00:38:44 +01:00
louis-e
cab154e631 fix(ci): explicitly add universe and backports repositories to resolve dependency issues 2024-12-25 00:37:46 +01:00
louis-e
5a1d2024f3 fix(ci): resolve libsoup3-dev installation issue by adding universe repository and fallback to source build 2024-12-25 00:36:21 +01:00
louis-e
3c29f3d2a0 fix(ci): resolve gdk-sys build failure on Ubuntu by installing GTK dependencies 2024-12-25 00:32:32 +01:00
louis-e
0f0115f6bc fix(ci): install libsoup3 and set PKG_CONFIG_PATH to resolve build issues on Ubuntu 2024-12-25 00:31:01 +01:00
louis-e
36af3ad79d fix(ci): resolve gdk-sys build failure on Ubuntu by installing GTK dependencies 2024-12-25 00:22:33 +01:00
louis-e
4685a7297c Implemented GUI 2024-12-25 00:08:04 +01:00
Louis Erbkamm
28800b2fc2 Add files via upload 2024-12-24 23:14:23 +01:00
Louis Erbkamm
7c62da68b3 Merge pull request #84 from louis-e/region_file_fix
Fix region file creation and template writing in create_region function
2024-11-02 16:57:46 +01:00
louis-e
e578e5da6e Added truncate 2024-11-02 16:47:47 +01:00
louis-e
8293df44d4 Fix region file creation and template writing in create_region function
- Added `.create(true)` to `File::options()` in `create_region` to ensure the region file is created if it doesn't exist, resolving "file not found" error.
- Replaced `std::fs::copy` with `include_bytes!` macro for in-memory access to `region.template`, simplifying file handling and improving performance.

These changes ensure smoother file creation and handling during world generation.
2024-11-02 16:42:28 +01:00
Louis Erbkamm
8aa2f2b76e Merge pull request #81 from scd31/dont_download_template_region
Don't download region.template from github
2024-11-02 13:35:19 +01:00
Louis Erbkamm
8947b93f2f Fix borrowing CI error 2024-11-02 13:26:29 +01:00
Louis Erbkamm
bc9590ab0d Merge branch 'main' into dont_download_template_region 2024-11-02 13:22:21 +01:00
Louis Erbkamm
7ef7950bcb Merge pull request #80 from scd31/clippy_fixes
Clippy fixes
2024-11-02 13:18:57 +01:00
Louis Erbkamm
25e4b17d32 Merge pull request #83 from scd31/world_format_bugfix
revert a small world change
2024-11-02 13:17:55 +01:00
Stephen D
2068c1aa92 revert a small world change 2024-10-27 21:17:17 -03:00
Stephen D
31fe15becb fix remaining clippys 2024-10-27 11:36:21 -03:00
Stephen D
4b9f9f7d98 add rustfmt to CI 2024-10-27 11:25:57 -03:00
Stephen D
aa68f40170 cargo fmt the project 2024-10-27 11:25:56 -03:00
Stephen D
a394f40659 run clippy lints in CI 2024-10-27 11:24:56 -03:00
Stephen D
4d4ae69a9e fix clippy lints 2024-10-27 11:24:55 -03:00
Louis Erbkamm
7decd3765f Merge pull request #82 from scd31/thicc_world
Raise the world
2024-10-27 13:09:06 +01:00
Louis Erbkamm
fe2ee73166 Merge pull request #79 from scd31/world_scale
allow custom world scales
2024-10-27 12:53:03 +01:00
Louis Erbkamm
c71fced60d Merge pull request #78 from scd31/memeory_improvements
Memory improvements
2024-10-27 12:52:12 +01:00
Louis Erbkamm
0e53b185dc Merge pull request #77 from scd31/river_perf_improvements
river performance improvements
2024-10-27 12:49:42 +01:00
Stephen D
1200e8c32d raise up world 2024-10-25 12:47:09 -03:00
Stephen D
4aea786cd4 make minecraft recompute light levels for us 2024-10-24 22:30:17 -03:00
Stephen D
e5dba4dfbe speed up world saving significantly 2024-10-23 21:44:09 -03:00
Stephen D
5edda1bd7e Don't download region.template from github 2024-10-20 22:30:14 -03:00
Stephen D
c3e7765852 allow custom world scales 2024-10-20 21:16:41 -03:00
Stephen D
c89b9e4941 improve memory usage 2024-10-20 19:34:30 -03:00
Stephen D
dc29108b06 missed a file 2024-10-20 12:50:22 -03:00
Stephen D
78a747a618 river performance improvements 2024-10-20 12:45:40 -03:00
Louis Erbkamm
b727e85f65 Update contributors section in README.md
Added new contributors and removed contributors from the old legacy Python version of the project since they're already mentioned in the according legacy branch
2024-10-19 20:05:33 +02:00
Louis Erbkamm
85ef56cf32 Update contributors section in README.md
Added new contributors and removed contributors from the old legacy Python version of the project since they're already mentioned in the according legacy branch
2024-10-19 20:04:44 +02:00
Louis Erbkamm
14c649963e Merge pull request #76 from scd31/wide_rivers
Wide rivers
2024-10-19 20:00:08 +02:00
Louis Erbkamm
d79593d1c8 Merge pull request #74 from scd31/world_format_fix
fix chunk file in wrong location
2024-10-19 19:55:47 +02:00
Stephen D
839260407b working river generation! Very slow; still have to optimize 2024-10-19 12:19:35 -03:00
Stephen D
13cae5c910 refactor in preparation of river processing 2024-10-18 20:05:35 -03:00
Stephen D
53a62d0150 make north point north 2024-10-17 19:15:16 -03:00
Stephen D
ce17ec3267 fix loading bar 2024-10-16 19:25:01 -03:00
Stephen D
7deb31d898 code cleanup 2024-10-16 19:04:18 -03:00
Stephen D
9b23b62ffe fix bug when computing chunk pos for cities larger than a single region file 2024-10-15 22:43:06 -03:00
Stephen D
8034bd7824 fix chunk file in wrong location 2024-10-15 22:34:41 -03:00
Louis Erbkamm
6d68afd726 Merge pull request #73 from scd31/negative_bbox
Allow hyphen at start of bbox argument
2024-10-14 16:14:55 +02:00
Stephen D
010a7f73f2 allow hyphen at start of bbox argument 2024-10-13 13:40:55 -03:00
Louis Erbkamm
e0cecfdb8d Merge pull request #71 from vfosnar/main
another round of my improvements
2024-09-29 21:59:06 +02:00
Vojtěch Fošnár
5a69850967 enable lto 2024-09-23 20:08:49 +02:00
Vojtěch Fošnár
dfc9dada46 finish color implementation 2024-09-23 20:01:25 +02:00
Vojtěch Fošnár
569f408a07 simple surface for playground/pitch 2024-09-23 19:05:49 +02:00
Vojtěch Fošnár
0ca289d2df start of better wall and roof color implementation 2024-09-23 18:56:18 +02:00
Vojtěch Fošnár
5fa09131e3 implement floodfill timeout 2024-09-23 13:12:40 +02:00
Louis Erbkamm
38aa80b707 Merge pull request #69 from vfosnar/main
- Respect building:colour and roof:colour - get_block_for_color
- Fix parsing height for values containing m suffix
- Raise height calculation from levels * 3 to levels * 4
- Offset floors by 2 to match with the windows
- Simple information=board implementation
2024-09-22 22:31:21 +02:00
Vojtěch Fošnár
d512da2267 offset floors by 2, simple information=board impl 2024-09-22 19:23:39 +02:00
Vojtěch Fošnár
cc92ac42d2 fix parsing height with meters suffix 2024-09-22 15:08:35 +02:00
Vojtěch Fošnár
50213a185e add flake.nix and direnv to support development on NixOS, respect building:colour and roof:colour 2024-09-22 14:52:07 +02:00
Louis Erbkamm
96ad9ebd34 Fixed Issue #65 (Buildings over roads) 2024-09-22 11:42:37 +02:00
Louis Erbkamm
0d48f60c2f Merge pull request #64 from louis-e/api-error-handling
Graceful handling of API request errors
2024-09-21 19:23:32 +02:00
louis-e
755583d540 Graceful handling of API request errors 2024-09-21 19:18:24 +02:00
Louis Erbkamm
1056b89660 Removed survey 2024-09-21 03:02:16 +02:00
Louis Erbkamm
73011560a7 Fixed REMOTE_CARGO_TOML_URL const url 2024-09-21 01:35:07 +02:00
109 changed files with 52418 additions and 1234 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
gui-src/** linguist-vendored

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
buy_me_a_coffee: louisdev

View File

@@ -10,11 +10,14 @@ assignees: ''
**Describe the bug**
A clear and concise description of what the bug is and what you expected to happen.
**Used bbox parameter**
**Used bbox area**
Please provide your input parameters so we can reproduce the issue.
**Used Minecraft version**
Please provide the Minecraft version you used.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here. Please also provide the --bbox input parameters you used so we can reproduce the issue.
Add any other context about the problem here. If you used any more custom parameters, please provide them here too.

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
os: [windows-latest, macos-latest]
steps:
- name: Checkout code
@@ -25,8 +25,15 @@ jobs:
with:
toolchain: stable
target: ${{ matrix.os == 'windows-latest' && 'x86_64-pc-windows-msvc' || 'x86_64-unknown-linux-gnu' || 'x86_64-apple-darwin' }}
components: clippy
- name: Install dependencies
- name: Check formatting
run: cargo fmt -- --check
- name: Check clippy lints
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Install Rust dependencies
run: cargo fetch
- name: Build

50
.gitignore vendored
View File

@@ -1,4 +1,50 @@
# Environment files
.env
.envrc
/.direnv
# Build artifacts
/target
**/*.rs.bk
# Lock files
Cargo.lock
export.json
parsed_osm_data.txt
# IDE/editor files
.idea/
/.vscode/
/*.swp
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.ps1
# System files
.DS_Store
Thumbs.db
*.tmp
*.log
# Generated files
/export.json
/parsed_osm_data.txt
/gen/
/build/
*.rmeta
*.dSYM
# Tauri specific
flake/
gen/
# Miscellaneous
*.bak
*.old
*.orig
# Ignore all in flake directory except specific files
/flake/*
!/flake/flake.nix
!/flake/flake.lock

View File

@@ -1,6 +1,6 @@
[package]
name = "arnis"
version = "2.0.0"
version = "2.1.1"
edition = "2021"
description = "Arnis - Generate real life cities in Minecraft"
homepage = "https://github.com/louis-e/arnis"
@@ -8,11 +8,21 @@ repository = "https://github.com/louis-e/arnis"
license = "GPL-3.0"
readme = "README.md"
[profile.release]
lto = true
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
clap = { version = "4.1", features = ["derive"] }
colored = "2.1.0"
dirs = "4.0.0"
fastanvil = "0.31.0"
fastnbt = "2.5.0"
flate2 = "1.0"
fnv = "1.0.7"
fs2 = "0.4"
geo = "0.28.0"
indicatif = "0.17.8"
itertools = "0.13.0"
@@ -20,6 +30,11 @@ nalgebra = "0.33.0"
once_cell = "1.19.0"
rand = "0.8.5"
reqwest = { version = "0.12.7", features = ["blocking", "json"] }
rfd = "0.15.0"
semver = "1.0.23"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2", features = [] }
tauri-plugin-dialog = "2.0.0"
tauri-plugin-shell = "2"
tokio = { version = "1.42.0", features = ["full"] }

103
README.md
View File

@@ -2,48 +2,57 @@
<img width="456" height="125" src="https://github.com/louis-e/arnis/blob/main/gitassets/logo.png?raw=true">
</p>
# Arnis [![CI Build Status](https://github.com/louis-e/arnis/actions/workflows/ci-build.yml/badge.svg)](https://github.com/louis-e/arnis/actions)
# Arnis [![CI Build Status](https://github.com/louis-e/arnis/actions/workflows/ci-build.yml/badge.svg)](https://github.com/louis-e/arnis/actions) [<img alt="GitHub Release" src="https://img.shields.io/github/v/release/louis-e/arnis" />](https://github.com/louis-e/arnis/releases) [<img alt="GitHub Downloads (all assets, all releases" src="https://img.shields.io/github/downloads/louis-e/arnis/total" />](https://github.com/louis-e/arnis/releases)
This open source project written in Rust generates any chosen location from the real world in Minecraft with a high level of detail.
⇒ [Where did you find this project?](https://6okq6xh5jt4.typeform.com/to/rSjZaB41)
This open source project written in Rust generates any chosen location from the real world in Minecraft Java Edition with a high level of detail.
## :desktop_computer: Example
<img width="700" height="400" src="https://github.com/louis-e/arnis/blob/main/gitassets/mc.gif?raw=true">
![Minecraft Preview](https://github.com/louis-e/arnis/blob/main/gitassets/mc.gif?raw=true)
By leveraging geospatial data from OpenStreetMap and utilizing the powerful capabilities of Rust, Arnis provides an efficient and robust solution for creating complex and accurate Minecraft worlds that reflect real-world geography and architecture.
Arnis is designed to handle large-scale data and generate rich, immersive environments that bring real-world cities, landmarks, and natural features into the Minecraft universe. Whether you're looking to replicate your hometown, explore urban environments, or simply build something unique and realistic, Arnis offers a comprehensive toolset to achieve your vision.
## :keyboard: Usage
<img width="60%" src="https://github.com/louis-e/arnis/blob/main/gitassets/gui.png?raw=true"><br>
Download the [latest release](https://github.com/louis-e/arnis/releases/) or [compile](#trophy-open-source) the project on your own.
Choose your area in Arnis using the rectangle tool and select your Minecraft world - then simply click on 'Start Generation'!
The world will always be generated starting from the coordinates 0 0 0.
If you choose to select an own world, make sure to generate a new flat world in advance in Minecraft.
<details>
<summary>Alternatively you can also run Arnis the old fashioned way in the command line.</summary>
```arnis.exe --path="C:/YOUR_PATH/.minecraft/saves/worldname" --bbox="min_lng,min_lat,max_lng,max_lat"```
The --bbox parameter specifies the bounding box coordinates in the format: min_lng,min_lat,max_lng,max_lat. Use --path to specify the location of the Minecraft world.
<img width="60%" src="https://github.com/louis-e/arnis/blob/main/gitassets/bbox-finder.png?raw=true"><br>
Use http://bboxfinder.com/ to draw a rectangle of your wanted area. Then copy the four box coordinates as shown below and use them as the input for the --bbox parameter. Try starting with a small area since large areas take a lot of computing power and time to process.<br>
<i>Note: This might not be working right now since the console gets suppressed. https://github.com/louis-e/arnis/issues/99</i>
</details>
## :floppy_disk: How it works
![CLI Generation](https://github.com/louis-e/arnis/blob/main/gitassets/cli.gif?raw=true)
The raw data obtained from the API *[(see FAQ)](#question-faq)* includes each element (buildings, walls, fountains, farmlands, etc.) with its respective corner coordinates (nodes) and descriptive tags. When you run Arnis, the following steps are performed automatically to generate a Minecraft world:
#### Processing Pipeline
1. Fetch Data from Overpass API: The script retrieves geospatial data for the desired bounding box from the Overpass API. You can specify the bounding box coordinates using the --bbox parameter.
2. Parse Raw Data: The raw data is parsed to extract essential information like nodes, ways, and relations. Nodes are converted into Minecraft coordinates, and relations are handled similarly to ways, ensuring all relevant elements are processed correctly.
3. Prioritize and Sort Elements: The elements (nodes, ways, relations) are sorted by priority to establish a layering system, which ensures that certain types of elements (e.g., entrances and buildings) are generated in the correct order to avoid conflicts and overlapping structures.
4. Generate Minecraft World: The Minecraft world is generated using a series of element processors (generate_buildings, generate_highways, generate_landuse, etc.) that interpret the tags and nodes of each element to place the appropriate blocks in the Minecraft world. These processors handle the logic for creating 3D structures, roads, natural formations, and more, as specified by the processed data.
5. Generate Ground Layer: A ground layer is generated based on the provided scale factors to provide a base for the entire Minecraft world. This step ensures all areas have an appropriate foundation (e.g., grass and dirt layers).
6. Save the Minecraft World: All the modified chunks are saved back to the Minecraft region files.
## :keyboard: Usage
Get the [latest release](https://github.com/louis-e/arnis/releases/) or [compile](#trophy-open-source) the project on your own.
#### Run: ```arnis.exe --path="C:/YOUR_PATH/.minecraft/saves/worldname" --bbox="min_lng,min_lat,max_lng,max_lat"```
### How to find your bbox coordinates
Use http://bboxfinder.com/ to draw a rectangle of your wanted area. Then copy the four box coordinates as shown below and use them as the input for the --bbox parameter.
![How to find area](https://github.com/louis-e/arnis/blob/main/gitassets/bbox-finder.png?raw=true)
The world will always be generated starting from the coordinates 0 0 0.
Manually generate a new Minecraft world (preferably a flat world) before running the script.
The --bbox parameter specifies the bounding box coordinates in the format: min_lng,min_lat,max_lng,max_lat.
Use --path to specify the location of the Minecraft world.
1. **Fetching Data from the Overpass API:** The script retrieves geospatial data for the desired bounding box from the Overpass API.
2. **Parsing Raw Data:** The raw data is parsed to extract essential information like nodes, ways, and relations. Nodes are converted into Minecraft coordinates, and relations are handled similarly to ways, ensuring all relevant elements are processed correctly. Relations and ways cluster several nodes into one specific object.
3. **Prioritizing and Sorting Elements:** The elements (nodes, ways, relations) are sorted by priority to establish a layering system, which ensures that certain types of elements (e.g., entrances and buildings) are generated in the correct order to avoid conflicts and overlapping structures.
4. **Generating Minecraft World:** The Minecraft world is generated using a series of element processors (generate_buildings, generate_highways, generate_landuse, etc.) that interpret the tags and nodes of each element to place the appropriate blocks in the Minecraft world. These processors handle the logic for creating 3D structures, roads, natural formations, and more, as specified by the processed data.
5. **Generating Ground Layer:** A ground layer is generated based on the provided scale factors to provide a base for the entire Minecraft world. This step ensures all areas have an appropriate foundation (e.g., grass and dirt layers).
6. **Saving the Minecraft World:** All the modified chunks are saved back to the Minecraft region files.
## :question: FAQ
- *Wasn't this written in Python before?*<br>
Yes! Arnis was initially developed in Python, which benefited from Python's open-source friendliness and ease of readability. This is why we strive for clear, well-documented code in the Rust port of this project to find the right balance. I decided to port the project to Rust to learn more about it and push the algorithm's performance further. We were nearing the limits of optimization in Python, and Rust's capabilities allow for even better performance and efficiency. The old Python implementation is still available in the python-legacy branch.
Yes! Arnis was initially developed in Python, which benefited from Python's open-source friendliness and ease of readability. This is why we strive for clear, well-documented code in the Rust port of this project to find the right balance. I decided to port the project to Rust to learn more about the language and push the algorithm's performance further. We were nearing the limits of optimization in Python, and Rust's capabilities allow for even better performance and efficiency. The old Python implementation is still available in the python-legacy branch.
- *Where does the data come from?*<br>
The geographic data is sourced from OpenStreetMap (OSM)[^1], a free, collaborative mapping project that serves as an open-source alternative to commercial mapping services. The data is accessed via the Overpass API, which queries OSM's database.
- *How does the Minecraft world generation work?*<br>
@@ -53,54 +62,60 @@ The project is named after the smallest city in Germany, Arnis[^2]. The city's s
## :memo: ToDo and Known Bugs
Feel free to choose an item from the To-Do or Known Bugs list, or bring your own idea to the table. Bug reports shall be raised as a Github issue. Contributions are highly welcome and appreciated!
- [ ] Design and implement a GUI
- [ ] Evaluate and implement multithreaded region saving
- [ ] Better code documentation
- [ ] Implement house roof types
- [ ] Refactor railway implementation
- [ ] Refactor bridges implementation
- [ ] Refactor fountain structure implementation
- [ ] Mapping real coordinates to Minecraft coordinates (https://github.com/louis-e/arnis/issues/29)
- [ ] Rotate maps (https://github.com/louis-e/arnis/issues/97)
- [ ] Evaluate and implement elevation (https://github.com/louis-e/arnis/issues/66)
- [ ] Fix Github Action Workflow for releasing Linux & MacOS Binary
- [ ] Evaluate and implement faster region saving
- [ ] Automatic new world creation instead of using an existing world
- [ ] Tool for mapping real coordinates to Minecraft coordinates
- [ ] Setup fork of [https://github.com/aaronr/bboxfinder.com](https://github.com/aaronr/bboxfinder.com) for easy bbox picking
- [ ] Implement house roof types
- [ ] Refactor bridges implementation
- [ ] Refactor railway implementation
- [ ] Better code documentation
- [ ] Refactor fountain structure implementation
- [ ] Add interior to buildings
- [ ] Evaluate and implement elevation
- [ ] Generate a few big cities using high performance hardware and make them available to download
- [x] Memory optimization
- [x] Design and implement a GUI
- [x] Fix faulty empty chunks ([https://github.com/owengage/fastnbt/issues/120](https://github.com/owengage/fastnbt/issues/120)) (workaround found)
- [x] Setup fork of [https://github.com/aaronr/bboxfinder.com](https://github.com/aaronr/bboxfinder.com) for easy bbox picking
## :trophy: Open Source
#### Key objectives of this project
- **Modularity**: Ensure that all components (e.g., data fetching, processing, and world generation) are cleanly separated into distinct modules for better maintainability and scalability.
- **Performance Optimization**: Utilize Rusts memory safety and concurrency features to optimize the performance of the world generation process.
- **Comprehensive Documentation**: Detailed in-code documentation for a clear structure and logic.
- **User-Friendly Experience**: Focus on making the project easy to use for end users, with the potential to develop a graphical user interface (GUI) in the future. Suggestions and discussions on UI/UX are welcome.
- **User-Friendly Experience**: Focus on making the project easy to use for end users.
- **Cross-Platform Support**: Ensure the project runs smoothly on Windows, macOS, and Linux.
#### How to contribute
This project is open source and welcomes contributions from everyone! Whether you're interested in fixing bugs, improving performance, adding new features, or enhancing documentation, your input is valuable. Simply fork the repository, make your changes, and submit a pull request. We encourage discussions and suggestions to ensure the project remains modular, optimized, and easy to use for the community. You can use the parameter --debug to get a more detailed output of the processed values, which can be helpful for debugging and development. Contributions of all levels are appreciated, and your efforts help improve this tool for everyone.
Build and run it using: ```cargo run --release -- --path="C:/YOUR_PATH/.minecraft/saves/worldname" --bbox="min_lng,min_lat,max_lng,max_lat"```<br>
For the GUI: ```cargo run --release```<br>
After your pull request was merged, I will take care of regularly creating update releases which will include your changes.
#### Contributors:
This section is dedicated to recognizing and celebrating the outstanding contributions of individuals who have significantly enhanced this project. Your work and dedication are deeply appreciated!
- louis-e
- callumfrance
- amir16yp
- EdwardWeir13579
- daniil2327
- scd31
- vfosnar
*(Including original Python implementation)*
## :star: Star History
[![Star History Chart](https://api.star-history.com/svg?repos=louis-e/arnis&type=Date)](https://star-history.com/#louis-e/arnis&Date)
<a href="https://star-history.com/#louis-e/arnis&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=louis-e/arnis&Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=louis-e/arnis&Date&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=louis-e/arnis&Date&type=Date" />
</picture>
</a>
## :copyright: License Information
This project is licensed under the GNU General Public License v3.0 (GPL-3.0).[^3]
Copyright (c) 2022-2024 louis-e
Copyright (c) 2022-2025 Louis Erbkamm (louis-e)
[^1]: https://en.wikipedia.org/wiki/OpenStreetMap

3
build.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

10
capabilities/default.json Normal file
View File

@@ -0,0 +1,10 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open"
]
}

99
flake/flake.lock generated Normal file
View File

@@ -0,0 +1,99 @@
{
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1724653830,
"narHash": "sha256-88f0KK8h6tGIP4Na5RJDKs0S+7WsGGaCGNkLj/bPV3g=",
"owner": "nix-community",
"repo": "fenix",
"rev": "9ecf5e7d800ace001320da8acadd4a3deb872a83",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1724479785,
"narHash": "sha256-pP3Azj5d6M5nmG68Fu4JqZmdGt4S4vqI5f8te+E/FTw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d0e1602ddde669d5beb01aec49d71a51937ed7be",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1724586512,
"narHash": "sha256-mrfwk6nO8N2WtCq3sB2zhd2QN1HMKzeSESzOA6lSsQg=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "7106cd3be50b2a43c1d9f2787bf22d4369c2b25b",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

27
flake/flake.nix Normal file
View File

@@ -0,0 +1,27 @@
{
inputs = {
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
flake-utils.url = "github:numtide/flake-utils";
nixpkgs.url = "nixpkgs/nixos-unstable";
};
outputs = { self, fenix, flake-utils, nixpkgs }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
fenixPkgs = (fenix.packages.${system}.stable);
in
{
devShell = pkgs.mkShell
{
buildInputs = with pkgs; [
openssl.dev
pkg-config
fenixPkgs.toolchain
];
};
});
}

BIN
gitassets/gui.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

347
gui-src/css/bbox.css vendored Normal file
View File

@@ -0,0 +1,347 @@
html,
body,
#map {
height: 100%;
width: 100%;
padding: 0px;
margin: 0px;
font-family: "Courier New", Courier, monospace;
}
#info-box {
position: absolute;
width: 100%;
height: auto;
bottom: 0;
border: 0 0 7px 0;
z-index: 10000;
}
#coord-format {
position: absolute;
bottom: 0;
right: 15;
}
#coord-format .bboxlabel {
width: 100%;
}
#coord-format form {
margin-bottom: 10px;
}
#info img {
vertical-align: middle;
height: 16px;
width: auto;
opacity: 0.6;
}
#info img:hover {
opacity: 1;
cursor: pointer;
}
#info-toggle ul {
margin: 0 0 0 20px;
padding: 0;
}
#info-toggle ul li {
display: inline-block;
padding: 4px 7px 0;
background-color: rgba(230, 230, 230, 0.4);
border-top-left-radius: 3px;
border-top-right-radius: 3px;
font-weight: 900;
cursor: pointer;
}
#info-toggle ul li.active {
background-color: rgba(255, 255, 255, 0.8);
}
#info-toggle-button {
background-color: rgba(0, 0, 0, 0.7) !important;
color: #fff;
}
#info-toggle-button:hover {
background-color: #f4f4f4;
}
#projlabel,
#wgslabel {
display: inline-block;
color: #333;
z-index: 10000;
}
#projcoords {
display: none;
}
.coords {
text-align: left;
padding: 7px 0;
background-color: rgba(255, 255, 255, 0.8);
}
#bbounds,
#mbounds,
#mcenter {
font-size: small;
display: block;
z-index: 10000;
padding: 2px 0;
}
#mouse,
#tile,
#zoom {
display: inline-block;
}
.bboxlabel {
font-size: small;
font-weight: bold;
z-index: 10000;
background-color: black;
color: white;
text-align: center;
display: inline-block;
padding-left: 2px;
width: 60px;
}
.bboxllpos,
.bboxllpossmall,
.bboxprojpos,
.bboxprojpossmall,
.tilesmall,
.zoomsmall {
font-size: small;
font-weight: bold;
color: black;
display: inline-block;
padding-left: 5px;
}
.bboxllpos {
width: 350px;
}
.bboxllpossmall {
width: 200px;
}
.bboxprojpos {
width: 450px;
}
.bboxprojpossmall {
width: 250px;
}
.tilesmall {
width: 60px;
}
.zoomsmall {
width: 20px;
}
#map-ui-proj {
position: absolute;
top: 460px;
left: 10px;
list-style: none;
margin: 0;
padding: 0;
z-index: 100;
}
#map-ui-proj a {
font: normal 13px/18px 'Helvetica Neue', Helvetica, sans-serif;
background: #FFF;
color: #3C4E5A;
display: block;
margin: 0;
padding: 0;
border: 1px solid #BBB;
border-bottom-width: 0;
min-width: 75px;
padding: 2px;
text-decoration: none;
text-align: center;
}
#map-ui-proj a:hover {
background: #ECF5FA;
}
#map-ui-proj li:last-child a {
border-bottom-width: 1px;
-webkit-border-radius: 0 0 3px 3px;
border-radius: 0 0 3px 3px;
}
#map-ui-proj li:first-child a {
-webkit-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
}
#map-ui-proj a.active {
background: #3887BE;
border-color: #3887BE;
border-top-color: #FFF;
color: #FFF;
}
.epsglabel {
font-size: small;
font-weight: bold;
background-color: black;
color: white;
z-index: 10000;
}
#map-ui {
position: absolute;
top: 330px;
left: 12px;
list-style: none;
margin: 0;
padding: 0;
z-index: 100;
}
#map-ui a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 30px;
height: 30px;
line-height: 30px;
display: block;
text-align: center;
text-decoration: none;
color: black;
background-image: url('css/maps/images/bbox-sprites.png');
background-repeat: no-repeat;
}
#map-ui a:hover {
background-color: #ECF5FA;
}
#map-ui li:last-child a {
border-bottom-width: 1px;
-webkit-border-radius: 0 0 3px 3px;
border-radius: 0 0 3px 3px;
background-position: -33px -2px;
}
#map-ui li:last-child a.enabled {
border-bottom-width: 1px;
-webkit-border-radius: 0 0 3px 3px;
border-radius: 0 0 3px 3px;
background-position: -278px -2px;
}
#map-ui li:first-child a {
-webkit-border-radius: 3px 3px 0 0;
border-radius: 3px 3px 0 0;
}
#create-geojson a.enabled {
background-position: -248px -2px;
}
#create-geojson a {
background-position: -2px -2px;
}
#geolocation a {
background-position: -186px -2px;
}
#geolocation a.active {
background-position: -63px -2px;
}
#help a.enabled {
background-position: -342px -2px;
}
#help a {
background-position: -93px -2px;
}
.zeroclipboard-is-hover {
opacity: 1 !important;
}
.leaflet-sidebar textarea {
/* display: none; */
height: 95%;
width: 100%;
border: 1;
padding: 2em;
resize: none;
-webkit-box-sizing: border-box;
/* Safari/Chrome, other WebKit */
-moz-box-sizing: border-box;
/* Firefox, other Gecko */
box-sizing: border-box;
/* Opera/IE 8+ */
}
.leaflet-sidebar button {
font-size: large;
}
.leaflet-sidebar .close {
z-index: 10001;
}
.ui-autocomplete {
max-height: 180px;
overflow-y: auto;
/* prevent horizontal scrollbar */
overflow-x: hidden;
}
/* IE 6 doesn't support max-height
* we use height instead, but this forces the menu to always be this tall
*/
* html .ui-autocomplete {
height: 180px;
}
.unblurred.leaflet-tile-loaded {
-moz-filter: blur(0) sepia(0) invert(0);
-webkit-filter: blur(0) sepia(0) invert(0);
filter: blur(0) sepia(0) invert(0);
-moz-transition: all 1s ease;
-webkit-transition: all 1s ease;
transition: all 1s ease;
}
.blurred.leaflet-tile-loaded {
-moz-filter: blur(1px) sepia(1) invert(1);
-moz-transition: all 1s ease;
-webkit-filter: blur(1px) sepia(1) invert(1);
-webkit-transition: all 1s ease;
filter: blur(1px) sepia(1) invert(1);
transition: all 1s ease;
}

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

BIN
gui-src/css/maps/images/bbox-sprites.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

505
gui-src/css/maps/images/bbox-sprites.svg vendored Normal file
View File

@@ -0,0 +1,505 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="740"
height="60"
viewBox="0 0 740.00003 59.999999"
enable-background="new 0 0 100 100"
xml:space="preserve"
inkscape:version="0.48.2 r9819"
sodipodi:docname="bbox-sprites.svg"
inkscape:export-filename="/Users/aaronr/github/bboxfinder.com/css/maps/images/bbox-sprites.png"
inkscape:export-xdpi="45"
inkscape:export-ydpi="45"><metadata
id="metadata51"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs49"><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 50 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="100 : 50 : 1"
inkscape:persp3d-origin="50 : 33.333333 : 1"
id="perspective5373" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1226"
inkscape:window-height="844"
id="namedview47"
showgrid="true"
inkscape:zoom="1.32"
inkscape:cx="372.13373"
inkscape:cy="27.069218"
inkscape:window-x="8"
inkscape:window-y="1"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
borderlayer="false"><inkscape:grid
type="xygrid"
id="grid6480"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview>
<g
id="g3285"
transform="matrix(0.84510875,0,0,0.84510875,2.4666349,3.9025275)"><path
inkscape:connector-curvature="0"
id="path3147"
d="m 19.02918,27.70898 0,-1.42397 1.52567,0 1.52569,0 0,-4.78047 0,-4.78046 -1.6274,0 -1.62738,0 0,1.52568 0,1.52568 -1.52567,0 -1.52569,0 0,-3.05136 0,-3.05135 7.83182,0 7.83182,0 0,3.05135 0,3.05136 -1.52569,0 -1.52567,0 0,-1.52568 0,-1.52568 -1.62739,0 -1.62739,0 0,4.78046 0,4.78047 1.52568,0 1.52567,0 0,1.42397 0,1.42396 -4.57703,0 -4.57704,0 0,-1.42396 z"
style="fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path3149"
d="m 19.64356,39.48975 0,-5.51027 1.35498,0 1.35497,0 0,4.15529 0,4.15527 12.64649,0 12.6465,0 0,-9.8462 0,-9.84619 -5.60059,0 -5.60059,0 0,-1.26465 0,-1.26465 6.95558,0 6.95556,0 0,12.46582 0,12.46583 L 35,45 l -15.35644,0 0,-5.51025 z"
style="fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /></g><path
inkscape:connector-curvature="0"
id="path6172"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6178"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6184"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6186"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6270"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6288"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6682"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6684"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><g
id="g3192"
transform="matrix(0.27873303,0,0,0.27873303,128.9627,14.868054)"
style="fill:#464646;fill-opacity:1">
<g
id="g3194"
style="fill:#464646;fill-opacity:1">
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-114.388"
y="10.951"
clip-rule="evenodd"
width="20.32"
height="20.318001"
id="rect3196" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-114.388"
y="36.349998"
clip-rule="evenodd"
width="20.32"
height="20.318001"
id="rect3198" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-114.388"
y="61.748001"
clip-rule="evenodd"
width="20.32"
height="20.319"
id="rect3200" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-139.785"
y="10.951"
clip-rule="evenodd"
width="20.318001"
height="20.318001"
id="rect3202" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-139.785"
y="36.349998"
clip-rule="evenodd"
width="20.318001"
height="20.318001"
id="rect3204" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-139.785"
y="61.748001"
clip-rule="evenodd"
width="20.318001"
height="20.319"
id="rect3206" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-165.187"
y="10.951"
clip-rule="evenodd"
width="20.32"
height="20.318001"
id="rect3208" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-165.187"
y="36.349998"
clip-rule="evenodd"
width="20.32"
height="20.318001"
id="rect3210" />
<rect
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
x="-165.187"
y="61.748001"
clip-rule="evenodd"
width="20.32"
height="20.319"
id="rect3212" />
</g>
<path
style="fill:#464646;fill-opacity:1;fill-rule:evenodd"
inkscape:connector-curvature="0"
clip-rule="evenodd"
d="m -175.144,0.991 v 90.855 h 91.032 V 0.991 h -91.032 z m 84.395,84.217 h -77.757 V 7.63 h 77.757 v 77.578 z"
id="path3214" />
</g><g
style="opacity:0.4;fill:#464646;fill-opacity:1"
transform="matrix(0.27657836,0,0,0.27657836,373.31286,14.597384)"
id="g3288">
<g
style="fill:#464646;fill-opacity:1"
id="g3290">
<rect
id="rect3292"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="10.951"
x="-114.388"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3294"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="36.349998"
x="-114.388"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3296"
height="20.319"
width="20.32"
clip-rule="evenodd"
y="61.748001"
x="-114.388"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3298"
height="20.318001"
width="20.318001"
clip-rule="evenodd"
y="10.951"
x="-139.785"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3300"
height="20.318001"
width="20.318001"
clip-rule="evenodd"
y="36.349998"
x="-139.785"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3302"
height="20.319"
width="20.318001"
clip-rule="evenodd"
y="61.748001"
x="-139.785"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3304"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="10.951"
x="-165.187"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3306"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="36.349998"
x="-165.187"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3308"
height="20.319"
width="20.32"
clip-rule="evenodd"
y="61.748001"
x="-165.187"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
</g>
<path
id="path3310"
d="m -175.144,0.991 v 90.855 h 91.032 V 0.991 h -91.032 z m 84.395,84.217 h -77.757 V 7.63 h 77.757 v 77.578 z"
clip-rule="evenodd"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1;fill-rule:evenodd" />
</g><g
id="g3465"
transform="matrix(0.25404736,0,0,0.25404736,140.09745,14.744984)"
style="fill:#464646;fill-opacity:1"><g
id="g3451"
style="fill:#464646;fill-opacity:1">
<path
id="path3453"
d="m 28.793,71.207 c -5.062,-5.065 -7.6,-11.705 -7.62,-18.356 l -6.515,-0.002 c 0.021,8.32 3.193,16.635 9.526,22.967 6.335,6.331 14.647,9.504 22.967,9.526 L 47.15,78.827 C 40.5,78.806 33.856,76.268 28.793,71.207 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
<path
id="path3455"
d="M 18.423,81.575 C 10.501,73.654 6.535,63.251 6.514,52.85 L 0,52.846 c 0.02,12.074 4.623,24.145 13.815,33.34 9.193,9.188 21.261,13.796 33.337,13.812 V 93.483 C 36.745,93.467 26.346,89.495 18.423,81.575 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" />
</g><path
id="path3457"
d="M 76.157,43.675 73.294,43.334 C 77.075,38.692 76.803,31.847 72.475,27.523 68.154,23.196 61.309,22.924 56.664,26.705 L 56.325,23.843 32.482,0 11.531,20.949 l 23.842,23.843 2.864,0.341 c -3.779,4.643 -3.506,11.485 0.819,15.811 4.325,4.325 11.167,4.596 15.81,0.819 L 55.205,64.625 79.05,88.466 100,67.519 76.157,43.675 z M 38.384,38.586 20.749,20.949 32.482,9.218 l 17.637,17.636 0.669,5.64 -6.762,6.761 -5.642,-0.669 z M 79.05,79.249 61.412,61.613 l -0.668,-5.64 6.761,-6.761 5.642,0.668 17.633,17.639 -11.73,11.73 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" /></g><g
id="g4314"
transform="matrix(0.24999998,0,0,0.24999998,200.32251,15.25725)"
style="fill:#464646;fill-opacity:1"><path
id="path4302"
d="M 49.541,0 C 22.198,-0.019 0.027,22.376 0,49.978 0.027,77.621 22.198,100.01 49.541,100 76.894,100.011 99.064,77.621 99.085,49.979 99.064,22.376 76.894,-0.019 49.541,0 z m 0,88.483 C 28.503,88.483 11.445,71.252 11.462,49.971 11.441,28.742 28.503,11.514 49.541,11.52 70.589,11.514 87.65,28.742 87.673,49.978 87.65,71.251 70.589,88.483 49.541,88.483 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" /><path
id="path4304"
d="m 45.37,64.349 9.073,-0.041 v -7.015 c -0.023,-1.901 0.863,-3.704 3.242,-5.261 2.38,-1.563 9.016,-4.725 9.025,-13.061 C 66.7,30.67 59.73,24.955 53.868,23.72 48.014,22.529 41.665,23.324 37.169,28.281 c -4.042,4.396 -4.892,7.892 -4.905,15.603 h 9.07 v -1.799 c -0.014,-4.103 0.458,-8.438 6.312,-9.64 3.222,-0.631 6.226,0.375 8.018,2.103 2.024,2.007 2.042,6.496 -1.222,8.722 l -5.09,3.465 c -2.959,1.919 -3.977,4.038 -3.983,7.142 v 10.472 l 10e-4,0 z"
inkscape:connector-curvature="0"
style="fill:#464646;fill-opacity:1" /><polygon
id="polygon4306"
points="45.325,67.903 54.484,67.903 54.484,77.194 45.325,77.194 "
style="fill:#464646;fill-opacity:1" /></g><g
transform="matrix(0.84510875,0,0,0.84510875,246.47145,2.7863074)"
id="g4319"
style="opacity:0.4"><path
style="fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
d="m 19.02918,27.70898 0,-1.42397 1.52567,0 1.52569,0 0,-4.78047 0,-4.78046 -1.6274,0 -1.62738,0 0,1.52568 0,1.52568 -1.52567,0 -1.52569,0 0,-3.05136 0,-3.05135 7.83182,0 7.83182,0 0,3.05135 0,3.05136 -1.52569,0 -1.52567,0 0,-1.52568 0,-1.52568 -1.62739,0 -1.62739,0 0,4.78046 0,4.78047 1.52568,0 1.52567,0 0,1.42397 0,1.42396 -4.57703,0 -4.57704,0 0,-1.42396 z"
id="path4321"
inkscape:connector-curvature="0" /><path
style="fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
d="m 19.64356,39.48975 0,-5.51027 1.35498,0 1.35497,0 0,4.15529 0,4.15527 12.64649,0 12.6465,0 0,-9.8462 0,-9.84619 -5.60059,0 -5.60059,0 0,-1.26465 0,-1.26465 6.95558,0 6.95556,0 0,12.46582 0,12.46583 L 35,45 l -15.35644,0 0,-5.51025 z"
id="path4323"
inkscape:connector-curvature="0" /></g><g
style="opacity:0.4;fill:#464646;fill-opacity:1"
transform="matrix(0.24999998,0,0,0.24999998,445.38927,15.676751)"
id="g4325"><path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 49.541,0 C 22.198,-0.019 0.027,22.376 0,49.978 0.027,77.621 22.198,100.01 49.541,100 76.894,100.011 99.064,77.621 99.085,49.979 99.064,22.376 76.894,-0.019 49.541,0 z m 0,88.483 C 28.503,88.483 11.445,71.252 11.462,49.971 11.441,28.742 28.503,11.514 49.541,11.52 70.589,11.514 87.65,28.742 87.673,49.978 87.65,71.251 70.589,88.483 49.541,88.483 z"
id="path4327" /><path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 45.37,64.349 9.073,-0.041 v -7.015 c -0.023,-1.901 0.863,-3.704 3.242,-5.261 2.38,-1.563 9.016,-4.725 9.025,-13.061 C 66.7,30.67 59.73,24.955 53.868,23.72 48.014,22.529 41.665,23.324 37.169,28.281 c -4.042,4.396 -4.892,7.892 -4.905,15.603 h 9.07 v -1.799 c -0.014,-4.103 0.458,-8.438 6.312,-9.64 3.222,-0.631 6.226,0.375 8.018,2.103 2.024,2.007 2.042,6.496 -1.222,8.722 l -5.09,3.465 c -2.959,1.919 -3.977,4.038 -3.983,7.142 v 10.472 l 10e-4,0 z"
id="path4329" /><polygon
style="fill:#464646;fill-opacity:1"
points="45.325,67.903 54.484,67.903 54.484,77.194 45.325,77.194 "
id="polygon4331" /></g><g
style="opacity:0.4;fill:#464646;fill-opacity:1"
transform="matrix(0.25404736,0,0,0.25404736,384.37987,15.991112)"
id="g4333"><g
style="fill:#464646;fill-opacity:1"
id="g4335">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 28.793,71.207 c -5.062,-5.065 -7.6,-11.705 -7.62,-18.356 l -6.515,-0.002 c 0.021,8.32 3.193,16.635 9.526,22.967 6.335,6.331 14.647,9.504 22.967,9.526 L 47.15,78.827 C 40.5,78.806 33.856,76.268 28.793,71.207 z"
id="path4337" />
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 18.423,81.575 C 10.501,73.654 6.535,63.251 6.514,52.85 L 0,52.846 c 0.02,12.074 4.623,24.145 13.815,33.34 9.193,9.188 21.261,13.796 33.337,13.812 V 93.483 C 36.745,93.467 26.346,89.495 18.423,81.575 z"
id="path4339" />
</g><path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 76.157,43.675 73.294,43.334 C 77.075,38.692 76.803,31.847 72.475,27.523 68.154,23.196 61.309,22.924 56.664,26.705 L 56.325,23.843 32.482,0 11.531,20.949 l 23.842,23.843 2.864,0.341 c -3.779,4.643 -3.506,11.485 0.819,15.811 4.325,4.325 11.167,4.596 15.81,0.819 L 55.205,64.625 79.05,88.466 100,67.519 76.157,43.675 z M 38.384,38.586 20.749,20.949 32.482,9.218 l 17.637,17.636 0.669,5.64 -6.762,6.761 -5.642,-0.669 z M 79.05,79.249 61.412,61.613 l -0.668,-5.64 6.761,-6.761 5.642,0.668 17.633,17.639 -11.73,11.73 z"
id="path4341" /></g><rect
style="opacity:0.8;fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3039"
width="250.00003"
height="60"
x="490"
y="-1.7162162e-06" /><g
transform="matrix(0.84510875,0,0,0.84510875,492.46665,4.1596899)"
id="g3041"
style="fill:#ffffff"><path
style="fill:#ffffff;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
d="m 19.02918,27.70898 0,-1.42397 1.52567,0 1.52569,0 0,-4.78047 0,-4.78046 -1.6274,0 -1.62738,0 0,1.52568 0,1.52568 -1.52567,0 -1.52569,0 0,-3.05136 0,-3.05135 7.83182,0 7.83182,0 0,3.05135 0,3.05136 -1.52569,0 -1.52567,0 0,-1.52568 0,-1.52568 -1.62739,0 -1.62739,0 0,4.78046 0,4.78047 1.52568,0 1.52567,0 0,1.42397 0,1.42396 -4.57703,0 -4.57704,0 0,-1.42396 z"
id="path3043"
inkscape:connector-curvature="0" /><path
style="fill:#ffffff;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
d="m 19.64356,39.48975 0,-5.51027 1.35498,0 1.35497,0 0,4.15529 0,4.15527 12.64649,0 12.6465,0 0,-9.8462 0,-9.84619 -5.60059,0 -5.60059,0 0,-1.26465 0,-1.26465 6.95558,0 6.95556,0 0,12.46582 0,12.46583 L 35,45 l -15.35644,0 0,-5.51025 z"
id="path3045"
inkscape:connector-curvature="0" /></g><g
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.27873303,0,0,0.27873303,618.96272,15.125216)"
id="g3047">
<g
style="fill:#ffffff;fill-opacity:1"
id="g3049">
<rect
id="rect3051"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="10.951"
x="-114.388"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3053"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="36.349998"
x="-114.388"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3055"
height="20.319"
width="20.32"
clip-rule="evenodd"
y="61.748001"
x="-114.388"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3057"
height="20.318001"
width="20.318001"
clip-rule="evenodd"
y="10.951"
x="-139.785"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3059"
height="20.318001"
width="20.318001"
clip-rule="evenodd"
y="36.349998"
x="-139.785"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3061"
height="20.319"
width="20.318001"
clip-rule="evenodd"
y="61.748001"
x="-139.785"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3063"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="10.951"
x="-165.187"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3065"
height="20.318001"
width="20.32"
clip-rule="evenodd"
y="36.349998"
x="-165.187"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
<rect
id="rect3067"
height="20.319"
width="20.32"
clip-rule="evenodd"
y="61.748001"
x="-165.187"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
</g>
<path
id="path3069"
d="m -175.144,0.991 v 90.855 h 91.032 V 0.991 h -91.032 z m 84.395,84.217 h -77.757 V 7.63 h 77.757 v 77.578 z"
clip-rule="evenodd"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd" />
</g><g
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.25404736,0,0,0.25404736,630.09747,15.002146)"
id="g3071"><g
style="fill:#ffffff;fill-opacity:1"
id="g3073">
<path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 28.793,71.207 c -5.062,-5.065 -7.6,-11.705 -7.62,-18.356 l -6.515,-0.002 c 0.021,8.32 3.193,16.635 9.526,22.967 6.335,6.331 14.647,9.504 22.967,9.526 L 47.15,78.827 C 40.5,78.806 33.856,76.268 28.793,71.207 z"
id="path3075" />
<path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 18.423,81.575 C 10.501,73.654 6.535,63.251 6.514,52.85 L 0,52.846 c 0.02,12.074 4.623,24.145 13.815,33.34 9.193,9.188 21.261,13.796 33.337,13.812 V 93.483 C 36.745,93.467 26.346,89.495 18.423,81.575 z"
id="path3077" />
</g><path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 76.157,43.675 73.294,43.334 C 77.075,38.692 76.803,31.847 72.475,27.523 68.154,23.196 61.309,22.924 56.664,26.705 L 56.325,23.843 32.482,0 11.531,20.949 l 23.842,23.843 2.864,0.341 c -3.779,4.643 -3.506,11.485 0.819,15.811 4.325,4.325 11.167,4.596 15.81,0.819 L 55.205,64.625 79.05,88.466 100,67.519 76.157,43.675 z M 38.384,38.586 20.749,20.949 32.482,9.218 l 17.637,17.636 0.669,5.64 -6.762,6.761 -5.642,-0.669 z M 79.05,79.249 61.412,61.613 l -0.668,-5.64 6.761,-6.761 5.642,0.668 17.633,17.639 -11.73,11.73 z"
id="path3079" /></g><g
style="fill:#ffffff;fill-opacity:1"
transform="matrix(0.24999998,0,0,0.24999998,690.32253,15.514412)"
id="g3081"><path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
d="M 49.541,0 C 22.198,-0.019 0.027,22.376 0,49.978 0.027,77.621 22.198,100.01 49.541,100 76.894,100.011 99.064,77.621 99.085,49.979 99.064,22.376 76.894,-0.019 49.541,0 z m 0,88.483 C 28.503,88.483 11.445,71.252 11.462,49.971 11.441,28.742 28.503,11.514 49.541,11.52 70.589,11.514 87.65,28.742 87.673,49.978 87.65,71.251 70.589,88.483 49.541,88.483 z"
id="path3083" /><path
style="fill:#ffffff;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 45.37,64.349 9.073,-0.041 v -7.015 c -0.023,-1.901 0.863,-3.704 3.242,-5.261 2.38,-1.563 9.016,-4.725 9.025,-13.061 C 66.7,30.67 59.73,24.955 53.868,23.72 48.014,22.529 41.665,23.324 37.169,28.281 c -4.042,4.396 -4.892,7.892 -4.905,15.603 h 9.07 v -1.799 c -0.014,-4.103 0.458,-8.438 6.312,-9.64 3.222,-0.631 6.226,0.375 8.018,2.103 2.024,2.007 2.042,6.496 -1.222,8.722 l -5.09,3.465 c -2.959,1.919 -3.977,4.038 -3.983,7.142 v 10.472 l 10e-4,0 z"
id="path3085" /><polygon
style="fill:#ffffff;fill-opacity:1"
points="45.325,77.194 45.325,67.903 54.484,67.903 54.484,77.194 "
id="polygon3087" /></g></svg>

After

Width:  |  Height:  |  Size: 21 KiB

BIN
gui-src/css/maps/images/icons-404040.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
gui-src/css/maps/images/icons-ffffff.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

35
gui-src/css/maps/images/icons.svg vendored Normal file
View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="260" viewBox="0 0 26 260" >
<g id="sprite" fill="#000000">
<path
id="zoomIn"
d="m 12.5,8.99999 c -0.277,0 -0.5,0.223 -0.5,0.5 l 0,2.50002 -2.5,0 c -0.277,0 -0.5,0.223 -0.5,0.5 l 0,1 c 0,0.277 0.223,0.5 0.5,0.5 l 2.5,0 0,2.5 c 0,0.277 0.223,0.5 0.5,0.5 l 1,0 c 0.277,0 0.5,-0.223 0.5,-0.5 l 0,-2.5 2.5,0 c 0.277,0 0.5,-0.223 0.5,-0.5 l 0,-1 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 l -2.5,0 0,-2.50002 c 0,-0.277 -0.223,-0.5 -0.5,-0.5 z" />
<path
id="zoomOut"
d="M 9.5,38 C 9.223,38 9,38.223 9,38.5 l 0,1 C 9,39.777 9.223,40 9.5,40 l 7,0 c 0.277,0 0.5,-0.223 0.5,-0.5 l 0,-1 C 17,38.223 16.777,38 16.5,38 Z" />
<path
id="close"
d="m 10,61 -1,1 3,3 -3,3 1,1 3,-3 3,3 1,-1 -3,-3 3,-3 -1,-1 -3,3 z" />
<path
id="info"
d="m 13,83.99999 c -3.866,0 -7,3.134 -7,7.00002 0,3.866 3.134,7 7,7 3.8659,0 7,-3.134 7,-7 0,-3.86602 -3.1341,-7.00002 -7,-7.00002 z m -0.5,3 1,0 c 0.277,0 0.5,0.223 0.5,0.5 l 0,1 c 0,0.27702 -0.223,0.50002 -0.5,0.50002 l -1,0 c -0.277,0 -0.5,-0.223 -0.5,-0.50002 l 0,-1 c 0,-0.277 0.223,-0.5 0.5,-0.5 z m 0,3.00002 1,0 c 0.277,0 0.5,0.223 0.5,0.5 l 0,4 c 0,0.277 -0.223,0.5 -0.5,0.5 l -1,0 c -0.277,0 -0.5,-0.223 -0.5,-0.5 l 0,-4 c 0,-0.277 0.223,-0.5 0.5,-0.5 z" />
<path
id="layers"
d="m 13,110.96875 c -0.1012,-5e-4 -0.19265,0.0145 -0.28125,0.0625 L 6,113 l 0,1 6.71875,1.9375 C 12.80675,115.9855 12.8995,116 13,116 c 0.1001,0 0.19325,-0.0145 0.28125,-0.0625 L 20,114 l 0,-1 -6.71875,-1.96875 c -0.088,-0.048 -0.18155,-0.0625 -0.28125,-0.0625 z M 6,118 l 6.71875,1.9375 C 12.80675,119.9855 12.8995,120 13,120 c 0.1001,0 0.19325,-0.0145 0.28125,-0.0625 L 20,118 l 0,-2 -6.71875,1.9375 C 13.19325,117.9855 13.1001,118 13,118 c -0.1005,0 -0.19325,-0.0145 -0.28125,-0.0625 L 6,116 Z m 0,4 6.71875,1.9375 C 12.80675,123.9855 12.8995,124 13,124 c 0.1001,0 0.19325,-0.0145 0.28125,-0.0625 L 20,122 l 0,-2 -6.71875,1.9375 C 13.19325,121.9855 13.1001,122 13,122 c -0.1005,0 -0.19325,-0.0145 -0.28125,-0.0625 L 6,120 Z" />
<path
id="share"
d="m 14.3438,136.99999 2.125,2.125 -4.25,4.25002 1.4062,1.4062 4.25,-4.25002 2.125,2.12502 0,-5.65622 z m -6.3438,1 -1,1 0,9.00002 1,1 10,0 1,-1 0,-4 -1,-1 -1,1 0,3 -8,0 0,-7.00002 4,0 1,-1 -1,-1 z" />
<path
id="search"
d="m 11.5,162.99999 c -2.4853,0 -4.5,2.0147 -4.5,4.50002 0,2.4853 2.0147,4.5 4.5,4.5 0.854,0 1.6635,-0.2431 2.3437,-0.6563 l 3.6563,3.6563 1.5,-1.5 -3.6563,-3.6563 c 0.4132,-0.6802 0.6563,-1.4897 0.6563,-2.3437 0,-2.48532 -2.0147,-4.50002 -4.5,-4.50002 z m 0,1.5 c 1.6568,0 3,1.3431 3,3.00002 0,1.6568 -1.3432,3 -3,3 -1.6569,0 -3,-1.3432 -3,-3 0,-1.65692 1.3431,-3.00002 3,-3.00002 z" />
<path
id="facebook"
d="m 7,186.99998 -1,1 0,12 1,1 6.81812,1e-5 0,-5.00001 -1.8245,0 0,-2 1.8245,0 0,-2.09264 c 0,-1.80824 1.10439,-2.79288 2.7175,-2.79288 0.77272,0 1.09654,0.0575 1.29005,0.0832 l 0,1.80232 -0.77851,5.2e-4 C 16.1699,191.0005 16,191.41736 16,192.0291 l 0,1.97088 2.09228,0 -0.2724,2 -1.81988,0 0,5.00001 3,-1e-5 1,-1 0,-12 -1,-1 z" />
<path
id="twitter"
d="m 19.99957,216.12602 c -0.51509,0.22492 -1.06868,0.37693 -1.64967,0.44524 0.59299,-0.34993 1.04838,-0.90408 1.26288,-1.56444 -0.55499,0.32413 -1.16968,0.55945 -1.82397,0.68616 C 17.26502,215.14343 16.51853,214.8 15.69245,214.8 c -1.58627,0 -2.87235,1.26611 -2.87235,2.82775 0,0.22164 0.025,0.43756 0.075,0.64448 -2.38705,-0.11791 -4.50341,-1.24373 -5.91999,-2.95458 -0.24719,0.41763 -0.38889,0.90338 -0.38889,1.42162 0,0.98111 0.50699,1.84658 1.27768,2.35373 -0.4708,-0.015 -0.91369,-0.14191 -1.30088,-0.35363 -3e-4,0.012 -3e-4,0.024 -3e-4,0.035 0,1.37002 0.99008,2.51291 2.30396,2.77274 -0.241,0.065 -0.49469,0.099 -0.75669,0.099 -0.1851,0 -0.36499,-0.018 -0.54029,-0.051 0.36539,1.1234 1.42607,1.94097 2.68295,1.96377 -0.98298,0.75847 -2.22136,1.21061 -3.56713,1.21061 -0.2318,0 -0.46039,-0.013 -0.68509,-0.04 C 7.27151,225.53176 8.78128,226 10.40325,226 c 5.2831,0 8.17205,-4.30888 8.17205,-8.0457 0,-0.12261 0,-0.24462 -0.01,-0.36585 0.56129,-0.39874 1.04818,-0.89668 1.43327,-1.46383" />
<path
id="pinterest"
d="M 13.50024,238.9996 C 9.35822,238.9996 6,242.35751 6,246.49969 c 0,3.07113 1.84674,5.70949 4.48939,6.86948 -0.0211,-0.5237 -0.004,-1.15239 0.13052,-1.7222 0.14421,-0.60892 0.96507,-4.08682 0.96507,-4.08682 0,0 -0.23961,-0.47889 -0.23961,-1.18669 0,-1.11141 0.64421,-1.94155 1.44648,-1.94155 0.68215,0 1.01169,0.51241 1.01169,1.12601 0,0.68581 -0.43735,1.71156 -0.66231,2.66165 -0.18789,0.79559 0.39892,1.44451 1.1838,1.44451 1.42105,0 2.37808,-1.82512 2.37808,-3.98753 0,-1.64383 -1.10715,-2.87415 -3.12081,-2.87415 -2.27506,0 -3.69243,1.69664 -3.69243,3.59178 0,0.6534 0.19268,1.11426 0.49445,1.47102 0.13874,0.1639 0.15803,0.22983 0.10778,0.418 -0.0359,0.13804 -0.11858,0.47026 -0.15277,0.60191 -0.05,0.18998 -0.20384,0.25786 -0.37553,0.18774 -1.04789,-0.4278 -1.53596,-1.5754 -1.53596,-2.86536 0,-2.13055 1.79685,-4.68521 5.36034,-4.68521 2.86349,0 4.74808,2.0721 4.74808,4.29635 0,2.94222 -1.63569,5.14028 -4.04684,5.14028 -0.8097,0 -1.57134,-0.43774 -1.83228,-0.9349 0,0 -0.43543,1.72808 -0.52766,2.06171 -0.15897,0.57829 -0.47026,1.1562 -0.75486,1.60673 0.67461,0.19909 1.3871,0.30755 2.12562,0.30755 4.1417,0 7.49977,-3.35795 7.49977,-7.50031 0,-4.14218 -3.35807,-7.50009 -7.49977,-7.50009" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
gui-src/css/maps/images/spritesheet.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

297
gui-src/css/maps/images/spritesheet.svg vendored Normal file
View File

@@ -0,0 +1,297 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="Layer_1"
x="0px"
y="0px"
width="1080"
height="60"
viewBox="0 0 1080 59.999999"
enable-background="new 0 0 100 100"
xml:space="preserve"
inkscape:version="0.48.2 r9819"
sodipodi:docname="spritesheet_new.svg"
inkscape:export-filename="/Users/aaronr/github/bboxfinder.com/css/maps/images/spritesheet_new.png"
inkscape:export-xdpi="45"
inkscape:export-ydpi="45"><metadata
id="metadata51"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs49"><inkscape:perspective
sodipodi:type="inkscape:persp3d"
inkscape:vp_x="0 : 50 : 1"
inkscape:vp_y="0 : 1000 : 0"
inkscape:vp_z="100 : 50 : 1"
inkscape:persp3d-origin="50 : 33.333333 : 1"
id="perspective5373" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1226"
inkscape:window-height="844"
id="namedview47"
showgrid="true"
inkscape:zoom="0.83039936"
inkscape:cx="502.47411"
inkscape:cy="27.069218"
inkscape:window-x="197"
inkscape:window-y="15"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
borderlayer="false"><inkscape:grid
type="xygrid"
id="grid6480"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" /></sodipodi:namedview>
<path
inkscape:connector-curvature="0"
id="path6172"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6178"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6184"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6186"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6270"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6288"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6682"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><path
inkscape:connector-curvature="0"
id="path6684"
d=""
style="fill:#bbbbbb;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none" /><g
style="fill:#464646;fill-opacity:1"
id="g3558"
transform="translate(-1.07,-5e-7)">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 18,36 v 6 h 6 v -6 h -6 z m 4,4 h -2 v -2 h 2 v 2 z"
id="path3560" />
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 36,18 v 6 h 6 v -6 h -6 z m 4,4 h -2 v -2 h 2 v 2 z"
id="path3562" />
<polygon
style="fill:#464646;fill-opacity:1"
points="20.857,36.854 36.857,20.857 39.142,23.142 23.142,39.145 "
id="polygon3564" />
</g><polygon
style="fill:#464646;fill-opacity:1"
points="100,24.565 97.904,39.396 83.07,42 76,28.773 86.463,18 "
id="polygon3566"
transform="translate(-1.07,-5e-7)" /><rect
style="fill:#464646;fill-opacity:1"
x="138.92999"
y="20"
width="20"
height="20"
id="rect3568" /><path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 219.93,30 c 0,6.078 -4.926,11 -11,11 -6.074,0 -11,-4.922 -11,-11 0,-6.074 4.926,-11 11,-11 6.074,0 11,4.926 11,11 z"
id="path3570" /><path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 268.93,19 c -4.971,0 -9,4.028999 -9,9 0,4.971 5.001,12 9,14 4.001,-2 9,-9.029 9,-14 0,-4.971001 -4.029,-9 -9,-9 z m 0,12.5 c -2.484,0 -4.5,-2.014 -4.5,-4.5 0,-2.484001 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.015999 4.5,4.5 0,2.486 -2.015,4.5 -4.5,4.5 z"
id="path3572" /><g
style="fill:#464646;fill-opacity:1"
id="g3574"
transform="translate(-1.07,-5e-7)">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 337,30.156 v 0.407 5.604 c 0,1.658 -1.344,3 -3,3 h -10 c -1.655,0 -3,-1.342 -3,-3 v -10 c 0,-1.657 1.345,-3 3,-3 h 6.345 l 3.19,-3.17 H 324 c -3.313,0 -6,2.687 -6,6 v 10 c 0,3.313 2.687,6 6,6 h 10 c 3.314,0 6,-2.687 6,-6 v -8.809 l -3,2.968"
id="path3576" />
<polygon
style="fill:#464646;fill-opacity:1"
points="335.891,21.809 338.719,24.637 329.828,33.529 327,33.529 327,30.699 "
id="polygon3578" />
<rect
style="fill:#464646;fill-opacity:1"
x="338.69699"
y="17.826"
transform="matrix(-0.707,-0.7072,0.7072,-0.707,567.5655,274.7729)"
width="4"
height="4"
id="rect3580" />
</g><g
style="fill:#464646;fill-opacity:1"
id="g3582"
transform="translate(-1.07,-5e-7)">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 381,42 h 18 V 24 h -18 v 18 z m 14,-16 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z"
id="path3584" />
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 395,20 v -2 -2 h -10 v 2 2 h -6 v 2 h 22 v -2 h -6 z m -2,0 h -6 v -2 h 6 v 2 z"
id="path3586" />
</g><g
style="opacity:0.4;fill:#464646;fill-opacity:1"
id="g3588"
transform="translate(-1.07,-5e-7)">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 458,30.156 v 0.407 5.604 c 0,1.658 -1.344,3 -3,3 h -10 c -1.655,0 -3,-1.342 -3,-3 v -10 c 0,-1.657 1.345,-3 3,-3 h 6.345 l 3.19,-3.17 H 445 c -3.313,0 -6,2.687 -6,6 v 10 c 0,3.313 2.687,6 6,6 h 10 c 3.314,0 6,-2.687 6,-6 v -8.809 l -3,2.968"
id="path3590" />
<polygon
style="fill:#464646;fill-opacity:1"
points="456.891,21.809 459.719,24.637 450.828,33.529 448,33.529 448,30.699 "
id="polygon3592" />
<rect
style="fill:#464646;fill-opacity:1"
x="459.69699"
y="17.826"
transform="matrix(-0.707,-0.7072,0.7072,-0.707,774.118,360.3402)"
width="4"
height="4"
id="rect3594" />
</g><g
style="opacity:0.4;fill:#464646;fill-opacity:1"
id="g3596"
transform="translate(-1.07,-5e-7)">
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 501,42 h 18 V 24 h -18 v 18 z m 14,-16 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z"
id="path3598" />
<path
style="fill:#464646;fill-opacity:1"
inkscape:connector-curvature="0"
d="m 515,20 v -2 -2 h -10 v 2 2 h -6 v 2 h 22 v -2 h -6 z m -2,0 h -6 v -2 h 6 v 2 z"
id="path3600" />
</g><rect
style="opacity:0.4;fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
id="rect3684"
width="540"
height="60"
x="542"
y="1.9999995" /><path
style="opacity:0.8;fill:#464646;fill-opacity:1;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none"
d="m 542.50845,29.920029 0,-29.0322577 268.74579,0 268.74576,0 0,29.0322577 0,29.032258 -268.74576,0 -268.74579,0 0,-29.032258 z"
id="path3686"
inkscape:connector-curvature="0" /><g
transform="translate(540,-1.0000005)"
id="g3654"
style="fill:#ffffff;fill-opacity:1">
<path
id="path3656"
d="m 18,36 v 6 h 6 v -6 h -6 z m 4,4 h -2 v -2 h 2 v 2 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" />
<path
id="path3658"
d="m 36,18 v 6 h 6 v -6 h -6 z m 4,4 h -2 v -2 h 2 v 2 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" />
<polygon
id="polygon3660"
points="36.857,20.857 39.142,23.142 23.142,39.145 20.857,36.854 "
style="fill:#ffffff;fill-opacity:1" />
</g><polygon
transform="translate(540,-1.0000005)"
id="polygon3662"
points="100,24.565 97.904,39.396 83.07,42 76,28.773 86.463,18 "
style="fill:#ffffff;fill-opacity:1" /><rect
id="rect3664"
height="20"
width="20"
y="19"
x="680"
style="fill:#ffffff;fill-opacity:1" /><path
id="path3666"
d="m 761,29 c 0,6.078001 -4.926,11 -11,11 -6.074,0 -11,-4.921999 -11,-11 0,-6.073999 4.926,-10.999999 11,-10.999999 6.074,0 11,4.926 11,10.999999 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><path
id="path3668"
d="m 810,18.000001 c -4.971,0 -9,4.028999 -9,8.999999 0,4.971001 5.001,12 9,14 4.001,-2 9,-9.028999 9,-14 0,-4.971 -4.029,-8.999999 -9,-8.999999 z M 810,30.5 c -2.484,0 -4.5,-2.013999 -4.5,-4.5 0,-2.483999 2.016,-4.5 4.5,-4.5 2.485,0 4.5,2.016001 4.5,4.5 0,2.486001 -2.015,4.5 -4.5,4.5 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" /><g
transform="translate(540,-1.0000005)"
id="g3670"
style="fill:#ffffff;fill-opacity:1">
<path
id="path3672"
d="m 337,30.156 v 0.407 5.604 c 0,1.658 -1.344,3 -3,3 h -10 c -1.655,0 -3,-1.342 -3,-3 v -10 c 0,-1.657 1.345,-3 3,-3 h 6.345 l 3.19,-3.17 H 324 c -3.313,0 -6,2.687 -6,6 v 10 c 0,3.313 2.687,6 6,6 h 10 c 3.314,0 6,-2.687 6,-6 v -8.809 l -3,2.968"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" />
<polygon
id="polygon3674"
points="335.891,21.809 338.719,24.637 329.828,33.529 327,33.529 327,30.699 "
style="fill:#ffffff;fill-opacity:1" />
<rect
id="rect3676"
height="4"
width="4"
transform="matrix(-0.707,-0.7072,0.7072,-0.707,567.5655,274.7729)"
y="17.826"
x="338.69699"
style="fill:#ffffff;fill-opacity:1" />
</g><g
transform="translate(540,-1.0000005)"
id="g3678"
style="fill:#ffffff;fill-opacity:1">
<path
id="path3680"
d="m 381,42 h 18 V 24 h -18 v 18 z m 14,-16 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z m -4,0 h 2 v 14 h -2 V 26 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" />
<path
id="path3682"
d="m 395,20 v -2 -2 h -10 v 2 2 h -6 v 2 h 22 v -2 h -6 z m -2,0 h -6 v -2 h 6 v 2 z"
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1" />
</g></svg>

After

Width:  |  Height:  |  Size: 11 KiB

478
gui-src/css/maps/leaflet.css vendored Normal file
View File

@@ -0,0 +1,478 @@
/* required styles */
.leaflet-map-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-tile-container,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
-ms-touch-action: none;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container img {
max-width: none !important;
}
/* stupid Android 2 doesn't understand "max-width: none" properly */
.leaflet-container img.leaflet-image-layer {
max-width: 15000px !important;
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-tile-pane { z-index: 2; }
.leaflet-objects-pane { z-index: 3; }
.leaflet-overlay-pane { z-index: 4; }
.leaflet-shadow-pane { z-index: 5; }
.leaflet-marker-pane { z-index: 6; }
.leaflet-popup-pane { z-index: 7; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 7;
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile,
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
-o-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded,
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile,
.leaflet-touching .leaflet-zoom-animated {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-clickable {
cursor: pointer;
}
.leaflet-container {
cursor: -webkit-grab;
cursor: -moz-grab;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-container,
.leaflet-dragging .leaflet-clickable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline: 0;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-container a.leaflet-active {
outline: 2px solid orange;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a,
.leaflet-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-control-zoom-out {
font-size: 20px;
}
.leaflet-touch .leaflet-control-zoom-in {
font-size: 22px;
}
.leaflet-touch .leaflet-control-zoom-out {
font-size: 24px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover {
text-decoration: underline;
}
.leaflet-container .leaflet-control-attribution,
.leaflet-container .leaflet-control-scale {
font-size: 11px;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: content-box;
box-sizing: content-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 19px;
line-height: 1.4;
}
.leaflet-popup-content p {
margin: 18px 0;
}
.leaflet-popup-tip-container {
margin: 0 auto;
width: 40px;
height: 20px;
position: relative;
overflow: hidden;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
-o-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
padding: 4px 4px 0 0;
text-align: center;
width: 18px;
height: 14px;
font: 16px/14px Tahoma, Verdana, sans-serif;
color: #c3c3c3;
text-decoration: none;
font-weight: bold;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover {
color: #999;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}

248
gui-src/css/maps/leaflet.draw.css vendored Normal file
View File

@@ -0,0 +1,248 @@
/* ================================================================== */
/* Toolbars
/* ================================================================== */
.leaflet-draw-section {
position: relative;
}
.leaflet-draw-toolbar {
margin-top: 12px;
}
.leaflet-draw-toolbar-top {
margin-top: 0;
}
.leaflet-draw-toolbar-notop a:first-child {
border-top-right-radius: 0;
}
.leaflet-draw-toolbar-nobottom a:last-child {
border-bottom-right-radius: 0;
}
.leaflet-draw-toolbar a {
background-image: url('images/spritesheet.png');
background-repeat: no-repeat;
}
.leaflet-retina .leaflet-draw-toolbar a {
background-image: url('images/spritesheet-2x.png');
background-size: 210px 30px;
}
.leaflet-draw a {
display: block;
text-align: center;
text-decoration: none;
}
/* ================================================================== */
/* Toolbar actions menu
/* ================================================================== */
.leaflet-draw-actions {
display: none;
list-style: none;
margin: 0;
padding: 0;
position: absolute;
left: 26px; /* leaflet-draw-toolbar.left + leaflet-draw-toolbar.width */
top: 0;
}
.leaflet-right .leaflet-draw-actions {
right:26px;
left:auto;
}
.leaflet-draw-actions li {
display: inline-block;
}
/* APR */
.leaflet-draw-actions li a {
color: #ffffff;
}
.leaflet-draw-actions li:first-child a {
border-left: none;
}
.leaflet-draw-actions li:last-child a {
-webkit-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
.leaflet-right .leaflet-draw-actions li:last-child a {
-webkit-border-radius: 0;
border-radius: 0;
}
.leaflet-right .leaflet-draw-actions li:first-child a {
-webkit-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.leaflet-draw-actions a {
background-color: #919187;
border-left: 1px solid #AAA;
color: #FFF;
font: 11px/19px "Helvetica Neue", Arial, Helvetica, sans-serif;
line-height: 28px;
text-decoration: none;
padding-left: 10px;
padding-right: 10px;
height: 28px;
}
.leaflet-draw-actions-bottom {
margin-top: 0;
white-space: nowrap;
}
.leaflet-draw-actions-top {
margin-top: 1px;
white-space: nowrap;
}
.leaflet-draw-actions-top a,
.leaflet-draw-actions-bottom a {
height: 27px;
line-height: 27px;
}
.leaflet-draw-actions a:hover {
background-color: #A0A098;
}
.leaflet-draw-actions-top.leaflet-draw-actions-bottom a {
height: 26px;
line-height: 26px;
}
/* ================================================================== */
/* Draw toolbar
/* ================================================================== */
.leaflet-draw-toolbar .leaflet-draw-draw-polyline {
background-position: -2px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-draw-polygon {
background-position: -31px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-draw-rectangle {
background-position: -62px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-draw-circle {
background-position: -92px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-draw-marker {
background-position: -122px -2px;
}
/* ================================================================== */
/* Edit toolbar
/* ================================================================== */
.leaflet-draw-toolbar .leaflet-draw-edit-edit {
background-position: -152px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-edit-remove {
background-position: -182px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-edit-edit.leaflet-disabled {
background-position: -212px -2px;
}
.leaflet-draw-toolbar .leaflet-draw-edit-remove.leaflet-disabled {
background-position: -242px -2px;
}
/* ================================================================== */
/* Drawing styles
/* ================================================================== */
.leaflet-mouse-marker {
background-color: #fff;
cursor: crosshair;
}
.leaflet-draw-tooltip {
background: rgb(54, 54, 54);
background: rgba(0, 0, 0, 0.5);
border: 1px solid transparent;
-webkit-border-radius: 4px;
border-radius: 4px;
color: #fff;
font: 12px/18px "Helvetica Neue", Arial, Helvetica, sans-serif;
margin-left: 20px;
margin-top: -21px;
padding: 4px 8px;
position: absolute;
white-space: nowrap;
z-index: 6;
}
.leaflet-draw-tooltip:before {
border-right: 6px solid black;
border-right-color: rgba(0, 0, 0, 0.5);
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
content: "";
position: absolute;
top: 7px;
left: -7px;
}
.leaflet-error-draw-tooltip {
background-color: #F2DEDE;
border: 1px solid #E6B6BD;
color: #B94A48;
}
.leaflet-error-draw-tooltip:before {
border-right-color: #E6B6BD;
}
.leaflet-draw-tooltip-single {
margin-top: -12px
}
.leaflet-draw-tooltip-subtext {
color: #f8d5e4;
}
.leaflet-draw-guide-dash {
font-size: 1%;
opacity: 0.6;
position: absolute;
width: 5px;
height: 5px;
}
/* ================================================================== */
/* Edit styles
/* ================================================================== */
.leaflet-edit-marker-selected {
background: rgba(254, 87, 161, 0.1);
border: 4px dashed rgba(254, 87, 161, 0.6);
-webkit-border-radius: 4px;
border-radius: 4px;
}
.leaflet-edit-move {
cursor: move;
}
.leaflet-edit-resize {
cursor: pointer;
}

49
gui-src/css/maps/leaflet.draw.ie.css vendored Normal file
View File

@@ -0,0 +1,49 @@
/* Conditional stylesheet for IE. */
.leaflet-draw-toolbar {
border: 3px solid #999;
}
.leaflet-draw-toolbar a {
background-color: #eee;
}
.leaflet-draw-toolbar a:hover {
background-color: #fff;
}
.leaflet-draw-actions {
left: 32px;
margin-top: 3px;
}
.leaflet-draw-actions li {
display: inline;
zoom: 1;
}
.leaflet-edit-marker-selected {
border: 4px dashed #fe93c2;
}
.leaflet-draw-actions a {
background-color: #999;
}
.leaflet-draw-actions a:hover {
background-color: #a5a5a5;
}
.leaflet-draw-actions-top a {
margin-top: 1px;
}
.leaflet-draw-actions-bottom a {
height: 28px;
line-height: 28px;
}
.leaflet-draw-actions-top.leaflet-draw-actions-bottom a {
height: 27px;
line-height: 27px;
}

51
gui-src/css/maps/leaflet.ie.css vendored Normal file
View File

@@ -0,0 +1,51 @@
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
.leaflet-control {
display: inline;
}
.leaflet-popup-tip {
width: 21px;
_width: 27px;
margin: 0 auto;
_margin-top: -3px;
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
}
.leaflet-popup-tip-container {
margin-top: -1px;
}
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
border: 1px solid #999;
}
.leaflet-popup-content-wrapper {
zoom: 1;
}
.leaflet-control-zoom,
.leaflet-control-layers {
border: 3px solid #999;
}
.leaflet-control-layers-toggle {
}
.leaflet-control-attribution,
.leaflet-control-layers,
.leaflet-control-scale-line {
background: white;
}
.leaflet-zoom-box {
filter: alpha(opacity=50);
}
.leaflet-control-attribution {
border-top: 1px solid #bbb;
border-left: 1px solid #bbb;
}

159
gui-src/css/maps/leaflet.sidebar.css vendored Normal file
View File

@@ -0,0 +1,159 @@
.leaflet-sidebar {
position: absolute;
height: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 10px;
z-index: 20000;
}
.leaflet-sidebar.left {
left: -700px;
transition: left 0.5s, width 0.5s;
padding-right: 0;
}
.leaflet-sidebar.right {
right: -700px;
transition: right 0.5s, width 0.5s;
padding-left: 0;
}
.leaflet-sidebar.left.visible {
left: 0;
}
.leaflet-sidebar.right.visible {
right: 0;
}
.leaflet-left {
transition: left 0.5s;
}
.leaflet-right {
transition: right 0.5s;
}
.leaflet-sidebar > .leaflet-control {
height: 100%;
width: 100%;
overflow: auto;
-webkit-overflow-scrolling: touch;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 8px 24px;
font-size: 1.1em;
background: white;
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
-webkit-border-radius: 4px;
border-radius: 4px;
}
@media(max-width:767px){
.leaflet-sidebar {
width: 100%;
padding: 0;
}
.leaflet-sidebar.left {
left: -100%;
}
.leaflet-sidebar.right {
right: -100%;
}
.leaflet-sidebar.left.visible {
left: 0;
}
.leaflet-sidebar.right.visible {
right: 0;
}
.leaflet-sidebar > .leaflet-control {
box-shadow: none;
-webkit-border-radius: 0;
border-radius: 0;
}
.leaflet-sidebar.left.visible ~ .leaflet-left {
left: 100%;
}
.leaflet-sidebar.right.visible ~ .leaflet-right {
right: 100%;
}
}
@media(min-width:768px) and (max-width:991px){
.leaflet-sidebar {
width: 505px;
}
.leaflet-sidebar.left.visible ~ .leaflet-left {
left: 505px;
}
.leaflet-sidebar.right.visible ~ .leaflet-right {
right: 505px;
}
}
@media(min-width:992px) and (max-width:1199px){
.leaflet-sidebar {
width: 590px;
}
.leaflet-sidebar.left.visible ~ .leaflet-left {
left: 590px;
}
.leaflet-sidebar.right.visible ~ .leaflet-right {
right: 590px;
}
}
@media(min-width:1200px){
.leaflet-sidebar {
width: 660px;
}
.leaflet-sidebar.left.visible ~ .leaflet-left {
left: 660px;
}
.leaflet-sidebar.right.visible ~ .leaflet-right {
right: 660px;
}
}
.leaflet-sidebar .close {
position: absolute;
right: 20px;
top: 20px;
width: 31px;
height: 31px;
color: #333;
font-size: 25pt;
line-height: 1em;
text-align: center;
background: white;
-webkit-border-radius: 16px;
border-radius: 16px;
cursor: pointer;
z-index: 8;
}

757
gui-src/css/maps/mapbox.standalone.css vendored Normal file
View File

@@ -0,0 +1,757 @@
/* general typography */
.leaflet-container {
background:#fff;
font:15px/25px 'Helvetica Neue', Arial, Helvetica, sans-serif;
color:#404040;
color:rgba(0,0,0,0.75);
outline:0;
overflow:hidden;
-ms-touch-action:none;
}
.leaflet-container *,
.leaflet-container *:after,
.leaflet-container *:before {
-webkit-box-sizing:border-box;
-moz-box-sizing:border-box;
box-sizing:border-box;
}
.leaflet-container h1,
.leaflet-container h2,
.leaflet-container h3,
.leaflet-container h4,
.leaflet-container h5,
.leaflet-container h6,
.leaflet-container p {
font-size:15px;
line-height:25px;
margin:0 0 10px;
}
.mapbox-small,
.leaflet-control-attribution,
.leaflet-control-scale,
.leaflet-container input,
.leaflet-container textarea,
.leaflet-container label,
.leaflet-container small {
font-size:12px;
line-height:20px;
}
.leaflet-container a {
color:#3887BE;
font-weight:normal;
text-decoration:none;
}
.leaflet-container a:hover { color:#63b6e5; }
.leaflet-container.dark a { color:#63b6e5; }
.leaflet-container.dark a:hover { color:#8fcaec; }
.leaflet-container.dark .mapbox-button,
.leaflet-container .mapbox-button {
background-color:#3887be;
display:inline-block;
height:40px;
line-height:40px;
text-decoration:none;
color:#fff;
font-size:12px;
white-space:nowrap;
text-overflow:ellipsis;
}
.leaflet-container.dark .mapbox-button:hover,
.leaflet-container .mapbox-button:hover {
color:#fff;
background-color:#3bb2d0;
}
/* Base Leaflet
------------------------------------------------------- */
.leaflet-map-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-pane,
.leaflet-tile-container,
.leaflet-overlay-pane,
.leaflet-shadow-pane,
.leaflet-marker-pane,
.leaflet-popup-pane,
.leaflet-overlay-pane svg,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position:absolute;
left:0;
top:0;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-drag:none;
-webkit-user-select:none;
-moz-user-select:none;
user-select:none;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
.leaflet-tile {
filter:inherit;
visibility:hidden;
}
.leaflet-tile-loaded {
visibility:inherit;
}
.leaflet-zoom-box {
width:0;
height:0;
}
.leaflet-tile-pane { z-index:2; }
.leaflet-objects-pane { z-index:3; }
.leaflet-overlay-pane { z-index:4; }
.leaflet-shadow-pane { z-index:5; }
.leaflet-marker-pane { z-index:6; }
.leaflet-popup-pane { z-index:7; }
.leaflet-control {
position:relative;
z-index:7;
pointer-events:auto;
float:left;
clear:both;
}
.leaflet-right .leaflet-control { float:right; }
.leaflet-top .leaflet-control { margin-top:10px; }
.leaflet-bottom .leaflet-control { margin-bottom:10px; }
.leaflet-left .leaflet-control { margin-left:10px; }
.leaflet-right .leaflet-control { margin-right:10px; }
.leaflet-top,
.leaflet-bottom {
position:absolute;
z-index:1000;
pointer-events:none;
}
.leaflet-top { top:0; }
.leaflet-right { right:0; }
.leaflet-bottom { bottom:0; }
.leaflet-left { left:0; }
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-tile,
.leaflet-fade-anim .leaflet-popup {
opacity:0;
-webkit-transition:opacity 0.2s linear;
-moz-transition:opacity 0.2s linear;
-o-transition:opacity 0.2s linear;
transition:opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-tile-loaded,
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity:1;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition:-webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
-o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile,
.leaflet-touching .leaflet-zoom-animated {
-webkit-transition:none;
-moz-transition:none;
-o-transition:none;
transition:none;
}
.leaflet-zoom-anim .leaflet-zoom-hide { visibility: hidden; }
/* cursors */
.map-clickable,
.leaflet-clickable {
cursor: pointer;
}
.leaflet-popup-pane,
.leaflet-control {
cursor:auto;
}
.leaflet-container {
cursor:-webkit-grab;
cursor: -moz-grab;
}
.leaflet-dragging,
.leaflet-dragging .map-clickable,
.leaflet-dragging .leaflet-clickable,
.leaflet-dragging .leaflet-container {
cursor:move;
cursor:-webkit-grabbing;
cursor: -moz-grabbing;
}
.leaflet-zoom-box {
background:#fff;
border:2px dotted #202020;
opacity:0.5;
}
/* general toolbar styles */
.leaflet-control-layers,
.leaflet-bar {
background-color:#fff;
border:1px solid #999;
border-color:rgba(0,0,0,0.4);
border-radius:3px;
box-shadow:none;
}
.leaflet-bar a,
.leaflet-bar a:hover {
color:#404040;
color:rgba(0,0,0,0.75);
border-bottom:1px solid #ddd;
border-bottom-color:rgba(0,0,0,0.10);
}
.leaflet-bar a:hover,
.leaflet-bar a:active {
background-color:#f8f8f8;
cursor:pointer;
}
.leaflet-bar a:first-child {
border-radius:3px 3px 0 0;
}
.leaflet-bar a:last-child {
border-bottom:none;
border-radius:0 0 3px 3px;
}
.leaflet-bar a:only-of-type {
border-radius:3px;
}
.leaflet-bar .leaflet-disabled {
cursor:default;
opacity:0.75;
}
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
display:block;
content:'';
text-indent:-999em;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display:none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display:block;
position:relative;
}
.leaflet-control-layers-expanded {
background:#fff;
padding:6px 10px 6px 6px;
color:#404040;
color:rgba(0,0,0,0.75);
}
.leaflet-control-layers-selector {
margin-top:2px;
position:relative;
top:1px;
}
.leaflet-control-layers label {
display: block;
}
.leaflet-control-layers-separator {
height:0;
border-top:1px solid #ddd;
border-top-color:rgba(0,0,0,0.10);
margin:5px -10px 5px -6px;
}
.leaflet-container .leaflet-control-attribution {
background-color:rgba(255,255,255,0.25);
margin:0;
box-shadow:none;
}
.leaflet-control-attribution a:hover,
.map-info-container a:hover {
color:inherit;
text-decoration:underline;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding:0 5px;
}
.leaflet-left .leaflet-control-scale { margin-left:5px; }
.leaflet-bottom .leaflet-control-scale { margin-bottom:5px; }
.leaflet-control-scale-line {
background-color:rgba(255,255,255,0.5);
border:1px solid #999;
border-color:rgba(0,0,0,0.4);
border-top:none;
padding:2px 5px 1px;
white-space:nowrap;
overflow:hidden;
}
.leaflet-control-scale-line:not(:first-child) {
border-top:2px solid #ddd;
border-top-color:rgba(0,0,0,0.10);
border-bottom:none;
margin-top:-2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom:2px solid #777;
}
/* popup */
.leaflet-popup {
position:absolute;
text-align:center;
pointer-events:none;
}
.leaflet-popup-content-wrapper {
padding:1px;
text-align:left;
pointer-events:all;
}
.leaflet-popup-content {
padding:10px 10px 15px;
margin:0;
line-height:inherit;
}
.leaflet-popup-tip-container {
width:20px;
height:20px;
margin:0 auto;
position:relative;
}
.leaflet-popup-tip {
width:0;
height:0;
margin:0;
border-left:10px solid transparent;
border-right:10px solid transparent;
border-top:10px solid #fff;
box-shadow:none;
}
.leaflet-popup-close-button {
text-indent:-999em;
position:absolute;
top:0;right:0;
pointer-events:all;
}
.leaflet-popup-close-button:hover {
background-color:#f8f8f8;
}
.leaflet-popup-scrolled {
overflow:auto;
border-bottom:1px solid #ddd;
border-top:1px solid #ddd;
}
/* div icon */
.leaflet-div-icon {
background:#fff;
border:1px solid #999;
border-color:rgba(0,0,0,0.4);
}
.leaflet-editing-icon {
border-radius:3px;
}
/* Leaflet + Mapbox
------------------------------------------------------- */
.leaflet-bar a,
.mapbox-icon,
.map-tooltip.closable .close,
.leaflet-control-layers-toggle,
.leaflet-popup-close-button,
.mapbox-button-icon:before {
content:'';
display:inline-block;
width:26px;
height:26px;
vertical-align:middle;
background-repeat:no-repeat;
}
.leaflet-bar a {
display:block;
}
.leaflet-control-zoom-in,
.leaflet-control-zoom-out,
.leaflet-popup-close-button,
.leaflet-control-layers-toggle,
.leaflet-container.dark .map-tooltip .close,
.map-tooltip .close,
.mapbox-icon {
background-image:url(./images/icons-404040.png);
background-repeat:no-repeat;
background-size:26px 260px;
}
.mapbox-button-icon:before,
.leaflet-container.dark .leaflet-control-zoom-in,
.leaflet-container.dark .leaflet-control-zoom-out,
.leaflet-container.dark .leaflet-control-layers-toggle,
.leaflet-container.dark .mapbox-icon {
background-image:url(./images/icons-ffffff.png);
background-size:26px 260px;
}
.leaflet-bar .leaflet-control-zoom-in { background-position:0 0; }
.leaflet-bar .leaflet-control-zoom-out { background-position:0 -26px; }
.map-tooltip .close, .leaflet-popup-close-button { background-position:0 -52px; }
.mapbox-icon-info { background-position:0 -78px; }
.leaflet-control-layers-toggle { background-position:0 -104px; }
.mapbox-icon-share:before, .mapbox-icon-share { background-position:0 -130px; }
.mapbox-icon-geocoder:before, .mapbox-icon-geocoder { background-position:0 -156px; }
.mapbox-icon-facebook:before, .mapbox-icon-facebook { background-position:0 -182px; }
.mapbox-icon-twitter:before, .mapbox-icon-twitter { background-position:0 -208px; }
.mapbox-icon-pinterest:before, .mapbox-icon-pinterest { background-position:0 -234px; }
@media
(-webkit-min-device-pixel-ratio:2),
(min-resolution:192dpi) {
.leaflet-control-zoom-in,
.leaflet-control-zoom-out,
.leaflet-popup-close-button,
.leaflet-control-layers-toggle,
.mapbox-icon {
background-image:url(./images/icons-404040@2x.png);
}
.mapbox-button-icon:before,
.leaflet-container.dark .leaflet-control-zoom-in,
.leaflet-container.dark .leaflet-control-zoom-out,
.leaflet-container.dark .leaflet-control-layers-toggle,
.leaflet-container.dark .mapbox-icon {
background-image:url(./images/icons-ffffff@2x.png);
}
}
.leaflet-popup-content-wrapper,
.map-legends,
.map-tooltip {
background:#fff;
border-radius:3px;
box-shadow:0 1px 2px rgba(0,0,0,0.10);
}
.map-legends,
.map-tooltip {
max-width:300px;
}
.map-legends .map-legend {
padding:10px;
}
.map-tooltip {
z-index:999999;
padding:10px;
min-width:180px;
max-height:400px;
overflow:auto;
opacity:1;
-webkit-transition:opacity 150ms;
-moz-transition:opacity 150ms;
-o-transition:opacity 150ms;
transition:opacity 150ms;
}
.map-tooltip .close {
text-indent:-999em;
overflow:hidden;
display:none;
}
.map-tooltip.closable .close {
position:absolute;
top:0;right:0;
border-radius:3px;
}
.map-tooltip.closable .close:active {
background-color:#f8f8f8;
}
.leaflet-control-interaction {
position:absolute;
top:10px;
right:10px;
width:300px;
}
.leaflet-popup-content .marker-title {
font-weight:bold;
}
.leaflet-control .mapbox-button {
background-color:#fff;
border:1px solid #ddd;
border-color:rgba(0,0,0,0.10);
padding:5px 10px;
border-radius:3px;
}
/* Share modal
------------------------------------------------------- */
.mapbox-modal > div {
position:absolute;
top:0;
left:0;
width:100%;
height:100%;
z-index:-1;
overflow-y:auto;
}
.mapbox-modal.active > div {
z-index:99999;
transition:all .2s, z-index 0 0;
}
.mapbox-modal .mapbox-modal-mask {
background:rgba(0,0,0,0.5);
opacity:0;
}
.mapbox-modal.active .mapbox-modal-mask { opacity:1; }
.mapbox-modal .mapbox-modal-content {
-webkit-transform:translateY(-100%);
-moz-transform:translateY(-100%);
-ms-transform:translateY(-100%);
transform:translateY(-100%);
}
.mapbox-modal.active .mapbox-modal-content {
-webkit-transform:translateY(0);
-moz-transform:translateY(0);
-ms-transform:translateY(0);
transform:translateY(0);
}
.mapbox-modal-body {
position:relative;
background:#fff;
padding:20px;
z-index:1000;
width:50%;
margin:20px 0 20px 25%;
}
.mapbox-share-buttons {
margin:0 0 20px;
}
.mapbox-share-buttons a {
width:33.3333%;
border-left:1px solid #fff;
text-align:center;
border-radius:0;
}
.mapbox-share-buttons a:last-child { border-radius:0 3px 3px 0; }
.mapbox-share-buttons a:first-child { border:none; border-radius:3px 0 0 3px; }
.mapbox-modal input {
width:100%;
height:40px;
padding:10px;
border:1px solid #ddd;
border-color:rgba(0,0,0,0.10);
color:rgba(0,0,0,0.5);
}
/* Info Control
------------------------------------------------------- */
.leaflet-control.mapbox-control-info {
margin:5px 30px 10px 10px;
min-height:26px;
}
.leaflet-control.mapbox-control-info-right {
margin:5px 10px 10px 30px;
}
.mapbox-info-toggle {
background-color:#fff;
background-color:rgba(255,255,255,0.25);
border-radius:50%;
position:absolute;
bottom:0;left:0;
z-index:1;
}
.mapbox-control-info-right .mapbox-info-toggle { left:auto; right:0; }
.mapbox-control-info.active .mapbox-info-toggle { background-color:#fff; }
.mapbox-info-toggle:hover { background-color:rgba(255,255,255,0.5); }
.map-info-container {
background:#fff;
background:rgba(255,255,255,0.75);
padding:3px 5px 3px 15px;
display:none;
position:relative;
bottom:0;
left:13px;
border-radius:3px;
}
.mapbox-control-info.active .map-info-container { display:inline-block; }
.mapbox-control-info-right .map-info-container {
left:auto;
right:13px;
padding:3px 15px 3px 5px;
}
/* Geocoder
------------------------------------------------------- */
.leaflet-control-mapbox-geocoder {
position:relative;
}
.leaflet-control-mapbox-geocoder.searching {
opacity:0.75;
}
.leaflet-control-mapbox-geocoder .leaflet-control-mapbox-geocoder-wrap {
background:#fff;
position:absolute;
border:1px solid #999;
border-color:rgba(0,0,0,0.4);
border-bottom-width:0;
overflow:hidden;
left:26px;
height:27px;
width:0;
top:-1px;
border-radius:0 3px 3px 0;
opacity:0;
-webkit-transition:opacity 100ms;
-moz-transition:opacity 100ms;
-o-transition:opacity 100ms;
transition:opacity 100ms;
}
.leaflet-control-mapbox-geocoder.active .leaflet-control-mapbox-geocoder-wrap {
width:180px;
opacity:1;
}
.leaflet-bar .leaflet-control-mapbox-geocoder-toggle,
.leaflet-bar .leaflet-control-mapbox-geocoder-toggle:hover {
border-bottom:none;
}
.leaflet-control-mapbox-geocoder-toggle {
border-radius:3px;
}
.leaflet-control-mapbox-geocoder.active,
.leaflet-control-mapbox-geocoder.active .leaflet-control-mapbox-geocoder-toggle {
border-top-right-radius:0;
border-bottom-right-radius:0;
}
.leaflet-control-mapbox-geocoder .leaflet-control-mapbox-geocoder-form input {
background:transparent;
border:0;
width:180px;
padding:0 0 0 10px;
height:26px;
outline:none;
}
.leaflet-control-mapbox-geocoder-results {
width:180px;
position:absolute;
left:26px;
top:25px;
border-radius:0 0 3px 3px;
}
.leaflet-control-mapbox-geocoder.active .leaflet-control-mapbox-geocoder-results {
background:#fff;
border:1px solid #999;
border-color:rgba(0,0,0,0.4);
}
.leaflet-control-mapbox-geocoder-results a,
.leaflet-control-mapbox-geocoder-results span {
padding:0 10px;
text-overflow:ellipsis;
white-space:nowrap;
display:block;
width:100%;
font-size:12px;
line-height:26px;
text-align:left;
overflow:hidden;
}
.leaflet-control-mapbox-geocoder-results a:first-child {
border-top:1px solid #999;
border-top-color:rgba(0,0,0,0.4);
border-radius:0;
}
.leaflet-container.dark .leaflet-control .leaflet-control-mapbox-geocoder-results a:hover,
.leaflet-control-mapbox-geocoder-results a:hover {
background:#f8f8f8;
opacity:1;
}
/* Dark Theme
------------------------------------------------------- */
.leaflet-container.dark .leaflet-bar {
background-color:#404040;
border-color:#202020;
border-color:rgba(0,0,0,0.75);
}
.leaflet-container.dark .leaflet-bar a {
color:#404040;
border-color:rgba(0,0,0,0.5);
}
.leaflet-container.dark .leaflet-bar a:active,
.leaflet-container.dark .leaflet-bar a:hover {
background-color:#505050;
}
.leaflet-container.dark .mapbox-info-toggle,
.leaflet-container.dark .map-info-container,
.leaflet-container.dark .leaflet-control-attribution {
background-color:rgba(0,0,0,0.25);
color:#f8f8f8;
}
.leaflet-container.dark .leaflet-bar a.leaflet-disabled,
.leaflet-container.dark .leaflet-control .mapbox-button.disabled {
background-color:#252525;
color:#404040;
}
.leaflet-container.dark .leaflet-control-mapbox-geocoder > div {
border-color:#202020;
border-color:rgba(0,0,0,0.75);
}
.leaflet-container.dark .leaflet-control .leaflet-control-mapbox-geocoder-results a {
border-color:#ddd #202020;
border-color:rgba(0,0,0,0.10) rgba(0,0,0,0.75);
}
.leaflet-container.dark .leaflet-control .leaflet-control-mapbox-geocoder-results span {
border-color:#202020;
border-color:rgba(0,0,0,0.75);
}
/* Larger Screens
------------------------------------------------------- */
@media only screen and (max-width:800px) {
.mapbox-modal-body {
width:83.3333%;
margin-left:8.3333%;
}
}
/* Smaller Screens
------------------------------------------------------- */
@media only screen and (max-width:640px) {
.mapbox-modal-body {
width:100%;
height:100%;
margin:0;
}
}
/* Browser Fixes
------------------------------------------------------- */
/* Map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container img { max-width:none!important; }
/* Stupid Android 2 doesn't understand "max-width: none" properly */
.leaflet-container img.leaflet-image-layer { max-width:15000px!important; }
/* Android chrome makes tiles disappear without this */
.leaflet-tile-container img { -webkit-backface-visibility:hidden; }
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg { -moz-user-select:none; }
/* Older IEs don't support the translateY property for display animation */
.leaflet-oldie .mapbox-modal .mapbox-modal-content { display:none; }
.leaflet-oldie .mapbox-modal.active .mapbox-modal-content { display:block; }
.map-tooltip { width:280px\8; /* < IE9 */ }

1
gui-src/css/maps/mapbox.v3.2.0.css vendored Normal file
View File

File diff suppressed because one or more lines are too long

358
gui-src/css/styles.css vendored Normal file
View File

@@ -0,0 +1,358 @@
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
p {
color: #ececec;
}
.container {
margin: 0;
padding-top: 1vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.arnis:hover {
filter: drop-shadow(0 0 2em #b3b3b3);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
.flex-container {
display: flex;
gap: 20px;
justify-content: center;
align-items: stretch;
margin-top: 5px;
}
.section {
background: #575757;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.map-box,
.controls-box {
width: 45%;
background: #575757;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.controls-content {
display: flex;
flex-direction: column;
height: 100%;
}
.controls-box .progress-section {
margin-top: auto;
}
.map-container {
border: 2px solid #e0e0e0;
border-radius: 8px;
}
.section h2 {
margin-top: 0;
margin-bottom: 10px;
}
p {
color: #d6d6d6;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
color: #0f0f0f;
background-color: #ffffff;
cursor: pointer;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
margin-top: 10px;
width: auto;
}
button:hover {
border-color: #656565;
}
#selected-directory {
font-size: 1em;
margin-top: 6px;
}
.progress-section {
margin-top: auto;
}
.progress-section h2 {
margin-bottom: 8px;
text-align: center;
}
.progress-bar-container {
width: 100%;
height: 20px;
background-color: #e0e0e0;
border-radius: 10px;
overflow: hidden;
margin-top: 8px;
}
.progress-bar {
height: 100%;
width: 0%;
background-color: #4caf50;
transition: width 0.4s;
}
/* Left and right alignment for "Saving world..." text */
.progress-status {
display: flex;
justify-content: space-between;
font-size: 0.9em;
margin-top: 8px;
color: #fff;
}
.footer {
margin-top: 20px;
text-align: center;
font-size: 0.9em;
}
.footer-link {
color: #ffffff;
text-decoration: none;
}
.footer-link:hover {
color: #b3b3b3;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
p {
color: #ececec;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}
.tooltip {
position: relative;
display: block;
width: 100%;
}
.tooltip button {
width: 100%;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 90%;
background-color: #808080;
color: #fff;
text-align: center;
padding: 5px 0;
border-radius: 6px;
position: absolute;
z-index: 1;
bottom: -100%;
left: 5%;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip .tooltiptext::after {
content: "";
position: absolute;
top: -10px;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #808080 transparent;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.controls-box button {
width: 100%;
}
/* Customization Settings */
.modal {
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: #797979;
padding: 20px;
border: 1px solid #797979;
border-radius: 10px;
width: 400px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2);
}
.close-button {
color: #e9e9e9;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
.close-button:hover {
color: #ffffff;
}
#winter-toggle {
accent-color: #fecc44;
}
.winter-toggle-container, .scale-slider-container {
margin: 15px 0;
}
.scale-slider-container label {
display: block;
margin-bottom: 5px;
}
#scale-value-slider {
accent-color: #fecc44;
}
#slider-value {
margin-left: 10px;
font-weight: bold;
}
.bbox-input-container {
margin-bottom: 20px;
}
.bbox-input-container label {
display: block;
margin-bottom: 5px;
}
#bbox-coords {
width: 100%;
padding: 8px;
border: 1px solid #fecc44;
border-radius: 4px;
font-size: 14px;
}
#bbox-coords:focus {
outline: none;
border-color: #fecc44;
box-shadow: 0 0 5px #fecc44;
}
.button-container {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 5px;
}
.start-button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
.start-button:hover {
background-color: #4caf50;
}
.settings-button {
width: 40px !important;
height: 38px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: background-color 0.3s, border-color 0.3s;
}
.settings-button .gear-icon::before {
content: "⚙️";
font-size: 18px;
}

BIN
gui-src/images/crosshair.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B

BIN
gui-src/images/logo.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
gui-src/images/tile.png vendored Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

133
gui-src/index.html vendored Normal file
View File

@@ -0,0 +1,133 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="./css/styles.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Arnis</title>
<script type="module" src="./js/main.js" defer></script>
</head>
<body>
<main class="container">
<div class="row">
<a href="https://github.com/louis-e/arnis" target="_blank">
<img src="./images/logo.png" class="logo arnis" alt="Arnis Logo" />
</a>
</div>
<div class="flex-container">
<!-- Left Box: Map and BBox Input -->
<section class="section map-box" style="margin-bottom: 0; padding-bottom: 0;">
<h2>Select Location</h2>
<span id="bbox-text" style="font-size: 1.0em; display: block; margin-top: -8px; margin-bottom: 3px;">
Zoom in and choose your area using the rectangle tool
</span>
<iframe src="maps.html" width="100%" height="300" class="map-container" title="Map Picker"></iframe>
<span id="bbox-info"
style="font-size: 0.75em; color: #7bd864; display: block; margin-bottom: 4px; font-weight: bold; min-height: 2em;"></span>
</section>
<!-- Right Box: Directory Selection, Start Button, and Progress Bar -->
<section class="section controls-box">
<div class="controls-content">
<h2>Select World</h2>
<!-- Updated Tooltip Structure -->
<div class="tooltip" style="width: 100%;">
<button type="button" onclick="openWorldPicker()" style="padding: 10px; line-height: 1.2; width: 100%;">
Choose World
<br>
<span id="selected-world" style="font-size: 0.8em; color: #fecc44; display: block; margin-top: 4px;">
No world selected
</span>
</button>
<span class="tooltiptext" style="font-size: 0.8em; line-height: 1.2;">
Please select a Minecraft world that can be overwritten, as the generation process will replace existing structures in the chosen world!
</span>
</div>
<div class="button-container">
<button type="button" id="start-button" class="start-button" onclick="startGeneration()">Start Generation</button>
<button type="button" class="settings-button" onclick="openSettings()">
<i class="gear-icon"></i>
</button>
</div>
<br><br>
<div class="progress-section">
<h2>Progress</h2>
<div class="progress-bar-container">
<div class="progress-bar" id="progress-bar"></div>
</div>
<div class="progress-status">
<span id="progress-message"></span>
<span id="progress-detail">0%</span>
</div>
</div>
</div>
</section>
</div>
<!-- World Picker Modal -->
<div id="world-modal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close-button" onclick="closeWorldPicker()">&times;</span>
<h2>Choose World</h2>
<button type="button" id="select-world-button" class="select-world-button" onclick="selectWorld(false)">Select existing world</button>
<button type="button" id="generate-world-button" class="generate-world-button" onclick="selectWorld(true)">Generate new world</button>
</div>
</div>
<!-- Settings Modal -->
<div id="settings-modal" class="modal" style="display: none;">
<div class="modal-content">
<span class="close-button" onclick="closeSettings()">&times;</span>
<h2>Customization Settings</h2>
<!-- Winter Mode Toggle Button -->
<div class="winter-toggle-container">
<label for="winter-toggle">Winter Mode:</label>
<input type="checkbox" id="winter-toggle" name="winter-toggle">
</div>
<!-- World Scale Slider -->
<div class="scale-slider-container">
<label for="scale-value-slider">World Scale:</label>
<input type="range" id="scale-value-slider" name="scale-value-slider" min="0.50" max="2.5" step="0.25" value="1">
<span id="slider-value">1.00</span>
</div>
<!-- Bounding Box Input -->
<div class="bbox-input-container">
<label for="bbox-coords">Custom Bounding Box:</label>
<input type="text" id="bbox-coords" name="bbox-coords" maxlength="55" style="width: 280px;" autocomplete="one-time-code" placeholder="Format: lat,lng,lat,lng">
</div>
<!-- Floodfill Timeout Input -->
<div class="timeout-input-container">
<label for="floodfill-timeout">Floodfill Timeout (sec):</label>
<input type="number" id="floodfill-timeout" name="floodfill-timeout" min="0" step="1" value="20" style="width: 100px;" placeholder="Seconds">
</div><br>
<!-- Ground Level Input -->
<div class="ground-level-input-container">
<label for="ground-level">Ground Level:</label>
<input type="number" id="ground-level" name="ground-level" min="-64" max="290" value="-62" style="width: 100px;" placeholder="Ground Level">
</div>
</div>
</div>
<!-- Footer -->
<footer class="footer">
<a href="https://github.com/louis-e/arnis" target="_blank" class="footer-link">
© <span id="current-year"></span> Arnis v<span id="version-placeholder">x.x.x</span> by louis-e
</a>
</footer>
</main>
</body>
</html>

657
gui-src/js/bbox.js vendored Normal file
View File

@@ -0,0 +1,657 @@
var map, rsidebar, lsidebar, drawControl, drawnItems = null;
// Where we keep the big list of proj defs from the server
var proj4defs = null;
// Where we keep the proj objects we are using in this session
var projdefs = { "4326": L.CRS.EPSG4326, "3857": L.CRS.EPSG3857 };
var currentproj = "3857";
var currentmouse = L.latLng(0, 0);
/*
**
** override L.Rectangle
** to fire an event after setting
**
** the base parent object L.Path
** includes the L.Mixin.Events
**
** ensures bbox box is always
** the topmost SVG feature
**
*/
L.Rectangle.prototype.setBounds = function (latLngBounds) {
this.setLatLngs(this._boundsToLatLngs(latLngBounds));
this.fire('bounds-set');
}
var FormatSniffer = (function () { // execute immediately
'use strict';
/*
**
** constructor
**
*/
var FormatSniffer = function (options) {
options || (options = {});
if (!this || !(this instanceof FormatSniffer)) {
return new FormatSniffer(options);
}
this.regExes = {
ogrinfoExtent: /Extent\:\s\((.*)\)/,
bbox: /^\(([\s|\-|0-9]*\.[0-9]*,[\s|\-|0-9]*\.[0-9]*,[\s|\-|0-9]*\.[0-9]*,[\s|\-|0-9]*\.[0-9|\s]*)\)$/
};
this.data = options.data || "";
this.parse_type = null;
};
/*
**
** functions
**
*/
FormatSniffer.prototype.sniff = function () {
return this._sniffFormat();
};
FormatSniffer.prototype._is_ogrinfo = function () {
var match = this.regExes.ogrinfoExtent.exec(this.data.trim());
var extent = [];
if (match) {
var pairs = match[1].split(") - (");
for (var indx = 0; indx < pairs.length; indx++) {
var coords = pairs[indx].trim().split(",");
extent = (extent.concat([parseFloat(coords[0].trim()), parseFloat(coords[1].trim())]));
}
}
this.parse_type = "ogrinfo";
return extent;
};
FormatSniffer.prototype._is_normal_bbox = function () {
var match = this.regExes.bbox.exec(this.data.trim());
var extent = [];
if (match) {
var bbox = match[1].split(",");
for (var indx = 0; indx < bbox.length; indx++) {
var coord = bbox[indx].trim();
extent = (extent.concat([parseFloat(coord)]));
}
}
this.parse_type = "bbox";
return extent;
};
FormatSniffer.prototype._is_geojson = function () {
try {
// try JSON
var json = JSON.parse(this.data);
// try GeoJSON
var parsed_data = new L.geoJson(json)
} catch (err) {
return null;
}
this.parse_type = "geojson";
return parsed_data;
};
FormatSniffer.prototype._is_wkt = function () {
if (this.data === "") {
throw new Error("empty -- nothing to parse");
}
try {
var parsed_data = new Wkt.Wkt(this.data);
} catch (err) {
return null;
}
this.parse_type = "wkt";
return parsed_data;
};
FormatSniffer.prototype._sniffFormat = function () {
var parsed_data = null;
var fail = false;
try {
var next = true;
// try ogrinfo
parsed_data = this._is_ogrinfo()
if (parsed_data.length > 0) {
next = false;
}
// try normal bbox
if (next) {
parsed_data = this._is_normal_bbox();
if (parsed_data.length > 0) next = false;
}
// try GeoJSON
if (next) {
parsed_data = this._is_geojson();
if (parsed_data) next = false;
}
// try WKT
if (next) {
parsed_data = this._is_wkt();
if (parsed_data) next = false;
}
// no matches, throw error
if (next) {
fail = true;
/*
** sorry, this block needs to be left aligned
** to make the alert more readable
** which means, we probably shouldn't use alerts ;-)
*/
throw {
"name": "NoTypeMatchError",
"message": "The data is not a recognized format:\n \
1. ogrinfo extent output\n \
2. bbox as (xMin,yMin,xMax,yMax )\n \
3. GeoJSON\n \
4. WKT\n\n "
}
}
} catch (err) {
alert("Your paste is not parsable:\n" + err.message);
fail = true;
}
// delegate to format handler
if (!fail) {
this._formatHandler[this.parse_type].call(this._formatHandler, parsed_data);
}
return (fail ? false : true);
};
/*
** an object with functions as property names.
** if we need to add another format
** we can do so here as a property name
** to enforce reusability
**
** to add different formats as L.FeatureGroup layer
** so they work with L.Draw edit and delete options
** we fake passing event information
** and triggering draw:created for L.Draw
*/
FormatSniffer.prototype._formatHandler = {
// coerce event objects to work with L.Draw types
coerce: function (lyr, type_obj) {
var event_obj = {
layer: lyr,
layerType: null,
}
// coerce to L.Draw types
if (/point/i.test(type_obj)) {
event_obj.layerType = "marker";
}
else if (/linestring/i.test(type_obj)) {
event_obj.layerType = "polyline";
}
else if (/polygon/i.test(type_obj)) {
event_obj.layerType = "polygon";
}
return event_obj;
},
reduce_layers: function (lyr) {
var lyr_parts = [];
if (typeof lyr['getLayers'] === 'undefined') {
return [lyr];
}
else {
var all_layers = lyr.getLayers();
for (var i = 0; i < all_layers.length; i++) {
lyr_parts = lyr_parts.concat(this.reduce_layers(all_layers[i]));
}
}
return lyr_parts;
},
get_leaflet_bounds: function (data) {
/*
** data comes in an extent ( xMin,yMin,xMax,yMax )
** we need to swap lat/lng positions
** because leaflet likes it hard
*/
var sw = [data[1], data[0]];
var ne = [data[3], data[2]];
return new L.LatLngBounds(sw, ne);
},
wkt: function (data) {
var wkt_layer = data.construct[data.type].call(data);
var all_layers = this.reduce_layers(wkt_layer);
for (var indx = 0; indx < all_layers.length; indx++) {
var lyr = all_layers[indx];
var evt = this.coerce(lyr, data.type);
// call L.Draw.Feature.prototype._fireCreatedEvent
map.fire('draw:created', evt);
}
},
geojson: function (geojson_layer) {
var all_layers = this.reduce_layers(geojson_layer);
for (var indx = 0; indx < all_layers.length; indx++) {
var lyr = all_layers[indx];
var geom_type = geojson_layer.getLayers()[0].feature.geometry.type;
var evt = this.coerce(lyr, geom_type);
// call L.Draw.Feature.prototype._fireCreatedEvent
map.fire('draw:created', evt);
}
},
ogrinfo: function (data) {
var lBounds = this.get_leaflet_bounds(data);
// create a rectangle layer
var lyr = new L.Rectangle(lBounds);
var evt = this.coerce(lyr, 'polygon');
// call L.Draw.Feature.prototype._fireCreatedEvent
map.fire('draw:created', evt);
},
bbox: function (data) {
var lBounds = this.get_leaflet_bounds(data);
// create a rectangle layer
var lyr = new L.Rectangle(lBounds);
var evt = this.coerce(lyr, 'polygon');
// call L.Draw.Feature.prototype._fireCreatedEvent
map.fire('draw:created', evt);
}
};
return FormatSniffer; // return class def
})(); // end FormatSniffer
function addLayer(layer, name, title, zIndex, on) {
if (on) {
layer.setZIndex(zIndex).addTo(map);
} else {
layer.setZIndex(zIndex);
}
// Create a simple layer switcher that toggles layers on and off.
var ui = document.getElementById('map-ui');
var item = document.createElement('li');
var link = document.createElement('a');
link.href = '#';
if (on) {
link.className = 'enabled';
} else {
link.className = '';
}
link.innerHTML = name;
link.title = title;
link.onclick = function(e) {
e.preventDefault();
e.stopPropagation();
if (map.hasLayer(layer)) {
map.removeLayer(layer);
this.className = '';
} else {
map.addLayer(layer);
this.className = 'enabled';
}
};
item.appendChild(link);
ui.appendChild(item);
};
function formatBounds(bounds, proj) {
var gdal = $("input[name='gdal-checkbox']").prop('checked');
var lngLat = $("input[name='coord-order']").prop('checked');
var formattedBounds = '';
var southwest = bounds.getSouthWest();
var northeast = bounds.getNorthEast();
var xmin = 0;
var ymin = 0;
var xmax = 0;
var ymax = 0;
if (proj == '4326') {
xmin = southwest.lng.toFixed(6);
ymin = southwest.lat.toFixed(6);
xmax = northeast.lng.toFixed(6);
ymax = northeast.lat.toFixed(6);
} else {
var proj_to_use = null;
if (typeof (projdefs[proj]) !== 'undefined') {
// we have it already, then grab it and use it...
proj_to_use = projdefs[proj];
} else {
// We have not used this one yet... make it and store it...
projdefs[proj] = new L.Proj.CRS(proj, proj4defs[proj][1]);
proj_to_use = projdefs[proj];
}
southwest = proj_to_use.project(southwest)
northeast = proj_to_use.project(northeast)
xmin = southwest.x.toFixed(4);
ymin = southwest.y.toFixed(4);
xmax = northeast.x.toFixed(4);
ymax = northeast.y.toFixed(4);
}
if (gdal) {
if (lngLat) {
formattedBounds = xmin + ',' + ymin + ',' + xmax + ',' + ymax;
} else {
formattedBounds = ymin + ',' + xmin + ',' + ymax + ',' + xmax;
}
} else {
if (lngLat) {
formattedBounds = xmin + ' ' + ymin + ' ' + xmax + ' ' + ymax;
} else {
formattedBounds = ymin + ' ' + xmin + ' ' + ymax + ' ' + xmax;
}
}
return formattedBounds
}
function formatTile(point, zoom) {
var xTile = Math.floor((point.lng + 180) / 360 * Math.pow(2, zoom));
var yTile = Math.floor((1 - Math.log(Math.tan(point.lat * Math.PI / 180) + 1 / Math.cos(point.lat * Math.PI / 180)) / Math.PI) / 2 * Math.pow(2, zoom));
return xTile.toString() + ',' + yTile.toString();
}
function formatPoint(point, proj) {
var gdal = $("input[name='gdal-checkbox']").prop('checked');
var lngLat = $("input[name='coord-order']").prop('checked');
var formattedPoint = '';
if (proj == '4326') {
x = point.lng.toFixed(6);
y = point.lat.toFixed(6);
} else {
var proj_to_use = null;
if (typeof (projdefs[proj]) !== 'undefined') {
// we have it already, then grab it and use it...
proj_to_use = projdefs[proj];
} else {
// We have not used this one yet... make it and store it...
projdefs[proj] = new L.Proj.CRS(proj, proj4defs[proj][1]);
proj_to_use = projdefs[proj];
}
point = proj_to_use.project(point)
x = point.x.toFixed(4);
y = point.y.toFixed(4);
}
if (gdal) {
if (lngLat) {
formattedBounds = x + ',' + y;
} else {
formattedBounds = y + ',' + x;
}
} else {
if (lngLat) {
formattedBounds = x + ' ' + y;
} else {
formattedBounds = y + ' ' + x;
}
}
return formattedBounds
}
function validateStringAsBounds(bounds) {
var splitBounds = bounds ? bounds.split(',') : null;
return ((splitBounds !== null) &&
(splitBounds.length == 4) &&
((-90.0 <= parseFloat(splitBounds[0]) <= 90.0) &&
(-180.0 <= parseFloat(splitBounds[1]) <= 180.0) &&
(-90.0 <= parseFloat(splitBounds[2]) <= 90.0) &&
(-180.0 <= parseFloat(splitBounds[3]) <= 180.0)) &&
(parseFloat(splitBounds[0]) < parseFloat(splitBounds[2]) &&
parseFloat(splitBounds[1]) < parseFloat(splitBounds[3])))
}
$(document).ready(function () {
/*
**
** make sure all textarea inputs
** are selected once they are clicked
** because some people might not
** have flash enabled or installed
** and yes...
** there's a fucking Flash movie floating
** on top of your DOM
**
*/
$('input[type="textarea"]').on('click', function (evt) { this.select() });
// Have to init the projection input box as it is used to format the initial values
$("#projection").val(currentproj);
L.mapbox.accessToken = 'pk.eyJ1IjoiY3Vnb3MiLCJhIjoiY2p4Nm43MzA3MDFmZDQwcGxsMjB4Z3hnNiJ9.SQbnMASwdqZe6G4n6OMvVw';
map = L.mapbox.map('map').setView([50.114768, 8.687322], 4).addLayer(L.mapbox.styleLayer('mapbox://styles/mapbox/streets-v11'));
rsidebar = L.control.sidebar('rsidebar', {
position: 'right',
closeButton: true
});
rsidebar.on("sidebar-show", function (e) {
$("#map .leaflet-tile-loaded").addClass("blurred");
});
rsidebar.on("sidebar-hide", function (e) {
$('#map .leaflet-tile-loaded').removeClass('blurred');
$('#map .leaflet-tile-loaded').addClass('unblurred');
setTimeout(function () {
$('#map .leaflet-tile-loaded').removeClass('unblurred');
}, 7000);
});
map.addControl(rsidebar);
lsidebar = L.control.sidebar('lsidebar', {
position: 'left'
});
map.addControl(lsidebar);
// Add in a crosshair for the map
var crosshairIcon = L.icon({
iconUrl: 'images/crosshair.png',
iconSize: [20, 20], // size of the icon
iconAnchor: [10, 10], // point of the icon which will correspond to marker's location
});
crosshair = new L.marker(map.getCenter(), { icon: crosshairIcon, clickable: false });
crosshair.addTo(map);
// Initialize the FeatureGroup to store editable layers
drawnItems = new L.FeatureGroup();
map.addLayer(drawnItems);
// Initialize the draw control and pass it the FeatureGroup of editable layers
drawControl = new L.Control.Draw({
edit: {
featureGroup: drawnItems
},
draw: {
polyline: false,
polygon: false,
circle: false,
marker: false
}
});
map.addControl(drawControl);
/*
**
** create bounds layer
** and default it at first
** to draw on null island
** so it's not seen onload
**
*/
startBounds = new L.LatLngBounds([0.0, 0.0], [0.0, 0.0]);
var bounds = new L.Rectangle(startBounds,
{
fill: false,
opacity: 1.0,
color: '#000'
}
);
bounds.on('bounds-set', function (e) {
// move it to the end of the parent
var parent = e.target._renderer._container.parentElement;
$(parent).append(e.target._renderer._container);
// Set the hash
var southwest = this.getBounds().getSouthWest();
var northeast = this.getBounds().getNorthEast();
var xmin = southwest.lng.toFixed(6);
var ymin = southwest.lat.toFixed(6);
var xmax = northeast.lng.toFixed(6);
var ymax = northeast.lat.toFixed(6);
location.hash = ymin + ',' + xmin + ',' + ymax + ',' + xmax;
});
map.addLayer(bounds)
map.on('draw:created', function (e) {
drawnItems.addLayer(e.layer);
bounds.setBounds(drawnItems.getBounds())
$('#boxbounds').text(formatBounds(bounds.getBounds(), '4326'));
$('#boxboundsmerc').text(formatBounds(bounds.getBounds(), currentproj));
notifyBboxUpdate();
if (!e.geojson &&
!((drawnItems.getLayers().length == 1) && (drawnItems.getLayers()[0] instanceof L.Marker))) {
map.fitBounds(bounds.getBounds());
} else {
if ((drawnItems.getLayers().length == 1) && (drawnItems.getLayers()[0] instanceof L.Marker)) {
map.panTo(drawnItems.getLayers()[0].getLatLng());
}
}
});
map.on('draw:deleted', function (e) {
e.layers.eachLayer(function (l) {
drawnItems.removeLayer(l);
});
if (drawnItems.getLayers().length > 0 &&
!((drawnItems.getLayers().length == 1) && (drawnItems.getLayers()[0] instanceof L.Marker))) {
bounds.setBounds(drawnItems.getBounds())
$('#boxbounds').text(formatBounds(bounds.getBounds(), '4326'));
$('#boxboundsmerc').text(formatBounds(bounds.getBounds(), currentproj));
notifyBboxUpdate();
map.fitBounds(bounds.getBounds());
} else {
bounds.setBounds(new L.LatLngBounds([0.0, 0.0], [0.0, 0.0]));
$('#boxbounds').text(formatBounds(bounds.getBounds(), '4326'));
$('#boxboundsmerc').text(formatBounds(bounds.getBounds(), currentproj));
notifyBboxUpdate();
if (drawnItems.getLayers().length == 1) {
map.panTo(drawnItems.getLayers()[0].getLatLng());
}
}
});
map.on('draw:edited', function (e) {
bounds.setBounds(drawnItems.getBounds())
$('#boxbounds').text(formatBounds(bounds.getBounds(), '4326'));
$('#boxboundsmerc').text(formatBounds(bounds.getBounds(), currentproj));
notifyBboxUpdate();
map.fitBounds(bounds.getBounds());
});
function display() {
$('#boxbounds').text(formatBounds(bounds.getBounds(), '4326'));
$('#boxboundsmerc').text(formatBounds(bounds.getBounds(), currentproj));
notifyBboxUpdate();
}
display();
map.on('move', function (e) {
crosshair.setLatLng(map.getCenter());
});
// handle geolocation click events
$('#geolocation').click(function () {
map.locate({ setView: true, maxZoom: 8 });
$('#geolocation a').toggleClass('active');
$('#geolocation a').toggleClass('active', 350);
});
$('button#add').on('click', function (evt) {
var sniffer = FormatSniffer({ data: $('div#rsidebar textarea').val() });
var is_valid = sniffer.sniff();
if (is_valid) {
rsidebar.hide();
$('#create-geojson a').toggleClass('enabled');
map.fitBounds(bounds.getBounds());
}
});
$('button#clear').on('click', function (evt) {
$('div#rsidebar textarea').val('');
});
var initialBBox = location.hash ? location.hash.replace(/^#/, '') : null;
if (initialBBox) {
if (validateStringAsBounds(initialBBox)) {
var splitBounds = initialBBox.split(',');
startBounds = new L.LatLngBounds([splitBounds[0], splitBounds[1]],
[splitBounds[2], splitBounds[3]]);
var lyr = new L.Rectangle(startBounds);
var evt = {
layer: lyr,
layerType: "polygon",
}
map.fire('draw:created', evt);
//map.fitBounds(bounds.getBounds());
} else {
// This will reset the hash if the original hash was not valid
bounds.setBounds(bounds.getBounds());
}
} else {
// Initially set the hash if there was not one set by the user
bounds.setBounds(bounds.getBounds());
}
$("input").click(function (e) {
display();
});
});
function notifyBboxUpdate() {
const bboxText = document.getElementById('boxbounds').textContent;
window.parent.postMessage({ bboxText: bboxText }, '*');
}

9597
gui-src/js/libs/jquery-1.9.1.js vendored Normal file
View File

File diff suppressed because it is too large Load Diff

5
gui-src/js/libs/jquery-1.9.1.min.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

14971
gui-src/js/libs/jquery-ui-1.10.3.custom.js vendored Normal file
View File

File diff suppressed because it is too large Load Diff

View File

File diff suppressed because one or more lines are too long

321
gui-src/js/main.js vendored Normal file
View File

@@ -0,0 +1,321 @@
const { invoke } = window.__TAURI__.core;
// Initialize elements and start the demo progress
window.addEventListener("DOMContentLoaded", async () => {
initFooter();
await checkForUpdates();
registerMessageEvent();
window.selectWorld = selectWorld;
window.startGeneration = startGeneration;
setupProgressListener();
initSettings();
initWorldPicker();
handleBboxInput();
});
// Function to initialize the footer with the current year and version
async function initFooter() {
const currentYear = new Date().getFullYear();
document.getElementById("current-year").textContent = currentYear;
try {
const version = await invoke('gui_get_version');
const footerLink = document.querySelector(".footer-link");
footerLink.textContent = `© ${currentYear} Arnis v${version} by louis-e`;
} catch (error) {
console.error("Failed to fetch version:", error);
}
}
// Function to check for updates and display a notification if available
async function checkForUpdates() {
try {
const isUpdateAvailable = await invoke('gui_check_for_updates');
if (isUpdateAvailable) {
const footer = document.querySelector(".footer");
const updateMessage = document.createElement("a");
updateMessage.href = "https://github.com/louis-e/arnis/releases";
updateMessage.target = "_blank";
updateMessage.style.color = "#fecc44";
updateMessage.style.marginTop = "-5px";
updateMessage.style.fontSize = "0.95em";
updateMessage.style.display = "block";
updateMessage.style.textDecoration = "none";
updateMessage.textContent = "There's a new version available! Click here to download it.";
footer.style.marginTop = "15px";
footer.appendChild(updateMessage);
}
} catch (error) {
console.error("Failed to check for updates: ", error);
}
}
// Function to register the event listener for bbox updates from iframe
function registerMessageEvent() {
window.addEventListener('message', function (event) {
const bboxText = event.data.bboxText;
if (bboxText) {
console.log("Updated BBOX Coordinates:", bboxText);
displayBboxInfoText(bboxText);
}
});
}
// Function to set up the progress bar listener
function setupProgressListener() {
const progressBar = document.getElementById("progress-bar");
const progressMessage = document.getElementById("progress-message");
const progressDetail = document.getElementById("progress-detail");
window.__TAURI__.event.listen("progress-update", (event) => {
const { progress, message } = event.payload;
if (progress != -1) {
progressBar.style.width = `${progress}%`;
progressDetail.textContent = `${Math.round(progress)}%`;
}
if (message != "") {
progressMessage.textContent = message;
if (message.startsWith("Error!")) {
progressMessage.style.color = "#fa7878";
generationButtonEnabled = true;
} else if (message.startsWith("Done!")) {
progressMessage.style.color = "#7bd864";
generationButtonEnabled = true;
} else {
progressMessage.style.color = "";
}
}
});
}
function initSettings() {
// Settings
const settingsModal = document.getElementById("settings-modal");
const slider = document.getElementById("scale-value-slider");
const sliderValue = document.getElementById("slider-value");
// Open settings modal
function openSettings() {
settingsModal.style.display = "flex";
settingsModal.style.justifyContent = "center";
settingsModal.style.alignItems = "center";
}
// Close settings modal
function closeSettings() {
settingsModal.style.display = "none";
}
window.openSettings = openSettings;
window.closeSettings = closeSettings;
// Update slider value display
slider.addEventListener("input", () => {
sliderValue.textContent = parseFloat(slider.value).toFixed(2);
});
}
function initWorldPicker() {
// World Picker
const worldPickerModal = document.getElementById("world-modal");
// Open world picker modal
function openWorldPicker() {
worldPickerModal.style.display = "flex";
worldPickerModal.style.justifyContent = "center";
worldPickerModal.style.alignItems = "center";
}
// Close world picker modal
function closeWorldPicker() {
worldPickerModal.style.display = "none";
}
window.openWorldPicker = openWorldPicker;
window.closeWorldPicker = closeWorldPicker;
}
// Function to validate and handle bbox input
function handleBboxInput() {
const inputBox = document.getElementById("bbox-coords");
const bboxInfo = document.getElementById("bbox-info");
inputBox.addEventListener("input", function () {
const input = inputBox.value.trim();
if (input === "") {
bboxInfo.textContent = "";
bboxInfo.style.color = "";
selectedBBox = "";
return;
}
// Regular expression to validate bbox input (supports both comma and space-separated formats)
const bboxPattern = /^(-?\d+(\.\d+)?)[,\s](-?\d+(\.\d+)?)[,\s](-?\d+(\.\d+)?)[,\s](-?\d+(\.\d+)?)$/;
if (bboxPattern.test(input)) {
const matches = input.match(bboxPattern);
// Extract coordinates (Lat / Lng order expected)
const lat1 = parseFloat(matches[1]);
const lng1 = parseFloat(matches[3]);
const lat2 = parseFloat(matches[5]);
const lng2 = parseFloat(matches[7]);
// Validate latitude and longitude ranges in the expected Lat / Lng order
if (
lat1 >= -90 && lat1 <= 90 &&
lng1 >= -180 && lng1 <= 180 &&
lat2 >= -90 && lat2 <= 90 &&
lng2 >= -180 && lng2 <= 180
) {
// Input is valid; trigger the event
const bboxText = `${lat1} ${lng1} ${lat2} ${lng2}`;
window.dispatchEvent(new MessageEvent('message', { data: { bboxText } }));
// Update the info text
bboxInfo.textContent = "Custom selection confirmed!";
bboxInfo.style.color = "#7bd864";
} else {
// Valid numbers but invalid order or range
bboxInfo.textContent = "Error: Coordinates are out of range or incorrectly ordered (Lat before Lng required).";
bboxInfo.style.color = "#fecc44";
selectedBBox = "";
}
} else {
// Input doesn't match the required format
bboxInfo.textContent = "Invalid format. Please use 'lat,lng,lat,lng' or 'lat lng lat lng'.";
bboxInfo.style.color = "#fecc44";
selectedBBox = "";
}
});
}
// Function to calculate the bounding box "size" in square meters based on latitude and longitude
function calculateBBoxSize(lng1, lat1, lng2, lat2) {
// Approximate distance calculation using Haversine formula or geodesic formula
const toRad = (angle) => (angle * Math.PI) / 180;
const R = 6371000; // Earth radius in meters
const latDistance = toRad(lat2 - lat1);
const lngDistance = toRad(lng2 - lng1);
const a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) +
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
Math.sin(lngDistance / 2) * Math.sin(lngDistance / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
// Width and height of the box
const height = R * latDistance;
const width = R * lngDistance;
return Math.abs(width * height);
}
const threshold1 = 12332660.00;
const threshold2 = 36084700.00;
let selectedBBox = "";
// Function to handle incoming bbox data
function displayBboxInfoText(bboxText) {
selectedBBox = bboxText;
const [lng1, lat1, lng2, lat2] = bboxText.split(" ").map(Number);
const bboxInfo = document.getElementById("bbox-info");
// Reset the info text if the bbox is 0,0,0,0
if (lng1 === 0 && lat1 === 0 && lng2 === 0 && lat2 === 0) {
bboxInfo.textContent = "";
return;
}
// Calculate the size of the selected bbox
const selectedSize = calculateBBoxSize(lng1, lat1, lng2, lat2);
if (selectedSize > threshold2) {
bboxInfo.textContent = "This area is very large and could exceed typical computing limits.";
bboxInfo.style.color = "#fa7878";
} else if (selectedSize > threshold1) {
bboxInfo.textContent = "The area is quite extensive and may take significant time and resources.";
bboxInfo.style.color = "#fecc44";
} else {
bboxInfo.textContent = "Selection confirmed!";
bboxInfo.style.color = "#7bd864";
}
}
let worldPath = "";
async function selectWorld(generate_new_world) {
try {
const worldName = await invoke('gui_select_world', { generateNew: generate_new_world } );
if (worldName) {
worldPath = worldName;
const lastSegment = worldName.split(/[\\/]/).pop();
document.getElementById('selected-world').textContent = lastSegment;
document.getElementById('selected-world').style.color = "#fecc44";
}
} catch (error) {
console.error(error);
document.getElementById('selected-world').textContent = error;
document.getElementById('selected-world').style.color = "#fa7878";
}
closeWorldPicker();
}
let generationButtonEnabled = true;
async function startGeneration() {
try {
if (generationButtonEnabled === false) {
return;
}
if (!selectedBBox || selectedBBox == "0.000000 0.000000 0.000000 0.000000") {
document.getElementById('bbox-info').textContent = "Select a location first!";
document.getElementById('bbox-info').style.color = "#fa7878";
return;
}
if (
worldPath === "No world selected" ||
worldPath == "Invalid Minecraft world" ||
worldPath == "The selected world is currently in use" ||
worldPath == "Minecraft directory not found." ||
worldPath === ""
) {
document.getElementById('selected-world').textContent = "Select a Minecraft world first!";
document.getElementById('selected-world').style.color = "#fa7878";
return;
}
var winter_mode = document.getElementById("winter-toggle").checked;
var scale = parseFloat(document.getElementById("scale-value-slider").value);
var floodfill_timeout = parseInt(document.getElementById("floodfill-timeout").value, 10);
var ground_level = parseInt(document.getElementById("ground-level").value, 10);
// Validate floodfill_timeout and ground_level
floodfill_timeout = isNaN(floodfill_timeout) || floodfill_timeout < 0 ? 20 : floodfill_timeout;
ground_level = isNaN(ground_level) || ground_level < -62 ? 20 : ground_level;
// Pass the bounding box and selected world to the Rust backend
await invoke("gui_start_generation", {
bboxText: selectedBBox,
selectedWorld: worldPath,
worldScale: scale,
groundLevel: ground_level,
winterMode: winter_mode,
floodfillTimeout: floodfill_timeout,
});
console.log("Generation process started.");
generationButtonEnabled = false;
} catch (error) {
console.error("Error starting generation:", error);
generationButtonEnabled = true;
}
}

9180
gui-src/js/maps/leaflet-src.js vendored Normal file
View File

File diff suppressed because it is too large Load Diff

2766
gui-src/js/maps/leaflet.draw-src.js vendored Normal file
View File

File diff suppressed because it is too large Load Diff

10
gui-src/js/maps/leaflet.draw.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

9
gui-src/js/maps/leaflet.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

122
gui-src/js/maps/leaflet.sidebar.js vendored Normal file
View File

@@ -0,0 +1,122 @@
L.Control.Sidebar = L.Control.extend({
includes: L.Mixin.Events,
options: {
closeButton: true,
position: 'left'
},
initialize: function (placeholder, options) {
L.setOptions(this, options);
// Find content container
var content = this._contentContainer = L.DomUtil.get(placeholder);
// Remove the content container from its original parent
content.parentNode.removeChild(content);
// Make sure we don't drag the map when we interact with the content
var stop = L.DomEvent.stopPropagation;
L.DomEvent
.on(content, 'click', stop)
.on(content, 'mousedown', stop)
.on(content, 'touchstart', stop)
.on(content, 'dblclick', stop)
.on(content, 'mousewheel', stop)
.on(content, 'MozMousePixelScroll', stop);
},
addTo: function (map) {
var sidebar = this;
var l = 'leaflet-';
// Create sidebar container
var container = this._container =
L.DomUtil.create('div', l + 'sidebar ' + this.options.position);
// Style and attach content container
var content = this._contentContainer;
L.DomUtil.addClass(content, l + 'control');
container.appendChild(content);
// Create close button and attach it if configured
if (this.options.closeButton) {
var close = L.DomUtil.create('a', 'close', container);
close.innerHTML = '&times;';
L.DomEvent.on(close, 'click', function (e) {
sidebar.hide();
L.DomEvent.stopPropagation(e);
});
}
// Attach sidebar container to controls container
var controlContainer = map._controlContainer;
controlContainer.insertBefore(container, controlContainer.firstChild);
this._map = map;
return this;
},
removeFrom: function (map) {
// Remove sidebar container from controls container
var controlContainer = map._controlContainer;
controlContainer.removeChild(this._container);
return this;
},
isVisible: function () {
return L.DomUtil.hasClass(this._container, 'visible');
},
show: function () {
if (!this.isVisible()) {
L.DomUtil.addClass(this._container, 'visible');
//this._map.panBy([-this.getOffset() / 2, 0], {
// duration: 0.5
//});
this.fire("sidebar-show");
}
},
hide: function () {
if (this.isVisible()) {
L.DomUtil.removeClass(this._container, 'visible');
//this._map.panBy([this.getOffset() / 2, 0], {
// duration: 0.5
//});
this.fire("sidebar-hide");
}
},
toggle: function () {
if (this.isVisible()) {
this.hide();
} else {
this.show();
}
},
getContainer: function () {
return this._contentContainer;
},
setContent: function (content) {
this.getContainer().innerHTML = content;
return this;
},
getOffset: function () {
if (this.options.position === 'right') {
return -this._container.offsetWidth;
} else {
return this._container.offsetWidth;
}
}
});
L.control.sidebar = function (placeholder, options) {
return new L.Control.Sidebar(placeholder, options);
};

3
gui-src/js/maps/mapbox.standalone.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

66
gui-src/js/maps/mapbox.v3.2.0.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

5733
gui-src/js/maps/proj4-src.js vendored Normal file
View File

File diff suppressed because it is too large Load Diff

3
gui-src/js/maps/proj4.js vendored Normal file
View File

File diff suppressed because one or more lines are too long

227
gui-src/js/maps/proj4leaflet.js vendored Normal file
View File

@@ -0,0 +1,227 @@
(function (factory) {
var L, proj4;
if (typeof define === 'function' && define.amd) {
// AMD
define(['leaflet', 'proj4'], factory);
} else if (typeof module !== 'undefined') {
// Node/CommonJS
L = require('leaflet');
proj4 = require('proj4');
module.exports = factory(L, proj4);
} else {
// Browser globals
if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined')
throw "Leaflet and proj4 must be loaded first";
factory(window.L, window.proj4);
}
}(function (L, proj4) {
L.Proj = {};
L.Proj._isProj4Obj = function(a) {
return (typeof a['inverse'] !== 'undefined'
&& typeof a['forward'] !== 'undefined');
};
L.Proj.ScaleDependantTransformation = function(scaleTransforms) {
this.scaleTransforms = scaleTransforms;
}
L.Proj.ScaleDependantTransformation.prototype.transform = function(point, scale) {
return this.scaleTransforms[scale].transform(point, scale);
};
L.Proj.ScaleDependantTransformation.prototype.untransform = function(point, scale) {
return this.scaleTransforms[scale].untransform(point, scale);
};
L.Proj.Projection = L.Class.extend({
initialize: function(a, def) {
if (L.Proj._isProj4Obj(a)) {
this._proj = a;
} else {
var code = a;
if (def) {
proj4.defs(code, def);
} else if (proj4.defs[code] === undefined) {
var urn = code.split(':');
if (urn.length > 3)
code = urn[urn.length - 3] + ':' + urn[urn.length - 1];
if (proj4.defs[code] === undefined)
throw "No projection definition for code " + code;
}
this._proj = proj4(code);
}
},
project: function (latlng) {
var point = this._proj.forward([latlng.lng, latlng.lat]);
return new L.Point(point[0], point[1]);
},
unproject: function (point, unbounded) {
var point2 = this._proj.inverse([point.x, point.y]);
return new L.LatLng(point2[1], point2[0], unbounded);
}
});
L.Proj.CRS = L.Class.extend({
includes: L.CRS,
options: {
transformation: new L.Transformation(1, 0, -1, 0)
},
initialize: function(a, b, c) {
var code, proj, def, options;
if (L.Proj._isProj4Obj(a)) {
proj = a;
code = proj.srsCode;
options = b || {};
this.projection = new L.Proj.Projection(proj);
} else {
code = a;
def = b;
options = c || {};
this.projection = new L.Proj.Projection(code, def);
}
L.Util.setOptions(this, options);
this.code = code;
this.transformation = this.options.transformation;
if (this.options.origin) {
this.transformation =
new L.Transformation(1, -this.options.origin[0],
-1, this.options.origin[1]);
}
if (this.options.scales) {
this.scale = function(zoom) {
return this.options.scales[zoom];
}
} else if (this.options.resolutions) {
this.scale = function(zoom) {
return 1 / this.options.resolutions[zoom];
}
}
}
});
L.Proj.CRS.TMS = L.Proj.CRS.extend({
initialize: function(a, b, c, d) {
if (L.Proj._isProj4Obj(a)) {
var proj = a,
projectedBounds = b,
options = c || {};
options.origin = [projectedBounds[0], projectedBounds[3]];
L.Proj.CRS.prototype.initialize.call(this, proj, options);
} else {
var code = a,
def = b,
projectedBounds = c,
options = d || {};
options.origin = [projectedBounds[0], projectedBounds[3]];
L.Proj.CRS.prototype.initialize.call(this, code, def, options);
}
this.projectedBounds = projectedBounds;
}
});
L.Proj.TileLayer = {};
L.Proj.TileLayer.TMS = L.TileLayer.extend({
options: {
tms: true,
continuousWorld: true
},
initialize: function(urlTemplate, crs, options) {
var boundsMatchesGrid = true,
scaleTransforms,
upperY,
crsBounds;
if (!(crs instanceof L.Proj.CRS.TMS)) {
throw new Error("CRS is not L.Proj.CRS.TMS.");
}
L.TileLayer.prototype.initialize.call(this, urlTemplate, options);
this.crs = crs;
crsBounds = this.crs.projectedBounds;
// Verify grid alignment
for (var i = this.options.minZoom; i < this.options.maxZoom && boundsMatchesGrid; i++) {
var gridHeight = (crsBounds[3] - crsBounds[1]) /
this._projectedTileSize(i);
boundsMatchesGrid = Math.abs(gridHeight - Math.round(gridHeight)) > 1e-3;
}
if (!boundsMatchesGrid) {
scaleTransforms = {};
for (var i = this.options.minZoom; i < this.options.maxZoom; i++) {
upperY = crsBounds[1] + Math.ceil((crsBounds[3] - crsBounds[1]) /
this._projectedTileSize(i)) * this._projectedTileSize(i);
scaleTransforms[this.crs.scale(i)] = new L.Transformation(1, -crsBounds[0], -1, upperY);
}
this.crs = new L.Proj.CRS.TMS(this.crs.projection._proj, crsBounds, this.crs.options);
this.crs.transformation = new L.Proj.ScaleDependantTransformation(scaleTransforms);
}
},
getTileUrl: function(tilePoint, zoom) {
var gridHeight = Math.ceil(
(this.crs.projectedBounds[3] - this.crs.projectedBounds[1]) /
this._projectedTileSize(zoom)
);
return L.Util.template(this._url, L.Util.extend({
s: this._getSubdomain(tilePoint),
z: zoom,
x: tilePoint.x,
y: gridHeight - tilePoint.y - 1
}, this.options));
},
_projectedTileSize: function(zoom) {
return (this.options.tileSize / this.crs.scale(zoom));
}
});
L.Proj.GeoJSON = L.GeoJSON.extend({
initialize: function(geojson, options) {
if (geojson.crs && geojson.crs.type == 'name') {
var crs = new L.Proj.CRS(geojson.crs.properties.name)
options = options || {};
options.coordsToLatLng = function(coords) {
var point = L.point(coords[0], coords[1]);
return crs.projection.unproject(point);
};
}
L.GeoJSON.prototype.initialize.call(this, geojson, options);
}
});
L.Proj.geoJson = function(geojson, options) {
return new L.Proj.GeoJSON(geojson, options);
};
if (typeof L.CRS !== 'undefined') {
// This is left here for backwards compatibility
L.CRS.proj4js = (function () {
return function (code, def, transformation, options) {
options = options || {};
if (transformation) options.transformation = transformation;
return new L.Proj.CRS(code, def, options);
};
}());
}
return L.Proj;
}));

158
gui-src/js/maps/test.runner.js vendored Normal file
View File

@@ -0,0 +1,158 @@
(function( definition ) { // execute immeidately
if ( typeof module !== 'undefined' &&
typeof module.exports !== 'undefined' ) {
module.exports = definition();
}
else if ( typeof window === "object" ) {
// example run syntax: BBOX_T( { 'url' : '/js/maps/testdata.js' } );
window.BBOX_T = definition();
}
})( function() {
'use strict';
/*
**
** constructor
**
*/
var TestRunner = function( options ) {
options || ( options = {} );
if( !this || !(this instanceof TestRunner )){
return new TestRunner( options );
}
this.test_url = options.url || "";
this.global_setup(); // execute immediately
};
/*
**
** functions
**
*/
TestRunner.prototype.global_setup = function() {
var self = this; // hold ref to instance
$.ajax({
'url' : this.test_url ,
'dataType' : 'json'
})
.done( function( json_data ) {
self.run_this_mother.call( self, json_data );
})
.fail( function( error ) {
console.log( "The test data didn't load: ", error );
});
};
TestRunner.prototype.single_setup = function() {
this.get_layer_count();
};
TestRunner.prototype.tear_down = function() {
if( this._draw_delete_handler ){
this._draw_delete_handler.off('draw:deleted');
}
};
TestRunner.prototype.run_this_mother = function( json_data ) {
for( var key in json_data ){
console.log( "[ RUNNING ]: test " + json_data[key]['type'] + "->" + "simple=" + json_data[key]['simple'] );
var data = json_data[key]['data'];
if( json_data[key]['type'] === 'geojson' ) {
data = JSON.stringify( data );
}
/*
** run different tests
** the context here is jQuery, so change
** to reference the instance
*/
this.single_setup();
this.test_parsing( data, json_data );
this.test_add2map( json_data );
this.test_deletable( json_data );
this.tear_down();
}
};
TestRunner.prototype.test_deletable = function(identifier){ // TODO: this needs work
var toolbar = null;
// get the right toolbar, depending on attributes
for( var key in drawControl._toolbars ){
var tbar = drawControl._toolbars[key];
if ( !(tbar instanceof L.EditToolbar ) ){
continue;
}
toolbar = tbar; // set the right one;
}
// create delete handler that makes sure things are deleted
this._draw_delete_handler = map.on('draw:deleted', function (e) {
try {
e.layers.eachLayer(function (l) {
drawnItems.removeLayer(l);
});
console.warn( "[ PASSED ]: test_deletable" );
}
catch ( err ) {
console.error( "[ DELETE TEST FAIL ]: ", err.message, identifier );
}
});
// loop through this toolbars featureGroup, delete layers
if ( !toolbar._activeMode ) {
toolbar._modes['remove'].button.click(); // enable deletable
}
for( var indx in toolbar.options['featureGroup']._layers ) {
try {
var lyr = toolbar.options['featureGroup']._layers[indx];
lyr.fire( 'click' ); // triggers delete
}
catch ( err ){
console.error( "[ DELETE TEST FAIL ]: ", err.message, identifier );
}
}
// WTF?
$('a[title="Save changes."]')[0].click(); // disable deletable
};
TestRunner.prototype.test_add2map = function(identifier){
var current_num = Object.keys( map._layers ).length;
if( current_num <= this.num_layers_before_parse ){
console.error( "[ ADD2MAP TEST FAIL ]: ", identifier );
}
else {
console.warn( "[ PASSED ]: test_add2map" );
}
};
TestRunner.prototype.get_layer_count = function(){
this.num_layers_before_parse = Object.keys( map._layers ).length;
};
TestRunner.prototype.test_parsing = function( data, identifier ){
var is_valid = FormatSniffer( { data : data } ).sniff();
if ( !is_valid ) {
console.error( "[ PARSE TEST FAIL ]: ", identifier );
}
else {
console.warn( "[ PASSED ]: test_parsing" );
}
};
return TestRunner; // return class def
});

549
gui-src/js/maps/wkt.parser.js vendored Normal file
View File

@@ -0,0 +1,549 @@
/*
**
** taking only necessary pieces from
** https://raw.github.com/arthur-e/Wicket/master/wicket.src.js
**
*/
var Wkt = (function () { // Execute function immediately
var beginsWith, endsWith;
/**
* Returns true if the substring is found at the beginning of the string.
* @param str {String} The String to search
* @param sub {String} The substring of interest
* @return {Boolean}
* @private
*/
beginsWith = function (str, sub) {
return str.substring(0, sub.length) === sub;
};
/**
* Returns true if the substring is found at the end of the string.
* @param str {String} The String to search
* @param sub {String} The substring of interest
* @return {Boolean}
* @private
*/
endsWith = function (str, sub) {
return str.substring(str.length - sub.length) === sub;
};
return {
/**
* The default delimiter for separating components of atomic geometry (coordinates)
* @ignore
*/
delimiter: ' ',
/**
* Determines whether or not the passed Object is an Array.
* @param obj {Object} The Object in question
* @return {Boolean}
* @member Wkt.isArray
* @method
*/
isArray: function (obj) {
return !!(obj && obj.constructor === Array);
},
/**
* Removes given character String(s) from a String.
* @param str {String} The String to search
* @param sub {String} The String character(s) to trim
* @return {String} The trimmed string
* @member Wkt.trim
* @method
*/
trim: function (str, sub) {
sub = sub || ' '; // Defaults to trimming spaces
// Trim beginning spaces
while (beginsWith(str, sub)) {
str = str.substring(1);
}
// Trim ending spaces
while (endsWith(str, sub)) {
str = str.substring(0, str.length - 1);
}
return str;
},
/**
* An object for reading WKT strings and writing geographic features
* @constructor Wkt.Wkt
* @param initializer {String} An optional WKT string for immediate read
* @property {Array} components - Holder for atomic geometry objects (internal representation of geometric components)
* @property {String} delimiter - The default delimiter for separating components of atomic geometry (coordinates)
* @property {Object} regExes - Some regular expressions copied from OpenLayers.Format.WKT.js
* @property {String} type - The Well-Known Text name (e.g. 'point') of the geometry
* @property {Boolean} wrapVerticies - True to wrap vertices in MULTIPOINT geometries; If true: MULTIPOINT((30 10),(10 30),(40 40)); If false: MULTIPOINT(30 10,10 30,40 40)
* @return {Wkt.Wkt}
* @memberof Wkt
*/
Wkt: function (initializer) {
/**
* The default delimiter between X and Y coordinates.
* @ignore
*/
this.delimiter = Wkt.delimiter;
/**
* Configuration parameter for controlling how Wicket seralizes
* MULTIPOINT strings. Examples; both are valid WKT:
* If true: MULTIPOINT((30 10),(10 30),(40 40))
* If false: MULTIPOINT(30 10,10 30,40 40)
* @ignore
*/
this.wrapVertices = true;
/**
* Some regular expressions copied from OpenLayers.Format.WKT.js
* @ignore
*/
this.regExes = {
'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
'spaces': /\s+|\+/, // Matches the '+' or the empty space
'numeric': /-*\d+(\.*\d+)?/,
'comma': /\s*,\s*/,
'parenComma': /\)\s*,\s*\(/,
'coord': /-*\d+\.*\d+ -*\d+\.*\d+/, // e.g. "24 -14"
'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,
'trimParens': /^\s*\(?(.*?)\)?\s*$/
};
/**
* The internal representation of geometry--the "components" of geometry.
* @ignore
*/
this.components = undefined;
// An initial WKT string may be provided
if (initializer && typeof initializer === 'string') {
this.read(initializer);
} else if (initializer && typeof initializer !== undefined) {
this.fromObject(initializer);
}
}
};
}());
/**
* Returns true if the internal geometry is a collection of geometries.
* @return {Boolean} Returns true when it is a collection
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.isCollection = function () {
switch (this.type.slice(0, 5)) {
case 'multi':
// Trivial; any multi-geometry is a collection
return true;
case 'polyg':
// Polygons with holes are "collections" of rings
return true;
default:
// Any other geometry is not a collection
return false;
}
};
/**
* Compares two x,y coordinates for equality.
* @param a {Object} An object with x and y properties
* @param b {Object} An object with x and y properties
* @return {Boolean}
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.sameCoords = function (a, b) {
return (a.x === b.x && a.y === b.y);
};
/**
* Sets internal geometry (components) from framework geometry (e.g.
* Google Polygon objects or google.maps.Polygon).
* @param obj {Object} The framework-dependent geometry representation
* @return {Wkt.Wkt} The object itself
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.fromObject = function (obj) {
var result = this.deconstruct.call(this, obj);
this.components = result.components;
this.isRectangle = result.isRectangle || false;
this.type = result.type;
return this;
};
/**
* Creates external geometry objects based on a plug-in framework's
* construction methods and available geometry classes.
* @param config {Object} An optional framework-dependent properties specification
* @return {Object} The framework-dependent geometry representation
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.toObject = function (config) {
return this.construct[this.type].call(this, config);
};
/**
* Absorbs the geometry of another Wkt.Wkt instance, merging it with its own,
* creating a collection (MULTI-geometry) based on their types, which must agree.
* For example, creates a MULTIPOLYGON from a POLYGON type merged with another
<<<<<<< HEAD
* POLYGON type.
=======
* POLYGON type, or adds a POLYGON instance to a MULTIPOLYGON instance.
>>>>>>> dev
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.merge = function (wkt) {
var prefix = this.type.slice(0, 5);
if (this.type !== wkt.type) {
if (this.type.slice(5, this.type.length) !== wkt.type) {
throw TypeError('The input geometry types must agree or the calling Wkt.Wkt instance must be a multigeometry of the other');
}
}
switch (prefix) {
case 'point':
this.components = [this.components.concat(wkt.components)];
break;
case 'multi':
this.components = this.components.concat((wkt.type.slice(0, 5) === 'multi') ? wkt.components : [wkt.components]);
break;
default:
this.components = [
this.components,
wkt.components
]
break;
}
if (prefix !== 'multi') {
this.type = 'multi' + this.type;
}
};
/**
* Reads a WKT string, validating and incorporating it.
* @param wkt {String} A WKT string
* @return {Array} An Array of internal geometry objects
* @memberof Wkt.Wkt
* @method
*/
Wkt.Wkt.prototype.read = function (wkt) {
var matches;
matches = this.regExes.typeStr.exec(wkt);
if (matches) {
this.type = matches[1].toLowerCase();
this.base = matches[2];
if (this.ingest[this.type]) {
this.components = this.ingest[this.type].apply(this, [this.base]);
}
} else {
console.log("Invalid WKT string provided to read()");
throw {
name: "WKTError",
message: "Invalid WKT string provided to read()"
};
}
return this.components;
}; // eo readWkt
/**
* This object contains functions as property names that ingest WKT
* strings into the internal representation.
* @memberof Wkt.Wkt
* @namespace Wkt.Wkt.ingest
* @instance
*/
Wkt.Wkt.prototype.ingest = {
/**
* Return point feature given a point WKT fragment.
* @param str {String} A WKT fragment representing the point
* @memberof Wkt.Wkt.ingest
* @instance
*/
point: function (str) {
var coords = Wkt.trim(str).split(this.regExes.spaces);
// In case a parenthetical group of coordinates is passed...
return [{ // ...Search for numeric substrings
x: parseFloat(this.regExes.numeric.exec(coords[0])[0]),
y: parseFloat(this.regExes.numeric.exec(coords[1])[0])
}];
},
/**
* Return a multipoint feature given a multipoint WKT fragment.
* @param str {String} A WKT fragment representing the multipoint
* @memberof Wkt.Wkt.ingest
* @instance
*/
multipoint: function (str) {
var i, components, points;
components = [];
points = Wkt.trim(str).split(this.regExes.comma);
for (i = 0; i < points.length; i += 1) {
components.push(this.ingest.point.apply(this, [points[i]]));
}
return components;
},
/**
* Return a linestring feature given a linestring WKT fragment.
* @param str {String} A WKT fragment representing the linestring
* @memberof Wkt.Wkt.ingest
* @instance
*/
linestring: function (str) {
var i, multipoints, components;
// In our x-and-y representation of components, parsing
// multipoints is the same as parsing linestrings
multipoints = this.ingest.multipoint.apply(this, [str]);
// However, the points need to be joined
components = [];
for (i = 0; i < multipoints.length; i += 1) {
components = components.concat(multipoints[i]);
}
return components;
},
/**
* Return a multilinestring feature given a multilinestring WKT fragment.
* @param str {String} A WKT fragment representing the multilinestring
* @memberof Wkt.Wkt.ingest
* @instance
*/
multilinestring: function (str) {
var i, components, line, lines;
components = [];
lines = Wkt.trim(str).split(this.regExes.doubleParenComma);
if (lines.length === 1) { // If that didn't work...
lines = Wkt.trim(str).split(this.regExes.parenComma);
}
for (i = 0; i < lines.length; i += 1) {
line = lines[i].replace(this.regExes.trimParens, '$1');
components.push(this.ingest.linestring.apply(this, [line]));
}
return components;
},
/**
* Return a polygon feature given a polygon WKT fragment.
* @param str {String} A WKT fragment representing the polygon
* @memberof Wkt.Wkt.ingest
* @instance
*/
polygon: function (str) {
var i, j, components, subcomponents, ring, rings;
rings = Wkt.trim(str).split(this.regExes.parenComma);
components = []; // Holds one or more rings
for (i = 0; i < rings.length; i += 1) {
ring = rings[i].replace(this.regExes.trimParens, '$1').split(this.regExes.comma);
subcomponents = []; // Holds the outer ring and any inner rings (holes)
for (j = 0; j < ring.length; j += 1) {
// Split on the empty space or '+' character (between coordinates)
subcomponents.push({
x: parseFloat(ring[j].split(this.regExes.spaces)[0]),
y: parseFloat(ring[j].split(this.regExes.spaces)[1])
});
}
components.push(subcomponents);
}
return components;
},
/**
* Return a multipolygon feature given a multipolygon WKT fragment.
* @param str {String} A WKT fragment representing the multipolygon
* @memberof Wkt.Wkt.ingest
* @instance
*/
multipolygon: function (str) {
var i, components, polygon, polygons;
components = [];
polygons = Wkt.trim(str).split(this.regExes.doubleParenComma);
for (i = 0; i < polygons.length; i += 1) {
polygon = polygons[i].replace(this.regExes.trimParens, '$1');
components.push(this.ingest.polygon.apply(this, [polygon]));
}
return components;
},
/**
* Return an array of features given a geometrycollection WKT fragment.
* @param str {String} A WKT fragment representing the geometry collection
* @memberof Wkt.Wkt.ingest
* @instance
*/
geometrycollection: function (str) {
console.log('The geometrycollection WKT type is not yet supported.');
}
}; // eo ingest
/**
* @augments Wkt.Wkt
* Truncates an Array of coordinates by the closing coordinate when it is
* equal to the first coordinate given--this is only to be used for closed
* geometries in order to provide merely an "implied" closure to Leaflet.
* @param coords {Array} An Array of x,y coordinates (objects)
* @return {Array}
*/
Wkt.Wkt.prototype.trunc = function (coords) {
var i, verts = [];
for (i = 0; i < coords.length; i += 1) {
if (Wkt.isArray(coords[i])) {
verts.push(this.trunc(coords[i]));
} else {
// Add the first coord, but skip the last if it is identical
if (i === 0 || !this.sameCoords(coords[0], coords[i])) {
verts.push(coords[i]);
}
}
}
return verts;
};
/**
* @augments Wkt.Wkt
* An object of framework-dependent construction methods used to generate
* objects belonging to the various geometry classes of the framework.
*/
Wkt.Wkt.prototype.construct = {
/**
* Creates the framework's equivalent point geometry object.
* @param config {Object} An optional properties hash the object should use
* @param component {Object} An optional component to build from
* @return {L.marker}
*/
point: function (config, component) {
var coord = component || this.components;
if (coord instanceof Array) {
coord = coord[0];
}
return L.marker(this.coordsToLatLng(coord), config);
},
/**
* Creates the framework's equivalent multipoint geometry object.
* @param config {Object} An optional properties hash the object should use
* @return {L.featureGroup}
*/
multipoint: function (config) {
var i,
layers = [],
coords = this.components;
for (i = 0; i < coords.length; i += 1) {
layers.push(this.construct.point.call(this, config, coords[i]));
}
return L.featureGroup(layers, config);
},
/**
* Creates the framework's equivalent linestring geometry object.
* @param config {Object} An optional properties hash the object should use
* @param component {Object} An optional component to build from
* @return {L.polyline}
*/
linestring: function (config, component) {
var coords = component || this.components,
latlngs = this.coordsToLatLngs(coords);
return L.polyline(latlngs, config);
},
/**
* Creates the framework's equivalent multilinestring geometry object.
* @param config {Object} An optional properties hash the object should use
* @return {L.multiPolyline}
*/
multilinestring: function (config) {
var coords = this.components,
latlngs = this.coordsToLatLngs(coords, 1);
return L.multiPolyline(latlngs, config);
},
/**
* Creates the framework's equivalent polygon geometry object.
* @param config {Object} An optional properties hash the object should use
* @return {L.multiPolygon}
*/
polygon: function (config) {
// Truncate the coordinates to remove the closing coordinate
var coords = this.trunc(this.components),
latlngs = this.coordsToLatLngs(coords, 1);
return L.polygon(latlngs, config);
},
/**
* Creates the framework's equivalent multipolygon geometry object.
* @param config {Object} An optional properties hash the object should use
* @return {L.multiPolygon}
*/
multipolygon: function (config) {
// Truncate the coordinates to remove the closing coordinate
var coords = this.trunc(this.components),
latlngs = this.coordsToLatLngs(coords, 2);
return L.multiPolygon(latlngs, config);
},
/**
* Creates the framework's equivalent collection of geometry objects.
* @param config {Object} An optional properties hash the object should use
* @return {L.featureGroup}
*/
geometrycollection: function (config) {
var comps, i, layers;
comps = this.trunc(this.components);
layers = [];
for (i = 0; i < this.components.length; i += 1) {
layers.push(this.construct[comps[i].type].call(this, comps[i]));
}
return L.featureGroup(layers, config);
}
};
L.Util.extend(Wkt.Wkt.prototype, {
coordsToLatLngs: L.GeoJSON.coordsToLatLngs,
// TODO Why doesn't the coordsToLatLng function in L.GeoJSON already suffice?
coordsToLatLng: function (coords, reverse) {
var lat = reverse ? coords.x : coords.y,
lng = reverse ? coords.y : coords.x;
return L.latLng(lat, lng, true);
}
});

60
gui-src/maps.html vendored Normal file
View File

@@ -0,0 +1,60 @@
<html>
<head>
<title>Map View</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="css/maps/leaflet.css" />
<link rel="stylesheet" href="css/maps/leaflet.draw.css" />
<link rel="stylesheet" href="css/maps/leaflet.sidebar.css" />
<link rel="stylesheet" href="css/maps/mapbox.v3.2.0.css" />
<link rel="stylesheet" href="css/bbox.css" />
<script src="js/libs/jquery-1.9.1.min.js"></script>
<script src="js/libs/jquery-ui-1.10.3.custom.js"></script>
<script src="js/maps/leaflet.js"></script>
<script src="js/maps/mapbox.v3.2.0.js"></script>
<script src="js/maps/leaflet.draw.js"></script>
<script src="js/maps/leaflet.sidebar.js"></script>
<script src="js/maps/wkt.parser.js"></script>
<script src="js/maps/proj4-src.js"></script>
<script src="js/maps/proj4leaflet.js"></script>
<script src="js/bbox.js"></script>
</head>
<body>
<div id="map"></div>
<div id="rsidebar" >
<button id="add">Add</button>
<button id="clear">Clear</button>
</div>
<div id="lsidebar">
</div>
<section id="info-box">
<div id="info">
<div id="wgscoords" class="coords">
<div id="bbounds">
<span id="bbboundslabel" class="bboxlabel">BBOX</span>
<span id="boxbounds" class="bboxllpos"></span>
</div>
</div>
<div id="projcoords" class="coords">
<div id="bbounds">
<span id="bbboundslabel" class="bboxlabel">BBOX</span>
<span id="boxboundsmerc" class="bboxprojpos"></span>
</div>
</div>
</div>
</section>
<div id="ocontainer" class="ocontainer" style="display:none;">
<textarea rows="25" cols="100" ></textarea>
<div id="submit-buttons">
<button id="cancel">Cancel</button>
<button id="add">Add</button>
</div>
</div>
</body>
</html>

BIN
icons/128x128.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
icons/128x128@2x.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
icons/32x32.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
icons/icon.icns Normal file
View File

Binary file not shown.

BIN
icons/icon.ico Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

BIN
icons/icon.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 KiB

BIN
mcassets/icon.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

BIN
mcassets/level.dat Normal file
View File

Binary file not shown.

View File

@@ -2,6 +2,7 @@ use clap::{ArgGroup, Parser};
use colored::Colorize;
use std::path::Path;
use std::process::exit;
use std::time::Duration;
/// Command-line arguments parser
#[derive(Parser, Debug)]
@@ -13,7 +14,7 @@ use std::process::exit;
))]
pub struct Args {
/// Bounding box of the area (min_lng,min_lat,max_lng,max_lat) (required)
#[arg(long)]
#[arg(long, allow_hyphen_values = true)]
pub bbox: Option<String>,
/// JSON file containing OSM data (optional)
@@ -28,13 +29,25 @@ pub struct Args {
#[arg(long, default_value = "requests")]
pub downloader: String,
/// World scale to use, in blocks per meter
#[arg(long, default_value = "1.0")]
pub scale: f64,
/// Ground level to use in the Minecraft world
#[arg(long, default_value_t = -62)]
pub ground_level: i32,
/// Enable winter mode (default: false)
#[arg(long, default_value_t = false)]
pub winter: bool,
/// Enable debug mode (optional)
#[arg(long, default_value_t = false, action = clap::ArgAction::SetTrue)]
pub debug: bool,
/// Set floodfill timeout (seconds) (optional) // TODO
#[arg(long, default_value_t = 2)]
pub timeout: u64,
/// Set floodfill timeout (seconds) (optional)
#[arg(long, value_parser = parse_duration)]
pub timeout: Option<Duration>,
}
impl Args {
@@ -42,7 +55,12 @@ impl Args {
// Validating the world path
let mc_world_path: &Path = Path::new(&self.path);
if !mc_world_path.join("region").exists() {
eprintln!("{}", "Error! No Minecraft world found at the given path".red().bold());
eprintln!(
"{}",
"Error! No Minecraft world found at the given path"
.red()
.bold()
);
exit(1);
}
@@ -68,13 +86,18 @@ fn validate_bounding_box(bbox: &str) -> bool {
let max_lng: f64 = parts[2].parse().ok().unwrap_or(0.0);
let max_lat: f64 = parts[3].parse().ok().unwrap_or(0.0);
if !(min_lng >= -180.0 && min_lng <= 180.0) || !(max_lng >= -180.0 && max_lng <= 180.0) {
if !(-180.0..=180.0).contains(&min_lng) || !(-180.0..=180.0).contains(&max_lng) {
return false;
}
if !(min_lat >= -90.0 && min_lat <= 90.0) || !(max_lat >= -90.0 && max_lat <= 90.0) {
if !(-90.0..=90.0).contains(&min_lat) || !(-90.0..=90.0).contains(&max_lat) {
return false;
}
min_lng < max_lng && min_lat < max_lat
}
fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
let seconds = arg.parse()?;
Ok(std::time::Duration::from_secs(seconds))
}

View File

@@ -5,249 +5,389 @@ use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone)]
use crate::colors::RGBTuple;
#[derive(Copy, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Debug)]
pub struct Block {
pub namespace: String,
pub name: String,
pub properties: Option<Value>,
id: u8,
}
impl Block {
pub fn new(namespace: &str, name: &str, properties: Option<Value>) -> Self {
Self {
namespace: namespace.to_string(),
name: name.to_string(),
properties,
const fn new(id: u8) -> Self {
Self { id }
}
pub fn id(&self) -> u8 {
self.id
}
pub fn namespace(&self) -> &str {
"mincraft"
}
pub fn name(&self) -> &str {
match self.id {
0 => "acacia_planks",
1 => "air",
2 => "andesite",
3 => "birch_leaves",
4 => "birch_log",
5 => "black_concrete",
6 => "blackstone",
7 => "blue_orchid",
8 => "blue_terracotta",
9 => "bricks",
10 => "cauldron",
11 => "chiseled_stone_bricks",
12 => "cobblestone_wall",
13 => "cobblestone",
14 => "cracked_polished_blackstone_bricks",
15 => "cracked_stone_bricks",
16 => "crimson_planks",
17 => "cut_sandstone",
18 => "cyan_concrete",
19 => "dark_oak_planks",
20 => "deepslate_bricks",
21 => "diorite",
22 => "dirt",
23 => "end_stone_bricks",
24 => "farmland",
25 => "glass_pane",
26 => "glowstone",
27 => "granite",
28 => "grass_block",
29 => "tall_grass",
30 => "gravel",
31 => "gray_concrete",
32 => "gray_terracotta",
33 => "green_terracotta",
34 => "green_wool",
35 => "hay_block",
36 => "iron_bars",
37 => "iron_block",
38 => "jungle_planks",
39 => "ladder",
40 => "light_blue_concrete",
41 => "light_blue_terracotta",
42 => "light_gray_concrete",
43 => "moss_block",
44 => "mossy_cobblestone",
45 => "mud_bricks",
46 => "nether_bricks",
47 => "nether_bricks",
48 => "oak_fence",
49 => "oak_leaves",
50 => "oak_log",
51 => "oak_planks",
52 => "oak_slab",
53 => "orange_terracotta",
54 => "podzol",
55 => "polished_andesite",
56 => "polished_basalt",
57 => "polished_blackstone_bricks",
58 => "polished_blackstone",
59 => "polished_deepslate",
60 => "polished_diorite",
61 => "polished_granite",
62 => "prismarine",
63 => "purpur_block",
64 => "purpur_pillar",
65 => "quartz_bricks",
66 => "rail",
67 => "poppy",
68 => "red_nether_bricks",
69 => "red_terracotta",
70 => "red_wool",
71 => "sand",
72 => "sandstone",
73 => "scaffolding",
74 => "smooth_quartz",
75 => "smooth_red_sandstone",
76 => "smooth_sandstone",
77 => "smooth_stone",
78 => "sponge",
79 => "spruce_log",
80 => "spruce_planks",
81 => "stone_slab",
82 => "stone_brick_slab",
83 => "stone_bricks",
84 => "stone",
85 => "terracotta",
86 => "warped_planks",
87 => "water",
88 => "white_concrete",
89 => "azure_bluet",
90 => "white_stained_glass",
91 => "white_terracotta",
92 => "white_wool",
93 => "yellow_concrete",
94 => "dandelion",
95 => "yellow_wool",
96 => "lime_concrete",
97 => "cyan_wool",
98 => "blue_concrete",
99 => "purple_concrete",
100 => "red_concrete",
101 => "magenta_concrete",
102 => "brown_wool",
103 => "oxidized_copper",
104 => "yellow_terracotta",
105 => "carrots",
106 => "dark_oak_door",
107 => "dark_oak_door",
108 => "potatoes",
109 => "wheat",
110 => "bedrock",
111 => "snow_block",
112 => "snow",
_ => panic!("Invalid id"),
}
}
pub fn properties(&self) -> Option<Value> {
match self.id {
105 => Some(Value::Compound({
let mut map: HashMap<String, Value> = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
106 => Some(Value::Compound({
let mut map: HashMap<String, Value> = HashMap::new();
map.insert("half".to_string(), Value::String("lower".to_string()));
map
})),
107 => Some(Value::Compound({
let mut map: HashMap<String, Value> = HashMap::new();
map.insert("half".to_string(), Value::String("upper".to_string()));
map
})),
108 => Some(Value::Compound({
let mut map: HashMap<String, Value> = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
109 => Some(Value::Compound({
let mut map: HashMap<String, Value> = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
_ => None,
}
}
}
// Lazy static blocks
pub static ACACIA_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "acacia_planks", None));
pub static AIR: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "air", None));
pub static ANDESITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "andesite", None));
pub static BIRCH_LEAVES: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "birch_leaves", None));
pub static BIRCH_LOG: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "birch_log", None));
pub static BLACK_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "black_concrete", None));
pub static BLACKSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "blackstone", None));
pub static BLUE_FLOWER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "blue_orchid", None));
pub static BLUE_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "blue_terracotta", None));
pub static BRICK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "bricks", None));
pub static CAULDRON: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cauldron", None));
pub static CHISELED_STONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "chiseled_stone_bricks", None));
pub static COBBLESTONE_WALL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cobblestone_wall", None));
pub static COBBLESTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cobblestone", None));
pub static CRACKED_POLISHED_BLACKSTONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cracked_polished_blackstone_bricks", None));
pub static CRACKED_STONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cracked_stone_bricks", None));
pub static CRIMSON_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "crimson_planks", None));
pub static CUT_SANDSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cut_sandstone", None));
pub static CYAN_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "cyan_concrete", None));
pub static DARK_OAK_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "dark_oak_planks", None));
pub static DEEPSLATE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "deepslate_bricks", None));
pub static DIORITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "diorite", None));
pub static DIRT: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "dirt", None));
pub static END_STONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "end_stone_bricks", None));
pub static FARMLAND: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "farmland", None));
pub static GLASS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "glass_pane", None));
pub static GLOWSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "glowstone", None));
pub static GRANITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "granite", None));
pub static GRASS_BLOCK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "grass_block", None));
pub static GRASS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "tall_grass", None));
pub static GRAVEL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "gravel", None));
pub static GRAY_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "gray_concrete", None));
pub static GRAY_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "gray_terracotta", None));
pub static GREEN_STAINED_HARDENED_CLAY: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "green_terracotta", None));
pub static GREEN_WOOL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "green_wool", None));
pub static HAY_BALE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "hay_block", None));
pub static IRON_BARS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "iron_bars", None));
pub static IRON_BLOCK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "iron_block", None));
pub static JUNGLE_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "jungle_planks", None));
pub static LADDER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "ladder", None));
pub static LIGHT_BLUE_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "light_blue_concrete", None));
pub static LIGHT_BLUE_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "light_blue_terracotta", None));
pub static LIGHT_GRAY_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "light_gray_concrete", None));
pub static MOSS_BLOCK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "moss_block", None));
pub static MOSSY_COBBLESTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "mossy_cobblestone", None));
pub static MUD_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "mud_bricks", None));
pub static NETHER_BRICK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "nether_bricks", None));
pub static NETHER_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "nether_bricks", None));
pub static OAK_FENCE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "oak_fence", None));
pub static OAK_LEAVES: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "oak_leaves", None));
pub static OAK_LOG: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "oak_log", None));
pub static OAK_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "oak_planks", None));
pub static OAK_SLAB: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "oak_slab", None));
pub static ORANGE_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "orange_terracotta", None));
pub static PODZOL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "podzol", None));
pub static POLISHED_ANDESITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_andesite", None));
pub static POLISHED_BASALT: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_basalt", None));
pub static POLISHED_BLACKSTONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_blackstone_bricks", None));
pub static POLISHED_BLACKSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_blackstone", None));
pub static POLISHED_DEEPSLATE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_deepslate", None));
pub static POLISHED_DIORITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_diorite", None));
pub static POLISHED_GRANITE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "polished_granite", None));
pub static PRISMARINE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "prismarine", None));
pub static PURPUR_BLOCK: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "purpur_block", None));
pub static PURPUR_PILLAR: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "purpur_pillar", None));
pub static QUARTZ_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "quartz_bricks", None));
pub static RAIL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "rail", None));
pub static RED_FLOWER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "poppy", None));
pub static RED_NETHER_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "red_nether_bricks", None));
pub static RED_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "red_terracotta", None));
pub static RED_WOOL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "red_wool", None));
pub static SAND: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "sand", None));
pub static SANDSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "sandstone", None));
pub static SCAFFOLDING: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "scaffolding", None));
pub static SMOOTH_QUARTZ: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "smooth_quartz", None));
pub static SMOOTH_RED_SANDSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "smooth_red_sandstone", None));
pub static SMOOTH_SANDSTONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "smooth_sandstone", None));
pub static SMOOTH_STONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "smooth_stone", None));
pub static SPONGE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "sponge", None));
pub static SPRUCE_LOG: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "spruce_log", None));
pub static SPRUCE_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "spruce_planks", None));
pub static STONE_BLOCK_SLAB: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "stone_slab", None));
pub static STONE_BRICK_SLAB: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "stone_brick_slab", None));
pub static STONE_BRICKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "stone_bricks", None));
pub static STONE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "stone", None));
pub static TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "terracotta", None));
pub static WARPED_PLANKS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "warped_planks", None));
pub static WATER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "water", None));
pub static WHITE_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "white_concrete", None));
pub static WHITE_FLOWER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "azure_bluet", None));
pub static WHITE_STAINED_GLASS: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "white_stained_glass", None));
pub static WHITE_TERRACOTTA: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "white_terracotta", None));
pub static WHITE_WOOL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "white_wool", None));
pub static YELLOW_CONCRETE: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "yellow_concrete", None));
pub static YELLOW_FLOWER: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "dandelion", None));
pub static YELLOW_WOOL: Lazy<Block> = Lazy::new(|| Block::new("minecraft", "yellow_wool", None));
pub const ACACIA_PLANKS: Block = Block::new(0);
pub const AIR: Block = Block::new(1);
pub const ANDESITE: Block = Block::new(2);
pub const BIRCH_LEAVES: Block = Block::new(3);
pub const BIRCH_LOG: Block = Block::new(4);
pub const BLACK_CONCRETE: Block = Block::new(5);
pub const BLACKSTONE: Block = Block::new(6);
pub const BLUE_FLOWER: Block = Block::new(7);
pub const BLUE_TERRACOTTA: Block = Block::new(8);
pub const BRICK: Block = Block::new(9);
pub const CAULDRON: Block = Block::new(10);
pub const CHISELED_STONE_BRICKS: Block = Block::new(11);
pub const COBBLESTONE_WALL: Block = Block::new(12);
pub const COBBLESTONE: Block = Block::new(13);
pub const CRACKED_POLISHED_BLACKSTONE_BRICKS: Block = Block::new(14);
pub const CRACKED_STONE_BRICKS: Block = Block::new(15);
pub const CRIMSON_PLANKS: Block = Block::new(16);
pub const CUT_SANDSTONE: Block = Block::new(17);
pub const CYAN_CONCRETE: Block = Block::new(18);
pub const DARK_OAK_PLANKS: Block = Block::new(19);
pub const DEEPSLATE_BRICKS: Block = Block::new(20);
pub const DIORITE: Block = Block::new(21);
pub const DIRT: Block = Block::new(22);
pub const END_STONE_BRICKS: Block = Block::new(23);
pub const FARMLAND: Block = Block::new(24);
pub const GLASS: Block = Block::new(25);
pub const GLOWSTONE: Block = Block::new(26);
pub const GRANITE: Block = Block::new(27);
pub const GRASS_BLOCK: Block = Block::new(28);
pub const GRASS: Block = Block::new(29);
pub const GRAVEL: Block = Block::new(30);
pub const GRAY_CONCRETE: Block = Block::new(31);
pub const GRAY_TERRACOTTA: Block = Block::new(32);
pub const GREEN_STAINED_HARDENED_CLAY: Block = Block::new(33);
pub const GREEN_WOOL: Block = Block::new(34);
pub const HAY_BALE: Block = Block::new(35);
pub const IRON_BARS: Block = Block::new(36);
pub const IRON_BLOCK: Block = Block::new(37);
pub const JUNGLE_PLANKS: Block = Block::new(38);
pub const LADDER: Block = Block::new(39);
pub const LIGHT_BLUE_CONCRETE: Block = Block::new(40);
pub const LIGHT_BLUE_TERRACOTTA: Block = Block::new(41);
pub const LIGHT_GRAY_CONCRETE: Block = Block::new(42);
pub const MOSS_BLOCK: Block = Block::new(43);
pub const MOSSY_COBBLESTONE: Block = Block::new(44);
pub const MUD_BRICKS: Block = Block::new(45);
pub const NETHER_BRICK: Block = Block::new(46);
pub const NETHER_BRICKS: Block = Block::new(47);
pub const OAK_FENCE: Block = Block::new(48);
pub const OAK_LEAVES: Block = Block::new(49);
pub const OAK_LOG: Block = Block::new(50);
pub const OAK_PLANKS: Block = Block::new(51);
pub const OAK_SLAB: Block = Block::new(52);
pub const ORANGE_TERRACOTTA: Block = Block::new(53);
pub const PODZOL: Block = Block::new(54);
pub const POLISHED_ANDESITE: Block = Block::new(55);
pub const POLISHED_BASALT: Block = Block::new(56);
pub const POLISHED_BLACKSTONE_BRICKS: Block = Block::new(57);
pub const POLISHED_BLACKSTONE: Block = Block::new(58);
pub const POLISHED_DEEPSLATE: Block = Block::new(59);
pub const POLISHED_DIORITE: Block = Block::new(60);
pub const POLISHED_GRANITE: Block = Block::new(61);
pub const PRISMARINE: Block = Block::new(62);
pub const PURPUR_BLOCK: Block = Block::new(63);
pub const PURPUR_PILLAR: Block = Block::new(64);
pub const QUARTZ_BRICKS: Block = Block::new(65);
pub const RAIL: Block = Block::new(66);
pub const RED_FLOWER: Block = Block::new(67);
pub const RED_NETHER_BRICKS: Block = Block::new(68);
pub const RED_TERRACOTTA: Block = Block::new(69);
pub const RED_WOOL: Block = Block::new(70);
pub const SAND: Block = Block::new(71);
pub const SANDSTONE: Block = Block::new(72);
pub const SCAFFOLDING: Block = Block::new(73);
pub const SMOOTH_QUARTZ: Block = Block::new(74);
pub const SMOOTH_RED_SANDSTONE: Block = Block::new(75);
pub const SMOOTH_SANDSTONE: Block = Block::new(76);
pub const SMOOTH_STONE: Block = Block::new(77);
pub const SPONGE: Block = Block::new(78);
pub const SPRUCE_LOG: Block = Block::new(79);
pub const SPRUCE_PLANKS: Block = Block::new(80);
pub const STONE_BLOCK_SLAB: Block = Block::new(81);
pub const STONE_BRICK_SLAB: Block = Block::new(82);
pub const STONE_BRICKS: Block = Block::new(83);
pub const STONE: Block = Block::new(84);
pub const TERRACOTTA: Block = Block::new(85);
pub const WARPED_PLANKS: Block = Block::new(86);
pub const WATER: Block = Block::new(87);
pub const WHITE_CONCRETE: Block = Block::new(88);
pub const WHITE_FLOWER: Block = Block::new(89);
pub const WHITE_STAINED_GLASS: Block = Block::new(90);
pub const WHITE_TERRACOTTA: Block = Block::new(91);
pub const WHITE_WOOL: Block = Block::new(92);
pub const YELLOW_CONCRETE: Block = Block::new(93);
pub const YELLOW_FLOWER: Block = Block::new(94);
pub const YELLOW_WOOL: Block = Block::new(95);
pub const LIME_CONCRETE: Block = Block::new(96);
pub const CYAN_WOOL: Block = Block::new(97);
pub const BLUE_CONCRETE: Block = Block::new(98);
pub const PURPLE_CONCRETE: Block = Block::new(99);
pub const RED_CONCRETE: Block = Block::new(100);
pub const MAGENTA_CONCRETE: Block = Block::new(101);
pub const BROWN_WOOL: Block = Block::new(102);
pub const OXIDIZED_COPPER: Block = Block::new(103);
pub const YELLOW_TERRACOTTA: Block = Block::new(104);
pub const SNOW_BLOCK: Block = Block::new(111);
pub const SNOW_LAYER: Block = Block::new(112);
pub static CARROTS: Lazy<Block> = Lazy::new(|| {
Block::new(
"minecraft",
"carrots",
Some(Value::Compound({
let mut map = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
)
});
pub static DARK_OAK_DOOR_LOWER: Lazy<Block> = Lazy::new(|| {
Block::new(
"minecraft",
"dark_oak_door",
Some(Value::Compound({
let mut map = HashMap::new();
map.insert("half".to_string(), Value::String("lower".to_string()));
map
})),
)
});
pub static DARK_OAK_DOOR_UPPER: Lazy<Block> = Lazy::new(|| {
Block::new(
"minecraft",
"dark_oak_door",
Some(Value::Compound({
let mut map = HashMap::new();
map.insert("half".to_string(), Value::String("upper".to_string()));
map
})),
)
});
pub static POTATOES: Lazy<Block> = Lazy::new(|| {
Block::new(
"minecraft",
"potatoes",
Some(Value::Compound({
let mut map = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
)
});
pub static WHEAT: Lazy<Block> = Lazy::new(|| {
Block::new(
"minecraft",
"wheat",
Some(Value::Compound({
let mut map = HashMap::new();
map.insert("age".to_string(), Value::String("7".to_string()));
map
})),
)
});
pub const CARROTS: Block = Block::new(105);
pub const DARK_OAK_DOOR_LOWER: Block = Block::new(106);
pub const DARK_OAK_DOOR_UPPER: Block = Block::new(107);
pub const POTATOES: Block = Block::new(108);
pub const WHEAT: Block = Block::new(109);
pub const BEDROCK: Block = Block::new(110);
// Variations for building corners
pub fn building_corner_variations() -> Vec<&'static Lazy<Block>> {
pub fn building_corner_variations() -> Vec<Block> {
vec![
&STONE_BRICKS,
&COBBLESTONE,
&BRICK,
&MOSSY_COBBLESTONE,
&SANDSTONE,
&RED_NETHER_BRICKS,
&BLACKSTONE,
&SMOOTH_QUARTZ,
&CHISELED_STONE_BRICKS,
&POLISHED_BASALT,
&CUT_SANDSTONE,
&POLISHED_BLACKSTONE_BRICKS,
&ANDESITE,
&GRANITE,
&DIORITE,
&CRACKED_STONE_BRICKS,
&PRISMARINE,
&BLUE_TERRACOTTA,
&NETHER_BRICK,
&QUARTZ_BRICKS,
STONE_BRICKS,
COBBLESTONE,
BRICK,
MOSSY_COBBLESTONE,
SANDSTONE,
RED_NETHER_BRICKS,
BLACKSTONE,
SMOOTH_QUARTZ,
CHISELED_STONE_BRICKS,
POLISHED_BASALT,
CUT_SANDSTONE,
POLISHED_BLACKSTONE_BRICKS,
ANDESITE,
GRANITE,
DIORITE,
CRACKED_STONE_BRICKS,
PRISMARINE,
BLUE_TERRACOTTA,
NETHER_BRICK,
QUARTZ_BRICKS,
]
}
// Variations for building walls
pub fn building_wall_variations() -> Vec<&'static Lazy<Block>> {
pub fn building_wall_variations() -> Vec<Block> {
building_wall_color_map()
.into_iter()
.map(|(_, block)| block)
.collect()
}
// https://wiki.openstreetmap.org/wiki/Key:building:colour
pub fn building_wall_color_map() -> Vec<(RGBTuple, Block)> {
vec![
&WHITE_TERRACOTTA,
&GRAY_TERRACOTTA,
&BRICK,
&SMOOTH_SANDSTONE,
&RED_TERRACOTTA,
&POLISHED_DIORITE,
&SMOOTH_STONE,
&POLISHED_ANDESITE,
&WARPED_PLANKS,
&END_STONE_BRICKS,
&SMOOTH_RED_SANDSTONE,
&NETHER_BRICKS,
&YELLOW_CONCRETE,
&ORANGE_TERRACOTTA,
&LIGHT_BLUE_TERRACOTTA,
&CYAN_CONCRETE,
&PURPUR_PILLAR,
&CRACKED_POLISHED_BLACKSTONE_BRICKS,
&DEEPSLATE_BRICKS,
&MUD_BRICKS,
((233, 107, 57), BRICK),
((18, 12, 13), CRACKED_POLISHED_BLACKSTONE_BRICKS),
((76, 127, 153), CYAN_CONCRETE),
((0, 0, 0), DEEPSLATE_BRICKS),
((186, 195, 142), END_STONE_BRICKS),
((57, 41, 35), GRAY_TERRACOTTA),
((112, 108, 138), LIGHT_BLUE_TERRACOTTA),
((122, 92, 66), MUD_BRICKS),
((24, 13, 14), NETHER_BRICKS),
((159, 82, 36), ORANGE_TERRACOTTA),
((128, 128, 128), POLISHED_ANDESITE),
((174, 173, 174), POLISHED_DIORITE),
((141, 101, 142), PURPUR_PILLAR),
((142, 60, 46), RED_TERRACOTTA),
((153, 83, 28), SMOOTH_RED_SANDSTONE),
((224, 216, 175), SMOOTH_SANDSTONE),
((188, 182, 179), SMOOTH_STONE),
((35, 86, 85), WARPED_PLANKS),
((255, 255, 255), WHITE_CONCRETE),
((209, 177, 161), WHITE_TERRACOTTA),
((191, 147, 42), YELLOW_TERRACOTTA),
]
}
// Variations for building floors
pub fn building_floor_variations() -> Vec<&'static Lazy<Block>> {
pub fn building_floor_variations() -> Vec<Block> {
building_wall_color_map()
.into_iter()
.map(|(_, block)| block)
.collect()
}
pub fn building_floor_color_map() -> Vec<(RGBTuple, Block)> {
vec![
&OAK_PLANKS,
&SPRUCE_PLANKS,
&DARK_OAK_PLANKS,
&STONE_BRICKS,
&POLISHED_GRANITE,
&POLISHED_DIORITE,
&ACACIA_PLANKS,
&JUNGLE_PLANKS,
&WARPED_PLANKS,
&PURPUR_BLOCK,
&SMOOTH_RED_SANDSTONE,
&POLISHED_BLACKSTONE,
&CRIMSON_PLANKS,
&LIGHT_BLUE_CONCRETE,
&MOSS_BLOCK,
&TERRACOTTA,
&BLACKSTONE,
&POLISHED_DEEPSLATE,
((181, 101, 59), ACACIA_PLANKS),
((22, 15, 16), BLACKSTONE),
((104, 51, 74), CRIMSON_PLANKS),
((82, 55, 26), DARK_OAK_PLANKS),
((182, 133, 99), JUNGLE_PLANKS),
((33, 128, 185), LIGHT_BLUE_CONCRETE),
((78, 103, 43), MOSS_BLOCK),
((171, 138, 88), OAK_PLANKS),
((0, 128, 0), OXIDIZED_COPPER),
((18, 12, 13), POLISHED_BLACKSTONE),
((64, 64, 64), POLISHED_DEEPSLATE),
((255, 255, 255), POLISHED_DIORITE),
((143, 96, 79), POLISHED_GRANITE),
((141, 101, 142), PURPUR_BLOCK),
((128, 0, 0), RED_NETHER_BRICKS),
((153, 83, 28), SMOOTH_RED_SANDSTONE),
((128, 96, 57), SPRUCE_PLANKS),
((128, 128, 128), STONE_BRICKS),
((150, 93, 68), TERRACOTTA),
((35, 86, 85), WARPED_PLANKS),
]
}

View File

@@ -1,6 +1,13 @@
/// Generates the coordinates for a line between two points using the Bresenham algorithm.
/// The result is a vector of 3D coordinates (x, y, z).
pub fn bresenham_line(x1: i32, y1: i32, z1: i32, x2: i32, y2: i32, z2: i32) -> Vec<(i32, i32, i32)> {
pub fn bresenham_line(
x1: i32,
y1: i32,
z1: i32,
x2: i32,
y2: i32,
z2: i32,
) -> Vec<(i32, i32, i32)> {
let mut points: Vec<(i32, i32, i32)> = Vec::new();
let dx: i32 = (x2 - x1).abs();

88
src/colors.rs Normal file
View File

@@ -0,0 +1,88 @@
pub type RGBTuple = (u8, u8, u8);
pub fn color_text_to_rgb_tuple(text: &str) -> Option<RGBTuple> {
if let Some(rgb) = full_hex_color_to_rgb_tuple(text) {
return Some(rgb);
}
if let Some(rgb) = short_hex_color_to_rgb_tuple(text) {
return Some(rgb);
}
if let Some(rgb) = color_name_to_rgb_tuple(text) {
return Some(rgb);
}
None
}
fn full_hex_color_to_rgb_tuple(text: &str) -> Option<RGBTuple> {
if text.len() != 7
|| !text.starts_with("#")
|| !text.chars().skip(1).all(|c: char| c.is_ascii_hexdigit())
{
return None;
}
let r: u8 = u8::from_str_radix(&text[1..3], 16).unwrap();
let g: u8 = u8::from_str_radix(&text[3..5], 16).unwrap();
let b: u8 = u8::from_str_radix(&text[5..7], 16).unwrap();
Some((r, g, b))
}
fn short_hex_color_to_rgb_tuple(text: &str) -> Option<RGBTuple> {
if text.len() != 4
|| !text.starts_with("#")
|| text.chars().skip(1).all(|c: char| c.is_ascii_hexdigit())
{
return None;
}
let r: u8 = u8::from_str_radix(&text[1..2], 16).unwrap();
let r: u8 = r | r << 4;
let g: u8 = u8::from_str_radix(&text[2..3], 16).unwrap();
let g: u8 = g | g << 4;
let b: u8 = u8::from_str_radix(&text[3..4], 16).unwrap();
let b: u8 = b | b << 4;
Some((r, g, b))
}
// https://wiki.openstreetmap.org/wiki/Key:colour
// https://wiki.openstreetmap.org/wiki/Key:roof:colour
fn color_name_to_rgb_tuple(text: &str) -> Option<RGBTuple> {
Some(match text {
"aqua" | "cyan" => (0, 255, 255),
"beige" => (187, 173, 142),
"black" => (0, 0, 0),
"blue" => (0, 0, 255),
"brown" => (128, 64, 0),
// darkgrey
"fuchsia" | "magenta" => (255, 0, 255),
"gray" | "grey" => (128, 128, 128),
"green" => (0, 128, 0),
// lightgrey
"lime" => (0, 255, 0),
"maroon" => (128, 0, 0),
"navy" => (0, 0, 128),
"olive" => (128, 128, 0),
"orange" => (255, 128, 0),
"purple" => (128, 0, 128),
"red" => (255, 0, 0),
"silver" => (192, 192, 192),
"teal" => (0, 128, 0),
"white" => (255, 255, 255),
"yellow" => (255, 255, 0),
_ => {
return None;
}
})
}
pub fn rgb_distance(from: &RGBTuple, to: &RGBTuple) -> u32 {
// i32 because .pow(2) returns the same data type as self and 255^2 wouldn't fit
let difference: (i32, i32, i32) = (
from.0 as i32 - to.0 as i32,
from.1 as i32 - to.1 as i32,
from.2 as i32 - to.2 as i32,
);
let distance: i32 = difference.0.pow(2) + difference.1.pow(2) + difference.2.pow(2);
distance as u32
}

View File

@@ -1,84 +1,103 @@
use colored::Colorize;
use crate::args::Args;
use crate::element_processing::{*};
use crate::block_definitions::{DIRT, GRASS_BLOCK, SNOW_BLOCK};
use crate::element_processing::*;
use crate::osm_parser::ProcessedElement;
use crate::progress::emit_gui_progress_update;
use crate::world_editor::WorldEditor;
use colored::Colorize;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::blocking::get;
use std::fs;
use std::io::Write;
use std::path::Path;
pub fn generate_world(elements: Vec<ProcessedElement>, args: &Args, scale_factor_x: f64, scale_factor_z: f64) {
println!("{} {}", "[3/5]".bold(), "Processing data...");
let region_template_path: &str = "region.template";
pub fn generate_world(
elements: Vec<ProcessedElement>,
args: &Args,
scale_factor_x: f64,
scale_factor_z: f64,
) -> Result<(), String> {
println!("{} Processing data...", "[3/5]".bold());
emit_gui_progress_update(10.0, "Processing data...");
let ground_level: i32 = args.ground_level;
let region_dir: String = format!("{}/region", args.path);
let ground_level: i32 = -62;
// Check if the region.template file exists, and download if necessary
if !Path::new(region_template_path).exists() {
let _ = download_region_template(region_template_path);
}
let mut editor: WorldEditor = WorldEditor::new(region_template_path, &region_dir, scale_factor_x, scale_factor_z, &args);
let mut editor: WorldEditor =
WorldEditor::new(&region_dir, scale_factor_x, scale_factor_z, args);
// Process data
let process_pb: ProgressBar = ProgressBar::new(elements.len() as u64);
let elements_count: usize = elements.len();
let process_pb: ProgressBar = ProgressBar::new(elements_count as u64);
process_pb.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:45.white/black}] {pos}/{len} elements ({eta}) {msg}")
.unwrap()
.progress_chars("█▓░"));
let progress_increment_prcs: f64 = 50.0 / elements_count as f64;
let mut current_progress_prcs: f64 = 10.0;
let mut last_emitted_progress: f64 = current_progress_prcs;
for element in &elements {
process_pb.inc(1);
current_progress_prcs += progress_increment_prcs;
if (current_progress_prcs - last_emitted_progress).abs() > 0.25 {
emit_gui_progress_update(current_progress_prcs, "");
last_emitted_progress = current_progress_prcs;
}
if args.debug {
process_pb.set_message(format!("(Element ID: {} / Type: {})", element.id, element.r#type));
process_pb.set_message(format!(
"(Element ID: {} / Type: {})",
element.id(),
element.kind()
));
} else {
process_pb.set_message("");
}
match element.r#type.as_str() {
"way" => {
if element.tags.contains_key("building") || element.tags.contains_key("building:part") || element.tags.contains_key("area:highway") {
buildings::generate_buildings(&mut editor, element, ground_level);
} else if element.tags.contains_key("highway") {
highways::generate_highways(&mut editor, element, ground_level);
} else if element.tags.contains_key("landuse") {
landuse::generate_landuse(&mut editor, element, ground_level);
} else if element.tags.contains_key("natural") {
natural::generate_natural(&mut editor, element, ground_level);
} else if element.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, element, ground_level);
} else if element.tags.contains_key("leisure") {
leisure::generate_leisure(&mut editor, element, ground_level);
} else if element.tags.contains_key("barrier") {
match element {
ProcessedElement::Way(way) => {
if way.tags.contains_key("building") || way.tags.contains_key("building:part") {
buildings::generate_buildings(&mut editor, way, ground_level, args);
} else if way.tags.contains_key("highway") {
highways::generate_highways(&mut editor, element, ground_level, args);
} else if way.tags.contains_key("landuse") {
landuse::generate_landuse(&mut editor, way, ground_level, args);
} else if way.tags.contains_key("natural") {
natural::generate_natural(&mut editor, element, ground_level, args);
} else if way.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, element, ground_level, args);
} else if way.tags.contains_key("leisure") {
leisure::generate_leisure(&mut editor, way, ground_level, args);
} else if way.tags.contains_key("barrier") {
barriers::generate_barriers(&mut editor, element, ground_level);
} else if element.tags.contains_key("waterway") {
waterways::generate_waterways(&mut editor, element, ground_level);
} else if element.tags.contains_key("bridge") {
bridges::generate_bridges(&mut editor, element, ground_level);
} else if element.tags.contains_key("railway") {
railways::generate_railways(&mut editor, element, ground_level);
} else if element.tags.get("service") == Some(&"siding".to_string()) {
highways::generate_siding(&mut editor, element, ground_level);
} else if way.tags.contains_key("waterway") {
waterways::generate_waterways(&mut editor, way, ground_level);
} else if way.tags.contains_key("bridge") {
bridges::generate_bridges(&mut editor, way, ground_level);
} else if way.tags.contains_key("railway") {
railways::generate_railways(&mut editor, way, ground_level);
} else if way.tags.get("service") == Some(&"siding".to_string()) {
highways::generate_siding(&mut editor, way, ground_level);
}
}
"node" => {
if element.tags.contains_key("door") || element.tags.contains_key("entrance") {
doors::generate_doors(&mut editor, element, ground_level);
} else if element.tags.contains_key("natural") && element.tags.get("natural") == Some(&"tree".to_string()) {
natural::generate_natural(&mut editor, element, ground_level);
} else if element.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, element, ground_level);
} else if element.tags.contains_key("barrier") {
ProcessedElement::Node(node) => {
if node.tags.contains_key("door") || node.tags.contains_key("entrance") {
doors::generate_doors(&mut editor, node, ground_level);
} else if node.tags.contains_key("natural")
&& node.tags.get("natural") == Some(&"tree".to_string())
{
natural::generate_natural(&mut editor, element, ground_level, args);
} else if node.tags.contains_key("amenity") {
amenities::generate_amenities(&mut editor, element, ground_level, args);
} else if node.tags.contains_key("barrier") {
barriers::generate_barriers(&mut editor, element, ground_level);
} else if element.tags.contains_key("highway") {
highways::generate_highways(&mut editor, element, ground_level);
} else if node.tags.contains_key("highway") {
highways::generate_highways(&mut editor, element, ground_level, args);
} else if node.tags.contains_key("tourism") {
tourisms::generate_tourisms(&mut editor, node, ground_level);
}
}
ProcessedElement::Relation(rel) => {
if rel.tags.contains_key("water") {
water_areas::generate_water_areas(&mut editor, rel, ground_level);
}
}
_ => {}
}
}
@@ -91,22 +110,39 @@ pub fn generate_world(elements: Vec<ProcessedElement>, args: &Args, scale_factor
let mut block_counter: u64 = 0;
println!("{} {}", "[4/5]".bold(), "Generating ground layer...");
println!("{} Generating ground layer...", "[4/5]".bold());
emit_gui_progress_update(60.0, "Generating ground layer...");
let ground_pb: ProgressBar = ProgressBar::new(total_blocks);
ground_pb.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:45}] {pos}/{len} blocks ({eta})")
.unwrap()
.progress_chars("█▓░"));
ground_pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:45}] {pos}/{len} blocks ({eta})")
.unwrap()
.progress_chars("█▓░"),
);
let mut gui_progress_grnd: f64 = 60.0;
let mut last_emitted_progress: f64 = gui_progress_grnd;
let total_iterations_grnd: f64 = (scale_factor_x + 1.0) * (scale_factor_z + 1.0);
let progress_increment_grnd: f64 = 30.0 / total_iterations_grnd;
let groundlayer_block = if args.winter { SNOW_BLOCK } else { GRASS_BLOCK };
for x in 0..=(scale_factor_x as i32) {
for z in 0..=(scale_factor_z as i32) {
editor.set_block(&crate::block_definitions::GRASS_BLOCK, x, ground_level, z, None, None);
editor.set_block(&crate::block_definitions::DIRT, x, ground_level - 1, z, None, None);
editor.set_block(groundlayer_block, x, ground_level, z, None, None);
editor.set_block(DIRT, x, ground_level - 1, z, None, None);
block_counter += 1;
if block_counter % batch_size == 0 {
ground_pb.inc(batch_size);
}
gui_progress_grnd += progress_increment_grnd;
if (gui_progress_grnd - last_emitted_progress).abs() > 0.25 {
emit_gui_progress_update(gui_progress_grnd, "");
last_emitted_progress = gui_progress_grnd;
}
}
}
@@ -116,22 +152,7 @@ pub fn generate_world(elements: Vec<ProcessedElement>, args: &Args, scale_factor
// Save world
editor.save();
println!("{}", "Done! World generation complete.".green().bold());
}
/// Downloads the region template file from a remote URL and saves it locally.
fn download_region_template(file_path: &str) -> Result<(), Box<dyn std::error::Error>> {
let url = "https://github.com/louis-e/arnis/raw/refs/heads/main/region.template";
// Download the file
let response = get(url)?;
if !response.status().is_success() {
return Err(format!("Failed to download file: HTTP {}", response.status()).into());
}
// Write the file to the specified path
let mut file = fs::File::create(file_path)?;
file.write_all(&response.bytes()?)?;
emit_gui_progress_update(100.0, "Done! World generation completed.");
println!("{}", "Done! World generation completed.".green().bold());
Ok(())
}

View File

@@ -1,61 +1,75 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedElement;
use crate::world_editor::WorldEditor;
pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_amenities(
editor: &mut WorldEditor,
element: &ProcessedElement,
ground_level: i32,
args: &Args,
) {
// Skip if 'layer' or 'level' is negative in the tags
if let Some(layer) = element.tags.get("layer") {
if let Some(layer) = element.tags().get("layer") {
if layer.parse::<i32>().unwrap_or(0) < 0 {
return;
}
}
if let Some(level) = element.tags.get("level") {
if let Some(level) = element.tags().get("level") {
if level.parse::<i32>().unwrap_or(0) < 0 {
return;
}
}
if let Some(amenity_type) = element.tags.get("amenity") {
let first_node: Option<&(i32, i32)> = element.nodes.first();
if let Some(amenity_type) = element.tags().get("amenity") {
let first_node: Option<(i32, i32)> = element
.nodes()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.next();
match amenity_type.as_str() {
"waste_disposal" | "waste_basket" => {
// Place a cauldron for waste disposal or waste basket
if let Some(&(x, z)) = first_node {
editor.set_block(&CAULDRON, x, ground_level + 1, z, None, None);
if let Some((x, z)) = first_node {
editor.set_block(CAULDRON, x, ground_level + 1, z, None, None);
}
return;
}
"vending_machine" | "atm" => {
if let Some(&(x, z)) = first_node {
editor.set_block(&IRON_BLOCK, x, ground_level + 1, z, None, None);
editor.set_block(&IRON_BLOCK, x, ground_level + 2, z, None, None);
if let Some((x, z)) = first_node {
editor.set_block(IRON_BLOCK, x, ground_level + 1, z, None, None);
editor.set_block(IRON_BLOCK, x, ground_level + 2, z, None, None);
}
return;
}
"bicycle_parking" => {
let ground_block: &once_cell::sync::Lazy<Block> = &OAK_PLANKS;
let roof_block: &once_cell::sync::Lazy<Block> = &STONE_BLOCK_SLAB;
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let ground_block: Block = OAK_PLANKS;
let roof_block: Block = STONE_BLOCK_SLAB;
let polygon_coords: Vec<(i32, i32)> = element
.nodes()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let floor_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
// Fill the floor area
for (x, z) in floor_area.iter() {
editor.set_block(ground_block, *x, ground_level, *z, None, None);
}
// Place fences and roof slabs at each corner node directly
for &(x, z) in &element.nodes {
for node in element.nodes() {
let x: i32 = node.x;
let z: i32 = node.z;
for y in 1..=4 {
editor.set_block(ground_block, x, ground_level, z, None, None);
editor.set_block(&OAK_FENCE, x, ground_level + y, z, None, None);
editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None);
}
editor.set_block(roof_block, x, ground_level + 5, z, None, None);
}
// Flood fill the roof area
let roof_height: i32 = ground_level + 5;
for (x, z) in floor_area.iter() {
@@ -64,17 +78,17 @@ pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement,
}
"bench" => {
// Place a bench
if let Some(&(x, z)) = first_node {
editor.set_block(&SMOOTH_STONE, x, ground_level + 1, z, None, None);
editor.set_block(&OAK_LOG, x + 1, ground_level + 1, z, None, None);
editor.set_block(&OAK_LOG, x - 1, ground_level + 1, z, None, None);
if let Some((x, z)) = first_node {
editor.set_block(SMOOTH_STONE, x, ground_level + 1, z, None, None);
editor.set_block(OAK_LOG, x + 1, ground_level + 1, z, None, None);
editor.set_block(OAK_LOG, x - 1, ground_level + 1, z, None, None);
}
}
"vending" => {
// Place vending machine blocks
if let Some(&(x, z)) = first_node {
editor.set_block(&IRON_BLOCK, x, ground_level + 1, z, None, None);
editor.set_block(&IRON_BLOCK, x, ground_level + 2, z, None, None);
if let Some((x, z)) = first_node {
editor.set_block(IRON_BLOCK, x, ground_level + 1, z, None, None);
editor.set_block(IRON_BLOCK, x, ground_level + 2, z, None, None);
}
}
"parking" | "fountain" => {
@@ -83,50 +97,82 @@ pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement,
let mut corner_addup: (i32, i32, i32) = (0, 0, 0);
let mut current_amenity: Vec<(i32, i32)> = vec![];
let block_type: &once_cell::sync::Lazy<Block> = match amenity_type.as_str() {
"fountain" => &WATER,
"parking" => &GRAY_CONCRETE,
_ => &GRAY_CONCRETE,
let block_type = match amenity_type.as_str() {
"fountain" => WATER,
"parking" => GRAY_CONCRETE,
_ => GRAY_CONCRETE,
};
for node in element.nodes() {
let x: i32 = node.x;
let z: i32 = node.z;
for &node in &element.nodes {
if let Some(prev) = previous_node {
// Create borders for fountain or parking area
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level, prev.1, node.0, ground_level, node.1);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
for (bx, _, bz) in bresenham_points {
editor.set_block(block_type, bx, ground_level, bz, Some(&[&BLACK_CONCRETE]), None);
editor.set_block(
block_type,
bx,
ground_level,
bz,
Some(&[BLACK_CONCRETE]),
None,
);
// Decorative border around fountains
if amenity_type == "fountain" {
for dx in [-1, 0, 1].iter() {
for dz in [-1, 0, 1].iter() {
if (*dx, *dz) != (0, 0) {
editor.set_block(&LIGHT_GRAY_CONCRETE, bx + dx, ground_level, bz + dz, None, None);
editor.set_block(
LIGHT_GRAY_CONCRETE,
bx + dx,
ground_level,
bz + dz,
None,
None,
);
}
}
}
}
current_amenity.push((node.0, node.1));
corner_addup.0 += node.0;
corner_addup.1 += node.1;
current_amenity.push((node.x, node.z));
corner_addup.0 += node.x;
corner_addup.1 += node.z;
corner_addup.2 += 1;
}
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// Flood-fill the interior area for parking or fountains
if corner_addup.2 > 0 {
let polygon_coords: Vec<(i32, i32)> = current_amenity.iter().copied().collect();
let flood_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = current_amenity.to_vec();
let flood_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
for (x, z) in flood_area {
editor.set_block(block_type, x, ground_level, z, Some(&[&BLACK_CONCRETE, &GRAY_CONCRETE]), None);
editor.set_block(
block_type,
x,
ground_level,
z,
Some(&[BLACK_CONCRETE, GRAY_CONCRETE]),
None,
);
// Add parking spot markings
if amenity_type == "parking" && (x + z) % 8 == 0 && (x * z) % 32 != 0 {
editor.set_block(&LIGHT_GRAY_CONCRETE, x, ground_level, z, Some(&[&BLACK_CONCRETE, &GRAY_CONCRETE]), None);
editor.set_block(
LIGHT_GRAY_CONCRETE,
x,
ground_level,
z,
Some(&[BLACK_CONCRETE, GRAY_CONCRETE]),
None,
);
}
}
}

View File

@@ -1,41 +1,56 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::osm_parser::ProcessedElement;
use crate::world_editor::WorldEditor;
pub fn generate_barriers(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
if let Some(barrier_type) = element.tags.get("barrier") {
if let Some(barrier_type) = element.tags().get("barrier") {
if barrier_type == "bollard" {
if let Some(&(x, z)) = element.nodes.first() {
editor.set_block(&COBBLESTONE_WALL, x, ground_level + 1, z, None, None); // Place bollard
if let ProcessedElement::Node(node) = element {
editor.set_block(
COBBLESTONE_WALL,
node.x,
ground_level + 1,
node.z,
None,
None,
);
// Place bollard
}
} else {
} else if let ProcessedElement::Way(way) = element {
// Determine wall height
let wall_height: i32 = element
.tags
.tags()
.get("height")
.and_then(|height: &String| height.parse::<f32>().ok())
.map(|height: f32| f32::min(3.0, height).round() as i32)
.unwrap_or(2); // Default height is 2 if not specified or invalid
// Process nodes to create the barrier wall
for i in 1..element.nodes.len() {
let (x1, z1) = element.nodes[i - 1];
let (x2, z2) = element.nodes[i];
for i in 1..way.nodes.len() {
let prev: &crate::osm_parser::ProcessedNode = &way.nodes[i - 1];
let x1: i32 = prev.x;
let z1: i32 = prev.z;
let cur: &crate::osm_parser::ProcessedNode = &way.nodes[i];
let x2: i32 = cur.x;
let z2: i32 = cur.z;
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
for (bx, _, bz) in bresenham_points {
// Build the barrier wall to the specified height
for y in (ground_level + 1)..=(ground_level + wall_height) {
editor.set_block(&COBBLESTONE_WALL, bx, y, bz, None, None); // Barrier wall
editor.set_block(COBBLESTONE_WALL, bx, y, bz, None, None);
// Barrier wall
}
// Add an optional top to the barrier if the height is more than 1
if wall_height > 1 {
editor.set_block(
&STONE_BRICK_SLAB,
STONE_BRICK_SLAB,
bx,
ground_level + wall_height + 1,
bz,

View File

@@ -1,9 +1,9 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::osm_parser::ProcessedWay;
use crate::world_editor::WorldEditor;
pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) {
if let Some(_bridge_type) = element.tags.get("bridge") {
let bridge_height: i32 = element
.tags
@@ -15,39 +15,46 @@ pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedElement, gr
let total_steps: usize = element
.nodes
.windows(2)
.map(|nodes: &[(i32, i32)]| {
let (x1, z1) = nodes[0];
let (x2, z2) = nodes[1];
.map(|nodes: &[crate::osm_parser::ProcessedNode]| {
let x1: i32 = nodes[0].x;
let z1: i32 = nodes[0].z;
let x2: i32 = nodes[1].x;
let z2: i32 = nodes[1].z;
bresenham_line(x1, ground_level, z1, x2, ground_level, z2).len()
})
.sum();
let half_steps = total_steps / 2; // Calculate midpoint for descending after rising
let mut current_step = 0;
let half_steps: usize = total_steps / 2; // Calculate midpoint for descending after rising
let mut current_step: usize = 0;
for i in 1..element.nodes.len() {
let (x1, z1) = element.nodes[i - 1];
let (x2, z2) = element.nodes[i];
let prev: &crate::osm_parser::ProcessedNode = &element.nodes[i - 1];
let x1: i32 = prev.x;
let z1: i32 = prev.z;
let cur: &crate::osm_parser::ProcessedNode = &element.nodes[i];
let x2: i32 = cur.x;
let z2: i32 = cur.z;
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
for (bx, _, bz) in bresenham_points {
// Calculate the current height of the bridge
let current_height: i32 = if current_step <= half_steps {
ground_level + bridge_height + current_step as i32 / 5 // Rise for the first half
} else {
ground_level
+ bridge_height
+ (half_steps as i32 / 5)
ground_level + bridge_height + (half_steps as i32 / 5)
- ((current_step - half_steps) as i32 / 5) // Descend for the second half
};
// Set bridge blocks
editor.set_block(&LIGHT_GRAY_CONCRETE, bx, current_height, bz, None, None);
editor.set_block(LIGHT_GRAY_CONCRETE, bx, current_height, bz, None, None);
for (offset_x, offset_z) in &[(-1, -1), (1, -1), (1, 1), (-1, 1)] {
editor.set_block(
&LIGHT_GRAY_CONCRETE,
LIGHT_GRAY_CONCRETE,
bx + offset_x,
current_height,
bz + offset_z,

View File

@@ -1,12 +1,20 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::colors::{color_text_to_rgb_tuple, rgb_distance, RGBTuple};
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedWay;
use crate::world_editor::WorldEditor;
use rand::Rng;
use std::collections::HashSet;
use std::time::Duration;
pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_buildings(
editor: &mut WorldEditor,
element: &ProcessedWay,
ground_level: i32,
args: &Args,
) {
let mut previous_node: Option<(i32, i32)> = None;
let mut corner_addup: (i32, i32, i32) = (0, 0, 0);
let mut current_building: Vec<(i32, i32)> = vec![];
@@ -17,10 +25,28 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
let variation_index_wall: usize = rng.gen_range(0..building_wall_variations().len());
let variation_index_floor: usize = rng.gen_range(0..building_floor_variations().len());
let corner_block: &&once_cell::sync::Lazy<Block> = &building_corner_variations()[variation_index_corner];
let wall_block: &&once_cell::sync::Lazy<Block> = &building_wall_variations()[variation_index_wall];
let floor_block: &&once_cell::sync::Lazy<Block> = &building_floor_variations()[variation_index_floor];
let window_block: &once_cell::sync::Lazy<Block> = &WHITE_STAINED_GLASS;
let corner_block: Block = building_corner_variations()[variation_index_corner];
let wall_block: Block = element
.tags
.get("building:colour")
.and_then(|building_colour: &String| {
color_text_to_rgb_tuple(building_colour).map(|rgb: (u8, u8, u8)| {
find_nearest_block_in_color_map(&rgb, building_wall_color_map())
})
})
.flatten()
.unwrap_or_else(|| building_wall_variations()[variation_index_wall]);
let floor_block: Block = element
.tags
.get("roof:colour")
.and_then(|roof_colour: &String| {
color_text_to_rgb_tuple(roof_colour).map(|rgb: (u8, u8, u8)| {
find_nearest_block_in_color_map(&rgb, building_floor_color_map())
})
})
.flatten()
.unwrap_or_else(|| building_floor_variations()[variation_index_floor]);
let window_block: Block = WHITE_STAINED_GLASS;
// Set to store processed flood fill points
let mut processed_points: HashSet<(i32, i32)> = HashSet::new();
@@ -32,7 +58,7 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
return;
}
}
if let Some(level) = element.tags.get("level") {
if level.parse::<i32>().unwrap_or(0) < 0 {
return;
@@ -42,14 +68,14 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
// Determine building height from tags
if let Some(levels_str) = element.tags.get("building:levels") {
if let Ok(levels) = levels_str.parse::<i32>() {
if levels >= 1 && (levels * 3) > building_height {
building_height = levels * 3;
if levels >= 1 && (levels * 4 + 2) > building_height {
building_height = levels * 4 + 2;
}
}
}
if let Some(height_str) = element.tags.get("height") {
if let Ok(height) = height_str.parse::<f64>() {
if let Ok(height) = height_str.trim_end_matches("m").trim().parse::<f64>() {
building_height = height.round() as i32;
}
}
@@ -59,66 +85,83 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
building_height = 2;
} else if building_type == "shed" {
building_height = 2;
if element.tags.contains_key("bicycle_parking") {
let ground_block: &once_cell::sync::Lazy<Block> = &OAK_PLANKS;
let roof_block: &once_cell::sync::Lazy<Block> = &STONE_BLOCK_SLAB;
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let ground_block: Block = OAK_PLANKS;
let roof_block: Block = STONE_BLOCK_SLAB;
let polygon_coords: Vec<(i32, i32)> = element
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let floor_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
// Fill the floor area
for (x, z) in floor_area.iter() {
editor.set_block(ground_block, *x, ground_level, *z, None, None);
}
// Place fences and roof slabs at each corner node directly
for &(x, z) in &element.nodes {
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
for y in 1..=4 {
editor.set_block(ground_block, x, ground_level, z, None, None);
editor.set_block(&OAK_FENCE, x, ground_level + y, z, None, None);
editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None);
}
editor.set_block(roof_block, x, ground_level + 5, z, None, None);
}
// Flood fill the roof area
let roof_height: i32 = ground_level + 5;
for (x, z) in floor_area.iter() {
editor.set_block(roof_block, *x, roof_height, *z, None, None);
}
return;
}
} else if building_type == "roof" {
let roof_height = ground_level + 5;
let roof_height: i32 = ground_level + 5;
// Iterate through the nodes to create the roof edges using Bresenham's line algorithm
for &node in &element.nodes {
let (x, z) = node;
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
if let Some(prev) = previous_node {
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, roof_height, prev.1, x, roof_height, z);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, roof_height, prev.1, x, roof_height, z);
for (bx, _, bz) in bresenham_points {
editor.set_block(&STONE_BRICK_SLAB, bx, roof_height, bz, None, None); // Set roof block at edge
editor.set_block(STONE_BRICK_SLAB, bx, roof_height, bz, None, None);
// Set roof block at edge
}
}
for y in (ground_level + 1)..=(roof_height - 1) {
editor.set_block(&COBBLESTONE_WALL, x, y, z, None, None);
editor.set_block(COBBLESTONE_WALL, x, y, z, None, None);
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// Use flood-fill to fill the interior of the roof
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let roof_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2); // Use flood-fill to determine the area
let polygon_coords: Vec<(i32, i32)> = element
.nodes
.iter()
.map(|node: &crate::osm_parser::ProcessedNode| (node.x, node.z))
.collect();
let roof_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref()); // Use flood-fill to determine the area
// Fill the interior of the roof with STONE_BRICK_SLAB
for (x, z) in roof_area.iter() {
editor.set_block(&STONE_BRICK_SLAB, *x, roof_height, *z, None, None); // Set roof block
editor.set_block(STONE_BRICK_SLAB, *x, roof_height, *z, None, None);
// Set roof block
}
return;
} else if building_type == "apartments" {
// If building has no height attribute, assign a defined height
@@ -131,44 +174,72 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
building_height = 23
}
} else if building_type == "bridge" {
generate_bridge(editor, element, ground_level);
generate_bridge(editor, element, ground_level, args.timeout.as_ref());
return;
}
}
// Process nodes to create walls and corners
for &node in &element.nodes {
let (x, z) = node;
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
if let Some(prev) = previous_node {
// Calculate walls and corners using Bresenham line
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
for (bx, _, bz) in bresenham_points {
for h in (ground_level + 1)..=(ground_level + building_height) {
if (bx, bz) == element.nodes[0] {
if element.nodes[0].x == bx && element.nodes[0].x == bz {
editor.set_block(corner_block, bx, h, bz, None, None); // Corner block
} else {
// Add windows to the walls at intervals
if h > ground_level + 1 && h % 4 != 0 && (bx + bz) % 6 < 3 {
editor.set_block(window_block, bx, h, bz, None, None); // Window block
editor.set_block(window_block, bx, h, bz, None, None);
// Window block
} else {
editor.set_block(wall_block, bx, h, bz, None, None); // Wall block
editor.set_block(wall_block, bx, h, bz, None, None);
// Wall block
}
}
}
editor.set_block(&COBBLESTONE, bx, ground_level + building_height + 1, bz, None, None); // Ceiling cobblestone
// Ceiling cobblestone
editor.set_block(
COBBLESTONE,
bx,
ground_level + building_height + 1,
bz,
None,
None,
);
if args.winter {
editor.set_block(
SNOW_LAYER,
x,
ground_level + building_height + 2,
z,
None,
None,
);
}
current_building.push((bx, bz));
corner_addup = (corner_addup.0 + bx, corner_addup.1 + bz, corner_addup.2 + 1);
}
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// Flood-fill interior with floor variation
if corner_addup != (0, 0, 0) {
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = element
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, args.timeout.as_ref());
for (x, z) in floor_area {
if processed_points.insert((x, z)) {
@@ -176,57 +247,97 @@ pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement,
// Set level ceilings if height > 4
if building_height > 4 {
for h in (ground_level + 4..ground_level + building_height).step_by(4) {
for h in (ground_level + 2 + 4..ground_level + building_height).step_by(4) {
if x % 6 == 0 && z % 6 == 0 {
editor.set_block(&GLOWSTONE, x, h, z, None, None); // Light fixtures
editor.set_block(GLOWSTONE, x, h, z, None, None); // Light fixtures
} else {
editor.set_block(floor_block, x, h, z, None, None);
}
}
} else if x % 6 == 0 && z % 6 == 0 {
editor.set_block(&GLOWSTONE, x, ground_level + building_height, z, None, None); // Light fixtures
editor.set_block(GLOWSTONE, x, ground_level + building_height, z, None, None);
// Light fixtures
}
// Set the house ceiling
editor.set_block(floor_block, x, ground_level + building_height + 1, z, None, None);
editor.set_block(
floor_block,
x,
ground_level + building_height + 1,
z,
None,
None,
);
if args.winter {
editor.set_block(
SNOW_LAYER,
x,
ground_level + building_height + 2,
z,
None,
None,
);
}
}
}
}
}
fn find_nearest_block_in_color_map(
rgb: &RGBTuple,
color_map: Vec<(RGBTuple, Block)>,
) -> Option<Block> {
color_map
.into_iter()
.min_by_key(|(entry_rgb, _)| rgb_distance(entry_rgb, rgb))
.map(|(_, block)| block)
}
/// Generates a bridge structure, paying attention to the "level" tag.
fn generate_bridge(editor: &mut WorldEditor, element: &ProcessedElement, base_level: i32) {
fn generate_bridge(
editor: &mut WorldEditor,
element: &ProcessedWay,
base_level: i32,
floodfill_timeout: Option<&Duration>,
) {
// Calculate the bridge level
let mut bridge_level = base_level;
let mut bridge_level: i32 = base_level;
if let Some(level_str) = element.tags.get("level") {
if let Ok(level) = level_str.parse::<i32>() {
bridge_level += (level * 3) + 1; // Adjust height by levels
}
}
let floor_block: &once_cell::sync::Lazy<Block> = &STONE;
let railing_block: &once_cell::sync::Lazy<Block> = &STONE_BRICKS;
let floor_block: Block = STONE;
let railing_block: Block = STONE_BRICKS;
// Process the nodes to create bridge pathways and railings
let mut previous_node: Option<(i32, i32)> = None;
for &node in &element.nodes {
let (x, z) = node;
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
// Create bridge path using Bresenham's line
if let Some(prev) = previous_node {
let bridge_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, bridge_level, prev.1, x, bridge_level, z);
let bridge_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, bridge_level, prev.1, x, bridge_level, z);
for (bx, by, bz) in bridge_points {
editor.set_block(railing_block, bx, by + 1, bz, None, None);
editor.set_block(railing_block, bx, by, bz, None, None);
}
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// Flood fill the area between the bridge path nodes
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let bridge_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = element
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let bridge_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, floodfill_timeout);
for (x, z) in bridge_area {
editor.set_block(floor_block, x, bridge_level, z, None, None);
}
}
}

View File

@@ -1,8 +1,8 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::block_definitions::*;
use crate::osm_parser::ProcessedNode;
use crate::world_editor::WorldEditor;
pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedNode, ground_level: i32) {
// Check if the element is a door or entrance
if element.tags.contains_key("door") || element.tags.contains_key("entrance") {
// Check for the "level" tag and skip doors that are not at ground level
@@ -14,12 +14,12 @@ pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedElement, grou
}
}
// Process the first node of the door/entrance element
if let Some(&(x, z)) = element.nodes.first() {
// Set the ground block and the door blocks
editor.set_block(&GRAY_CONCRETE, x, ground_level, z, None, None);
editor.set_block(&DARK_OAK_DOOR_LOWER, x, ground_level + 1, z, None, None);
editor.set_block(&DARK_OAK_DOOR_UPPER, x, ground_level + 2, z, None, None);
}
let x: i32 = element.x;
let z: i32 = element.z;
// Set the ground block and the door blocks
editor.set_block(GRAY_CONCRETE, x, ground_level, z, None, None);
editor.set_block(DARK_OAK_DOOR_LOWER, x, ground_level + 1, z, None, None);
editor.set_block(DARK_OAK_DOOR_UPPER, x, ground_level + 2, z, None, None);
}
}

View File

@@ -1,89 +1,120 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::floodfill::flood_fill_area; // Assuming you have a flood fill function for area filling
use crate::floodfill::flood_fill_area;
use crate::osm_parser::{ProcessedElement, ProcessedWay};
use crate::world_editor::WorldEditor;
pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
if let Some(highway_type) = element.tags.get("highway") {
pub fn generate_highways(
editor: &mut WorldEditor,
element: &ProcessedElement,
ground_level: i32,
args: &Args,
) {
if let Some(highway_type) = element.tags().get("highway") {
if highway_type == "street_lamp" {
// Handle street lamps
if let Some(first_node) = element.nodes.first() {
let (x, z) = *first_node;
if let ProcessedElement::Node(first_node) = element {
let x: i32 = first_node.x;
let z: i32 = first_node.z;
for y in 1..=4 {
editor.set_block(&OAK_FENCE, x, ground_level + y, z, None, None);
editor.set_block(OAK_FENCE, x, ground_level + y, z, None, None);
}
editor.set_block(&GLOWSTONE, x, ground_level + 5, z, None, None);
editor.set_block(GLOWSTONE, x, ground_level + 5, z, None, None);
}
} else if highway_type == "crossing" {
// Handle traffic signals for crossings
if let Some(crossing_type) = element.tags.get("crossing") {
if let Some(crossing_type) = element.tags().get("crossing") {
if crossing_type == "traffic_signals" {
if let Some(first_node) = element.nodes.first() {
let (x, z) = *first_node;
if let ProcessedElement::Node(node) = element {
let x: i32 = node.x;
let z: i32 = node.z;
for y in 1..=3 {
editor.set_block(&COBBLESTONE_WALL, x, ground_level + y, z, None, None);
editor.set_block(COBBLESTONE_WALL, x, ground_level + y, z, None, None);
}
editor.set_block(&GREEN_WOOL, x, ground_level + 4, z, None, None);
editor.set_block(&YELLOW_WOOL, x, ground_level + 5, z, None, None);
editor.set_block(&RED_WOOL, x, ground_level + 6, z, None, None);
editor.set_block(GREEN_WOOL, x, ground_level + 4, z, None, None);
editor.set_block(YELLOW_WOOL, x, ground_level + 5, z, None, None);
editor.set_block(RED_WOOL, x, ground_level + 6, z, None, None);
if args.winter {
editor.set_block(SNOW_LAYER, x, ground_level + 7, z, None, None);
}
}
}
}
} else if highway_type == "bus_stop" {
// Handle bus stops
if let Some(first_node) = element.nodes.first() {
let (x, z) = *first_node;
if let ProcessedElement::Node(node) = element {
let x: i32 = node.x;
let z: i32 = node.z;
for y in 1..=3 {
editor.set_block(&COBBLESTONE_WALL, x, ground_level + y, z, None, None);
editor.set_block(COBBLESTONE_WALL, x, ground_level + y, z, None, None);
}
editor.set_block(&WHITE_WOOL, x, ground_level + 4, z, None, None);
editor.set_block(&WHITE_WOOL, x + 1, ground_level + 4, z, None, None);
editor.set_block(WHITE_WOOL, x, ground_level + 4, z, None, None);
editor.set_block(WHITE_WOOL, x + 1, ground_level + 4, z, None, None);
}
} else if element.tags.get("area").map_or(false, |v| v == "yes") {
} else if element
.tags()
.get("area")
.map_or(false, |v: &String| v == "yes")
{
let ProcessedElement::Way(way) = element else {
return;
};
// Handle areas like pedestrian plazas
let mut surface_block: &once_cell::sync::Lazy<Block> = &STONE; // Default block
let mut surface_block: Block = STONE; // Default block
// Determine the block type based on the 'surface' tag
if let Some(surface) = element.tags.get("surface") {
if let Some(surface) = element.tags().get("surface") {
surface_block = match surface.as_str() {
"paving_stones" | "sett" => &STONE_BRICKS,
"bricks" => &BRICK,
"wood" => &OAK_PLANKS,
"asphalt" => &BLACK_CONCRETE,
"gravel" | "fine_gravel" => &GRAVEL,
"grass" => &GRASS_BLOCK,
"dirt" => &DIRT,
"sand" => &SAND,
"concrete" => &LIGHT_GRAY_CONCRETE,
_ => &STONE, // Default to stone for unknown surfaces
"paving_stones" | "sett" => STONE_BRICKS,
"bricks" => BRICK,
"wood" => OAK_PLANKS,
"asphalt" => BLACK_CONCRETE,
"gravel" | "fine_gravel" => GRAVEL,
"grass" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"dirt" => DIRT,
"sand" => SAND,
"concrete" => LIGHT_GRAY_CONCRETE,
_ => STONE, // Default to stone for unknown surfaces
};
}
// Fill the area using flood fill or by iterating through the nodes
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let filled_area = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = way
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let filled_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
for (x, z) in filled_area {
editor.set_block(surface_block, x, ground_level, z, None, None);
}
} else {
let mut previous_node: Option<(i32, i32)> = None;
let mut block_type: &once_cell::sync::Lazy<Block> = &BLACK_CONCRETE;
let mut block_type = BLACK_CONCRETE;
let mut block_range: i32 = 2;
let mut add_stripe = false; // Flag for adding stripes
let mut add_stripe = false;
// Skip if 'layer' or 'level' is negative in the tags
if let Some(layer) = element.tags.get("layer") {
if let Some(layer) = element.tags().get("layer") {
if layer.parse::<i32>().unwrap_or(0) < 0 {
return;
}
}
if let Some(level) = element.tags.get("level") {
if let Some(level) = element.tags().get("level") {
if level.parse::<i32>().unwrap_or(0) < 0 {
return;
}
@@ -92,11 +123,11 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
// Determine block type and range based on highway type
match highway_type.as_str() {
"footway" | "pedestrian" => {
block_type = &GRAY_CONCRETE;
block_type = GRAY_CONCRETE;
block_range = 1;
}
"path" => {
block_type = &LIGHT_GRAY_CONCRETE;
block_type = LIGHT_GRAY_CONCRETE;
block_range = 1;
}
"motorway" | "primary" => {
@@ -107,11 +138,11 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
block_range = 1;
}
"service" => {
block_type = &GRAY_CONCRETE;
block_type = GRAY_CONCRETE;
block_range = 2;
}
_ => {
if let Some(lanes) = element.tags.get("lanes") {
if let Some(lanes) = element.tags().get("lanes") {
if lanes == "2" {
block_range = 3;
add_stripe = true;
@@ -123,20 +154,25 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
}
}
let ProcessedElement::Way(way) = element else {
return;
};
// Iterate over nodes to create the highway
for &node in &element.nodes {
for node in &way.nodes {
if let Some(prev) = previous_node {
let (x1, z1) = prev;
let (x2, z2) = node;
let x2: i32 = node.x;
let z2: i32 = node.z;
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
// Variables to manage dashed line pattern
let mut stripe_length = 0;
let dash_length = 5; // Length of the solid part of the stripe
let gap_length = 5; // Length of the gap part of the stripe
let mut stripe_length: i32 = 0;
let dash_length: i32 = 5; // Length of the solid part of the stripe
let gap_length: i32 = 5; // Length of the gap part of the stripe
for (x, _, z) in bresenham_points {
// Draw the road surface for the entire width
@@ -147,22 +183,23 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
// Zebra crossing logic
if highway_type == "footway"
&& element.tags.get("footway") == Some(&"crossing".to_string())
&& element.tags().get("footway")
== Some(&"crossing".to_string())
{
let is_horizontal: bool = (x2 - x1).abs() >= (z2 - z1).abs();
if is_horizontal {
if set_x % 2 < 1 {
editor.set_block(
&WHITE_CONCRETE,
WHITE_CONCRETE,
set_x,
ground_level,
set_z,
Some(&[&BLACK_CONCRETE]),
Some(&[BLACK_CONCRETE]),
None,
);
} else {
editor.set_block(
&BLACK_CONCRETE,
BLACK_CONCRETE,
set_x,
ground_level,
set_z,
@@ -170,26 +207,24 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
None,
);
}
} else if set_z % 2 < 1 {
editor.set_block(
WHITE_CONCRETE,
set_x,
ground_level,
set_z,
Some(&[BLACK_CONCRETE]),
None,
);
} else {
if set_z % 2 < 1 {
editor.set_block(
&WHITE_CONCRETE,
set_x,
ground_level,
set_z,
Some(&[&BLACK_CONCRETE]),
None,
);
} else {
editor.set_block(
&BLACK_CONCRETE,
set_x,
ground_level,
set_z,
None,
None,
);
}
editor.set_block(
BLACK_CONCRETE,
set_x,
ground_level,
set_z,
None,
None,
);
}
} else {
editor.set_block(
@@ -198,7 +233,7 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
ground_level,
set_z,
None,
Some(&[&BLACK_CONCRETE, &WHITE_CONCRETE]),
Some(&[BLACK_CONCRETE, WHITE_CONCRETE]),
);
}
}
@@ -210,11 +245,11 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
let stripe_x: i32 = x;
let stripe_z: i32 = z;
editor.set_block(
&WHITE_CONCRETE,
WHITE_CONCRETE,
stripe_x,
ground_level,
stripe_z,
Some(&[&BLACK_CONCRETE]),
Some(&[BLACK_CONCRETE]),
None,
);
}
@@ -227,30 +262,38 @@ pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, g
}
}
}
previous_node = Some(node);
previous_node = Some((node.x, node.z));
}
}
}
}
/// Generates a siding using stone brick slabs
pub fn generate_siding(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_siding(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) {
let mut previous_node: Option<(i32, i32)> = None;
let siding_block: &once_cell::sync::Lazy<Block> = &STONE_BRICK_SLAB;
let siding_block: Block = STONE_BRICK_SLAB;
for &node in &element.nodes {
let (x, z) = node;
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
// Draw the siding using Bresenham's line algorithm between nodes
if let Some(prev) = previous_node {
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level + 1, prev.1, x, ground_level + 1, z);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level + 1, prev.1, x, ground_level + 1, z);
for (bx, by, bz) in bresenham_points {
if !editor.check_for_block(bx, by - 1, bz, None, Some(&[&BLACK_CONCRETE, &WHITE_CONCRETE])) {
if !editor.check_for_block(
bx,
by - 1,
bz,
None,
Some(&[BLACK_CONCRETE, WHITE_CONCRETE]),
) {
editor.set_block(siding_block, bx, by, bz, None, None);
}
}
}
previous_node = Some(node);
previous_node = Some((x, z));
}
}

View File

@@ -1,12 +1,18 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::floodfill::flood_fill_area;
use crate::element_processing::tree::create_tree;
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedWay;
use crate::world_editor::WorldEditor;
use rand::Rng;
pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_landuse(
editor: &mut WorldEditor,
element: &ProcessedWay,
ground_level: i32,
args: &Args,
) {
let mut previous_node: Option<(i32, i32)> = None;
let mut corner_addup: (i32, i32, i32) = (0, 0, 0);
let mut current_landuse: Vec<(i32, i32)> = vec![];
@@ -15,45 +21,67 @@ pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, gr
let binding: String = "".to_string();
let landuse_tag: &String = element.tags.get("landuse").unwrap_or(&binding);
let block_type: &once_cell::sync::Lazy<Block> = match landuse_tag.as_str() {
"greenfield" | "meadow" | "grass" => &GRASS_BLOCK,
"farmland" => &FARMLAND,
"forest" => &GRASS_BLOCK,
"cemetery" => &PODZOL,
"beach" => &SAND,
"construction" => &DIRT,
"traffic_island" => &STONE_BLOCK_SLAB,
"residential" => &STONE_BRICKS,
"commercial" => &SMOOTH_STONE,
"education" => &LIGHT_GRAY_CONCRETE,
"industrial" => &COBBLESTONE,
"military" => &GRAY_CONCRETE,
"railway" => &GRAVEL,
_ => &GRASS_BLOCK,
let block_type = match landuse_tag.as_str() {
"greenfield" | "meadow" | "grass" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"farmland" => FARMLAND,
"forest" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"cemetery" => PODZOL,
"beach" => SAND,
"construction" => DIRT,
"traffic_island" => STONE_BLOCK_SLAB,
"residential" => STONE_BRICKS,
"commercial" => SMOOTH_STONE,
"education" => LIGHT_GRAY_CONCRETE,
"industrial" => COBBLESTONE,
"military" => GRAY_CONCRETE,
"railway" => GRAVEL,
_ => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
};
let bresenham_block: Block = if args.winter { SNOW_BLOCK } else { GRASS_BLOCK };
// Process landuse nodes to fill the area
for &node in &element.nodes {
let (x, z) = node;
for node in &element.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
if let Some(prev) = previous_node {
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
for (bx, _, bz) in bresenham_points {
editor.set_block(&GRASS_BLOCK, bx, ground_level, bz, None, None);
editor.set_block(bresenham_block, bx, ground_level, bz, None, None);
}
current_landuse.push((x, z));
corner_addup = (corner_addup.0 + x, corner_addup.1 + z, corner_addup.2 + 1);
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// If there are landuse nodes, flood-fill the area
if !current_landuse.is_empty() {
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().map(|n| (n.x, n.z)).collect();
let floor_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, args.timeout.as_ref());
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
@@ -61,7 +89,7 @@ pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, gr
if landuse_tag == "traffic_island" {
editor.set_block(block_type, x, ground_level + 1, z, None, None);
} else if landuse_tag == "construction" || landuse_tag == "railway" {
editor.set_block(block_type, x, ground_level, z, None, Some(&[&SPONGE]));
editor.set_block(block_type, x, ground_level, z, None, Some(&[SPONGE]));
} else {
editor.set_block(block_type, x, ground_level, z, None, None);
}
@@ -73,72 +101,177 @@ pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, gr
let random_choice: i32 = rng.gen_range(0..100);
if random_choice < 15 {
// Place graves
if editor.check_for_block(x, ground_level, z, Some(&[&PODZOL]), None) {
if editor.check_for_block(x, ground_level, z, Some(&[PODZOL]), None) {
if rng.gen_bool(0.5) {
editor.set_block(&COBBLESTONE, x - 1, ground_level + 1, z, None, None);
editor.set_block(&STONE_BRICK_SLAB, x - 1, ground_level + 2, z, None, None);
editor.set_block(&STONE_BRICK_SLAB, x, ground_level + 1, z, None, None);
editor.set_block(&STONE_BRICK_SLAB, x + 1, ground_level + 1, z, None, None);
editor.set_block(
COBBLESTONE,
x - 1,
ground_level + 1,
z,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x - 1,
ground_level + 2,
z,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x,
ground_level + 1,
z,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x + 1,
ground_level + 1,
z,
None,
None,
);
} else {
editor.set_block(&COBBLESTONE, x, ground_level + 1, z - 1, None, None);
editor.set_block(&STONE_BRICK_SLAB, x, ground_level + 2, z - 1, None, None);
editor.set_block(&STONE_BRICK_SLAB, x, ground_level + 1, z, None, None);
editor.set_block(&STONE_BRICK_SLAB, x, ground_level + 1, z + 1, None, None);
editor.set_block(
COBBLESTONE,
x,
ground_level + 1,
z - 1,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x,
ground_level + 2,
z - 1,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x,
ground_level + 1,
z,
None,
None,
);
editor.set_block(
STONE_BRICK_SLAB,
x,
ground_level + 1,
z + 1,
None,
None,
);
}
}
} else if random_choice < 30 {
if editor.check_for_block(x, ground_level, z, Some(&[&PODZOL]), None) {
editor.set_block(&RED_FLOWER, x, ground_level + 1, z, None, None);
if editor.check_for_block(x, ground_level, z, Some(&[PODZOL]), None) {
editor.set_block(RED_FLOWER, x, ground_level + 1, z, None, None);
}
} else if random_choice < 33 {
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
}
}
}
"forest" => {
if !editor.check_for_block(x, ground_level, z, None, Some(&[&WATER])) {
if !editor.check_for_block(x, ground_level, z, None, Some(&[WATER])) {
let random_choice: i32 = rng.gen_range(0..21);
if random_choice == 20 {
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
} else if random_choice == 2 {
let flower_block: &once_cell::sync::Lazy<Block> = match rng.gen_range(1..=4) {
1 => &RED_FLOWER,
2 => &BLUE_FLOWER,
3 => &YELLOW_FLOWER,
_ => &WHITE_FLOWER,
let flower_block: Block = match rng.gen_range(1..=4) {
1 => RED_FLOWER,
2 => BLUE_FLOWER,
3 => YELLOW_FLOWER,
_ => WHITE_FLOWER,
};
editor.set_block(flower_block, x, ground_level + 1, z, None, None);
} else if random_choice <= 1 {
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
editor.set_block(GRASS, x, ground_level + 1, z, None, None);
}
}
}
"farmland" => {
// Check if the current block is not water or another undesired block
if !editor.check_for_block(x, ground_level, z, None, Some(&[&WATER])) {
if !editor.check_for_block(x, ground_level, z, None, Some(&[WATER])) {
if x % 15 == 0 || z % 15 == 0 {
// Place water on the edges
editor.set_block(&WATER, x, ground_level, z, Some(&[&FARMLAND]), None);
editor.set_block(&AIR, x, ground_level + 1, z, Some(&[&GRASS, &WHEAT, &CARROTS, &POTATOES]), None);
editor.set_block(WATER, x, ground_level, z, Some(&[FARMLAND]), None);
editor.set_block(
AIR,
x,
ground_level + 1,
z,
Some(&[GRASS, WHEAT, CARROTS, POTATOES]),
None,
);
} else {
// Set the block below as farmland
editor.set_block(&FARMLAND, x, ground_level, z, None, None);
editor.set_block(FARMLAND, x, ground_level, z, None, None);
// If a random condition is met, place a special object
if rng.gen_range(0..76) == 0 {
let special_choice = rng.gen_range(1..=10);
let special_choice: i32 = rng.gen_range(1..=10);
if special_choice <= 2 {
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
} else if special_choice <= 6 {
editor.set_block(&HAY_BALE, x, ground_level + 1, z, None, None);
editor.set_block(HAY_BALE, x, ground_level + 1, z, None, None);
} else {
editor.set_block(&OAK_LEAVES, x, ground_level + 1, z, None, None);
editor.set_block(
OAK_LEAVES,
x,
ground_level + 1,
z,
None,
None,
);
}
} else {
// Set crops only if the block below is farmland
if editor.check_for_block(x, ground_level, z, Some(&[&FARMLAND]), None) {
let crop_choice = [&WHEAT, &CARROTS, &POTATOES][rng.gen_range(0..3)];
editor.set_block(crop_choice, x, ground_level + 1, z, None, None);
if editor.check_for_block(
x,
ground_level,
z,
Some(&[FARMLAND]),
None,
) {
let crop_choice =
[WHEAT, CARROTS, POTATOES][rng.gen_range(0..3)];
editor.set_block(
crop_choice,
x,
ground_level + 1,
z,
None,
None,
);
}
}
}
@@ -147,57 +280,89 @@ pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, gr
"construction" => {
let random_choice: i32 = rng.gen_range(0..1501);
if random_choice < 6 {
editor.set_block(&SCAFFOLDING, x, ground_level + 1, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 1, z, None, None);
if random_choice < 2 {
editor.set_block(&SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 3, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 3, z, None, None);
} else if random_choice < 4 {
editor.set_block(&SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 3, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 4, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 1, z + 1, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 3, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 4, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 1, z + 1, None, None);
} else {
editor.set_block(&SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 3, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 4, z, None, None);
editor.set_block(&SCAFFOLDING, x, ground_level + 5, z, None, None);
editor.set_block(&SCAFFOLDING, x - 1, ground_level + 1, z, None, None);
editor.set_block(&SCAFFOLDING, x + 1, ground_level + 1, z - 1, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 2, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 3, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 4, z, None, None);
editor.set_block(SCAFFOLDING, x, ground_level + 5, z, None, None);
editor.set_block(SCAFFOLDING, x - 1, ground_level + 1, z, None, None);
editor.set_block(
SCAFFOLDING,
x + 1,
ground_level + 1,
z - 1,
None,
None,
);
}
} else if random_choice < 30 {
let construction_items: [&once_cell::sync::Lazy<Block>; 11] = [
&OAK_LOG, &COBBLESTONE, &GRAVEL, &GLOWSTONE, &STONE,
&COBBLESTONE_WALL, &BLACK_CONCRETE, &SAND, &OAK_PLANKS, &DIRT, &BRICK,
let construction_items: [Block; 11] = [
OAK_LOG,
COBBLESTONE,
GRAVEL,
GLOWSTONE,
STONE,
COBBLESTONE_WALL,
BLACK_CONCRETE,
SAND,
OAK_PLANKS,
DIRT,
BRICK,
];
editor.set_block(construction_items[rng.gen_range(0..construction_items.len())], x, ground_level + 1, z, None, None);
editor.set_block(
construction_items[rng.gen_range(0..construction_items.len())],
x,
ground_level + 1,
z,
None,
None,
);
} else if random_choice < 35 {
if random_choice < 30 {
editor.set_block(&DIRT, x, ground_level + 1, z, None, None);
editor.set_block(&DIRT, x, ground_level + 2, z, None, None);
editor.set_block(&DIRT, x + 1, ground_level + 1, z, None, None);
editor.set_block(&DIRT, x, ground_level + 1, z + 1, None, None);
editor.set_block(DIRT, x, ground_level + 1, z, None, None);
editor.set_block(DIRT, x, ground_level + 2, z, None, None);
editor.set_block(DIRT, x + 1, ground_level + 1, z, None, None);
editor.set_block(DIRT, x, ground_level + 1, z + 1, None, None);
} else {
editor.set_block(&DIRT, x, ground_level + 1, z, None, None);
editor.set_block(&DIRT, x, ground_level + 2, z, None, None);
editor.set_block(&DIRT, x - 1, ground_level + 1, z, None, None);
editor.set_block(&DIRT, x, ground_level + 1, z - 1, None, None);
editor.set_block(DIRT, x, ground_level + 1, z, None, None);
editor.set_block(DIRT, x, ground_level + 2, z, None, None);
editor.set_block(DIRT, x - 1, ground_level + 1, z, None, None);
editor.set_block(DIRT, x, ground_level + 1, z - 1, None, None);
}
} else if random_choice < 150 {
editor.set_block(&AIR, x, ground_level, z, None, Some(&[&SPONGE]));
editor.set_block(AIR, x, ground_level, z, None, Some(&[SPONGE]));
}
}
"grass" => {
if rng.gen_range(1..=7) != 1 && !editor.check_for_block(x, ground_level, z, None, Some(&[&WATER])) {
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
if rng.gen_range(1..=7) != 1
&& !editor.check_for_block(x, ground_level, z, None, Some(&[WATER]))
{
editor.set_block(GRASS, x, ground_level + 1, z, None, None);
}
}
"meadow" => {
if !editor.check_for_block(x, ground_level, z, None, Some(&[&WATER])) {
if !editor.check_for_block(x, ground_level, z, None, Some(&[WATER])) {
let random_choice: i32 = rng.gen_range(0..1001);
if random_choice < 5 {
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
} else if random_choice < 800 {
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
editor.set_block(GRASS, x, ground_level + 1, z, None, None);
}
}
}

View File

@@ -1,80 +1,146 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::floodfill::flood_fill_area;
use crate::element_processing::tree::create_tree;
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedWay;
use crate::world_editor::WorldEditor;
use rand::Rng;
pub fn generate_leisure(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_leisure(
editor: &mut WorldEditor,
element: &ProcessedWay,
ground_level: i32,
args: &Args,
) {
if let Some(leisure_type) = element.tags.get("leisure") {
let mut previous_node: Option<(i32, i32)> = None;
let mut corner_addup: (i32, i32, i32) = (0, 0, 0);
let mut current_leisure: Vec<(i32, i32)> = vec![];
// Determine block type based on leisure type
let block_type: &once_cell::sync::Lazy<Block> = match leisure_type.as_str() {
"park" => &GRASS_BLOCK,
"playground" | "recreation_ground" | "pitch" => &GREEN_STAINED_HARDENED_CLAY,
"garden" => &GRASS_BLOCK,
"swimming_pool" => &WATER,
_ => &GRASS_BLOCK,
let block_type: Block = match leisure_type.as_str() {
"park" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"playground" | "recreation_ground" | "pitch" => {
if let Some(surface) = element.tags.get("surface") {
match surface.as_str() {
"clay" => TERRACOTTA,
"sand" => SAND,
"tartan" => RED_TERRACOTTA,
_ => GREEN_STAINED_HARDENED_CLAY,
}
} else {
GREEN_STAINED_HARDENED_CLAY
}
}
"garden" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"swimming_pool" => WATER,
_ => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
};
// Process leisure area nodes
for &node in &element.nodes {
for node in &element.nodes {
if let Some(prev) = previous_node {
// Draw a line between the current and previous node
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level, prev.1, node.0, ground_level, node.1);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level, prev.1, node.x, ground_level, node.z);
for (bx, _, bz) in bresenham_points {
editor.set_block(block_type, bx, ground_level, bz, Some(&[&GRASS_BLOCK, &STONE_BRICKS, &SMOOTH_STONE, &LIGHT_GRAY_CONCRETE, &COBBLESTONE, &GRAY_CONCRETE]), None);
editor.set_block(
block_type,
bx,
ground_level,
bz,
Some(&[
GRASS_BLOCK,
STONE_BRICKS,
SMOOTH_STONE,
LIGHT_GRAY_CONCRETE,
COBBLESTONE,
GRAY_CONCRETE,
]),
None,
);
}
current_leisure.push((node.0, node.1));
corner_addup.0 += node.0;
corner_addup.1 += node.1;
current_leisure.push((node.x, node.z));
corner_addup.0 += node.x;
corner_addup.1 += node.z;
corner_addup.2 += 1;
}
previous_node = Some(node);
previous_node = Some((node.x, node.z));
}
// Flood-fill the interior of the leisure area
if corner_addup != (0, 0, 0) {
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let filled_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = element
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let filled_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
for (x, z) in filled_area {
editor.set_block(block_type, x, ground_level, z, Some(&[&GRASS_BLOCK]), None);
editor.set_block(block_type, x, ground_level, z, Some(&[GRASS_BLOCK]), None);
// Add decorative elements for parks and gardens
if matches!(leisure_type.as_str(), "park" | "garden") {
if editor.check_for_block(x, ground_level, z, Some(&[&GRASS_BLOCK]), None) {
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
let random_choice: i32 = rng.gen_range(0..1000);
if matches!(leisure_type.as_str(), "park" | "garden")
&& editor.check_for_block(x, ground_level, z, Some(&[GRASS_BLOCK]), None)
{
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
let random_choice: i32 = rng.gen_range(0..1000);
match random_choice {
0 => { // Benches
editor.set_block(&OAK_LOG, x, ground_level + 1, z, None, None);
editor.set_block(&OAK_LOG, x + 1, ground_level + 1, z, None, None);
editor.set_block(&OAK_LOG, x - 1, ground_level + 1, z, None, None);
}
1..=30 => { // Flowers
let flower_choice: &once_cell::sync::Lazy<Block> = match rng.gen_range(0..4) {
0 => &RED_FLOWER,
1 => &YELLOW_FLOWER,
2 => &BLUE_FLOWER,
_ => &WHITE_FLOWER,
};
editor.set_block(flower_choice, x, ground_level + 1, z, None, None);
}
31..=70 => { // Grass
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
}
71..=80 => { // Tree
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
}
_ => {}
match random_choice {
0 => {
// Benches
editor.set_block(OAK_LOG, x, ground_level + 1, z, None, None);
editor.set_block(OAK_LOG, x + 1, ground_level + 1, z, None, None);
editor.set_block(OAK_LOG, x - 1, ground_level + 1, z, None, None);
}
1..=30 => {
// Flowers
let flower_choice = match rng.gen_range(0..4) {
0 => RED_FLOWER,
1 => YELLOW_FLOWER,
2 => BLUE_FLOWER,
_ => WHITE_FLOWER,
};
editor.set_block(flower_choice, x, ground_level + 1, z, None, None);
}
31..=70 => {
// Grass
editor.set_block(GRASS, x, ground_level + 1, z, None, None);
}
71..=80 => {
// Tree
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
}
_ => {}
}
}
@@ -84,27 +150,40 @@ pub fn generate_leisure(editor: &mut WorldEditor, element: &ProcessedElement, gr
let random_choice: i32 = rng.gen_range(0..5000);
match random_choice {
0..=10 => { // Swing set
0..=10 => {
// Swing set
for y in 1..=4 {
editor.set_block(&OAK_FENCE, x - 1, ground_level + y, z, None, None);
editor.set_block(&OAK_FENCE, x + 1, ground_level + y, z, None, None);
editor.set_block(OAK_FENCE, x - 1, ground_level + y, z, None, None);
editor.set_block(OAK_FENCE, x + 1, ground_level + y, z, None, None);
}
editor.set_block(&OAK_FENCE, x, ground_level + 4, z, None, None);
editor.set_block(&STONE_BLOCK_SLAB, x, ground_level + 2, z, None, None);
editor.set_block(OAK_FENCE, x, ground_level + 4, z, None, None);
editor.set_block(STONE_BLOCK_SLAB, x, ground_level + 2, z, None, None);
}
11..=20 => { // Slide
editor.set_block(&OAK_SLAB, x, ground_level + 1, z, None, None);
editor.set_block(&OAK_SLAB, x + 1, ground_level + 2, z, None, None);
editor.set_block(&OAK_SLAB, x + 2, ground_level + 3, z, None, None);
11..=20 => {
// Slide
editor.set_block(OAK_SLAB, x, ground_level + 1, z, None, None);
editor.set_block(OAK_SLAB, x + 1, ground_level + 2, z, None, None);
editor.set_block(OAK_SLAB, x + 2, ground_level + 3, z, None, None);
editor.set_block(&OAK_PLANKS, x + 2, ground_level + 2, z, None, None);
editor.set_block(&OAK_PLANKS, x + 2, ground_level + 1, z, None, None);
editor.set_block(OAK_PLANKS, x + 2, ground_level + 2, z, None, None);
editor.set_block(OAK_PLANKS, x + 2, ground_level + 1, z, None, None);
editor.set_block(&LADDER, x + 2, ground_level + 2, z - 1, None, None);
editor.set_block(&LADDER, x + 2, ground_level + 1, z - 1, None, None);
editor.set_block(LADDER, x + 2, ground_level + 2, z - 1, None, None);
editor.set_block(LADDER, x + 2, ground_level + 1, z - 1, None, None);
}
21..=30 => { // Sandpit
editor.fill_blocks(&SAND, x - 3, ground_level, z - 3, x + 3, ground_level, z + 3, Some(&[&GREEN_STAINED_HARDENED_CLAY]), None);
21..=30 => {
// Sandpit
editor.fill_blocks(
SAND,
x - 3,
ground_level,
z - 3,
x + 3,
ground_level,
z + 3,
Some(&[GREEN_STAINED_HARDENED_CLAY]),
None,
);
}
_ => {}
}

View File

@@ -8,5 +8,7 @@ pub mod landuse;
pub mod leisure;
pub mod natural;
pub mod railways;
pub mod tourisms;
pub mod tree;
pub mod waterways;
pub mod water_areas;
pub mod waterways;

View File

@@ -1,18 +1,33 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::args::Args;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::floodfill::flood_fill_area;
use crate::element_processing::tree::create_tree;
use crate::floodfill::flood_fill_area;
use crate::osm_parser::ProcessedElement;
use crate::world_editor::WorldEditor;
use rand::Rng;
pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
if let Some(natural_type) = element.tags.get("natural") {
pub fn generate_natural(
editor: &mut WorldEditor,
element: &ProcessedElement,
ground_level: i32,
args: &Args,
) {
if let Some(natural_type) = element.tags().get("natural") {
if natural_type == "tree" {
if let Some(first_node) = element.nodes.first() {
let (x, z) = *first_node;
if let ProcessedElement::Node(node) = element {
let x: i32 = node.x;
let z: i32 = node.z;
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
}
} else {
let mut previous_node: Option<(i32, i32)> = None;
@@ -20,21 +35,45 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, gr
let mut current_natural: Vec<(i32, i32)> = vec![];
// Determine block type based on natural tag
let block_type: &once_cell::sync::Lazy<Block> = match natural_type.as_str() {
"scrub" | "grassland" | "wood" => &GRASS_BLOCK,
"beach" | "sand" => &SAND,
"tree_row" => &GRASS_BLOCK,
"wetland" | "water" => &WATER,
_ => &GRASS_BLOCK,
let block_type: Block = match natural_type.as_str() {
"scrub" | "grassland" | "wood" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"beach" | "sand" => SAND,
"tree_row" => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
"wetland" | "water" => WATER,
_ => {
if args.winter {
SNOW_BLOCK
} else {
GRASS_BLOCK
}
}
};
let ProcessedElement::Way(way) = element else {
return;
};
// Process natural nodes to fill the area
for &node in &element.nodes {
let (x, z) = node;
for node in &way.nodes {
let x: i32 = node.x;
let z: i32 = node.z;
if let Some(prev) = previous_node {
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(prev.0, ground_level, prev.1, x, ground_level, z);
for (bx, _, bz) in bresenham_points {
editor.set_block(block_type, bx, ground_level, bz, None, None);
}
@@ -43,13 +82,18 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, gr
corner_addup = (corner_addup.0 + x, corner_addup.1 + z, corner_addup.2 + 1);
}
previous_node = Some(node);
previous_node = Some((x, z));
}
// If there are natural nodes, flood-fill the area
if corner_addup != (0, 0, 0) {
let polygon_coords: Vec<(i32, i32)> = element.nodes.iter().copied().collect();
let filled_area: Vec<(i32, i32)> = flood_fill_area(&polygon_coords, 2);
let polygon_coords: Vec<(i32, i32)> = way
.nodes
.iter()
.map(|n: &crate::osm_parser::ProcessedNode| (n.x, n.z))
.collect();
let filled_area: Vec<(i32, i32)> =
flood_fill_area(&polygon_coords, args.timeout.as_ref());
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
@@ -58,23 +102,30 @@ pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, gr
// Generate elements for "wood" and "tree_row"
if natural_type == "wood" || natural_type == "tree_row" {
if editor.check_for_block(x, ground_level, z, None, Some(&[&WATER])) {
if editor.check_for_block(x, ground_level, z, None, Some(&[WATER])) {
continue;
}
let random_choice: i32 = rng.gen_range(0..26);
if random_choice == 25 {
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
create_tree(
editor,
x,
ground_level + 1,
z,
rng.gen_range(1..=3),
args.winter,
);
} else if random_choice == 2 {
let flower_block = match rng.gen_range(1..=4) {
1 => &RED_FLOWER,
2 => &BLUE_FLOWER,
3 => &YELLOW_FLOWER,
_ => &WHITE_FLOWER,
1 => RED_FLOWER,
2 => BLUE_FLOWER,
3 => YELLOW_FLOWER,
_ => WHITE_FLOWER,
};
editor.set_block(flower_block, x, ground_level + 1, z, None, None);
} else if random_choice <= 1 {
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
editor.set_block(GRASS, x, ground_level + 1, z, None, None);
}
}
}

View File

@@ -1,9 +1,9 @@
use crate::world_editor::WorldEditor;
use crate::osm_parser::ProcessedElement;
use crate::block_definitions::*;
use crate::bresenham::bresenham_line;
use crate::osm_parser::ProcessedWay;
use crate::world_editor::WorldEditor;
pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedWay, ground_level: i32) {
if let Some(railway_type) = element.tags.get("railway") {
if ["proposed", "abandoned", "subway", "construction"].contains(&railway_type.as_str()) {
return;
@@ -22,19 +22,25 @@ pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedElement, g
}
for i in 1..element.nodes.len() {
let (x1, z1) = element.nodes[i - 1];
let (x2, z2) = element.nodes[i];
let prev: &crate::osm_parser::ProcessedNode = &element.nodes[i - 1];
let x1: i32 = prev.x;
let z1: i32 = prev.z;
let cur: &crate::osm_parser::ProcessedNode = &element.nodes[i];
let x2: i32 = cur.x;
let z2: i32 = cur.z;
// Generate the line of coordinates between the two nodes
let bresenham_points: Vec<(i32, i32, i32)> = bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
let bresenham_points: Vec<(i32, i32, i32)> =
bresenham_line(x1, ground_level, z1, x2, ground_level, z2);
for (bx, _, bz) in bresenham_points {
// TODO: Set direction of rail
editor.set_block(&IRON_BLOCK, bx, ground_level, bz, None, None);
editor.set_block(&RAIL, bx, ground_level + 1, bz, None, None);
editor.set_block(IRON_BLOCK, bx, ground_level, bz, None, None);
editor.set_block(RAIL, bx, ground_level + 1, bz, None, None);
if bx % 4 == 0 {
editor.set_block(&OAK_LOG, bx, ground_level, bz, None, None);
editor.set_block(OAK_LOG, bx, ground_level, bz, None, None);
}
}
}

View File

@@ -0,0 +1,30 @@
use crate::block_definitions::*;
use crate::osm_parser::ProcessedNode;
use crate::world_editor::WorldEditor;
pub fn generate_tourisms(editor: &mut WorldEditor, element: &ProcessedNode, ground_level: i32) {
// Skip if 'layer' or 'level' is negative in the tags
if let Some(layer) = element.tags.get("layer") {
if layer.parse::<i32>().unwrap_or(0) < 0 {
return;
}
}
if let Some(level) = element.tags.get("level") {
if level.parse::<i32>().unwrap_or(0) < 0 {
return;
}
}
if let Some(tourism_type) = element.tags.get("tourism") {
let x: i32 = element.x;
let z: i32 = element.z;
if tourism_type == "information" {
if let Some("board") = element.tags.get("information").map(|x: &String| x.as_str()) {
// TODO draw a sign
editor.set_block(OAK_PLANKS, x, ground_level + 1, z, None, None);
}
}
}
}

View File

@@ -1,9 +1,8 @@
use crate::block_definitions::*;
use crate::world_editor::WorldEditor;
use once_cell::sync::Lazy;
/// Helper function to set blocks in a circular pattern around a central point.
fn round1(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i32, z: i32) {
fn round1(editor: &mut WorldEditor, material: Block, x: i32, y: i32, z: i32) {
editor.set_block(material, x - 2, y, z, None, None);
editor.set_block(material, x + 2, y, z, None, None);
editor.set_block(material, x, y, z - 2, None, None);
@@ -15,7 +14,7 @@ fn round1(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i
}
/// Helper function to set blocks in a wider circular pattern.
fn round2(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i32, z: i32) {
fn round2(editor: &mut WorldEditor, material: Block, x: i32, y: i32, z: i32) {
editor.set_block(material, x + 3, y, z, None, None);
editor.set_block(material, x + 2, y, z - 1, None, None);
editor.set_block(material, x + 2, y, z + 1, None, None);
@@ -31,7 +30,7 @@ fn round2(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i
}
/// Helper function to set blocks in a more scattered circular pattern.
fn round3(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i32, z: i32) {
fn round3(editor: &mut WorldEditor, material: Block, x: i32, y: i32, z: i32) {
editor.set_block(material, x + 3, y, z - 1, None, None);
editor.set_block(material, x + 3, y, z + 1, None, None);
editor.set_block(material, x + 2, y, z - 2, None, None);
@@ -47,68 +46,120 @@ fn round3(editor: &mut WorldEditor, material: &'static Lazy<Block>, x: i32, y: i
}
/// Function to create different types of trees.
pub fn create_tree(editor: &mut WorldEditor, x: i32, y: i32, z: i32, typetree: u8) {
let mut blacklist: Vec<&'static Lazy<Block>> = Vec::new();
pub fn create_tree(editor: &mut WorldEditor, x: i32, y: i32, z: i32, typetree: u8, snow: bool) {
let mut blacklist: Vec<Block> = Vec::new();
blacklist.extend(building_corner_variations());
blacklist.extend(building_wall_variations());
blacklist.extend(building_floor_variations());
blacklist.push(&WATER);
blacklist.push(WATER);
if editor.check_for_block(x, y - 1, z, None, Some(&blacklist)) {
return;
}
match typetree {
1 => { // Oak tree
editor.fill_blocks(&OAK_LOG, x, y, z, x, y + 8, z, None, None);
editor.fill_blocks(&OAK_LEAVES, x - 1, y + 3, z, x - 1, y + 9, z, None, None);
editor.fill_blocks(&OAK_LEAVES, x + 1, y + 3, z, x + 1, y + 9, z, None, None);
editor.fill_blocks(&OAK_LEAVES, x, y + 3, z - 1, x, y + 9, z - 1, None, None);
editor.fill_blocks(&OAK_LEAVES, x, y + 3, z + 1, x, y + 9, z + 1, None, None);
editor.fill_blocks(&OAK_LEAVES, x, y + 9, z, x, y + 10, z, None, None);
round1(editor, &OAK_LEAVES, x, y + 8, z);
round1(editor, &OAK_LEAVES, x, y + 7, z);
round1(editor, &OAK_LEAVES, x, y + 6, z);
round1(editor, &OAK_LEAVES, x, y + 5, z);
round1(editor, &OAK_LEAVES, x, y + 4, z);
round1(editor, &OAK_LEAVES, x, y + 3, z);
round2(editor, &OAK_LEAVES, x, y + 7, z);
round2(editor, &OAK_LEAVES, x, y + 6, z);
round2(editor, &OAK_LEAVES, x, y + 5, z);
round2(editor, &OAK_LEAVES, x, y + 4, z);
round3(editor, &OAK_LEAVES, x, y + 6, z);
round3(editor, &OAK_LEAVES, x, y + 5, z);
1 => {
// Oak tree
editor.fill_blocks(OAK_LOG, x, y, z, x, y + 8, z, None, None);
editor.fill_blocks(OAK_LEAVES, x - 1, y + 3, z, x - 1, y + 9, z, None, None);
editor.fill_blocks(OAK_LEAVES, x + 1, y + 3, z, x + 1, y + 9, z, None, None);
editor.fill_blocks(OAK_LEAVES, x, y + 3, z - 1, x, y + 9, z - 1, None, None);
editor.fill_blocks(OAK_LEAVES, x, y + 3, z + 1, x, y + 9, z + 1, None, None);
editor.fill_blocks(OAK_LEAVES, x, y + 9, z, x, y + 10, z, None, None);
round1(editor, OAK_LEAVES, x, y + 8, z);
round1(editor, OAK_LEAVES, x, y + 7, z);
round1(editor, OAK_LEAVES, x, y + 6, z);
round1(editor, OAK_LEAVES, x, y + 5, z);
round1(editor, OAK_LEAVES, x, y + 4, z);
round1(editor, OAK_LEAVES, x, y + 3, z);
round2(editor, OAK_LEAVES, x, y + 7, z);
round2(editor, OAK_LEAVES, x, y + 6, z);
round2(editor, OAK_LEAVES, x, y + 5, z);
round2(editor, OAK_LEAVES, x, y + 4, z);
round3(editor, OAK_LEAVES, x, y + 6, z);
round3(editor, OAK_LEAVES, x, y + 5, z);
if snow {
editor.set_block(SNOW_LAYER, x, y + 11, z, None, None);
editor.set_block(SNOW_LAYER, x + 1, y + 10, z, None, None);
editor.set_block(SNOW_LAYER, x - 1, y + 10, z, None, None);
editor.set_block(SNOW_LAYER, x, y + 10, z - 1, None, None);
editor.set_block(SNOW_LAYER, x, y + 10, z + 1, None, None);
round1(editor, SNOW_LAYER, x, y + 9, z);
round1(editor, SNOW_LAYER, x, y + 8, z);
round1(editor, SNOW_LAYER, x, y + 7, z);
round1(editor, SNOW_LAYER, x, y + 6, z);
round2(editor, SNOW_LAYER, x, y + 8, z);
round2(editor, SNOW_LAYER, x, y + 7, z);
round2(editor, SNOW_LAYER, x, y + 6, z);
round2(editor, SNOW_LAYER, x, y + 5, z);
round3(editor, SNOW_LAYER, x, y + 7, z);
round3(editor, SNOW_LAYER, x, y + 6, z);
}
}
2 => { // Spruce tree
editor.fill_blocks(&SPRUCE_LOG, x, y, z, x, y + 9, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x - 1, y + 3, z, x - 1, y + 10, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x + 1, y + 3, z, x + 1, y + 10, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x, y + 3, z - 1, x, y + 10, z - 1, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x, y + 3, z + 1, x, y + 10, z + 1, None, None);
editor.set_block(&BIRCH_LEAVES, x, y + 10, z, None, None);
round1(editor, &BIRCH_LEAVES, x, y + 9, z);
round1(editor, &BIRCH_LEAVES, x, y + 7, z);
round1(editor, &BIRCH_LEAVES, x, y + 6, z);
round1(editor, &BIRCH_LEAVES, x, y + 4, z);
round1(editor, &BIRCH_LEAVES, x, y + 3, z);
round2(editor, &BIRCH_LEAVES, x, y + 6, z);
round2(editor, &BIRCH_LEAVES, x, y + 3, z);
2 => {
// Spruce tree
editor.fill_blocks(SPRUCE_LOG, x, y, z, x, y + 9, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x - 1, y + 3, z, x - 1, y + 10, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x + 1, y + 3, z, x + 1, y + 10, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x, y + 3, z - 1, x, y + 10, z - 1, None, None);
editor.fill_blocks(BIRCH_LEAVES, x, y + 3, z + 1, x, y + 10, z + 1, None, None);
editor.set_block(BIRCH_LEAVES, x, y + 10, z, None, None);
round1(editor, BIRCH_LEAVES, x, y + 9, z);
round1(editor, BIRCH_LEAVES, x, y + 7, z);
round1(editor, BIRCH_LEAVES, x, y + 6, z);
round1(editor, BIRCH_LEAVES, x, y + 4, z);
round1(editor, BIRCH_LEAVES, x, y + 3, z);
round2(editor, BIRCH_LEAVES, x, y + 6, z);
round2(editor, BIRCH_LEAVES, x, y + 3, z);
if snow {
editor.set_block(SNOW_LAYER, x, y + 11, z, None, None);
editor.set_block(SNOW_LAYER, x + 1, y + 11, z, None, None);
editor.set_block(SNOW_LAYER, x - 1, y + 11, z, None, None);
editor.set_block(SNOW_LAYER, x, y + 11, z - 1, None, None);
editor.set_block(SNOW_LAYER, x, y + 11, z + 1, None, None);
round1(editor, SNOW_LAYER, x, y + 10, z);
round1(editor, SNOW_LAYER, x, y + 8, z);
round1(editor, SNOW_LAYER, x, y + 7, z);
round1(editor, SNOW_LAYER, x, y + 5, z);
round1(editor, SNOW_LAYER, x, y + 4, z);
round2(editor, SNOW_LAYER, x, y + 7, z);
round2(editor, SNOW_LAYER, x, y + 4, z);
}
}
3 => { // Birch tree
editor.fill_blocks(&BIRCH_LOG, x, y, z, x, y + 6, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x - 1, y + 2, z, x - 1, y + 7, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x + 1, y + 2, z, x + 1, y + 7, z, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x, y + 2, z - 1, x, y + 7, z - 1, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x, y + 2, z + 1, x, y + 7, z + 1, None, None);
editor.fill_blocks(&BIRCH_LEAVES, x, y + 7, z, x, y + 8, z, None, None);
round1(editor, &BIRCH_LEAVES, x, y + 6, z);
round1(editor, &BIRCH_LEAVES, x, y + 5, z);
round1(editor, &BIRCH_LEAVES, x, y + 4, z);
round1(editor, &BIRCH_LEAVES, x, y + 3, z);
round1(editor, &BIRCH_LEAVES, x, y + 2, z);
round2(editor, &BIRCH_LEAVES, x, y + 2, z);
round2(editor, &BIRCH_LEAVES, x, y + 3, z);
round2(editor, &BIRCH_LEAVES, x, y + 4, z);
3 => {
// Birch tree
editor.fill_blocks(BIRCH_LOG, x, y, z, x, y + 6, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x - 1, y + 2, z, x - 1, y + 7, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x + 1, y + 2, z, x + 1, y + 7, z, None, None);
editor.fill_blocks(BIRCH_LEAVES, x, y + 2, z - 1, x, y + 7, z - 1, None, None);
editor.fill_blocks(BIRCH_LEAVES, x, y + 2, z + 1, x, y + 7, z + 1, None, None);
editor.fill_blocks(BIRCH_LEAVES, x, y + 7, z, x, y + 8, z, None, None);
round1(editor, BIRCH_LEAVES, x, y + 6, z);
round1(editor, BIRCH_LEAVES, x, y + 5, z);
round1(editor, BIRCH_LEAVES, x, y + 4, z);
round1(editor, BIRCH_LEAVES, x, y + 3, z);
round1(editor, BIRCH_LEAVES, x, y + 2, z);
round2(editor, BIRCH_LEAVES, x, y + 2, z);
round2(editor, BIRCH_LEAVES, x, y + 3, z);
round2(editor, BIRCH_LEAVES, x, y + 4, z);
if snow {
editor.set_block(SNOW_LAYER, x, y + 9, z, None, None);
editor.set_block(SNOW_LAYER, x + 1, y + 8, z, None, None);
editor.set_block(SNOW_LAYER, x - 1, y + 8, z, None, None);
editor.set_block(SNOW_LAYER, x, y + 8, z - 1, None, None);
editor.set_block(SNOW_LAYER, x, y + 8, z + 1, None, None);
round1(editor, SNOW_LAYER, x, y + 7, z);
round1(editor, SNOW_LAYER, x, y + 6, z);
round1(editor, SNOW_LAYER, x, y + 5, z);
round1(editor, SNOW_LAYER, x, y + 4, z);
round1(editor, SNOW_LAYER, x, y + 3, z);
round2(editor, SNOW_LAYER, x, y + 3, z);
round2(editor, SNOW_LAYER, x, y + 4, z);
round2(editor, SNOW_LAYER, x, y + 5, z);
}
}
_ => {} // Do nothing if typetree is not recognized
}

View File

@@ -0,0 +1,296 @@
use geo::{Contains, Intersects, LineString, Point, Polygon, Rect};
use crate::{
block_definitions::WATER,
osm_parser::{ProcessedMemberRole, ProcessedNode, ProcessedRelation},
world_editor::WorldEditor,
};
pub fn generate_water_areas(
editor: &mut WorldEditor,
element: &ProcessedRelation,
ground_level: i32,
) {
if !element.tags.contains_key("water") {
return;
}
// don't handle water below layer 0
if let Some(layer) = element.tags.get("layer") {
if layer.parse::<i32>().map(|x| x < 0).unwrap_or(false) {
return;
}
}
let mut outers: Vec<Vec<ProcessedNode>> = vec![];
let mut inners: Vec<Vec<ProcessedNode>> = vec![];
for mem in &element.members {
match mem.role {
ProcessedMemberRole::Outer => outers.push(mem.way.nodes.clone()),
ProcessedMemberRole::Inner => inners.push(mem.way.nodes.clone()),
}
}
merge_loopy_loops(&mut outers);
if !verify_loopy_loops(&outers) {
return;
}
merge_loopy_loops(&mut inners);
if !verify_loopy_loops(&inners) {
return;
}
let (max_x, max_z) = editor.get_max_coords();
let outers: Vec<Vec<(f64, f64)>> = outers
.iter()
.map(|x: &Vec<ProcessedNode>| {
x.iter()
.map(|y: &ProcessedNode| (y.x as f64, y.z as f64))
.collect()
})
.collect();
let inners: Vec<Vec<(f64, f64)>> = inners
.iter()
.map(|x: &Vec<ProcessedNode>| {
x.iter()
.map(|y: &ProcessedNode| (y.x as f64, y.z as f64))
.collect()
})
.collect();
inverse_floodfill(max_x, max_z, outers, inners, editor, ground_level);
}
// Merges ways that share nodes into full loops
fn merge_loopy_loops(loops: &mut Vec<Vec<ProcessedNode>>) {
let mut removed: Vec<usize> = vec![];
let mut merged: Vec<Vec<ProcessedNode>> = vec![];
for i in 0..loops.len() {
for j in 0..loops.len() {
if i == j {
continue;
}
if removed.contains(&i) || removed.contains(&j) {
continue;
}
let x: &Vec<ProcessedNode> = &loops[i];
let y: &Vec<ProcessedNode> = &loops[j];
// it's looped already
if x[0].id == x.last().unwrap().id {
continue;
}
// it's looped already
if y[0].id == y.last().unwrap().id {
continue;
}
if x[0].id == y[0].id {
removed.push(i);
removed.push(j);
let mut x: Vec<ProcessedNode> = x.clone();
x.reverse();
x.extend(y.iter().skip(1).cloned());
merged.push(x);
} else if x.last().unwrap().id == y.last().unwrap().id {
removed.push(i);
removed.push(j);
let mut x: Vec<ProcessedNode> = x.clone();
x.extend(y.iter().rev().skip(1).cloned());
merged.push(x);
} else if x[0].id == y.last().unwrap().id {
removed.push(i);
removed.push(j);
let mut y: Vec<ProcessedNode> = y.clone();
y.extend(x.iter().skip(1).cloned());
merged.push(y);
}
}
}
removed.sort();
for r in removed.iter().rev() {
loops.remove(*r);
}
let merged_len: usize = merged.len();
for m in merged {
loops.push(m);
}
if merged_len > 0 {
merge_loopy_loops(loops);
}
}
fn verify_loopy_loops(loops: &[Vec<ProcessedNode>]) -> bool {
let mut valid: bool = true;
for l in loops {
if l[0].id != l.last().unwrap().id {
eprintln!("WARN: Disconnected loop");
valid = false;
}
}
valid
}
// Water areas are absolutely huge. We can't easily flood fill the entire thing.
// Instead, we'll iterate over all the blocks in our MC world, and check if each
// one is in the river or not
fn inverse_floodfill(
max_x: i32,
max_z: i32,
outers: Vec<Vec<(f64, f64)>>,
inners: Vec<Vec<(f64, f64)>>,
editor: &mut WorldEditor,
ground_level: i32,
) {
let min_x: i32 = 0;
let min_z: i32 = 0;
let inners: Vec<_> = inners
.into_iter()
.map(|x: Vec<(f64, f64)>| Polygon::new(LineString::from(x), vec![]))
.collect();
let outers: Vec<_> = outers
.into_iter()
.map(|x: Vec<(f64, f64)>| Polygon::new(LineString::from(x), vec![]))
.collect();
inverse_floodfill_recursive(
(min_x, min_z),
(max_x, max_z),
ground_level,
&outers,
&inners,
editor,
);
}
fn inverse_floodfill_recursive(
min: (i32, i32),
max: (i32, i32),
ground_level: i32,
outers: &[Polygon],
inners: &[Polygon],
editor: &mut WorldEditor,
) {
const ITERATIVE_THRES: i32 = 10_000;
if min.0 > max.0 || min.1 > max.1 {
return;
}
if (max.0 - min.0) * (max.1 - min.1) < ITERATIVE_THRES {
inverse_floodfill_iterative(min, max, ground_level, outers, inners, editor);
return;
}
let center_x: i32 = (min.0 + max.0) / 2;
let center_z: i32 = (min.1 + max.1) / 2;
let quadrants: [(i32, i32, i32, i32); 4] = [
(min.0, center_x, min.1, center_z),
(center_x, max.0, min.1, center_z),
(min.0, center_x, center_z, max.1),
(center_x, max.0, center_z, max.1),
];
for (min_x, max_x, min_z, max_z) in quadrants {
let rect: Rect = Rect::new(
Point::new(min_x as f64, min_z as f64),
Point::new(max_x as f64, max_z as f64),
);
if outers.iter().any(|outer: &Polygon| outer.contains(&rect))
&& !inners.iter().any(|inner: &Polygon| inner.intersects(&rect))
{
// every block in rect is water
// so we can safely just set the whole thing to water
rect_fill(min_x, max_x, min_z, max_z, ground_level, editor);
continue;
}
// When we recurse, we only really need the polygons we potentially intersect with
// This saves on processing time
let outers_intersects: Vec<_> = outers
.iter()
.filter(|poly: &&Polygon| poly.intersects(&rect))
.cloned()
.collect();
// Moving this inside the below `if` statement makes it slower for some reason.
// I assume it changes how the compiler is able to optimize it
let inners_intersects: Vec<_> = inners
.iter()
.filter(|poly: &&Polygon| poly.intersects(&rect))
.cloned()
.collect();
if !outers_intersects.is_empty() {
// recurse
inverse_floodfill_recursive(
(min_x, min_z),
(max_x, max_z),
ground_level,
&outers_intersects,
&inners_intersects,
editor,
);
}
}
}
// once we "zoom in" enough, it's more efficient to switch to iteration
fn inverse_floodfill_iterative(
min: (i32, i32),
max: (i32, i32),
ground_level: i32,
outers: &[Polygon],
inners: &[Polygon],
editor: &mut WorldEditor,
) {
for x in min.0..max.0 {
for z in min.1..max.1 {
let p: Point = Point::new(x as f64, z as f64);
if outers.iter().any(|poly: &Polygon| poly.contains(&p))
&& inners.iter().all(|poly: &Polygon| !poly.contains(&p))
{
editor.set_block(WATER, x, ground_level, z, None, None);
}
}
}
}
fn rect_fill(
min_x: i32,
max_x: i32,
min_z: i32,
max_z: i32,
ground_level: i32,
editor: &mut WorldEditor,
) {
for x in min_x..max_x {
for z in min_z..max_z {
editor.set_block(WATER, x, ground_level, z, None, None);
}
}
}

Some files were not shown because too many files have changed in this diff Show More