Compare commits
No commits in common. "master" and "0.2.0" have entirely different histories.
4669
Cargo.lock
generated
73
Cargo.toml
@ -1,23 +1,23 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "forestiles"
|
name = "forestiles"
|
||||||
version = "0.6.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "forestiles"
|
name = "forestiles"
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
crate-type = ["cdylib"]
|
crate-type=[
|
||||||
|
"staticlib",
|
||||||
|
"cdylib",
|
||||||
|
"rlib"
|
||||||
|
]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
name = "forestiles"
|
name = "forestiles"
|
||||||
|
|
||||||
[features]
|
|
||||||
debug = ["dep:bevy_editor_pls"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# git = "https://git.arkitu.fr/forestia/bevy.git"
|
bevy = { version = "0.15", default-features = false, features = [
|
||||||
bevy = { git = "https://git.arkitu.fr/forestia/bevy.git", default-features = false, features = [
|
|
||||||
"android-native-activity",
|
"android-native-activity",
|
||||||
"android_shared_stdcxx",
|
"android_shared_stdcxx",
|
||||||
"bevy_color",
|
"bevy_color",
|
||||||
@ -27,7 +27,6 @@ bevy = { git = "https://git.arkitu.fr/forestia/bevy.git", default-features = fal
|
|||||||
"bevy_text",
|
"bevy_text",
|
||||||
"bevy_ui",
|
"bevy_ui",
|
||||||
"bevy_ui_picking_backend",
|
"bevy_ui_picking_backend",
|
||||||
"bevy_mesh_picking_backend",
|
|
||||||
"bevy_window",
|
"bevy_window",
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
"default_font",
|
"default_font",
|
||||||
@ -35,40 +34,29 @@ bevy = { git = "https://git.arkitu.fr/forestia/bevy.git", default-features = fal
|
|||||||
"sysinfo_plugin",
|
"sysinfo_plugin",
|
||||||
"webgl2",
|
"webgl2",
|
||||||
"wayland",
|
"wayland",
|
||||||
"png",
|
]}
|
||||||
"tonemapping_luts",
|
bevy-inspector-egui = { version = "0.28", default-features = false, features = [
|
||||||
"bevy_pbr",
|
"bevy_image",
|
||||||
"bevy_gltf",
|
"bevy_render",
|
||||||
"animation",
|
"egui_open_url"
|
||||||
"async_executor",
|
]}
|
||||||
"bevy_animation",
|
|
||||||
"bevy_asset",
|
|
||||||
"bevy_scene",
|
|
||||||
] }
|
|
||||||
#bevy-inspector-egui = { version = "0.28", default-features = false, features = [
|
|
||||||
# "bevy_image",
|
|
||||||
# "bevy_render",
|
|
||||||
# "egui_open_url",
|
|
||||||
#] }
|
|
||||||
mevy = { version = "0.2", features = ["0.16"] }
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
rand = { version = "0.9", features = ["small_rng"] }
|
rand = { version = "0.8", features = ["small_rng"] }
|
||||||
voronoice = "0.2"
|
voronoice = "0.2"
|
||||||
noise = "0.9"
|
noise = "0.9"
|
||||||
bevy_editor_pls = { version = "0.11", git = "https://github.com/jakobhellermann/bevy_editor_pls.git", optional = true }
|
|
||||||
async-channel = "*"
|
|
||||||
bitflags = "*"
|
|
||||||
bytemuck = { version = "1", features = ["derive", "must_cast"] }
|
|
||||||
include_dir = "0.7"
|
|
||||||
|
|
||||||
# [target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
# console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
# console_log = "1.0"
|
console_log = "1.0"
|
||||||
# wgpu = { version = "22", features = ["webgl"] }
|
wgpu = { version = "22", features = ["webgl"]}
|
||||||
# wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
# wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
# web-sys = { version = "0.3", features = ["Document", "Window", "Element"] }
|
web-sys = { version = "0.3", features = [
|
||||||
# getrandom = { version = "*", features = ["js"] }
|
"Document",
|
||||||
|
"Window",
|
||||||
|
"Element",
|
||||||
|
]}
|
||||||
|
getrandom ={ version = "*", features = ["js"]}
|
||||||
|
|
||||||
[target.'cfg(target_os = "android")'.dependencies]
|
[target.'cfg(target_os = "android")'.dependencies]
|
||||||
android_logger = "0.14"
|
android_logger = "0.14"
|
||||||
@ -77,12 +65,15 @@ android_logger = "0.14"
|
|||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|
||||||
# Enable a large amount of optimization in the dev profile for dependencies.
|
# Enable a large amount of optimization in the dev profile for dependencies.
|
||||||
# [profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
# opt-level = 1
|
opt-level = 3
|
||||||
|
|
||||||
[package.metadata.android]
|
[package.metadata.android]
|
||||||
package = "org.forestiles.game"
|
package = "org.forestiles.example"
|
||||||
apk_name = "forestiles"
|
apk_name = "forestiles"
|
||||||
strip = "strip"
|
strip = "strip"
|
||||||
# see https://github.com/rust-mobile/cargo-apk
|
# see https://github.com/rust-mobile/cargo-apk
|
||||||
|
@ -1,374 +0,0 @@
|
|||||||
{
|
|
||||||
"accessors": [
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 3797,
|
|
||||||
"max": [
|
|
||||||
13.791570663452148,
|
|
||||||
3.689924955368042,
|
|
||||||
6.587912082672119
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
8.412104606628418,
|
|
||||||
-3.378239154815674,
|
|
||||||
3.2653744220733643
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 45564,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 3797,
|
|
||||||
"max": [
|
|
||||||
0.999547004699707,
|
|
||||||
0.9979217648506165,
|
|
||||||
0.9994550347328186
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
-0.9993845820426941,
|
|
||||||
-0.99895840883255,
|
|
||||||
-0.9959900379180908
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 0,
|
|
||||||
"componentType": 5125,
|
|
||||||
"count": 3804,
|
|
||||||
"type": "SCALAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 91128,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 858,
|
|
||||||
"max": [
|
|
||||||
10.12944221496582,
|
|
||||||
1.2994542121887207,
|
|
||||||
7.381155014038086
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
7.413111686706543,
|
|
||||||
-1.4659144878387451,
|
|
||||||
5.502954006195068
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 101424,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 858,
|
|
||||||
"max": [
|
|
||||||
0.9817375540733337,
|
|
||||||
0.9940162301063538,
|
|
||||||
0.9970744848251343
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
-0.9994650483131409,
|
|
||||||
-0.9916966557502747,
|
|
||||||
-0.9975656270980835
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 0,
|
|
||||||
"byteOffset": 15216,
|
|
||||||
"componentType": 5125,
|
|
||||||
"count": 858,
|
|
||||||
"type": "SCALAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 111720,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 3223,
|
|
||||||
"max": [
|
|
||||||
13.084306716918945,
|
|
||||||
2.6238322257995605,
|
|
||||||
7.360774993896484
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
6.982709884643555,
|
|
||||||
-2.6466314792633057,
|
|
||||||
4.409972190856934
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 150396,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 3223,
|
|
||||||
"max": [
|
|
||||||
0.9894658327102661,
|
|
||||||
0.9988145232200623,
|
|
||||||
0.9966117143630981
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
-0.9995312094688416,
|
|
||||||
-0.9980282187461853,
|
|
||||||
-0.9977282285690308
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 0,
|
|
||||||
"byteOffset": 18648,
|
|
||||||
"componentType": 5125,
|
|
||||||
"count": 3354,
|
|
||||||
"type": "SCALAR"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 189072,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 7794,
|
|
||||||
"max": [
|
|
||||||
13.333925247192383,
|
|
||||||
2.5410332679748535,
|
|
||||||
6.903109073638916
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
7.7406463623046875,
|
|
||||||
-2.3769795894622803,
|
|
||||||
-0.008923768997192383
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 1,
|
|
||||||
"byteOffset": 282600,
|
|
||||||
"componentType": 5126,
|
|
||||||
"count": 7794,
|
|
||||||
"max": [
|
|
||||||
0.9995447397232056,
|
|
||||||
0.998537003993988,
|
|
||||||
0.9921087622642517
|
|
||||||
],
|
|
||||||
"min": [
|
|
||||||
-0.9995536804199219,
|
|
||||||
-0.9998522996902466,
|
|
||||||
-0.9576923847198486
|
|
||||||
],
|
|
||||||
"type": "VEC3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bufferView": 0,
|
|
||||||
"byteOffset": 32064,
|
|
||||||
"componentType": 5125,
|
|
||||||
"count": 15528,
|
|
||||||
"type": "SCALAR"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"asset": {
|
|
||||||
"extras": {
|
|
||||||
"author": "UtsavSharma (https://sketchfab.com/UtsavSharma)",
|
|
||||||
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
|
|
||||||
"source": "https://sketchfab.com/3d-models/low-poly-tree-7f080d12b5074ffc9d194ef8c2a0bfb9",
|
|
||||||
"title": "Low Poly Tree"
|
|
||||||
},
|
|
||||||
"generator": "Sketchfab-12.68.0",
|
|
||||||
"version": "2.0"
|
|
||||||
},
|
|
||||||
"bufferViews": [
|
|
||||||
{
|
|
||||||
"buffer": 0,
|
|
||||||
"byteLength": 94176,
|
|
||||||
"name": "floatBufferViews",
|
|
||||||
"target": 34963
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"buffer": 0,
|
|
||||||
"byteLength": 376128,
|
|
||||||
"byteOffset": 94176,
|
|
||||||
"byteStride": 12,
|
|
||||||
"name": "floatBufferViews",
|
|
||||||
"target": 34962
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"buffers": [
|
|
||||||
{
|
|
||||||
"byteLength": 470304,
|
|
||||||
"uri": "scene.bin"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"materials": [
|
|
||||||
{
|
|
||||||
"doubleSided": true,
|
|
||||||
"name": "GreenDark",
|
|
||||||
"pbrMetallicRoughness": {
|
|
||||||
"baseColorFactor": [
|
|
||||||
0.12549,
|
|
||||||
0.505882,
|
|
||||||
0.133333,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"metallicFactor": 0.0,
|
|
||||||
"roughnessFactor": 0.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doubleSided": true,
|
|
||||||
"name": "Green",
|
|
||||||
"pbrMetallicRoughness": {
|
|
||||||
"baseColorFactor": [
|
|
||||||
0.239216,
|
|
||||||
1.0,
|
|
||||||
0.254902,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"metallicFactor": 0.0,
|
|
||||||
"roughnessFactor": 0.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doubleSided": true,
|
|
||||||
"name": "GreenDark1",
|
|
||||||
"pbrMetallicRoughness": {
|
|
||||||
"baseColorFactor": [
|
|
||||||
0.196078,
|
|
||||||
0.513726,
|
|
||||||
0.0901961,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"metallicFactor": 0.0,
|
|
||||||
"roughnessFactor": 0.6
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"doubleSided": true,
|
|
||||||
"name": "Material.002",
|
|
||||||
"pbrMetallicRoughness": {
|
|
||||||
"baseColorFactor": [
|
|
||||||
0.301961,
|
|
||||||
0.145098,
|
|
||||||
0.0352941,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"metallicFactor": 0.0,
|
|
||||||
"roughnessFactor": 0.6
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"meshes": [
|
|
||||||
{
|
|
||||||
"name": "Object_0",
|
|
||||||
"primitives": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"NORMAL": 1,
|
|
||||||
"POSITION": 0
|
|
||||||
},
|
|
||||||
"indices": 2,
|
|
||||||
"material": 0,
|
|
||||||
"mode": 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Object_1",
|
|
||||||
"primitives": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"NORMAL": 4,
|
|
||||||
"POSITION": 3
|
|
||||||
},
|
|
||||||
"indices": 5,
|
|
||||||
"material": 1,
|
|
||||||
"mode": 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Object_2",
|
|
||||||
"primitives": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"NORMAL": 7,
|
|
||||||
"POSITION": 6
|
|
||||||
},
|
|
||||||
"indices": 8,
|
|
||||||
"material": 2,
|
|
||||||
"mode": 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Object_3",
|
|
||||||
"primitives": [
|
|
||||||
{
|
|
||||||
"attributes": {
|
|
||||||
"NORMAL": 10,
|
|
||||||
"POSITION": 9
|
|
||||||
},
|
|
||||||
"indices": 11,
|
|
||||||
"material": 3,
|
|
||||||
"mode": 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nodes": [
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
1
|
|
||||||
],
|
|
||||||
"matrix": [
|
|
||||||
1.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
2.220446049250313e-16,
|
|
||||||
-1.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
1.0,
|
|
||||||
2.220446049250313e-16,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
0.0,
|
|
||||||
1.0
|
|
||||||
],
|
|
||||||
"name": "Sketchfab_model"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"children": [
|
|
||||||
2,
|
|
||||||
3,
|
|
||||||
4,
|
|
||||||
5
|
|
||||||
],
|
|
||||||
"name": "a303c315ac2a43a583a938036f0b9bed.3ds"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mesh": 0,
|
|
||||||
"name": "Object_2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mesh": 1,
|
|
||||||
"name": "Object_3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mesh": 2,
|
|
||||||
"name": "Object_4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mesh": 3,
|
|
||||||
"name": "Object_5"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"scene": 0,
|
|
||||||
"scenes": [
|
|
||||||
{
|
|
||||||
"name": "Sketchfab_Scene",
|
|
||||||
"nodes": [
|
|
||||||
0
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
#import bevy_pbr::{
|
|
||||||
mesh_bindings::mesh,
|
|
||||||
mesh_functions,
|
|
||||||
skinning,
|
|
||||||
morph::morph,
|
|
||||||
forward_io::{Vertex, VertexOutput},
|
|
||||||
view_transformations::position_world_to_clip,
|
|
||||||
}
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vertex(vertex: Vertex) -> VertexOutput {
|
|
||||||
var out: VertexOutput;
|
|
||||||
|
|
||||||
let mesh_world_from_local = mesh_functions::get_world_from_local(vertex.instance_index);
|
|
||||||
|
|
||||||
#ifdef SKINNED
|
|
||||||
var world_from_local = skinning::skin_model(
|
|
||||||
vertex.joint_indices,
|
|
||||||
vertex.joint_weights,
|
|
||||||
vertex.instance_index
|
|
||||||
);
|
|
||||||
#else
|
|
||||||
var world_from_local = mesh_world_from_local;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_NORMALS
|
|
||||||
#ifdef SKINNED
|
|
||||||
out.world_normal = skinning::skin_normals(world_from_local, vertex.normal);
|
|
||||||
#else
|
|
||||||
out.world_normal = mesh_functions::mesh_normal_local_to_world(
|
|
||||||
vertex.normal,
|
|
||||||
vertex.instance_index
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_POSITIONS
|
|
||||||
out.world_position = mesh_functions::mesh_position_local_to_world(world_from_local, vec4<f32>(vertex.position, 1.0));
|
|
||||||
out.position = position_world_to_clip(out.world_position.xyz);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_UVS_A
|
|
||||||
out.uv = vertex.uv;
|
|
||||||
#endif
|
|
||||||
#ifdef VERTEX_UVS_B
|
|
||||||
out.uv_b = vertex.uv_b;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_TANGENTS
|
|
||||||
out.world_tangent = mesh_functions::mesh_tangent_local_to_world(
|
|
||||||
world_from_local,
|
|
||||||
vertex.tangent,
|
|
||||||
vertex.instance_index
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_COLORS
|
|
||||||
out.color = vertex.color;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VERTEX_OUTPUT_INSTANCE_INDEX
|
|
||||||
out.instance_index = vertex.instance_index;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef VISIBILITY_RANGE_DITHER
|
|
||||||
out.visibility_range_dither = mesh_functions::get_visibility_range_dither_level(
|
|
||||||
vertex.instance_index, mesh_world_from_local[3]
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.7 KiB |
@ -1,21 +0,0 @@
|
|||||||
use bevy::{asset::io::embedded::EmbeddedAssetRegistry, prelude::*};
|
|
||||||
use include_dir::{include_dir, Dir, DirEntry, File};
|
|
||||||
|
|
||||||
const ASSETS_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/assets");
|
|
||||||
|
|
||||||
fn add_assets(assets: &mut EmbeddedAssetRegistry, dir: Dir<'static>) {
|
|
||||||
for f in dir.files() {
|
|
||||||
assets.insert_asset(f.path().to_path_buf(), f.path(), f.contents());
|
|
||||||
}
|
|
||||||
for d in dir.dirs() {
|
|
||||||
add_assets(assets, d.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Plugin;
|
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
let mut embedded = app.world_mut().resource_mut::<EmbeddedAssetRegistry>();
|
|
||||||
add_assets(&mut embedded, ASSETS_DIR);
|
|
||||||
}
|
|
||||||
}
|
|
102
src/camera.rs
@ -1,32 +1,92 @@
|
|||||||
use bevy::prelude::*;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::{input::mouse::MouseWheel, math::NormedVectorSpace, picking::{focus::HoverMap, pointer::PointerId}, prelude::*, utils::HashMap, window::PrimaryWindow};
|
||||||
|
|
||||||
|
use crate::ui;
|
||||||
|
|
||||||
pub struct Plugin;
|
pub struct Plugin;
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
impl bevy::prelude::Plugin for Plugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(Startup, setup);
|
app.add_systems(Startup, setup);
|
||||||
|
// .add_systems(Update, move_cam)
|
||||||
|
// .init_resource::<Pointers>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
fn setup(mut cmds: Commands, window: Query<&Window>) {
|
||||||
pub struct CameraMarker;
|
let zoom = 2./window.single().width().min(window.single().height());
|
||||||
|
|
||||||
fn setup(mut cmds: Commands) {
|
|
||||||
cmds.spawn((
|
cmds.spawn((
|
||||||
CameraMarker,
|
Camera2d,
|
||||||
Camera3d {
|
Transform::from_scale(Vec3::new(zoom, zoom, zoom))
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Projection::default(),
|
|
||||||
Transform::from_translation(Vec3::new(0., 1., 1.))
|
|
||||||
.looking_to(Vec3::new(0., -1., -1.), Vec3::Y),
|
|
||||||
));
|
|
||||||
cmds.spawn((
|
|
||||||
DirectionalLight {
|
|
||||||
// color: Color::WHITE,
|
|
||||||
// illuminance: 17000.,
|
|
||||||
// shadows_enabled: true
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
Transform::default().looking_to(Vec3::new(1., -1., -1.), Vec3::ZERO),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
struct Pointers(HashMap<PointerId, (Vec2, Option<Duration>)>);
|
||||||
|
|
||||||
|
fn move_cam(
|
||||||
|
mut cam: Query<&mut Transform, With<Camera2d>>,
|
||||||
|
map_ui_entity: Query<Entity, With<ui::MapUIComponent>>,
|
||||||
|
mouse_buttons: Res<ButtonInput<MouseButton>>,
|
||||||
|
mut ev_scroll: EventReader<MouseWheel>,
|
||||||
|
touches: Res<Touches>,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
mut pointers: ResMut<Pointers>,
|
||||||
|
hover_map: Res<HoverMap>,
|
||||||
|
time: Res<Time>
|
||||||
|
) {
|
||||||
|
let window = window.single();
|
||||||
|
let mut cam = cam.single_mut();
|
||||||
|
let map_ui_entity = map_ui_entity.single();
|
||||||
|
let ps = hover_map.iter().filter_map(|(id, hit_map)| match id {
|
||||||
|
PointerId::Mouse => window.cursor_position().map(|p|
|
||||||
|
match pointers.0.get(id) {
|
||||||
|
Some(p_cache) => (p_cache.1.filter(|_| mouse_buttons.pressed(MouseButton::Left)), p, p_cache.0),
|
||||||
|
None => (None, p, p)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
PointerId::Touch(i) => touches.get_pressed(*i).map(|t| (pointers.0.get(id).map(|(pos, start)| (*start).unwrap_or(time.elapsed())), t.position(), t.previous_position())),
|
||||||
|
_ => None
|
||||||
|
}.map(|(pressed_start,new_pos, old_pos)| (pressed_start,new_pos,old_pos,id,hit_map))
|
||||||
|
).collect::<Vec<_>>();
|
||||||
|
let pressed_on_map = ps.iter().filter(|p| p.0.is_some() && p.4.contains_key(&map_ui_entity)).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let old_midpoint = pressed_on_map.iter().fold(Vec2::ZERO, |acc, (_, _, old_pos, _, _)| {
|
||||||
|
acc + (old_pos/pressed_on_map.len() as f32)
|
||||||
|
});
|
||||||
|
let new_midpoint = pressed_on_map.iter().fold(Vec2::ZERO, |acc, (_, new_pos, _, _, _)| {
|
||||||
|
acc + (new_pos/pressed_on_map.len() as f32)
|
||||||
|
});
|
||||||
|
|
||||||
|
// move camera
|
||||||
|
cam.translation.x -= (new_midpoint.x - old_midpoint.x)*cam.scale.x;
|
||||||
|
cam.translation.y += (new_midpoint.y - old_midpoint.y)*cam.scale.y;
|
||||||
|
|
||||||
|
// multiple fingers zoom
|
||||||
|
if pressed_on_map.len() > 1 {
|
||||||
|
let old_d_to_midpoint = pressed_on_map.iter().fold(0., |acc, (_, _, old_pos, _, _)| {
|
||||||
|
acc + (old_midpoint-old_pos).norm()
|
||||||
|
});
|
||||||
|
let new_d_to_midpoint = pressed_on_map.iter().fold(0., |acc, (_, new_pos, _, _, _)| {
|
||||||
|
acc + (new_midpoint-new_pos).norm()
|
||||||
|
});
|
||||||
|
let zoom = new_d_to_midpoint/old_d_to_midpoint;
|
||||||
|
cam.scale /= zoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mouse scroll zoom
|
||||||
|
for ev in ev_scroll.read() {
|
||||||
|
let scale = (cam.scale.x-(ev.y*0.1/window.width().min(window.height()))).clamp(0.0001, 2./window.width().min(window.height()));
|
||||||
|
cam.scale = Vec3::new(scale, scale, scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update cached pointer positions
|
||||||
|
pointers.0.clear();
|
||||||
|
for (pressed_start, new_pos, _, id, _) in ps {
|
||||||
|
match id {
|
||||||
|
PointerId::Mouse => {pointers.0.insert(*id, (new_pos, pressed_start));},
|
||||||
|
PointerId::Touch(_) => {pointers.0.insert(*id, (new_pos, pressed_start));},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/lib.rs
@ -1,48 +1,24 @@
|
|||||||
#![feature(iter_array_chunks)]
|
use bevy::{prelude::*, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}};
|
||||||
use bevy::{
|
use bevy_inspector_egui::quick::WorldInspectorPlugin;
|
||||||
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
|
|
||||||
platform::collections::HashSet,
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub mod assets;
|
|
||||||
pub mod camera;
|
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod time;
|
pub mod camera;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
#[bevy_main]
|
#[bevy_main]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
.add_plugins((
|
.add_plugins((
|
||||||
DefaultPlugins, // .set(bevy::render::RenderPlugin {
|
DefaultPlugins,
|
||||||
// render_creation: bevy::render::settings::RenderCreation::Automatic(
|
// WorldInspectorPlugin::default(),
|
||||||
// bevy::render::settings::WgpuSettings {
|
|
||||||
// backends: Some(bevy::render::settings::Backends::GL),
|
|
||||||
// ..default()
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// ..default()
|
|
||||||
// })
|
|
||||||
|
|
||||||
// bevy_inspector_egui::DefaultInspectorConfigPlugin,
|
|
||||||
// #[cfg(feature = "debug")]
|
|
||||||
// bevy_editor_pls::EditorPlugin::default(), // for debug
|
|
||||||
camera::Plugin,
|
camera::Plugin,
|
||||||
map::Plugin,
|
map::Plugin,
|
||||||
ui::Plugin,
|
ui::Plugin,
|
||||||
time::Plugin,
|
FrameTimeDiagnosticsPlugin,
|
||||||
assets::Plugin,
|
|
||||||
FrameTimeDiagnosticsPlugin::default(),
|
|
||||||
LogDiagnosticsPlugin {
|
LogDiagnosticsPlugin {
|
||||||
filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]),
|
filter: Some(vec![FrameTimeDiagnosticsPlugin::FPS]),
|
||||||
// filter: Some({
|
|
||||||
// let mut set = HashSet::new();
|
|
||||||
// set.insert(FrameTimeDiagnosticsPlugin::FPS);
|
|
||||||
// set
|
|
||||||
// }),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
}
|
||||||
))
|
))
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
380
src/map.rs
@ -1,305 +1,209 @@
|
|||||||
use bevy::{
|
use std::time::Duration;
|
||||||
asset::{embedded_asset, RenderAssetUsages},
|
|
||||||
picking::PickSet,
|
use bevy::{asset::RenderAssetUsages, picking::PickSet, prelude::*, render::mesh::{Indices, PrimitiveTopology}, utils::HashMap};
|
||||||
prelude::*,
|
|
||||||
render::mesh::{Indices, PrimitiveTopology},
|
|
||||||
};
|
|
||||||
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
use noise::{Fbm, MultiFractal, NoiseFn, Perlin};
|
||||||
use rand::{thread_rng, Rng, SeedableRng};
|
use rand::{thread_rng, Rng, SeedableRng};
|
||||||
use voronoice::{BoundingBox, Point, VoronoiBuilder};
|
use voronoice::{BoundingBox, Point, VoronoiBuilder};
|
||||||
|
|
||||||
mod animals;
|
|
||||||
mod cells;
|
mod cells;
|
||||||
pub use animals::AnimalKind;
|
mod picking;
|
||||||
pub use cells::CellKind;
|
use picking::*;
|
||||||
use cells::*;
|
use cells::*;
|
||||||
|
|
||||||
use crate::{
|
|
||||||
map::cells::{
|
|
||||||
grow::GrowThread,
|
|
||||||
material::{cell_material, CellMaterial},
|
|
||||||
},
|
|
||||||
time::GameTime,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct Plugin;
|
pub struct Plugin;
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
impl bevy::prelude::Plugin for Plugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugins((cells::Plugin, MeshPickingPlugin))
|
app.add_systems(Startup, setup)
|
||||||
.add_systems(Startup, setup)
|
.insert_resource(Time::<Fixed>::from_seconds(0.25)) // Time for a day
|
||||||
.add_systems(Update, update_chunk_mesh)
|
.add_systems(FixedUpdate, update_cells)
|
||||||
|
.add_systems(PreUpdate, picking_backend.in_set(PickSet::Backend))
|
||||||
|
.add_systems(Update, update_map_mesh)
|
||||||
.insert_resource(ClearColor(Color::srgb(0., 0., 1.)))
|
.insert_resource(ClearColor(Color::srgb(0., 0., 1.)))
|
||||||
.insert_resource(Seed(thread_rng().gen()));
|
.insert_resource(Seed(thread_rng().gen()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determined empirically
|
pub const HEIGHT: f32 = 2.;
|
||||||
pub const AVERAGE_NEIGHBORS_NUMBER: f64 = 6.;
|
pub const WIDTH: f32 = 2.;
|
||||||
|
pub const REAL_HEIGHT: f32 = 500.;
|
||||||
pub const HEIGHT: f64 = 4.;
|
pub const REAL_WIDTH: f32 = 500.;
|
||||||
pub const WIDTH: f64 = 4.;
|
pub const CELL_AREA: f32 = REAL_HEIGHT * REAL_WIDTH / SIZE as f32;
|
||||||
pub const REAL_HEIGHT: f32 = 8000.;
|
pub const SIZE: usize = 10000;
|
||||||
pub const REAL_WIDTH: f32 = 8000.;
|
|
||||||
pub const CELL_AREA: f32 = REAL_HEIGHT * REAL_WIDTH / CELLS as f32;
|
|
||||||
pub const CELLS_TARGET_NUMBER: usize = 100000;
|
|
||||||
pub const CHUNKS_RESOLUTION: usize = 8;
|
|
||||||
pub const CHUNKS: usize = CHUNKS_RESOLUTION.pow(2);
|
|
||||||
pub const CELLS_PER_CHUNK: usize = CELLS_TARGET_NUMBER / CHUNKS;
|
|
||||||
pub const CELLS: usize = CELLS_PER_CHUNK * CHUNKS;
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Resource)]
|
||||||
struct Seed(u32);
|
struct Seed(u32);
|
||||||
|
|
||||||
#[derive(Resource)]
|
#[derive(Component)]
|
||||||
pub struct Voronoi(voronoice::Voronoi);
|
pub struct Voronoi (voronoice::Voronoi);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MeshMarker;
|
pub struct MapMarker;
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct MeshColors(Vec<[f32; 4]>);
|
struct MapColors (Vec<[f32; 4]>);
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct CellsEntities(Vec<Entity>);
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MeshNeedsUpdate(f32);
|
pub struct CellsEntities (Vec<Entity>);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Chunk(usize);
|
pub struct MeshNeedsUpdate(bool);
|
||||||
|
|
||||||
fn setup(
|
fn setup(
|
||||||
mut cmds: Commands,
|
mut cmds: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
// mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
||||||
mut materials: ResMut<Assets<CellMaterial>>,
|
seed: Res<Seed>
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
seed: Res<Seed>,
|
|
||||||
gt: Res<GameTime>,
|
|
||||||
) {
|
) {
|
||||||
// cmds.spawn(SceneRoot(asset_server.load(
|
|
||||||
// GltfAssetLabel::Scene(0).from_asset("embedded://models/tree.glb"),
|
|
||||||
// )));
|
|
||||||
// return;
|
|
||||||
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
|
let mut rng = rand::rngs::SmallRng::seed_from_u64(seed.0 as u64);
|
||||||
let mut sites = Vec::with_capacity(CELLS);
|
let mut sites = Vec::with_capacity(SIZE);
|
||||||
for chunk_x in 0..CHUNKS_RESOLUTION {
|
for _ in 0..SIZE {
|
||||||
let min_x = WIDTH / 2. * ((2. * (chunk_x as f64) / (CHUNKS_RESOLUTION as f64)) - 1.);
|
sites.push(Point { x:rng.gen_range(-WIDTH/2.0..WIDTH/2.0) as f64, y:rng.gen_range(-HEIGHT/2.0..HEIGHT/2.0) as f64 })
|
||||||
let max_x = min_x + (WIDTH as f64 / CHUNKS_RESOLUTION as f64);
|
|
||||||
for chunk_y in 0..CHUNKS_RESOLUTION {
|
|
||||||
let min_y = HEIGHT / 2. * ((2. * (chunk_y as f64) / (CHUNKS_RESOLUTION as f64)) - 1.);
|
|
||||||
let max_y = min_y + (HEIGHT as f64 / CHUNKS_RESOLUTION as f64);
|
|
||||||
for _ in 0..CELLS_PER_CHUNK {
|
|
||||||
sites.push(Point {
|
|
||||||
x: rng.gen_range(min_x..max_x),
|
|
||||||
y: rng.gen_range(min_y..max_y),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// dbg!(sites.len());
|
|
||||||
|
|
||||||
let voronoi = VoronoiBuilder::default()
|
let voronoi = VoronoiBuilder::default()
|
||||||
.set_sites(sites)
|
.set_sites(sites)
|
||||||
.set_bounding_box(BoundingBox::new_centered(WIDTH, HEIGHT))
|
.set_bounding_box(BoundingBox::new_centered(WIDTH as f64, HEIGHT as f64))
|
||||||
.set_lloyd_relaxation_iterations(3)
|
.set_lloyd_relaxation_iterations(3)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let mut cells_data = Vec::with_capacity(SIZE);
|
||||||
let mut cells = Vec::with_capacity(CELLS);
|
let z_noise = Fbm::<Perlin>::new(seed.0);
|
||||||
let z_noise = Fbm::<Perlin>::new(seed.0).set_octaves(4).set_lacunarity(3.);
|
let moisture_noise = Fbm::<Perlin>::new(seed.0+1)
|
||||||
let moisture_noise = Fbm::<Perlin>::new(seed.0 + 1).set_frequency(2.);
|
.set_frequency(2.);
|
||||||
for i in 0..CELLS {
|
for i in 0..SIZE {
|
||||||
let c = voronoi.cell(i);
|
let c = voronoi.cell(i);
|
||||||
let site = c.site_position();
|
let site = c.site_position();
|
||||||
let z = get_altitude(&z_noise, &[site.x as f32, site.y as f32]);
|
let z = (
|
||||||
let _m = (
|
0.3 // Arbitrary value
|
||||||
(moisture_noise.get([site.x, site.y]) + 1.) / 2.
|
+ ((z_noise.get([site.x, site.y])+1.)/2.) // Noise + [0; 1]
|
||||||
// Noise + [0; 1]
|
- ((site.x.powi(2)+site.y.powi(2)).sqrt()*0.5) // Distance - [0; sqrt(2)] * 0.5
|
||||||
)
|
).clamp(0., 1.);
|
||||||
.clamp(0., 1.) as f32;
|
let m = (
|
||||||
let k = if z <= 0. {
|
(moisture_noise.get([site.x, site.y])+1.)/2. // Noise + [0; 1]
|
||||||
|
).clamp(0., 1.) as f32;
|
||||||
|
let k = if z <= 0.5 {
|
||||||
CellKind::Sea
|
CellKind::Sea
|
||||||
} else if z <= 0.04 {
|
} else if z <= 0.52 {
|
||||||
CellKind::Beach
|
CellKind::Beach
|
||||||
} else if z < 0.6 {
|
} else if z < 0.8 {
|
||||||
CellKind::Dirt
|
CellKind::Dirt
|
||||||
} else {
|
} else {
|
||||||
CellKind::Stone
|
CellKind::Stone
|
||||||
};
|
};
|
||||||
cells.push(Cell {
|
cells_data.push(CellData::new(k, i, (z*255.) as u8, (m*255.) as u8, 0, vec![]));
|
||||||
kind: k,
|
|
||||||
voronoi_id: i,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// let mut poss = [Vec::new(); CHUNKS];
|
|
||||||
let mut indices: [Vec<u32>; CHUNKS] = std::array::from_fn(|_| Vec::new());
|
|
||||||
// let mut normals = Vec::new();
|
|
||||||
|
|
||||||
let mut poss = voronoi.sites().chunks_exact(CELLS_PER_CHUNK).map(|ch| {
|
let mut poss = Vec::new();
|
||||||
ch.iter()
|
let mut colors = Vec::new();
|
||||||
.map(|pos| {
|
let mut indices = Vec::new();
|
||||||
let z = get_altitude(&z_noise, &[pos.x as f32, pos.y as f32]);
|
|
||||||
Vec3::new(pos.x as f32, z, -pos.y as f32) // adapt to bevy's coordinate system
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
});
|
|
||||||
let mut poss: [Vec<Vec3>; CHUNKS] = std::array::from_fn(|_| poss.next().unwrap());
|
|
||||||
|
|
||||||
// for (i, pos) in voronoi.sites().iter().enumerate() {
|
for (c, cd) in voronoi.iter_cells().zip(cells_data.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) {
|
||||||
// let z = get_altitude(&z_noise, &[pos.x as f32, pos.y as f32]);
|
let color = cd.color();
|
||||||
// poss.push(Vec3::new(pos.x as f32, pos.y as f32, z));
|
// if c.site() == selected_tile {
|
||||||
// cells[i].vertices.push(i);
|
// color[0] = (color[0]+0.4).clamp(0., 1.);
|
||||||
// }
|
// color[1] = (color[1]+0.4).clamp(0., 1.);
|
||||||
// let mut hybrid_cells: HashMap<usize, usize> = HashMap::new();
|
// color[2] = (color[2]+0.4).clamp(0., 1.);
|
||||||
for t in voronoi.triangulation().triangles.chunks_exact(3) {
|
// }
|
||||||
let on_hull = t
|
let vs = c.iter_vertices().collect::<Vec<_>>();
|
||||||
.iter()
|
let i = poss.len();
|
||||||
.filter(|v| voronoi.triangulation().hull.contains(v))
|
for v in vs.iter() {
|
||||||
.count();
|
poss.push(Vec3::new(v.x as f32, v.y as f32, 0.));// [v.x as f32, v.y as f32, 0.]);
|
||||||
// Don't draw triangles on the hull that are often long and look glitchy
|
// poss.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1));
|
||||||
if on_hull > 0 {
|
colors.push(color);
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
let mut chs = t.iter().map(|c| Cell::chunk_from_id(*c));
|
for v in 1..(vs.len()-1) {
|
||||||
let chs: [usize; 3] = std::array::from_fn(|_| chs.next().unwrap());
|
indices.extend_from_slice(&[i as u32, (i+v) as u32, (i+v+1) as u32]);
|
||||||
for ch in 0..CHUNKS {
|
cd.vertices.extend_from_slice(&[i, i+v, i+v+1]);
|
||||||
if !chs.contains(&ch) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// let ch = if chs[1] == chs[2] { chs[1] } else { chs[0] };
|
|
||||||
// Add vertex to chunk if it is from an external chunk but we need it for a triangle. Else, just make the triangle
|
|
||||||
if chs[2] != ch {
|
|
||||||
poss[ch].push(poss[chs[2]][t[2] % CELLS_PER_CHUNK]);
|
|
||||||
// hybrid_cells.insert(chs[2], chs[0]);
|
|
||||||
indices[ch].push((poss[ch].len() - 1) as u32);
|
|
||||||
} else {
|
|
||||||
indices[ch].push((t[2] % CELLS_PER_CHUNK) as u32);
|
|
||||||
}
|
|
||||||
if chs[1] != ch {
|
|
||||||
poss[ch].push(poss[chs[1]][t[1] % CELLS_PER_CHUNK]);
|
|
||||||
// hybrid_cells.insert(chs[1], chs[0]);
|
|
||||||
indices[ch].push((poss[ch].len() - 1) as u32);
|
|
||||||
} else {
|
|
||||||
indices[ch].push((t[1] % CELLS_PER_CHUNK) as u32);
|
|
||||||
}
|
|
||||||
if chs[0] != ch {
|
|
||||||
poss[ch].push(poss[chs[0]][t[0] % CELLS_PER_CHUNK]);
|
|
||||||
// hybrid_cells.insert(chs[0], chs[1]);
|
|
||||||
indices[ch].push((poss[ch].len() - 1) as u32);
|
|
||||||
} else {
|
|
||||||
indices[ch].push((t[0] % CELLS_PER_CHUNK) as u32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cells_entities = Vec::with_capacity(cells.len());
|
let mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default())
|
||||||
for (ch_id, ((poss, indices), ch_cells)) in poss
|
// Add 4 vertices, each with its own position attribute (coordinate in
|
||||||
.into_iter()
|
// 3D space), for each of the corners of the parallelogram.
|
||||||
.zip(indices.into_iter())
|
.with_inserted_attribute(
|
||||||
.zip(cells.chunks_exact(CELLS_PER_CHUNK))
|
Mesh::ATTRIBUTE_POSITION,
|
||||||
.enumerate()
|
poss
|
||||||
{
|
)
|
||||||
let mut cmd_mesh = cmds.spawn_empty();
|
.with_inserted_attribute(
|
||||||
cmd_mesh.with_children(|parent| {
|
Mesh::ATTRIBUTE_COLOR,
|
||||||
for cell in ch_cells {
|
colors.clone()
|
||||||
let mut cmd = parent.spawn((
|
|
||||||
cell.clone(),
|
|
||||||
Transform::from_translation(poss[cell.voronoi_id % CELLS_PER_CHUNK]),
|
|
||||||
));
|
|
||||||
cells_entities.push(cmd.id());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let colors = vec![[0.; 4]; poss.len()];
|
|
||||||
let mut mesh = Mesh::new(
|
|
||||||
PrimitiveTopology::TriangleList,
|
|
||||||
RenderAssetUsages::default(),
|
|
||||||
)
|
)
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, poss)
|
|
||||||
.with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, colors.clone())
|
|
||||||
// .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals)
|
|
||||||
.with_inserted_indices(Indices::U32(indices));
|
.with_inserted_indices(Indices::U32(indices));
|
||||||
|
|
||||||
mesh.compute_smooth_normals();
|
let mut cells_entities = Vec::with_capacity(cells_data.len());
|
||||||
|
cmds.spawn((
|
||||||
|
Mesh2d(meshes.add(mesh)),
|
||||||
|
MeshMaterial2d(materials.add(ColorMaterial::default())),
|
||||||
|
Transform::default(),
|
||||||
|
Voronoi(voronoi),
|
||||||
|
MapColors(colors),
|
||||||
|
MeshNeedsUpdate(true),
|
||||||
|
MapMarker
|
||||||
|
)).with_children(|parent| {
|
||||||
|
for cd in cells_data {
|
||||||
|
let mut cmd = parent.spawn((cd, LastUpdate(0)));
|
||||||
|
cmd.observe(|trigger: Trigger<Pointer<Click>>, mut cells: Query<&mut CellData>, mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>| {
|
||||||
|
if trigger.duration > Duration::from_millis(100) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let mut cd = cells.get_mut(trigger.target).unwrap();
|
||||||
|
match cd.kind {
|
||||||
|
CellKind::Dirt | CellKind::Grass => {
|
||||||
|
cd.kind = CellKind::Forest;
|
||||||
|
map_needs_update.single_mut().0 = true;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
dbg!(trigger.duration);
|
||||||
|
});
|
||||||
|
cells_entities.push(cmd.id());
|
||||||
|
}
|
||||||
|
}).insert(CellsEntities(cells_entities));
|
||||||
|
|
||||||
cmd_mesh
|
|
||||||
.insert((
|
|
||||||
Mesh3d(meshes.add(mesh)),
|
|
||||||
// StandardMaterial
|
|
||||||
// MeshMaterial3d(materials.add(cells::pbr_material::StandardMaterial::default())),
|
|
||||||
MeshMaterial3d(materials.add(cell_material())),
|
|
||||||
Transform::default(),
|
|
||||||
MeshColors(colors),
|
|
||||||
MeshNeedsUpdate(f32::MAX),
|
|
||||||
MeshMarker,
|
|
||||||
Chunk(ch_id),
|
|
||||||
))
|
|
||||||
.observe(self::cells::input::on_click);
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds.insert_resource(GrowThread::new(
|
|
||||||
voronoi.clone(),
|
|
||||||
gt.secs.clone(),
|
|
||||||
cells,
|
|
||||||
rng,
|
|
||||||
));
|
|
||||||
|
|
||||||
cmds.insert_resource(Voronoi(voronoi));
|
|
||||||
cmds.insert_resource(CellsEntities(cells_entities));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update this to take chunks into account
|
#[derive(Component)]
|
||||||
fn update_chunk_mesh(
|
pub struct LastUpdate(usize);
|
||||||
cells: Query<&Cell>,
|
|
||||||
mut chunks: Query<
|
fn update_cells(
|
||||||
(&Mesh3d, &mut MeshColors, &mut MeshNeedsUpdate, &Children),
|
mut cells: Query<(&mut CellData, &mut LastUpdate)>,
|
||||||
With<MeshMarker>,
|
mut map_needs_update: Query<&mut MeshNeedsUpdate, With<MapMarker>>
|
||||||
>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
) {
|
) {
|
||||||
for (mesh, mut cols, mut needs_update, children) in chunks
|
let mut map_needs_update = map_needs_update.single_mut();
|
||||||
.iter_mut()
|
for (mut cd, mut lu) in cells.iter_mut() {
|
||||||
.sort_unstable_by::<&MeshNeedsUpdate>(|nu1, nu2| nu2.0.total_cmp(&nu1.0))
|
lu.0 += 1;
|
||||||
{
|
if lu.0 > match cd.kind {
|
||||||
if needs_update.0 > 0. {
|
CellKind::Void | CellKind::Sea | CellKind::Beach | CellKind::Dirt | CellKind::Stone => usize::MAX,
|
||||||
if let Some(mesh) = meshes.get_mut(mesh) {
|
CellKind::Forest => 100*365/4, // Let's say that a forest takes 100 years to mature
|
||||||
// let mut modified = false;
|
CellKind::Grass => 7*7/4 // Let's say that grass takes 7 weaks to reach its max
|
||||||
// let mut rng = thread_rng();
|
} {
|
||||||
for child in children.iter() {
|
lu.0 = 0;
|
||||||
let cell = cells.get(child).unwrap();
|
cd.resource = (cd.resource + 1).clamp(0, 4);
|
||||||
// let col: [f32; 4] = [rng.gen(), rng.gen(), rng.gen(), 1.];
|
map_needs_update.0 = true;
|
||||||
let col = cell.color();
|
|
||||||
// modified = modified || cols.0[cell.voronoi_id % CELLS_PER_CHUNK] != col;
|
|
||||||
cols.0[cell.voronoi_id % CELLS_PER_CHUNK] = col;
|
|
||||||
// for id in cell.vertices.iter() {
|
|
||||||
// modified = modified || cols.0[*id] != col;
|
|
||||||
// cols.0[*id] = col.clone();
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// if modified {
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone());
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
needs_update.0 = 0.;
|
|
||||||
// Update at most one chunk per frame to avoid freezes
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Between 0 and 1
|
fn update_map_mesh(
|
||||||
fn get_altitude(z_noise: &Fbm<Perlin>, pos: &[f32; 2]) -> f32 {
|
cells: Query<&CellData>,
|
||||||
let z_noise = ((z_noise.get([pos[0] as f64, pos[1] as f64]) as f32) + 1.) / 2.; // Noise [0; 1]
|
mut map: Query<(&Mesh2d, &mut MapColors, &mut MeshNeedsUpdate), With<MapMarker>>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>
|
||||||
-0.2// Arbitrary value
|
) {
|
||||||
+ (z_noise.exp() / 1f32.exp())
|
let (mesh, mut cols, mut needs_update) = map.single_mut();
|
||||||
// Noise + [0; 1]
|
if needs_update.0 {
|
||||||
// - ((pos[0].powi(2)+pos[1].powi(2)).sqrt()*0.3)
|
if let Some(mesh) = meshes.get_mut(mesh) {
|
||||||
// Distance - [0; sqrt(2)] * 0.5
|
let mut modified = false;
|
||||||
|
for cd in cells.iter() {
|
||||||
// .clamp(-1., 1.)
|
let col = cd.color();
|
||||||
|
for id in cd.vertices.iter() {
|
||||||
|
modified = modified || cols.0[*id] != col;
|
||||||
|
cols.0[*id] = col.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if modified {
|
||||||
|
mesh.insert_attribute(Mesh::ATTRIBUTE_COLOR, cols.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
needs_update.0 = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn map_input()
|
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
pub struct Plugin;
|
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
|
||||||
fn build(&self, app: &mut App) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub enum AnimalKind {
|
|
||||||
Goat,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
#[require(Transform)]
|
|
||||||
pub struct Animal {
|
|
||||||
pub kind: AnimalKind,
|
|
||||||
}
|
|
136
src/map/cells.rs
@ -1,129 +1,45 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{time::GameTime, ui::CurrentAction};
|
|
||||||
|
|
||||||
use super::{AnimalKind, CELLS_PER_CHUNK, CELL_AREA};
|
|
||||||
|
|
||||||
pub mod grow;
|
|
||||||
pub mod input;
|
|
||||||
pub mod material;
|
|
||||||
use material::CellMaterial;
|
|
||||||
|
|
||||||
pub struct Plugin;
|
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_plugins(MaterialPlugin::<CellMaterial> {
|
|
||||||
prepass_enabled: false,
|
|
||||||
shadows_enabled: false,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.add_plugins(grow::Plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum CellKind {
|
pub enum CellKind {
|
||||||
|
Void,
|
||||||
Sea,
|
Sea,
|
||||||
Beach,
|
Beach,
|
||||||
Forest { wealth: WealthType },
|
Forest,
|
||||||
Dirt,
|
Dirt,
|
||||||
Stone,
|
Stone,
|
||||||
Grass { wealth: WealthType },
|
Grass
|
||||||
}
|
|
||||||
impl CellKind {
|
|
||||||
pub const FOREST: CellKind = CellKind::Forest { wealth: 0 };
|
|
||||||
pub const GRASS: CellKind = CellKind::Grass { wealth: 0 };
|
|
||||||
pub const fn growth_duration(&self) -> u64 {
|
|
||||||
match self {
|
|
||||||
CellKind::Forest { .. } => 100 * 365 * 24 * 60 * 60, // a forest takes 100 years to mature
|
|
||||||
CellKind::Grass { .. } => 7 * 7 * 24 * 60 * 60, // grass takes 7 weeks to reach its max
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Time before trying to extend to one neighbor
|
|
||||||
pub const fn extend_duration(&self) -> usize {
|
|
||||||
match self {
|
|
||||||
CellKind::Grass { wealth } => {
|
|
||||||
// "Grass can expand into adjacent dirt at a rate of 2.5 to 7.5 cm (1 to 3 inches) per month.", DeepSeek
|
|
||||||
59_388_377
|
|
||||||
* (CELL_AREA as usize).isqrt()
|
|
||||||
* WealthType::MAX.saturating_div(*wealth) as usize
|
|
||||||
}
|
|
||||||
CellKind::Forest { wealth } => {
|
|
||||||
// Arbitrary value : 1m per 10 years
|
|
||||||
(10 * 365 * 24 * 3600)
|
|
||||||
* (CELL_AREA as usize).isqrt()
|
|
||||||
* WealthType::MAX.saturating_div(*wealth) as usize
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn can_place_animal(&self, kind: AnimalKind) -> bool {
|
|
||||||
match self {
|
|
||||||
CellKind::Sea => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub const fn grass_wealth(&self) -> Option<WealthType> {
|
|
||||||
match self {
|
|
||||||
CellKind::Grass { wealth } => Some(*wealth),
|
|
||||||
CellKind::Forest { wealth } => Some(wealth.saturating_mul(
|
|
||||||
(CellKind::FOREST.growth_duration() / CellKind::GRASS.growth_duration())
|
|
||||||
as WealthType,
|
|
||||||
)),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn diff(&self, other: &CellKind) -> f32 {
|
|
||||||
match (self, other) {
|
|
||||||
(CellKind::Grass { wealth: w1 }, CellKind::Grass { wealth: w2 }) => {
|
|
||||||
wealth_to_unit(w2.abs_diff(*w1))
|
|
||||||
}
|
|
||||||
(CellKind::Forest { wealth: w1 }, CellKind::Forest { wealth: w2 }) => {
|
|
||||||
wealth_to_unit(w2.abs_diff(*w1))
|
|
||||||
}
|
|
||||||
(k1, k2) => {
|
|
||||||
if k1 == k2 {
|
|
||||||
0.
|
|
||||||
} else {
|
|
||||||
1.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Component, Clone)]
|
#[derive(Debug, Component)]
|
||||||
pub struct Cell {
|
pub struct CellData {
|
||||||
pub kind: CellKind,
|
pub kind: CellKind,
|
||||||
pub voronoi_id: usize,
|
pub cid: usize,
|
||||||
|
z: u8,
|
||||||
|
pub moisture: u8,
|
||||||
|
pub resource: u8, // How much resource there is (between 0 and 4)
|
||||||
|
pub vertices: Vec<usize>
|
||||||
}
|
}
|
||||||
impl Cell {
|
impl CellData {
|
||||||
pub const fn color(&self) -> [f32; 4] {
|
pub fn new(kind: CellKind, cell: usize, z: u8, moisture: u8, resource: u8, vertices: Vec<usize>) -> Self {
|
||||||
|
Self {
|
||||||
|
kind,
|
||||||
|
cid: cell,
|
||||||
|
z,
|
||||||
|
moisture,
|
||||||
|
resource,
|
||||||
|
vertices
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn color(&self) -> [f32; 4] {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
|
CellKind::Void => [0.; 4],
|
||||||
CellKind::Sea => [0., 0., 1., 1.],
|
CellKind::Sea => [0., 0., 1., 1.],
|
||||||
CellKind::Beach => [0.82, 0.84, 0.51, 1.],
|
CellKind::Beach => [0.82, 0.84, 0.51, 1.],
|
||||||
CellKind::Forest { wealth } => [0., 0.5 - (wealth_to_unit(wealth) * 0.4), 0., 1.],
|
CellKind::Forest => [0., 0.5 - (self.resource as f32/4.*0.4), 0., 1.],
|
||||||
CellKind::Dirt => [0.53, 0.38, 0.29, 1.],
|
CellKind::Dirt => [0.53 - (self.resource as f32/4.*0.4), 0.38-(self.resource as f32/4.*0.4), 0.29-(self.resource as f32/4.*0.4), 1.],
|
||||||
CellKind::Stone => [0.5, 0.5, 0.5, 1.],
|
CellKind::Stone => [0.5, 0.5, 0.5, 1.],
|
||||||
CellKind::Grass { wealth } => [
|
CellKind::Grass => [(136./255.) - (self.resource as f32/4.*0.4), (204./255.) - (self.resource as f32/4.*0.4), (59./255.) - (self.resource as f32/4.*0.4), 1.]
|
||||||
(136. / 255.) - (wealth_to_unit(wealth) * 0.15),
|
|
||||||
(154. / 255.) + (wealth_to_unit(wealth) * 0.1),
|
|
||||||
(59. / 255.) - (wealth_to_unit(wealth) * 0.15),
|
|
||||||
1.,
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn chunk_from_id(id: usize) -> usize {
|
|
||||||
id / CELLS_PER_CHUNK
|
|
||||||
}
|
|
||||||
pub const fn chunk(&self) -> usize {
|
|
||||||
Self::chunk_from_id(self.voronoi_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type WealthType = u16;
|
|
||||||
const fn wealth_to_unit(wealth: WealthType) -> f32 {
|
|
||||||
wealth as f32 / WealthType::MAX as f32
|
|
||||||
}
|
}
|
@ -1,195 +0,0 @@
|
|||||||
use std::net::UdpSocket;
|
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use bevy::asset::{embedded_asset, embedded_path};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::tasks::{AsyncComputeTaskPool, Task};
|
|
||||||
use log::info;
|
|
||||||
use rand::rngs::SmallRng;
|
|
||||||
use rand::seq::IteratorRandom;
|
|
||||||
use rand::{rng, Rng};
|
|
||||||
use voronoice::Voronoi;
|
|
||||||
|
|
||||||
use crate::map::cells::{wealth_to_unit, WealthType};
|
|
||||||
use crate::map::{CellKind, CellsEntities, MeshNeedsUpdate};
|
|
||||||
|
|
||||||
use super::Cell;
|
|
||||||
|
|
||||||
pub struct Plugin;
|
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_systems(Update, rx_chunks_updates);
|
|
||||||
// embedded_asset!(app, "../../../", "assets/models/tree.glb");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Background task that manages terrain
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct GrowThread(
|
|
||||||
pub Task<()>,
|
|
||||||
pub async_channel::Sender<Cell>,
|
|
||||||
pub async_channel::Receiver<Cell>,
|
|
||||||
);
|
|
||||||
impl GrowThread {
|
|
||||||
pub fn new(voronoi: Voronoi, gt: Arc<AtomicU64>, cells: Vec<Cell>, rng: SmallRng) -> Self {
|
|
||||||
let task_pool = AsyncComputeTaskPool::get();
|
|
||||||
let (tx_control, rx_control) = async_channel::unbounded();
|
|
||||||
let (tx_update, rx_update) = async_channel::unbounded();
|
|
||||||
Self(
|
|
||||||
task_pool.spawn(grow_thread(voronoi, gt, cells, rng, rx_control, tx_update)),
|
|
||||||
tx_control,
|
|
||||||
rx_update,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(Component)]
|
|
||||||
// struct GetChunkTask(Task<Vec<Cell>>);
|
|
||||||
|
|
||||||
// const MAX_UPDATE_PER_FRAME: usize = 10000;
|
|
||||||
fn rx_chunks_updates(
|
|
||||||
mut chunks: Query<&mut MeshNeedsUpdate>,
|
|
||||||
mut cells: Query<(&mut Cell, &ChildOf, Entity)>,
|
|
||||||
cells_entities: Res<CellsEntities>,
|
|
||||||
grow_thread: Res<GrowThread>,
|
|
||||||
mut cmds: Commands,
|
|
||||||
asset_server: Res<AssetServer>,
|
|
||||||
) {
|
|
||||||
while let Ok(c) = grow_thread.2.try_recv() {
|
|
||||||
let id = c.voronoi_id;
|
|
||||||
let (mut cell, parent, id) = cells.get_mut(cells_entities.0[id]).unwrap();
|
|
||||||
chunks.get_mut(parent.parent()).unwrap().0 += cell.kind.diff(&c.kind);
|
|
||||||
|
|
||||||
if matches!(c.kind, CellKind::Forest { .. })
|
|
||||||
&& !matches!(cell.kind, CellKind::Forest { .. })
|
|
||||||
{
|
|
||||||
// Spawn tree
|
|
||||||
cmds.spawn((
|
|
||||||
SceneRoot(
|
|
||||||
asset_server
|
|
||||||
.load(GltfAssetLabel::Scene(0).from_asset("embedded://models/tree.glb")),
|
|
||||||
),
|
|
||||||
ChildOf(id),
|
|
||||||
Transform::from_scale(Vec3::new(0.1, 0.1, 0.1)).with_rotation(
|
|
||||||
Quat::from_axis_angle(
|
|
||||||
Vec3::Y,
|
|
||||||
rng().random_range(-std::f32::consts::PI..std::f32::consts::PI),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
*cell = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const GRASS_EXTEND_STEP: u64 = CellKind::GRASS.growth_duration();
|
|
||||||
const FOREST_EXTEND_STEP: u64 = CellKind::FOREST.growth_duration();
|
|
||||||
pub async fn grow_thread(
|
|
||||||
voronoi: Voronoi,
|
|
||||||
gt: Arc<AtomicU64>,
|
|
||||||
cells: Vec<Cell>,
|
|
||||||
mut rng: SmallRng,
|
|
||||||
rx_control: async_channel::Receiver<Cell>,
|
|
||||||
tx_update: async_channel::Sender<Cell>,
|
|
||||||
) {
|
|
||||||
// Add "modified" flag
|
|
||||||
let mut cells: Vec<(Cell, f32)> = cells.into_iter().map(|c| (c, 0.)).collect();
|
|
||||||
let mut last_grass_grow = 0;
|
|
||||||
let mut last_forest_grow = 0;
|
|
||||||
let mut last_grass_extend = 0;
|
|
||||||
let mut last_forest_extend = 0;
|
|
||||||
loop {
|
|
||||||
// Receive control from the game
|
|
||||||
while let Ok(c) = rx_control.try_recv() {
|
|
||||||
let id = c.voronoi_id;
|
|
||||||
cells[id] = (c, 1.);
|
|
||||||
}
|
|
||||||
|
|
||||||
let gt = gt.load(Ordering::Acquire);
|
|
||||||
|
|
||||||
// Update step
|
|
||||||
let grass_grow = gt.saturating_sub(last_grass_grow) * WealthType::MAX as u64
|
|
||||||
/ CellKind::GRASS.growth_duration();
|
|
||||||
let forest_grow = gt.saturating_sub(last_forest_grow) * WealthType::MAX as u64
|
|
||||||
/ CellKind::FOREST.growth_duration();
|
|
||||||
if grass_grow > 0 || forest_grow > 0 {
|
|
||||||
for (c, modified) in cells.iter_mut() {
|
|
||||||
if grass_grow > 0 {
|
|
||||||
if let CellKind::Grass { ref mut wealth } = c.kind {
|
|
||||||
*wealth = wealth.saturating_add(grass_grow as WealthType);
|
|
||||||
*modified += grass_grow as f32 / WealthType::MAX as f32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if forest_grow > 0 {
|
|
||||||
if let CellKind::Forest { ref mut wealth } = c.kind {
|
|
||||||
*wealth = wealth.saturating_add(forest_grow as WealthType);
|
|
||||||
*modified += grass_grow as f32 / WealthType::MAX as f32;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Don't make multiple updates at once to avoid problems and lags
|
|
||||||
} else if gt - last_grass_extend > GRASS_EXTEND_STEP {
|
|
||||||
for i in 0..cells.len() {
|
|
||||||
if cells[i].1 < 1. {
|
|
||||||
if let Some(wealth) = cells[i].0.kind.grass_wealth() {
|
|
||||||
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
|
|
||||||
let target = &mut cells
|
|
||||||
[voronoi.cell(i).iter_neighbors().choose(&mut rng).unwrap()];
|
|
||||||
|
|
||||||
if matches!(target.0.kind, CellKind::Dirt) {
|
|
||||||
target.0.kind = CellKind::Grass { wealth: 0 };
|
|
||||||
target.1 = 1.;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last_grass_extend += GRASS_EXTEND_STEP;
|
|
||||||
} else if gt - last_forest_extend > FOREST_EXTEND_STEP {
|
|
||||||
for i in 0..cells.len() {
|
|
||||||
if cells[i].1 < 1. {
|
|
||||||
if let CellKind::Forest { wealth } = cells[i].0.kind {
|
|
||||||
if rng.gen_bool(wealth_to_unit(wealth) as f64) {
|
|
||||||
let target = &mut cells
|
|
||||||
[voronoi.cell(i).iter_neighbors().choose(&mut rng).unwrap()];
|
|
||||||
match target.0.kind {
|
|
||||||
CellKind::Dirt => {
|
|
||||||
target.0.kind = CellKind::Forest { wealth: 0 };
|
|
||||||
target.1 = 1.;
|
|
||||||
}
|
|
||||||
CellKind::Grass { wealth: w } => {
|
|
||||||
target.0.kind = CellKind::Forest {
|
|
||||||
wealth: (w as f32
|
|
||||||
* CellKind::GRASS.growth_duration() as f32
|
|
||||||
/ CellKind::FOREST.growth_duration() as f32)
|
|
||||||
as WealthType,
|
|
||||||
};
|
|
||||||
target.1 = 1.;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last_forest_extend += FOREST_EXTEND_STEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
let grass_count = cells
|
|
||||||
.iter()
|
|
||||||
.filter(|(c, _)| matches!(c.kind, CellKind::Grass { .. }))
|
|
||||||
.count();
|
|
||||||
|
|
||||||
last_grass_grow += grass_grow * CellKind::GRASS.growth_duration() / WealthType::MAX as u64;
|
|
||||||
last_forest_grow += forest_grow * CellKind::FOREST.growth_duration();
|
|
||||||
|
|
||||||
// Send modifications
|
|
||||||
let modification_treshold = 1. - (1. / (tx_update.len() as f32));
|
|
||||||
for (c, m) in cells.iter_mut().filter(|(_, m)| *m > modification_treshold) {
|
|
||||||
tx_update.send(c.clone()).await.unwrap();
|
|
||||||
*m = 0.;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use voronoice::Point;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
map::{
|
|
||||||
animals::Animal, cells::grow::GrowThread, CellsEntities, MeshMarker, MeshNeedsUpdate,
|
|
||||||
Voronoi,
|
|
||||||
},
|
|
||||||
ui::CurrentAction,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::{Cell, CellKind, WealthType};
|
|
||||||
|
|
||||||
pub fn on_click(
|
|
||||||
trigger: Trigger<Pointer<Click>>,
|
|
||||||
mut cells: Query<(&mut Cell, &ChildOf)>,
|
|
||||||
voronoi: Res<Voronoi>,
|
|
||||||
mut chunks: Query<&mut MeshNeedsUpdate, With<MeshMarker>>,
|
|
||||||
mut cmds: Commands,
|
|
||||||
ca: Res<CurrentAction>,
|
|
||||||
grow_task: Res<GrowThread>,
|
|
||||||
cells_entities: Res<CellsEntities>,
|
|
||||||
) {
|
|
||||||
if trigger.duration > Duration::from_millis(200) || trigger.hit.position.is_none() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let pos = trigger.hit.position.unwrap();
|
|
||||||
let (mut cell, parent) = cells
|
|
||||||
.get_mut(
|
|
||||||
cells_entities.0[voronoi
|
|
||||||
.0
|
|
||||||
.cell(0)
|
|
||||||
.iter_path(Point {
|
|
||||||
x: pos.x as f64,
|
|
||||||
y: -pos.z as f64,
|
|
||||||
})
|
|
||||||
.last()
|
|
||||||
.unwrap()],
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// let (mut cell, parent) = cells.get_mut(trigger.target).unwrap();
|
|
||||||
// dbg!(&cell);
|
|
||||||
match *ca {
|
|
||||||
CurrentAction::ChangeCell(ck) => match ck {
|
|
||||||
CellKind::Forest { .. } => match cell.kind {
|
|
||||||
CellKind::Dirt | CellKind::Grass { .. } => {
|
|
||||||
cell.kind = CellKind::Forest {
|
|
||||||
wealth: WealthType::MAX / 2,
|
|
||||||
};
|
|
||||||
chunks.get_mut(parent.parent()).unwrap().0 += f32::MAX;
|
|
||||||
grow_task.1.send_blocking(cell.clone()).unwrap();
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
CellKind::Grass { .. } => match cell.kind {
|
|
||||||
CellKind::Dirt => {
|
|
||||||
cell.kind = CellKind::Grass {
|
|
||||||
wealth: WealthType::MAX / 2,
|
|
||||||
};
|
|
||||||
chunks.get_mut(parent.parent()).unwrap().0 += f32::MAX;
|
|
||||||
dbg!(grow_task.1.send_blocking(cell.clone()));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
CurrentAction::AddAnimal(ak) => {
|
|
||||||
if cell.kind.can_place_animal(ak) {
|
|
||||||
let v_cell = voronoi.0.cell(cell.voronoi_id);
|
|
||||||
let cell_pos = v_cell.site_position();
|
|
||||||
cmds.spawn((
|
|
||||||
Animal { kind: ak },
|
|
||||||
Transform::from_xyz(cell_pos.x as f32, 0., -cell_pos.y as f32),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
use bevy::pbr::{ExtendedMaterial, MaterialExtension};
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use bevy::render::render_resource::*;
|
|
||||||
use noise::utils::{NoiseMapBuilder, PlaneMapBuilder};
|
|
||||||
use noise::{Fbm, Perlin};
|
|
||||||
|
|
||||||
const SHADER_PATH: &str = "shaders/cell.wgsl";
|
|
||||||
|
|
||||||
#[derive(Asset, AsBindGroup, Clone, Debug, Reflect)]
|
|
||||||
pub struct CellMaterialExtension {}
|
|
||||||
|
|
||||||
pub type CellMaterial = ExtendedMaterial<StandardMaterial, CellMaterialExtension>;
|
|
||||||
|
|
||||||
impl MaterialExtension for CellMaterialExtension {
|
|
||||||
// fn vertex_shader() -> ShaderRef {
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn fragment_shader() -> ShaderRef {
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn deferred_vertex_shader() -> ShaderRef {
|
|
||||||
// info!("d_vertex_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn deferred_fragment_shader() -> ShaderRef {
|
|
||||||
// info!("d_fragment_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn prepass_vertex_shader() -> ShaderRef {
|
|
||||||
// info!("p_vertex_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn prepass_fragment_shader() -> ShaderRef {
|
|
||||||
// info!("p_fragment_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn cell_material() -> CellMaterial {
|
|
||||||
CellMaterial {
|
|
||||||
base: StandardMaterial {
|
|
||||||
alpha_mode: AlphaMode::Mask(0.5),
|
|
||||||
interpolation_method: bevy::pbr::InterpolationMethod::Flat,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
extension: CellMaterialExtension {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[derive(Clone, AsBindGroup, Asset, TypePath)]
|
|
||||||
// pub struct CellMaterial {}
|
|
||||||
|
|
||||||
// impl Material for CellMaterial {
|
|
||||||
// fn vertex_shader() -> ShaderRef {
|
|
||||||
// info!("vertex_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn fragment_shader() -> ShaderRef {
|
|
||||||
// info!("fragment_shader");
|
|
||||||
// SHADER_PATH.into()
|
|
||||||
// }
|
|
||||||
// fn alpha_mode(&self) -> AlphaMode {
|
|
||||||
// AlphaMode::Mask(0.5)
|
|
||||||
// }
|
|
||||||
// fn opaque_render_method(&self) -> bevy::pbr::OpaqueRendererMethod {
|
|
||||||
// bevy::pbr::OpaqueRendererMethod::Forward
|
|
||||||
// }
|
|
||||||
// fn specialize(
|
|
||||||
// pipeline: &bevy::pbr::MaterialPipeline<Self>,
|
|
||||||
// descriptor: &mut RenderPipelineDescriptor,
|
|
||||||
// layout: &bevy::render::mesh::MeshVertexBufferLayoutRef,
|
|
||||||
// key: bevy::pbr::MaterialPipelineKey<Self>,
|
|
||||||
// ) -> Result<(), SpecializedMeshPipelineError> {
|
|
||||||
// let vertex_layout = layout.0.get_layout(&[
|
|
||||||
// Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
|
||||||
// Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
|
|
||||||
// Mesh::ATTRIBUTE_COLOR.at_shader_location(2),
|
|
||||||
// ])?;
|
|
||||||
// descriptor.vertex.buffers = vec![vertex_layout];
|
|
||||||
|
|
||||||
// // let fragment_layout = layout.0.get_layout(&[
|
|
||||||
// // Mesh::ATTRIBUTE_POSITION.at_shader_location(0),
|
|
||||||
// // Mesh::ATTRIBUTE_NORMAL.at_shader_location(1),
|
|
||||||
// // Mesh::ATTRIBUTE_COLOR.at_shader_location(2),
|
|
||||||
// // ])?;
|
|
||||||
// Ok(())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn cell_material() -> CellMaterial {
|
|
||||||
// CellMaterial {}
|
|
||||||
// }
|
|
39
src/map/picking.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use bevy::{picking::{backend::{HitData, PointerHits}, pointer::{PointerId, PointerLocation}}, prelude::*, window::PrimaryWindow};
|
||||||
|
use voronoice::Point;
|
||||||
|
|
||||||
|
use super::{CellsEntities, MapMarker, Voronoi};
|
||||||
|
|
||||||
|
pub fn picking_backend(
|
||||||
|
cam: Query<(&Transform, Entity), With<Camera2d>>,
|
||||||
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
pointers: Query<(&PointerId, &PointerLocation)>,
|
||||||
|
map: Query<(&Voronoi, &CellsEntities, &Transform), With<MapMarker>>,
|
||||||
|
mut output: EventWriter<PointerHits>
|
||||||
|
) {
|
||||||
|
let (cam, cam_id) = cam.single();
|
||||||
|
let window = window.single();
|
||||||
|
let (voronoi, cells_entities, map_pos) = map.single();
|
||||||
|
let mut last_cell = 0;
|
||||||
|
for (id, l) in pointers.iter() {
|
||||||
|
if let Some(mut pos) = l.location().map(|l| l.position) {
|
||||||
|
pos -= window.size()/2.;
|
||||||
|
pos *= cam.scale.xy();
|
||||||
|
pos.x += cam.translation.x;
|
||||||
|
pos.y -= cam.translation.y;
|
||||||
|
if let Some(c) = voronoi.0.cell(last_cell).iter_path(Point { x: pos.x as f64, y: -pos.y as f64 }).last() {
|
||||||
|
last_cell = c;
|
||||||
|
output.send(PointerHits {
|
||||||
|
pointer: *id,
|
||||||
|
picks: vec![(cells_entities.0[c], HitData {
|
||||||
|
camera: cam_id,
|
||||||
|
depth: map_pos.translation.z,
|
||||||
|
position: Some(Vec3 { x: pos.x, y: pos.y, z: map_pos.translation.z }),
|
||||||
|
normal: None
|
||||||
|
})],
|
||||||
|
order: map_pos.translation.z
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/time.rs
@ -1,34 +0,0 @@
|
|||||||
use std::{
|
|
||||||
sync::{
|
|
||||||
atomic::{AtomicU64, Ordering},
|
|
||||||
Arc,
|
|
||||||
},
|
|
||||||
time::Duration,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bevy::prelude::*;
|
|
||||||
|
|
||||||
pub struct Plugin;
|
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.insert_resource(GameTime {
|
|
||||||
current: Duration::ZERO,
|
|
||||||
secs: Arc::new(AtomicU64::new(0)),
|
|
||||||
speed: 3600. * 24. * 365.,
|
|
||||||
})
|
|
||||||
.add_systems(PreUpdate, update_time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource)]
|
|
||||||
pub struct GameTime {
|
|
||||||
pub current: Duration,
|
|
||||||
pub secs: Arc<AtomicU64>,
|
|
||||||
pub speed: f32, // = game time / real time
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_time(mut gt: ResMut<GameTime>, time: Res<Time>) {
|
|
||||||
let speed = gt.speed;
|
|
||||||
gt.current += Duration::from_secs_f32(time.delta_secs() * speed);
|
|
||||||
gt.secs.store(gt.current.as_secs(), Ordering::Release);
|
|
||||||
}
|
|
243
src/ui.rs
@ -1,211 +1,96 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use bevy::{
|
use bevy::{input::mouse::MouseWheel, math::{NormedVectorSpace, VectorSpace}, picking::{focus::HoverMap, pointer::PointerId}, prelude::*, utils::HashMap, window::PrimaryWindow};
|
||||||
asset::embedded_asset,
|
|
||||||
input::mouse::MouseWheel,
|
|
||||||
math::NormedVectorSpace,
|
|
||||||
picking::{hover::HoverMap, pointer::PointerId},
|
|
||||||
prelude::*,
|
|
||||||
};
|
|
||||||
use mevy::*;
|
|
||||||
|
|
||||||
use crate::camera::CameraMarker;
|
use crate::map::{self, MapMarker};
|
||||||
use crate::map::{AnimalKind, CellKind};
|
|
||||||
|
|
||||||
// #77767b
|
|
||||||
const TABBAR_COLOR: Color = Color::srgb(119. / 255., 118. / 255., 123. / 255.);
|
|
||||||
|
|
||||||
// #E8E8E8
|
|
||||||
const ENABLED_BUTTON_COLOR: Color = Color::srgb(232. / 255., 232. / 255., 232. / 255.);
|
|
||||||
|
|
||||||
pub struct Plugin;
|
pub struct Plugin;
|
||||||
impl bevy::prelude::Plugin for Plugin {
|
impl bevy::prelude::Plugin for Plugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.init_resource::<CurrentAction>()
|
app.add_systems(Startup, setup)
|
||||||
.add_systems(Startup, setup)
|
|
||||||
.add_systems(Update, zoom_with_scroll);
|
.add_systems(Update, zoom_with_scroll);
|
||||||
embedded_asset!(app, "../assets/ui/enabled_tree.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/disabled_tree.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/enabled_grass.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/disabled_grass.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/enabled_cross.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/disabled_cross.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/enabled_goat.png");
|
|
||||||
embedded_asset!(app, "../assets/ui/disabled_goat.png");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Resource, Default, PartialEq, Eq)]
|
#[derive(Component)]
|
||||||
pub enum CurrentAction {
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
ChangeCell(CellKind),
|
|
||||||
AddAnimal(AnimalKind),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component, Debug)]
|
|
||||||
struct PointersDragging(HashMap<PointerId, Vec2>);
|
struct PointersDragging(HashMap<PointerId, Vec2>);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct MapUIComponent;
|
pub struct MapUIComponent;
|
||||||
|
|
||||||
fn setup(mut world: Commands, asset_server: Res<AssetServer>) {
|
fn setup(
|
||||||
// Spawn all ui elements as children of this one
|
mut cmds: Commands
|
||||||
entity! {
|
) {
|
||||||
<world>
|
cmds.spawn((
|
||||||
Node {width: 100%, height: 100%, display: Display::Flex, flex_direction: FlexDirection::Column, !};
|
Node {
|
||||||
Pickable {
|
width: Val::Percent(100.0),
|
||||||
|
height: Val::Percent(100.0),
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
justify_content: JustifyContent::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
PickingBehavior {
|
||||||
should_block_lower: false,
|
should_block_lower: false,
|
||||||
is_hoverable: true
|
is_hoverable: true
|
||||||
};
|
},
|
||||||
.observe(|trigger: Trigger<Pointer<DragEnd>>, mut ptrs: Query<&mut PointersDragging>| {
|
MapUIComponent,
|
||||||
if trigger.button == PointerButton::Primary {
|
PointersDragging(HashMap::new())
|
||||||
ptrs.single_mut().unwrap().0.remove(&trigger.pointer_id);
|
)).observe(|trigger: Trigger<Pointer<DragStart>>, mut ptrs: Query<&mut PointersDragging>| {
|
||||||
}
|
let event = trigger.event();
|
||||||
});
|
// dbg!(event);
|
||||||
.observe(|
|
if event.button == PointerButton::Primary {
|
||||||
trigger: Trigger<Pointer<Drag>>,
|
ptrs.get_mut(event.target).unwrap().0.insert(event.pointer_id, event.pointer_location.position);
|
||||||
mut ptrs: Query<&mut PointersDragging>,
|
}
|
||||||
mut cam: Query<&mut Transform, With<CameraMarker>>,
|
}).observe(|trigger: Trigger<Pointer<DragEnd>>, mut ptrs: Query<&mut PointersDragging>| {
|
||||||
| {
|
let event = trigger.event();
|
||||||
if trigger.button == PointerButton::Primary {
|
// dbg!(event);
|
||||||
let mut ptrs = ptrs.single_mut().unwrap();
|
if event.button == PointerButton::Primary {
|
||||||
if !ptrs.0.contains_key(&trigger.pointer_id) {
|
ptrs.get_mut(event.target).unwrap().0.remove(&event.pointer_id);
|
||||||
return
|
}
|
||||||
}
|
}).observe(|
|
||||||
let mut cam = cam.single_mut().unwrap();
|
trigger: Trigger<Pointer<Drag>>,
|
||||||
|
mut ptrs: Query<&mut PointersDragging>,
|
||||||
|
mut cam: Query<&mut Transform, With<Camera2d>>,
|
||||||
|
| {
|
||||||
|
let event = trigger.event();
|
||||||
|
// dbg!(event);
|
||||||
|
if event.button == PointerButton::Primary {
|
||||||
|
let mut cam = cam.single_mut();
|
||||||
|
let mut ptrs = ptrs.get_mut(event.target).unwrap();
|
||||||
|
|
||||||
let old_midpoint = ptrs.0.values().fold(Vec2::ZERO, |acc, pos| acc + (pos/ptrs.0.len() as f32));
|
let old_midpoint = ptrs.0.values().fold(Vec2::ZERO, |acc, pos| acc + (pos/ptrs.0.len() as f32));
|
||||||
let old_d_to_midpoint = ptrs.0.values().fold(0., |acc, pos| acc + (old_midpoint-pos).norm());
|
let old_d_to_midpoint = ptrs.0.values().fold(0., |acc, pos| acc + (old_midpoint-pos).norm());
|
||||||
ptrs.0.insert(trigger.pointer_id, trigger.pointer_location.position);
|
ptrs.0.insert(event.pointer_id, event.pointer_location.position);
|
||||||
let new_midpoint = ptrs.0.values().fold(Vec2::ZERO, |acc, pos| acc + (pos/ptrs.0.len() as f32));
|
let new_midpoint = ptrs.0.values().fold(Vec2::ZERO, |acc, pos| acc + (pos/ptrs.0.len() as f32));
|
||||||
let new_d_to_midpoint = ptrs.0.values().fold(0., |acc, pos| acc + (new_midpoint-pos).norm());
|
let new_d_to_midpoint = ptrs.0.values().fold(0., |acc, pos| acc + (new_midpoint-pos).norm());
|
||||||
|
|
||||||
// move camera
|
// move camera
|
||||||
cam.translation.x -= (new_midpoint.x - old_midpoint.x)*cam.translation.z*0.001;
|
cam.translation.x -= (new_midpoint.x - old_midpoint.x)*cam.scale.x;
|
||||||
cam.translation.z -= (new_midpoint.y - old_midpoint.y)*cam.translation.z*0.001;
|
cam.translation.y += (new_midpoint.y - old_midpoint.y)*cam.scale.y;
|
||||||
|
|
||||||
if ptrs.0.len() > 1 {
|
if ptrs.0.len() > 1 {
|
||||||
let forward = cam.forward();
|
cam.scale *= old_d_to_midpoint/new_d_to_midpoint;
|
||||||
let z = cam.translation.z;
|
|
||||||
cam.translation += forward * (z * (1. - (new_d_to_midpoint/old_d_to_midpoint)) / forward.z);
|
|
||||||
// cam.scale *= old_d_to_midpoint/new_d_to_midpoint;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
}
|
||||||
[map][
|
);
|
||||||
Node{
|
// Spawn all ui elements as children of this one
|
||||||
flex_grow: 1.,
|
|
||||||
!};
|
|
||||||
PointersDragging(HashMap::new());
|
|
||||||
MapUIComponent;
|
|
||||||
Pickable {
|
|
||||||
should_block_lower: false,
|
|
||||||
is_hoverable: true
|
|
||||||
};
|
|
||||||
.observe(|trigger: Trigger<Pointer<DragStart>>, mut ptrs: Query<&mut PointersDragging>| {
|
|
||||||
if trigger.button == PointerButton::Primary {
|
|
||||||
if let Ok(mut ptrs) = ptrs.get_mut(trigger.target()) {
|
|
||||||
ptrs.0.insert(trigger.pointer_id, trigger.pointer_location.position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
]
|
|
||||||
[
|
|
||||||
Node{
|
|
||||||
width: 100%,
|
|
||||||
height: 10vh,
|
|
||||||
align_self: AlignSelf::FlexEnd,
|
|
||||||
!};
|
|
||||||
Button;
|
|
||||||
BackgroundColor(TABBAR_COLOR);
|
|
||||||
[forest][
|
|
||||||
ImageNode::new(asset_server.load("embedded://forestiles/../assets/ui/enabled_tree.png"));
|
|
||||||
Node {
|
|
||||||
// height: 80%,
|
|
||||||
// margin: [>1vh],
|
|
||||||
!};
|
|
||||||
BackgroundColor(TABBAR_COLOR);
|
|
||||||
.observe(move |trigger: Trigger<Pointer<Click>>, mut ca: ResMut<CurrentAction>, mut bg: Query<&mut BackgroundColor>| {
|
|
||||||
if trigger.button == PointerButton::Primary {
|
|
||||||
if matches!(*ca, CurrentAction::ChangeCell(CellKind::Forest {..})) {
|
|
||||||
*ca = CurrentAction::None;
|
|
||||||
bg.get_mut(forest).unwrap().0 = TABBAR_COLOR;
|
|
||||||
} else {
|
|
||||||
*ca = CurrentAction::ChangeCell(CellKind::Forest {wealth: 0});
|
|
||||||
bg.get_mut(forest).unwrap().0 = ENABLED_BUTTON_COLOR;
|
|
||||||
}
|
|
||||||
bg.get_mut(grass).unwrap().0 = TABBAR_COLOR;
|
|
||||||
bg.get_mut(goat).unwrap().0 = TABBAR_COLOR;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
]
|
|
||||||
[grass][
|
|
||||||
ImageNode::new(asset_server.load("embedded://forestiles/../assets/ui/enabled_grass.png"));
|
|
||||||
Node {
|
|
||||||
// height: 80%,
|
|
||||||
// margin: [>1vh],
|
|
||||||
!};
|
|
||||||
BackgroundColor(TABBAR_COLOR);
|
|
||||||
.observe(move |trigger: Trigger<Pointer<Click>>, mut ca: ResMut<CurrentAction>, mut bg: Query<&mut BackgroundColor>| {
|
|
||||||
if trigger.button == PointerButton::Primary {
|
|
||||||
if matches!(*ca, CurrentAction::ChangeCell(CellKind::Grass {..})) {
|
|
||||||
*ca = CurrentAction::None;
|
|
||||||
bg.get_mut(grass).unwrap().0 = TABBAR_COLOR;
|
|
||||||
} else {
|
|
||||||
*ca = CurrentAction::ChangeCell(CellKind::Grass {wealth: 0});
|
|
||||||
bg.get_mut(grass).unwrap().0 = ENABLED_BUTTON_COLOR;
|
|
||||||
}
|
|
||||||
bg.get_mut(forest).unwrap().0 = TABBAR_COLOR;
|
|
||||||
bg.get_mut(goat).unwrap().0 = TABBAR_COLOR;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
]
|
|
||||||
[goat][
|
|
||||||
ImageNode::new(asset_server.load("embedded://forestiles/../assets/ui/enabled_goat.png"));
|
|
||||||
Node {
|
|
||||||
// height: 80%,
|
|
||||||
// margin: [>1vh],
|
|
||||||
!};
|
|
||||||
BackgroundColor(TABBAR_COLOR);
|
|
||||||
.observe(move |trigger: Trigger<Pointer<Click>>, mut ca: ResMut<CurrentAction>, mut bg: Query<&mut BackgroundColor>| {
|
|
||||||
if trigger.button == PointerButton::Primary {
|
|
||||||
if *ca == CurrentAction::AddAnimal(AnimalKind::Goat) {
|
|
||||||
*ca = CurrentAction::None;
|
|
||||||
bg.get_mut(goat).unwrap().0 = TABBAR_COLOR;
|
|
||||||
} else {
|
|
||||||
*ca = CurrentAction::AddAnimal(AnimalKind::Goat);
|
|
||||||
bg.get_mut(goat).unwrap().0 = ENABLED_BUTTON_COLOR;
|
|
||||||
}
|
|
||||||
bg.get_mut(forest).unwrap().0 = TABBAR_COLOR;
|
|
||||||
bg.get_mut(grass).unwrap().0 = TABBAR_COLOR;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoom_with_scroll(
|
fn zoom_with_scroll(
|
||||||
mut cam: Query<&mut Transform, With<CameraMarker>>,
|
mut cam: Query<&mut Transform, With<Camera2d>>,
|
||||||
mut ev_scroll: EventReader<MouseWheel>,
|
mut ev_scroll: EventReader<MouseWheel>,
|
||||||
hover_map: Res<HoverMap>,
|
hover_map: Res<HoverMap>,
|
||||||
map_ui_id: Query<Entity, With<MapUIComponent>>,
|
window: Query<&Window, With<PrimaryWindow>>,
|
||||||
|
map_ui_id: Query<Entity, With<MapUIComponent>>
|
||||||
) {
|
) {
|
||||||
let map_ui_id = map_ui_id.single().unwrap();
|
let map_ui_id = map_ui_id.single();
|
||||||
if hover_map
|
if hover_map.get(&PointerId::Mouse).and_then(|hovered_ids| hovered_ids.get(&map_ui_id)).is_some() {
|
||||||
.get(&PointerId::Mouse)
|
let window = window.single();
|
||||||
.and_then(|hovered_ids| hovered_ids.get(&map_ui_id))
|
let mut cam = cam.single_mut();
|
||||||
.is_some()
|
|
||||||
{
|
|
||||||
let mut cam = cam.single_mut().unwrap();
|
|
||||||
for ev in ev_scroll.read() {
|
for ev in ev_scroll.read() {
|
||||||
let forward = cam.forward();
|
let scale = (cam.scale.x-(ev.y*0.1/window.width().min(window.height()))).clamp(0.0001, 2./window.width().min(window.height()));
|
||||||
cam.translation += forward * (ev.y * 0.1);
|
cam.scale = Vec3::new(scale, scale, scale);
|
||||||
// cam.fov = cam.fov + (ev.y * 0.1);
|
|
||||||
// let scale = (cam.scale.x - (ev.y * 0.1 / window.width().min(window.height())))
|
|
||||||
// .clamp(0.0001, 2. / window.width().min(window.height()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|