Compare commits
6 Commits
master
...
texture-in
| Author | SHA1 | Date | |
|---|---|---|---|
| cdc3522773 | |||
| e8cf54a578 | |||
| 9131f68624 | |||
| 8d109a359a | |||
| a3163684c7 | |||
| b644da1f56 |
140
.github/workflows/ci.yaml
vendored
140
.github/workflows/ci.yaml
vendored
@@ -1,140 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
# Reduce compile time and cache size.
|
||||
RUSTFLAGS: -Zshare-generics=y -Zthreads=0
|
||||
RUSTDOCFLAGS: -Zshare-generics=y -Zthreads=0
|
||||
# Use the same Rust toolchain across jobs so they can share a cache.
|
||||
toolchain: nightly-2025-04-03
|
||||
|
||||
jobs:
|
||||
# Run Clippy lints.
|
||||
clippy-lints:
|
||||
name: Clippy lints
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.toolchain }}
|
||||
components: clippy
|
||||
|
||||
- name: Restore Rust cache
|
||||
id: cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: ci
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install build dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: sudo apt-get -y update; sudo apt-get -y install --no-install-recommends libasound2-dev libudev-dev libwayland-dev
|
||||
|
||||
- name: Run Clippy lints
|
||||
run: cargo clippy --locked --workspace --all-targets --profile ci --all-features
|
||||
|
||||
# Run Bevy lints.
|
||||
bevy-lints:
|
||||
name: Bevy lints
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust toolchain (plus bevy_lint)
|
||||
uses: TheBevyFlock/bevy_cli/bevy_lint@lint-v0.3.0
|
||||
|
||||
- name: Restore Rust cache
|
||||
id: cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: ci
|
||||
save-if: false
|
||||
|
||||
- name: Install build dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: sudo apt-get -y update; sudo apt-get -y install --no-install-recommends libasound2-dev libudev-dev libwayland-dev
|
||||
|
||||
- name: Run Bevy lints
|
||||
run: bevy_lint --locked --workspace --all-targets --profile ci --all-features
|
||||
|
||||
# Run tests.
|
||||
tests:
|
||||
name: Tests
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 40
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up environment
|
||||
run: echo "RUSTFLAGS=${RUSTFLAGS:+$RUSTFLAGS }-Zcodegen-backend=cranelift" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.toolchain }}
|
||||
components: rustc-codegen-cranelift-preview
|
||||
|
||||
- name: Restore Rust cache
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: test
|
||||
cache-directories: ${{ env.LD_LIBRARY_PATH }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install build dependencies
|
||||
run: sudo apt-get -y update; sudo apt-get -y install --no-install-recommends libasound2-dev libudev-dev libwayland-dev
|
||||
|
||||
- name: Run tests
|
||||
run: cargo test --locked --workspace --all-targets --profile ci --no-fail-fast
|
||||
|
||||
# Check that the web build compiles.
|
||||
# check-web:
|
||||
# name: Check web
|
||||
# runs-on: ubuntu-latest
|
||||
# env:
|
||||
# RUSTFLAGS: -Zshare-generics=y -Zthreads=0 --cfg getrandom_backend="wasm_js"
|
||||
# timeout-minutes: 20
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Install Rust toolchain
|
||||
# uses: dtolnay/rust-toolchain@master
|
||||
# with:
|
||||
# toolchain: ${{ env.toolchain }}
|
||||
# targets: wasm32-unknown-unknown
|
||||
|
||||
# - name: Restore Rust cache
|
||||
# id: cache
|
||||
# uses: Swatinem/rust-cache@v2
|
||||
# with:
|
||||
# shared-key: web-ci
|
||||
# save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
# - name: Install build dependencies
|
||||
# if: steps.cache.outputs.cache-hit != 'true'
|
||||
# run: sudo apt-get -y update; sudo apt-get -y install --no-install-recommends libasound2-dev libudev-dev libwayland-dev
|
||||
|
||||
# - name: Check web
|
||||
# run: cargo check --config 'profile.web.inherits="dev"' --profile ci --no-default-features --features "dev web" --target wasm32-unknown-unknown
|
||||
335
.github/workflows/release.yaml
vendored
335
.github/workflows/release.yaml
vendored
@@ -1,335 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
# Trigger this workflow when a tag is pushed in the format `v1.2.3`.
|
||||
push:
|
||||
tags:
|
||||
# Pattern syntax: <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet>.
|
||||
- "v[0-9]+.[0-9]+.[0-9]+*"
|
||||
# Trigger this workflow manually via workflow dispatch.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version number in the format `v1.2.3`"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
# Configure the release workflow by editing the following values.
|
||||
env:
|
||||
# The base filename of the binary produced by `cargo build`.
|
||||
cargo_build_binary_name: phos
|
||||
|
||||
# The path to the assets directory.
|
||||
assets_path: assets
|
||||
|
||||
# Whether to build and package a release for a given target platform.
|
||||
build_for_web: false
|
||||
build_for_linux: true
|
||||
build_for_windows: true
|
||||
build_for_macos: false
|
||||
|
||||
# Whether to upload the packages produced by this workflow to a GitHub release.
|
||||
upload_to_github: true
|
||||
|
||||
# The itch.io project to upload to in the format `user-name/project-name`.
|
||||
# There will be no upload to itch.io if this is commented out.
|
||||
# upload_to_itch: amatsugu/phos
|
||||
|
||||
############
|
||||
# ADVANCED #
|
||||
############
|
||||
|
||||
# The ID of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Must contain only A-Z, a-z, 0-9, hyphen, and period: <https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier>.
|
||||
app_id: amatsugu.phos
|
||||
|
||||
# The base filename of the binary in the package produced by this workflow.
|
||||
# Applies to Windows, macOS, and Linux releases.
|
||||
# Defaults to `cargo_build_binary_name` if commented out.
|
||||
#app_binary_name: bevy_jam_6
|
||||
|
||||
# The name of the `.zip` or `.dmg` file produced by this workflow.
|
||||
# Defaults to `app_binary_name` if commented out.
|
||||
#app_package_name: bevy-new-2d
|
||||
|
||||
# The display name of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Defaults to `app_package_name` if commented out.
|
||||
app_display_name: phos
|
||||
|
||||
# The short display name of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Must be 15 or fewer characters: <https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundlename>.
|
||||
# Defaults to `app_display_name` if commented out.
|
||||
#app_short_name: Phos
|
||||
|
||||
# Before enabling LFS, please take a look at GitHub's documentation for costs and quota limits:
|
||||
# <https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-storage-and-bandwidth-usage>
|
||||
git_lfs: false
|
||||
|
||||
# Enabling this only helps with consecutive releases to the same tag (and takes up cache storage space).
|
||||
# See: <https://github.com/orgs/community/discussions/27059>.
|
||||
use_github_cache: false
|
||||
|
||||
# Reduce compile time.
|
||||
RUSTFLAGS: -Zshare-generics=y -Zthreads=0
|
||||
|
||||
jobs:
|
||||
# Forward some environment variables as outputs of this job.
|
||||
# This is needed because the `env` context can't be used in the `if:` condition of a job:
|
||||
# <https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability>
|
||||
forward-env:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Do nothing
|
||||
run: "true"
|
||||
outputs:
|
||||
upload_to_itch: ${{ env.upload_to_itch }}
|
||||
|
||||
# Determine the version number for this workflow.
|
||||
get-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine version number
|
||||
id: tag
|
||||
run: echo "tag=${GITHUB_REF#refs/tags/}" >> "${GITHUB_OUTPUT}"
|
||||
outputs:
|
||||
# Use the input from workflow dispatch, or fall back to the git tag.
|
||||
version: ${{ inputs.version || steps.tag.outputs.tag }}
|
||||
|
||||
# Build and package a release for each platform.
|
||||
build:
|
||||
needs:
|
||||
- get-version
|
||||
env:
|
||||
version: ${{ needs.get-version.outputs.version }}
|
||||
# Avoid rate-limiting. See: <https://github.com/cargo-bins/cargo-binstall/issues/2045>.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: web
|
||||
targets: wasm32-unknown-unknown
|
||||
package_ext: .zip
|
||||
runner: ubuntu-latest
|
||||
|
||||
- platform: linux
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
package_ext: .zip
|
||||
runner: ubuntu-latest
|
||||
|
||||
- platform: windows
|
||||
targets: x86_64-pc-windows-msvc
|
||||
binary_ext: .exe
|
||||
package_ext: .zip
|
||||
runner: windows-latest
|
||||
|
||||
- platform: macos
|
||||
targets: x86_64-apple-darwin aarch64-apple-darwin
|
||||
app_suffix: .app/Contents/MacOS
|
||||
package_ext: .dmg
|
||||
runner: macos-latest
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
# Required to create a GitHub release: <https://docs.github.com/en/rest/releases/releases#create-a-release>.
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Set up environment
|
||||
run: |
|
||||
# Default values:
|
||||
echo "app_binary_name=${app_binary_name:=${{ env.cargo_build_binary_name }}}" >> "${GITHUB_ENV}"
|
||||
echo "app_package_name=${app_package_name:=${app_binary_name}}" >> "${GITHUB_ENV}"
|
||||
echo "app_display_name=${app_display_name:=${app_package_name}}" >> "${GITHUB_ENV}"
|
||||
echo "app_short_name=${app_short_name:=${app_display_name}}" >> "${GITHUB_ENV}"
|
||||
|
||||
# File paths:
|
||||
echo "app=tmp/app/${app_package_name}"'${{ matrix.app_suffix }}' >> "${GITHUB_ENV}"
|
||||
echo "package=${app_package_name}-"'${{ matrix.platform }}${{ matrix.package_ext }}' >> "${GITHUB_ENV}"
|
||||
|
||||
# macOS environment:
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
echo 'MACOSX_DEPLOYMENT_TARGET=11.0' >> "${GITHUB_ENV}" # macOS 11.0 Big Sur is the first version to support universal binaries.
|
||||
echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> "${GITHUB_ENV}"
|
||||
fi
|
||||
|
||||
# Check if building for this platform is enabled.
|
||||
echo 'is_platform_enabled=${{
|
||||
(matrix.platform == 'web' && env.build_for_web == 'true') ||
|
||||
(matrix.platform == 'linux' && env.build_for_linux == 'true') ||
|
||||
(matrix.platform == 'windows' && env.build_for_windows == 'true') ||
|
||||
(matrix.platform == 'macos' && env.build_for_macos == 'true')
|
||||
}}' >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout repository
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: ${{ env.git_lfs }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
targets: ${{ matrix.targets }}
|
||||
|
||||
- name: Restore Rust cache
|
||||
if: ${{ env.is_platform_enabled == 'true' && env.use_github_cache == 'true' }}
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: release
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install build dependencies (Linux)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'linux' }}
|
||||
run: sudo apt-get -y update; sudo apt-get -y install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
|
||||
|
||||
- name: Prepare output directories
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
run: rm -rf tmp; mkdir -p tmp/binary '${{ env.app }}'
|
||||
|
||||
- name: Install cargo-binstall
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
|
||||
- name: Install Bevy CLI
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
run: cargo binstall --locked --no-confirm --force --git='https://github.com/TheBevyFlock/bevy_cli' bevy_cli
|
||||
|
||||
- name: Build and add web bundle to app (Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'web' }}
|
||||
run: |
|
||||
cargo binstall --locked --no-confirm --force wasm-bindgen-cli
|
||||
cargo binstall --locked --no-confirm --force wasm-opt
|
||||
RUSTFLAGS='-Zshare-generics=y -Zthreads=0 --cfg getrandom_backend="wasm_js"' bevy build --locked --release --features='${{ matrix.features }} web' --yes web --bundle
|
||||
mv 'target/bevy_web/web-release/${{ env.cargo_build_binary_name }}' '${{ env.app }}'
|
||||
|
||||
- name: Build and add binaries to app (non-Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'web' }}
|
||||
run: |
|
||||
for target in ${{ matrix.targets }}; do
|
||||
bevy build --locked --release --target="${target}" --features='${{ matrix.features }}'
|
||||
mv target/"${target}"/release/'${{ env.cargo_build_binary_name }}${{ matrix.binary_ext }}' tmp/binary/"${target}"'${{ matrix.binary_ext }}'
|
||||
done
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
lipo tmp/binary/*'${{ matrix.binary_ext }}' -create -output '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}'
|
||||
else
|
||||
mv tmp/binary/*'${{ matrix.binary_ext }}' '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}'
|
||||
fi
|
||||
|
||||
- name: Add assets to app (non-Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'web' }}
|
||||
run: cp -R ./'${{ env.assets_path }}' '${{ env.app }}' || true # Ignore error if assets folder does not exist.
|
||||
|
||||
- name: Add metadata to app (macOS)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'macos' }}
|
||||
run: |
|
||||
cat >'${{ env.app }}/../Info.plist' <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${{ env.app_display_name }}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${{ env.app_binary_name }}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${{ env.app_id }}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${{ env.app_short_name }}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${{ env.version }}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${{ env.version }}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
- name: Package app (non-Windows)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'windows' }}
|
||||
working-directory: tmp/app
|
||||
run: |
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
ln -s /Applications .
|
||||
hdiutil create -fs HFS+ -volname '${{ env.app_package_name }}' -srcfolder . '${{ env.package }}'
|
||||
else
|
||||
zip --recurse-paths '${{ env.package }}' '${{ env.app_package_name }}'
|
||||
fi
|
||||
|
||||
- name: Package app (Windows)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'windows' }}
|
||||
working-directory: tmp/app
|
||||
shell: pwsh
|
||||
run: Compress-Archive -Path '${{ env.app_package_name }}' -DestinationPath '${{ env.package }}'
|
||||
|
||||
- name: Upload package to workflow artifacts
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: tmp/app/${{ env.package }}
|
||||
name: package-${{ matrix.platform }}
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload package to GitHub release
|
||||
if: ${{ env.is_platform_enabled == 'true' && env.upload_to_github == 'true' }}
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: tmp/app/${{ env.package }}
|
||||
asset_name: ${{ env.package }}
|
||||
release_name: ${{ env.version }}
|
||||
tag: ${{ env.version }}
|
||||
overwrite: true
|
||||
|
||||
# Upload all packages to itch.io.
|
||||
upload-to-itch:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- forward-env
|
||||
- get-version
|
||||
- build
|
||||
if: ${{ needs.forward-env.outputs.upload_to_itch != '' }}
|
||||
|
||||
steps:
|
||||
- name: Download all packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: package-*
|
||||
path: tmp
|
||||
|
||||
- name: Install butler
|
||||
run: |
|
||||
curl -L -o butler.zip 'https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default'
|
||||
unzip butler.zip
|
||||
chmod +x butler
|
||||
./butler -V
|
||||
|
||||
- name: Upload all packages to itch.io
|
||||
env:
|
||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
run: |
|
||||
for channel in $(ls tmp); do
|
||||
./butler push \
|
||||
--fix-permissions \
|
||||
--userversion='${{ needs.get-version.outputs.version }}' \
|
||||
tmp/"${channel}"/* \
|
||||
'${{ env.upload_to_itch }}':"${channel#package-}"
|
||||
done
|
||||
347
.github/workflows/release.yaml.template
vendored
347
.github/workflows/release.yaml.template
vendored
@@ -1,347 +0,0 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
# Trigger this workflow when a tag is pushed in the format `v1.2.3`.
|
||||
push:
|
||||
tags:
|
||||
# Pattern syntax: <https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet>.
|
||||
- "v[0-9]+.[0-9]+.[0-9]+*"
|
||||
# Trigger this workflow manually via workflow dispatch.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version number in the format `v1.2.3`"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
{% raw -%}
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
{%- endraw %}
|
||||
|
||||
# Configure the release workflow by editing the following values.
|
||||
env:
|
||||
# The base filename of the binary produced by `cargo build`.
|
||||
cargo_build_binary_name: {{project-name}}
|
||||
|
||||
# The path to the assets directory.
|
||||
assets_path: assets
|
||||
|
||||
# Whether to build and package a release for a given target platform.
|
||||
build_for_web: true
|
||||
build_for_linux: true
|
||||
build_for_windows: true
|
||||
build_for_macos: true
|
||||
|
||||
# Whether to upload the packages produced by this workflow to a GitHub release.
|
||||
upload_to_github: true
|
||||
|
||||
# The itch.io project to upload to in the format `user-name/project-name`.
|
||||
# There will be no upload to itch.io if this is commented out.
|
||||
{%- if itch_username != "" %}
|
||||
{%- if itch_project != "" %}
|
||||
upload_to_itch: {{itch_username}}/{{itch_project}}
|
||||
{%- else %}
|
||||
upload_to_itch: {{itch_username}}/{{project-name}}
|
||||
{%- endif %}
|
||||
{%- else %}
|
||||
#upload_to_itch: your-itch-username/{{project-name}}
|
||||
{%- endif %}
|
||||
|
||||
############
|
||||
# ADVANCED #
|
||||
############
|
||||
|
||||
# The ID of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Must contain only A-Z, a-z, 0-9, hyphen, and period: <https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleidentifier>.
|
||||
app_id: {{itch_username | kebab_case}}.{{project-name | kebab_case}}
|
||||
|
||||
# The base filename of the binary in the package produced by this workflow.
|
||||
# Applies to Windows, macOS, and Linux releases.
|
||||
# Defaults to `cargo_build_binary_name` if commented out.
|
||||
#app_binary_name: {{project-name}}
|
||||
|
||||
# The name of the `.zip` or `.dmg` file produced by this workflow.
|
||||
# Defaults to `app_binary_name` if commented out.
|
||||
#app_package_name: {{project-name | kebab_case}}
|
||||
|
||||
# The display name of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Defaults to `app_package_name` if commented out.
|
||||
#app_display_name: {{project-name | title_case}}
|
||||
|
||||
# The short display name of the app produced by this workflow.
|
||||
# Applies to macOS releases.
|
||||
# Must be 15 or fewer characters: <https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundlename>.
|
||||
# Defaults to `app_display_name` if commented out.
|
||||
#app_short_name: {{project-name | title_case | truncate: 15, "…"}}
|
||||
|
||||
# Before enabling LFS, please take a look at GitHub's documentation for costs and quota limits:
|
||||
# <https://docs.github.com/en/repositories/working-with-files/managing-large-files/about-storage-and-bandwidth-usage>
|
||||
git_lfs: false
|
||||
|
||||
# Enabling this only helps with consecutive releases to the same tag (and takes up cache storage space).
|
||||
# See: <https://github.com/orgs/community/discussions/27059>.
|
||||
use_github_cache: false
|
||||
|
||||
# Reduce compile time.
|
||||
RUSTFLAGS: -Dwarnings -Zshare-generics=y -Zthreads=0
|
||||
|
||||
{% raw -%}
|
||||
jobs:
|
||||
# Forward some environment variables as outputs of this job.
|
||||
# This is needed because the `env` context can't be used in the `if:` condition of a job:
|
||||
# <https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability>
|
||||
forward-env:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Do nothing
|
||||
run: "true"
|
||||
outputs:
|
||||
upload_to_itch: ${{ env.upload_to_itch }}
|
||||
|
||||
# Determine the version number for this workflow.
|
||||
get-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine version number
|
||||
id: tag
|
||||
run: echo "tag=${GITHUB_REF#refs/tags/}" >> "${GITHUB_OUTPUT}"
|
||||
outputs:
|
||||
# Use the input from workflow dispatch, or fall back to the git tag.
|
||||
version: ${{ inputs.version || steps.tag.outputs.tag }}
|
||||
|
||||
# Build and package a release for each platform.
|
||||
build:
|
||||
needs:
|
||||
- get-version
|
||||
env:
|
||||
version: ${{ needs.get-version.outputs.version }}
|
||||
# Avoid rate-limiting. See: <https://github.com/cargo-bins/cargo-binstall/issues/2045>.
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- platform: web
|
||||
targets: wasm32-unknown-unknown
|
||||
package_ext: .zip
|
||||
runner: ubuntu-latest
|
||||
|
||||
- platform: linux
|
||||
targets: x86_64-unknown-linux-gnu
|
||||
package_ext: .zip
|
||||
runner: ubuntu-latest
|
||||
|
||||
- platform: windows
|
||||
targets: x86_64-pc-windows-msvc
|
||||
binary_ext: .exe
|
||||
package_ext: .zip
|
||||
runner: windows-latest
|
||||
|
||||
- platform: macos
|
||||
targets: x86_64-apple-darwin aarch64-apple-darwin
|
||||
app_suffix: .app/Contents/MacOS
|
||||
package_ext: .dmg
|
||||
runner: macos-latest
|
||||
runs-on: ${{ matrix.runner }}
|
||||
permissions:
|
||||
# Required to create a GitHub release: <https://docs.github.com/en/rest/releases/releases#create-a-release>.
|
||||
contents: write
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
steps:
|
||||
- name: Set up environment
|
||||
run: |
|
||||
# Default values:
|
||||
echo "app_binary_name=${app_binary_name:=${{ env.cargo_build_binary_name }}}" >> "${GITHUB_ENV}"
|
||||
echo "app_package_name=${app_package_name:=${app_binary_name}}" >> "${GITHUB_ENV}"
|
||||
echo "app_display_name=${app_display_name:=${app_package_name}}" >> "${GITHUB_ENV}"
|
||||
echo "app_short_name=${app_short_name:=${app_display_name}}" >> "${GITHUB_ENV}"
|
||||
|
||||
# File paths:
|
||||
echo "app=tmp/app/${app_package_name}"'${{ matrix.app_suffix }}' >> "${GITHUB_ENV}"
|
||||
echo "package=${app_package_name}-"'${{ matrix.platform }}${{ matrix.package_ext }}' >> "${GITHUB_ENV}"
|
||||
|
||||
# macOS environment:
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
echo 'MACOSX_DEPLOYMENT_TARGET=11.0' >> "${GITHUB_ENV}" # macOS 11.0 Big Sur is the first version to support universal binaries.
|
||||
echo "SDKROOT=$(xcrun --sdk macosx --show-sdk-path)" >> "${GITHUB_ENV}"
|
||||
fi
|
||||
|
||||
# Check if building for this platform is enabled.
|
||||
echo 'is_platform_enabled=${{
|
||||
(matrix.platform == 'web' && env.build_for_web == 'true') ||
|
||||
(matrix.platform == 'linux' && env.build_for_linux == 'true') ||
|
||||
(matrix.platform == 'windows' && env.build_for_windows == 'true') ||
|
||||
(matrix.platform == 'macos' && env.build_for_macos == 'true')
|
||||
}}' >> "${GITHUB_ENV}"
|
||||
|
||||
- name: Checkout repository
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
lfs: ${{ env.git_lfs }}
|
||||
|
||||
- name: Install Rust toolchain
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
targets: ${{ matrix.targets }}
|
||||
|
||||
- name: Restore Rust cache
|
||||
if: ${{ env.is_platform_enabled == 'true' && env.use_github_cache == 'true' }}
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
shared-key: release
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Install build dependencies (Linux)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'linux' }}
|
||||
run: sudo apt-get update; sudo apt-get install --no-install-recommends libasound2-dev libudev-dev libwayland-dev libxkbcommon-dev
|
||||
|
||||
- name: Prepare output directories
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
run: rm -rf tmp; mkdir -p tmp/binary '${{ env.app }}'
|
||||
|
||||
- name: Install cargo-binstall
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: cargo-bins/cargo-binstall@main
|
||||
|
||||
- name: Install Bevy CLI
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
run: cargo binstall --locked --no-confirm --force --git='https://github.com/TheBevyFlock/bevy_cli' bevy_cli
|
||||
|
||||
- name: Build and add web bundle to app (Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'web' }}
|
||||
run: |
|
||||
cargo binstall --locked --no-confirm --force wasm-bindgen-cli
|
||||
cargo binstall --locked --no-confirm --force wasm-opt
|
||||
bevy build --locked --release --features='${{ matrix.features }}' --yes web --bundle
|
||||
mv 'target/bevy_web/web-release/${{ env.cargo_build_binary_name }}' '${{ env.app }}'
|
||||
|
||||
- name: Build and add binaries to app (non-Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'web' }}
|
||||
run: |
|
||||
for target in ${{ matrix.targets }}; do
|
||||
bevy build --locked --release --target="${target}" --features='${{ matrix.features }}'
|
||||
mv target/"${target}"/release/'${{ env.cargo_build_binary_name }}${{ matrix.binary_ext }}' tmp/binary/"${target}"'${{ matrix.binary_ext }}'
|
||||
done
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
lipo tmp/binary/*'${{ matrix.binary_ext }}' -create -output '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}'
|
||||
else
|
||||
mv tmp/binary/*'${{ matrix.binary_ext }}' '${{ env.app }}/${{ env.app_binary_name }}${{ matrix.binary_ext }}'
|
||||
fi
|
||||
|
||||
- name: Add assets to app (non-Web)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'web' }}
|
||||
run: cp -R ./'${{ env.assets_path }}' '${{ env.app }}' || true # Ignore error if assets folder does not exist.
|
||||
|
||||
- name: Add metadata to app (macOS)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'macos' }}
|
||||
run: |
|
||||
cat >'${{ env.app }}/../Info.plist' <<EOF
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${{ env.app_display_name }}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${{ env.app_binary_name }}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${{ env.app_id }}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${{ env.app_short_name }}</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${{ env.version }}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${{ env.version }}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
EOF
|
||||
|
||||
- name: Package app (non-Windows)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform != 'windows' }}
|
||||
working-directory: tmp/app
|
||||
run: |
|
||||
if [ '${{ matrix.platform }}' = 'macos' ]; then
|
||||
ln -s /Applications .
|
||||
hdiutil create -fs HFS+ -volname '${{ env.app_package_name }}' -srcfolder . '${{ env.package }}'
|
||||
else
|
||||
zip --recurse-paths '${{ env.package }}' '${{ env.app_package_name }}'
|
||||
fi
|
||||
|
||||
- name: Package app (Windows)
|
||||
if: ${{ env.is_platform_enabled == 'true' && matrix.platform == 'windows' }}
|
||||
working-directory: tmp/app
|
||||
shell: pwsh
|
||||
run: Compress-Archive -Path '${{ env.app_package_name }}' -DestinationPath '${{ env.package }}'
|
||||
|
||||
- name: Upload package to workflow artifacts
|
||||
if: ${{ env.is_platform_enabled == 'true' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: tmp/app/${{ env.package }}
|
||||
name: package-${{ matrix.platform }}
|
||||
retention-days: 1
|
||||
|
||||
- name: Upload package to GitHub release
|
||||
if: ${{ env.is_platform_enabled == 'true' && env.upload_to_github == 'true' }}
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: tmp/app/${{ env.package }}
|
||||
asset_name: ${{ env.package }}
|
||||
release_name: ${{ env.version }}
|
||||
tag: ${{ env.version }}
|
||||
overwrite: true
|
||||
|
||||
# Upload all packages to itch.io.
|
||||
upload-to-itch:
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- forward-env
|
||||
- get-version
|
||||
- build
|
||||
if: ${{ needs.forward-env.outputs.upload_to_itch != '' }}
|
||||
|
||||
steps:
|
||||
- name: Download all packages
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: package-*
|
||||
path: tmp
|
||||
|
||||
- name: Install butler
|
||||
run: |
|
||||
curl -L -o butler.zip 'https://broth.itch.zone/butler/linux-amd64/LATEST/archive/default'
|
||||
unzip butler.zip
|
||||
chmod +x butler
|
||||
./butler -V
|
||||
|
||||
- name: Upload all packages to itch.io
|
||||
env:
|
||||
BUTLER_API_KEY: ${{ secrets.BUTLER_CREDENTIALS }}
|
||||
run: |
|
||||
for channel in $(ls tmp); do
|
||||
./butler push \
|
||||
--fix-permissions \
|
||||
--userversion='${{ needs.get-version.outputs.version }}' \
|
||||
tmp/"${channel}"/* \
|
||||
'${{ env.upload_to_itch }}':"${channel#package-}"
|
||||
done
|
||||
{%- endraw %}
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||
</state>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/phos-neo.iml" filepath="$PROJECT_DIR$/.idea/phos-neo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
13
.idea/phos-neo.iml
generated
Normal file
13
.idea/phos-neo.iml
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="EMPTY_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/engine/world_generation/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/game/camera_system/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/game/main/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -12,19 +12,8 @@
|
||||
"name": "Debug",
|
||||
"program": "${workspaceRoot}/target/debug/phos.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceRoot}/game/main",
|
||||
"preLaunchTask": "Build",
|
||||
"environment": [
|
||||
// {
|
||||
// "name": "RUST_BACKTRACE",
|
||||
// "value": "1"
|
||||
// },
|
||||
{
|
||||
//Set Asset Folder
|
||||
"name": "CARGO_MANIFEST_DIR",
|
||||
"value": "${workspaceRoot}\\game\\main"
|
||||
}
|
||||
]
|
||||
"cwd": "${workspaceRoot}/target/debug",
|
||||
"preLaunchTask": "Build"
|
||||
}
|
||||
]
|
||||
}
|
||||
5918
Cargo.lock
generated
5918
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
13
Cargo.toml
13
Cargo.toml
@@ -2,15 +2,9 @@
|
||||
resolver = "2"
|
||||
members = [
|
||||
"game/main",
|
||||
"game/shared",
|
||||
"game/buildings",
|
||||
"game/units",
|
||||
"game/resources",
|
||||
"engine/world_generation",
|
||||
"engine/asset_loader",
|
||||
"engine/data",
|
||||
"engine/hex",
|
||||
]
|
||||
"game/camera_system"
|
||||
, "engine/asset_loader"]
|
||||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
[profile.dev]
|
||||
@@ -19,6 +13,3 @@ opt-level = 1
|
||||
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
# Phos
|
||||
|
||||
You play as a robot colonizer that has been sent out to an alien planet with one goal. Harvest its resources and send them back home. To accomplish this you must build a base on the planet. As your base develops you will be able to build more advanced structures to harvest at an increased scale. And eventually you will be able to accomplish your goal of sending resources back to your mother planet. But your invasion wont go unchallenged. The native inhabitants of the planet won’t sit idly by as you strip their world of its resources.
|
||||
|
||||

|
||||
|
||||
@@ -3,11 +3,9 @@ name = "asset_loader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
serde = "1.0.228"
|
||||
serde_json = "1.0.149"
|
||||
bevy = "0.18.0"
|
||||
ron = "0.12.0"
|
||||
serde = "1.0.197"
|
||||
serde_json = "1.0.115"
|
||||
bevy = "0.13.2"
|
||||
|
||||
@@ -1 +1,106 @@
|
||||
pub mod macros;
|
||||
pub mod macros {
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! create_asset_loader {
|
||||
(
|
||||
$plugin_name: ident,
|
||||
$loader_name: ident,
|
||||
$asset_type: ident,
|
||||
$asset_loadstate_name: ident,
|
||||
$extensions: expr,
|
||||
$($string_name: ident -> $handle_name: ident)* ;
|
||||
$($string_array_name: ident -> $handle_array_name: ident)* ?
|
||||
) => {
|
||||
use bevy::prelude::*;
|
||||
use bevy::asset::{AssetLoader, AssetEvent, LoadContext, AsyncReadExt, io::Reader};
|
||||
use bevy::utils::BoxedFuture;
|
||||
pub struct $plugin_name;
|
||||
impl Plugin for $plugin_name {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_asset::<$asset_type>()
|
||||
.init_asset_loader::<$loader_name>()
|
||||
.insert_resource($asset_loadstate_name::default())
|
||||
.add_systems(Update, finalize);
|
||||
}
|
||||
}
|
||||
|
||||
fn finalize(
|
||||
mut asset_events: EventReader<AssetEvent<$asset_type>>,
|
||||
mut assets: ResMut<Assets<$asset_type>>,
|
||||
mut load_state: ResMut<$asset_loadstate_name>,
|
||||
asset_server: Res<AssetServer>
|
||||
) {
|
||||
for event in asset_events.read() {
|
||||
match event {
|
||||
AssetEvent::Added { id } => load_state.added += 1,
|
||||
AssetEvent::LoadedWithDependencies { id } => {
|
||||
let asset = assets.get_mut(id.clone()).unwrap();
|
||||
|
||||
$(
|
||||
asset.$handle_name = asset_server.load(&asset.$string_name);
|
||||
)*
|
||||
$(
|
||||
for i in 0..asset.$string_array_name.len(){
|
||||
asset.$handle_array_name.push(asset_server.load(&asset.$string_array_name[i]));
|
||||
}
|
||||
)?
|
||||
load_state.loaded += 1;
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Default)]
|
||||
pub struct $asset_loadstate_name{
|
||||
pub loaded: u32,
|
||||
pub added: u32,
|
||||
}
|
||||
|
||||
impl $asset_loadstate_name{
|
||||
pub fn is_all_loaded(&self) -> bool{
|
||||
if self.added == 0{
|
||||
return false;
|
||||
}
|
||||
return self.loaded >= self.added;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct $loader_name;
|
||||
|
||||
impl AssetLoader for $loader_name {
|
||||
type Asset = $asset_type;
|
||||
|
||||
type Settings = ();
|
||||
|
||||
type Error = String;
|
||||
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
reader: &'a mut Reader,
|
||||
_settings: &'a Self::Settings,
|
||||
_load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
|
||||
return Box::pin(async move {
|
||||
let mut data: String = String::new();
|
||||
let read_result = reader.read_to_string(&mut data).await;
|
||||
if read_result.is_err() {
|
||||
return Err(read_result.err().unwrap().to_string());
|
||||
}
|
||||
let serialized: Result<Self::Asset, serde_json::Error> =
|
||||
serde_json::from_str(&data);
|
||||
if serialized.is_err() {
|
||||
return Err(serialized.err().unwrap().to_string());
|
||||
}
|
||||
return Ok(serialized.unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
$extensions
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
#[macro_export]
|
||||
macro_rules! create_asset_loader {
|
||||
(
|
||||
$plugin_name: ident,
|
||||
$loader_name: ident,
|
||||
$asset_type: ident,
|
||||
$extensions: expr,
|
||||
$($string_name: ident -> $handle_name: ident)* ;
|
||||
$($string_array_name: ident -> $handle_array_name: ident)* ?
|
||||
) => {
|
||||
use bevy::prelude::*;
|
||||
use bevy::asset::{AssetLoader, AssetEvent, LoadContext, LoadState, AsyncReadExt, io::Reader};
|
||||
pub struct $plugin_name;
|
||||
impl Plugin for $plugin_name {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_asset::<$asset_type>()
|
||||
.init_asset_loader::<$loader_name>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, TypePath)]
|
||||
pub struct $loader_name;
|
||||
|
||||
impl AssetLoader for $loader_name {
|
||||
type Asset = $asset_type;
|
||||
|
||||
type Settings = ();
|
||||
|
||||
type Error = String;
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
reader: &mut dyn bevy::asset::io::Reader,
|
||||
settings: &Self::Settings,
|
||||
load_context: &mut bevy::asset::LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error>{
|
||||
let mut bytes = Vec::new();
|
||||
let read_result = reader.read_to_end(&mut bytes).await;
|
||||
if read_result.is_err() {
|
||||
return Err(read_result.err().unwrap().to_string());
|
||||
}
|
||||
let serialized: Result<Self::Asset, _> =
|
||||
ron::de::from_bytes::<Self::Asset>(&bytes);
|
||||
if serialized.is_err() {
|
||||
return Err(serialized.err().unwrap().to_string());
|
||||
}
|
||||
let mut asset = serialized.unwrap();
|
||||
$(
|
||||
|
||||
asset.$handle_name = load_context.load(&asset.$string_name);
|
||||
)*
|
||||
$(
|
||||
for i in 0..asset.$string_array_name.len(){
|
||||
asset.$handle_array_name.push(load_context.load(&asset.$string_array_name[i]));
|
||||
}
|
||||
)?
|
||||
return Ok(asset);
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
$extensions
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
[package]
|
||||
name = "data"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
[package]
|
||||
name = "hex"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.18.1"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
@@ -1,90 +0,0 @@
|
||||
use crate::SHORT_DIAGONAL;
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Chunk
|
||||
{
|
||||
pub heights: [f32; Chunk::AREA],
|
||||
pub textures: [[u32; 2]; Chunk::AREA],
|
||||
// pub biome_data: [BiomeData; Chunk::AREA],
|
||||
pub biome_id: [usize; Chunk::AREA],
|
||||
pub chunk_offset: IVec2,
|
||||
pub min_level: f32,
|
||||
pub max_level: f32,
|
||||
}
|
||||
|
||||
impl Default for Chunk
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
heights: [0.; Chunk::AREA],
|
||||
textures: [[0; 2]; Chunk::AREA],
|
||||
// biome_data: [BiomeData::default(); Chunk::AREA],
|
||||
biome_id: [0; Chunk::AREA],
|
||||
chunk_offset: Default::default(),
|
||||
min_level: 0.0,
|
||||
max_level: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Chunk
|
||||
{
|
||||
pub const SIZE: usize = 64;
|
||||
pub const AREA: usize = Chunk::SIZE * Chunk::SIZE;
|
||||
pub const WORLD_WIDTH: f32 = Chunk::SIZE as f32 * SHORT_DIAGONAL;
|
||||
pub const WORLD_HEIGHT: f32 = Chunk::SIZE as f32 * 1.5;
|
||||
pub const WORLD_SIZE: Vec2 = Vec2::new(Chunk::WORLD_WIDTH, Chunk::WORLD_HEIGHT);
|
||||
|
||||
pub fn get_pos_z_edge(&self) -> [f32; Chunk::SIZE]
|
||||
{
|
||||
let mut data = [0.; Chunk::SIZE];
|
||||
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = x + (Chunk::SIZE - 1) * Chunk::SIZE;
|
||||
data[x] = self.heights[idx];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn get_neg_z_edge(&self) -> [f32; Chunk::SIZE]
|
||||
{
|
||||
let mut data = [0.; Chunk::SIZE];
|
||||
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
data[x] = self.heights[x];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn get_pos_x_edge(&self) -> [f32; Chunk::SIZE]
|
||||
{
|
||||
let mut data = [0.; Chunk::SIZE];
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = (Chunk::SIZE - 1) + z * Chunk::SIZE;
|
||||
data[z] = self.heights[idx];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn get_neg_x_edge(&self) -> [f32; Chunk::SIZE]
|
||||
{
|
||||
let mut data = [0.; Chunk::SIZE];
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = z * Chunk::SIZE;
|
||||
data[z] = self.heights[idx];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{INNER_RADIUS, OUTER_RADIUS, chunk::Chunk};
|
||||
|
||||
pub fn offset3d_to_world(offset: Vec3) -> Vec3
|
||||
{
|
||||
let x = (offset.x + (offset.z * 0.5) - (offset.z / 2.).floor()) * (INNER_RADIUS * 2.);
|
||||
return Vec3::new(x, offset.y, offset.z * OUTER_RADIUS * 1.5);
|
||||
}
|
||||
|
||||
pub fn offset_to_world(offset: IVec2, height: f32) -> Vec3
|
||||
{
|
||||
let off = offset.as_vec2();
|
||||
let x = (off.x + (off.y * 0.5) - (off.y / 2.).floor()) * (INNER_RADIUS * 2.);
|
||||
return Vec3::new(x, height, off.y * OUTER_RADIUS * 1.5);
|
||||
}
|
||||
|
||||
pub fn offset_to_hex(offset: IVec2) -> IVec3
|
||||
{
|
||||
let mut v = IVec3 {
|
||||
x: offset.x - (offset.y / 2),
|
||||
y: offset.y,
|
||||
z: 0,
|
||||
};
|
||||
v.z = -v.x - v.y;
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn offset_to_index(offset: IVec2, width: usize) -> usize
|
||||
{
|
||||
return offset.x as usize + offset.y as usize * width;
|
||||
}
|
||||
|
||||
pub fn snap_to_hex_grid(world_pos: Vec3) -> Vec3
|
||||
{
|
||||
return offset_to_world(world_to_offset_pos(world_pos), world_pos.y);
|
||||
}
|
||||
|
||||
pub fn world_to_offset_pos(world_pos: Vec3) -> IVec2
|
||||
{
|
||||
let offset = world_pos.z / (OUTER_RADIUS * 3.);
|
||||
let x = (world_pos.x / (INNER_RADIUS * 2.)) - offset;
|
||||
let z = -world_pos.x - offset;
|
||||
|
||||
let ix = x.round() as i32;
|
||||
let iz = z.round() as i32;
|
||||
let ox = ix + iz / 2;
|
||||
let oz = iz;
|
||||
return IVec2::new(ox, oz);
|
||||
}
|
||||
|
||||
pub fn tile_to_world_distance(dist: u32) -> f32
|
||||
{
|
||||
return dist as f32 * (2. * INNER_RADIUS);
|
||||
}
|
||||
|
||||
pub fn get_tile_count_in_range(radius: usize) -> usize
|
||||
{
|
||||
return 1 + 3 * (radius + 1) * radius;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, Hash)]
|
||||
pub struct HexCoord
|
||||
{
|
||||
pub hex: IVec3,
|
||||
}
|
||||
|
||||
impl Display for HexCoord
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
|
||||
{
|
||||
f.write_fmt(format_args!("HexCoord{}", self.hex))
|
||||
}
|
||||
}
|
||||
|
||||
impl HexCoord
|
||||
{
|
||||
pub const DIRECTIONS: [IVec3; 6] = [
|
||||
IVec3::new(0, 1, -1),
|
||||
IVec3::new(1, 0, -1),
|
||||
IVec3::new(1, -1, 0),
|
||||
IVec3::new(0, -1, 1),
|
||||
IVec3::new(-1, 0, 1),
|
||||
IVec3::new(-1, 1, 0),
|
||||
];
|
||||
|
||||
pub const ZERO: HexCoord = HexCoord { hex: IVec3::ZERO };
|
||||
|
||||
pub fn new(x: i32, z: i32) -> Self
|
||||
{
|
||||
return HexCoord {
|
||||
hex: IVec3::new(x, z, -x - z),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_axial(hex: IVec2) -> Self
|
||||
{
|
||||
return HexCoord {
|
||||
hex: IVec3::new(hex.x, hex.y, -hex.x - hex.y),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_offset_pos(x: usize, z: usize) -> Self
|
||||
{
|
||||
return HexCoord::new(x as i32 - (z as i32 / 2), z as i32);
|
||||
}
|
||||
pub fn from_offset(offset_pos: IVec2) -> Self
|
||||
{
|
||||
return HexCoord {
|
||||
hex: offset_to_hex(offset_pos),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_world_pos(world_pos: Vec3) -> Self
|
||||
{
|
||||
let offset = world_pos.z / (OUTER_RADIUS * 3.);
|
||||
let mut x = world_pos.x / (INNER_RADIUS * 2.);
|
||||
let mut z = -x;
|
||||
z -= offset;
|
||||
x -= offset;
|
||||
|
||||
let i_x = x.round() as i32;
|
||||
let i_z = (-x - z).round() as i32;
|
||||
let offset_pos = IVec2::new(i_x + i_z / 2, i_z);
|
||||
|
||||
return Self::from_offset(offset_pos);
|
||||
}
|
||||
|
||||
pub fn is_in_bounds(&self, map_height: usize, map_width: usize) -> bool
|
||||
{
|
||||
let off = self.to_offset();
|
||||
if off.x < 0 || off.y < 0
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if off.x >= map_width as i32 || off.y >= map_height as i32
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn is_on_chunk_edge(&self) -> bool
|
||||
{
|
||||
let offset = self.to_offset().rem_euclid(IVec2::splat(Chunk::SIZE as i32));
|
||||
let e = (Chunk::SIZE - 1) as i32;
|
||||
return offset.x == 0 || offset.y == 0 || offset.x == e || offset.y == e;
|
||||
}
|
||||
|
||||
pub fn to_chunk_pos(&self) -> IVec2
|
||||
{
|
||||
let off = self.to_offset();
|
||||
|
||||
return IVec2 {
|
||||
x: (off.x as f32 / Chunk::SIZE as f32).floor() as i32,
|
||||
y: (off.y as f32 / Chunk::SIZE as f32).floor() as i32,
|
||||
};
|
||||
}
|
||||
|
||||
/// Converts this coordinate to it's chunk local equivalent
|
||||
pub fn to_chunk(&self) -> HexCoord
|
||||
{
|
||||
let c_pos = self.to_chunk_pos();
|
||||
let off = self.to_offset();
|
||||
return HexCoord::from_offset(
|
||||
(
|
||||
off.x - (c_pos.x * Chunk::SIZE as i32),
|
||||
off.y - (c_pos.y * Chunk::SIZE as i32),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn to_world(&self, height: f32) -> Vec3
|
||||
{
|
||||
return offset_to_world(self.to_offset(), height);
|
||||
}
|
||||
|
||||
pub fn to_offset(&self) -> IVec2
|
||||
{
|
||||
return IVec2::new(self.hex.x + (self.hex.y / 2), self.hex.y);
|
||||
}
|
||||
|
||||
/// Convert the current coordiante to an index
|
||||
pub fn to_index(&self, width: usize) -> usize
|
||||
{
|
||||
return ((self.hex.x + self.hex.y * width as i32) + (self.hex.y / 2)) as usize;
|
||||
}
|
||||
|
||||
/// Gets the index of this coord in the chunk array.
|
||||
///
|
||||
/// [`width`] is in number of chunks
|
||||
pub fn to_chunk_index(&self, width: usize) -> usize
|
||||
{
|
||||
let pos = self.to_chunk_pos();
|
||||
return (pos.x + pos.y * width as i32) as usize;
|
||||
}
|
||||
|
||||
/// Gets the index of this tile in the chunk
|
||||
pub fn to_chunk_local_index(&self) -> usize
|
||||
{
|
||||
return self.to_chunk().to_index(Chunk::SIZE);
|
||||
}
|
||||
|
||||
pub fn distance(&self, other: &HexCoord) -> i32
|
||||
{
|
||||
return (self.hex.x - other.hex.x).abs() + (self.hex.y - other.hex.y).abs() + (self.hex.z - other.hex.z).abs();
|
||||
}
|
||||
|
||||
pub fn rotate_around(&self, center: &HexCoord, angle: i32) -> HexCoord
|
||||
{
|
||||
if self == center || angle == 0
|
||||
{
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
let mut a = angle % 6;
|
||||
let mut pc = self.hex - center.hex;
|
||||
|
||||
if a > 0
|
||||
{
|
||||
for _ in 0..a
|
||||
{
|
||||
pc = Self::slide_right(pc);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
a = a.abs();
|
||||
for _ in 0..a
|
||||
{
|
||||
pc = Self::slide_left(pc);
|
||||
}
|
||||
}
|
||||
return HexCoord::from_axial(pc.xy() + center.hex.xy());
|
||||
}
|
||||
|
||||
fn slide_left(hex: IVec3) -> IVec3
|
||||
{
|
||||
return (hex * -1).yzx();
|
||||
}
|
||||
|
||||
fn slide_right(hex: IVec3) -> IVec3
|
||||
{
|
||||
return (hex * -1).zxy();
|
||||
}
|
||||
|
||||
pub fn scale(&self, dir: usize, radius: usize) -> HexCoord
|
||||
{
|
||||
let s = Self::DIRECTIONS[dir % 6] * radius as i32;
|
||||
return Self::from_axial(self.hex.xy() + s.xy());
|
||||
}
|
||||
|
||||
pub fn get_neighbor(&self, dir: usize) -> HexCoord
|
||||
{
|
||||
let d = Self::DIRECTIONS[dir % 6];
|
||||
return Self::from_axial(self.hex.xy() + d.xy());
|
||||
}
|
||||
|
||||
pub fn get_neighbors(&self) -> [HexCoord; 6]
|
||||
{
|
||||
return [
|
||||
self.get_neighbor(0),
|
||||
self.get_neighbor(1),
|
||||
self.get_neighbor(2),
|
||||
self.get_neighbor(3),
|
||||
self.get_neighbor(4),
|
||||
self.get_neighbor(5),
|
||||
];
|
||||
}
|
||||
|
||||
pub fn hex_select(&self, radius: usize, include_center: bool) -> Vec<HexCoord>
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
let mut result = Vec::with_capacity(get_tile_count_in_range(radius));
|
||||
|
||||
if include_center
|
||||
{
|
||||
result.push(*self);
|
||||
}
|
||||
|
||||
for k in 0..(radius + 1)
|
||||
{
|
||||
let mut p = self.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
result.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn hex_select_bounded(&self, radius: usize, include_center: bool, height: usize, width: usize)
|
||||
-> Vec<HexCoord>
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
let mut result = Vec::with_capacity(get_tile_count_in_range(radius));
|
||||
|
||||
if include_center
|
||||
{
|
||||
if self.is_in_bounds(height, width)
|
||||
{
|
||||
result.push(*self);
|
||||
}
|
||||
}
|
||||
|
||||
for k in 0..(radius + 1)
|
||||
{
|
||||
let mut p = self.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
if p.is_in_bounds(height, width)
|
||||
{
|
||||
result.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn select_ring(&self, radius: usize) -> Vec<HexCoord>
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
let mut result = Vec::with_capacity(radius * 6);
|
||||
|
||||
let mut p = self.scale(4, radius);
|
||||
|
||||
// if radius == 1 {
|
||||
// result.push(*self);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..radius
|
||||
{
|
||||
result.push(p);
|
||||
p = p.get_neighbor(i);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
mod chunk;
|
||||
mod hex_coord;
|
||||
pub mod prelude
|
||||
{
|
||||
pub use crate::chunk::*;
|
||||
pub use crate::hex_coord::*;
|
||||
pub use crate::*;
|
||||
}
|
||||
|
||||
pub const OUTER_RADIUS: f32 = 1.;
|
||||
pub const INNER_RADIUS: f32 = OUTER_RADIUS * (SQRT_3 / 2.);
|
||||
pub const SHORT_DIAGONAL: f32 = 1. * SQRT_3;
|
||||
pub const LONG_DIAGONAL: f32 = 2. * OUTER_RADIUS;
|
||||
pub const SQRT_3: f32 = 1.7320508076;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -1,11 +0,0 @@
|
||||
use super::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn create_coord()
|
||||
{
|
||||
let center = HexCoord::from_offset_pos(3, 3);
|
||||
for dir in 0..6
|
||||
{
|
||||
assert_eq!(center.get_neighbor(dir).get_neighbor(dir), center.scale(dir, 2));
|
||||
}
|
||||
}
|
||||
@@ -6,21 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.18.0"
|
||||
bevy = "0.13.2"
|
||||
noise = "0.9.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
asset_loader = { path = "../asset_loader" }
|
||||
rayon = "1.11.0"
|
||||
bevy-inspector-egui = "0.36.0"
|
||||
bevy_asset_loader = { version = "0.25.0", features = [
|
||||
"standard_dynamic_assets",
|
||||
"3d",
|
||||
] }
|
||||
ron = "0.12.0"
|
||||
image = "0.25.9"
|
||||
num = "0.4.3"
|
||||
hex = { path = "../hex" }
|
||||
|
||||
[features]
|
||||
tracing = ["bevy/trace_tracy"]
|
||||
serde = {version="1.0.197", features=["derive"]}
|
||||
serde_json = "1.0.115"
|
||||
asset_loader = {path = "../asset_loader"}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{prelude::NoiseConfig, tile_mapper::TileMapperAsset};
|
||||
|
||||
#[derive(Serialize, Deserialize, Asset, TypePath, Debug, Clone)]
|
||||
pub struct BiomeAsset {
|
||||
pub moisture: f32,
|
||||
pub temperature: f32,
|
||||
pub continentality: f32,
|
||||
pub name: String,
|
||||
#[serde(skip)]
|
||||
pub tile_mapper: Handle<TileMapperAsset>,
|
||||
pub tile_mapper_path: String,
|
||||
pub noise: NoiseConfig,
|
||||
}
|
||||
|
||||
impl BiomeAsset {
|
||||
pub fn distance(&self, data: Vec3) -> f32 {
|
||||
let a = Vec3::new(self.moisture, self.temperature, self.continentality);
|
||||
return (a - data).length();
|
||||
}
|
||||
}
|
||||
|
||||
create_asset_loader!(
|
||||
BiomeAssetPlugin,
|
||||
BiomeAssetLoader,
|
||||
BiomeAsset,
|
||||
&["biome", "biome.ron"],
|
||||
tile_mapper_path -> tile_mapper
|
||||
;
|
||||
?
|
||||
);
|
||||
@@ -1,89 +1,34 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_resource::encase::rts_array::Length;
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{biome_asset::BiomeAsset, map::biome_map::BiomeData};
|
||||
use bevy_asset_loader::prelude::*;
|
||||
use crate::tile_mapper::TileMapperAsset;
|
||||
|
||||
#[derive(AssetCollection, Debug, Clone, Resource)]
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset)]
|
||||
pub struct BiomePainterAsset {
|
||||
#[asset(key = "biomes", collection(typed))]
|
||||
pub biomes: Vec<Handle<BiomeAsset>>,
|
||||
#[serde(skip)]
|
||||
pub biomes: Vec<Handle<TileMapperAsset>>,
|
||||
pub biomes_path: [String; 16],
|
||||
}
|
||||
|
||||
impl BiomePainterAsset {
|
||||
pub fn sample_biome(&self, assets: &Assets<BiomeAsset>, data: &BiomeData) -> AssetId<BiomeAsset> {
|
||||
assert!(self.biomes.length() != 0, "There are no biomes");
|
||||
let mut biome = self.biomes.first().unwrap().id();
|
||||
let mut dist = f32::INFINITY;
|
||||
|
||||
for b in &self.biomes {
|
||||
let asset = assets.get(b.id()).unwrap();
|
||||
let d = asset.distance(data.into());
|
||||
if d < dist {
|
||||
biome = b.id();
|
||||
dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
return biome;
|
||||
}
|
||||
|
||||
pub fn build(&self, assets: &Assets<BiomeAsset>) -> BiomePainter {
|
||||
let mut biomes = Vec::with_capacity(self.biomes.len());
|
||||
for b in &self.biomes {
|
||||
let asset = assets.get(b.id()).unwrap();
|
||||
biomes.push(asset.clone());
|
||||
}
|
||||
return BiomePainter { biomes };
|
||||
pub fn sample_biome(&self, moisture: f32, temperature: f32) -> Handle<TileMapperAsset> {
|
||||
let x = (moisture.clamp(0., 1.) * 3.).ceil() as usize;
|
||||
let y = (temperature.clamp(0., 1.) * 3.).ceil() as usize;
|
||||
return self.biomes[x + y * 4].clone();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct BiomePainter {
|
||||
pub biomes: Vec<BiomeAsset>,
|
||||
}
|
||||
|
||||
impl BiomePainter {
|
||||
pub fn sample_biome(&self, data: &BiomeData) -> &BiomeAsset {
|
||||
assert!(self.biomes.length() != 0, "There are no biomes");
|
||||
let mut biome = &self.biomes[0];
|
||||
let mut dist = f32::INFINITY;
|
||||
|
||||
for b in &self.biomes {
|
||||
let d = b.distance(data.into());
|
||||
if d < dist {
|
||||
biome = b;
|
||||
dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
return biome;
|
||||
}
|
||||
|
||||
pub fn sample_biome_index(&self, data: &BiomeData) -> usize {
|
||||
assert!(self.biomes.length() != 0, "There are no biomes");
|
||||
let mut biome = 0;
|
||||
let mut dist = f32::INFINITY;
|
||||
|
||||
for i in 0..self.biomes.len() {
|
||||
let d = self.biomes[i].distance(data.into());
|
||||
if d < dist {
|
||||
biome = i;
|
||||
dist = d;
|
||||
}
|
||||
}
|
||||
|
||||
return biome;
|
||||
}
|
||||
}
|
||||
|
||||
// create_asset_loader!(
|
||||
// BiomePainterPlugin,
|
||||
// BiomePainterLoader,
|
||||
// BiomePainterAsset,
|
||||
// BiomePainterLoadState,
|
||||
// &["bimoes.json"],
|
||||
// ;
|
||||
// biomes_path -> biomes
|
||||
// ?
|
||||
// );
|
||||
create_asset_loader!(
|
||||
BiomePainterPlugin,
|
||||
BiomePainterLoader,
|
||||
BiomePainterAsset,
|
||||
BiomePainterLoadState,
|
||||
&["bimoes.json"],
|
||||
;
|
||||
biomes_path -> biomes
|
||||
?
|
||||
);
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
use bevy::{mesh::MeshVertexAttribute, prelude::*, render::render_resource::VertexFormat};
|
||||
use hex::{INNER_RADIUS, OUTER_RADIUS};
|
||||
|
||||
// use crate::hex_utils::{INNER_RADIUS, OUTER_RADIUS};
|
||||
|
||||
pub const TEX_MULTI: Vec2 = Vec2::new(1000., 1.);
|
||||
|
||||
pub const HEX_CORNERS: [Vec3; 6] = [
|
||||
Vec3::new(0., 0., OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(0., 0., -OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
];
|
||||
|
||||
pub const WATER_HEX_CORNERS: [Vec3; 12] = [
|
||||
Vec3::new(0., 0., OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS / 2.0, 0., 0.75 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.),
|
||||
Vec3::new(INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS / 2.0, 0., -0.75 * OUTER_RADIUS),
|
||||
Vec3::new(0., 0., -OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS / 2.0, 0., -0.75 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS / 2.0, 0., 0.75 * OUTER_RADIUS),
|
||||
];
|
||||
|
||||
pub const HEX_NORMALS: [Vec3; 6] = [
|
||||
Vec3::new(INNER_RADIUS / 2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.),
|
||||
Vec3::Z,
|
||||
Vec3::new(INNER_RADIUS / -2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.),
|
||||
Vec3::new(INNER_RADIUS / -2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.),
|
||||
Vec3::NEG_Z,
|
||||
Vec3::new(INNER_RADIUS / 2., 0., (OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.),
|
||||
];
|
||||
|
||||
pub const ATTRIBUTE_PACKED_VERTEX_DATA: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("PackedVertexData", 7, VertexFormat::Uint32);
|
||||
pub const ATTRIBUTE_VERTEX_HEIGHT: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("VertexHeight", 8, VertexFormat::Float32);
|
||||
|
||||
pub const ATTRIBUTE_TEXTURE_INDEX: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("TextureIndex", 988540917, VertexFormat::Uint32);
|
||||
@@ -1,72 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
const CHUNK_TOTAL: usize = Chunk::SIZE * Chunk::SIZE;
|
||||
|
||||
pub fn generate_chunk_collider(chunk: &MeshChunkData) -> (Vec<Vec3>, Vec<[u32; 3]>)
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = info_span!("generate_chunk_collider").entered();
|
||||
let vertex_count: usize = CHUNK_TOTAL * 6;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let height = chunk.heights[x + z * Chunk::SIZE];
|
||||
let coord = HexCoord::from_offset_pos(x, z);
|
||||
let neighbors = chunk.get_neighbors(&coord);
|
||||
let off_pos = Vec3::new(x as f32, height, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
create_tile_collider(tile_pos, &mut verts, &mut indices, &neighbors);
|
||||
}
|
||||
}
|
||||
return (verts, indices);
|
||||
}
|
||||
|
||||
fn create_tile_collider(pos: Vec3, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32; 3]>, neighbors: &[f32; 6])
|
||||
{
|
||||
let idx = verts.len() as u32;
|
||||
for i in 0..6
|
||||
{
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
}
|
||||
|
||||
//Top Surface
|
||||
indices.push([idx, idx + 1, idx + 5]);
|
||||
indices.push([idx + 1, idx + 2, idx + 5]);
|
||||
indices.push([idx + 2, idx + 4, idx + 5]);
|
||||
indices.push([idx + 2, idx + 3, idx + 4]);
|
||||
|
||||
for i in 0..neighbors.len()
|
||||
{
|
||||
let n_height = neighbors[i];
|
||||
if n_height < pos.y
|
||||
{
|
||||
create_tile_wall_collider(
|
||||
idx,
|
||||
Vec3::new(pos.x, n_height.min(pos.y - OUTER_RADIUS / 2.), pos.z),
|
||||
i,
|
||||
verts,
|
||||
indices,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tile_wall_collider(idx: u32, pos: Vec3, dir: usize, verts: &mut Vec<Vec3>, indices: &mut Vec<[u32; 3]>)
|
||||
{
|
||||
let idx2 = verts.len() as u32;
|
||||
|
||||
verts.push(pos + HEX_CORNERS[(dir) % 6]);
|
||||
verts.push(pos + HEX_CORNERS[(dir + 1) % 6]);
|
||||
|
||||
let off = dir as u32;
|
||||
indices.push([idx + off, idx + ((off + 1) % 6), idx2 + 1]);
|
||||
indices.push([idx + off, idx2 + 1, idx2]);
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use bevy::asset::RenderAssetUsages;
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::mesh::{Indices, PrimitiveTopology};
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
pub fn generate_chunk_mesh(chunk: &MeshChunkData) -> Mesh
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = info_span!("generate_chunk_mesh").entered();
|
||||
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 6;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut uvs = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut normals = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = x + z * Chunk::SIZE;
|
||||
let height = chunk.heights[idx];
|
||||
let off_pos = Vec3::new(x as f32, height, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
let coord = HexCoord::from_offset_pos(x, z);
|
||||
let n = chunk.get_neighbors(&coord);
|
||||
|
||||
create_tile(
|
||||
tile_pos,
|
||||
&n,
|
||||
&mut verts,
|
||||
&mut uvs,
|
||||
&mut indices,
|
||||
&mut normals,
|
||||
// &mut tex,
|
||||
chunk.textures[idx][0],
|
||||
chunk.textures[idx][1],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
fn create_tile(
|
||||
pos: Vec3,
|
||||
neighbors: &[f32; 6],
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
texture_index: u32,
|
||||
side_texture_index: u32,
|
||||
)
|
||||
{
|
||||
let uv_offset = Vec2::splat(0.5);
|
||||
let tex_off = Vec2::new(texture_index as f32, 0.);
|
||||
let side_tex_off = Vec2::new(side_texture_index as f32, 0.);
|
||||
|
||||
let idx = verts.len() as u32;
|
||||
for i in 0..6
|
||||
{
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
let uv = (HEX_CORNERS[i].xz() / 2.) + uv_offset;
|
||||
uvs.push((uv / TEX_MULTI) + tex_off);
|
||||
normals.push(Vec3::Y);
|
||||
}
|
||||
for i in 0..3
|
||||
{
|
||||
let off = i * 2;
|
||||
indices.push(off + idx);
|
||||
indices.push(((off + 1) % 6) + idx);
|
||||
indices.push(((off + 2) % 6) + idx);
|
||||
}
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 4);
|
||||
|
||||
for i in 0..neighbors.len()
|
||||
{
|
||||
let n_height = neighbors[i];
|
||||
if n_height < pos.y
|
||||
{
|
||||
create_tile_wall(pos, i, n_height, verts, uvs, indices, normals, side_tex_off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn generate_chunk_water_mesh(chunk: &MeshChunkData, sealevel: f32, map_width: usize, map_height: usize) -> Mesh
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _gen_mesh = info_span!("Generate Water Surface Mesh").entered();
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 7;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut uvs = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut normals = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = x + z * Chunk::SIZE;
|
||||
let height = chunk.heights[idx];
|
||||
if height > sealevel
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let off_pos = Vec3::new(x as f32, sealevel, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
let coord = HexCoord::from_offset_pos(x, z);
|
||||
let (n, neighbor_has_land) = chunk.get_neighbors_with_water_info(&coord);
|
||||
|
||||
create_tile_water_surface(
|
||||
tile_pos,
|
||||
chunk.distance_to_land[idx],
|
||||
&n,
|
||||
neighbor_has_land,
|
||||
&mut verts,
|
||||
&mut uvs,
|
||||
&mut indices,
|
||||
&mut normals,
|
||||
);
|
||||
}
|
||||
}
|
||||
let mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
fn create_tile_water_surface(
|
||||
pos: Vec3,
|
||||
dist_to_land: f32,
|
||||
neighbors: &[(f32, Option<f32>); 6],
|
||||
neighbor_has_land: bool,
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
)
|
||||
{
|
||||
if !neighbor_has_land
|
||||
{
|
||||
crate_tile_water_inner_surface(pos, dist_to_land, neighbors, verts, uvs, indices, normals);
|
||||
return;
|
||||
}
|
||||
crate_tile_water_shore_surface(pos, dist_to_land, neighbors, verts, uvs, indices, normals);
|
||||
}
|
||||
|
||||
fn crate_tile_water_inner_surface(
|
||||
pos: Vec3,
|
||||
dist_to_land: f32,
|
||||
neighbors: &[(f32, Option<f32>); 6],
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
)
|
||||
{
|
||||
//todo: share verts
|
||||
let idx = verts.len() as u32;
|
||||
for i in 0..6
|
||||
{
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
let n1 = if let Some(v) = neighbors[i].1 { v } else { dist_to_land };
|
||||
let n2 = if let Some(v) = neighbors[(i + 5) % 6].1
|
||||
{
|
||||
v
|
||||
}
|
||||
else
|
||||
{
|
||||
dist_to_land
|
||||
};
|
||||
let d = (n1 + n2 + dist_to_land) / 3.0;
|
||||
uvs.push(Vec2::new(0.0, d.remap(0., 4., 1.0, 0.0)));
|
||||
normals.push(Vec3::Y);
|
||||
}
|
||||
for i in 0..3
|
||||
{
|
||||
let off = i * 2;
|
||||
indices.push(off + idx);
|
||||
indices.push(((off + 1) % 6) + idx);
|
||||
indices.push(((off + 2) % 6) + idx);
|
||||
}
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 4);
|
||||
}
|
||||
|
||||
fn crate_tile_water_shore_surface(
|
||||
pos: Vec3,
|
||||
dist_to_land: f32,
|
||||
neighbors: &[(f32, Option<f32>); 6],
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
)
|
||||
{
|
||||
let idx = verts.len() as u32;
|
||||
//todo: only use triangle fan when on shoreline
|
||||
verts.push(pos);
|
||||
uvs.push(Vec2::new(0.0, dist_to_land.remap(0., 4., 1.0, 0.0)));
|
||||
normals.push(Vec3::Y);
|
||||
for i in 0..12
|
||||
{
|
||||
let p = pos + WATER_HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
let ni = i / 2;
|
||||
let n = neighbors[ni];
|
||||
let nn = neighbors[(ni + 5) % 6];
|
||||
let mut uv = Vec2::new(0.0, dist_to_land.remap(0., 4., 1.0, 0.0));
|
||||
|
||||
if nn.0 > pos.y || n.0 > pos.y
|
||||
{
|
||||
uv.x = 1.0;
|
||||
}
|
||||
if ni * 2 != i
|
||||
{
|
||||
if n.0 <= pos.y
|
||||
{
|
||||
uv.x = 0.0;
|
||||
}
|
||||
let d = if let Some(v) = n.1 { v } else { dist_to_land };
|
||||
uv.y = ((d + dist_to_land) / 2.0).remap(0., 4., 1.0, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
let d = if let Some(v) = n.1 { v } else { dist_to_land };
|
||||
let d2 = if let Some(v) = nn.1 { v } else { dist_to_land };
|
||||
uv.y = ((d + d2 + dist_to_land) / 3.0).remap(0., 4., 1.0, 0.0);
|
||||
}
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1 + i as u32);
|
||||
indices.push(idx + 1 + ((i as u32 + 1) % 12));
|
||||
|
||||
uvs.push(uv);
|
||||
normals.push(Vec3::Y);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tile_wall(
|
||||
pos: Vec3,
|
||||
dir: usize,
|
||||
height: f32,
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
tex_off: Vec2,
|
||||
)
|
||||
{
|
||||
let p1 = HEX_CORNERS[(dir) % 6] + pos;
|
||||
let p2 = HEX_CORNERS[(dir + 1) % 6] + pos;
|
||||
let p3 = Vec3::new(p1.x, height, p1.z);
|
||||
let p4 = Vec3::new(p2.x, height, p2.z);
|
||||
|
||||
let idx = verts.len() as u32;
|
||||
|
||||
verts.push(p1);
|
||||
verts.push(p2);
|
||||
verts.push(p3);
|
||||
verts.push(p4);
|
||||
|
||||
let n = HEX_NORMALS[dir];
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 3);
|
||||
|
||||
uvs.push(Vec2::ZERO + tex_off);
|
||||
uvs.push((Vec2::new(1., 0.) / TEX_MULTI) + tex_off);
|
||||
uvs.push((Vec2::new(0., pos.y - height) / TEX_MULTI) + tex_off);
|
||||
uvs.push((Vec2::new(1., pos.y - height) / TEX_MULTI) + tex_off);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generate_tile_wall()
|
||||
{
|
||||
let mut verts = Vec::new();
|
||||
let mut uvs = Vec::new();
|
||||
let mut normals = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
create_tile_wall(
|
||||
Vec3::ZERO,
|
||||
3,
|
||||
5.0,
|
||||
&mut verts,
|
||||
&mut uvs,
|
||||
&mut indices,
|
||||
&mut normals,
|
||||
Vec2::new(3.0, 0.0),
|
||||
);
|
||||
|
||||
assert!(verts.len() == 4, "Number of verts don't match");
|
||||
assert!(uvs.len() == 4, "Number of uvs don't match");
|
||||
assert!(normals.len() == 4, "Number of normals don't match");
|
||||
assert!(indices.len() == 6, "Number of normals don't match");
|
||||
|
||||
let index = uvs[0].x.floor();
|
||||
assert!(index == 3.0, "Texture Index could not be decoded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generate_tile()
|
||||
{
|
||||
let mut verts = Vec::new();
|
||||
let mut uvs = Vec::new();
|
||||
let mut normals = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
//4 side faces
|
||||
let nbors = [2.0, 2.0, 0.0, 0.0, 0.0, 0.0];
|
||||
|
||||
create_tile(Vec3::Y, &nbors, &mut verts, &mut uvs, &mut indices, &mut normals, 3, 7);
|
||||
|
||||
assert!(verts.len() == (6 + 4 * 4), "Number of verts don't match");
|
||||
assert!(uvs.len() == (6 + 4 * 4), "Number of uvs don't match");
|
||||
assert!(normals.len() == (6 + 4 * 4), "Number of normals don't match");
|
||||
//12 tris for surface, 6 tris per side
|
||||
assert!(indices.len() == (12 + 4 * 6), "Number of indicies don't match");
|
||||
|
||||
let top_index = uvs[0].x.floor();
|
||||
assert!(top_index == 3.0, "Top Texture Index could not be decoded");
|
||||
let side_index = uvs[6].x.floor();
|
||||
assert!(side_index == 7.0, "Top Texture Index could not be decoded");
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
pub mod chunk_colliders;
|
||||
pub mod mesh_generator;
|
||||
pub mod packed_mesh_generator;
|
||||
@@ -1,135 +0,0 @@
|
||||
use crate::prelude::*;
|
||||
use bevy::asset::RenderAssetUsages;
|
||||
use bevy::{
|
||||
mesh::{Indices, PrimitiveTopology},
|
||||
prelude::*,
|
||||
};
|
||||
use hex::prelude::*;
|
||||
|
||||
pub fn generate_packed_chunk_mesh(chunk: &MeshChunkData) -> Mesh
|
||||
{
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 6;
|
||||
let mut packed_data = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut heights = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = x + z * Chunk::SIZE;
|
||||
let height = chunk.heights[idx];
|
||||
let coord = HexCoord::from_offset_pos(x, z);
|
||||
let n = chunk.get_neighbors(&coord);
|
||||
|
||||
create_packed_tile(
|
||||
UVec2::new(x as u32, z as u32),
|
||||
height,
|
||||
&n,
|
||||
&mut packed_data,
|
||||
&mut indices,
|
||||
&mut heights,
|
||||
chunk.textures[idx][0],
|
||||
chunk.textures[idx][1],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(ATTRIBUTE_PACKED_VERTEX_DATA, packed_data)
|
||||
.with_inserted_attribute(ATTRIBUTE_VERTEX_HEIGHT, heights)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
fn create_packed_tile(
|
||||
offset: UVec2,
|
||||
height: f32,
|
||||
neighbors: &[f32; 6],
|
||||
packed_data: &mut Vec<u32>,
|
||||
indices: &mut Vec<u32>,
|
||||
heights: &mut Vec<f32>,
|
||||
texture_index: u32,
|
||||
side_texture_index: u32,
|
||||
)
|
||||
{
|
||||
let idx = packed_data.len() as u32;
|
||||
|
||||
packed_data.push(pack_vertex_data(offset, 0, texture_index));
|
||||
heights.push(height);
|
||||
for i in 0..6
|
||||
{
|
||||
packed_data.push(pack_vertex_data(offset, i + 1, texture_index));
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1 + i as u32);
|
||||
indices.push(idx + 1 + ((i as u32 + 1) % 6));
|
||||
heights.push(height);
|
||||
}
|
||||
|
||||
for i in 0..neighbors.len()
|
||||
{
|
||||
let n_height = neighbors[i];
|
||||
if n_height < height
|
||||
{
|
||||
create_packed_tile_wall(
|
||||
offset,
|
||||
height,
|
||||
n_height,
|
||||
i,
|
||||
packed_data,
|
||||
indices,
|
||||
heights,
|
||||
side_texture_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_packed_tile_wall(
|
||||
offset: UVec2,
|
||||
height_top: f32,
|
||||
height_bottom: f32,
|
||||
side: usize,
|
||||
packed_data: &mut Vec<u32>,
|
||||
indices: &mut Vec<u32>,
|
||||
heights: &mut Vec<f32>,
|
||||
side_texture_index: u32,
|
||||
)
|
||||
{
|
||||
let idx = packed_data.len() as u32;
|
||||
|
||||
let side_2 = ((side + 1) % 6) + 1;
|
||||
packed_data.push(pack_vertex_data(offset, side + 1, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side_2, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side + 1, side_texture_index));
|
||||
packed_data.push(pack_vertex_data(offset, side_2, side_texture_index));
|
||||
|
||||
heights.push(height_top);
|
||||
heights.push(height_top);
|
||||
heights.push(height_bottom);
|
||||
heights.push(height_bottom);
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 3);
|
||||
}
|
||||
|
||||
fn pack_vertex_data(offset: UVec2, vert: usize, tex: u32) -> u32
|
||||
{
|
||||
//6 + 6 bits offset
|
||||
//4 bits vert
|
||||
//12 bits texture
|
||||
let mut data = offset.x;
|
||||
data += (offset.y) << 6;
|
||||
data += (vert as u32) << (6 + 6);
|
||||
data += tex << (6 + 6 + 4);
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -1,280 +1,115 @@
|
||||
use core::f32;
|
||||
|
||||
use bevy::math::{IVec2, UVec2};
|
||||
use bevy::math::IVec2;
|
||||
use bevy::prelude::{FloatExt, Vec2};
|
||||
use bevy::utils::default;
|
||||
use hex::prelude::*;
|
||||
use noise::{NoiseFn, Simplex, SuperSimplex};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use noise::{NoiseFn, SuperSimplex};
|
||||
|
||||
use crate::biome_painter::BiomePainter;
|
||||
use crate::map::biome_map::{BiomeChunk, BiomeData, BiomeMap};
|
||||
use crate::prelude::*;
|
||||
|
||||
pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32, painter: &BiomePainter) -> (Map, BiomeMap)
|
||||
{
|
||||
let biomes = generate_biomes(cfg, seed, painter);
|
||||
let biomes_borrow = &biomes;
|
||||
// let mut chunks: Vec<Chunk> = Vec::with_capacity(cfg.size.length_squared() as usize);
|
||||
let chunks: Vec<Chunk> = (0..cfg.size.y)
|
||||
.into_par_iter()
|
||||
.flat_map(|z| {
|
||||
(0..cfg.size.x).into_par_iter().map(move |x| {
|
||||
let biome_chunk = &biomes_borrow.chunks[x as usize + z as usize * cfg.size.x as usize];
|
||||
return generate_chunk(x, z, cfg, seed, &biome_chunk, painter);
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let mut min = f32::MAX;
|
||||
let mut max = f32::MIN;
|
||||
for chunk in &chunks
|
||||
{
|
||||
if chunk.min_level < min
|
||||
{
|
||||
min = chunk.min_level;
|
||||
}
|
||||
if chunk.max_level > max
|
||||
{
|
||||
max = chunk.max_level;
|
||||
pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32) -> Map {
|
||||
let mut chunks: Vec<Chunk> = Vec::with_capacity(cfg.size.length_squared() as usize);
|
||||
for z in 0..cfg.size.y {
|
||||
for x in 0..cfg.size.x {
|
||||
chunks.push(generate_chunk(x as f64, z as f64, cfg, seed));
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
Map {
|
||||
chunks,
|
||||
height: cfg.size.y as usize,
|
||||
width: cfg.size.x as usize,
|
||||
sealevel: cfg.sea_level as f32,
|
||||
min_level: min,
|
||||
max_level: max,
|
||||
biome_count: painter.biomes.len(),
|
||||
},
|
||||
biomes,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn generate_biomes(cfg: &GenerationConfig, seed: u32, biome_painter: &BiomePainter) -> BiomeMap
|
||||
{
|
||||
let mut map = BiomeMap::new(cfg.size, biome_painter.biomes.len());
|
||||
map.chunks = (0..cfg.size.y)
|
||||
.into_par_iter()
|
||||
.flat_map(|y| {
|
||||
(0..cfg.size.x).into_par_iter().map(move |x| {
|
||||
return generate_biome_chunk(x as usize, y as usize, cfg, seed, biome_painter);
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
map.blend(cfg.biome_blend);
|
||||
return map;
|
||||
}
|
||||
|
||||
pub fn generate_biome_chunk(
|
||||
chunk_x: usize,
|
||||
chunk_y: usize,
|
||||
cfg: &GenerationConfig,
|
||||
seed: u32,
|
||||
biome_painter: &BiomePainter,
|
||||
) -> BiomeChunk
|
||||
{
|
||||
let mut chunk = BiomeChunk {
|
||||
offset: UVec2::new(chunk_x as u32, chunk_y as u32),
|
||||
data: [BiomeData::default(); Chunk::AREA],
|
||||
tiles: Vec::with_capacity(Chunk::AREA),
|
||||
return Map {
|
||||
chunks,
|
||||
height: cfg.size.y as usize,
|
||||
width: cfg.size.x as usize,
|
||||
};
|
||||
let noise_m = Simplex::new(seed + 1);
|
||||
let noise_t = Simplex::new(seed + 2);
|
||||
let noise_c = Simplex::new(seed + 3);
|
||||
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let moisture = sample_point(
|
||||
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
|
||||
z as f64 + chunk_y as f64 * Chunk::SIZE as f64,
|
||||
&cfg.moisture_noise,
|
||||
&noise_m,
|
||||
cfg.size.as_vec2(),
|
||||
cfg.border_size,
|
||||
100.0,
|
||||
);
|
||||
let temperature = sample_point(
|
||||
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
|
||||
z as f64 + chunk_y as f64 * Chunk::SIZE as f64,
|
||||
&cfg.temperature_noise,
|
||||
&noise_t,
|
||||
cfg.size.as_vec2(),
|
||||
cfg.border_size,
|
||||
50.0,
|
||||
);
|
||||
let continentality = sample_point(
|
||||
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
|
||||
z as f64 + chunk_y as f64 * Chunk::SIZE as f64,
|
||||
&cfg.continent_noise,
|
||||
&noise_c,
|
||||
cfg.size.as_vec2(),
|
||||
cfg.border_size,
|
||||
0.0,
|
||||
);
|
||||
let data = BiomeData {
|
||||
moisture: moisture.clamp(0., 100.),
|
||||
temperature: temperature.clamp(0., 100.),
|
||||
continentality: continentality.clamp(0., 100.),
|
||||
};
|
||||
let mut b = vec![0.; biome_painter.biomes.len()];
|
||||
b[biome_painter.sample_biome_index(&data)] = 1.;
|
||||
|
||||
chunk.data[x + z * Chunk::SIZE] = data;
|
||||
chunk.tiles.push(b);
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
pub fn generate_noise_map(size: UVec2, seed: u32, cfg: &NoiseConfig, border_size: f32) -> Vec<f32>
|
||||
{
|
||||
pub fn generate_chunk(chunk_x: f64, chunk_z: f64, cfg: &GenerationConfig, seed: u32) -> Chunk {
|
||||
let mut result: [f32; Chunk::SIZE * Chunk::SIZE] = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let mut moisture = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let mut temp = [0.; Chunk::SIZE * Chunk::SIZE];
|
||||
let noise = SuperSimplex::new(seed);
|
||||
|
||||
let data: Vec<_> = (0..(size.y as usize * Chunk::SIZE))
|
||||
.into_par_iter()
|
||||
.flat_map(|y| {
|
||||
let mut row = Vec::with_capacity(size.x as usize * Chunk::SIZE);
|
||||
for x in 0..row.capacity()
|
||||
{
|
||||
row.push(sample_point(
|
||||
x as f64,
|
||||
y as f64,
|
||||
cfg,
|
||||
&noise,
|
||||
size.as_vec2(),
|
||||
border_size,
|
||||
0.0,
|
||||
));
|
||||
}
|
||||
return row;
|
||||
})
|
||||
.collect();
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn generate_chunk(
|
||||
chunk_x: u32,
|
||||
chunk_z: u32,
|
||||
cfg: &GenerationConfig,
|
||||
seed: u32,
|
||||
biome_chunk: &BiomeChunk,
|
||||
biome_painter: &BiomePainter,
|
||||
) -> Chunk
|
||||
{
|
||||
let mut result: [f32; Chunk::SIZE * Chunk::SIZE] = [0.; Chunk::AREA];
|
||||
let mut data = [BiomeData::default(); Chunk::AREA];
|
||||
let mut biome_ids = [0; Chunk::AREA];
|
||||
let noise = Simplex::new(seed);
|
||||
let mut min = f32::MAX;
|
||||
let mut max = f32::MIN;
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let biome_data = biome_chunk.get_biome_data(x, z);
|
||||
let biome_blend = biome_chunk.get_biome(x, z);
|
||||
let mut sample = 0.;
|
||||
for i in 0..biome_blend.len()
|
||||
{
|
||||
let blend = biome_blend[i];
|
||||
if blend == 0.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let biome = &biome_painter.biomes[i];
|
||||
sample += sample_point(
|
||||
x as f64 + chunk_x as f64 * Chunk::SIZE as f64,
|
||||
z as f64 + chunk_z as f64 * Chunk::SIZE as f64,
|
||||
&biome.noise,
|
||||
&noise,
|
||||
cfg.size.as_vec2(),
|
||||
cfg.border_size,
|
||||
0.0,
|
||||
) * blend;
|
||||
}
|
||||
let idx = x + z * Chunk::SIZE;
|
||||
biome_ids[idx] = biome_chunk.get_biome_id_dithered(x, z, &noise, cfg.biome_dither);
|
||||
result[idx] = sample;
|
||||
if sample > max
|
||||
{
|
||||
max = sample;
|
||||
}
|
||||
if sample < min
|
||||
{
|
||||
min = sample;
|
||||
}
|
||||
data[idx] = biome_data.clone();
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
let sample = sample_point(
|
||||
x as f64 + chunk_x * Chunk::SIZE as f64,
|
||||
z as f64 + chunk_z * Chunk::SIZE as f64,
|
||||
&cfg,
|
||||
&noise,
|
||||
);
|
||||
result[x + z * Chunk::SIZE] = sample;
|
||||
moisture[x + z * Chunk::SIZE] = noise.get([
|
||||
(x as f64 + chunk_x * Chunk::SIZE as f64) / &cfg.noise_scale,
|
||||
(z as f64 + chunk_z * Chunk::SIZE as f64) / &cfg.noise_scale,
|
||||
]) as f32;
|
||||
temp[x + z * Chunk::SIZE] = sample_tempurature(
|
||||
z as f32 + chunk_z as f32 * Chunk::SIZE as f32,
|
||||
sample,
|
||||
&cfg,
|
||||
100.,
|
||||
);
|
||||
}
|
||||
}
|
||||
return Chunk {
|
||||
heights: result,
|
||||
biome_id: biome_ids,
|
||||
moisture: moisture,
|
||||
temperature: temp,
|
||||
chunk_offset: IVec2::new(chunk_x as i32, chunk_z as i32),
|
||||
max_level: max,
|
||||
min_level: min,
|
||||
..default()
|
||||
};
|
||||
}
|
||||
|
||||
fn sample_point(
|
||||
x: f64,
|
||||
z: f64,
|
||||
cfg: &NoiseConfig,
|
||||
noise: &impl NoiseFn<f64, 2>,
|
||||
size: Vec2,
|
||||
border_size: f32,
|
||||
border_value: f32,
|
||||
) -> f32
|
||||
{
|
||||
let x_s = x / cfg.scale;
|
||||
let z_s = z / cfg.scale;
|
||||
fn sample_tempurature(z: f32, height: f32, cfg: &GenerationConfig, equator: f32) -> f32 {
|
||||
let d = (equator - z).abs();
|
||||
let max_d = equator.max(cfg.get_total_height() as f32 - equator);
|
||||
let t_mod = d.remap(0., max_d, 0., 1.).clamp(0., 1.);
|
||||
|
||||
// let max_d = d.max()
|
||||
return (height.remap(0., 50., 0., 1.).clamp(0., 1.) + t_mod) / 2.;
|
||||
}
|
||||
|
||||
fn sample_point(x: f64, z: f64, cfg: &GenerationConfig, noise: &impl NoiseFn<f64, 2>) -> f32 {
|
||||
let x_s = x / cfg.noise_scale;
|
||||
let z_s = z / cfg.noise_scale;
|
||||
|
||||
let mut elevation: f64 = 0.;
|
||||
for i in 0..cfg.layers.len()
|
||||
{
|
||||
let mut first_layer: f64 = 0.;
|
||||
for i in 0..cfg.layers.len() {
|
||||
let value: f64;
|
||||
let layer = &cfg.layers[i];
|
||||
if layer.is_rigid
|
||||
{
|
||||
if layer.is_rigid {
|
||||
value = sample_rigid(x_s, z_s, layer, noise);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
value = sample_simple(x_s, z_s, layer, noise);
|
||||
}
|
||||
elevation += value;
|
||||
if i == 0 {
|
||||
first_layer = value;
|
||||
}
|
||||
if layer.first_layer_mask {
|
||||
elevation += mask(first_layer, value, cfg.sea_level);
|
||||
} else {
|
||||
elevation += value;
|
||||
}
|
||||
}
|
||||
|
||||
if border_size == 0.0
|
||||
{
|
||||
return elevation as f32;
|
||||
}
|
||||
|
||||
let outer = size * Chunk::SIZE as f32;
|
||||
let outer = cfg.size.as_vec2() * Chunk::SIZE as f32;
|
||||
|
||||
let p = Vec2::new(x as f32, z as f32);
|
||||
let d1 = p.x.min(p.y);
|
||||
let od = outer - p;
|
||||
let d2 = od.x.min(od.y);
|
||||
let d = d1.min(d2).min(border_size).remap(0., border_size, 0., 1.);
|
||||
let d = d1
|
||||
.min(d2)
|
||||
.min(cfg.border_size)
|
||||
.remap(0., cfg.border_size, 0., 1.);
|
||||
|
||||
return border_value.lerp(elevation as f32, d);
|
||||
return (elevation as f32) * d;
|
||||
}
|
||||
|
||||
fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64
|
||||
{
|
||||
fn mask(mask: f64, value: f64, sea_level: f64) -> f64 {
|
||||
let m = (mask - sea_level).max(0.);
|
||||
return value * m;
|
||||
}
|
||||
|
||||
fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64 {
|
||||
let mut freq: f64 = cfg.base_roughness;
|
||||
let mut amp: f64 = 1.;
|
||||
let mut value = 0.;
|
||||
|
||||
for _ in 0..cfg.layers
|
||||
{
|
||||
for _ in 0..cfg.layers {
|
||||
let v = noise.get([x * freq, z * freq]);
|
||||
value += (v + 1.) * 0.5 * amp;
|
||||
freq *= cfg.roughness;
|
||||
@@ -283,14 +118,12 @@ fn sample_simple(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64,
|
||||
value -= cfg.min_value;
|
||||
return value * cfg.strength;
|
||||
}
|
||||
fn sample_rigid(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64
|
||||
{
|
||||
fn sample_rigid(x: f64, z: f64, cfg: &GeneratorLayer, noise: &impl NoiseFn<f64, 2>) -> f64 {
|
||||
let mut freq: f64 = cfg.base_roughness;
|
||||
let mut amp: f64 = 1.;
|
||||
let mut value = 0.;
|
||||
let mut weight = 1.;
|
||||
for _ in 0..cfg.layers
|
||||
{
|
||||
for _ in 0..cfg.layers {
|
||||
let mut v = 1. - noise.get([x * freq, z * freq]).abs();
|
||||
v *= v;
|
||||
v *= weight;
|
||||
|
||||
198
engine/world_generation/src/hex_utils.rs
Normal file
198
engine/world_generation/src/hex_utils.rs
Normal file
@@ -0,0 +1,198 @@
|
||||
use crate::prelude::Chunk;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub const OUTER_RADIUS: f32 = 1.;
|
||||
pub const INNER_RADIUS: f32 = OUTER_RADIUS * 0.866025404;
|
||||
|
||||
pub fn offset3d_to_world(offset: Vec3) -> Vec3 {
|
||||
let x = (offset.x + (offset.z * 0.5) - (offset.z / 2.).floor()) * (INNER_RADIUS * 2.);
|
||||
return Vec3::new(x, offset.y, offset.z * OUTER_RADIUS * 1.5);
|
||||
}
|
||||
|
||||
pub fn offset_to_world(offset: IVec2, height: f32) -> Vec3 {
|
||||
let off = offset.as_vec2();
|
||||
let x = (off.x + (off.y * 0.5) - (off.y / 2.).floor()) * (INNER_RADIUS * 2.);
|
||||
return Vec3::new(x, height, off.y * OUTER_RADIUS * 1.5);
|
||||
}
|
||||
|
||||
pub fn offset_to_hex(offset: IVec2) -> IVec3 {
|
||||
let mut v = IVec3 {
|
||||
x: offset.x - (offset.y / 2),
|
||||
y: offset.y,
|
||||
z: 0,
|
||||
};
|
||||
v.z = -v.x - v.y;
|
||||
return v;
|
||||
}
|
||||
|
||||
pub fn snap_to_hex_grid(world_pos: Vec3) -> Vec3 {
|
||||
return offset_to_world(world_to_offset_pos(world_pos), world_pos.y);
|
||||
}
|
||||
|
||||
pub fn world_to_offset_pos(world_pos: Vec3) -> IVec2 {
|
||||
let offset = world_pos.z / (OUTER_RADIUS * 3.);
|
||||
let x = (world_pos.x / (INNER_RADIUS * 2.)) - offset;
|
||||
let z = -world_pos.x - offset;
|
||||
|
||||
let ix = x.round() as i32;
|
||||
let iz = z.round() as i32;
|
||||
let ox = ix + iz / 2;
|
||||
let oz = iz;
|
||||
return IVec2::new(ox, oz);
|
||||
}
|
||||
|
||||
pub fn tile_to_world_distance(dist: i32) -> f32 {
|
||||
return dist as f32 * (2. * INNER_RADIUS);
|
||||
}
|
||||
|
||||
pub fn get_tile_count(radius: i32) -> i32 {
|
||||
return 1 + 3 * (radius + 1) * radius;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct HexCoord {
|
||||
pub hex: IVec3,
|
||||
}
|
||||
|
||||
impl HexCoord {
|
||||
pub const DIRECTIONS: [IVec3; 6] = [
|
||||
IVec3::new(0, 1, -1),
|
||||
IVec3::new(1, 0, -1),
|
||||
IVec3::new(1, -1, 0),
|
||||
IVec3::new(0, -1, 1),
|
||||
IVec3::new(-1, 0, 1),
|
||||
IVec3::new(-1, 1, 0),
|
||||
];
|
||||
|
||||
pub const ZERO: HexCoord = HexCoord { hex: IVec3::ZERO };
|
||||
|
||||
pub fn new(x: i32, z: i32) -> Self {
|
||||
return HexCoord {
|
||||
hex: IVec3::new(x, z, -x - z),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn from_hex(hex: IVec2) -> Self {
|
||||
return HexCoord {
|
||||
hex: IVec3::new(hex.x, hex.y, -hex.x - hex.y),
|
||||
};
|
||||
}
|
||||
pub fn from_grid_pos(x: usize, z: usize) -> Self {
|
||||
return HexCoord::new(x as i32 - (z as i32 / 2), z as i32);
|
||||
}
|
||||
pub fn from_offset(offset_pos: IVec2) -> Self {
|
||||
return HexCoord {
|
||||
hex: offset_to_hex(offset_pos),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_in_bounds(&self, map_height: usize, map_width: usize) -> bool {
|
||||
let off = self.to_offset();
|
||||
if off.x < 0 || off.y < 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
if off.x >= map_width as i32 || off.y >= map_height as i32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn to_chunk_pos(&self) -> IVec2 {
|
||||
let off = self.to_offset();
|
||||
|
||||
return IVec2 {
|
||||
x: (off.x as f32 / Chunk::SIZE as f32).floor() as i32,
|
||||
y: (off.y as f32 / Chunk::SIZE as f32).floor() as i32,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn to_chunk(&self) -> HexCoord {
|
||||
let c_pos = self.to_chunk_pos();
|
||||
let off = self.to_offset();
|
||||
return HexCoord::from_offset(
|
||||
(
|
||||
off.x - (c_pos.x * Chunk::SIZE as i32),
|
||||
off.y - (c_pos.y * Chunk::SIZE as i32),
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn to_world(&self, height: f32) -> Vec3 {
|
||||
return offset_to_world(self.to_offset(), height);
|
||||
}
|
||||
|
||||
pub fn to_offset(&self) -> IVec2 {
|
||||
return IVec2::new(self.hex.x + (self.hex.y / 2), self.hex.y);
|
||||
}
|
||||
|
||||
pub fn to_index(&self, width: usize) -> usize {
|
||||
return ((self.hex.x + self.hex.y * width as i32) + (self.hex.y / 2)) as usize;
|
||||
}
|
||||
pub fn to_chunk_index(&self, width: usize) -> usize {
|
||||
let pos = self.to_chunk_pos();
|
||||
return (pos.x + pos.y * width as i32) as usize;
|
||||
}
|
||||
|
||||
pub fn to_chunk_local_index(&self) -> usize {
|
||||
return self.to_chunk().to_index(Chunk::SIZE);
|
||||
}
|
||||
|
||||
pub fn distance(&self, other: &HexCoord) -> i32 {
|
||||
return (self.hex.x - other.hex.x).abs()
|
||||
+ (self.hex.y - other.hex.y).abs()
|
||||
+ (self.hex.z - other.hex.z).abs();
|
||||
}
|
||||
|
||||
pub fn rotate_around(&self, center: &HexCoord, angle: i32) -> HexCoord {
|
||||
if self == center || angle == 0 {
|
||||
return self.clone();
|
||||
}
|
||||
|
||||
let mut a = angle % 6;
|
||||
let mut pc = self.hex - center.hex;
|
||||
|
||||
if a > 0 {
|
||||
for _ in 0..a {
|
||||
pc = Self::slide_right(pc);
|
||||
}
|
||||
} else {
|
||||
a = a.abs();
|
||||
for _ in 0..a {
|
||||
pc = Self::slide_left(pc);
|
||||
}
|
||||
}
|
||||
return HexCoord::from_hex(pc.xy() + center.hex.xy());
|
||||
}
|
||||
|
||||
fn slide_left(hex: IVec3) -> IVec3 {
|
||||
return (hex * -1).yzx();
|
||||
}
|
||||
|
||||
fn slide_right(hex: IVec3) -> IVec3 {
|
||||
return (hex * -1).zxy();
|
||||
}
|
||||
|
||||
pub fn scale(&self, dir: i32, radius: usize) -> HexCoord {
|
||||
let s = Self::DIRECTIONS[(dir % 6) as usize] * radius as i32;
|
||||
return Self::from_hex(self.hex.xy() + s.xy());
|
||||
}
|
||||
|
||||
pub fn get_neighbor(&self, dir: usize) -> HexCoord {
|
||||
let d = Self::DIRECTIONS[dir % 6];
|
||||
return Self::from_hex(self.hex.xy() + d.xy());
|
||||
}
|
||||
|
||||
pub fn get_neighbors(&self) -> [HexCoord; 6] {
|
||||
return [
|
||||
self.get_neighbor(0),
|
||||
self.get_neighbor(1),
|
||||
self.get_neighbor(2),
|
||||
self.get_neighbor(3),
|
||||
self.get_neighbor(4),
|
||||
self.get_neighbor(5),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,100 @@
|
||||
pub mod biome_asset;
|
||||
pub mod biome_painter;
|
||||
pub mod consts;
|
||||
pub mod generators;
|
||||
pub mod heightmap;
|
||||
pub mod map;
|
||||
pub mod prelude;
|
||||
pub mod states;
|
||||
pub mod hex_utils;
|
||||
pub mod mesh_generator;
|
||||
pub mod tile_manager;
|
||||
pub mod tile_mapper;
|
||||
|
||||
pub mod prelude {
|
||||
use crate::hex_utils::HexCoord;
|
||||
use bevy::math::{IVec2, UVec2};
|
||||
use bevy::prelude::Resource;
|
||||
use bevy::render::mesh::MeshVertexAttribute;
|
||||
use bevy::render::render_resource::VertexFormat;
|
||||
|
||||
pub struct GenerationConfig {
|
||||
pub noise_scale: f64,
|
||||
pub sea_level: f64,
|
||||
pub border_size: f32,
|
||||
pub size: UVec2,
|
||||
pub layers: Vec<GeneratorLayer>,
|
||||
}
|
||||
|
||||
impl GenerationConfig {
|
||||
pub fn get_total_width(&self) -> usize {
|
||||
return self.size.x as usize * Chunk::SIZE;
|
||||
}
|
||||
pub fn get_total_height(&self) -> usize {
|
||||
return self.size.y as usize * Chunk::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GeneratorLayer {
|
||||
pub strength: f64,
|
||||
pub min_value: f64,
|
||||
pub base_roughness: f64,
|
||||
pub roughness: f64,
|
||||
pub persistence: f64,
|
||||
pub is_rigid: bool,
|
||||
pub weight: f64,
|
||||
pub weight_multi: f64,
|
||||
pub layers: usize,
|
||||
pub first_layer_mask: bool,
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub heights: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub moisture: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub temperature: [f32; Chunk::SIZE * Chunk::SIZE],
|
||||
pub chunk_offset: IVec2,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub const SIZE: usize = 64;
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct Map {
|
||||
pub chunks: Vec<Chunk>,
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
}
|
||||
|
||||
impl Map {
|
||||
pub fn get_neighbors(&self, pos: &HexCoord) -> [Option<f32>; 6] {
|
||||
let mut results: [Option<f32>; 6] = [None; 6];
|
||||
let w = self.width * Chunk::SIZE;
|
||||
let h = self.height * Chunk::SIZE;
|
||||
let n_tiles = pos.get_neighbors();
|
||||
for i in 0..6 {
|
||||
let n_tile = n_tiles[i];
|
||||
if !n_tile.is_in_bounds(h, w) {
|
||||
continue;
|
||||
}
|
||||
let c_idx = n_tile.to_chunk_index(self.width);
|
||||
let chunk = &self.chunks[c_idx];
|
||||
let local = n_tile.to_chunk_local_index();
|
||||
results[i] = Some(chunk.heights[local]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
pub fn get_height(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.heights[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn get_moisture(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.moisture[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn get_tempurature(&self, pos: &HexCoord) -> f32 {
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.temperature[pos.to_chunk_local_index()];
|
||||
}
|
||||
}
|
||||
|
||||
pub const ATTRIBUTE_TEXTURE_INDEX: MeshVertexAttribute =
|
||||
MeshVertexAttribute::new("TextureIndex", 988540917, VertexFormat::Uint32);
|
||||
}
|
||||
|
||||
@@ -1,275 +0,0 @@
|
||||
use bevy::{
|
||||
math::{UVec2, Vec3},
|
||||
prelude::Resource,
|
||||
};
|
||||
use noise::NoiseFn;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use hex::prelude::*;
|
||||
|
||||
#[derive(Clone, Resource)]
|
||||
pub struct BiomeMap
|
||||
{
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
pub size: UVec2,
|
||||
pub biome_count: usize,
|
||||
pub chunks: Vec<BiomeChunk>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
pub struct BiomeData
|
||||
{
|
||||
pub moisture: f32,
|
||||
pub temperature: f32,
|
||||
pub continentality: f32,
|
||||
}
|
||||
|
||||
impl Into<Vec3> for &BiomeData
|
||||
{
|
||||
fn into(self) -> Vec3
|
||||
{
|
||||
return Vec3::new(self.moisture, self.temperature, self.continentality);
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec3> for BiomeData
|
||||
{
|
||||
fn into(self) -> Vec3
|
||||
{
|
||||
return Vec3::new(self.moisture, self.temperature, self.continentality);
|
||||
}
|
||||
}
|
||||
|
||||
impl BiomeMap
|
||||
{
|
||||
pub fn new(size: UVec2, biome_count: usize) -> Self
|
||||
{
|
||||
let len = size.x as usize * size.y as usize * Chunk::AREA;
|
||||
return BiomeMap {
|
||||
size,
|
||||
height: size.y as usize * Chunk::SIZE,
|
||||
width: size.x as usize * Chunk::SIZE,
|
||||
biome_count,
|
||||
chunks: Vec::with_capacity(len),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn blend(&mut self, count: usize)
|
||||
{
|
||||
assert!(count != 0, "Count cannot be 0");
|
||||
for _ in 0..count
|
||||
{
|
||||
self.blend_once();
|
||||
}
|
||||
}
|
||||
|
||||
fn blend_once(&mut self)
|
||||
{
|
||||
let c: Vec<BiomeChunk> = (0..self.chunks.len())
|
||||
.into_par_iter()
|
||||
.map(|i| &self.chunks[i])
|
||||
.map(|chunk| {
|
||||
let tiles: Vec<_> = (0..Chunk::SIZE)
|
||||
.into_par_iter()
|
||||
.map(|y| {
|
||||
let mut new_tiles = Vec::with_capacity(self.width);
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let tx = x as u32 + chunk.offset.x * Chunk::SIZE as u32;
|
||||
let ty = y as u32 + chunk.offset.y * Chunk::SIZE as u32;
|
||||
let kernel = self.get_kernel(tx as i32, ty as i32);
|
||||
|
||||
let r = kernel
|
||||
.iter()
|
||||
.filter_map(|f| *f)
|
||||
.fold(vec![0.; self.biome_count], |a, b| {
|
||||
return a.iter().zip(b).map(|v| v.0 + v.1).collect();
|
||||
});
|
||||
|
||||
let sum: f32 = r.iter().sum();
|
||||
if sum == 0.
|
||||
{
|
||||
new_tiles.push(vec![0.; self.biome_count]);
|
||||
continue;
|
||||
}
|
||||
new_tiles.push(r.iter().map(|f| f / sum).collect());
|
||||
}
|
||||
return new_tiles;
|
||||
})
|
||||
.flatten()
|
||||
.collect();
|
||||
return BiomeChunk {
|
||||
offset: chunk.offset,
|
||||
tiles,
|
||||
data: chunk.data,
|
||||
};
|
||||
})
|
||||
.collect();
|
||||
self.chunks = c;
|
||||
}
|
||||
|
||||
fn get_kernel(&self, x: i32, y: i32) -> [Option<&Vec<f32>>; 9]
|
||||
{
|
||||
return [
|
||||
self.get_biome(x - 1, y - 1),
|
||||
self.get_biome(x, y - 1),
|
||||
self.get_biome(x + 1, y - 1),
|
||||
self.get_biome(x - 1, y),
|
||||
self.get_biome(x, y),
|
||||
self.get_biome(x + 1, y),
|
||||
self.get_biome(x - 1, y + 1),
|
||||
self.get_biome(x, y + 1),
|
||||
self.get_biome(x + 1, y + 1),
|
||||
];
|
||||
}
|
||||
|
||||
pub fn get_biome(&self, x: i32, y: i32) -> Option<&Vec<f32>>
|
||||
{
|
||||
if x < 0 || y < 0
|
||||
{
|
||||
return None;
|
||||
}
|
||||
if x >= self.width as i32 || y >= self.height as i32
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let cx = (x as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
let cy = (y as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
|
||||
let chunk = &self.chunks[cx + cy * self.size.x as usize];
|
||||
return Some(chunk.get_biome(x as usize - cx * Chunk::SIZE, y as usize - cy * Chunk::SIZE));
|
||||
}
|
||||
|
||||
pub fn get_biome_id(&self, x: usize, y: usize) -> usize
|
||||
{
|
||||
let cx = (x as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
let cy = (y as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
|
||||
let chunk = &self.chunks[cx + cy * self.size.x as usize];
|
||||
|
||||
return chunk.get_biome_id(x - (cx * Chunk::SIZE), y - (cy * Chunk::SIZE));
|
||||
}
|
||||
|
||||
pub fn get_biome_id_dithered(&self, x: usize, y: usize, noise: &impl NoiseFn<f64, 2>, scale: f64) -> usize
|
||||
{
|
||||
let cx = (x as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
let cy = (y as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
|
||||
let chunk = &self.chunks[cx + cy * self.size.x as usize];
|
||||
|
||||
return chunk.get_biome_id_dithered(x - (cx * Chunk::SIZE), y - (cy * Chunk::SIZE), noise, scale);
|
||||
}
|
||||
|
||||
pub fn get_biome_data(&self, x: usize, y: usize) -> &BiomeData
|
||||
{
|
||||
let cx = (x as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
let cy = (y as f32 / Chunk::SIZE as f32).floor() as usize;
|
||||
|
||||
let chunk = &self.chunks[cx + cy * self.size.x as usize];
|
||||
|
||||
return chunk.get_biome_data(x - (cx * Chunk::SIZE), y - (cy * Chunk::SIZE));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BiomeChunk
|
||||
{
|
||||
pub tiles: Vec<Vec<f32>>,
|
||||
pub offset: UVec2,
|
||||
pub data: [BiomeData; Chunk::AREA],
|
||||
}
|
||||
|
||||
impl BiomeChunk
|
||||
{
|
||||
pub fn get_biome(&self, x: usize, y: usize) -> &Vec<f32>
|
||||
{
|
||||
return &self.tiles[x + y * Chunk::SIZE];
|
||||
}
|
||||
|
||||
pub fn get_biome_data(&self, x: usize, y: usize) -> &BiomeData
|
||||
{
|
||||
return &self.data[x + y * Chunk::SIZE];
|
||||
}
|
||||
|
||||
pub fn get_biome_id(&self, x: usize, y: usize) -> usize
|
||||
{
|
||||
let b = self.get_biome(x, y);
|
||||
let mut max = 0.;
|
||||
let mut idx = 0;
|
||||
for i in 0..b.len()
|
||||
{
|
||||
let blend = b[i];
|
||||
if blend > max
|
||||
{
|
||||
max = blend;
|
||||
idx = i;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
pub fn get_biome_id_dithered(&self, x: usize, y: usize, noise: &impl NoiseFn<f64, 2>, scale: f64) -> usize
|
||||
{
|
||||
let mut cur_id = self.get_biome_id(x, y);
|
||||
let b = self.get_biome(x, y);
|
||||
let n = (noise.get([x as f64 / scale, y as f64 / scale]) as f32 - 0.5) / 2.0;
|
||||
let mut max = b[cur_id] + n;
|
||||
for i in 0..b.len()
|
||||
{
|
||||
let blend = b[i];
|
||||
if blend == 0.
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if blend > max
|
||||
{
|
||||
max = blend + n;
|
||||
cur_id = i;
|
||||
}
|
||||
}
|
||||
|
||||
return cur_id;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn biome_blend()
|
||||
{
|
||||
let mut biome = BiomeMap::new(UVec2::splat(4), 8);
|
||||
let w = biome.size.x as usize;
|
||||
let h = biome.size.y as usize;
|
||||
|
||||
for y in 0..h
|
||||
{
|
||||
for x in 0..w
|
||||
{
|
||||
let mut b = vec![0.; biome.biome_count];
|
||||
let idx = (x + y) % biome.biome_count;
|
||||
b[idx] = 1.;
|
||||
biome.chunks.push(generate_chunk(x, y, b));
|
||||
}
|
||||
}
|
||||
|
||||
biome.blend(8);
|
||||
assert!(biome.chunks.iter().all(|f| f.tiles.len() == Chunk::AREA), "Data Lost");
|
||||
}
|
||||
|
||||
fn generate_chunk(x: usize, y: usize, biome: Vec<f32>) -> BiomeChunk
|
||||
{
|
||||
let chunk = BiomeChunk {
|
||||
offset: UVec2::new(x as u32, y as u32),
|
||||
data: [BiomeData::default(); Chunk::AREA],
|
||||
tiles: (0..Chunk::AREA).into_iter().map(|_| biome.clone()).collect(),
|
||||
};
|
||||
|
||||
return chunk;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_inspector_egui::InspectorOptions;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use hex::prelude::*;
|
||||
|
||||
#[derive(Resource, Reflect, Default, Clone)]
|
||||
#[reflect(Resource)]
|
||||
pub struct GenerationConfig
|
||||
{
|
||||
pub sea_level: f64,
|
||||
pub border_size: f32,
|
||||
pub biome_blend: usize,
|
||||
pub biome_dither: f64,
|
||||
pub moisture_noise: NoiseConfig,
|
||||
pub temperature_noise: NoiseConfig,
|
||||
pub continent_noise: NoiseConfig,
|
||||
pub size: UVec2,
|
||||
}
|
||||
|
||||
impl GenerationConfig
|
||||
{
|
||||
pub fn get_total_width(&self) -> usize
|
||||
{
|
||||
return self.size.x as usize * Chunk::SIZE;
|
||||
}
|
||||
pub fn get_total_height(&self) -> usize
|
||||
{
|
||||
return self.size.y as usize * Chunk::SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Reflect, Clone, Debug)]
|
||||
pub struct NoiseConfig
|
||||
{
|
||||
pub scale: f64,
|
||||
pub layers: Vec<GeneratorLayer>,
|
||||
}
|
||||
|
||||
#[derive(Reflect, InspectorOptions, Serialize, Deserialize, Debug, Clone, Default)]
|
||||
pub struct GeneratorLayer
|
||||
{
|
||||
pub strength: f64,
|
||||
pub min_value: f64,
|
||||
pub base_roughness: f64,
|
||||
pub roughness: f64,
|
||||
pub persistence: f64,
|
||||
pub is_rigid: bool,
|
||||
pub weight: f64,
|
||||
pub weight_multi: f64,
|
||||
pub layers: usize,
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
use super::mesh_chunk::MeshChunkData;
|
||||
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct Map
|
||||
{
|
||||
pub chunks: Vec<Chunk>,
|
||||
pub height: usize,
|
||||
pub width: usize,
|
||||
pub sealevel: f32,
|
||||
pub min_level: f32,
|
||||
pub max_level: f32,
|
||||
pub biome_count: usize,
|
||||
}
|
||||
|
||||
impl Map
|
||||
{
|
||||
pub fn get_tile_count(&self) -> usize
|
||||
{
|
||||
return self.get_tile_width() * self.get_tile_height();
|
||||
}
|
||||
|
||||
pub fn get_tile_width(&self) -> usize
|
||||
{
|
||||
return self.width * Chunk::SIZE;
|
||||
}
|
||||
|
||||
pub fn get_tile_height(&self) -> usize
|
||||
{
|
||||
return self.height * Chunk::SIZE;
|
||||
}
|
||||
|
||||
pub fn get_chunk_mesh_data(&self, chunk_index: usize) -> MeshChunkData
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Chunk Mesh Data").entered();
|
||||
let chunk = &self.chunks[chunk_index];
|
||||
|
||||
return MeshChunkData {
|
||||
min_height: self.min_level,
|
||||
sealevel: self.sealevel,
|
||||
heights: chunk.heights.clone(),
|
||||
textures: chunk.textures.clone(),
|
||||
distance_to_land: self.get_distance_from_land(chunk.chunk_offset, 4),
|
||||
};
|
||||
}
|
||||
|
||||
fn get_distance_from_land(&self, chunk_offset: IVec2, range: usize) -> [f32; Chunk::AREA]
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Chunk Land Dist Data").entered();
|
||||
let mut dists = [0.0; Chunk::AREA];
|
||||
let cx = chunk_offset.x as usize * Chunk::SIZE;
|
||||
let cz = chunk_offset.y as usize * Chunk::SIZE;
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let coord = HexCoord::from_offset_pos(x + cx, z + cz);
|
||||
let index = coord.to_chunk_local_index();
|
||||
|
||||
if !self.is_in_bounds(&coord)
|
||||
{
|
||||
warn!("Coord is not in bounds!?");
|
||||
}
|
||||
|
||||
//Current tile is land tile
|
||||
if self.sample_height(&coord) > self.sealevel
|
||||
{
|
||||
dists[index] = 0.0;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Find closest land tile
|
||||
if let Some(d) = self.hex_select_first(&coord, range, false, |_t, h, r| {
|
||||
if h > self.sealevel
|
||||
{
|
||||
return Some(r as f32);
|
||||
}
|
||||
return None;
|
||||
})
|
||||
{
|
||||
dists[index] = d;
|
||||
}
|
||||
else
|
||||
{
|
||||
dists[index] = range as f32;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dists;
|
||||
}
|
||||
|
||||
pub fn get_neighbors(&self, pos: &HexCoord) -> [Option<f32>; 6]
|
||||
{
|
||||
let mut results: [Option<f32>; 6] = [None; 6];
|
||||
let w = self.width * Chunk::SIZE;
|
||||
let h = self.height * Chunk::SIZE;
|
||||
let n_tiles = pos.get_neighbors();
|
||||
for i in 0..6
|
||||
{
|
||||
let n_tile = n_tiles[i];
|
||||
if !n_tile.is_in_bounds(h, w)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let c_idx = n_tile.to_chunk_index(self.width);
|
||||
let chunk = &self.chunks[c_idx];
|
||||
let local = n_tile.to_chunk_local_index();
|
||||
results[i] = Some(chunk.heights[local]);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
pub fn sample_height(&self, pos: &HexCoord) -> f32
|
||||
{
|
||||
assert!(
|
||||
self.is_in_bounds(pos),
|
||||
"The provided coordinate is not within the map bounds"
|
||||
);
|
||||
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.heights[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn sample_height_mut(&mut self, pos: &HexCoord) -> &mut f32
|
||||
{
|
||||
assert!(
|
||||
self.is_in_bounds(pos),
|
||||
"The provided coordinate is not within the map bounds"
|
||||
);
|
||||
|
||||
let chunk = &mut self.chunks[pos.to_chunk_index(self.width)];
|
||||
return &mut chunk.heights[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn is_in_bounds(&self, pos: &HexCoord) -> bool
|
||||
{
|
||||
return pos.is_in_bounds(self.height * Chunk::SIZE, self.width * Chunk::SIZE);
|
||||
}
|
||||
|
||||
pub fn get_biome_id(&self, pos: &HexCoord) -> usize
|
||||
{
|
||||
assert!(
|
||||
self.is_in_bounds(pos),
|
||||
"The provided coordinate is not within the map bounds"
|
||||
);
|
||||
|
||||
let chunk = &self.chunks[pos.to_chunk_index(self.width)];
|
||||
return chunk.biome_id[pos.to_chunk_local_index()];
|
||||
}
|
||||
|
||||
pub fn get_center(&self) -> Vec3
|
||||
{
|
||||
let w = self.get_world_width();
|
||||
let h = self.get_world_height();
|
||||
return Vec3::new(w / 2., self.sealevel, h / 2.);
|
||||
}
|
||||
|
||||
pub fn get_center_with_height(&self) -> Vec3
|
||||
{
|
||||
let w = self.get_world_width();
|
||||
let h = self.get_world_height();
|
||||
let mut pos = Vec3::new(w / 2., self.sealevel, h / 2.);
|
||||
pos.y = self.sample_height(&HexCoord::from_world_pos(pos));
|
||||
return pos;
|
||||
}
|
||||
|
||||
pub fn get_world_width(&self) -> f32
|
||||
{
|
||||
return (self.width * Chunk::SIZE) as f32 * SHORT_DIAGONAL;
|
||||
}
|
||||
pub fn get_world_height(&self) -> f32
|
||||
{
|
||||
return (self.height * Chunk::SIZE) as f32 * 1.5;
|
||||
}
|
||||
|
||||
pub fn get_world_size(&self) -> Vec2
|
||||
{
|
||||
return Vec2::new(self.get_world_width(), self.get_world_height());
|
||||
}
|
||||
|
||||
pub fn set_height(&mut self, pos: &HexCoord, height: f32)
|
||||
{
|
||||
self.chunks[pos.to_chunk_index(self.width)].heights[pos.to_chunk_local_index()] = height;
|
||||
}
|
||||
|
||||
pub fn create_crater(&mut self, pos: &HexCoord, radius: usize, depth: f32) -> Vec<(HexCoord, f32)>
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
|
||||
let tiles = self.hex_select_mut(pos, radius, true, |p, h, r| {
|
||||
let d = (r as f32) / (radius as f32);
|
||||
let cur = *h;
|
||||
let h2 = cur - depth;
|
||||
*h = h2.lerp(cur, d * d).max(0.);
|
||||
|
||||
return (*p, *h);
|
||||
});
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
pub fn hex_select<OP, Ret>(&self, center: &HexCoord, radius: usize, include_center: bool, op: OP) -> Vec<Ret>
|
||||
where
|
||||
OP: (Fn(&HexCoord, f32, usize) -> Ret) + Sync + Send,
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
|
||||
let mut result = if include_center
|
||||
{
|
||||
Vec::with_capacity(get_tile_count_in_range(radius) + 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec::with_capacity(get_tile_count_in_range(radius))
|
||||
};
|
||||
if include_center
|
||||
{
|
||||
let h = self.sample_height(¢er);
|
||||
result.push((op)(center, h, 0));
|
||||
}
|
||||
|
||||
for k in 0..(radius + 1)
|
||||
{
|
||||
let mut p = center.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
if self.is_in_bounds(&p)
|
||||
{
|
||||
let h = self.sample_height(&p);
|
||||
result.push((op)(&p, h, k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn hex_select_first<OP, Ret>(
|
||||
&self,
|
||||
center: &HexCoord,
|
||||
radius: usize,
|
||||
include_center: bool,
|
||||
op: OP,
|
||||
) -> Option<Ret>
|
||||
where
|
||||
OP: (Fn(&HexCoord, f32, usize) -> Option<Ret>) + Sync + Send,
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
|
||||
if include_center
|
||||
{
|
||||
let h = self.sample_height(¢er);
|
||||
let r = (op)(center, h, 0);
|
||||
if r.is_some()
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
for k in 0..(radius + 1)
|
||||
{
|
||||
let mut p = center.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
if self.is_in_bounds(&p)
|
||||
{
|
||||
let h = self.sample_height(&p);
|
||||
let r = (op)(&p, h, k);
|
||||
if r.is_some()
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn ring_select_first<OP, Ret>(
|
||||
&self,
|
||||
center: &HexCoord,
|
||||
start_radius: usize,
|
||||
end_radius: usize,
|
||||
op: OP,
|
||||
) -> Option<Ret>
|
||||
where
|
||||
OP: (Fn(&HexCoord, f32, usize) -> Option<Ret>) + Sync + Send,
|
||||
{
|
||||
assert!(start_radius != 0, "Start radius cannot be zero");
|
||||
assert!(
|
||||
start_radius > end_radius,
|
||||
"Start radius cannot be lower than end radius"
|
||||
);
|
||||
|
||||
for k in start_radius..(end_radius + 1)
|
||||
{
|
||||
let mut p = center.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
if self.is_in_bounds(&p)
|
||||
{
|
||||
let h = self.sample_height(&p);
|
||||
let r = (op)(&p, h, k);
|
||||
if r.is_some()
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn hex_select_mut<OP, Ret>(
|
||||
&mut self,
|
||||
center: &HexCoord,
|
||||
radius: usize,
|
||||
include_center: bool,
|
||||
op: OP,
|
||||
) -> Vec<Ret>
|
||||
where
|
||||
OP: (Fn(&HexCoord, &mut f32, usize) -> Ret) + Sync + Send,
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
|
||||
let mut result = if include_center
|
||||
{
|
||||
Vec::with_capacity(get_tile_count_in_range(radius) + 1)
|
||||
}
|
||||
else
|
||||
{
|
||||
Vec::with_capacity(get_tile_count_in_range(radius))
|
||||
};
|
||||
if include_center
|
||||
{
|
||||
let h = self.sample_height_mut(¢er);
|
||||
result.push((op)(center, h, 0));
|
||||
}
|
||||
|
||||
for k in 0..(radius + 1)
|
||||
{
|
||||
let mut p = center.scale(4, k);
|
||||
for i in 0..6
|
||||
{
|
||||
for _j in 0..k
|
||||
{
|
||||
p = p.get_neighbor(i);
|
||||
if self.is_in_bounds(&p)
|
||||
{
|
||||
let h = self.sample_height_mut(&p);
|
||||
result.push((op)(&p, h, k));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
use std::ops::Add;
|
||||
|
||||
use bevy::{math::VectorSpace, prelude::*};
|
||||
use image::ImageBuffer;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use hex::prelude::*;
|
||||
|
||||
use super::{biome_map::BiomeMap, map::Map};
|
||||
|
||||
pub fn render_image(
|
||||
size: UVec2,
|
||||
data: &Vec<f32>,
|
||||
color1: LinearRgba,
|
||||
color2: LinearRgba,
|
||||
) -> ImageBuffer<image::Rgba<u8>, Vec<u8>>
|
||||
{
|
||||
let mut image = ImageBuffer::new(size.x * Chunk::SIZE as u32, size.y * Chunk::SIZE as u32);
|
||||
update_image(size, data, color1, color2, &mut image);
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn update_image(
|
||||
size: UVec2,
|
||||
data: &Vec<f32>,
|
||||
color1: LinearRgba,
|
||||
color2: LinearRgba,
|
||||
image: &mut ImageBuffer<image::Rgba<u8>, Vec<u8>>,
|
||||
)
|
||||
{
|
||||
let min = *data.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or(&0.0);
|
||||
let max = *data.iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap_or(&1.0);
|
||||
|
||||
let w = size.x * Chunk::SIZE as u32;
|
||||
|
||||
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
|
||||
let idx = (y * w + x) as usize;
|
||||
let v = data[idx];
|
||||
let t = v.remap(min, max, 0.0, 1.0);
|
||||
let col = LinearRgba::lerp(color1, color2, t);
|
||||
*pixel = to_pixel(&col);
|
||||
});
|
||||
}
|
||||
|
||||
fn to_pixel(col: &LinearRgba) -> image::Rgba<u8>
|
||||
{
|
||||
return image::Rgba([
|
||||
(col.red * 255.0) as u8,
|
||||
(col.green * 255.0) as u8,
|
||||
(col.blue * 255.0) as u8,
|
||||
255,
|
||||
]);
|
||||
}
|
||||
pub fn render_map(map: &Map, smooth: f32) -> ImageBuffer<image::Rgba<u8>, Vec<u8>>
|
||||
{
|
||||
let mut image = ImageBuffer::new(
|
||||
map.width as u32 * Chunk::SIZE as u32,
|
||||
map.height as u32 * Chunk::SIZE as u32,
|
||||
);
|
||||
update_map(map, smooth, &mut image);
|
||||
return image;
|
||||
}
|
||||
pub fn update_map(map: &Map, smooth: f32, image: &mut ImageBuffer<image::Rgba<u8>, Vec<u8>>)
|
||||
{
|
||||
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
|
||||
let coord = HexCoord::from_offset_pos(x as usize, y as usize);
|
||||
let right = coord.get_neighbor(1);
|
||||
let height = map.sample_height(&coord);
|
||||
|
||||
let mut color = Hsla::hsl(138.0, 1.0, 0.4);
|
||||
if height < map.sealevel
|
||||
{
|
||||
color.hue = 217.0;
|
||||
}
|
||||
|
||||
if map.is_in_bounds(&right)
|
||||
{
|
||||
let h2 = map.sample_height(&right);
|
||||
color = get_height_color_blend(color, height, h2, smooth);
|
||||
}
|
||||
|
||||
*pixel = to_pixel(&color.into());
|
||||
});
|
||||
}
|
||||
|
||||
fn get_height_color_blend(base_color: Hsla, height: f32, height2: f32, smooth: f32) -> Hsla
|
||||
{
|
||||
let mut color = base_color;
|
||||
let mut d = height2 - height;
|
||||
if smooth == 0.0 || d.abs() > smooth
|
||||
{
|
||||
if d > 0.0
|
||||
{
|
||||
color.lightness += 0.1;
|
||||
}
|
||||
else if d < 0.0
|
||||
{
|
||||
color.lightness -= 0.1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if d.abs() <= smooth
|
||||
{
|
||||
d /= smooth;
|
||||
if d > 0.0
|
||||
{
|
||||
let c2: LinearRgba = color.with_lightness(color.lightness + 0.1).into();
|
||||
color = LinearRgba::lerp(color.into(), c2, d).into();
|
||||
}
|
||||
else
|
||||
{
|
||||
let c2: LinearRgba = color.with_lightness(color.lightness - 0.1).into();
|
||||
color = LinearRgba::lerp(color.into(), c2, d.abs()).into();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
pub fn render_biome_noise_map(map: &BiomeMap, multi: Vec3) -> ImageBuffer<image::Rgba<u8>, Vec<u8>>
|
||||
{
|
||||
let mut image = ImageBuffer::new(map.width as u32, map.height as u32);
|
||||
update_biome_noise_map(map, multi, &mut image);
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn update_biome_noise_map(map: &BiomeMap, multi: Vec3, image: &mut ImageBuffer<image::Rgba<u8>, Vec<u8>>)
|
||||
{
|
||||
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
|
||||
let tile = map.get_biome_data(x as usize, y as usize);
|
||||
|
||||
let color = LinearRgba::rgb(
|
||||
(tile.temperature / 100.0) * multi.x,
|
||||
(tile.continentality / 100.0) * multi.y,
|
||||
(tile.moisture / 100.0) * multi.z,
|
||||
);
|
||||
*pixel = to_pixel(&color);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render_biome_map(map: &Map, biome_map: &BiomeMap) -> ImageBuffer<image::Rgba<u8>, Vec<u8>>
|
||||
{
|
||||
let mut image = ImageBuffer::new(
|
||||
map.width as u32 * Chunk::SIZE as u32,
|
||||
map.height as u32 * Chunk::SIZE as u32,
|
||||
);
|
||||
update_biome_map(map, biome_map, &mut image);
|
||||
return image;
|
||||
}
|
||||
|
||||
pub fn update_biome_map(map: &Map, biome_map: &BiomeMap, image: &mut ImageBuffer<image::Rgba<u8>, Vec<u8>>)
|
||||
{
|
||||
let map_biome_count = map.biome_count as f32;
|
||||
image.par_enumerate_pixels_mut().for_each(|(x, y, pixel)| {
|
||||
let coord = HexCoord::from_offset_pos(x as usize, y as usize);
|
||||
let biome_blend = biome_map.get_biome(x as i32, y as i32).unwrap();
|
||||
let right = coord.get_neighbor(1);
|
||||
let mut color = Oklaba::BLACK;
|
||||
for i in 0..biome_blend.len()
|
||||
{
|
||||
let mut c: Oklaba = Hsla::hsl((i as f32 / map_biome_count) * 360.0, 0.8, 0.7).into();
|
||||
c *= biome_blend[i];
|
||||
color = Oklaba::add(c, color.into()).into();
|
||||
}
|
||||
if map.is_in_bounds(&right)
|
||||
{
|
||||
let h1 = map.sample_height(&coord);
|
||||
let h2 = map.sample_height(&right);
|
||||
color = get_height_color_blend(color.into(), h1, h2, 0.5).into();
|
||||
}
|
||||
|
||||
*pixel = to_pixel(&color.into());
|
||||
});
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use bevy::math::IVec2;
|
||||
|
||||
use hex::prelude::*;
|
||||
|
||||
pub struct MeshChunkData
|
||||
{
|
||||
pub heights: [f32; Chunk::AREA],
|
||||
pub textures: [[u32; 2]; Chunk::AREA],
|
||||
pub min_height: f32,
|
||||
pub sealevel: f32,
|
||||
pub distance_to_land: [f32; Chunk::AREA],
|
||||
}
|
||||
|
||||
impl MeshChunkData
|
||||
{
|
||||
pub fn get_neighbors(&self, coord: &HexCoord) -> [f32; 6]
|
||||
{
|
||||
let mut data = [self.min_height; 6];
|
||||
let n_tiles = coord.get_neighbors();
|
||||
for i in 0..6
|
||||
{
|
||||
let n = n_tiles[i];
|
||||
if !n.is_in_bounds(Chunk::SIZE, Chunk::SIZE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
data[i] = self.heights[n.to_index(Chunk::SIZE)];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
pub fn get_neighbors_with_water_info(&self, coord: &HexCoord) -> ([(f32, Option<f32>); 6], bool)
|
||||
{
|
||||
let mut has_land = false;
|
||||
let mut data = [(self.min_height, None); 6];
|
||||
let n_tiles = coord.get_neighbors();
|
||||
for i in 0..6
|
||||
{
|
||||
let n = n_tiles[i];
|
||||
if !n.is_in_bounds(Chunk::SIZE, Chunk::SIZE)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let idx = n.to_index(Chunk::SIZE);
|
||||
data[i] = (self.heights[idx], Some(self.distance_to_land[idx]));
|
||||
if data[i].0 > self.sealevel
|
||||
{
|
||||
has_land = true;
|
||||
}
|
||||
}
|
||||
return (data, has_land);
|
||||
}
|
||||
|
||||
pub fn caluclate_water_distances(data: &mut Vec<MeshChunkData>, height: usize, width: usize, _range: usize)
|
||||
{
|
||||
let mut open: VecDeque<(HexCoord, f32, usize)> = VecDeque::new();
|
||||
// let mut closed: Vec<(HexCoord, f32)> = Vec::new();
|
||||
for z in 0..height
|
||||
{
|
||||
for x in 0..width
|
||||
{
|
||||
let chunk = &mut data[z * height + x];
|
||||
chunk.prepare_chunk_open(x * Chunk::SIZE, z * Chunk::SIZE, &mut open);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_chunk_open(&mut self, offset_x: usize, offset_z: usize, open: &mut VecDeque<(HexCoord, f32, usize)>)
|
||||
{
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let coord = HexCoord::from_offset_pos(x + offset_x, z + offset_z);
|
||||
let idx = coord.to_chunk_local_index();
|
||||
let h = self.heights[idx];
|
||||
self.distance_to_land[idx] = if h > self.sealevel { 0.0 } else { 4.0 };
|
||||
if h > self.sealevel
|
||||
{
|
||||
open.push_back((coord, h, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused)]
|
||||
fn fill_chunk_borders(
|
||||
&mut self,
|
||||
chunks: &Vec<MeshChunkData>,
|
||||
offset: IVec2,
|
||||
open: &mut VecDeque<(HexCoord, f32, usize)>,
|
||||
closed: &mut Vec<(HexCoord, f32)>,
|
||||
)
|
||||
{
|
||||
self.prepare_chunk_open(offset.x as usize * Chunk::SIZE, offset.y as usize * Chunk::SIZE, open);
|
||||
todo!("Fill closed list with bordering tiles")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod biome_map;
|
||||
pub mod config;
|
||||
pub mod map;
|
||||
pub mod map_utils;
|
||||
pub mod mesh_chunk;
|
||||
208
engine/world_generation/src/mesh_generator.rs
Normal file
208
engine/world_generation/src/mesh_generator.rs
Normal file
@@ -0,0 +1,208 @@
|
||||
use crate::biome_painter::BiomePainterAsset;
|
||||
use crate::hex_utils::HexCoord;
|
||||
use crate::tile_manager::TileAsset;
|
||||
use crate::tile_mapper::TileMapperAsset;
|
||||
use crate::{
|
||||
hex_utils::{offset3d_to_world, INNER_RADIUS, OUTER_RADIUS},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{
|
||||
mesh::{Indices, PrimitiveTopology},
|
||||
render_asset::RenderAssetUsages,
|
||||
},
|
||||
};
|
||||
use std::vec::Vec;
|
||||
|
||||
const HEX_CORNERS: [Vec3; 6] = [
|
||||
Vec3::new(0., 0., OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
Vec3::new(INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(0., 0., -OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., -0.5 * OUTER_RADIUS),
|
||||
Vec3::new(-INNER_RADIUS, 0., 0.5 * OUTER_RADIUS),
|
||||
];
|
||||
|
||||
const HEX_NORMALS: [Vec3; 6] = [
|
||||
Vec3::new(
|
||||
INNER_RADIUS / 2.,
|
||||
0.,
|
||||
(OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.,
|
||||
),
|
||||
Vec3::Z,
|
||||
Vec3::new(
|
||||
INNER_RADIUS / -2.,
|
||||
0.,
|
||||
(OUTER_RADIUS + 0.5 * OUTER_RADIUS) / 2.,
|
||||
),
|
||||
Vec3::new(
|
||||
INNER_RADIUS / -2.,
|
||||
0.,
|
||||
(OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.,
|
||||
),
|
||||
Vec3::NEG_Z,
|
||||
Vec3::new(
|
||||
INNER_RADIUS / 2.,
|
||||
0.,
|
||||
(OUTER_RADIUS + 0.5 * OUTER_RADIUS) / -2.,
|
||||
),
|
||||
];
|
||||
|
||||
pub fn generate_chunk_mesh(
|
||||
chunk: &Chunk,
|
||||
map: &Map,
|
||||
painter: &BiomePainterAsset,
|
||||
tiles: &Res<Assets<TileAsset>>,
|
||||
mappers: &Res<Assets<TileMapperAsset>>,
|
||||
) -> Mesh {
|
||||
let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 6;
|
||||
let mut verts = Vec::with_capacity(vertex_count);
|
||||
let mut uvs = Vec::with_capacity(vertex_count);
|
||||
let mut indices = Vec::with_capacity(vertex_count);
|
||||
let mut normals = Vec::with_capacity(vertex_count);
|
||||
let mut texture_indicies = Vec::with_capacity(vertex_count);
|
||||
|
||||
for z in 0..Chunk::SIZE {
|
||||
for x in 0..Chunk::SIZE {
|
||||
let height = chunk.heights[x + z * Chunk::SIZE];
|
||||
let moisture = chunk.moisture[x + z * Chunk::SIZE];
|
||||
let temperature = chunk.temperature[x + z * Chunk::SIZE];
|
||||
let off_pos = Vec3::new(x as f32, height, z as f32);
|
||||
let tile_pos = offset3d_to_world(off_pos);
|
||||
let coord = HexCoord::from_offset(
|
||||
IVec2::new(x as i32, z as i32) + (chunk.chunk_offset * Chunk::SIZE as i32),
|
||||
);
|
||||
let n = map.get_neighbors(&coord);
|
||||
let biome = mappers.get(painter.sample_biome(moisture, temperature));
|
||||
let tile_handle = biome.unwrap().sample_tile(height);
|
||||
let tile = tiles.get(tile_handle).unwrap();
|
||||
|
||||
create_tile(
|
||||
tile_pos,
|
||||
&n,
|
||||
&mut verts,
|
||||
&mut uvs,
|
||||
&mut indices,
|
||||
&mut normals,
|
||||
&mut texture_indicies,
|
||||
// &mut tex,
|
||||
tile.texture_id,
|
||||
tile.side_texture_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let mesh = Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs)
|
||||
.with_inserted_attribute(ATTRIBUTE_TEXTURE_INDEX, texture_indicies)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
||||
.with_inserted_indices(Indices::U32(indices));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
fn create_tile(
|
||||
pos: Vec3,
|
||||
neighbors: &[Option<f32>; 6],
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
texture_indices: &mut Vec<u32>,
|
||||
texture_index: u32,
|
||||
side_texture_index: u32,
|
||||
) {
|
||||
let uv_offset = Vec2::splat(0.5);
|
||||
|
||||
let idx = verts.len() as u32;
|
||||
texture_indices.push(texture_index);
|
||||
uvs.push(uv_offset);
|
||||
verts.push(pos);
|
||||
normals.push(Vec3::Y);
|
||||
|
||||
for i in 0..6 {
|
||||
let p = pos + HEX_CORNERS[i];
|
||||
verts.push(p);
|
||||
let uv = (HEX_CORNERS[i].xz() / 2.) + uv_offset;
|
||||
uvs.push(uv);
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1 + i as u32);
|
||||
indices.push(idx + 1 + ((i as u32 + 1) % 6));
|
||||
normals.push(Vec3::Y);
|
||||
texture_indices.push(texture_index);
|
||||
}
|
||||
|
||||
for i in 0..neighbors.len() {
|
||||
let cur_n = neighbors[i];
|
||||
match cur_n {
|
||||
Some(n_height) => {
|
||||
if n_height < pos.y {
|
||||
create_tile_wall(
|
||||
pos,
|
||||
i,
|
||||
n_height,
|
||||
verts,
|
||||
uvs,
|
||||
indices,
|
||||
normals,
|
||||
texture_indices,
|
||||
side_texture_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_tile_wall(
|
||||
pos: Vec3,
|
||||
dir: usize,
|
||||
height: f32,
|
||||
verts: &mut Vec<Vec3>,
|
||||
uvs: &mut Vec<Vec2>,
|
||||
indices: &mut Vec<u32>,
|
||||
normals: &mut Vec<Vec3>,
|
||||
texture_indices: &mut Vec<u32>,
|
||||
texture_index: u32,
|
||||
) {
|
||||
let p1 = HEX_CORNERS[(dir) % 6] + pos;
|
||||
let p2 = HEX_CORNERS[(dir + 1) % 6] + pos;
|
||||
let p3 = Vec3::new(p1.x, height, p1.z);
|
||||
let p4 = Vec3::new(p2.x, height, p2.z);
|
||||
|
||||
let idx = verts.len() as u32;
|
||||
|
||||
verts.push(p1);
|
||||
verts.push(p2);
|
||||
verts.push(p3);
|
||||
verts.push(p4);
|
||||
|
||||
texture_indices.push(texture_index);
|
||||
texture_indices.push(texture_index);
|
||||
texture_indices.push(texture_index);
|
||||
texture_indices.push(texture_index);
|
||||
|
||||
let n = HEX_NORMALS[dir].normalize();
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
normals.push(n);
|
||||
|
||||
indices.push(idx);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 3);
|
||||
|
||||
uvs.push(Vec2::ZERO);
|
||||
uvs.push(Vec2::new(1., 0.));
|
||||
uvs.push(Vec2::new(0., pos.y - height));
|
||||
uvs.push(Vec2::new(1., pos.y - height));
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub use crate::consts::*;
|
||||
pub use crate::map::config::*;
|
||||
pub use crate::map::map::*;
|
||||
pub use crate::map::mesh_chunk::*;
|
||||
pub use crate::states::*;
|
||||
@@ -1,11 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum GeneratorState {
|
||||
Startup,
|
||||
GenerateHeightmap,
|
||||
SpawnMap,
|
||||
Idle,
|
||||
Regenerate,
|
||||
Cleanup,
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::{asset::Asset, reflect::TypePath};
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
ecs::system::Resource,
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Resource, Debug)]
|
||||
pub struct TileManager {
|
||||
@@ -20,7 +24,7 @@ impl TileManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset, Clone)]
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath, Asset)]
|
||||
pub struct TileAsset {
|
||||
#[serde(skip)]
|
||||
pub id: usize,
|
||||
@@ -33,4 +37,4 @@ pub struct TileAsset {
|
||||
pub side_texture: String,
|
||||
}
|
||||
|
||||
create_asset_loader!(TileAssetPlugin, TileAssetLoader, TileAsset, &["tile", "tile.ron"],;?);
|
||||
create_asset_loader!(TileAssetPlugin, TileAssetLoader, TileAsset, TileAssetLoadState, &["tile.json"],;?);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::prelude::*;
|
||||
use bevy::{asset::Asset, reflect::TypePath};
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
reflect::TypePath,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::tile_manager::TileAsset;
|
||||
@@ -16,14 +19,14 @@ pub struct TileMapperAsset {
|
||||
}
|
||||
|
||||
impl TileMapperAsset {
|
||||
pub fn sample_tile(&self, height: f32) -> AssetId<TileAsset> {
|
||||
pub fn sample_tile(&self, height: f32) -> Handle<TileAsset> {
|
||||
for i in 0..self.thresholds.len() {
|
||||
let t = self.thresholds[i];
|
||||
if t >= height {
|
||||
return self.tiles[i].id();
|
||||
return self.tiles[i].clone();
|
||||
}
|
||||
}
|
||||
return self.tiles.last().unwrap().id();
|
||||
return self.tiles.last().unwrap().clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +34,8 @@ create_asset_loader!(
|
||||
TileMapperAssetPlugin,
|
||||
TileMapperAssetLoader,
|
||||
TileMapperAsset,
|
||||
&["mapper", "mapper.ron"],;
|
||||
TileMapperLoadState,
|
||||
&["mapper.json"],;
|
||||
tiles_path -> tiles
|
||||
?
|
||||
);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
[package]
|
||||
name = "buildings"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.18.0"
|
||||
world_generation = { path = "../../engine/world_generation" }
|
||||
shared = { path = "../shared" }
|
||||
bevy_rapier3d = "0.33.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
asset_loader = { path = "../../engine/asset_loader" }
|
||||
serde_json = "1.0.149"
|
||||
ron = "0.12.0"
|
||||
bevy_asset_loader = { version = "0.25.0", features = [
|
||||
"standard_dynamic_assets",
|
||||
"3d",
|
||||
] }
|
||||
hex = { path = "../../engine/hex" }
|
||||
|
||||
[features]
|
||||
tracing = []
|
||||
@@ -1,158 +0,0 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::{
|
||||
ecs::relationship::RelatedSpawnerCommands,
|
||||
gltf::{GltfMesh, GltfNode},
|
||||
prelude::*,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::{component_defination::ComponentDefination, identifiers::ResourceIdentifier};
|
||||
|
||||
use crate::{
|
||||
buildings::{
|
||||
basic_building::BasicBuildingInfo, conduit_building::ResourceConduitInfo,
|
||||
factory_building::FactoryBuildingInfo, resource_gathering::ResourceGatheringBuildingInfo,
|
||||
tech_building::TechBuildingInfo,
|
||||
},
|
||||
footprint::BuildingFootprint,
|
||||
prelude::Building,
|
||||
};
|
||||
|
||||
#[derive(Asset, TypePath, Debug, Serialize, Deserialize)]
|
||||
pub struct BuildingAsset
|
||||
{
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub footprint: BuildingFootprint,
|
||||
pub prefab_path: String,
|
||||
#[serde(skip)]
|
||||
pub prefab: Handle<Gltf>,
|
||||
pub base_mesh_path: String,
|
||||
|
||||
pub cost: Vec<ResourceIdentifier>,
|
||||
pub consumption: Vec<ResourceIdentifier>,
|
||||
pub production: Vec<ResourceIdentifier>,
|
||||
|
||||
pub health: u32,
|
||||
|
||||
pub building_type: BuildingType,
|
||||
pub components: Option<Vec<ComponentDefination>>,
|
||||
}
|
||||
|
||||
impl BuildingAsset
|
||||
{
|
||||
#[allow(unused)]
|
||||
pub fn spawn(
|
||||
&self,
|
||||
pos: Vec3,
|
||||
rot: Quat,
|
||||
gltf: &Gltf,
|
||||
commands: &mut Commands,
|
||||
meshes: &Assets<GltfMesh>,
|
||||
nodes: &Assets<GltfNode>,
|
||||
) -> Option<Entity>
|
||||
{
|
||||
let base_node = &gltf.named_nodes[&self.base_mesh_path.clone().into_boxed_str()];
|
||||
if let Some(node) = nodes.get(base_node.id()) {
|
||||
if let Some(mesh_handle) = &node.mesh {
|
||||
if let Some(gltf_mesh) = meshes.get(mesh_handle.id()) {
|
||||
if let Some(primitive) = gltf_mesh.primitives.first() {
|
||||
let mesh = primitive.mesh.clone();
|
||||
let mat = primitive
|
||||
.material
|
||||
.clone()
|
||||
.expect(format!("Mesh '{}' does not have a meterial", primitive.name.as_str()).as_str());
|
||||
let mut entity = commands.spawn((
|
||||
Mesh3d(mesh),
|
||||
MeshMaterial3d(mat),
|
||||
Transform::from_translation(pos).with_rotation(rot),
|
||||
Building,
|
||||
));
|
||||
entity.with_children(|b| {
|
||||
for child in &node.children {
|
||||
let child_node = nodes.get(child.id());
|
||||
if child_node.is_none() {
|
||||
continue;
|
||||
}
|
||||
self.process_node(child_node.unwrap(), meshes, nodes, b, &node.name);
|
||||
}
|
||||
});
|
||||
if let Some(component) = self.get_component_def(&format!("/{0}", &node.name)) {
|
||||
component.apply(&mut entity);
|
||||
}
|
||||
return Some(entity.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn process_node(
|
||||
&self,
|
||||
node: &GltfNode,
|
||||
meshes: &Assets<GltfMesh>,
|
||||
nodes: &Assets<GltfNode>,
|
||||
commands: &mut RelatedSpawnerCommands<ChildOf>,
|
||||
parent: &String,
|
||||
) -> Option<Entity>
|
||||
{
|
||||
let path = format!("{0}/{1}", parent, node.name);
|
||||
if let Some(mesh) = &node.mesh {
|
||||
if let Some(gltf_mesh) = meshes.get(mesh.id()) {
|
||||
if let Some(primitive) = gltf_mesh.primitives.first() {
|
||||
let mesh = primitive.mesh.clone();
|
||||
let mat = primitive
|
||||
.material
|
||||
.clone()
|
||||
.expect(format!("Mesh '{}' does not have a meterial", primitive.name.as_str()).as_str());
|
||||
let mut entity = commands.spawn((Mesh3d(mesh), MeshMaterial3d(mat), node.transform, Building));
|
||||
entity.with_children(|b| {
|
||||
for child in &node.children {
|
||||
let child_node = nodes.get(child.id());
|
||||
if child_node.is_none() {
|
||||
continue;
|
||||
}
|
||||
self.process_node(child_node.unwrap(), meshes, nodes, b, &path);
|
||||
}
|
||||
});
|
||||
if let Some(component) = self.get_component_def(&path) {
|
||||
component.apply(&mut entity);
|
||||
}
|
||||
return Some(entity.id());
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn get_component_def(&self, path: &String) -> Option<&ComponentDefination>
|
||||
{
|
||||
if let Some(components) = &self.components {
|
||||
for c in components {
|
||||
if c.path.ends_with(path) {
|
||||
return Some(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, TypePath)]
|
||||
pub enum BuildingType
|
||||
{
|
||||
Basic(BasicBuildingInfo),
|
||||
Gathering(ResourceGatheringBuildingInfo),
|
||||
FactoryBuildingInfo(FactoryBuildingInfo),
|
||||
ResourceConduit(ResourceConduitInfo),
|
||||
Tech(TechBuildingInfo),
|
||||
}
|
||||
|
||||
create_asset_loader!(
|
||||
BuildingAssetPlugin,
|
||||
BuildingAssetLoader,
|
||||
BuildingAsset,
|
||||
&["building", "building.ron"],
|
||||
prefab_path -> prefab
|
||||
;?
|
||||
);
|
||||
@@ -1,11 +0,0 @@
|
||||
use bevy::{asset::Handle, prelude::Resource};
|
||||
|
||||
use super::building_asset::BuildingAsset;
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
#[derive(Resource, AssetCollection)]
|
||||
pub struct BuildingDatabase {
|
||||
#[asset(key = "buildings", collection(typed))]
|
||||
pub buildings: Vec<Handle<BuildingAsset>>,
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod building_asset;
|
||||
pub mod building_database;
|
||||
@@ -1,26 +0,0 @@
|
||||
use bevy::prelude::Resource;
|
||||
use hex::prelude::*;
|
||||
use shared::building::BuildingIdentifier;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct BuildQueue
|
||||
{
|
||||
pub queue: Vec<QueueEntry>,
|
||||
}
|
||||
|
||||
impl Default for BuildQueue
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
queue: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct QueueEntry
|
||||
{
|
||||
pub building: BuildingIdentifier,
|
||||
pub pos: HexCoord,
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
use bevy::{
|
||||
ecs::world::CommandQueue,
|
||||
gltf::{GltfMesh, GltfNode},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_asset_loader::loading_state::{
|
||||
config::{ConfigureLoadingState, LoadingStateConfig},
|
||||
LoadingStateAppExt,
|
||||
};
|
||||
use shared::{
|
||||
despawn::Despawn,
|
||||
events::TileModifiedEvent,
|
||||
resources::TileUnderCursor,
|
||||
states::{AssetLoadState, GameplayState},
|
||||
};
|
||||
use world_generation::{map::map::Map, prelude::GenerationConfig, states::GeneratorState};
|
||||
|
||||
use crate::{
|
||||
assets::{
|
||||
building_asset::{BuildingAsset, BuildingAssetPlugin},
|
||||
building_database::BuildingDatabase,
|
||||
},
|
||||
build_queue::{BuildQueue, QueueEntry},
|
||||
buildings_map::{BuildingEntry, BuildingMap},
|
||||
prelude::Building,
|
||||
};
|
||||
|
||||
pub struct BuildingPugin;
|
||||
|
||||
impl Plugin for BuildingPugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.insert_resource(BuildQueue::default());
|
||||
app.add_plugins(BuildingAssetPlugin);
|
||||
|
||||
app.configure_loading_state(
|
||||
LoadingStateConfig::new(AssetLoadState::Loading).load_collection::<BuildingDatabase>(),
|
||||
);
|
||||
|
||||
app.add_systems(Update, init.run_if(in_state(AssetLoadState::Loading)));
|
||||
app.add_systems(
|
||||
Update,
|
||||
hq_placement.run_if(in_state(GameplayState::PlaceHQ).and(in_state(GeneratorState::Idle))),
|
||||
);
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
prepare_building_map.run_if(in_state(GeneratorState::SpawnMap)),
|
||||
);
|
||||
app.add_systems(Update, regernerate.run_if(in_state(GeneratorState::Regenerate)));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_building_heights.run_if(in_state(GeneratorState::Idle)),
|
||||
);
|
||||
|
||||
app.add_systems(PreUpdate, process_build_queue.run_if(in_state(GameplayState::Playing)));
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_building_map(mut commands: Commands, cfg: Res<GenerationConfig>)
|
||||
{
|
||||
commands.insert_resource(BuildingMap::new(cfg.size));
|
||||
}
|
||||
|
||||
fn regernerate(mut commands: Commands, buildings: Query<Entity, With<Building>>, cfg: Res<GenerationConfig>)
|
||||
{
|
||||
for e in buildings.iter()
|
||||
{
|
||||
commands.entity(e).despawn();
|
||||
}
|
||||
commands.insert_resource(BuildingMap::new(cfg.size));
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct IndicatorCube(Handle<Mesh>, Handle<StandardMaterial>);
|
||||
|
||||
fn init(mut commands: Commands, mut meshes: ResMut<Assets<Mesh>>, mut materials: ResMut<Assets<StandardMaterial>>)
|
||||
{
|
||||
let cube = Cuboid::from_size(Vec3::splat(1.));
|
||||
let mesh_handle = meshes.add(cube);
|
||||
let mat_handle = materials.add(Color::WHITE);
|
||||
commands.insert_resource(IndicatorCube(mesh_handle, mat_handle));
|
||||
}
|
||||
|
||||
fn hq_placement(
|
||||
mut commands: Commands,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
tile_under_cursor: Res<TileUnderCursor>,
|
||||
map: Res<Map>,
|
||||
indicator: Res<IndicatorCube>,
|
||||
mut build_queue: ResMut<BuildQueue>,
|
||||
mut next_state: ResMut<NextState<GameplayState>>,
|
||||
)
|
||||
{
|
||||
if let Some(contact) = tile_under_cursor.0
|
||||
{
|
||||
let positions = map.hex_select(&contact.tile, 3, true, |pos, h, _| pos.to_world(h));
|
||||
show_indicators(positions, &mut commands, &indicator);
|
||||
|
||||
if mouse.just_pressed(MouseButton::Left)
|
||||
{
|
||||
build_queue.queue.push(QueueEntry {
|
||||
building: 0.into(),
|
||||
pos: contact.tile,
|
||||
});
|
||||
|
||||
next_state.set(GameplayState::Playing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn show_indicators(positions: Vec<Vec3>, commands: &mut Commands, indicator: &IndicatorCube)
|
||||
{
|
||||
for p in positions
|
||||
{
|
||||
commands.spawn((
|
||||
Mesh3d(indicator.0.clone()),
|
||||
MeshMaterial3d(indicator.1.clone()),
|
||||
Transform::from_translation(p),
|
||||
Despawn,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn process_build_queue(
|
||||
mut queue: ResMut<BuildQueue>,
|
||||
mut commands: Commands,
|
||||
db: Res<BuildingDatabase>,
|
||||
building_assets: Res<Assets<BuildingAsset>>,
|
||||
gltf_assets: Res<Assets<Gltf>>,
|
||||
gltf_meshes: Res<Assets<GltfMesh>>,
|
||||
gltf_nodes: Res<Assets<GltfNode>>,
|
||||
mut building_map: ResMut<BuildingMap>,
|
||||
heightmap: Res<Map>,
|
||||
)
|
||||
{
|
||||
for item in &queue.queue
|
||||
{
|
||||
let handle = &db.buildings[item.building.0];
|
||||
if let Some(building) = building_assets.get(handle.id())
|
||||
{
|
||||
let h = heightmap.sample_height(&item.pos);
|
||||
println!("Spawning {} at {}", building.name, item.pos);
|
||||
if let Some(gltf) = gltf_assets.get(building.prefab.id())
|
||||
{
|
||||
let e = building.spawn(
|
||||
item.pos.to_world(h),
|
||||
Quat::IDENTITY,
|
||||
gltf,
|
||||
&mut commands,
|
||||
&gltf_meshes,
|
||||
&gltf_nodes,
|
||||
);
|
||||
if let Some(b) = e
|
||||
{
|
||||
building_map.add_building(BuildingEntry::new(item.pos, b));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
warn!("Failed to spawn building");
|
||||
}
|
||||
}
|
||||
}
|
||||
queue.queue.clear();
|
||||
}
|
||||
|
||||
fn update_building_heights(
|
||||
mut tile_updates: MessageReader<TileModifiedEvent>,
|
||||
building_map: Res<BuildingMap>,
|
||||
mut commands: Commands,
|
||||
)
|
||||
{
|
||||
for event in tile_updates.read()
|
||||
{
|
||||
match event
|
||||
{
|
||||
TileModifiedEvent::HeightChanged(coord, new_height) =>
|
||||
{
|
||||
if let Some(building) = building_map.get_building(coord)
|
||||
{
|
||||
let mut queue = CommandQueue::default();
|
||||
let e = building.entity.clone();
|
||||
let h = *new_height;
|
||||
queue.push(move |world: &mut World| {
|
||||
let mut emut = world.entity_mut(e);
|
||||
if let Some(mut transform) = emut.get_mut::<Transform>()
|
||||
{
|
||||
transform.translation.y = h;
|
||||
}
|
||||
});
|
||||
|
||||
commands.append(&mut queue);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BasicBuildingInfo
|
||||
{
|
||||
health: u32,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ResourceConduitInfo {
|
||||
pub range: usize,
|
||||
pub connection_range: usize,
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct FactoryBuildingInfo {
|
||||
pub units_to_build: Vec<()>
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod basic_building;
|
||||
pub mod conduit_building;
|
||||
pub mod factory_building;
|
||||
pub mod resource_gathering;
|
||||
pub mod tech_building;
|
||||
@@ -1,8 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::identifiers::ResourceIdentifier;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ResourceGatheringBuildingInfo {
|
||||
pub resources_to_gather: Vec<ResourceIdentifier>,
|
||||
pub gather_range: usize,
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::{building::BuildingIdentifier, StatusEffect};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TechBuildingInfo {
|
||||
pub effect_range: usize,
|
||||
pub buildings_to_unlock: Vec<BuildingIdentifier>,
|
||||
pub buffs: Vec<StatusEffect>,
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct BuildingMap
|
||||
{
|
||||
pub chunks: Vec<BuildingChunk>,
|
||||
pub size: UVec2,
|
||||
}
|
||||
|
||||
impl BuildingMap
|
||||
{
|
||||
pub fn new(size: UVec2) -> Self
|
||||
{
|
||||
let mut db = BuildingMap {
|
||||
size,
|
||||
chunks: Vec::with_capacity(size.length_squared() as usize),
|
||||
};
|
||||
|
||||
for y in 0..size.y as i32
|
||||
{
|
||||
for x in 0..size.x as i32
|
||||
{
|
||||
let offset = IVec2::new(x, y);
|
||||
let index = (x + y * size.x as i32) as usize;
|
||||
db.chunks.push(BuildingChunk::new(offset, index));
|
||||
}
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
pub fn get_buildings_in_range(&self, coord: &HexCoord, radius: usize) -> Vec<&BuildingEntry>
|
||||
{
|
||||
assert!(radius != 0, "Radius cannot be zero");
|
||||
|
||||
let w = self.size.x as usize * Chunk::SIZE;
|
||||
let h = self.size.y as usize * Chunk::SIZE;
|
||||
let coords = coord.hex_select_bounded(radius, true, h, w);
|
||||
return self.get_buildings_in_coords(coords);
|
||||
}
|
||||
|
||||
pub fn get_buildings_in_coords(&self, coords: Vec<HexCoord>) -> Vec<&BuildingEntry>
|
||||
{
|
||||
let mut result = Vec::new();
|
||||
for coord in &coords
|
||||
{
|
||||
if let Some(buidling) = self.get_building(coord)
|
||||
{
|
||||
result.push(buidling);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn get_building(&self, coord: &HexCoord) -> Option<&BuildingEntry>
|
||||
{
|
||||
let chunk = &self.chunks[coord.to_chunk_index(self.size.x as usize)];
|
||||
return chunk.get_building(coord);
|
||||
}
|
||||
|
||||
pub fn add_building(&mut self, entry: BuildingEntry)
|
||||
{
|
||||
let chunk = &mut self.chunks[entry.coord.to_chunk_index(self.size.x as usize)];
|
||||
chunk.add_building(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuildingChunk
|
||||
{
|
||||
pub entries: Vec<BuildingEntry>,
|
||||
pub index: usize,
|
||||
pub offset: IVec2,
|
||||
}
|
||||
|
||||
impl BuildingChunk
|
||||
{
|
||||
pub fn new(offset: IVec2, index: usize) -> Self
|
||||
{
|
||||
return BuildingChunk {
|
||||
entries: Vec::new(),
|
||||
index,
|
||||
offset,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn get_building(&self, coord: &HexCoord) -> Option<&BuildingEntry>
|
||||
{
|
||||
return self.entries.iter().find(|b| &b.coord == coord);
|
||||
}
|
||||
|
||||
pub fn add_building(&mut self, entry: BuildingEntry)
|
||||
{
|
||||
self.entries.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BuildingEntry
|
||||
{
|
||||
pub coord: HexCoord,
|
||||
pub entity: Entity,
|
||||
pub is_main: bool,
|
||||
pub main_entity: Option<Entity>,
|
||||
pub has_children: bool,
|
||||
pub child_entities: Option<Vec<Entity>>,
|
||||
}
|
||||
|
||||
impl BuildingEntry
|
||||
{
|
||||
pub fn new(coord: HexCoord, entity: Entity) -> Self
|
||||
{
|
||||
return BuildingEntry {
|
||||
coord,
|
||||
entity,
|
||||
child_entities: None,
|
||||
has_children: false,
|
||||
main_entity: None,
|
||||
is_main: true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn new_with_children(coord: HexCoord, entity: Entity, children: Vec<Entity>) -> BuildingEntry
|
||||
{
|
||||
return BuildingEntry {
|
||||
coord,
|
||||
entity,
|
||||
child_entities: Some(children),
|
||||
has_children: true,
|
||||
main_entity: None,
|
||||
is_main: true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn new_with_parent(coord: HexCoord, entity: Entity, main: Entity) -> BuildingEntry
|
||||
{
|
||||
return BuildingEntry {
|
||||
coord,
|
||||
entity,
|
||||
child_entities: None,
|
||||
has_children: false,
|
||||
main_entity: Some(main),
|
||||
is_main: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
use bevy::math::{IVec2, Vec3Swizzles};
|
||||
use hex::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::coords::CoordsCollection;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BuildingFootprint
|
||||
{
|
||||
pub footprint: Vec<IVec2>,
|
||||
}
|
||||
|
||||
impl BuildingFootprint
|
||||
{
|
||||
pub fn get_footprint(&self, position: &HexCoord) -> CoordsCollection
|
||||
{
|
||||
CoordsCollection::from_points(self.footprint.clone()).with_translation(position)
|
||||
}
|
||||
|
||||
pub fn get_neighbors(&self, position: &HexCoord) -> CoordsCollection
|
||||
{
|
||||
let n_points: Vec<IVec2> = self
|
||||
.footprint
|
||||
.iter()
|
||||
.flat_map(|p| HexCoord::from_axial(*p).get_neighbors())
|
||||
.map(|c| c.hex.xy())
|
||||
.filter(|p| !self.footprint.contains(p))
|
||||
.collect();
|
||||
let mut out_points: Vec<IVec2> = Vec::with_capacity(n_points.len());
|
||||
for p in n_points
|
||||
{
|
||||
if out_points.contains(&p)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out_points.push(p);
|
||||
}
|
||||
return CoordsCollection::from_points(out_points).with_translation(position);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
pub mod assets;
|
||||
pub mod build_queue;
|
||||
pub mod building_plugin;
|
||||
pub mod buildings_map;
|
||||
pub mod footprint;
|
||||
pub mod prelude;
|
||||
mod buildings;
|
||||
pub use building_plugin::*;
|
||||
@@ -1,3 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
#[derive(Component)]
|
||||
pub struct Building;
|
||||
9
game/camera_system/Cargo.toml
Normal file
9
game/camera_system/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "camera_system"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.13.1"
|
||||
127
game/camera_system/src/lib.rs
Normal file
127
game/camera_system/src/lib.rs
Normal file
@@ -0,0 +1,127 @@
|
||||
use crate::prelude::PhosCamera;
|
||||
use bevy::input::mouse::MouseMotion;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::CursorGrabMode;
|
||||
|
||||
pub mod prelude {
|
||||
use bevy::prelude::Component;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct PhosCamera {
|
||||
pub min_height: f32,
|
||||
pub max_height: f32,
|
||||
pub speed: f32,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PhosCameraPlugin;
|
||||
|
||||
impl Plugin for PhosCameraPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, setup).add_systems(
|
||||
Update,
|
||||
(grab_mouse, (update_camera, update_camera_mouse).chain()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0., 30., 0.)
|
||||
.looking_at(Vec3::new(1000., 0., 1000.), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
PhosCamera {
|
||||
speed: 100.,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
fn update_camera(
|
||||
mut cam_query: Query<(&PhosCamera, &mut Transform)>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
windows: Query<&Window>,
|
||||
) {
|
||||
let window = windows.single();
|
||||
if window.cursor.grab_mode != CursorGrabMode::Locked {
|
||||
return;
|
||||
}
|
||||
let (cam, mut transform) = cam_query.single_mut();
|
||||
|
||||
let mut move_vec = Vec3::ZERO;
|
||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||
move_vec += Vec3::NEG_X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||
move_vec += Vec3::X;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||
move_vec += Vec3::NEG_Z;
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||
move_vec += Vec3::Z;
|
||||
}
|
||||
|
||||
let rot = transform.rotation;
|
||||
move_vec = (rot * move_vec.normalize_or_zero()) * cam.speed * time.delta_seconds();
|
||||
|
||||
if keyboard_input.pressed(KeyCode::ShiftLeft) {
|
||||
move_vec += Vec3::from(transform.down());
|
||||
}
|
||||
if keyboard_input.pressed(KeyCode::Space) {
|
||||
move_vec += Vec3::from(transform.up());
|
||||
}
|
||||
|
||||
transform.translation += move_vec.normalize_or_zero() * cam.speed * time.delta_seconds();
|
||||
}
|
||||
|
||||
fn update_camera_mouse(
|
||||
mut cam_query: Query<&mut Transform, With<PhosCamera>>,
|
||||
mut mouse_move: EventReader<MouseMotion>,
|
||||
time: Res<Time>,
|
||||
windows: Query<&Window>,
|
||||
) {
|
||||
let window = windows.single();
|
||||
if window.cursor.grab_mode != CursorGrabMode::Locked {
|
||||
return;
|
||||
}
|
||||
let mut transform = cam_query.single_mut();
|
||||
|
||||
for ev in mouse_move.read() {
|
||||
let (mut yaw, mut pitch, _) = transform.rotation.to_euler(EulerRot::YXZ);
|
||||
match window.cursor.grab_mode {
|
||||
CursorGrabMode::None => (),
|
||||
_ => {
|
||||
// Using smallest of height or width ensures equal vertical and horizontal sensitivity
|
||||
pitch -= (ev.delta.y * time.delta_seconds() * 5.).to_radians();
|
||||
yaw -= (ev.delta.x * time.delta_seconds() * 5.).to_radians();
|
||||
}
|
||||
}
|
||||
|
||||
pitch = pitch.clamp(-1.54, 1.54);
|
||||
|
||||
// Order is important to prevent unintended roll
|
||||
transform.rotation =
|
||||
Quat::from_axis_angle(Vec3::Y, yaw) * Quat::from_axis_angle(Vec3::X, pitch);
|
||||
}
|
||||
}
|
||||
|
||||
fn grab_mouse(
|
||||
mut windows: Query<&mut Window>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let mut window = windows.single_mut();
|
||||
|
||||
if mouse.just_pressed(MouseButton::Middle) {
|
||||
window.cursor.visible = false;
|
||||
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.cursor.visible = true;
|
||||
window.cursor.grab_mode = CursorGrabMode::None;
|
||||
}
|
||||
}
|
||||
@@ -7,36 +7,10 @@ build = "build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = { version = "0.18.0", features = ["file_watcher"] }
|
||||
bevy-inspector-egui = "0.36.0"
|
||||
# iyes_perf_ui = "0.3.0"
|
||||
noise = "0.9.0"
|
||||
world_generation = { path = "../../engine/world_generation" }
|
||||
bevy_rapier3d = { version = "0.33.0", features = [
|
||||
"simd-stable",
|
||||
"parallel",
|
||||
"debug-render-3d",
|
||||
] }
|
||||
rayon = "1.11.0"
|
||||
buildings = { path = "../buildings" }
|
||||
units = { path = "../units" }
|
||||
shared = { path = "../shared" }
|
||||
bevy_asset_loader = { version = "0.25.0", features = [
|
||||
"standard_dynamic_assets",
|
||||
"3d",
|
||||
] }
|
||||
ron = "0.12.0"
|
||||
image = "0.25.9"
|
||||
hex = { path = "../../engine/hex" }
|
||||
|
||||
# bevy_lunex = "0.2.4"
|
||||
|
||||
[features]
|
||||
editor = []
|
||||
tracing = [
|
||||
"bevy/trace_tracy",
|
||||
"world_generation/tracing",
|
||||
"buildings/tracing",
|
||||
"units/tracing",
|
||||
"shared/tracing",
|
||||
]
|
||||
bevy = "0.13.2"
|
||||
bevy-inspector-egui = "0.23.4"
|
||||
iyes_perf_ui = "0.2.3"
|
||||
noise = "0.8.2"
|
||||
world_generation ={path="../../engine/world_generation"}
|
||||
camera_system={path = "../camera_system"}
|
||||
bevy_rapier3d = { version = "0.25.0", features = [ "simd-stable", "debug-render-3d" ] }
|
||||
|
||||
Submodule game/main/assets updated: 9cd8a544d5...830f846764
@@ -13,10 +13,6 @@ where
|
||||
|
||||
for path in fs::read_dir(from).unwrap() {
|
||||
let path = path.unwrap().path();
|
||||
println!("{path:?}");
|
||||
if path.starts_with("assets/raw_assets") {
|
||||
continue;
|
||||
}
|
||||
let to = to.clone().join(path.file_name().unwrap());
|
||||
|
||||
if path.is_file() {
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
use bevy::anti_alias::taa::TemporalAntiAliasing;
|
||||
use bevy::camera::visibility::RenderLayers;
|
||||
use bevy::core_pipeline::prepass::DepthPrepass;
|
||||
use bevy::input::mouse::{MouseMotion, MouseScrollUnit, MouseWheel};
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::{CursorGrabMode, CursorOptions, PrimaryWindow};
|
||||
use hex::prelude::*;
|
||||
use shared::sets::GameplaySet;
|
||||
use shared::tags::MainCamera;
|
||||
use world_generation::prelude::Map;
|
||||
use world_generation::states::GeneratorState;
|
||||
|
||||
use super::components::*;
|
||||
|
||||
pub struct PhosCameraPlugin;
|
||||
|
||||
impl Plugin for PhosCameraPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.register_type::<PhosCamera>();
|
||||
app.register_type::<PhosOrbitCamera>();
|
||||
|
||||
app.add_systems(PreStartup, setup);
|
||||
|
||||
app.add_systems(Update, orbit_camera_upate.in_set(GameplaySet));
|
||||
|
||||
app.add_systems(Update, init_bounds.run_if(in_state(GeneratorState::SpawnMap)));
|
||||
}
|
||||
}
|
||||
|
||||
fn init_bounds(mut commands: Commands, cam: Single<(&mut Transform, Entity), With<PhosCamera>>, heightmap: Res<Map>)
|
||||
{
|
||||
let (mut cam_t, cam_entity) = cam.into_inner();
|
||||
cam_t.translation = heightmap.get_center();
|
||||
commands
|
||||
.entity(cam_entity)
|
||||
.insert(CameraBounds::from_size(heightmap.get_world_size()))
|
||||
.insert(PhosOrbitCamera {
|
||||
target: heightmap.get_center_with_height(),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands)
|
||||
{
|
||||
commands
|
||||
.spawn((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0., 30., 0.).looking_to(Vec3::NEG_Z, Vec3::Y),
|
||||
PhosCamera::default(),
|
||||
MainCamera,
|
||||
DepthPrepass,
|
||||
PhosOrbitCamera::default(),
|
||||
TemporalAntiAliasing::default(),
|
||||
))
|
||||
.insert(Msaa::Off)
|
||||
.insert(RenderLayers::default());
|
||||
}
|
||||
|
||||
fn orbit_camera_upate(
|
||||
cam_query: Single<(&mut Transform, &PhosCamera, &mut PhosOrbitCamera, &CameraBounds)>,
|
||||
mut wheel: MessageReader<MouseWheel>,
|
||||
mut mouse_motion: MessageReader<MouseMotion>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
mut cursor_options: Single<&mut CursorOptions, With<PrimaryWindow>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
time: Res<Time>,
|
||||
map: Res<Map>,
|
||||
#[cfg(debug_assertions)] mut gizmos: Gizmos,
|
||||
)
|
||||
{
|
||||
let (mut transform, config, mut orbit, bounds) = cam_query.into_inner();
|
||||
|
||||
let target = orbit.target;
|
||||
let mut cam_pos = target;
|
||||
|
||||
//Apply Camera Dist
|
||||
cam_pos -= orbit.forward * orbit.distance;
|
||||
|
||||
if mouse.pressed(MouseButton::Middle)
|
||||
{
|
||||
let mut orbit_move = Vec2::ZERO;
|
||||
for e in mouse_motion.read()
|
||||
{
|
||||
orbit_move += e.delta;
|
||||
}
|
||||
orbit_move *= config.pan_speed * time.delta_secs() * -1.0;
|
||||
let rot_y = Quat::from_axis_angle(Vec3::Y, orbit_move.x);
|
||||
let right = orbit.forward.cross(Vec3::Y).normalize();
|
||||
let rot_x = Quat::from_axis_angle(right, orbit_move.y);
|
||||
orbit.forward = rot_x * rot_y * orbit.forward;
|
||||
// orbit.forward.y = orbit.forward.y.clamp(-0.9, 0.0);
|
||||
orbit.forward = orbit.forward.normalize();
|
||||
cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
cursor_options.visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
cursor_options.grab_mode = CursorGrabMode::None;
|
||||
cursor_options.visible = true;
|
||||
}
|
||||
if key.pressed(KeyCode::KeyE)
|
||||
{
|
||||
let rot = Quat::from_axis_angle(Vec3::Y, f32::to_radians(config.speed) * time.delta_secs());
|
||||
orbit.forward = rot * orbit.forward;
|
||||
}
|
||||
else if key.pressed(KeyCode::KeyQ)
|
||||
{
|
||||
let rot = Quat::from_axis_angle(Vec3::Y, f32::to_radians(-config.speed) * time.delta_secs());
|
||||
orbit.forward = rot * orbit.forward;
|
||||
}
|
||||
|
||||
let mut cam_move = Vec3::ZERO;
|
||||
|
||||
if key.pressed(KeyCode::KeyA)
|
||||
{
|
||||
cam_move.x = 1.;
|
||||
}
|
||||
else if key.pressed(KeyCode::KeyD)
|
||||
{
|
||||
cam_move.x = -1.;
|
||||
}
|
||||
|
||||
if key.pressed(KeyCode::KeyW)
|
||||
{
|
||||
cam_move.z = 1.;
|
||||
}
|
||||
else if key.pressed(KeyCode::KeyS)
|
||||
{
|
||||
cam_move.z = -1.;
|
||||
}
|
||||
|
||||
let move_speed = if key.pressed(KeyCode::ShiftLeft)
|
||||
{
|
||||
config.speed * 2.0
|
||||
}
|
||||
else
|
||||
{
|
||||
config.speed
|
||||
};
|
||||
|
||||
if cam_move != Vec3::ZERO
|
||||
{
|
||||
cam_move = cam_move.normalize();
|
||||
let move_fwd = Vec3::new(orbit.forward.x, 0., orbit.forward.z).normalize();
|
||||
let move_rot = Quat::from_rotation_arc(Vec3::NEG_Z, move_fwd);
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
gizmos.arrow(orbit.target, orbit.target + move_fwd, LinearRgba::WHITE.with_alpha(0.5));
|
||||
gizmos.arrow(orbit.target, orbit.target - (move_rot * cam_move), LinearRgba::BLUE);
|
||||
}
|
||||
orbit.target -= (move_rot * cam_move) * move_speed * time.delta_secs();
|
||||
orbit.target.y = sample_ground(orbit.target, &map);
|
||||
|
||||
orbit.target.x = orbit.target.x.clamp(bounds.min.x, bounds.max.x);
|
||||
orbit.target.z = orbit.target.z.clamp(bounds.min.y, bounds.max.y);
|
||||
}
|
||||
|
||||
let mut scroll = 0.0;
|
||||
for e in wheel.read()
|
||||
{
|
||||
match e.unit
|
||||
{
|
||||
MouseScrollUnit::Line => scroll += e.y * 5.,
|
||||
MouseScrollUnit::Pixel => scroll += e.y,
|
||||
}
|
||||
}
|
||||
|
||||
orbit.distance -= scroll * time.delta_secs() * config.zoom_speed;
|
||||
orbit.distance = orbit.distance.clamp(config.min_height, config.max_height);
|
||||
|
||||
// let ground_below_cam = sample_ground(cam_pos, &map) + config.min_height;
|
||||
// if cam_pos.y <= ground_below_cam {
|
||||
// cam_pos.y = ground_below_cam;
|
||||
// }
|
||||
|
||||
// if cam_pos.y < target.y {
|
||||
// cam_pos.y = target.y;
|
||||
// }
|
||||
|
||||
transform.translation = cam_pos;
|
||||
transform.look_at(target, Vec3::Y);
|
||||
}
|
||||
|
||||
fn sample_ground(pos: Vec3, heightmap: &Map) -> f32
|
||||
{
|
||||
let tile_under = HexCoord::from_world_pos(pos);
|
||||
let neighbors = heightmap.get_neighbors(&tile_under);
|
||||
let mut ground_height = if heightmap.is_in_bounds(&tile_under)
|
||||
{
|
||||
heightmap.sample_height(&tile_under)
|
||||
}
|
||||
else
|
||||
{
|
||||
heightmap.sealevel
|
||||
};
|
||||
|
||||
for n in neighbors
|
||||
{
|
||||
if let Some(h) = n
|
||||
{
|
||||
if h > ground_height
|
||||
{
|
||||
ground_height = h;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ground_height < heightmap.sealevel
|
||||
{
|
||||
ground_height = heightmap.sealevel;
|
||||
}
|
||||
return ground_height;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
use rayon::str;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct PhosCamera
|
||||
{
|
||||
pub min_height: f32,
|
||||
pub max_height: f32,
|
||||
pub speed: f32,
|
||||
pub zoom_speed: f32,
|
||||
pub pan_speed: Vec2,
|
||||
pub min_angle: f32,
|
||||
pub max_angle: f32,
|
||||
}
|
||||
|
||||
impl Default for PhosCamera
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
min_height: 10.,
|
||||
max_height: 420.,
|
||||
speed: 100.,
|
||||
pan_speed: Vec2::new(0.8, 0.5),
|
||||
zoom_speed: 20.,
|
||||
min_angle: (20. as f32).to_radians(),
|
||||
max_angle: 1.,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct PhosOrbitCamera
|
||||
{
|
||||
pub target: Vec3,
|
||||
pub distance: f32,
|
||||
pub forward: Vec3,
|
||||
}
|
||||
|
||||
impl Default for PhosOrbitCamera
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
target: Default::default(),
|
||||
distance: 40.0,
|
||||
forward: Vec3::new(0.0, -0.5, 0.5).normalize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct CameraBounds
|
||||
{
|
||||
pub min: Vec2,
|
||||
pub max: Vec2,
|
||||
}
|
||||
|
||||
impl CameraBounds
|
||||
{
|
||||
pub fn from_size(world_size: Vec2) -> Self
|
||||
{
|
||||
let padding = Chunk::WORLD_SIZE;
|
||||
return Self {
|
||||
min: Vec2::ZERO - padding,
|
||||
max: world_size + padding,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod camera_plugin;
|
||||
pub mod components;
|
||||
@@ -1,23 +1,14 @@
|
||||
use bevy::image::{ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor};
|
||||
use bevy::pbr::wireframe::WireframePlugin;
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::texture::{ImageAddressMode, ImageFilterMode, ImageSamplerDescriptor};
|
||||
use bevy::window::PresentMode;
|
||||
#[cfg(debug_assertions)]
|
||||
use bevy::window::WindowResolution;
|
||||
use bevy_inspector_egui::bevy_egui::EguiPlugin;
|
||||
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||
use phos::PhosGamePlugin;
|
||||
|
||||
mod camera_system;
|
||||
mod map_rendering;
|
||||
mod phos;
|
||||
mod prelude;
|
||||
mod shader_extensions;
|
||||
mod ui;
|
||||
mod utlis;
|
||||
use phos::PhosGamePlugin;
|
||||
|
||||
fn main()
|
||||
{
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins
|
||||
@@ -25,11 +16,9 @@ fn main()
|
||||
primary_window: Some(Window {
|
||||
title: "Phos".into(),
|
||||
name: Some("phos".into()),
|
||||
#[cfg(debug_assertions)]
|
||||
resolution: WindowResolution::new(1920, 1080),
|
||||
resolution: (1920.0, 1080.0).into(),
|
||||
resizable: true,
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
#[cfg(not(debug_assertions))]
|
||||
mode: bevy::window::WindowMode::BorderlessFullscreen,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
@@ -41,15 +30,8 @@ fn main()
|
||||
mag_filter: ImageFilterMode::Nearest,
|
||||
..default()
|
||||
},
|
||||
})
|
||||
.set(AssetPlugin {
|
||||
#[cfg(not(debug_assertions))]
|
||||
watch_for_changes_override: Some(true),
|
||||
..Default::default()
|
||||
}),
|
||||
EguiPlugin::default(),
|
||||
WorldInspectorPlugin::new(),
|
||||
WireframePlugin::default(),
|
||||
PhosGamePlugin,
|
||||
))
|
||||
.run();
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
use bevy::ecs::world::CommandQueue;
|
||||
use bevy::prelude::*;
|
||||
use bevy::tasks::*;
|
||||
use bevy_rapier3d::geometry::Collider;
|
||||
use bevy_rapier3d::geometry::TriMeshFlags;
|
||||
use shared::events::ChunkModifiedEvent;
|
||||
use shared::events::TileModifiedEvent;
|
||||
use world_generation::prelude::Map;
|
||||
use world_generation::states::GeneratorState;
|
||||
|
||||
use crate::prelude::RebuildChunk;
|
||||
use crate::prelude::WaterMesh;
|
||||
use crate::{
|
||||
prelude::{PhosChunk, PhosChunkRegistry},
|
||||
utlis::chunk_utils::prepare_chunk_mesh,
|
||||
};
|
||||
|
||||
pub struct ChunkRebuildPlugin;
|
||||
|
||||
impl Plugin for ChunkRebuildPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.init_resource::<PhosChunkRegistry>();
|
||||
app.add_message::<ChunkModifiedEvent>();
|
||||
app.add_message::<TileModifiedEvent>();
|
||||
app.add_systems(PreUpdate, chunk_rebuilder.run_if(in_state(GeneratorState::Idle)));
|
||||
app.add_systems(PostUpdate, collider_task_resolver);
|
||||
}
|
||||
}
|
||||
|
||||
fn chunk_rebuilder(
|
||||
mut commands: Commands,
|
||||
chunk_query: Query<(Entity, &PhosChunk), (With<RebuildChunk>, Without<ChunkRebuildTask>)>,
|
||||
heightmap: Res<Map>,
|
||||
)
|
||||
{
|
||||
let pool = AsyncComputeTaskPool::get();
|
||||
let map_size = UVec2::new(heightmap.width as u32, heightmap.height as u32);
|
||||
|
||||
for (chunk_entity, idx) in &chunk_query
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Rebuild Chunk").entered();
|
||||
info!("Rebuilding Chunk");
|
||||
let chunk_index = idx.index;
|
||||
let chunk_data = heightmap.get_chunk_mesh_data(chunk_index);
|
||||
let chunk_offset = heightmap.chunks[chunk_index].chunk_offset;
|
||||
|
||||
let task = pool.spawn(async move {
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Rebuild Task").entered();
|
||||
let mut queue = CommandQueue::default();
|
||||
let (mesh, water_mesh, collider_data, _, _) =
|
||||
prepare_chunk_mesh(&chunk_data, chunk_data.sealevel, chunk_offset, chunk_index, map_size);
|
||||
#[cfg(feature = "tracing")]
|
||||
let trimesh_span = info_span!("Chunk Trimesh").entered();
|
||||
let c = Collider::trimesh_with_flags(
|
||||
collider_data.0,
|
||||
collider_data.1,
|
||||
TriMeshFlags::DELETE_DUPLICATE_TRIANGLES,
|
||||
)
|
||||
.expect("Failed to build chunk mesh");
|
||||
#[cfg(feature = "tracing")]
|
||||
drop(trimesh_span);
|
||||
queue.push(move |world: &mut World| {
|
||||
world.entity_mut(chunk_entity).insert(c).remove::<ChunkRebuildTask>();
|
||||
});
|
||||
|
||||
return (queue, mesh, water_mesh);
|
||||
});
|
||||
|
||||
commands
|
||||
.entity(chunk_entity)
|
||||
.insert(ChunkRebuildTask { task })
|
||||
.remove::<RebuildChunk>();
|
||||
}
|
||||
}
|
||||
|
||||
fn collider_task_resolver(
|
||||
mut chunks: Query<(&mut ChunkRebuildTask, &Mesh3d, &WaterMesh), With<PhosChunk>>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
)
|
||||
{
|
||||
for (mut task, mesh_handle, water_mesh_handle) in &mut chunks
|
||||
{
|
||||
if let Some((mut c, chunk_mesh, water_mesh)) = futures::check_ready(&mut task.task)
|
||||
{
|
||||
commands.append(&mut c);
|
||||
meshes
|
||||
.insert(mesh_handle.id(), chunk_mesh)
|
||||
.expect("Failed to update chunk mesh");
|
||||
meshes
|
||||
.insert(water_mesh_handle.0, water_mesh)
|
||||
.expect("Failed to update chink water mesh");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct ChunkRebuildTask
|
||||
{
|
||||
pub task: Task<(CommandQueue, Mesh, Mesh)>,
|
||||
}
|
||||
@@ -1,310 +0,0 @@
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::{light::NotShadowCaster, pbr::ExtendedMaterial, prelude::*};
|
||||
use bevy_asset_loader::prelude::*;
|
||||
|
||||
use bevy_inspector_egui::quick::ResourceInspectorPlugin;
|
||||
use hex::prelude::*;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use shared::states::{AssetLoadState, GameplayState, MenuState};
|
||||
|
||||
use world_generation::{
|
||||
biome_asset::{BiomeAsset, BiomeAssetPlugin},
|
||||
biome_painter::*,
|
||||
heightmap::generate_heightmap,
|
||||
map::biome_map::BiomeMap,
|
||||
prelude::*,
|
||||
tile_manager::*,
|
||||
tile_mapper::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
prelude::{PhosAssets, PhosChunk, PhosChunkRegistry, WaterMesh},
|
||||
shader_extensions::{
|
||||
chunk_material::ChunkMaterial,
|
||||
water_material::{WaterMaterial, WaterSettings},
|
||||
},
|
||||
utlis::chunk_utils::{paint_map, prepare_chunk_mesh_with_collider},
|
||||
};
|
||||
|
||||
use super::{chunk_rebuild::ChunkRebuildPlugin, render_distance_system::RenderDistanceVisibility};
|
||||
|
||||
pub struct MapInitPlugin;
|
||||
|
||||
impl Plugin for MapInitPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.insert_state(GeneratorState::Startup);
|
||||
app.insert_state(AssetLoadState::Loading);
|
||||
|
||||
//Assets
|
||||
app.add_plugins(TileAssetPlugin);
|
||||
app.add_plugins(TileMapperAssetPlugin);
|
||||
app.add_plugins(BiomeAssetPlugin);
|
||||
|
||||
app.add_plugins(ResourceInspectorPlugin::<GenerationConfig>::default());
|
||||
app.register_type::<ExtendedMaterial<StandardMaterial, WaterMaterial>>();
|
||||
app.register_asset_reflect::<ExtendedMaterial<StandardMaterial, WaterMaterial>>();
|
||||
app.add_plugins((
|
||||
ChunkRebuildPlugin,
|
||||
// TerraFormingTestPlugin,
|
||||
MaterialPlugin::<ExtendedMaterial<StandardMaterial, ChunkMaterial>>::default(),
|
||||
MaterialPlugin::<ExtendedMaterial<StandardMaterial, WaterMaterial>>::default(),
|
||||
));
|
||||
|
||||
app.configure_loading_state(
|
||||
LoadingStateConfig::new(AssetLoadState::Loading)
|
||||
.with_dynamic_assets_file::<StandardDynamicAssetCollection>("phos.assets.ron")
|
||||
.load_collection::<PhosAssets>()
|
||||
.load_collection::<BiomePainterAsset>(),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
create_heightmap.run_if(in_state(GeneratorState::GenerateHeightmap)),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
(finalize_texture, setup_materials, finalize_biome_painter)
|
||||
.run_if(in_state(AssetLoadState::FinalizeAssets)),
|
||||
);
|
||||
|
||||
app.add_systems(Update, despawn_map.run_if(in_state(GeneratorState::Regenerate)));
|
||||
app.add_systems(
|
||||
Update,
|
||||
spawn_map.run_if(in_state(AssetLoadState::LoadComplete).and(in_state(GeneratorState::SpawnMap))),
|
||||
);
|
||||
|
||||
app.insert_resource(TileManager::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_materials(
|
||||
mut phos_assets: ResMut<PhosAssets>,
|
||||
mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, WaterMaterial>>>,
|
||||
)
|
||||
{
|
||||
let water_material = water_materials.add(ExtendedMaterial {
|
||||
base: StandardMaterial {
|
||||
base_color: Color::srgb(0., 0.878, 1.),
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
metallic: 1.0,
|
||||
..Default::default()
|
||||
},
|
||||
extension: WaterMaterial {
|
||||
settings: WaterSettings {
|
||||
offset: -4.97,
|
||||
scale: 1.,
|
||||
deep_color: LinearRgba::rgb(0.0, 0.04, 0.085).into(),
|
||||
..Default::default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
});
|
||||
phos_assets.water_material = water_material;
|
||||
}
|
||||
|
||||
fn finalize_biome_painter(
|
||||
mut commands: Commands,
|
||||
mut next_generator_state: ResMut<NextState<GeneratorState>>,
|
||||
biome_painter: Res<BiomePainterAsset>,
|
||||
biomes: Res<Assets<BiomeAsset>>,
|
||||
)
|
||||
{
|
||||
let painter = biome_painter.build(&biomes);
|
||||
commands.insert_resource(painter);
|
||||
next_generator_state.set(GeneratorState::GenerateHeightmap);
|
||||
}
|
||||
|
||||
fn finalize_texture(
|
||||
mut atlas: ResMut<PhosAssets>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
mut chunk_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ChunkMaterial>>>,
|
||||
mut next_load_state: ResMut<NextState<AssetLoadState>>,
|
||||
)
|
||||
{
|
||||
let image = images.get_mut(atlas.handle.id()).unwrap();
|
||||
|
||||
let array_layers = image.height() / image.width();
|
||||
image
|
||||
.reinterpret_stacked_2d_as_array(array_layers)
|
||||
.expect("Failed to reinterpret as array");
|
||||
|
||||
let chunk_material = chunk_materials.add(ExtendedMaterial {
|
||||
base: StandardMaterial::default(),
|
||||
extension: ChunkMaterial {
|
||||
array_texture: atlas.handle.clone(),
|
||||
},
|
||||
});
|
||||
atlas.chunk_material_handle = chunk_material;
|
||||
|
||||
next_load_state.set(AssetLoadState::LoadComplete);
|
||||
}
|
||||
|
||||
fn create_heightmap(
|
||||
mut commands: Commands,
|
||||
mut next_state: ResMut<NextState<GeneratorState>>,
|
||||
biome_painter: Res<BiomePainter>,
|
||||
)
|
||||
{
|
||||
let config = GenerationConfig {
|
||||
biome_blend: 32,
|
||||
biome_dither: 10.,
|
||||
continent_noise: NoiseConfig {
|
||||
scale: 800.,
|
||||
layers: vec![GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 100.,
|
||||
min_value: 0.,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 1,
|
||||
}],
|
||||
},
|
||||
moisture_noise: NoiseConfig {
|
||||
scale: 900.,
|
||||
layers: vec![GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 100.,
|
||||
min_value: 0.,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 1,
|
||||
}],
|
||||
},
|
||||
temperature_noise: NoiseConfig {
|
||||
scale: 700.,
|
||||
layers: vec![GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 100.,
|
||||
min_value: 0.,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 1,
|
||||
}],
|
||||
},
|
||||
sea_level: 8.5,
|
||||
border_size: 64.,
|
||||
size: UVec2::splat(16),
|
||||
// size: UVec2::splat(1),
|
||||
};
|
||||
let (heightmap, biome_map) = generate_heightmap(&config, 42069, &biome_painter);
|
||||
|
||||
commands.insert_resource(heightmap);
|
||||
commands.insert_resource(biome_map);
|
||||
commands.insert_resource(config);
|
||||
next_state.set(GeneratorState::SpawnMap);
|
||||
}
|
||||
|
||||
fn spawn_map(
|
||||
mut heightmap: ResMut<Map>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
atlas: Res<PhosAssets>,
|
||||
tile_assets: Res<Assets<TileAsset>>,
|
||||
tile_mappers: Res<Assets<TileMapperAsset>>,
|
||||
mut generator_state: ResMut<NextState<GeneratorState>>,
|
||||
cur_game_state: Res<State<MenuState>>,
|
||||
mut game_state: ResMut<NextState<MenuState>>,
|
||||
mut gameplay_state: ResMut<NextState<GameplayState>>,
|
||||
biome_painter: Res<BiomePainter>,
|
||||
)
|
||||
{
|
||||
paint_map(&mut heightmap, &biome_painter, &tile_assets, &tile_mappers);
|
||||
|
||||
//Prepare Mesh Data
|
||||
let map_size = UVec2::new(heightmap.width as u32, heightmap.height as u32);
|
||||
let chunk_meshes: Vec<_> = heightmap
|
||||
.chunks
|
||||
.par_iter()
|
||||
.map(|chunk: &Chunk| {
|
||||
let index = offset_to_index(chunk.chunk_offset, heightmap.width);
|
||||
return prepare_chunk_mesh_with_collider(
|
||||
&heightmap.get_chunk_mesh_data(index),
|
||||
heightmap.sealevel,
|
||||
chunk.chunk_offset,
|
||||
index,
|
||||
map_size,
|
||||
);
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut registry = PhosChunkRegistry::new(chunk_meshes.len());
|
||||
|
||||
//Spawn Chunks
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _spawn_span = info_span!("Spawn Chunks").entered();
|
||||
let visibility_offset = Vec3::new(
|
||||
(Chunk::SIZE / 2) as f32 * SHORT_DIAGONAL,
|
||||
0.,
|
||||
(Chunk::SIZE / 2) as f32 * 1.5,
|
||||
);
|
||||
for (chunk_mesh, water_mesh, collider, pos, index) in chunk_meshes
|
||||
{
|
||||
// let mesh_handle = meshes.a
|
||||
let water_mesh_handle = meshes.add(water_mesh);
|
||||
let chunk = commands
|
||||
.spawn((
|
||||
Mesh3d(meshes.add(chunk_mesh)),
|
||||
MeshMaterial3d(atlas.chunk_material_handle.clone()),
|
||||
Transform::from_translation(pos),
|
||||
PhosChunk::new(index),
|
||||
WaterMesh(water_mesh_handle.id()),
|
||||
RenderDistanceVisibility::default().with_offset(visibility_offset),
|
||||
collider,
|
||||
))
|
||||
.id();
|
||||
let water = commands
|
||||
.spawn((
|
||||
Mesh3d(water_mesh_handle),
|
||||
MeshMaterial3d(atlas.water_material.clone()),
|
||||
Transform::from_translation(pos),
|
||||
PhosChunk::new(index),
|
||||
NotShadowCaster,
|
||||
RenderDistanceVisibility::default().with_offset(visibility_offset),
|
||||
))
|
||||
.id();
|
||||
registry.chunks.push(chunk);
|
||||
registry.waters.push(water);
|
||||
}
|
||||
}
|
||||
|
||||
commands.insert_resource(registry);
|
||||
generator_state.set(GeneratorState::Idle);
|
||||
if cur_game_state.get() != &MenuState::InGame
|
||||
{
|
||||
game_state.set(MenuState::InGame);
|
||||
gameplay_state.set(GameplayState::PlaceHQ);
|
||||
}
|
||||
}
|
||||
|
||||
fn despawn_map(
|
||||
mut commands: Commands,
|
||||
mut heightmap: ResMut<Map>,
|
||||
mut biome_map: ResMut<BiomeMap>,
|
||||
cfg: Res<GenerationConfig>,
|
||||
chunks: Query<Entity, With<PhosChunk>>,
|
||||
mut next_state: ResMut<NextState<GeneratorState>>,
|
||||
biome_painter: Res<BiomePainter>,
|
||||
)
|
||||
{
|
||||
for chunk in chunks.iter()
|
||||
{
|
||||
commands.entity(chunk).despawn();
|
||||
}
|
||||
|
||||
(*heightmap, *biome_map) = generate_heightmap(&cfg, 4, &biome_painter);
|
||||
next_state.set(GeneratorState::SpawnMap);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod chunk_rebuild;
|
||||
pub mod map_init;
|
||||
pub mod prelude;
|
||||
pub mod terraforming_test;
|
||||
pub mod render_distance_system;
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use shared::tags::MainCamera;
|
||||
|
||||
pub struct RenderDistancePlugin;
|
||||
|
||||
impl Plugin for RenderDistancePlugin
|
||||
{
|
||||
fn build(&self, app: &mut bevy::prelude::App)
|
||||
{
|
||||
app.register_type::<RenderDistanceSettings>();
|
||||
app.add_systems(PostUpdate, render_distance_system)
|
||||
.insert_resource(RenderDistanceSettings::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
pub struct RenderDistanceSettings
|
||||
{
|
||||
pub render_distance: f32,
|
||||
}
|
||||
|
||||
impl RenderDistanceSettings
|
||||
{
|
||||
pub fn new(distance: f32) -> Self
|
||||
{
|
||||
return Self {
|
||||
render_distance: distance,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderDistanceSettings
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::new(500.)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RenderDistanceVisibility
|
||||
{
|
||||
pub offset: Vec3,
|
||||
}
|
||||
|
||||
impl RenderDistanceVisibility
|
||||
{
|
||||
pub fn with_offset(mut self, offset: Vec3) -> Self
|
||||
{
|
||||
self.offset = offset;
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderDistanceVisibility
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self { offset: Vec3::ZERO }
|
||||
}
|
||||
}
|
||||
|
||||
fn render_distance_system(
|
||||
mut objects: Query<(&Transform, &mut Visibility, &RenderDistanceVisibility)>,
|
||||
camera: Single<&Transform, With<MainCamera>>,
|
||||
settings: Res<RenderDistanceSettings>,
|
||||
)
|
||||
{
|
||||
let cam_pos = Vec3::new(camera.translation.x, 0.0, camera.translation.z);
|
||||
for (t, mut vis, r) in objects.iter_mut()
|
||||
{
|
||||
let dist = (cam_pos - (t.translation + r.offset)).length();
|
||||
if settings.render_distance < dist
|
||||
{
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
else
|
||||
{
|
||||
*vis = Visibility::Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
use bevy::{platform::collections::HashSet, prelude::*};
|
||||
use shared::{
|
||||
events::{ChunkModifiedEvent, TileModifiedEvent},
|
||||
resources::TileUnderCursor,
|
||||
states::GameplayState,
|
||||
};
|
||||
use world_generation::{prelude::Map, states::GeneratorState};
|
||||
|
||||
use crate::prelude::{PhosChunkRegistry, RebuildChunk};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct TerraFormingTestPlugin;
|
||||
|
||||
impl Plugin for TerraFormingTestPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.add_systems(
|
||||
Update,
|
||||
deform
|
||||
.run_if(in_state(GeneratorState::Idle))
|
||||
.run_if(in_state(GameplayState::Playing)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn deform(
|
||||
mut commands: Commands,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
mut heightmap: ResMut<Map>,
|
||||
chunks: Res<PhosChunkRegistry>,
|
||||
tile_under_cursor: Res<TileUnderCursor>,
|
||||
mut chunk_modified: MessageWriter<ChunkModifiedEvent>,
|
||||
mut tile_modified: MessageWriter<TileModifiedEvent>,
|
||||
)
|
||||
{
|
||||
let mut multi = 0.;
|
||||
if mouse.just_pressed(MouseButton::Left)
|
||||
{
|
||||
multi = 1.;
|
||||
}
|
||||
else if mouse.just_pressed(MouseButton::Right)
|
||||
{
|
||||
multi = -1.;
|
||||
}
|
||||
|
||||
if multi == 0.
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(contact) = tile_under_cursor.0
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let span = info_span!("Deform Mesh").entered();
|
||||
let modified_tiles = heightmap.create_crater(&contact.tile, 5, 5. * multi);
|
||||
let mut chunk_set: HashSet<usize> = HashSet::new();
|
||||
for (tile, height) in modified_tiles
|
||||
{
|
||||
let chunk = tile.to_chunk_index(heightmap.width);
|
||||
if !chunk_set.contains(&chunk)
|
||||
{
|
||||
chunk_modified.write(ChunkModifiedEvent { index: chunk });
|
||||
chunk_set.insert(chunk);
|
||||
commands.entity(chunks.chunks[chunk]).insert(RebuildChunk);
|
||||
}
|
||||
tile_modified.write(TileModifiedEvent::HeightChanged(tile, height));
|
||||
}
|
||||
// commands.entity(e).insert(RebuildChunk);
|
||||
}
|
||||
}
|
||||
@@ -1,155 +1,235 @@
|
||||
use crate::camera_system::components::PhosCamera;
|
||||
use crate::map_rendering::map_init::MapInitPlugin;
|
||||
use crate::map_rendering::render_distance_system::RenderDistancePlugin;
|
||||
use crate::ui::build_ui::BuildUIPlugin;
|
||||
use crate::utlis::tile_selection_plugin::TileSelectionPlugin;
|
||||
use crate::{camera_system::camera_plugin::PhosCameraPlugin, utlis::debug_plugin::DebugPlugin};
|
||||
use bevy::diagnostic::{EntityCountDiagnosticsPlugin, FrameTimeDiagnosticsPlugin};
|
||||
use bevy::light::CascadeShadowConfig;
|
||||
use bevy::{pbr::wireframe::WireframeConfig, prelude::*};
|
||||
use bevy_asset_loader::prelude::*;
|
||||
use bevy_rapier3d::dynamics::{Ccd, RigidBody, Velocity};
|
||||
use bevy_rapier3d::geometry::Collider;
|
||||
use crate::prelude::*;
|
||||
use crate::shader_extensions::chunk_material::ChunkMaterial;
|
||||
use bevy::asset::LoadState;
|
||||
use bevy::core_pipeline::experimental::taa::TemporalAntiAliasPlugin;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::{pbr::CascadeShadowConfig, prelude::*};
|
||||
use bevy_rapier3d::plugin::{NoUserData, RapierPhysicsPlugin};
|
||||
use shared::sets::GameplaySet;
|
||||
use shared::states::{GameplayState, MenuState};
|
||||
use shared::{despawn::DespawnPuglin, states::AssetLoadState};
|
||||
use world_generation::states::GeneratorState;
|
||||
use bevy_rapier3d::render::RapierDebugRenderPlugin;
|
||||
use camera_system::PhosCameraPlugin;
|
||||
use iyes_perf_ui::prelude::*;
|
||||
use world_generation::biome_painter::{
|
||||
BiomePainterAsset, BiomePainterLoadState, BiomePainterPlugin,
|
||||
};
|
||||
use world_generation::hex_utils::offset_to_world;
|
||||
use world_generation::tile_manager::{TileAsset, TileAssetLoadState, TileAssetPlugin, TileManager};
|
||||
use world_generation::tile_mapper::{TileMapperAsset, TileMapperAssetPlugin, TileMapperLoadState};
|
||||
use world_generation::{
|
||||
heightmap::generate_heightmap, mesh_generator::generate_chunk_mesh, prelude::*,
|
||||
};
|
||||
|
||||
pub struct PhosGamePlugin;
|
||||
|
||||
impl Plugin for PhosGamePlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.insert_state(AssetLoadState::Loading);
|
||||
app.insert_state(MenuState::Loading);
|
||||
app.insert_state(GameplayState::Waiting);
|
||||
|
||||
app.add_loading_state(
|
||||
LoadingState::new(AssetLoadState::Loading).continue_to_state(AssetLoadState::FinalizeAssets),
|
||||
);
|
||||
|
||||
app.add_plugins((
|
||||
PhosCameraPlugin,
|
||||
MapInitPlugin,
|
||||
RenderDistancePlugin,
|
||||
// BuildingPugin,
|
||||
BuildUIPlugin,
|
||||
// SimpleAnimationPlugin,
|
||||
// UnitsPlugin,
|
||||
DespawnPuglin,
|
||||
TileSelectionPlugin,
|
||||
#[cfg(feature = "editor")]
|
||||
crate::utlis::editor_plugin::EditorPlugin,
|
||||
#[cfg(debug_assertions)]
|
||||
DebugPlugin,
|
||||
));
|
||||
|
||||
configure_gameplay_set(app);
|
||||
impl Plugin for PhosGamePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(PhosCameraPlugin)
|
||||
.add_plugins(MaterialPlugin::<
|
||||
ExtendedMaterial<StandardMaterial, ChunkMaterial>,
|
||||
>::default())
|
||||
.add_plugins(TemporalAntiAliasPlugin);
|
||||
|
||||
//Systems - Startup
|
||||
app.add_systems(Startup, init_game);
|
||||
|
||||
app.add_systems(Startup, init_game)
|
||||
.add_systems(Startup, (load_textures, load_tiles, create_map).chain());
|
||||
//Systems - Update
|
||||
app.add_systems(Update, spawn_sphere);
|
||||
app.add_systems(Update, (finalize_texture, spawn_map));
|
||||
|
||||
//Perf UI
|
||||
app.add_plugins(FrameTimeDiagnosticsPlugin::default())
|
||||
.add_plugins(EntityCountDiagnosticsPlugin::default())
|
||||
.add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin);
|
||||
// .add_plugins(PerfUiPlugin);
|
||||
app.add_plugins(bevy::diagnostic::FrameTimeDiagnosticsPlugin)
|
||||
.add_plugins(bevy::diagnostic::EntityCountDiagnosticsPlugin)
|
||||
.add_plugins(bevy::diagnostic::SystemInformationDiagnosticsPlugin)
|
||||
.add_plugins(PerfUiPlugin);
|
||||
|
||||
//Assets
|
||||
app.add_plugins(TileAssetPlugin);
|
||||
app.add_plugins(TileMapperAssetPlugin);
|
||||
app.add_plugins(BiomePainterPlugin);
|
||||
//Physics
|
||||
app.add_plugins(RapierPhysicsPlugin::<NoUserData>::default());
|
||||
// app.add_plugins(RapierDebugRenderPlugin::default());
|
||||
|
||||
app.insert_resource(WireframeConfig {
|
||||
global: false,
|
||||
default_color: Srgba::hex("FF0064").unwrap().into(),
|
||||
});
|
||||
app.add_plugins(RapierPhysicsPlugin::<NoUserData>::default())
|
||||
.add_plugins(RapierDebugRenderPlugin::default());
|
||||
}
|
||||
}
|
||||
|
||||
fn configure_gameplay_set(app: &mut App)
|
||||
{
|
||||
app.configure_sets(
|
||||
Update,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
app.configure_sets(
|
||||
PreUpdate,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
app.configure_sets(
|
||||
PostUpdate,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
|
||||
app.configure_sets(
|
||||
FixedUpdate,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
app.configure_sets(
|
||||
FixedPreUpdate,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
app.configure_sets(
|
||||
FixedPostUpdate,
|
||||
GameplaySet.run_if(in_state(GeneratorState::Idle).and(in_state(MenuState::InGame))),
|
||||
);
|
||||
}
|
||||
|
||||
fn init_game(mut commands: Commands, mut materials: ResMut<Assets<StandardMaterial>>)
|
||||
{
|
||||
// commands.spawn((
|
||||
// PerfUiRoot::default(),
|
||||
// PerfUiEntryFPS::default(),
|
||||
// PerfUiEntryFPSWorst::default(),
|
||||
// PerfUiEntryFrameTime::default(),
|
||||
// PerfUiEntryFrameTimeWorst::default(),
|
||||
// ));
|
||||
|
||||
fn init_game(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
DirectionalLight {
|
||||
PerfUiRoot::default(),
|
||||
PerfUiEntryFPS::default(),
|
||||
PerfUiEntryFPSWorst::default(),
|
||||
PerfUiEntryFrameTime::default(),
|
||||
PerfUiEntryFrameTimeWorst::default(),
|
||||
));
|
||||
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
CascadeShadowConfig {
|
||||
bounds: vec![200., 400., 600., 800.],
|
||||
cascade_shadow_config: CascadeShadowConfig {
|
||||
bounds: vec![500., 1000., 2000., 5000.],
|
||||
..default()
|
||||
},
|
||||
Transform::from_xyz(500., 260.0, 500.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
));
|
||||
|
||||
let sphere_mat = StandardMaterial {
|
||||
base_color: Color::srgb(1., 1., 0.),
|
||||
transform: Transform::from_xyz(500., 260.0, 500.).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
};
|
||||
let handle = materials.add(sphere_mat);
|
||||
commands.insert_resource(SphereMat(handle));
|
||||
});
|
||||
|
||||
commands.insert_resource(PhosMap::default());
|
||||
commands.insert_resource(TileManager::default());
|
||||
}
|
||||
|
||||
fn load_textures(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let main_tex = asset_server.load("textures/world/stack.png");
|
||||
commands.insert_resource(ChunkAtlas {
|
||||
handle: main_tex.clone(),
|
||||
is_loaded: false,
|
||||
});
|
||||
}
|
||||
#[derive(Resource)]
|
||||
struct SphereMat(Handle<StandardMaterial>);
|
||||
struct Painter(Handle<BiomePainterAsset>);
|
||||
|
||||
fn spawn_sphere(
|
||||
fn load_tiles(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let handle: Handle<BiomePainterAsset> = asset_server.load("biome_painters/terra.biomes.json");
|
||||
commands.insert_resource(Painter(handle));
|
||||
}
|
||||
|
||||
fn finalize_texture(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut atlas: ResMut<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
painter: Res<Painter>,
|
||||
painter_load: Res<BiomePainterLoadState>,
|
||||
tile_load: Res<TileAssetLoadState>,
|
||||
mapper_load: Res<TileMapperLoadState>,
|
||||
) {
|
||||
if atlas.is_loaded {
|
||||
return;
|
||||
}
|
||||
|
||||
if !painter_load.is_all_loaded() || !tile_load.is_all_loaded() || !mapper_load.is_all_loaded() {
|
||||
return;
|
||||
}
|
||||
|
||||
if asset_server.load_state(atlas.handle.clone()) != LoadState::Loaded {
|
||||
return;
|
||||
}
|
||||
if asset_server.load_state(painter.0.clone()) != LoadState::Loaded {
|
||||
return;
|
||||
}
|
||||
let image = images.get_mut(&atlas.handle).unwrap();
|
||||
|
||||
let array_layers = 14;
|
||||
image.reinterpret_stacked_2d_as_array(array_layers);
|
||||
|
||||
atlas.is_loaded = true;
|
||||
map.ready = true;
|
||||
map.regenerate = true;
|
||||
}
|
||||
|
||||
fn create_map(mut commands: Commands) {
|
||||
let heightmap = generate_heightmap(
|
||||
&GenerationConfig {
|
||||
layers: vec![
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.14,
|
||||
roughness: 0.87,
|
||||
strength: 2.93,
|
||||
min_value: -0.2,
|
||||
persistence: 0.77,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.85,
|
||||
roughness: 2.,
|
||||
strength: -0.23,
|
||||
min_value: -0.,
|
||||
persistence: 1.,
|
||||
is_rigid: false,
|
||||
weight: 0.,
|
||||
weight_multi: 0.,
|
||||
layers: 4,
|
||||
first_layer_mask: false,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 2.6,
|
||||
roughness: 4.,
|
||||
strength: 10.44,
|
||||
min_value: 0.,
|
||||
persistence: 1.57,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 0.35,
|
||||
layers: 4,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
GeneratorLayer {
|
||||
base_roughness: 3.87,
|
||||
roughness: 5.8,
|
||||
strength: -1.,
|
||||
min_value: 0.,
|
||||
persistence: 0.,
|
||||
is_rigid: true,
|
||||
weight: 1.,
|
||||
weight_multi: 4.57,
|
||||
layers: 3,
|
||||
first_layer_mask: true,
|
||||
},
|
||||
],
|
||||
noise_scale: 350.,
|
||||
sea_level: 4.,
|
||||
border_size: 64.,
|
||||
size: UVec2::splat(1024 / Chunk::SIZE as u32),
|
||||
},
|
||||
4,
|
||||
);
|
||||
|
||||
commands.insert_resource(heightmap);
|
||||
}
|
||||
|
||||
fn spawn_map(
|
||||
heightmap: Res<Map>,
|
||||
mut commands: Commands,
|
||||
cam: Single<&Transform, With<PhosCamera>>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, ChunkMaterial>>>,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mat: Res<SphereMat>,
|
||||
)
|
||||
{
|
||||
if keyboard_input.just_pressed(KeyCode::KeyF) {
|
||||
atlas: Res<ChunkAtlas>,
|
||||
mut map: ResMut<PhosMap>,
|
||||
tile_assets: Res<Assets<TileAsset>>,
|
||||
tile_mappers: Res<Assets<TileMapperAsset>>,
|
||||
biome_painters: Res<Assets<BiomePainterAsset>>,
|
||||
painter: Res<Painter>,
|
||||
) {
|
||||
if !map.ready || !map.regenerate {
|
||||
return;
|
||||
}
|
||||
let b_painter = biome_painters.get(painter.0.clone());
|
||||
map.regenerate = false;
|
||||
let chunk_material = materials.add(ExtendedMaterial {
|
||||
base: StandardMaterial::default(),
|
||||
extension: ChunkMaterial {
|
||||
array_texture: atlas.handle.clone(),
|
||||
},
|
||||
});
|
||||
|
||||
for chunk in &heightmap.chunks {
|
||||
let mesh = generate_chunk_mesh(
|
||||
&chunk,
|
||||
&heightmap,
|
||||
b_painter.unwrap(),
|
||||
&tile_assets,
|
||||
&tile_mappers,
|
||||
);
|
||||
let pos = offset_to_world(chunk.chunk_offset * Chunk::SIZE as i32, 0.);
|
||||
commands.spawn((
|
||||
Mesh3d(meshes.add(Sphere::new(0.3))),
|
||||
MeshMaterial3d(mat.0.clone()),
|
||||
Transform::from_translation(cam.translation),
|
||||
Collider::ball(0.3),
|
||||
RigidBody::Dynamic,
|
||||
Ccd::enabled(),
|
||||
Velocity::linear(cam.forward() * 50.),
|
||||
MaterialMeshBundle {
|
||||
mesh: meshes.add(mesh),
|
||||
material: chunk_material.clone(),
|
||||
transform: Transform::from_translation(pos),
|
||||
..default()
|
||||
},
|
||||
PhosChunk,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,17 @@
|
||||
use bevy::asset::Handle;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::prelude::*;
|
||||
use bevy::prelude::{Component, Image, Resource};
|
||||
use bevy_asset_loader::asset_collection::AssetCollection;
|
||||
|
||||
use crate::shader_extensions::chunk_material::ChunkMaterial;
|
||||
use crate::shader_extensions::water_material::WaterMaterial;
|
||||
|
||||
#[derive(AssetCollection, Resource, Default)]
|
||||
pub struct PhosAssets
|
||||
{
|
||||
#[asset(key = "chunk_atlas")]
|
||||
#[derive(Resource)]
|
||||
pub struct ChunkAtlas {
|
||||
pub handle: Handle<Image>,
|
||||
pub chunk_material_handle: Handle<ExtendedMaterial<StandardMaterial, ChunkMaterial>>,
|
||||
pub water_material: Handle<ExtendedMaterial<StandardMaterial, WaterMaterial>>,
|
||||
pub is_loaded: bool,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PhosChunk
|
||||
{
|
||||
pub index: usize,
|
||||
}
|
||||
|
||||
impl PhosChunk
|
||||
{
|
||||
pub fn new(index: usize) -> Self
|
||||
{
|
||||
return Self { index };
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct WaterMesh(pub AssetId<Mesh>);
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct PhosChunkRegistry
|
||||
{
|
||||
pub chunks: Vec<Entity>,
|
||||
pub waters: Vec<Entity>,
|
||||
}
|
||||
|
||||
impl PhosChunkRegistry
|
||||
{
|
||||
pub fn new(size: usize) -> Self
|
||||
{
|
||||
return Self {
|
||||
chunks: Vec::with_capacity(size),
|
||||
waters: Vec::with_capacity(size),
|
||||
};
|
||||
}
|
||||
pub struct PhosMap {
|
||||
pub ready: bool,
|
||||
pub regenerate: bool,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RebuildChunk;
|
||||
pub struct PhosChunk;
|
||||
|
||||
@@ -1,76 +1,18 @@
|
||||
use bevy::asset::{Asset, Handle};
|
||||
use bevy::image::Image;
|
||||
use bevy::pbr::{Material, MaterialExtension};
|
||||
use bevy::pbr::MaterialExtension;
|
||||
use bevy::reflect::TypePath;
|
||||
use bevy::render::render_resource::AsBindGroup;
|
||||
use bevy::shader::ShaderRef;
|
||||
use world_generation::consts::{ATTRIBUTE_PACKED_VERTEX_DATA, ATTRIBUTE_VERTEX_HEIGHT};
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
use bevy::render::texture::Image;
|
||||
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct ChunkMaterial
|
||||
{
|
||||
pub struct ChunkMaterial {
|
||||
#[texture(100, dimension = "2d_array")]
|
||||
#[sampler(101)]
|
||||
pub array_texture: Handle<Image>,
|
||||
}
|
||||
|
||||
impl MaterialExtension for ChunkMaterial
|
||||
{
|
||||
fn fragment_shader() -> ShaderRef
|
||||
{
|
||||
impl MaterialExtension for ChunkMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/world/chunk.wgsl".into()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct PackedChunkMaterial
|
||||
{
|
||||
#[texture(100, dimension = "2d_array")]
|
||||
#[sampler(101)]
|
||||
pub array_texture: Handle<Image>,
|
||||
}
|
||||
|
||||
impl Material for PackedChunkMaterial
|
||||
{
|
||||
fn fragment_shader() -> ShaderRef
|
||||
{
|
||||
"shaders/world/chunk.wgsl".into()
|
||||
}
|
||||
|
||||
fn vertex_shader() -> ShaderRef
|
||||
{
|
||||
"shaders/world/chunk_packed.wgsl".into()
|
||||
}
|
||||
|
||||
fn prepass_vertex_shader() -> ShaderRef
|
||||
{
|
||||
"shaders/world/chunk_packed.wgsl".into()
|
||||
}
|
||||
|
||||
// fn deferred_vertex_shader() -> ShaderRef {
|
||||
// "shaders/world/chunk_packed.wgsl".into()
|
||||
// }
|
||||
|
||||
// fn opaque_render_method(&self) -> bevy::pbr::OpaqueRendererMethod {
|
||||
// return OpaqueRendererMethod::Auto;
|
||||
// }
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &bevy::pbr::MaterialPipeline,
|
||||
descriptor: &mut bevy::render::render_resource::RenderPipelineDescriptor,
|
||||
layout: &bevy::mesh::MeshVertexBufferLayoutRef,
|
||||
_key: bevy::pbr::MaterialPipelineKey<Self>,
|
||||
) -> bevy::ecs::error::Result<(), bevy::render::render_resource::SpecializedMeshPipelineError>
|
||||
{
|
||||
let vertex_layout = layout.0.get_layout(&[
|
||||
// Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
// Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
// Mesh::ATTRIBUTE_NORMAL.at_shader_location(2),
|
||||
ATTRIBUTE_PACKED_VERTEX_DATA.at_shader_location(7),
|
||||
ATTRIBUTE_VERTEX_HEIGHT.at_shader_location(8),
|
||||
])?;
|
||||
descriptor.vertex.buffers = vec![vertex_layout];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
pub mod chunk_material;
|
||||
pub mod water_material;
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
use bevy::asset::Asset;
|
||||
use bevy::pbr::MaterialExtension;
|
||||
use bevy::prelude::*;
|
||||
use bevy::reflect::Reflect;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderType};
|
||||
use bevy::shader::ShaderRef;
|
||||
|
||||
#[derive(Asset, Reflect, AsBindGroup, Debug, Clone, Default)]
|
||||
pub struct WaterMaterial
|
||||
{
|
||||
#[uniform(100)]
|
||||
pub settings: WaterSettings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ShaderType, Reflect)]
|
||||
pub struct WaterSettings
|
||||
{
|
||||
pub offset: f32,
|
||||
pub scale: f32,
|
||||
pub f_power: f32,
|
||||
pub deep_color: LinearRgba,
|
||||
}
|
||||
|
||||
impl Default for WaterSettings
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
offset: 0.0,
|
||||
scale: 1.0,
|
||||
f_power: 2.0,
|
||||
deep_color: default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MaterialExtension for WaterMaterial
|
||||
{
|
||||
fn fragment_shader() -> ShaderRef
|
||||
{
|
||||
"shaders/world/water.wgsl".into()
|
||||
}
|
||||
|
||||
// fn specialize(
|
||||
// pipeline: &bevy::pbr::MaterialExtensionPipeline,
|
||||
// descriptor: &mut bevy::render::render_resource::RenderPipelineDescriptor,
|
||||
// layout: &bevy::mesh::MeshVertexBufferLayoutRef,
|
||||
// key: bevy::pbr::MaterialExtensionKey<Self>,
|
||||
// ) -> std::result::Result<(), bevy::render::render_resource::SpecializedMeshPipelineError>
|
||||
// {
|
||||
// let vertex_layout = layout.0.get_layout(&[
|
||||
// // Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
// // Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
// // Mesh::ATTRIBUTE_NORMAL.at_shader_location(2),
|
||||
// ATTRIBUTE_PACKED_VERTEX_DATA.at_shader_location(7),
|
||||
// ATTRIBUTE_VERTEX_HEIGHT.at_shader_location(8),
|
||||
// ])?;
|
||||
// descriptor.vertex.buffers = vec![vertex_layout];
|
||||
// Ok(())
|
||||
// }
|
||||
}
|
||||
43
game/main/src/shaders/chunk.rs
Normal file
43
game/main/src/shaders/chunk.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use bevy::{
|
||||
asset::{Asset, Handle},
|
||||
pbr::{MaterialExtension, MaterialExtensionKey, MaterialExtensionPipeline},
|
||||
prelude::*,
|
||||
reflect::TypePath,
|
||||
render::{
|
||||
mesh::MeshVertexBufferLayout,
|
||||
render_resource::{
|
||||
AsBindGroup, RenderPipelineDescriptor, ShaderRef, SpecializedMeshPipelineError,
|
||||
},
|
||||
texture::Image,
|
||||
},
|
||||
};
|
||||
use world_generation::prelude::ATTRIBUTE_TEXTURE_INDEX;
|
||||
|
||||
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
|
||||
pub struct ChunkMaterial {
|
||||
#[texture(100, dimension = "2d_array")]
|
||||
#[sampler(101)]
|
||||
pub array_texture: Handle<Image>,
|
||||
}
|
||||
|
||||
impl MaterialExtension for ChunkMaterial {
|
||||
fn fragment_shader() -> ShaderRef {
|
||||
"shaders/world/chunk.wgsl".into()
|
||||
}
|
||||
|
||||
fn specialize(
|
||||
_pipeline: &MaterialExtensionPipeline,
|
||||
descriptor: &mut RenderPipelineDescriptor,
|
||||
layout: &MeshVertexBufferLayout,
|
||||
_key: MaterialExtensionKey<Self>,
|
||||
) -> Result<(), SpecializedMeshPipelineError> {
|
||||
let vertex_layout = layout.get_layout(&[
|
||||
Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
||||
Mesh::ATTRIBUTE_UV_0.at_shader_location(1),
|
||||
Mesh::ATTRIBUTE_NORMAL.at_shader_location(2),
|
||||
ATTRIBUTE_TEXTURE_INDEX.at_shader_location(7),
|
||||
])?;
|
||||
descriptor.vertex.buffers = vec![vertex_layout];
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
1
game/main/src/shaders/mod.rs
Normal file
1
game/main/src/shaders/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod chunk;
|
||||
@@ -1,223 +0,0 @@
|
||||
use bevy::{
|
||||
camera::{visibility::RenderLayers, CameraOutputMode},
|
||||
prelude::*,
|
||||
render::render_resource::BlendState,
|
||||
};
|
||||
use shared::states::AssetLoadState;
|
||||
|
||||
use crate::ui::states::BuildUIState;
|
||||
pub struct BuildUIPlugin;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct BuildUIItem;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[require(BuildUIItem)]
|
||||
struct BuildUIMenuItem;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
struct BuildMenuRoot;
|
||||
|
||||
#[derive(Resource, Reflect, Debug, Default)]
|
||||
struct BuildUIInfo
|
||||
{
|
||||
menu: BuildUIMenu,
|
||||
}
|
||||
|
||||
#[derive(Default, Reflect, Debug)]
|
||||
enum BuildUIMenu
|
||||
{
|
||||
#[default]
|
||||
Structure,
|
||||
Defense,
|
||||
}
|
||||
|
||||
impl Plugin for BuildUIPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.add_systems(Startup, setup_cameras);
|
||||
app.init_resource::<BuildUIInfo>();
|
||||
app.insert_state(BuildUIState::Init);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
spawn_ui.run_if(in_state(AssetLoadState::LoadComplete).and(in_state(BuildUIState::Init))),
|
||||
draw_menu_ui.run_if(in_state(AssetLoadState::LoadComplete).and(in_state(BuildUIState::DrawMenu))),
|
||||
),
|
||||
);
|
||||
app.add_systems(PostUpdate, cleanup_ui.run_if(in_state(BuildUIState::Cleanup)));
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_cameras(mut commands: Commands)
|
||||
{
|
||||
commands
|
||||
.spawn((
|
||||
Camera2d,
|
||||
Camera {
|
||||
order: 1,
|
||||
clear_color: ClearColorConfig::None,
|
||||
msaa_writeback: MsaaWriteback::Always,
|
||||
output_mode: CameraOutputMode::Write {
|
||||
blend_state: Some(BlendState::ALPHA_BLENDING),
|
||||
clear_color: ClearColorConfig::None,
|
||||
},
|
||||
..default()
|
||||
},
|
||||
IsDefaultUiCamera,
|
||||
))
|
||||
.insert(RenderLayers::layer(1))
|
||||
.insert(Msaa::Off);
|
||||
}
|
||||
|
||||
fn spawn_ui(mut commands: Commands, mut next_state: ResMut<NextState<BuildUIState>>)
|
||||
{
|
||||
commands
|
||||
.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.),
|
||||
height: Val::Percent(100.),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::End,
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
Name::new("Build UI Root"),
|
||||
BuildUIItem,
|
||||
))
|
||||
// .insert(PickingBehavior::IGNORE)
|
||||
.with_children(|build_root| {
|
||||
build_root
|
||||
.spawn((
|
||||
Name::new("Build UI"),
|
||||
Node {
|
||||
width: Val::Px(500.),
|
||||
height: Val::Px(100.),
|
||||
justify_content: JustifyContent::Stretch,
|
||||
flex_direction: FlexDirection::Column,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(LinearRgba::GREEN.into()),
|
||||
))
|
||||
.with_children(|build_ui| {
|
||||
build_ui.spawn((
|
||||
Name::new("Menu Root"),
|
||||
BuildMenuRoot,
|
||||
Node {
|
||||
width: Val::Percent(100.),
|
||||
height: Val::Px(70.),
|
||||
column_gap: Val::Px(5.),
|
||||
padding: UiRect::all(Val::Px(2.)),
|
||||
flex_direction: FlexDirection::Row,
|
||||
overflow: Overflow::scroll_x(),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
build_ui
|
||||
.spawn((
|
||||
Name::new("Toolbar"),
|
||||
Node {
|
||||
width: Val::Percent(100.),
|
||||
height: Val::Px(30.),
|
||||
column_gap: Val::Px(5.),
|
||||
padding: UiRect::horizontal(Val::Px(10.)),
|
||||
justify_content: JustifyContent::Stretch,
|
||||
align_self: AlignSelf::End,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(LinearRgba::BLUE.into()),
|
||||
))
|
||||
.with_children(|toolbar| {
|
||||
for i in 0..6
|
||||
{
|
||||
toolbar.spawn((
|
||||
Name::new(format!("Button {}", i)),
|
||||
Button,
|
||||
Node {
|
||||
height: Val::Percent(100.),
|
||||
width: Val::Percent(100.),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(LinearRgba::WHITE.into()),
|
||||
children![(
|
||||
Text::new(format!("Button {}", i)),
|
||||
TextFont {
|
||||
font_size: 15.,
|
||||
..default()
|
||||
},
|
||||
TextColor(LinearRgba::BLACK.into()),
|
||||
TextShadow {
|
||||
offset: Vec2::splat(2.),
|
||||
..default()
|
||||
}
|
||||
)],
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
next_state.set(BuildUIState::DrawMenu);
|
||||
}
|
||||
|
||||
fn draw_menu_ui(
|
||||
mut commands: Commands,
|
||||
menu_root: Single<Entity, With<BuildMenuRoot>>,
|
||||
menu_items: Query<Entity, With<BuildUIMenuItem>>,
|
||||
menu_info: Res<BuildUIInfo>,
|
||||
mut next: ResMut<NextState<BuildUIState>>,
|
||||
)
|
||||
{
|
||||
for entity in menu_items.iter()
|
||||
{
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
info!("Draw Menu");
|
||||
match menu_info.menu
|
||||
{
|
||||
BuildUIMenu::Structure => draw_structure_ui(commands, menu_root.into_inner()),
|
||||
BuildUIMenu::Defense => (),
|
||||
}
|
||||
next.set(BuildUIState::Update);
|
||||
}
|
||||
|
||||
fn draw_structure_ui(mut commands: Commands, root: Entity)
|
||||
{
|
||||
commands.entity(root).with_children(|root| {
|
||||
for i in 0..10
|
||||
{
|
||||
root.spawn((
|
||||
BuildUIMenuItem,
|
||||
Node {
|
||||
height: Val::Percent(100.),
|
||||
width: Val::Px(100.),
|
||||
display: Display::Grid,
|
||||
grid_template_rows: vec![RepeatedGridTrack::px(1, 100.), RepeatedGridTrack::fr(1, 1.)],
|
||||
..default()
|
||||
},
|
||||
children![
|
||||
(
|
||||
Node {
|
||||
height: Val::Px(100.),
|
||||
width: Val::Px(100.),
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(LinearRgba::RED.into())
|
||||
),
|
||||
(Text::new(format!("Icon {}", i))),
|
||||
],
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn cleanup_ui(mut commands: Commands, ui_items: Query<Entity, With<BuildUIItem>>)
|
||||
{
|
||||
for entity in ui_items.iter()
|
||||
{
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub mod build_ui;
|
||||
pub mod states;
|
||||
@@ -1,10 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum BuildUIState
|
||||
{
|
||||
Init,
|
||||
DrawMenu,
|
||||
Update,
|
||||
Cleanup,
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
#[cfg(feature = "tracing")]
|
||||
use bevy::log::*;
|
||||
use bevy::{
|
||||
asset::Assets,
|
||||
ecs::system::Res,
|
||||
math::{IVec2, UVec2, Vec3},
|
||||
mesh::Mesh,
|
||||
};
|
||||
use bevy_rapier3d::geometry::{Collider, TriMeshFlags};
|
||||
use hex::prelude::*;
|
||||
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
|
||||
use world_generation::{
|
||||
biome_painter::BiomePainter,
|
||||
generators::{
|
||||
chunk_colliders::generate_chunk_collider,
|
||||
mesh_generator::{generate_chunk_mesh, generate_chunk_water_mesh},
|
||||
},
|
||||
prelude::{Map, MeshChunkData},
|
||||
tile_manager::TileAsset,
|
||||
tile_mapper::TileMapperAsset,
|
||||
};
|
||||
|
||||
pub fn paint_map(
|
||||
map: &mut Map,
|
||||
painter: &BiomePainter,
|
||||
tiles: &Res<Assets<TileAsset>>,
|
||||
mappers: &Res<Assets<TileMapperAsset>>,
|
||||
)
|
||||
{
|
||||
map.chunks.par_iter_mut().for_each(|chunk: &mut Chunk| {
|
||||
paint_chunk(chunk, painter, tiles, mappers);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn paint_chunk(
|
||||
chunk: &mut Chunk,
|
||||
painter: &BiomePainter,
|
||||
tiles: &Res<Assets<TileAsset>>,
|
||||
mappers: &Res<Assets<TileMapperAsset>>,
|
||||
)
|
||||
{
|
||||
for z in 0..Chunk::SIZE
|
||||
{
|
||||
for x in 0..Chunk::SIZE
|
||||
{
|
||||
let idx = x + z * Chunk::SIZE;
|
||||
let height = chunk.heights[idx];
|
||||
let biome_id = chunk.biome_id[idx];
|
||||
let biome = &painter.biomes[biome_id];
|
||||
let mapper = mappers.get(biome.tile_mapper.id());
|
||||
let tile_handle = mapper.unwrap().sample_tile(height);
|
||||
let tile = tiles.get(tile_handle).unwrap();
|
||||
chunk.textures[idx] = [tile.texture_id, tile.side_texture_id];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_chunk_mesh(
|
||||
chunk: &MeshChunkData,
|
||||
sealevel: f32,
|
||||
chunk_offset: IVec2,
|
||||
chunk_index: usize,
|
||||
map_size: UVec2,
|
||||
) -> (Mesh, Mesh, (Vec<Vec3>, Vec<[u32; 3]>), Vec3, usize)
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _gen_mesh = info_span!("Generate Chunk").entered();
|
||||
let chunk_mesh = generate_chunk_mesh(chunk);
|
||||
let water_mesh = generate_chunk_water_mesh(chunk, sealevel, map_size.x as usize, map_size.y as usize);
|
||||
let col_data = generate_chunk_collider(chunk);
|
||||
|
||||
return (
|
||||
chunk_mesh,
|
||||
water_mesh,
|
||||
col_data,
|
||||
offset_to_world(chunk_offset * Chunk::SIZE as i32, 0.),
|
||||
chunk_index,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn prepare_chunk_mesh_with_collider(
|
||||
chunk: &MeshChunkData,
|
||||
sealevel: f32,
|
||||
chunk_offset: IVec2,
|
||||
chunk_index: usize,
|
||||
map_size: UVec2,
|
||||
) -> (Mesh, Mesh, Collider, Vec3, usize)
|
||||
{
|
||||
let (chunk_mesh, water_mesh, (col_verts, col_indicies), pos, index) =
|
||||
prepare_chunk_mesh(chunk, sealevel, chunk_offset, chunk_index, map_size);
|
||||
let collider: Collider;
|
||||
{
|
||||
#[cfg(feature = "tracing")]
|
||||
let _collider_span = info_span!("Create Collider Trimesh").entered();
|
||||
collider = Collider::trimesh_with_flags(col_verts, col_indicies, TriMeshFlags::DELETE_DUPLICATE_TRIANGLES)
|
||||
.expect("Failed to generate chunk collision mesh");
|
||||
}
|
||||
return (chunk_mesh, water_mesh, collider, pos, index);
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use shared::states::GameplayState;
|
||||
use shared::{resources::TileUnderCursor, sets::GameplaySet};
|
||||
use world_generation::{
|
||||
consts::{HEX_CORNERS, WATER_HEX_CORNERS},
|
||||
prelude::Map,
|
||||
states::GeneratorState,
|
||||
};
|
||||
|
||||
use crate::camera_system::components::{PhosCamera, PhosOrbitCamera};
|
||||
|
||||
pub struct DebugPlugin;
|
||||
|
||||
impl Plugin for DebugPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.insert_state(DebugState::Base);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
show_tile_heights
|
||||
.run_if(in_state(GeneratorState::Idle))
|
||||
.run_if(not(in_state(DebugState::None))),
|
||||
);
|
||||
|
||||
app.add_systems(
|
||||
Update,
|
||||
verbose_data
|
||||
.run_if(in_state(GeneratorState::Idle))
|
||||
.run_if(in_state(DebugState::Verbose)),
|
||||
);
|
||||
|
||||
app.add_systems(Update, camera_debug.in_set(GameplaySet));
|
||||
app.add_systems(Update, regenerate_map.run_if(in_state(GeneratorState::Idle)));
|
||||
|
||||
app.insert_resource(Shape(Polyline3d::new([
|
||||
HEX_CORNERS[0],
|
||||
HEX_CORNERS[1],
|
||||
HEX_CORNERS[2],
|
||||
HEX_CORNERS[3],
|
||||
HEX_CORNERS[4],
|
||||
HEX_CORNERS[5],
|
||||
HEX_CORNERS[0],
|
||||
])));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct Shape(pub Polyline3d);
|
||||
|
||||
#[derive(States, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum DebugState
|
||||
{
|
||||
Base,
|
||||
None,
|
||||
Verbose,
|
||||
}
|
||||
|
||||
fn regenerate_map(
|
||||
mut generator_state: ResMut<NextState<GeneratorState>>,
|
||||
mut gameplay_state: ResMut<NextState<GameplayState>>,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
)
|
||||
{
|
||||
if input.just_pressed(KeyCode::KeyR)
|
||||
{
|
||||
generator_state.set(GeneratorState::Regenerate);
|
||||
gameplay_state.set(GameplayState::PlaceHQ);
|
||||
}
|
||||
}
|
||||
|
||||
fn show_tile_heights(map: Res<Map>, mut gizmos: Gizmos, shape: Res<Shape>, tile_under_cursor: Res<TileUnderCursor>)
|
||||
{
|
||||
if let Some(contact) = tile_under_cursor.0
|
||||
{
|
||||
let height = map.sample_height(&contact.tile);
|
||||
gizmos.primitive_3d(&shape.0, contact.tile.to_world(height + 0.01), Color::WHITE);
|
||||
|
||||
gizmos.line(contact.point, contact.point + Vec3::X, LinearRgba::RED);
|
||||
gizmos.line(contact.point, contact.point + Vec3::Y, LinearRgba::GREEN);
|
||||
gizmos.line(contact.point, contact.point + Vec3::Z, LinearRgba::BLUE);
|
||||
|
||||
// show_water_corners(contact.tile.to_world(height + 1.0), &mut gizmos);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn show_water_corners(pos: Vec3, gizmos: &mut Gizmos)
|
||||
{
|
||||
for i in 0..WATER_HEX_CORNERS.len()
|
||||
{
|
||||
let p = pos + WATER_HEX_CORNERS[i];
|
||||
let p2 = pos + WATER_HEX_CORNERS[(i + 1) % WATER_HEX_CORNERS.len()];
|
||||
|
||||
gizmos.line(p, p2, LinearRgba::RED);
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_debug(cam_query: Single<(&PhosCamera, &PhosOrbitCamera)>, mut gizmos: Gizmos)
|
||||
{
|
||||
let (_config, orbit) = cam_query.into_inner();
|
||||
|
||||
gizmos.sphere(orbit.target, 0.3, LinearRgba::RED);
|
||||
let cam_proxy = orbit.target - (orbit.forward * 10.0);
|
||||
gizmos.ray(orbit.target, orbit.forward * 10.0, LinearRgba::rgb(1.0, 0.0, 1.0));
|
||||
|
||||
gizmos.circle(cam_proxy, 0.3, LinearRgba::rgb(1.0, 1.0, 0.0));
|
||||
}
|
||||
|
||||
fn verbose_data() {}
|
||||
@@ -1,180 +0,0 @@
|
||||
use bevy::asset::RenderAssetUsages;
|
||||
use bevy::prelude::*;
|
||||
use bevy_inspector_egui::bevy_egui::{EguiContexts, EguiTextureHandle};
|
||||
use bevy_inspector_egui::egui::{self};
|
||||
use image::{ImageBuffer, Rgba};
|
||||
use world_generation::biome_asset::BiomeAsset;
|
||||
use world_generation::biome_painter::BiomePainterAsset;
|
||||
use world_generation::map::biome_map::BiomeMap;
|
||||
use world_generation::map::map_utils::{render_biome_map, render_biome_noise_map};
|
||||
use world_generation::{map::map_utils::render_map, prelude::Map, states::GeneratorState};
|
||||
|
||||
pub struct EditorPlugin;
|
||||
|
||||
impl Plugin for EditorPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.init_resource::<UIState>();
|
||||
|
||||
app.add_systems(PostUpdate, prepare_image.run_if(in_state(GeneratorState::SpawnMap)));
|
||||
app.add_systems(
|
||||
Update,
|
||||
(render_map_ui, update_map_render, asset_reloaded).run_if(in_state(GeneratorState::Idle)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct MapImage(pub Handle<Image>);
|
||||
|
||||
pub fn prepare_image(mut images: ResMut<Assets<Image>>, heightmap: Res<Map>, mut commands: Commands)
|
||||
{
|
||||
let image = render_map(&heightmap, 0.1);
|
||||
let handle = images.add(Image::from_dynamic(image.into(), true, RenderAssetUsages::RENDER_WORLD));
|
||||
|
||||
commands.insert_resource(MapImage(handle));
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct UIState
|
||||
{
|
||||
pub is_open: bool,
|
||||
pub target_map_type: MapDisplayType,
|
||||
pub cur_map_type: MapDisplayType,
|
||||
}
|
||||
|
||||
impl Default for UIState
|
||||
{
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
is_open: true,
|
||||
target_map_type: default(),
|
||||
cur_map_type: default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
|
||||
enum MapDisplayType
|
||||
{
|
||||
#[default]
|
||||
HeightMap,
|
||||
Biomes,
|
||||
BiomeNoise,
|
||||
BiomeNoiseTemp,
|
||||
BiomeNoiseContinent,
|
||||
BiomeNoiseMoisture,
|
||||
}
|
||||
|
||||
fn asset_reloaded(
|
||||
mut asset_events: MessageReader<AssetEvent<BiomeAsset>>,
|
||||
biomes: Res<Assets<BiomeAsset>>,
|
||||
biome_painter: Res<BiomePainterAsset>,
|
||||
mut commands: Commands,
|
||||
)
|
||||
{
|
||||
let mut rebuild = false;
|
||||
for event in asset_events.read()
|
||||
{
|
||||
match event
|
||||
{
|
||||
AssetEvent::Modified { .. } => rebuild = true,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if rebuild
|
||||
{
|
||||
let painter = biome_painter.build(&biomes);
|
||||
commands.insert_resource(painter);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_map_ui(
|
||||
image: Res<MapImage>,
|
||||
heightmap: Res<Map>,
|
||||
biome_map: Res<BiomeMap>,
|
||||
mut contexts: EguiContexts,
|
||||
mut state: ResMut<UIState>,
|
||||
)
|
||||
{
|
||||
let id = contexts.add_image(EguiTextureHandle::Weak(image.0.id()));
|
||||
let mut map_type = state.target_map_type;
|
||||
let ctx = contexts.ctx_mut().expect("Failed to get egui context");
|
||||
egui::Window::new("Map").open(&mut state.is_open).show(ctx, |ui| {
|
||||
ui.label("Map Test");
|
||||
egui::ComboBox::from_label("Display Type")
|
||||
.selected_text(format!("{:?}", map_type))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut map_type, MapDisplayType::HeightMap, "Heightmap");
|
||||
ui.selectable_value(&mut map_type, MapDisplayType::Biomes, "Biomes");
|
||||
ui.selectable_value(&mut map_type, MapDisplayType::BiomeNoise, "Biome Noise");
|
||||
ui.selectable_value(
|
||||
&mut map_type,
|
||||
MapDisplayType::BiomeNoiseTemp,
|
||||
"Biome Noise: Tempurature",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut map_type,
|
||||
MapDisplayType::BiomeNoiseContinent,
|
||||
"Biome Noise: Continent",
|
||||
);
|
||||
ui.selectable_value(
|
||||
&mut map_type,
|
||||
MapDisplayType::BiomeNoiseMoisture,
|
||||
"Biome Noise: Moisture",
|
||||
);
|
||||
});
|
||||
|
||||
ui.add(egui::widgets::Image::new(egui::load::SizedTexture::new(
|
||||
id,
|
||||
[512.0, 512.0],
|
||||
)));
|
||||
|
||||
if ui.button("Save Image").clicked()
|
||||
{
|
||||
let img = get_map_image(&heightmap, &biome_map, map_type);
|
||||
_ = img.save(format!("{:?}.png", map_type));
|
||||
}
|
||||
});
|
||||
|
||||
state.target_map_type = map_type;
|
||||
}
|
||||
|
||||
fn update_map_render(
|
||||
mut state: ResMut<UIState>,
|
||||
mut images: ResMut<Assets<Image>>,
|
||||
heightmap: Res<Map>,
|
||||
biome_map: Res<BiomeMap>,
|
||||
image: Res<MapImage>,
|
||||
)
|
||||
{
|
||||
if state.cur_map_type == state.target_map_type
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let result = get_map_image(&heightmap, &biome_map, state.target_map_type);
|
||||
images
|
||||
.insert(
|
||||
image.0.id(),
|
||||
Image::from_dynamic(result.into(), true, RenderAssetUsages::RENDER_WORLD),
|
||||
)
|
||||
.expect("Failed to update map image");
|
||||
|
||||
state.cur_map_type = state.target_map_type;
|
||||
}
|
||||
|
||||
fn get_map_image(heightmap: &Map, biome_map: &BiomeMap, map_type: MapDisplayType) -> ImageBuffer<Rgba<u8>, Vec<u8>>
|
||||
{
|
||||
return match map_type
|
||||
{
|
||||
MapDisplayType::HeightMap => render_map(&heightmap, 0.1),
|
||||
MapDisplayType::Biomes => render_biome_map(&heightmap, &biome_map),
|
||||
MapDisplayType::BiomeNoise => render_biome_noise_map(&biome_map, Vec3::ONE),
|
||||
MapDisplayType::BiomeNoiseTemp => render_biome_noise_map(&biome_map, Vec3::X),
|
||||
MapDisplayType::BiomeNoiseContinent => render_biome_noise_map(&biome_map, Vec3::Y),
|
||||
MapDisplayType::BiomeNoiseMoisture => render_biome_noise_map(&biome_map, Vec3::Z),
|
||||
};
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
pub mod chunk_utils;
|
||||
pub mod debug_plugin;
|
||||
#[cfg(feature = "editor")]
|
||||
pub mod editor_plugin;
|
||||
pub mod tile_selection_plugin;
|
||||
@@ -1,76 +0,0 @@
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
use bevy_rapier3d::{plugin::ReadRapierContext, prelude::QueryFilter};
|
||||
use hex::prelude::*;
|
||||
use shared::{
|
||||
resources::{TileContact, TileUnderCursor},
|
||||
tags::MainCamera,
|
||||
};
|
||||
use world_generation::{prelude::Map, states::GeneratorState};
|
||||
|
||||
pub struct TileSelectionPlugin;
|
||||
|
||||
impl Plugin for TileSelectionPlugin
|
||||
{
|
||||
fn build(&self, app: &mut App)
|
||||
{
|
||||
app.init_resource::<TileUnderCursor>();
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
update_tile_under_cursor.run_if(in_state(GeneratorState::Idle)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tile_under_cursor(
|
||||
cam_query: Single<(&GlobalTransform, &Camera), With<MainCamera>>,
|
||||
window: Single<&Window, With<PrimaryWindow>>,
|
||||
rapier: ReadRapierContext,
|
||||
map: Res<Map>,
|
||||
mut tile_under_cursor: ResMut<TileUnderCursor>,
|
||||
)
|
||||
{
|
||||
let (cam_transform, camera) = cam_query.into_inner();
|
||||
let Some(cursor_pos) = window.cursor_position()
|
||||
else
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(cam_ray) = camera.viewport_to_world(cam_transform, cursor_pos)
|
||||
else
|
||||
{
|
||||
return;
|
||||
};
|
||||
|
||||
let ctx = rapier.single().expect("Failed to get rapier read context");
|
||||
|
||||
let collision = ctx.cast_ray(
|
||||
cam_ray.origin,
|
||||
cam_ray.direction.into(),
|
||||
500.,
|
||||
true,
|
||||
QueryFilter::only_fixed(),
|
||||
);
|
||||
|
||||
if let Some((_e, dist)) = collision
|
||||
{
|
||||
let contact_point = cam_ray.get_point(dist);
|
||||
let contact_coord = HexCoord::from_world_pos(contact_point);
|
||||
//todo: handle correct tile detection when contacting a tile from the side
|
||||
if !map.is_in_bounds(&contact_coord)
|
||||
{
|
||||
tile_under_cursor.0 = None;
|
||||
return;
|
||||
}
|
||||
let surface = map.sample_height(&contact_coord);
|
||||
tile_under_cursor.0 = Some(TileContact::new(
|
||||
contact_coord,
|
||||
contact_point,
|
||||
contact_coord.to_world(surface),
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
tile_under_cursor.0 = None;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
[package]
|
||||
name = "resources"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.18.0"
|
||||
world_generation = { path = "../../engine/world_generation" }
|
||||
shared = { path = "../shared" }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
asset_loader = { path = "../../engine/asset_loader" }
|
||||
serde_json = "1.0.149"
|
||||
ron = "0.12.0"
|
||||
bevy_asset_loader = { version = "0.25.0", features = [
|
||||
"standard_dynamic_assets",
|
||||
"3d",
|
||||
] }
|
||||
|
||||
[features]
|
||||
tracing = []
|
||||
@@ -1,16 +0,0 @@
|
||||
pub mod resource_asset;
|
||||
|
||||
pub fn add(left: usize, right: usize) -> usize {
|
||||
left + right
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let result = add(2, 2);
|
||||
assert_eq!(result, 4);
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
use asset_loader::create_asset_loader;
|
||||
use bevy::prelude::*;
|
||||
use bevy_asset_loader::asset_collection::AssetCollection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shared::Tier;
|
||||
|
||||
#[derive(Asset, TypePath, Debug, Serialize, Deserialize)]
|
||||
pub struct ResourceAsset {
|
||||
pub identifier: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub sprite_id: usize,
|
||||
pub tier: Tier,
|
||||
}
|
||||
|
||||
create_asset_loader!(
|
||||
ResourceAssetPlugin,
|
||||
ResourceAssetLoader,
|
||||
ResourceAsset,
|
||||
&["res", "res.ron"],
|
||||
;?
|
||||
);
|
||||
|
||||
#[derive(Resource, AssetCollection)]
|
||||
pub struct ResourceDatabase {
|
||||
#[asset(key = "resources", collection(typed))]
|
||||
pub units: Vec<Handle<ResourceAsset>>,
|
||||
}
|
||||
|
||||
impl ResourceDatabase {
|
||||
pub fn create_lookup(&self, assets: &Assets<ResourceAsset>) -> ResourceLookup {
|
||||
let mut identifiers = Vec::with_capacity(self.units.len());
|
||||
for handle in &self.units {
|
||||
if let Some(asset) = assets.get(handle.id()) {
|
||||
identifiers.push(asset.identifier.clone());
|
||||
}
|
||||
}
|
||||
return ResourceLookup {
|
||||
handles: self.units.clone(),
|
||||
identifiers,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ResourceLookup {
|
||||
pub handles: Vec<Handle<ResourceAsset>>,
|
||||
pub identifiers: Vec<String>,
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
[package]
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
bevy = "0.18.0"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
world_generation = { path = "../../engine/world_generation" }
|
||||
hex = { path = "../../engine/hex" }
|
||||
|
||||
|
||||
[features]
|
||||
tracing = []
|
||||
@@ -1,18 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::prefab_defination::RotationAnimation;
|
||||
|
||||
pub struct SimpleAnimationPlugin;
|
||||
|
||||
impl Plugin for SimpleAnimationPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Update, rotate);
|
||||
}
|
||||
}
|
||||
|
||||
fn rotate(mut query: Query<(&mut Transform, &RotationAnimation)>, time: Res<Time>) {
|
||||
for (mut transform, rot) in query.iter_mut() {
|
||||
let cur_rot = transform.rotation;
|
||||
transform.rotation = cur_rot * Quat::from_axis_angle(rot.axis, rot.speed.to_radians() * time.delta_secs());
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
use bevy::reflect::Reflect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Default, Reflect, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BuildingIdentifier(pub usize);
|
||||
|
||||
impl From<i32> for BuildingIdentifier {
|
||||
fn from(value: i32) -> Self {
|
||||
return BuildingIdentifier(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for BuildingIdentifier {
|
||||
fn from(value: u32) -> Self {
|
||||
return BuildingIdentifier(value as usize);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for BuildingIdentifier {
|
||||
fn from(value: usize) -> Self {
|
||||
return BuildingIdentifier(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<usize> for BuildingIdentifier {
|
||||
fn into(self) -> usize {
|
||||
return self.0;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
use bevy::{ecs::system::EntityCommands, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::prefab_defination::AnimationComponent;
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct ComponentDefination
|
||||
{
|
||||
pub path: String,
|
||||
pub animations: Vec<AnimationComponent>,
|
||||
}
|
||||
|
||||
impl ComponentDefination
|
||||
{
|
||||
pub fn apply(&self, commands: &mut EntityCommands)
|
||||
{
|
||||
for c in &self.animations {
|
||||
c.apply(commands);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
use bevy::prelude::*;
|
||||
use hex::prelude::*;
|
||||
|
||||
#[derive(Default, Debug, Reflect)]
|
||||
pub struct CoordsCollection
|
||||
{
|
||||
points: Vec<IVec2>,
|
||||
origin: IVec2,
|
||||
translation: IVec2,
|
||||
rotation: i32,
|
||||
}
|
||||
|
||||
impl CoordsCollection
|
||||
{
|
||||
pub fn from_hex(coords: Vec<HexCoord>) -> Self
|
||||
{
|
||||
CoordsCollection {
|
||||
points: coords.iter().map(|c| c.hex.xy()).collect(),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_points(points: Vec<IVec2>) -> Self
|
||||
{
|
||||
CoordsCollection { points, ..default() }
|
||||
}
|
||||
|
||||
pub fn with_translation(mut self, translation: &HexCoord) -> Self
|
||||
{
|
||||
self.translation = translation.hex.xy();
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn with_translation_vec(mut self, translation: IVec2) -> Self
|
||||
{
|
||||
self.translation = translation;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn with_origin(mut self, orign: &HexCoord) -> Self
|
||||
{
|
||||
self.origin = orign.hex.xy();
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn with_rotation(mut self, rotation: i32) -> Self
|
||||
{
|
||||
self.rotation = rotation;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn get_coords(&self) -> Vec<HexCoord>
|
||||
{
|
||||
let center = HexCoord::from_axial(self.origin);
|
||||
return self
|
||||
.points
|
||||
.iter()
|
||||
.map(|p| HexCoord::from_axial(p + self.origin).rotate_around(¢er, self.rotation))
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Vec<HexCoord>> for CoordsCollection
|
||||
{
|
||||
fn into(self) -> Vec<HexCoord>
|
||||
{
|
||||
self.get_coords()
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
use crate::states::MenuState;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct DespawnPuglin;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct DespawnAt(f32);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct DespawnAfter(Timer);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Despawn;
|
||||
|
||||
impl Plugin for DespawnPuglin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(PostUpdate, despawn_at);
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
(despawn, despawn_after).run_if(not(in_state(MenuState::Paused))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn despawn_at(mut commands: Commands, time: Res<Time>, entities: Query<(Entity, &DespawnAt), Without<DespawnAfter>>) {
|
||||
for (entity, at) in entities.iter() {
|
||||
let d = at.0 - time.elapsed_secs();
|
||||
commands
|
||||
.entity(entity)
|
||||
.insert(DespawnAfter(Timer::from_seconds(d, TimerMode::Once)));
|
||||
}
|
||||
}
|
||||
|
||||
fn despawn_after(mut commands: Commands, mut entities: Query<(&mut DespawnAfter, Entity)>, time: Res<Time>) {
|
||||
for (mut after, entity) in &mut entities.iter_mut() {
|
||||
after.0.tick(time.delta());
|
||||
if after.0.is_finished() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn despawn(mut commands: Commands, entities: Query<Entity, With<Despawn>>) {
|
||||
for entity in entities.iter() {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user