Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00ab57e1ea | ||
|
|
0ff31781fb | ||
|
|
f8bf0c02dd | ||
|
|
89135b8ddd | ||
|
|
543c1e551d | ||
|
|
7a17524de9 | ||
|
|
444c6f2d50 | ||
|
|
122c5f01a9 | ||
|
|
851c46b86d | ||
|
|
8552b412e6 | ||
|
|
f48598e1bc | ||
|
|
45ace99a9e | ||
|
|
540b8db7d9 | ||
|
|
023bd888f2 | ||
|
|
c237978ce9 | ||
|
|
1b661c1619 | ||
|
|
685df7caa0 | ||
|
|
15f4bf0345 | ||
|
|
eee26aeda5 | ||
|
|
4cc432b699 | ||
|
|
baa70a7f3c | ||
|
|
9e4f743778 | ||
|
|
10bcfbfc8a | ||
|
|
385ee223e0 | ||
|
|
2b09c18ec9 | ||
|
|
ee88e9dc47 | ||
|
|
f9ce6f208c | ||
|
|
83e1296ce9 | ||
|
|
72c8cdc3b7 | ||
|
|
15837d5050 | ||
|
|
53f352dad4 | ||
|
|
dbbb6a896a | ||
|
|
e9ca4789c0 | ||
|
|
c3b7904286 | ||
|
|
0b385197db | ||
|
|
77fe64c910 | ||
|
|
bfb4bdaf6c | ||
|
|
8d57da0cd7 | ||
|
|
704736f9dd | ||
|
|
a7c1f7be5e | ||
|
|
1bdbf83d22 | ||
|
|
478ffb6709 | ||
|
|
0d3565e351 | ||
|
|
96c7d33cb8 | ||
|
|
e549ded318 | ||
|
|
2ad01f8298 | ||
|
|
6f34b125bf | ||
|
|
1036e36c15 | ||
|
|
ee49015291 | ||
|
|
121437cfbf | ||
|
|
3bb459cdb7 | ||
|
|
87bf75c161 | ||
|
|
24bd642839 |
5
.flake8
@@ -1,5 +0,0 @@
|
||||
[flake8]
|
||||
ignore = E203, W503, F405, F403
|
||||
# line length is intentionally set to 80 here because black uses Bugbear
|
||||
# See https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#line-length for more details
|
||||
max-line-length = 100
|
||||
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,8 +1,8 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: ''
|
||||
title: "[BUG] Your title here"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
@@ -17,4 +17,4 @@ Please provide your input parameters so we can reproduce the issue.
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
Add any other context about the problem here. Please also provide the --bbox input parameters you used so we can reproduce the issue.
|
||||
|
||||
10
.github/ISSUE_TEMPLATE/discussion.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
---
|
||||
name: Discusssion
|
||||
about: Discuss a topic or change
|
||||
title: "[DISCUSSION] Your title here"
|
||||
labels: discussion
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
17
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FEATURE] Your title here"
|
||||
labels: feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
45
.github/workflows/build-linux.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Build and Release for Linux
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created] # Triggers only when a release is created
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Create Executable
|
||||
uses: Martin005/pyinstaller-action@v1.2.0
|
||||
with:
|
||||
python_ver: '3.10'
|
||||
spec: 'pyinst-compile.spec'
|
||||
requirements: 'requirements.txt'
|
||||
upload_exe_with_name: 'arnis'
|
||||
options: '--onefile --name arnis'
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./dist/arnis
|
||||
asset_name: arnis
|
||||
asset_content_type: application/octet-stream
|
||||
45
.github/workflows/build-windows.yml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Build and Release for Windows
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created] # Triggers only when a release is created
|
||||
|
||||
jobs:
|
||||
build-and-release:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Create Executable
|
||||
uses: Martin005/pyinstaller-action@v1.2.0
|
||||
with:
|
||||
python_ver: '3.10'
|
||||
spec: 'pyinst-compile.spec'
|
||||
requirements: 'requirements.txt'
|
||||
upload_exe_with_name: 'arnis.exe'
|
||||
options: '--onefile --name arnis'
|
||||
|
||||
- name: Build
|
||||
run: make build
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ github.event.release.upload_url }}
|
||||
asset_path: ./dist/arnis.exe
|
||||
asset_name: arnis.exe
|
||||
asset_content_type: application/octet-stream
|
||||
33
.github/workflows/ci-build.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: CI Build
|
||||
|
||||
# Trigger on pull request creation, update, and on pushes to the main branch
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.os == 'windows-latest' && 'x86_64-pc-windows-msvc' || 'x86_64-unknown-linux-gnu' || 'x86_64-apple-darwin' }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: cargo fetch
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
32
.github/workflows/python-app.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
|
||||
# Reduced subset of file from https://github.com/actions/starter-workflows/blob/main/ci/python-app.yml
|
||||
|
||||
name: Python application
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install flake8 pytest black
|
||||
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
||||
- name: Check the black styling
|
||||
run: |
|
||||
make style-check
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
make lint
|
||||
- name: Run unit tests
|
||||
run: |
|
||||
python -m pytest
|
||||
52
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Build and Release Rust Project
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Rust for Windows
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: x86_64-pc-windows-msvc
|
||||
|
||||
- name: Install dependencies
|
||||
run: cargo fetch
|
||||
|
||||
- name: Build
|
||||
run: cargo build --release
|
||||
|
||||
- name: Upload artifact (Windows)
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: windows-latest-build
|
||||
path: target/release/arnis.exe
|
||||
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download Windows build artifact
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: windows-latest-build
|
||||
path: ./builds/windows
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
builds/windows/arnis.exe
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
171
.gitignore
vendored
@@ -1,167 +1,4 @@
|
||||
# Original file is from https://github.com/github/gitignore/blob/main/Python.gitignore
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# Debug files
|
||||
arnis-debug-raw_data.json
|
||||
arnis-debug-processed_data.json
|
||||
arnis-debug-map.png
|
||||
image.img
|
||||
/target
|
||||
Cargo.lock
|
||||
export.json
|
||||
parsed_osm_data.txt
|
||||
25
Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "arnis"
|
||||
version = "2.0.0"
|
||||
edition = "2021"
|
||||
description = "Arnis - Generate real life cities in Minecraft"
|
||||
homepage = "https://github.com/louis-e/arnis"
|
||||
repository = "https://github.com/louis-e/arnis"
|
||||
license = "GPL-3.0"
|
||||
readme = "README.md"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
colored = "2.1.0"
|
||||
fastanvil = "0.31.0"
|
||||
fastnbt = "2.5.0"
|
||||
geo = "0.28.0"
|
||||
indicatif = "0.17.8"
|
||||
itertools = "0.13.0"
|
||||
nalgebra = "0.33.0"
|
||||
once_cell = "1.19.0"
|
||||
rand = "0.8.5"
|
||||
reqwest = { version = "0.12.7", features = ["blocking", "json"] }
|
||||
semver = "1.0.23"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
@@ -1,6 +0,0 @@
|
||||
FROM python:3.9
|
||||
RUN apt-get update && apt-get -y install git ffmpeg libsm6 libxext6
|
||||
RUN cd /home && mkdir /home/region && git clone https://github.com/louis-e/arnis.git
|
||||
WORKDIR /home/arnis
|
||||
RUN pip install -r requirements.txt
|
||||
ENTRYPOINT ["python", "arnis.py"]
|
||||
687
LICENSE
@@ -1,21 +1,674 @@
|
||||
MIT License
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (c) 2022 Louis
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Preamble
|
||||
|
||||
The above copyright notice, the author ("louis-e") and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
11
Makefile
@@ -1,11 +0,0 @@
|
||||
# Check if black formatter will work without any rewrites, and produces an exit code
|
||||
style-check:
|
||||
black src/ --check
|
||||
|
||||
# This will reformat all python files unders bookdifferent/ into the python black standard
|
||||
style:
|
||||
black src/
|
||||
|
||||
# Checks that the python source files are compliant regarding errors and style conventions
|
||||
lint:
|
||||
flake8 src/
|
||||
146
README.md
@@ -2,32 +2,37 @@
|
||||
<img width="456" height="125" src="https://github.com/louis-e/arnis/blob/main/gitassets/logo.png?raw=true">
|
||||
</p>
|
||||
|
||||
# Arnis [](https://github.com/louis-e/arnis/actions/workflows/python-app.yml)
|
||||
This open source project generates any chosen location from the real world in Minecraft, allowing users to explore and build in a virtual world that mirrors the real one.
|
||||
<br>
|
||||
# Arnis [](https://github.com/louis-e/arnis/actions)
|
||||
|
||||
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)
|
||||
|
||||
## :desktop_computer: Example
|
||||

|
||||

|
||||
<img width="700" height="400" src="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.
|
||||
|
||||
## :floppy_disk: How it works
|
||||

|
||||

|
||||
|
||||
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 the script, the following steps are performed automatically to generate a Minecraft world:
|
||||
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. Scraping Data from API: The script fetches geospatial data from the Overpass Turbo API.
|
||||
2. Determine Coordinate Extremes: Identifies the highest and lowest latitude and longitude values from the dataset.
|
||||
3. Standardize Coordinate Lengths: Ensures all coordinates are of uniform length and removes the decimal separator.
|
||||
4. Normalize Data: Adjusts all coordinates to start from zero by subtracting the previously determined lowest values.
|
||||
5. Parse Data: Transforms the raw data into a standardized structure.
|
||||
6. Sort elements by priority: Enables a layering system with prioritized elements.
|
||||
7. Optimize Array Size: Focuses on the outermost buildings to reduce array size.
|
||||
8. Generate Minecraft World: Iterates through the array to create the Minecraft world, including 3D structures like forests, houses, and rivers.
|
||||
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
|
||||
```python3 arnis.py --bbox="min_lng,min_lat,max_lng,max_lat" --path="C:/Users/username/AppData/Roaming/.minecraft/saves/worldname"```
|
||||
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.
|
||||

|
||||
The world will always be generated starting from the coordinates 0 0 0.
|
||||
@@ -35,103 +40,72 @@ 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.
|
||||
With the --timeout parameter you can set the timeout for the floodfill algorithm in seconds (default: 2).
|
||||
You can optionally use the parameter --debug to see processed value outputs during runtime.
|
||||
|
||||
#### Experimental City/State/Country Input Method
|
||||
The following method is experimental and may not perform as expected. Support is limited.
|
||||
|
||||
```python3 arnis.py --city="CityName" --state="StateName" --country="CountryName" --path="C:/Users/username/AppData/Roaming/.minecraft/saves/worldname"```
|
||||
|
||||
### Docker image (experimental)
|
||||
If you want to run this project containerized, you can use the Dockerfile provided in this repository. It will automatically scrape the latest source code from the repository. After running the container, you have to manually copy the generated region files from the container to the host machine in order to use them. When running the Docker image, set the ```--path``` parameter to ```/home```.
|
||||
```
|
||||
docker build -t arnis .
|
||||
docker run arnis --city="Arnis" --state="Schleswig Holstein" --country="Deutschland" --path="/home"
|
||||
docker cp CONTAINER_ID:/home/region DESTINATION_PATH
|
||||
```
|
||||
|
||||
## :cd: Requirements
|
||||
- Python 3
|
||||
- ```pip install -r requirements.txt```
|
||||
|
||||
- To conform with style guide please format any changes and check the code quality
|
||||
```black .```
|
||||
```flake8 src/```
|
||||
|
||||
- Functionality should be covered by automated tests.
|
||||
```python -m pytest```
|
||||
|
||||
## :question: FAQ
|
||||
- *Why do some cities take so long to generate?*<br>
|
||||
The script's performance can be significantly affected by large elements, such as extensive farmlands. The floodfill algorithm can slow down considerably when dealing with such elements, leading to long processing times. Thus there is also a timeout restriction in place, which can be adjusted by the user *[(see Usage)](#keyboard-usage)*. It is recommended to start with smaller areas to get a sense of the script's performance. Continuous improvements on the algorithm especially focus on effiency improvements.
|
||||
- *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.
|
||||
- *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>
|
||||
The script uses the [anvil-parser](https://github.com/matcool/anvil-parser) library to interact with Minecraft's world format. This library allows the script to create and manipulate Minecraft region files, enabling the generation of real-world locations within the game.
|
||||
The script uses the [fastnbt](https://github.com/owengage/fastnbt) cargo package to interact with Minecraft's world format. This library allows Arnis to manipulate Minecraft region files, enabling the generation of real-world locations.
|
||||
- *Where does the name come from?*<br>
|
||||
The project is named after Arnis[^2], the smallest city in Germany. The city's small size made it an ideal test case for developing and debugging the script efficiently.
|
||||
The project is named after the smallest city in Germany, Arnis[^2]. The city's small size made it an ideal test case for developing and debugging the algorithm efficiently.
|
||||
|
||||
## :memo: ToDo
|
||||
Feel free to choose an item from the To-Do or Known Bugs list, or bring your own idea to the table. Contributions from everyone are welcome and encouraged to help improve this project.
|
||||
- [ ] Look into https://github.com/Intergalactyc/anvil-new which seems to have a better support
|
||||
## :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
|
||||
- [ ] Automatic new world creation instead of using an existing world
|
||||
- [ ] Tool for mapping real coordinates to Minecraft coordinates
|
||||
- [ ] Fix railway orientation
|
||||
- [ ] Fix gaps in bridges
|
||||
- [ ] Full refactoring of variable and function names, establish naming conventions
|
||||
- [ ] Detection of wrong bbox input
|
||||
- [ ] Evaluate and implement multiprocessing in the ground layer initialization and floodfill algorithm
|
||||
- [ ] Implement elevation
|
||||
- [ ] Setup fork of [https://github.com/aaronr/bboxfinder.com](https://github.com/aaronr/bboxfinder.com) for easy bbox picking
|
||||
- [ ] Add interior to buildings
|
||||
- [ ] Save fountain structure in the code (similar to the tree structure)
|
||||
- [ ] Add windows to buildings
|
||||
- [ ] Evaluate and implement elevation
|
||||
- [ ] Generate a few big cities using high performance hardware and make them available to download
|
||||
- [ ] Optimize region file size
|
||||
- [ ] Street markings
|
||||
- [ ] Add better code comments
|
||||
- [x] Alternative reliable city input options
|
||||
- [x] Split up processData array into several smaller ones for big cities
|
||||
- [x] Find alternative for CV2 package
|
||||
- [x] Floodfill timeout parameter
|
||||
- [x] Automated Tests
|
||||
- [x] PEP8
|
||||
- [x] Use f-Strings in print statements
|
||||
- [x] Add Dockerfile
|
||||
- [x] Added path check
|
||||
- [x] Improve RAM usage
|
||||
- [x] Fix faulty empty chunks ([https://github.com/owengage/fastnbt/issues/120](https://github.com/owengage/fastnbt/issues/120)) (workaround found)
|
||||
|
||||
## :bug: Known Bugs
|
||||
- [ ] Docker image size
|
||||
- [x] 'Noer' bug (occurs when several different digits appear in coordinates before the decimal point)
|
||||
- [x] 'Nortorf' bug (occurs when there are several elements with a big distance to each other, e.g. the API returns several different cities with the exact same name)
|
||||
- [x] Saving step memory overflow
|
||||
- [x] Non uniform OSM naming standards (dashes) (See name tags at https://overpass-turbo.eu/s/1mMj)
|
||||
## :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 Rust’s 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.
|
||||
- **Cross-Platform Support**: Ensure the project runs smoothly on Windows, macOS, and Linux.
|
||||
|
||||
## :trophy: Hall of Fame 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!
|
||||
#### 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>
|
||||
|
||||
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
|
||||
|
||||
*(Including original Python implementation)*
|
||||
|
||||
## :star: Star History
|
||||
|
||||
[](https://star-history.com/#louis-e/arnis&Date)
|
||||
|
||||
## :copyright: License
|
||||
MIT License[^3]
|
||||
## :copyright: License Information
|
||||
This project is licensed under the GNU General Public License v3.0 (GPL-3.0).[^3]
|
||||
|
||||
Copyright (c) 2022 louis-e
|
||||
Copyright (c) 2022-2024 louis-e
|
||||
|
||||
[^1]: https://en.wikipedia.org/wiki/OpenStreetMap
|
||||
|
||||
[^2]: https://en.wikipedia.org/wiki/Arnis,_Germany
|
||||
|
||||
[^3]:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice, the author ("louis-e") and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
For the full license text, see the LICENSE file.
|
||||
|
||||
10
arnis.py
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2022 by louis-e, https://github.com/louis-e/.
|
||||
# MIT License
|
||||
# Please see the LICENSE file that should have been included as part of this package.
|
||||
|
||||
from src.main import run
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
Before Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 79 KiB |
BIN
gitassets/cli.gif
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
BIN
gitassets/mc.gif
Normal file
|
After Width: | Height: | Size: 9.7 MiB |
|
Before Width: | Height: | Size: 2.9 MiB |
|
Before Width: | Height: | Size: 2.5 MiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 2.2 MiB |
@@ -1,34 +0,0 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
import os
|
||||
import site
|
||||
|
||||
# Locate the site-packages directory
|
||||
site_packages_path = next(p for p in site.getsitepackages() if 'site-packages' in p)
|
||||
|
||||
# Path to the legacy_blocks.json file
|
||||
legacy_blocks_path = os.path.join(site_packages_path, 'anvil', 'legacy_blocks.json')
|
||||
|
||||
block_cipher = None
|
||||
|
||||
a = Analysis(['arnis.py'],
|
||||
pathex=['.'],
|
||||
binaries=[],
|
||||
datas=[(legacy_blocks_path, 'anvil')],
|
||||
hiddenimports=[],
|
||||
hookspath=[],
|
||||
runtime_hooks=[],
|
||||
excludes=[],
|
||||
cipher=block_cipher,
|
||||
noarchive=False)
|
||||
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||
exe = EXE(pyz,
|
||||
a.scripts,
|
||||
a.binaries,
|
||||
a.zipfiles,
|
||||
a.datas,
|
||||
name='arnis',
|
||||
debug=False,
|
||||
strip=False,
|
||||
upx=True,
|
||||
runtime_tmpdir=None,
|
||||
console=True )
|
||||
BIN
region.template
Normal file
@@ -1,10 +0,0 @@
|
||||
anvil-parser==0.9.0
|
||||
matplotlib==3.9.0
|
||||
numpy==1.26.4
|
||||
pytest==8.2.1
|
||||
python-polylabel==0.6
|
||||
requests==2.32.2
|
||||
argparse==1.4.0
|
||||
black==24.4.2
|
||||
flake8==7.0.0
|
||||
tqdm==4.66.5
|
||||
80
src/args.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use clap::{ArgGroup, Parser};
|
||||
use colored::Colorize;
|
||||
use std::path::Path;
|
||||
use std::process::exit;
|
||||
|
||||
/// Command-line arguments parser
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about)]
|
||||
#[command(group(
|
||||
ArgGroup::new("location")
|
||||
.required(true)
|
||||
.args(&["bbox", "file"])
|
||||
))]
|
||||
pub struct Args {
|
||||
/// Bounding box of the area (min_lng,min_lat,max_lng,max_lat) (required)
|
||||
#[arg(long)]
|
||||
pub bbox: Option<String>,
|
||||
|
||||
/// JSON file containing OSM data (optional)
|
||||
#[arg(long)]
|
||||
pub file: Option<String>,
|
||||
|
||||
/// Path to the Minecraft world (required)
|
||||
#[arg(long, required = true)]
|
||||
pub path: String,
|
||||
|
||||
/// Downloader method (requests/curl/wget) (optional)
|
||||
#[arg(long, default_value = "requests")]
|
||||
pub downloader: String,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl Args {
|
||||
pub fn run(&self) {
|
||||
// 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());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Validating bbox if provided
|
||||
if let Some(bbox) = &self.bbox {
|
||||
if !validate_bounding_box(bbox) {
|
||||
eprintln!("{}", "Error! Invalid bbox input".red().bold());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Validates the bounding box string
|
||||
fn validate_bounding_box(bbox: &str) -> bool {
|
||||
let parts: Vec<&str> = bbox.split(',').collect();
|
||||
if parts.len() != 4 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let min_lng: f64 = parts[0].parse().ok().unwrap_or(0.0);
|
||||
let min_lat: f64 = parts[1].parse().ok().unwrap_or(0.0);
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !(min_lat >= -90.0 && min_lat <= 90.0) || !(max_lat >= -90.0 && max_lat <= 90.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
min_lng < max_lng && min_lat < max_lat
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import anvil
|
||||
|
||||
air = anvil.Block("minecraft", "air")
|
||||
birch_leaves = anvil.Block("minecraft", "birch_leaves")
|
||||
birch_log = anvil.Block("minecraft", "birch_log")
|
||||
black_concrete = anvil.Block("minecraft", "black_concrete")
|
||||
blue_flower = anvil.Block("minecraft", "blue_orchid")
|
||||
brick = anvil.Block("minecraft", "bricks")
|
||||
carrots = anvil.Block("minecraft", "carrots", {"age": 7})
|
||||
cauldron = anvil.Block.from_numeric_id(118, 0)
|
||||
cobblestone = anvil.Block("minecraft", "cobblestone")
|
||||
cobblestone_wall = anvil.Block("minecraft", "cobblestone_wall")
|
||||
dark_oak_door_lower = anvil.Block("minecraft", "dark_oak_door", {"half": "lower"})
|
||||
dark_oak_door_upper = anvil.Block("minecraft", "dark_oak_door", {"half": "upper"})
|
||||
dirt = anvil.Block("minecraft", "dirt")
|
||||
farmland = anvil.Block("minecraft", "farmland")
|
||||
glass = anvil.Block("minecraft", "glass_pane")
|
||||
glowstone = anvil.Block("minecraft", "glowstone")
|
||||
grass = anvil.Block.from_numeric_id(175, 2)
|
||||
grass_block = anvil.Block("minecraft", "grass_block")
|
||||
gravel = anvil.Block("minecraft", "gravel")
|
||||
gray_concrete = anvil.Block("minecraft", "gray_concrete")
|
||||
green_stained_hardened_clay = anvil.Block.from_numeric_id(159, 5)
|
||||
hay_bale = anvil.Block("minecraft", "hay_block")
|
||||
iron_block = anvil.Block("minecraft", "iron_block")
|
||||
light_gray_concrete = anvil.Block("minecraft", "light_gray_concrete")
|
||||
oak_fence = anvil.Block.from_numeric_id(85, 0)
|
||||
oak_leaves = anvil.Block("minecraft", "oak_leaves")
|
||||
oak_log = anvil.Block.from_numeric_id(17)
|
||||
oak_planks = anvil.Block("minecraft", "oak_planks")
|
||||
podzol = anvil.Block.from_numeric_id(3, 2)
|
||||
potatoes = anvil.Block("minecraft", "potatoes", {"age": 7})
|
||||
rail = anvil.Block("minecraft", "rail")
|
||||
red_flower = anvil.Block.from_numeric_id(38)
|
||||
sand = anvil.Block("minecraft", "sand")
|
||||
scaffolding = anvil.Block("minecraft", "scaffolding")
|
||||
sponge = anvil.Block("minecraft", "sponge")
|
||||
spruce_log = anvil.Block("minecraft", "spruce_log")
|
||||
stone = anvil.Block("minecraft", "stone")
|
||||
stone_block_slab = anvil.Block.from_numeric_id(44, 0)
|
||||
stone_brick_slab = anvil.Block.from_numeric_id(44, 5)
|
||||
water = anvil.Block("minecraft", "water")
|
||||
wheat = anvil.Block("minecraft", "wheat", {"age": 7})
|
||||
white_concrete = anvil.Block("minecraft", "white_concrete")
|
||||
white_flower = anvil.Block("minecraft", "azure_bluet")
|
||||
white_stained_glass = anvil.Block("minecraft", "white_stained_glass")
|
||||
yellow_flower = anvil.Block("minecraft", "dandelion")
|
||||
|
||||
# Variations for building corners
|
||||
building_corner_variations = [
|
||||
anvil.Block("minecraft", "stone_bricks"),
|
||||
anvil.Block("minecraft", "cobblestone"),
|
||||
anvil.Block("minecraft", "bricks"),
|
||||
anvil.Block("minecraft", "mossy_cobblestone"),
|
||||
anvil.Block("minecraft", "sandstone"),
|
||||
anvil.Block("minecraft", "red_nether_bricks"),
|
||||
anvil.Block("minecraft", "blackstone"),
|
||||
anvil.Block("minecraft", "smooth_quartz"),
|
||||
anvil.Block("minecraft", "chiseled_stone_bricks"),
|
||||
anvil.Block("minecraft", "polished_basalt"),
|
||||
anvil.Block("minecraft", "cut_sandstone"),
|
||||
anvil.Block("minecraft", "polished_blackstone_bricks"),
|
||||
]
|
||||
|
||||
# Variations for building walls
|
||||
building_wall_variations = [
|
||||
anvil.Block("minecraft", "white_terracotta"),
|
||||
anvil.Block("minecraft", "gray_terracotta"),
|
||||
anvil.Block("minecraft", "bricks"),
|
||||
anvil.Block("minecraft", "smooth_sandstone"),
|
||||
anvil.Block("minecraft", "red_terracotta"),
|
||||
anvil.Block("minecraft", "polished_diorite"),
|
||||
anvil.Block("minecraft", "smooth_stone"),
|
||||
anvil.Block("minecraft", "polished_andesite"),
|
||||
anvil.Block("minecraft", "warped_planks"),
|
||||
anvil.Block("minecraft", "end_stone_bricks"),
|
||||
anvil.Block("minecraft", "smooth_red_sandstone"),
|
||||
anvil.Block("minecraft", "nether_bricks"),
|
||||
]
|
||||
|
||||
# Variations for building floors
|
||||
building_floor_variations = [
|
||||
anvil.Block("minecraft", "oak_planks"),
|
||||
anvil.Block("minecraft", "spruce_planks"),
|
||||
anvil.Block("minecraft", "dark_oak_planks"),
|
||||
anvil.Block("minecraft", "stone_bricks"),
|
||||
anvil.Block("minecraft", "polished_granite"),
|
||||
anvil.Block("minecraft", "polished_diorite"),
|
||||
anvil.Block("minecraft", "acacia_planks"),
|
||||
anvil.Block("minecraft", "jungle_planks"),
|
||||
anvil.Block("minecraft", "warped_planks"),
|
||||
anvil.Block("minecraft", "purpur_block"),
|
||||
anvil.Block("minecraft", "smooth_red_sandstone"),
|
||||
anvil.Block("minecraft", "polished_blackstone"),
|
||||
]
|
||||
253
src/block_definitions.rs
Normal file
@@ -0,0 +1,253 @@
|
||||
#![allow(unused)]
|
||||
|
||||
use fastnbt::Value;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Block {
|
||||
pub namespace: String,
|
||||
pub name: String,
|
||||
pub properties: Option<Value>,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn new(namespace: &str, name: &str, properties: Option<Value>) -> Self {
|
||||
Self {
|
||||
namespace: namespace.to_string(),
|
||||
name: name.to_string(),
|
||||
properties,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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
|
||||
})),
|
||||
)
|
||||
});
|
||||
|
||||
// Variations for building corners
|
||||
pub fn building_corner_variations() -> Vec<&'static Lazy<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,
|
||||
]
|
||||
}
|
||||
|
||||
// Variations for building walls
|
||||
pub fn building_wall_variations() -> Vec<&'static Lazy<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,
|
||||
]
|
||||
}
|
||||
|
||||
// Variations for building floors
|
||||
pub fn building_floor_variations() -> Vec<&'static Lazy<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,
|
||||
]
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
def bresenham(x1, y1, x2, y2):
|
||||
"""Bresenham Line Algorithm Credit: encukou/bresenham@Github"""
|
||||
dx = x2 - x1
|
||||
dy = y2 - y1
|
||||
|
||||
xsign = 1 if dx > 0 else -1
|
||||
ysign = 1 if dy > 0 else -1
|
||||
|
||||
dx = abs(dx)
|
||||
dy = abs(dy)
|
||||
|
||||
if dx > dy:
|
||||
xx, xy, yx, yy = xsign, 0, 0, ysign
|
||||
else:
|
||||
dx, dy = dy, dx
|
||||
xx, xy, yx, yy = 0, ysign, xsign, 0
|
||||
|
||||
D = 2 * dy - dx
|
||||
y = 0
|
||||
|
||||
for x in range(dx + 1):
|
||||
yield x1 + x * xx + y * yx, y1 + x * xy + y * yy
|
||||
if D >= 0:
|
||||
y += 1
|
||||
D -= 2 * dx
|
||||
D += 2 * dy
|
||||
76
src/bresenham.rs
Normal file
@@ -0,0 +1,76 @@
|
||||
/// 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)> {
|
||||
let mut points: Vec<(i32, i32, i32)> = Vec::new();
|
||||
|
||||
let dx: i32 = (x2 - x1).abs();
|
||||
let dy: i32 = (y2 - y1).abs();
|
||||
let dz: i32 = (z2 - z1).abs();
|
||||
|
||||
let xs: i32 = if x1 < x2 { 1 } else { -1 };
|
||||
let ys: i32 = if y1 < y2 { 1 } else { -1 };
|
||||
let zs: i32 = if z1 < z2 { 1 } else { -1 };
|
||||
|
||||
let mut x: i32 = x1;
|
||||
let mut y: i32 = y1;
|
||||
let mut z: i32 = z1;
|
||||
|
||||
if dx >= dy && dx >= dz {
|
||||
let mut p1: i32 = 2 * dy - dx;
|
||||
let mut p2: i32 = 2 * dz - dx;
|
||||
|
||||
while x != x2 {
|
||||
points.push((x, y, z));
|
||||
if p1 >= 0 {
|
||||
y += ys;
|
||||
p1 -= 2 * dx;
|
||||
}
|
||||
if p2 >= 0 {
|
||||
z += zs;
|
||||
p2 -= 2 * dx;
|
||||
}
|
||||
p1 += 2 * dy;
|
||||
p2 += 2 * dz;
|
||||
x += xs;
|
||||
}
|
||||
} else if dy >= dx && dy >= dz {
|
||||
let mut p1: i32 = 2 * dx - dy;
|
||||
let mut p2: i32 = 2 * dz - dy;
|
||||
|
||||
while y != y2 {
|
||||
points.push((x, y, z));
|
||||
if p1 >= 0 {
|
||||
x += xs;
|
||||
p1 -= 2 * dy;
|
||||
}
|
||||
if p2 >= 0 {
|
||||
z += zs;
|
||||
p2 -= 2 * dy;
|
||||
}
|
||||
p1 += 2 * dx;
|
||||
p2 += 2 * dz;
|
||||
y += ys;
|
||||
}
|
||||
} else {
|
||||
let mut p1: i32 = 2 * dy - dz;
|
||||
let mut p2: i32 = 2 * dx - dz;
|
||||
|
||||
while z != z2 {
|
||||
points.push((x, y, z));
|
||||
if p1 >= 0 {
|
||||
y += ys;
|
||||
p1 -= 2 * dz;
|
||||
}
|
||||
if p2 >= 0 {
|
||||
x += xs;
|
||||
p2 -= 2 * dz;
|
||||
}
|
||||
p1 += 2 * dy;
|
||||
p2 += 2 * dx;
|
||||
z += zs;
|
||||
}
|
||||
}
|
||||
|
||||
points.push((x2, y2, z2));
|
||||
points
|
||||
}
|
||||
137
src/data_processing.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use colored::Colorize;
|
||||
use crate::args::Args;
|
||||
use crate::element_processing::{*};
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::world_editor::WorldEditor;
|
||||
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";
|
||||
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, ®ion_dir, scale_factor_x, scale_factor_z, &args);
|
||||
|
||||
// Process data
|
||||
let process_pb: ProgressBar = ProgressBar::new(elements.len() 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("█▓░"));
|
||||
|
||||
for element in &elements {
|
||||
process_pb.inc(1);
|
||||
|
||||
if args.debug {
|
||||
process_pb.set_message(format!("(Element ID: {} / Type: {})", element.id, element.r#type));
|
||||
} 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") {
|
||||
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);
|
||||
}
|
||||
}
|
||||
"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") {
|
||||
barriers::generate_barriers(&mut editor, element, ground_level);
|
||||
} else if element.tags.contains_key("highway") {
|
||||
highways::generate_highways(&mut editor, element, ground_level);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
process_pb.finish();
|
||||
|
||||
// Generate ground layer
|
||||
let total_blocks: u64 = (scale_factor_x as i32 + 1) as u64 * (scale_factor_z as i32 + 1) as u64;
|
||||
let desired_updates: u64 = 1500;
|
||||
let batch_size: u64 = (total_blocks / desired_updates).max(1);
|
||||
|
||||
let mut block_counter: u64 = 0;
|
||||
|
||||
println!("{} {}", "[4/5]".bold(), "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("█▓░"));
|
||||
|
||||
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);
|
||||
|
||||
block_counter += 1;
|
||||
if block_counter % batch_size == 0 {
|
||||
ground_pb.inc(batch_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ground_pb.inc(block_counter % batch_size);
|
||||
ground_pb.finish();
|
||||
|
||||
// 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()?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
137
src/element_processing/amenities.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::floodfill::flood_fill_area;
|
||||
|
||||
pub fn generate_amenities(editor: &mut WorldEditor, element: &ProcessedElement, 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(amenity_type) = element.tags.get("amenity") {
|
||||
let first_node: Option<&(i32, i32)> = element.nodes.first();
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
|
||||
// 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 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(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);
|
||||
}
|
||||
}
|
||||
"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);
|
||||
}
|
||||
}
|
||||
"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);
|
||||
}
|
||||
}
|
||||
"parking" | "fountain" => {
|
||||
// Process parking or fountain areas
|
||||
let mut previous_node: Option<(i32, i32)> = None;
|
||||
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,
|
||||
};
|
||||
|
||||
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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current_amenity.push((node.0, node.1));
|
||||
corner_addup.0 += node.0;
|
||||
corner_addup.1 += node.1;
|
||||
corner_addup.2 += 1;
|
||||
}
|
||||
}
|
||||
previous_node = Some(node);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
for (x, z) in flood_area {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
src/element_processing/barriers.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
|
||||
pub fn generate_barriers(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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
|
||||
}
|
||||
} else {
|
||||
// Determine wall height
|
||||
let wall_height: i32 = element
|
||||
.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];
|
||||
|
||||
// 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);
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Add an optional top to the barrier if the height is more than 1
|
||||
if wall_height > 1 {
|
||||
editor.set_block(
|
||||
&STONE_BRICK_SLAB,
|
||||
bx,
|
||||
ground_level + wall_height + 1,
|
||||
bz,
|
||||
None,
|
||||
None,
|
||||
); // Top of the barrier
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
src/element_processing/bridges.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
|
||||
pub fn generate_bridges(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
if let Some(_bridge_type) = element.tags.get("bridge") {
|
||||
let bridge_height: i32 = element
|
||||
.tags
|
||||
.get("layer")
|
||||
.and_then(|layer: &String| layer.parse::<i32>().ok())
|
||||
.unwrap_or(1); // Default height if not specified
|
||||
|
||||
// Calculate the total length of the bridge
|
||||
let total_steps: usize = element
|
||||
.nodes
|
||||
.windows(2)
|
||||
.map(|nodes: &[(i32, i32)]| {
|
||||
let (x1, z1) = nodes[0];
|
||||
let (x2, z2) = nodes[1];
|
||||
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;
|
||||
|
||||
for i in 1..element.nodes.len() {
|
||||
let (x1, z1) = element.nodes[i - 1];
|
||||
let (x2, z2) = element.nodes[i];
|
||||
|
||||
// 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);
|
||||
|
||||
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)
|
||||
- ((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);
|
||||
for (offset_x, offset_z) in &[(-1, -1), (1, -1), (1, 1), (-1, 1)] {
|
||||
editor.set_block(
|
||||
&LIGHT_GRAY_CONCRETE,
|
||||
bx + offset_x,
|
||||
current_height,
|
||||
bz + offset_z,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
current_step += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
232
src/element_processing/buildings.rs
Normal file
@@ -0,0 +1,232 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::floodfill::flood_fill_area;
|
||||
use rand::Rng;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn generate_buildings(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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![];
|
||||
|
||||
// Randomly select block variations for corners, walls, and floors
|
||||
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
let variation_index_corner: usize = rng.gen_range(0..building_corner_variations().len());
|
||||
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;
|
||||
|
||||
// Set to store processed flood fill points
|
||||
let mut processed_points: HashSet<(i32, i32)> = HashSet::new();
|
||||
let mut building_height: i32 = 6; // Default building height
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 let Some(height_str) = element.tags.get("height") {
|
||||
if let Ok(height) = height_str.parse::<f64>() {
|
||||
building_height = height.round() as i32;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(building_type) = element.tags.get("building") {
|
||||
if building_type == "garage" {
|
||||
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);
|
||||
|
||||
// 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 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(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;
|
||||
|
||||
// Iterate through the nodes to create the roof edges using Bresenham's line algorithm
|
||||
for &node in &element.nodes {
|
||||
let (x, z) = node;
|
||||
|
||||
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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
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);
|
||||
}
|
||||
|
||||
previous_node = Some(node);
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
return;
|
||||
} else if building_type == "apartments" {
|
||||
// If building has no height attribute, assign a defined height
|
||||
if building_height == 6 {
|
||||
building_height = 15
|
||||
}
|
||||
} else if building_type == "hospital" {
|
||||
// If building has no height attribute, assign a defined height
|
||||
if building_height == 6 {
|
||||
building_height = 23
|
||||
}
|
||||
} else if building_type == "bridge" {
|
||||
generate_bridge(editor, element, ground_level);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Process nodes to create walls and corners
|
||||
for &node in &element.nodes {
|
||||
let (x, z) = node;
|
||||
|
||||
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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
for h in (ground_level + 1)..=(ground_level + building_height) {
|
||||
if (bx, bz) == element.nodes[0] {
|
||||
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
|
||||
} else {
|
||||
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
|
||||
current_building.push((bx, bz));
|
||||
corner_addup = (corner_addup.0 + bx, corner_addup.1 + bz, corner_addup.2 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
previous_node = Some(node);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
for (x, z) in floor_area {
|
||||
if processed_points.insert((x, z)) {
|
||||
editor.set_block(floor_block, x, ground_level, z, None, None); // Set floor
|
||||
|
||||
// Set level ceilings if height > 4
|
||||
if building_height > 4 {
|
||||
for h in (ground_level + 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
|
||||
} 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
|
||||
}
|
||||
|
||||
// Set the house ceiling
|
||||
editor.set_block(floor_block, x, ground_level + building_height + 1, z, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a bridge structure, paying attention to the "level" tag.
|
||||
fn generate_bridge(editor: &mut WorldEditor, element: &ProcessedElement, base_level: i32) {
|
||||
// Calculate the bridge level
|
||||
let mut bridge_level = 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;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
for (x, z) in bridge_area {
|
||||
editor.set_block(floor_block, x, bridge_level, z, None, None);
|
||||
}
|
||||
}
|
||||
25
src/element_processing/doors.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
|
||||
pub fn generate_doors(editor: &mut WorldEditor, element: &ProcessedElement, 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
|
||||
if let Some(level_str) = element.tags.get("level") {
|
||||
if let Ok(level) = level_str.parse::<i32>() {
|
||||
if level != 0 {
|
||||
return; // Skip doors not on ground level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
256
src/element_processing/highways.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
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
|
||||
|
||||
pub fn generate_highways(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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;
|
||||
for y in 1..=4 {
|
||||
editor.set_block(&OAK_FENCE, x, ground_level + y, 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 crossing_type == "traffic_signals" {
|
||||
if let Some(first_node) = element.nodes.first() {
|
||||
let (x, z) = *first_node;
|
||||
for y in 1..=3 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if highway_type == "bus_stop" {
|
||||
// Handle bus stops
|
||||
if let Some(first_node) = element.nodes.first() {
|
||||
let (x, z) = *first_node;
|
||||
for y in 1..=3 {
|
||||
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);
|
||||
}
|
||||
} else if element.tags.get("area").map_or(false, |v| v == "yes") {
|
||||
// Handle areas like pedestrian plazas
|
||||
let mut surface_block: &once_cell::sync::Lazy<Block> = &STONE; // Default block
|
||||
|
||||
// Determine the block type based on the 'surface' tag
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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_range: i32 = 2;
|
||||
let mut add_stripe = false; // Flag for adding stripes
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine block type and range based on highway type
|
||||
match highway_type.as_str() {
|
||||
"footway" | "pedestrian" => {
|
||||
block_type = &GRAY_CONCRETE;
|
||||
block_range = 1;
|
||||
}
|
||||
"path" => {
|
||||
block_type = &LIGHT_GRAY_CONCRETE;
|
||||
block_range = 1;
|
||||
}
|
||||
"motorway" | "primary" => {
|
||||
block_range = 5;
|
||||
add_stripe = true; // Add stripes for motorways and primary roads
|
||||
}
|
||||
"track" => {
|
||||
block_range = 1;
|
||||
}
|
||||
"service" => {
|
||||
block_type = &GRAY_CONCRETE;
|
||||
block_range = 2;
|
||||
}
|
||||
_ => {
|
||||
if let Some(lanes) = element.tags.get("lanes") {
|
||||
if lanes == "2" {
|
||||
block_range = 3;
|
||||
add_stripe = true;
|
||||
} else if lanes != "1" {
|
||||
block_range = 4;
|
||||
add_stripe = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate over nodes to create the highway
|
||||
for &node in &element.nodes {
|
||||
if let Some(prev) = previous_node {
|
||||
let (x1, z1) = prev;
|
||||
let (x2, z2) = node;
|
||||
|
||||
// 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
|
||||
|
||||
for (x, _, z) in bresenham_points {
|
||||
// Draw the road surface for the entire width
|
||||
for dx in -block_range..=block_range {
|
||||
for dz in -block_range..=block_range {
|
||||
let set_x: i32 = x + dx;
|
||||
let set_z: i32 = z + dz;
|
||||
|
||||
// Zebra crossing logic
|
||||
if highway_type == "footway"
|
||||
&& 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,
|
||||
set_x,
|
||||
ground_level,
|
||||
set_z,
|
||||
Some(&[&BLACK_CONCRETE]),
|
||||
None,
|
||||
);
|
||||
} else {
|
||||
editor.set_block(
|
||||
&BLACK_CONCRETE,
|
||||
set_x,
|
||||
ground_level,
|
||||
set_z,
|
||||
None,
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
editor.set_block(
|
||||
block_type,
|
||||
set_x,
|
||||
ground_level,
|
||||
set_z,
|
||||
None,
|
||||
Some(&[&BLACK_CONCRETE, &WHITE_CONCRETE]),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a dashed white line in the middle for larger roads
|
||||
if add_stripe {
|
||||
if stripe_length < dash_length {
|
||||
let stripe_x: i32 = x;
|
||||
let stripe_z: i32 = z;
|
||||
editor.set_block(
|
||||
&WHITE_CONCRETE,
|
||||
stripe_x,
|
||||
ground_level,
|
||||
stripe_z,
|
||||
Some(&[&BLACK_CONCRETE]),
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
// Increment stripe_length and reset after completing a dash and gap
|
||||
stripe_length += 1;
|
||||
if stripe_length >= dash_length + gap_length {
|
||||
stripe_length = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
previous_node = Some(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a siding using stone brick slabs
|
||||
pub fn generate_siding(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
let mut previous_node: Option<(i32, i32)> = None;
|
||||
let siding_block: &once_cell::sync::Lazy<Block> = &STONE_BRICK_SLAB;
|
||||
|
||||
for &node in &element.nodes {
|
||||
let (x, z) = node;
|
||||
|
||||
// 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);
|
||||
for (bx, by, bz) in bresenham_points {
|
||||
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);
|
||||
}
|
||||
}
|
||||
208
src/element_processing/landuse.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::floodfill::flood_fill_area;
|
||||
use crate::element_processing::tree::create_tree;
|
||||
use rand::Rng;
|
||||
|
||||
pub fn generate_landuse(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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![];
|
||||
|
||||
// Determine block type based on landuse tag
|
||||
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,
|
||||
};
|
||||
|
||||
// Process landuse nodes to fill the area
|
||||
for &node in &element.nodes {
|
||||
let (x, z) = node;
|
||||
|
||||
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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
editor.set_block(&GRASS_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);
|
||||
}
|
||||
|
||||
// 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 mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
|
||||
for (x, z) in floor_area {
|
||||
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]));
|
||||
} else {
|
||||
editor.set_block(block_type, x, ground_level, z, None, None);
|
||||
}
|
||||
|
||||
// Add specific features for different landuse types
|
||||
match landuse_tag.as_str() {
|
||||
"cemetery" => {
|
||||
if (x % 3 == 0) && (z % 3 == 0) {
|
||||
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 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);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} else if random_choice < 33 {
|
||||
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
|
||||
}
|
||||
}
|
||||
}
|
||||
"forest" => {
|
||||
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));
|
||||
} 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,
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
"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 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);
|
||||
} else {
|
||||
// Set the block below as farmland
|
||||
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);
|
||||
if special_choice <= 2 {
|
||||
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
|
||||
} else if special_choice <= 6 {
|
||||
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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"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);
|
||||
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);
|
||||
} 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);
|
||||
} 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);
|
||||
}
|
||||
} 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,
|
||||
];
|
||||
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);
|
||||
} 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);
|
||||
}
|
||||
} else if random_choice < 150 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
"meadow" => {
|
||||
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));
|
||||
} else if random_choice < 800 {
|
||||
editor.set_block(&GRASS, x, ground_level + 1, z, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/element_processing/leisure.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::floodfill::flood_fill_area;
|
||||
use crate::element_processing::tree::create_tree;
|
||||
use rand::Rng;
|
||||
|
||||
pub fn generate_leisure(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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,
|
||||
};
|
||||
|
||||
// Process leisure area 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);
|
||||
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);
|
||||
}
|
||||
|
||||
current_leisure.push((node.0, node.1));
|
||||
corner_addup.0 += node.0;
|
||||
corner_addup.1 += node.1;
|
||||
corner_addup.2 += 1;
|
||||
}
|
||||
previous_node = Some(node);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
for (x, z) in filled_area {
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add playground or recreation ground features
|
||||
if matches!(leisure_type.as_str(), "playground" | "recreation_ground") {
|
||||
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
let random_choice: i32 = rng.gen_range(0..5000);
|
||||
|
||||
match random_choice {
|
||||
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, 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);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/element_processing/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
pub mod amenities;
|
||||
pub mod barriers;
|
||||
pub mod bridges;
|
||||
pub mod buildings;
|
||||
pub mod doors;
|
||||
pub mod highways;
|
||||
pub mod landuse;
|
||||
pub mod leisure;
|
||||
pub mod natural;
|
||||
pub mod railways;
|
||||
pub mod tree;
|
||||
pub mod waterways;
|
||||
84
src/element_processing/natural.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
use crate::floodfill::flood_fill_area;
|
||||
use crate::element_processing::tree::create_tree;
|
||||
use rand::Rng;
|
||||
|
||||
pub fn generate_natural(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
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;
|
||||
let mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
create_tree(editor, x, ground_level + 1, z, rng.gen_range(1..=3));
|
||||
}
|
||||
} else {
|
||||
let mut previous_node: Option<(i32, i32)> = None;
|
||||
let mut corner_addup: (i32, i32, i32) = (0, 0, 0);
|
||||
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,
|
||||
};
|
||||
|
||||
// Process natural nodes to fill the area
|
||||
for &node in &element.nodes {
|
||||
let (x, z) = node;
|
||||
|
||||
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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
editor.set_block(block_type, bx, ground_level, bz, None, None);
|
||||
}
|
||||
|
||||
current_natural.push((x, z));
|
||||
corner_addup = (corner_addup.0 + x, corner_addup.1 + z, corner_addup.2 + 1);
|
||||
}
|
||||
|
||||
previous_node = Some(node);
|
||||
}
|
||||
|
||||
// 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 mut rng: rand::prelude::ThreadRng = rand::thread_rng();
|
||||
|
||||
for (x, z) in filled_area {
|
||||
editor.set_block(block_type, x, ground_level, z, None, None);
|
||||
|
||||
// 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])) {
|
||||
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));
|
||||
} 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,
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/element_processing/railways.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
|
||||
pub fn generate_railways(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
if let Some(railway_type) = element.tags.get("railway") {
|
||||
if ["proposed", "abandoned", "subway", "construction"].contains(&railway_type.as_str()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(subway) = element.tags.get("subway") {
|
||||
if subway == "yes" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(tunnel) = element.tags.get("tunnel") {
|
||||
if tunnel == "yes" {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 1..element.nodes.len() {
|
||||
let (x1, z1) = element.nodes[i - 1];
|
||||
let (x2, z2) = element.nodes[i];
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
if bx % 4 == 0 {
|
||||
editor.set_block(&OAK_LOG, bx, ground_level, bz, None, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/element_processing/tree.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
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) {
|
||||
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);
|
||||
editor.set_block(material, x, y, z + 2, None, None);
|
||||
editor.set_block(material, x - 1, y, z - 1, None, None);
|
||||
editor.set_block(material, x + 1, y, z + 1, None, None);
|
||||
editor.set_block(material, x + 1, y, z - 1, None, None);
|
||||
editor.set_block(material, x - 1, y, z + 1, None, None);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
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);
|
||||
editor.set_block(material, x + 1, y, z - 2, None, None);
|
||||
editor.set_block(material, x + 1, y, z + 2, None, None);
|
||||
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);
|
||||
editor.set_block(material, x - 1, y, z + 2, None, None);
|
||||
editor.set_block(material, x - 1, y, z - 2, None, None);
|
||||
editor.set_block(material, x, y, z - 3, None, None);
|
||||
editor.set_block(material, x, y, z + 3, None, None);
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
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);
|
||||
editor.set_block(material, x + 2, y, z + 2, None, None);
|
||||
editor.set_block(material, x + 1, y, z - 3, None, None);
|
||||
editor.set_block(material, x + 1, y, z + 3, None, None);
|
||||
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);
|
||||
editor.set_block(material, x - 2, y, z + 2, None, None);
|
||||
editor.set_block(material, x - 1, y, z + 3, None, None);
|
||||
editor.set_block(material, x - 1, y, z - 3, None, None);
|
||||
}
|
||||
|
||||
/// 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();
|
||||
blacklist.extend(building_corner_variations());
|
||||
blacklist.extend(building_wall_variations());
|
||||
blacklist.extend(building_floor_variations());
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
_ => {} // Do nothing if typetree is not recognized
|
||||
}
|
||||
}
|
||||
45
src/element_processing/waterways.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
use crate::world_editor::WorldEditor;
|
||||
use crate::osm_parser::ProcessedElement;
|
||||
use crate::block_definitions::*;
|
||||
use crate::bresenham::bresenham_line;
|
||||
|
||||
pub fn generate_waterways(editor: &mut WorldEditor, element: &ProcessedElement, ground_level: i32) {
|
||||
if let Some(_waterway_type) = element.tags.get("waterway") {
|
||||
let mut previous_node: Option<(i32, i32)> = None;
|
||||
let mut waterway_width: i32 = 4; // Default waterway width
|
||||
|
||||
// Check for custom width in tags
|
||||
if let Some(width_str) = element.tags.get("width") {
|
||||
waterway_width = width_str.parse::<i32>().unwrap_or_else(|_| {
|
||||
width_str
|
||||
.parse::<f32>()
|
||||
.map(|f: f32| f as i32)
|
||||
.unwrap_or(waterway_width)
|
||||
});
|
||||
}
|
||||
|
||||
// Process nodes to create waterways
|
||||
for &node in &element.nodes {
|
||||
if let Some(prev) = previous_node {
|
||||
// Skip layers below the ground level
|
||||
if !matches!(
|
||||
element.tags.get("layer").map(|s| s.as_str()),
|
||||
Some("-1") | Some("-2") | Some("-3")
|
||||
) {
|
||||
// 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);
|
||||
for (bx, _, bz) in bresenham_points {
|
||||
for x in (bx - waterway_width / 2)..=(bx + waterway_width / 2) {
|
||||
for z in (bz - waterway_width / 2)..=(bz + waterway_width / 2) {
|
||||
editor.set_block(&WATER, x, ground_level, z, None, None); // Set water block
|
||||
editor.set_block(&AIR, x, ground_level + 1, z, Some(&[&GRASS, &WHEAT, &CARROTS, &POTATOES]), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
previous_node = Some(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
79
src/floodfill.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::time::{Duration, Instant};
|
||||
use geo::{LineString, Point, Polygon, Contains};
|
||||
use itertools::Itertools;
|
||||
|
||||
/// Perform a flood-fill to find the area inside a polygon.
|
||||
/// Returns a vector of (x, z) coordinates representing the filled area.
|
||||
pub fn flood_fill_area(polygon_coords: &[(i32, i32)], timeout_secs: u64) -> Vec<(i32, i32)> {
|
||||
if polygon_coords.len() < 3 {
|
||||
return vec![]; // Not a valid polygon
|
||||
}
|
||||
|
||||
let start_time = Instant::now();
|
||||
let timeout = Duration::from_secs(timeout_secs);
|
||||
|
||||
// Calculate bounding box of the polygon using itertools
|
||||
let (min_x, max_x) = polygon_coords.iter().map(|&(x, _)| x).minmax().into_option().unwrap();
|
||||
let (min_z, max_z) = polygon_coords.iter().map(|&(_, z)| z).minmax().into_option().unwrap();
|
||||
|
||||
let mut filled_area: Vec<(i32, i32)> = Vec::new();
|
||||
let mut visited: HashSet<(i32, i32)> = HashSet::new();
|
||||
|
||||
// Convert input to a geo::Polygon for efficient point-in-polygon testing
|
||||
let exterior_coords: Vec<(f64, f64)> = polygon_coords.iter().map(|&(x, z)| (x as f64, z as f64)).collect::<Vec<_>>();
|
||||
let exterior: LineString = LineString::from(exterior_coords); // Create LineString from coordinates
|
||||
let polygon: Polygon<f64> = Polygon::new(exterior, vec![]); // Create Polygon using LineString
|
||||
|
||||
// Determine safe step sizes for grid sampling
|
||||
let step_x = ((max_x - min_x) / 10).max(1); // Ensure step is at least 1
|
||||
let step_z = ((max_z - min_z) / 10).max(1); // Ensure step is at least 1
|
||||
|
||||
// Sample multiple starting points within the bounding box
|
||||
let mut candidate_points: VecDeque<(i32, i32)> = VecDeque::new();
|
||||
for x in (min_x..=max_x).step_by(step_x as usize) {
|
||||
for z in (min_z..=max_z).step_by(step_z as usize) {
|
||||
candidate_points.push_back((x, z));
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt flood-fill from each candidate point
|
||||
while let Some((start_x, start_z)) = candidate_points.pop_front() {
|
||||
if start_time.elapsed() > timeout {
|
||||
eprintln!("Floodfill timeout"); // TODO only print when debug arg is set?
|
||||
break;
|
||||
}
|
||||
|
||||
if polygon.contains(&Point::new(start_x as f64, start_z as f64)) {
|
||||
// Start flood-fill from the valid interior point
|
||||
let mut queue: VecDeque<(i32, i32)> = VecDeque::new();
|
||||
queue.push_back((start_x, start_z));
|
||||
visited.insert((start_x, start_z));
|
||||
|
||||
while let Some((x, z)) = queue.pop_front() {
|
||||
if start_time.elapsed() > timeout {
|
||||
eprintln!("Floodfill timeout"); // TODO only print when debug arg is set?
|
||||
break;
|
||||
}
|
||||
|
||||
if polygon.contains(&Point::new(x as f64, z as f64)) {
|
||||
filled_area.push((x, z));
|
||||
|
||||
// Check adjacent points
|
||||
for (nx, nz) in [(x - 1, z), (x + 1, z), (x, z - 1), (x, z + 1)].iter() {
|
||||
if *nx >= min_x && *nx <= max_x && *nz >= min_z && *nz <= max_z && !visited.contains(&(*nx, *nz)) {
|
||||
visited.insert((*nx, *nz));
|
||||
queue.push_back((*nx, *nz));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !filled_area.is_empty() {
|
||||
break; // Exit if a valid area has been flood-filled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filled_area
|
||||
}
|
||||
166
src/getData.py
@@ -1,166 +0,0 @@
|
||||
from random import choice
|
||||
import json
|
||||
import os
|
||||
import requests
|
||||
import subprocess
|
||||
|
||||
|
||||
def download_with_requests(url, params, filename):
|
||||
response = requests.get(url, params=params)
|
||||
if response.status_code == 200:
|
||||
with open(filename, "w") as file:
|
||||
json.dump(response.json(), file)
|
||||
return filename
|
||||
else:
|
||||
print("Failed to download data. Status code:", response.status_code)
|
||||
return None
|
||||
|
||||
|
||||
def download_with_curl(url, params, filename):
|
||||
# Prepare curl command with parameters
|
||||
curl_command = [
|
||||
"curl",
|
||||
"-o",
|
||||
filename,
|
||||
url + "?" + "&".join([f"{key}={value}" for key, value in params.items()]),
|
||||
]
|
||||
subprocess.call(curl_command)
|
||||
return filename
|
||||
|
||||
|
||||
def download_with_wget(url, params, filename):
|
||||
# Prepare wget command with parameters
|
||||
wget_command = [
|
||||
"wget",
|
||||
"-O",
|
||||
filename,
|
||||
url + "?" + "&".join([f"{key}={value}" for key, value in params.items()]),
|
||||
]
|
||||
subprocess.call(wget_command)
|
||||
return filename
|
||||
|
||||
|
||||
def getData(city, state, country, bbox, file, debug, download_method="requests"):
|
||||
print("Fetching data...")
|
||||
api_servers = [
|
||||
"https://overpass-api.de/api/interpreter",
|
||||
"https://lz4.overpass-api.de/api/interpreter",
|
||||
"https://z.overpass-api.de/api/interpreter",
|
||||
"https://overpass.kumi.systems/api/interpreter",
|
||||
"https://overpass.private.coffee/api/interpreter",
|
||||
]
|
||||
url = choice(api_servers)
|
||||
|
||||
if city:
|
||||
query1 = f"""
|
||||
[out:json];
|
||||
area[name="{city}"]->.city;
|
||||
area[name="{state}"]->.state;
|
||||
area[name="{country}"]->.country;
|
||||
(
|
||||
nwr(area.country)(area.state)(area.city)[building];
|
||||
nwr(area.country)(area.state)(area.city)[highway];
|
||||
nwr(area.country)(area.state)(area.city)[landuse];
|
||||
nwr(area.country)(area.state)(area.city)[natural];
|
||||
nwr(area.country)(area.state)(area.city)[leisure];
|
||||
nwr(area.country)(area.state)(area.city)[waterway]["waterway"!="fairway"];
|
||||
nwr(area.country)(area.state)(area.city)[amenity];
|
||||
nwr(area.country)(area.state)(area.city)[bridge];
|
||||
nwr(area.country)(area.state)(area.city)[railway];
|
||||
nwr(area.country)(area.state)(area.city)[barrier];
|
||||
);
|
||||
(._;>;);
|
||||
out;
|
||||
"""
|
||||
elif bbox:
|
||||
bbox = bbox.split(",")
|
||||
bbox = [float(i) for i in bbox]
|
||||
if debug:
|
||||
print(f"Bbox input: {bbox}")
|
||||
|
||||
query1 = f"""
|
||||
[out:json][bbox:{bbox[1]},{bbox[0]},{bbox[3]},{bbox[2]}];
|
||||
(
|
||||
nwr["building"];
|
||||
nwr["highway"];
|
||||
nwr["landuse"];
|
||||
nwr["natural"];
|
||||
nwr["leisure"];
|
||||
nwr["waterway"];
|
||||
nwr["amenity"];
|
||||
nwr["bridge"];
|
||||
nwr["railway"];
|
||||
nwr["barrier"];
|
||||
nwr["entrance"];
|
||||
nwr["door"];
|
||||
)->.waysinbbox;
|
||||
(
|
||||
node(w.waysinbbox);
|
||||
)->.nodesinbbox;
|
||||
.waysinbbox out body;
|
||||
.nodesinbbox out skel qt;
|
||||
"""
|
||||
elif file:
|
||||
print("Loading data from file")
|
||||
else:
|
||||
query1 = f"""
|
||||
[out:json];
|
||||
area[name="{city}"]->.city;
|
||||
area[name="{country}"]->.country;
|
||||
(
|
||||
nwr(area.country)(area.city)[building];
|
||||
nwr(area.country)(area.city)[highway];
|
||||
nwr(area.country)(area.city)[landuse];
|
||||
nwr(area.country)(area.city)[natural];
|
||||
nwr(area.country)(area.city)[leisure];
|
||||
nwr(area.country)(area.city)[waterway]["waterway"!="fairway"];
|
||||
nwr(area.country)(area.city)[amenity];
|
||||
nwr(area.country)(area.city)[bridge];
|
||||
nwr(area.country)(area.city)[railway];
|
||||
nwr(area.country)(area.city)[barrier];
|
||||
);
|
||||
(._;>;);
|
||||
out;
|
||||
"""
|
||||
|
||||
if debug:
|
||||
print(f"OSM Query: {query1}")
|
||||
|
||||
try:
|
||||
if file:
|
||||
with open("data.json", encoding="utf8") as dataset:
|
||||
data = json.load(dataset)
|
||||
else:
|
||||
if debug:
|
||||
print(f"Chosen server: {url}")
|
||||
filename = "arnis-debug-raw_data.json"
|
||||
if download_method == "requests":
|
||||
file_path = download_with_requests(url, {"data": query1}, filename)
|
||||
elif download_method == "curl":
|
||||
file_path = download_with_curl(url, {"data": query1}, filename)
|
||||
elif download_method == "wget":
|
||||
file_path = download_with_wget(url, {"data": query1}, filename)
|
||||
else:
|
||||
print("Invalid download method. Using 'requests' by default.")
|
||||
file_path = download_with_requests(url, {"data": query1}, filename)
|
||||
|
||||
if file_path is None:
|
||||
os._exit(1)
|
||||
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
|
||||
if len(data["elements"]) == 0:
|
||||
print("Error! No data available")
|
||||
os._exit(1)
|
||||
|
||||
except Exception as e:
|
||||
if "The server is probably too busy to handle your request." in str(e):
|
||||
print("Error! OSM server overloaded")
|
||||
elif "Dispatcher_Client::request_read_and_idx::rate_limited" in str(e):
|
||||
print("Error! IP rate limited, wait before trying again")
|
||||
else:
|
||||
print(f"Error! {e}")
|
||||
os._exit(1)
|
||||
|
||||
return data
|
||||
106
src/main.py
@@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# Copyright 2022 by louis-e, https://github.com/louis-e/.
|
||||
# MIT License
|
||||
# Please see the LICENSE file that should have been included as part of this package.
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import numpy as np
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .getData import getData
|
||||
from .processData import processRawData, generateWorld
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Arnis - Generate cities from real life in Minecraft"
|
||||
)
|
||||
parser.add_argument("--bbox", dest="bbox", help="Bounding box of the area")
|
||||
parser.add_argument("--city", dest="city", help="Name of the city (Experimental)")
|
||||
parser.add_argument("--state", dest="state", help="Name of the state (Experimental)")
|
||||
parser.add_argument(
|
||||
"--country", dest="country", help="Name of the country (Experimental)"
|
||||
)
|
||||
parser.add_argument("--file", dest="file", help="JSON file containing OSM data")
|
||||
parser.add_argument(
|
||||
"--path", dest="path", required=True, help="Path to the minecraft world"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--downloader",
|
||||
dest="downloader",
|
||||
choices=["requests", "curl", "wget"],
|
||||
default="requests",
|
||||
help="Downloader method (requests/curl/wget)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug",
|
||||
dest="debug",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Enable debug mode",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
dest="timeout",
|
||||
default=2,
|
||||
action="store_true",
|
||||
help="Set floodfill timeout (seconds)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Ensure either bbox or city/state/country is provided
|
||||
if not args.bbox and not (args.city and args.state and args.country):
|
||||
print(
|
||||
"""Error! You must provide either a bounding box (bbox) or city/state/country \
|
||||
(experimental) information."""
|
||||
)
|
||||
os._exit(1)
|
||||
|
||||
# Ensure file argument is handled correctly
|
||||
if args.file and args.bbox:
|
||||
print("Error! You cannot provide both a bounding box (bbox) and a file.")
|
||||
os._exit(1)
|
||||
|
||||
gc.collect()
|
||||
np.seterr(all="raise")
|
||||
np.set_printoptions(threshold=sys.maxsize)
|
||||
|
||||
mcWorldPath = args.path
|
||||
if mcWorldPath[-1] == "/":
|
||||
mcWorldPath = mcWorldPath[:-1]
|
||||
|
||||
|
||||
def run():
|
||||
print(
|
||||
"""\n
|
||||
▄████████ ▄████████ ███▄▄▄▄ ▄█ ▄████████
|
||||
███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███▌ ███ █▀
|
||||
███ ███ ▄███▄▄▄▄██▀ ███ ███ ███▌ ███
|
||||
▀███████████ ▀▀███▀▀▀▀▀ ███ ███ ███▌ ▀███████████
|
||||
███ ███ ▀███████████ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ▄█ ███
|
||||
███ █▀ ███ ███ ▀█ █▀ █▀ ▄████████▀
|
||||
███ ███
|
||||
|
||||
https://github.com/louis-e/arnis
|
||||
"""
|
||||
)
|
||||
|
||||
if not (os.path.exists(mcWorldPath + "/region")):
|
||||
print("Error! No Minecraft world found at given path")
|
||||
os._exit(1)
|
||||
|
||||
rawdata = getData(
|
||||
args.city,
|
||||
args.state,
|
||||
args.country,
|
||||
args.bbox,
|
||||
args.file,
|
||||
args.debug,
|
||||
args.downloader,
|
||||
)
|
||||
rawData = processRawData(rawdata, args)
|
||||
generateWorld(rawData)
|
||||
os._exit(0)
|
||||
85
src/main.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
mod args;
|
||||
mod block_definitions;
|
||||
mod bresenham;
|
||||
mod data_processing;
|
||||
mod element_processing;
|
||||
mod floodfill;
|
||||
mod osm_parser;
|
||||
mod retrieve_data;
|
||||
mod version_check;
|
||||
mod world_editor;
|
||||
|
||||
use args::Args;
|
||||
use clap::Parser;
|
||||
use colored::*;
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
|
||||
fn print_banner() {
|
||||
let version: &str = env!("CARGO_PKG_VERSION");
|
||||
let repository: &str = env!("CARGO_PKG_REPOSITORY");
|
||||
println!(
|
||||
r#"
|
||||
▄████████ ▄████████ ███▄▄▄▄ ▄█ ▄████████
|
||||
███ ███ ███ ███ ███▀▀▀██▄ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███▌ ███ █▀
|
||||
███ ███ ▄███▄▄▄▄██▀ ███ ███ ███▌ ███
|
||||
▀███████████ ▀▀███▀▀▀▀▀ ███ ███ ███▌ ▀███████████
|
||||
███ ███ ▀███████████ ███ ███ ███ ███
|
||||
███ ███ ███ ███ ███ ███ ███ ▄█ ███
|
||||
███ █▀ ███ ███ ▀█ █▀ █▀ ▄████████▀
|
||||
███ ███
|
||||
|
||||
version {}
|
||||
{}
|
||||
"#,
|
||||
version,
|
||||
repository.bright_white().bold()
|
||||
);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
print_banner();
|
||||
|
||||
// Check for updates
|
||||
if let Err(e) = version_check::check_for_updates() {
|
||||
eprintln!("{}: {}", "Error checking for version updates".red().bold(), e);
|
||||
}
|
||||
|
||||
// Parse input arguments
|
||||
let args: Args = Args::parse();
|
||||
args.run();
|
||||
|
||||
let bbox: Vec<f64> = args.bbox
|
||||
.as_ref()
|
||||
.expect("Bounding box is required")
|
||||
.split(',')
|
||||
.map(|s: &str| s.parse::<f64>().expect("Invalid bbox coordinate"))
|
||||
.collect::<Vec<f64>>();
|
||||
|
||||
let bbox_tuple: (f64, f64, f64, f64) = (bbox[0], bbox[1], bbox[2], bbox[3]);
|
||||
|
||||
// Fetch data
|
||||
let raw_data: serde_json::Value = retrieve_data::fetch_data(
|
||||
bbox_tuple,
|
||||
args.file.as_deref(),
|
||||
args.debug,
|
||||
"requests",
|
||||
).expect("Failed to fetch data");
|
||||
|
||||
// Parse raw data
|
||||
let (mut parsed_elements, scale_factor_x, scale_factor_z) = osm_parser::parse_osm_data(&raw_data, bbox_tuple, &args);
|
||||
parsed_elements.sort_by_key(|element: &osm_parser::ProcessedElement| osm_parser::get_priority(element));
|
||||
|
||||
// Write the parsed OSM data to a file for inspection
|
||||
if args.debug {
|
||||
let mut output_file: File = File::create("parsed_osm_data.txt").expect("Failed to create output file");
|
||||
for element in &parsed_elements {
|
||||
writeln!(output_file, "Element ID: {}, Type: {}, Tags: {:?}, Nodes: {:?}", element.id, element.r#type, element.tags, element.nodes)
|
||||
.expect("Failed to write to output file");
|
||||
}
|
||||
}
|
||||
|
||||
// Generate world
|
||||
data_processing::generate_world(parsed_elements, &args, scale_factor_x, scale_factor_z);
|
||||
}
|
||||
181
src/osm_parser.rs
Normal file
@@ -0,0 +1,181 @@
|
||||
use crate::args::Args;
|
||||
use colored::Colorize;
|
||||
use serde_json::Value;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct OSMElement {
|
||||
pub r#type: String,
|
||||
pub id: u64,
|
||||
pub lat: Option<f64>,
|
||||
pub lon: Option<f64>,
|
||||
pub nodes: Option<Vec<u64>>,
|
||||
pub tags: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct OSMData {
|
||||
pub elements: Vec<OSMElement>,
|
||||
}
|
||||
|
||||
pub struct ProcessedElement {
|
||||
pub id: u64,
|
||||
pub r#type: String,
|
||||
pub tags: HashMap<String, String>,
|
||||
pub nodes: Vec<(i32, i32)>, // Minecraft coordinates (x, z)
|
||||
}
|
||||
|
||||
// Function to convert latitude and longitude to Minecraft coordinates.
|
||||
fn lat_lon_to_minecraft_coords(
|
||||
lat: f64,
|
||||
lon: f64,
|
||||
bbox: (f64, f64, f64, f64), // (min_lon, min_lat, max_lon, max_lat)
|
||||
scale_factor_x: f64,
|
||||
scale_factor_z: f64,
|
||||
) -> (i32, i32) {
|
||||
let (min_lon, min_lat, max_lon, max_lat) = bbox;
|
||||
|
||||
// Calculate the relative position within the bounding box
|
||||
let rel_x: f64 = (lon - min_lon) / (max_lon - min_lon);
|
||||
let rel_z: f64 = (lat - min_lat) / (max_lat - min_lat);
|
||||
|
||||
// Apply scaling factors for each dimension and convert to Minecraft coordinates
|
||||
let x: i32 = (rel_x * scale_factor_x) as i32;
|
||||
let z: i32 = (rel_z * scale_factor_z) as i32;
|
||||
|
||||
(z, x) // Swap x and z coords to avoid a mirrored projection on the Minecraft map
|
||||
}
|
||||
|
||||
/// Function to determine the number of decimal places in a float as a string
|
||||
fn count_decimal_places(value: f64) -> usize {
|
||||
let s: String = value.to_string();
|
||||
if let Some(pos) = s.find('.') {
|
||||
s.len() - pos - 1 // Number of digits after the decimal point
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to convert f64 to an integer based on the number of decimal places
|
||||
fn convert_to_scaled_int(value: f64, max_decimal_places: usize) -> i64 {
|
||||
let multiplier: i64 = 10_i64.pow(max_decimal_places as u32); // Compute multiplier
|
||||
(value * multiplier as f64).round() as i64 // Scale and convert to integer
|
||||
}
|
||||
|
||||
pub fn parse_osm_data(
|
||||
json_data: &Value,
|
||||
bbox: (f64, f64, f64, f64),
|
||||
args: &Args,
|
||||
) -> (Vec<ProcessedElement>, f64, f64) {
|
||||
println!("{} {}", "[2/5]".bold(), "Parsing data...");
|
||||
|
||||
// Deserialize the JSON data into the OSMData structure
|
||||
let data: OSMData = serde_json::from_value(json_data.clone()).expect("Failed to parse OSM data");
|
||||
|
||||
// Calculate the maximum number of decimal places in bbox elements
|
||||
let max_decimal_places: usize = [
|
||||
count_decimal_places(bbox.0),
|
||||
count_decimal_places(bbox.1),
|
||||
count_decimal_places(bbox.2),
|
||||
count_decimal_places(bbox.3),
|
||||
]
|
||||
.into_iter()
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
// Convert each element to a scaled integer
|
||||
let bbox_scaled: (i64, i64, i64, i64) = (
|
||||
convert_to_scaled_int(bbox.0, max_decimal_places),
|
||||
convert_to_scaled_int(bbox.1, max_decimal_places),
|
||||
convert_to_scaled_int(bbox.2, max_decimal_places),
|
||||
convert_to_scaled_int(bbox.3, max_decimal_places),
|
||||
);
|
||||
|
||||
// Determine which dimension is larger and assign scale factors accordingly
|
||||
let (scale_factor_x, scale_factor_z) = if (bbox_scaled.2 - bbox_scaled.0) > (bbox_scaled.3 - bbox_scaled.1) {
|
||||
// Longitude difference is greater than latitude difference
|
||||
(
|
||||
((bbox_scaled.2 - bbox_scaled.0) * 10 / 100) as f64, // Scale for width (x) is based on longitude difference
|
||||
((bbox_scaled.3 - bbox_scaled.1) * 14 / 100) as f64, // Scale for length (z) is based on latitude difference
|
||||
)
|
||||
} else {
|
||||
// Latitude difference is greater than or equal to longitude difference
|
||||
(
|
||||
((bbox_scaled.3 - bbox_scaled.1) * 10 / 100) as f64, // Scale for width (x) is based on latitude difference
|
||||
((bbox_scaled.2 - bbox_scaled.0) * 14 / 100) as f64, // Scale for length (z) is based on longitude difference
|
||||
)
|
||||
};
|
||||
|
||||
if args.debug {
|
||||
println!("Scale factor X: {}", scale_factor_x);
|
||||
println!("Scale factor Z: {}", scale_factor_z);
|
||||
}
|
||||
|
||||
let mut nodes_map: HashMap<u64, (i32, i32)> = HashMap::new();
|
||||
let mut processed_elements: Vec<ProcessedElement> = Vec::new();
|
||||
|
||||
// First pass: store all nodes with Minecraft coordinates and process nodes with tags
|
||||
for element in &data.elements {
|
||||
if element.r#type == "node" {
|
||||
if let (Some(lat), Some(lon)) = (element.lat, element.lon) {
|
||||
let mc_coords: (i32, i32) = lat_lon_to_minecraft_coords(lat, lon, bbox, scale_factor_x, scale_factor_z);
|
||||
nodes_map.insert(element.id, mc_coords);
|
||||
|
||||
// Process nodes with tags
|
||||
if let Some(tags) = &element.tags {
|
||||
if !tags.is_empty() {
|
||||
processed_elements.push(ProcessedElement {
|
||||
id: element.id,
|
||||
r#type: element.r#type.clone(),
|
||||
tags: tags.clone(),
|
||||
nodes: vec![mc_coords], // Nodes for nodes is just the single coordinate
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: process ways and relations
|
||||
for element in data.elements {
|
||||
match element.r#type.as_str() {
|
||||
"way" | "relation" => {
|
||||
let mut nodes: Vec<(i32, i32)> = Vec::new();
|
||||
if let Some(node_ids) = element.nodes {
|
||||
for &node_id in &node_ids {
|
||||
if let Some(&coords) = nodes_map.get(&node_id) {
|
||||
nodes.push(coords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !nodes.is_empty() {
|
||||
processed_elements.push(ProcessedElement {
|
||||
id: element.id,
|
||||
r#type: element.r#type.clone(),
|
||||
tags: element.tags.unwrap_or_default(),
|
||||
nodes,
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
(processed_elements, scale_factor_z, scale_factor_x)
|
||||
}
|
||||
|
||||
const PRIORITY_ORDER: [&str; 5] = ["entrance", "building", "highway", "waterway", "barrier"];
|
||||
|
||||
// Function to determine the priority of each element
|
||||
pub fn get_priority(element: &ProcessedElement) -> usize {
|
||||
// Check each tag against the priority order
|
||||
for (i, &tag) in PRIORITY_ORDER.iter().enumerate() {
|
||||
if element.tags.contains_key(tag) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Return a default priority if none of the tags match
|
||||
PRIORITY_ORDER.len()
|
||||
}
|
||||
1405
src/processData.py
119
src/retrieve_data.rs
Normal file
@@ -0,0 +1,119 @@
|
||||
use colored::Colorize;
|
||||
use reqwest::blocking::Client;
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
use std::io::{self, BufReader, Write};
|
||||
use std::process::Command;
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
/// Function to download data using the `reqwest` crate
|
||||
fn download_with_reqwest(url: &str, query: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let client: Client = Client::new();
|
||||
let response: String = client.get(url).query(&[("data", query)]).send()?.text()?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
/// Function to download data using `curl`
|
||||
fn download_with_curl(url: &str, query: &str) -> io::Result<String> {
|
||||
let output: std::process::Output = Command::new("curl")
|
||||
.arg("-s") // Add silent mode to suppress output
|
||||
.arg(format!("{}?data={}", url, query))
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "Curl command failed"))
|
||||
} else {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to download data using `wget`
|
||||
fn download_with_wget(url: &str, query: &str) -> io::Result<String> {
|
||||
let output: std::process::Output = Command::new("wget")
|
||||
.arg("-qO-") // Use `-qO-` to output the result directly to stdout
|
||||
.arg(format!("{}?data={}", url, query))
|
||||
.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
Err(io::Error::new(io::ErrorKind::Other, "Wget command failed"))
|
||||
} else {
|
||||
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Main function to fetch data
|
||||
pub fn fetch_data(
|
||||
bbox: (f64, f64, f64, f64),
|
||||
file: Option<&str>,
|
||||
debug: bool,
|
||||
download_method: &str,
|
||||
) -> Result<Value, Box<dyn std::error::Error>> {
|
||||
println!("{} {}", "[1/5]".bold(), "Fetching data...");
|
||||
|
||||
// List of Overpass API servers
|
||||
let api_servers: Vec<&str> = vec![
|
||||
"https://overpass-api.de/api/interpreter",
|
||||
"https://lz4.overpass-api.de/api/interpreter",
|
||||
"https://z.overpass-api.de/api/interpreter",
|
||||
"https://overpass.kumi.systems/api/interpreter",
|
||||
"https://overpass.private.coffee/api/interpreter",
|
||||
];
|
||||
let url: &&str = api_servers.choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
// Generate Overpass API query for bounding box
|
||||
let query: String = format!(
|
||||
r#"[out:json][bbox:{},{},{},{}];
|
||||
(
|
||||
nwr["building"];
|
||||
nwr["highway"];
|
||||
nwr["landuse"];
|
||||
nwr["natural"];
|
||||
nwr["leisure"];
|
||||
nwr["waterway"];
|
||||
nwr["amenity"];
|
||||
nwr["bridge"];
|
||||
nwr["railway"];
|
||||
nwr["barrier"];
|
||||
nwr["entrance"];
|
||||
nwr["door"];
|
||||
way;
|
||||
)->.waysinbbox;
|
||||
(
|
||||
node(w.waysinbbox);
|
||||
)->.nodesinbbox;
|
||||
.waysinbbox out body;
|
||||
.nodesinbbox out skel qt;"#,
|
||||
bbox.1, bbox.0, bbox.3, bbox.2
|
||||
);
|
||||
|
||||
if let Some(file) = file {
|
||||
// Load data from file
|
||||
let file: File = File::open(file)?;
|
||||
let reader: BufReader<File> = BufReader::new(file);
|
||||
let data: Value = serde_json::from_reader(reader)?;
|
||||
Ok(data)
|
||||
} else {
|
||||
// Fetch data from Overpass API
|
||||
let response: String = match download_method {
|
||||
"requests" => download_with_reqwest(url, &query)?,
|
||||
"curl" => download_with_curl(url, &query)?,
|
||||
"wget" => download_with_wget(url, &query)?,
|
||||
_ => download_with_reqwest(url, &query)?, // Default to requests
|
||||
};
|
||||
|
||||
let data: Value = serde_json::from_str(&response)?;
|
||||
|
||||
if data["elements"].as_array().map_or(0, |elements: &Vec<Value>| elements.len()) == 0 {
|
||||
println!("Error! No data available");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
// If debug is enabled, write data to file
|
||||
if debug {
|
||||
let mut file: File = File::create("export.json")?;
|
||||
file.write_all(response.as_bytes())?;
|
||||
}
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
import collections
|
||||
import pytest
|
||||
|
||||
from src.bresenham import bresenham
|
||||
|
||||
|
||||
TestBresenhamParameters = collections.namedtuple(
|
||||
"TestBresenhamParameters", ["x1", "y1", "x2", "y2", "result"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"parameters",
|
||||
(
|
||||
TestBresenhamParameters(x1=0, y1=0, x2=0, y2=0, result=((0, 0),)),
|
||||
TestBresenhamParameters(
|
||||
x1=0,
|
||||
y1=0,
|
||||
x2=5,
|
||||
y2=0,
|
||||
result=((0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)),
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0,
|
||||
y1=0,
|
||||
x2=-5,
|
||||
y2=0,
|
||||
result=((0, 0), (-1, 0), (-2, 0), (-3, 0), (-4, 0), (-5, 0)),
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0,
|
||||
y1=0,
|
||||
x2=0,
|
||||
y2=5,
|
||||
result=((0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5)),
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0,
|
||||
y1=0,
|
||||
x2=0,
|
||||
y2=-5,
|
||||
result=((0, 0), (0, -1), (0, -2), (0, -3), (0, -4), (0, -5)),
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0, y1=0, x2=2, y2=3, result=((0, 0), (1, 1), (1, 2), (2, 3))
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0, y1=0, x2=-2, y2=3, result=((0, 0), (-1, 1), (-1, 2), (-2, 3))
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0, y1=0, x2=2, y2=-3, result=((0, 0), (1, -1), (1, -2), (2, -3))
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0, y1=0, x2=-2, y2=-3, result=((0, 0), (-1, -1), (-1, -2), (-2, -3))
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=-1,
|
||||
y1=-3,
|
||||
x2=3,
|
||||
y2=3,
|
||||
result=((-1, -3), (0, -2), (0, -1), (1, 0), (2, 1), (2, 2), (3, 3)),
|
||||
),
|
||||
TestBresenhamParameters(
|
||||
x1=0,
|
||||
y1=0,
|
||||
x2=11,
|
||||
y2=1,
|
||||
result=(
|
||||
(0, 0),
|
||||
(1, 0),
|
||||
(2, 0),
|
||||
(3, 0),
|
||||
(4, 0),
|
||||
(5, 0),
|
||||
(6, 1),
|
||||
(7, 1),
|
||||
(8, 1),
|
||||
(9, 1),
|
||||
(10, 1),
|
||||
(11, 1),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
def test_bresenham(parameters: TestBresenhamParameters):
|
||||
assert (
|
||||
tuple(
|
||||
bresenham(
|
||||
x1=parameters.x1, y1=parameters.y1, x2=parameters.x2, y2=parameters.y2
|
||||
)
|
||||
)
|
||||
== parameters.result
|
||||
)
|
||||
assert tuple(
|
||||
bresenham(
|
||||
x1=parameters.x2, y1=parameters.y2, x2=parameters.x1, y2=parameters.y1
|
||||
)
|
||||
) == tuple(reversed(parameters.result))
|
||||
|
||||
|
||||
def test_min_slope_uphill():
|
||||
assert tuple(bresenham(x1=0, y1=0, x2=10, y2=1)) == (
|
||||
(0, 0),
|
||||
(1, 0),
|
||||
(2, 0),
|
||||
(3, 0),
|
||||
(4, 0),
|
||||
(5, 1),
|
||||
(6, 1),
|
||||
(7, 1),
|
||||
(8, 1),
|
||||
(9, 1),
|
||||
(10, 1),
|
||||
)
|
||||
|
||||
|
||||
def test_min_slope_downhill():
|
||||
assert tuple(bresenham(x1=10, y1=1, x2=0, y2=0)) == (
|
||||
(10, 1),
|
||||
(9, 1),
|
||||
(8, 1),
|
||||
(7, 1),
|
||||
(6, 1),
|
||||
(5, 0),
|
||||
(4, 0),
|
||||
(3, 0),
|
||||
(2, 0),
|
||||
(1, 0),
|
||||
(0, 0),
|
||||
)
|
||||
99
src/tree.py
@@ -1,99 +0,0 @@
|
||||
from .processData import (
|
||||
setBlock,
|
||||
fillBlocks,
|
||||
)
|
||||
from .blockDefinitions import *
|
||||
|
||||
|
||||
def round1(material, x, y, z):
|
||||
setBlock(material, x - 2, y, z)
|
||||
setBlock(material, x + 2, y, z)
|
||||
setBlock(material, x, y, z - 2)
|
||||
setBlock(material, x, y, z + 2)
|
||||
setBlock(material, x - 1, y, z - 1)
|
||||
setBlock(material, x + 1, y, z + 1)
|
||||
setBlock(material, x + 1, y, z - 1)
|
||||
setBlock(material, x - 1, y, z + 1)
|
||||
|
||||
|
||||
def round2(material, x, y, z):
|
||||
setBlock(material, x + 3, y, z)
|
||||
setBlock(material, x + 2, y, z - 1)
|
||||
setBlock(material, x + 2, y, z + 1)
|
||||
setBlock(material, x + 1, y, z - 2)
|
||||
setBlock(material, x + 1, y, z + 2)
|
||||
setBlock(material, x - 3, y, z)
|
||||
setBlock(material, x - 2, y, z - 1)
|
||||
setBlock(material, x - 2, y, z + 1)
|
||||
setBlock(material, x - 1, y, z + 2)
|
||||
setBlock(material, x - 1, y, z - 2)
|
||||
setBlock(material, x, y, z - 3)
|
||||
setBlock(material, x, y, z + 3)
|
||||
|
||||
|
||||
def round3(material, x, y, z):
|
||||
setBlock(material, x + 3, y, z - 1)
|
||||
setBlock(material, x + 3, y, z + 1)
|
||||
setBlock(material, x + 2, y, z - 2)
|
||||
setBlock(material, x + 2, y, z + 2)
|
||||
setBlock(material, x + 1, y, z - 3)
|
||||
setBlock(material, x + 1, y, z + 3)
|
||||
setBlock(material, x - 3, y, z - 1)
|
||||
setBlock(material, x - 3, y, z + 1)
|
||||
setBlock(material, x - 2, y, z - 2)
|
||||
setBlock(material, x - 2, y, z + 2)
|
||||
setBlock(material, x - 1, y, z + 3)
|
||||
setBlock(material, x - 1, y, z - 3)
|
||||
|
||||
|
||||
def createTree(x, y, z, typetree=1):
|
||||
if typetree == 1: # Oak tree
|
||||
fillBlocks(oak_log, x, y, z, x, y + 8, z)
|
||||
fillBlocks(oak_leaves, x - 1, y + 3, z, x - 1, y + 9, z)
|
||||
fillBlocks(oak_leaves, x + 1, y + 3, z, x + 1, y + 9, z)
|
||||
fillBlocks(oak_leaves, x, y + 3, z - 1, x, y + 9, z - 1)
|
||||
fillBlocks(oak_leaves, x, y + 3, z + 1, x, y + 9, z + 1)
|
||||
fillBlocks(oak_leaves, x, y + 9, z, x, y + 10, z)
|
||||
round1(oak_leaves, x, y + 8, z)
|
||||
round1(oak_leaves, x, y + 7, z)
|
||||
round1(oak_leaves, x, y + 6, z)
|
||||
round1(oak_leaves, x, y + 5, z)
|
||||
round1(oak_leaves, x, y + 4, z)
|
||||
round1(oak_leaves, x, y + 3, z)
|
||||
round2(oak_leaves, x, y + 7, z)
|
||||
round2(oak_leaves, x, y + 6, z)
|
||||
round2(oak_leaves, x, y + 5, z)
|
||||
round2(oak_leaves, x, y + 4, z)
|
||||
round3(oak_leaves, x, y + 6, z)
|
||||
round3(oak_leaves, x, y + 5, z)
|
||||
|
||||
elif typetree == 2: # Spruce tree
|
||||
fillBlocks(spruce_log, x, y, z, x, y + 9, z)
|
||||
fillBlocks(birch_leaves, x - 1, y + 3, z, x - 1, y + 10, z)
|
||||
fillBlocks(birch_leaves, x + 1, y + 3, z, x + 1, y + 10, z)
|
||||
fillBlocks(birch_leaves, x, y + 3, z - 1, x, y + 10, z - 1)
|
||||
fillBlocks(birch_leaves, x, y + 3, z + 1, x, y + 10, z + 1)
|
||||
setBlock(birch_leaves, x, y + 10, z)
|
||||
round1(birch_leaves, x, y + 9, z)
|
||||
round1(birch_leaves, x, y + 7, z)
|
||||
round1(birch_leaves, x, y + 6, z)
|
||||
round1(birch_leaves, x, y + 4, z)
|
||||
round1(birch_leaves, x, y + 3, z)
|
||||
round2(birch_leaves, x, y + 6, z)
|
||||
round2(birch_leaves, x, y + 3, z)
|
||||
|
||||
elif typetree == 3: # Birch tree
|
||||
fillBlocks(birch_log, x, y, z, x, y + 6, z)
|
||||
fillBlocks(birch_leaves, x - 1, y + 2, z, x - 1, y + 7, z)
|
||||
fillBlocks(birch_leaves, x + 1, y + 2, z, x + 1, y + 7, z)
|
||||
fillBlocks(birch_leaves, x, y + 2, z - 1, x, y + 7, z - 1)
|
||||
fillBlocks(birch_leaves, x, y + 2, z + 1, x, y + 7, z + 1)
|
||||
fillBlocks(birch_leaves, x, y + 7, z, x, y + 8, z)
|
||||
round1(birch_leaves, x, y + 6, z)
|
||||
round1(birch_leaves, x, y + 5, z)
|
||||
round1(birch_leaves, x, y + 4, z)
|
||||
round1(birch_leaves, x, y + 3, z)
|
||||
round1(birch_leaves, x, y + 2, z)
|
||||
round2(birch_leaves, x, y + 2, z)
|
||||
round2(birch_leaves, x, y + 3, z)
|
||||
round2(birch_leaves, x, y + 4, z)
|
||||
77
src/version_check.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use colored::Colorize;
|
||||
use reqwest::{Error as ReqwestError, StatusCode};
|
||||
use reqwest::blocking::Client;
|
||||
use semver::Version;
|
||||
use std::error::Error;
|
||||
|
||||
/// URL to the remote Cargo.toml file to check for the latest version
|
||||
const REMOTE_CARGO_TOML_URL: &str = "https://raw.githubusercontent.com/louis-e/arnis/experimental-rust-dev/Cargo.toml";
|
||||
|
||||
/// Fetches the latest version from the remote Cargo.toml file and compares it with the local version.
|
||||
/// If a newer version is available, prints a message.
|
||||
pub fn check_for_updates() -> Result<(), Box<dyn Error>> {
|
||||
let client: Client = Client::new();
|
||||
|
||||
// Fetch the remote Cargo.toml file with a User-Agent header
|
||||
let response: Result<reqwest::blocking::Response, ReqwestError> = client
|
||||
.get(REMOTE_CARGO_TOML_URL)
|
||||
.header("User-Agent", "arnis-client")
|
||||
.send();
|
||||
|
||||
match response {
|
||||
Ok(res) => {
|
||||
// If the response status is not 200 OK, handle it as an HTTP error
|
||||
if !res.status().is_success() {
|
||||
handle_http_error(res.status());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let response_text: String = res.text()?;
|
||||
// Extract the version from the remote Cargo.toml
|
||||
let remote_version: Version = extract_version_from_cargo_toml(&response_text)?;
|
||||
let local_version: Version = Version::parse(env!("CARGO_PKG_VERSION"))?;
|
||||
|
||||
// Compare versions
|
||||
if remote_version > local_version {
|
||||
println!(
|
||||
"{} {} -> {}",
|
||||
"A new version is available:".yellow().bold(),
|
||||
local_version,
|
||||
remote_version
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => handle_request_error(err),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Extracts the version from the contents of a Cargo.toml file.
|
||||
fn extract_version_from_cargo_toml(cargo_toml_contents: &str) -> Result<Version, Box<dyn Error>> {
|
||||
for line in cargo_toml_contents.lines() {
|
||||
if line.starts_with("version") {
|
||||
let version_str: &str = line.split('=').nth(1).unwrap().trim().trim_matches('"');
|
||||
return Ok(Version::parse(version_str)?);
|
||||
}
|
||||
}
|
||||
Err("Failed to find version in Cargo.toml".into())
|
||||
}
|
||||
|
||||
/// Handles HTTP errors by printing the status code and a user-friendly message.
|
||||
fn handle_http_error(status: StatusCode) {
|
||||
eprintln!(
|
||||
"Failed to fetch remote Cargo.toml: HTTP error {}: {}",
|
||||
status.as_u16(),
|
||||
status.canonical_reason().unwrap_or("Unknown error")
|
||||
);
|
||||
}
|
||||
|
||||
/// Handles the error for HTTP requests more gracefully, including printing HTTP status codes when applicable.
|
||||
fn handle_request_error(err: ReqwestError) {
|
||||
if err.is_timeout() {
|
||||
eprintln!("Request timed out. Please check your network connection.");
|
||||
} else if let Some(status) = err.status() {
|
||||
handle_http_error(status);
|
||||
}
|
||||
}
|
||||
337
src/world_editor.rs
Normal file
@@ -0,0 +1,337 @@
|
||||
use colored::Colorize;
|
||||
use crate::args::Args;
|
||||
use crate::block_definitions::*;
|
||||
use fastanvil::Region;
|
||||
use fastnbt::{ByteArray, LongArray, Value};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Chunk {
|
||||
sections: Vec<Section>,
|
||||
#[serde(flatten)]
|
||||
other: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Section {
|
||||
block_states: Blockstates,
|
||||
#[serde(flatten)]
|
||||
other: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Blockstates {
|
||||
palette: Vec<PaletteItem>,
|
||||
#[serde(flatten)]
|
||||
other: HashMap<String, Value>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct PaletteItem {
|
||||
#[serde(rename = "Name")]
|
||||
name: String,
|
||||
#[serde(rename = "Properties")]
|
||||
properties: Option<Value>,
|
||||
}
|
||||
|
||||
pub struct WorldEditor<'a> {
|
||||
region_template_path: String,
|
||||
region_dir: String,
|
||||
regions: HashMap<(i32, i32), Region<File>>,
|
||||
chunks_to_modify: HashMap<(i32, i32, i32, i32), HashMap<(i32, i32, i32), Block>>,
|
||||
scale_factor_x: f64,
|
||||
scale_factor_z: f64,
|
||||
args: &'a Args,
|
||||
}
|
||||
|
||||
impl<'a> WorldEditor<'a> {
|
||||
/// Initializes the WorldEditor with the region directory and template region path.
|
||||
pub fn new(
|
||||
region_template_path: &str,
|
||||
region_dir: &str,
|
||||
scale_factor_x: f64,
|
||||
scale_factor_z: f64,
|
||||
args: &'a Args,
|
||||
) -> Self {
|
||||
Self {
|
||||
region_template_path: region_template_path.to_string(),
|
||||
region_dir: region_dir.to_string(),
|
||||
regions: HashMap::new(),
|
||||
chunks_to_modify: HashMap::new(),
|
||||
scale_factor_x,
|
||||
scale_factor_z,
|
||||
args,
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads or creates a region for the given region coordinates.
|
||||
fn load_region(&mut self, region_x: i32, region_z: i32) -> &mut Region<File> {
|
||||
self.regions.entry((region_x, region_z)).or_insert_with(|| {
|
||||
let out_path: String = format!("{}/r.{}.{}.mca", self.region_dir, region_x, region_z);
|
||||
std::fs::copy(&self.region_template_path, &out_path)
|
||||
.expect("Failed to copy region template");
|
||||
let region_file = File::options().read(true).write(true).open(&out_path)
|
||||
.expect("Failed to open region file");
|
||||
Region::from_stream(region_file).expect("Failed to load region")
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets a block of the specified type at the given coordinates.
|
||||
pub fn set_block(
|
||||
&mut self,
|
||||
block: &'static Lazy<Block>,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
override_whitelist: Option<&[&'static Lazy<Block>]>,
|
||||
override_blacklist: Option<&[&'static Lazy<Block>]>,
|
||||
) {
|
||||
let position: (i32, i32, i32) = (x, y, z);
|
||||
|
||||
// Check if coordinates are within bounds
|
||||
if x < 0 || x > self.scale_factor_x as i32 || z < 0 || z > self.scale_factor_z as i32 {
|
||||
return;
|
||||
}
|
||||
|
||||
let chunk_x: i32 = x >> 4;
|
||||
let chunk_z: i32 = z >> 4;
|
||||
let region_x: i32 = chunk_x >> 5;
|
||||
let region_z: i32 = chunk_z >> 5;
|
||||
|
||||
let chunk_x_within_region: i32 = chunk_x & 31;
|
||||
let chunk_z_within_region: i32 = chunk_z & 31;
|
||||
|
||||
let chunk_key: (i32, i32, i32, i32) = (region_x, region_z, chunk_x_within_region, chunk_z_within_region);
|
||||
let chunk_blocks: &mut HashMap<(i32, i32, i32), Block> = self.chunks_to_modify.entry(chunk_key).or_default();
|
||||
|
||||
if let Some(existing_block) = chunk_blocks.get(&position) {
|
||||
// Check against whitelist and blacklist
|
||||
if let Some(whitelist) = override_whitelist {
|
||||
if whitelist.iter().any(|&whitelisted_block| whitelisted_block.name == existing_block.name) {
|
||||
chunk_blocks.insert(position, (*block).clone());
|
||||
}
|
||||
} else if let Some(blacklist) = override_blacklist {
|
||||
if !blacklist.iter().any(|&blacklisted_block| blacklisted_block.name == existing_block.name) {
|
||||
chunk_blocks.insert(position, (*block).clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chunk_blocks.insert(position, (*block).clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills a cuboid area with the specified block between two coordinates.
|
||||
pub fn fill_blocks(
|
||||
&mut self,
|
||||
block: &'static Lazy<Block>,
|
||||
x1: i32,
|
||||
y1: i32,
|
||||
z1: i32,
|
||||
x2: i32,
|
||||
y2: i32,
|
||||
z2: i32,
|
||||
override_whitelist: Option<&[&'static Lazy<Block>]>,
|
||||
override_blacklist: Option<&[&'static Lazy<Block>]>,
|
||||
) {
|
||||
let (min_x, max_x) = if x1 < x2 { (x1, x2) } else { (x2, x1) };
|
||||
let (min_y, max_y) = if y1 < y2 { (y1, y2) } else { (y2, y1) };
|
||||
let (min_z, max_z) = if z1 < z2 { (z1, z2) } else { (z2, z1) };
|
||||
|
||||
for x in min_x..=max_x {
|
||||
for y in min_y..=max_y {
|
||||
for z in min_z..=max_z {
|
||||
self.set_block(block, x, y, z, override_whitelist, override_blacklist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks for a block at the given coordinates.
|
||||
pub fn check_for_block(
|
||||
&self,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
whitelist: Option<&[&'static Lazy<Block>]>,
|
||||
blacklist: Option<&[&'static Lazy<Block>]>,
|
||||
) -> bool {
|
||||
let chunk_x: i32 = x >> 4;
|
||||
let chunk_z: i32 = z >> 4;
|
||||
let region_x: i32 = chunk_x >> 5;
|
||||
let region_z: i32 = chunk_z >> 5;
|
||||
|
||||
let chunk_x_within_region: i32 = chunk_x & 31;
|
||||
let chunk_z_within_region: i32 = chunk_z & 31;
|
||||
|
||||
let chunk_key: (i32, i32, i32, i32) = (region_x, region_z, chunk_x_within_region, chunk_z_within_region);
|
||||
|
||||
// Retrieve the chunk modification map
|
||||
if let Some(chunk_blocks) = self.chunks_to_modify.get(&chunk_key) {
|
||||
if let Some(existing_block) = chunk_blocks.get(&(x, y, z)) {
|
||||
// Check against whitelist and blacklist
|
||||
if let Some(whitelist) = whitelist {
|
||||
if whitelist.iter().any(|&whitelisted_block| whitelisted_block.name == existing_block.name) {
|
||||
return true; // Block is in whitelist
|
||||
}
|
||||
}
|
||||
if let Some(blacklist) = blacklist {
|
||||
if blacklist.iter().any(|&blacklisted_block| blacklisted_block.name == existing_block.name) {
|
||||
return true; // Block is in blacklist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Saves all changes made to the world by writing modified chunks to the appropriate region files.
|
||||
pub fn save(&mut self) {
|
||||
println!("{} {}", "[5/5]".bold(), "Saving world...");
|
||||
|
||||
let _debug: bool = self.args.debug;
|
||||
let total_chunks: u64 = self.chunks_to_modify.len() as u64;
|
||||
|
||||
let save_pb: ProgressBar = ProgressBar::new(total_chunks);
|
||||
save_pb.set_style(ProgressStyle::default_bar()
|
||||
.template("{spinner:.green} [{elapsed_precise}] [{bar:45}] {pos}/{len} chunks ({eta})")
|
||||
.unwrap()
|
||||
.progress_chars("█▓░"));
|
||||
|
||||
let mut chunks_to_process = self.chunks_to_modify.clone(); // Clone to prevent modifying while iterating
|
||||
|
||||
for ((region_x, region_z, chunk_x, chunk_z), block_list) in &mut chunks_to_process {
|
||||
let region: &mut Region<File> = self.load_region(*region_x, *region_z);
|
||||
|
||||
if let Ok(Some(data)) = region.read_chunk(*chunk_x as usize, *chunk_z as usize) {
|
||||
let mut chunk: Chunk = fastnbt::from_bytes(&data).unwrap();
|
||||
|
||||
for ((x, y, z), block) in block_list {
|
||||
set_block_in_chunk(&mut chunk, block.clone(), *x, *y, *z);
|
||||
}
|
||||
|
||||
let ser: Vec<u8> = fastnbt::to_bytes(&chunk).unwrap();
|
||||
|
||||
// Write chunk data back to the correct location, ensuring correct chunk coordinates
|
||||
let expected_chunk_location: (usize, usize) = ((*chunk_x as usize) & 31, (*chunk_z as usize) & 31);
|
||||
region.write_chunk(expected_chunk_location.0, expected_chunk_location.1, &ser).unwrap();
|
||||
}
|
||||
|
||||
// Remove chunk from the modification map after processing it
|
||||
self.chunks_to_modify.remove(&(*region_x, *region_z, *chunk_x, *chunk_z));
|
||||
|
||||
save_pb.inc(1);
|
||||
}
|
||||
|
||||
save_pb.finish();
|
||||
}
|
||||
}
|
||||
|
||||
fn bits_per_block(palette_size: u32) -> u32 {
|
||||
(palette_size as f32).log2().ceil().max(4.0).min(8.0) as u32
|
||||
}
|
||||
|
||||
fn set_block_in_chunk(
|
||||
chunk: &mut Chunk,
|
||||
block: Block,
|
||||
x: i32,
|
||||
y: i32,
|
||||
z: i32,
|
||||
) {
|
||||
let local_x: usize = (x & 15) as usize;
|
||||
let local_y: usize = y as usize;
|
||||
let local_z: usize = (z & 15) as usize;
|
||||
|
||||
for section in chunk.sections.iter_mut() {
|
||||
if let Some(Value::Byte(y_byte)) = section.other.get("Y") {
|
||||
if *y_byte == (local_y >> 4) as i8 {
|
||||
let palette: &mut Vec<PaletteItem> = &mut section.block_states.palette;
|
||||
let block_index: usize = (local_y % 16 * 256 + local_z * 16 + local_x) as usize;
|
||||
|
||||
// Add SkyLight with 2048 bytes of value 0xFF
|
||||
let skylight_data = vec![0xFFu8 as i8; 2048];
|
||||
section.other.insert("SkyLight".to_string(), Value::ByteArray(ByteArray::new(skylight_data)));
|
||||
|
||||
// Check if the block is already in the palette with matching properties
|
||||
let mut palette_index: Option<usize> = palette.iter().position(|item: &PaletteItem| {
|
||||
item.name == block.name && item.properties == block.properties
|
||||
});
|
||||
|
||||
// If the block is not in the palette and adding it would exceed a reasonable size, skip or replace
|
||||
if palette_index.is_none() {
|
||||
if palette.len() >= 16 {
|
||||
palette_index = Some(0);
|
||||
} else {
|
||||
// Add the new block type to the palette with its properties
|
||||
palette.push(PaletteItem {
|
||||
name: block.name.clone(),
|
||||
properties: block.properties.clone(),
|
||||
});
|
||||
palette_index = Some(palette.len() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Unwrap because we are sure palette_index is Some after this point
|
||||
let palette_index: u32 = palette_index.unwrap() as u32;
|
||||
|
||||
let bits_per_block: u32 = bits_per_block(palette.len() as u32);
|
||||
if let Some(Value::LongArray(ref mut data)) = section.block_states.other.get_mut("data") {
|
||||
// Convert LongArray to Vec<i64>
|
||||
let mut vec_data: Vec<i64> = data.as_mut().to_vec();
|
||||
set_block_in_section(&mut vec_data, block_index, palette_index, bits_per_block);
|
||||
// Update LongArray with modified Vec<i64>
|
||||
*data = LongArray::new(vec_data);
|
||||
} else {
|
||||
// Properly initialize new data array with correct length
|
||||
let required_longs: usize = ((4096 + (64 / bits_per_block) - 1) / (64 / bits_per_block)) as usize;
|
||||
let mut new_data: Vec<i64> = vec![0i64; required_longs];
|
||||
set_block_in_section(&mut new_data, block_index, palette_index, bits_per_block);
|
||||
section.block_states.other.insert("data".to_string(), Value::LongArray(LongArray::new(new_data)));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_block_in_section(
|
||||
data: &mut Vec<i64>,
|
||||
block_index: usize,
|
||||
palette_index: u32,
|
||||
bits_per_block: u32
|
||||
) {
|
||||
let blocks_per_long: u32 = 64 / bits_per_block;
|
||||
let required_longs: usize = ((4096 + blocks_per_long - 1) / blocks_per_long) as usize;
|
||||
|
||||
// Ensure data vector is large enough
|
||||
assert!(data.len() >= required_longs, "Data slice is too small");
|
||||
|
||||
let mask: u64 = (1u64 << bits_per_block) - 1;
|
||||
let long_index: usize = block_index / blocks_per_long as usize;
|
||||
let start_bit: usize = (block_index % blocks_per_long as usize) * bits_per_block as usize;
|
||||
|
||||
let current_value: u64 = data[long_index] as u64;
|
||||
let new_value: u64 = (current_value & !(mask << start_bit)) | ((palette_index as u64 & mask) << start_bit);
|
||||
|
||||
// Update data
|
||||
data[long_index] = new_value as i64;
|
||||
|
||||
// Handle cases where bits spill over into the next long
|
||||
if start_bit + bits_per_block as usize > 64 {
|
||||
let overflow_bits: usize = (start_bit + bits_per_block as usize) - 64;
|
||||
let next_long_index: usize = long_index + 1;
|
||||
|
||||
if next_long_index < data.len() {
|
||||
let next_value: u64 = data[next_long_index] as u64;
|
||||
let new_next_value: u64 = (next_value & !(mask >> overflow_bits)) | ((palette_index as u64 & mask) >> overflow_bits);
|
||||
data[next_long_index] = new_next_value as i64;
|
||||
} else {
|
||||
panic!("Data slice is too small even after resizing. This should never happen.");
|
||||
}
|
||||
}
|
||||
}
|
||||