make wasm work + cleaning + fix mouse

This commit is contained in:
Arkitu 2024-12-26 12:46:03 +01:00
parent a07f0fee0f
commit f49be0b90d
12 changed files with 20 additions and 1314 deletions

View File

@ -1,6 +1,2 @@
[target.wasm32-unknown-unknown]
runner = "wasm-server-runner"
# [target.aarch64-unknown-linux-gnu]
# linker = "clang"
# rustflags = ["-C", "link-arg=-fuse-ld=/usr/bin/mold"]

68
Cargo.lock generated
View File

@ -196,24 +196,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arboard"
version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df099ccb16cd014ff054ac1bf392c67feeef57164b05c42f037cd40f5d4357f4"
dependencies = [
"clipboard-win",
"core-graphics",
"image",
"log",
"objc2",
"objc2-app-kit",
"objc2-foundation",
"parking_lot",
"windows-sys 0.48.0",
"x11rb",
]
[[package]]
name = "arrayref"
version = "0.3.8"
@ -655,7 +637,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "954fbe8551af4b40767ea9390ec7d32fe1070a6ab55d524cf0868c17f8469a55"
dependencies = [
"arboard",
"bevy_app",
"bevy_asset",
"bevy_derive",
@ -676,7 +657,6 @@ dependencies = [
"encase",
"js-sys",
"log",
"thread_local",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@ -1605,15 +1585,6 @@ dependencies = [
"libloading",
]
[[package]]
name = "clipboard-win"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15efe7a882b08f34e38556b14f2fb3daa98769d06c7f0c1b076dfd0d983bc892"
dependencies = [
"error-code",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -2161,12 +2132,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "error-code"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5d9305ccc6942a704f4335694ecd3de2ea531b114ac2d51f5f843750787a92f"
[[package]]
name = "euclid"
version = "0.22.11"
@ -2832,7 +2797,6 @@ dependencies = [
"byteorder-lite",
"num-traits",
"png",
"tiff",
]
[[package]]
@ -2936,12 +2900,6 @@ dependencies = [
"libc",
]
[[package]]
name = "jpeg-decoder"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
[[package]]
name = "js-sys"
version = "0.3.76"
@ -4438,17 +4396,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "tiff"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
@ -5007,12 +4954,6 @@ dependencies = [
"web-sys",
]
[[package]]
name = "weezl"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "wgpu"
version = "22.1.0"
@ -5427,15 +5368,6 @@ dependencies = [
"windows-targets 0.42.2",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"

View File

@ -17,10 +17,6 @@ path = "src/lib.rs"
name = "forestiles"
[dependencies]
# bevy = { version = "0.15", default-features = false, features = [
# "bevy_color","bevy_core_pipeline","bevy_render","bevy_winit","bevy_window","multi_threaded","wayland","bevy_sprite",
# "bevy_picking","bevy_mesh_picking_backend","bevy_ui_picking_backend","bevy_sprite_picking_backend"
# ]}
bevy = { version = "0.15", default-features = false, features = [
"android-native-activity",
"android_shared_stdcxx",
@ -57,20 +53,16 @@ bevy = { version = "0.15", default-features = false, features = [
"webgl2",
"wayland",
]}
bevy-inspector-egui = { version = "0.28" }
# winit = { version = "0.30", features = ["rwh_05", "android-native-activity"] }
# env_logger = "0.11"
bevy-inspector-egui = { version = "0.28", default-features = false, features = [
"bevy_pbr",
"bevy_image",
"bevy_render",
"egui_open_url"
]}
log = "0.4"
# wgpu = "22.1"
# cfg-if = "1"
# pollster = "0.3"
# bytemuck = { version = "1.18", features = [ "derive" ] }
rand = { version = "0.8", features = ["small_rng"] }
# nalgebra = "0.33"
voronoice = "0.2"
noise = "0.9"
# lazy_static = "1.5"
# image = { version = "0.25", default-features = false, features = ["rayon", "png"]}
[target.'cfg(target_arch = "wasm32")'.dependencies]
console_error_panic_hook = "0.1.6"
@ -99,9 +91,9 @@ opt-level = 3
[package.metadata.android]
package = "org.forestiles.example"
apk_name = "forestiles"
# strip = "strip"
strip = "strip"
# see https://github.com/rust-mobile/cargo-apk
# assets = "assets"
assets = "assets"
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
[package.metadata.android.sdk]

View File

@ -49,6 +49,7 @@ fn move_cam(
// movement.y += delta.y*cam.scale.y/pressed_num as f32;
// pointers.0.insert(**id, *new_pos);
// }
let old_midpoint = pressed_on_map.iter().fold(Vec2::ZERO, |acc, (_, _, old_pos, _, _)| {
acc + (old_pos/pressed_on_map.len() as f32)
});
@ -70,4 +71,8 @@ fn move_cam(
cam.scale.x /= zoom;
cam.scale.y /= zoom;
}
for (_, new_pos, _, id, _) in ps {
pointers.0.insert(*id, new_pos);
}
}

View File

@ -1,331 +0,0 @@
use std::sync::Arc;
use bytemuck::{Pod, Zeroable};
use wgpu::{include_wgsl, util::DeviceExt, BindGroup, Buffer, Device, Queue, RenderPipeline, Surface, SurfaceConfiguration, VertexBufferLayout};
use winit::{event::WindowEvent, window::Window};
use crate::state::State;
mod texture;
use texture::{Texture, TEXTURE_DIMENSIONS};
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Debug)]
pub struct Vertex {
pub pos: [f32; 2],
/// Rgba color by default but if texture flag is not set.
/// Else the first 2 f32 are texture coordinates and the 2 last are not used
pub color: [f32; 4],
/// Each bit is used as a flag :
///
/// 1: Scaled and moved according to camera
///
/// 2: Grayscale
///
/// 4: Texture instead of color
///
/// For example 0b001 corresponds to Scaled and 0b011 to Scaled and Grayscale
pub effect: u32,
}
impl Vertex {
const DESC: VertexBufferLayout<'static> = VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &wgpu::vertex_attr_array![0 => Float32x2, 1 => Float32x4, 2 => Uint32],
};
pub const fn new_col(pos: [f32; 2], color: [f32; 4], effect: u32) -> Self {
Self {
pos,
color,
effect
}
}
pub const fn new_tex(pos: [f32; 2], tex_pos: [u16; 2], effect: u32) -> Self {
Self {
pos,
color: [tex_pos[0] as f32/TEXTURE_DIMENSIONS[0] as f32, tex_pos[1] as f32/TEXTURE_DIMENSIONS[1] as f32, 0., 0.],
effect: effect | 0b100
}
}
}
#[repr(C)]
#[derive(Clone, Copy, Zeroable, Pod, Debug)]
pub struct Uniforms {
pub camera: [f32; 2],
pub zooms: [f32; 2]
}
impl Default for Uniforms {
fn default() -> Self {
Self {
camera: [0., 0.],
zooms: [1., 1.]
}
}
}
pub struct Graphics<'a> {
// window: &'a Window,
pub surface_config: SurfaceConfiguration,
surface: Surface<'a>,
device: Device,
render_pipeline: RenderPipeline,
queue: Queue,
vertex_buf: Buffer,
index_buf: Buffer,
uniforms_buf: Buffer,
uniforms_bind_group: BindGroup,
diffuse_bind_group: BindGroup,
}
impl<'a> Graphics<'a> {
pub async fn init(state: &State, window: Arc<Window>) -> Self {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::PRIMARY,
dx12_shader_compiler: Default::default(),
..Default::default()
});
let surface = instance.create_surface(window).unwrap();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
force_fallback_adapter: false,
// Request an adapter which can render to our surface
compatible_surface: Some(&surface),
})
.await
.expect("Failed to find an appropriate adapter");
// Create the logical device and command queue
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
required_limits: wgpu::Limits::default()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::MemoryUsage,
},
None,
)
.await
.expect("Failed to create device");
let vertex_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: &[bytemuck::cast_slice::<Vertex, _>(&state.vertices), &[0; 100000]].concat(),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
let index_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: &[bytemuck::cast_slice::<u32, _>(&state.indices), &[0; 100000]].concat(),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
let uniforms_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Uniforms Buffer"),
contents: bytemuck::cast_slice(&[state.uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let uniforms_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
}
],
label: Some("Uniforms Bind Group Layout"),
});
let uniforms_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &uniforms_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: uniforms_buf.as_entire_binding(),
}
],
label: Some("Uniforms Bind Group"),
});
let texture_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
// This should match the filterable field of the
// corresponding Texture entry above.
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
// Load the shaders from disk
let shader = device.create_shader_module(include_wgsl!("shader.wgsl"));
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[
&uniforms_bind_group_layout,
&texture_bind_group_layout
],
push_constant_ranges: &[],
});
let swapchain_capabilities = surface.get_capabilities(&adapter);
let swapchain_format = swapchain_capabilities.formats[0];
let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[
Vertex::DESC
],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: swapchain_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::One,
operation: wgpu::BlendOperation::Add
}
}),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState::default(),
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview: None,
cache: None,
});
let surface_config = surface
.get_default_config(&adapter, 1, 1)
.unwrap();
surface.configure(&device, &surface_config);
let diffuse_bytes = include_bytes!("../assets/texture.png");
let diffuse_texture = Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png");
let diffuse_bind_group = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
}
],
label: Some("diffuse_bind_group"),
}
);
Self {
// window,
surface_config,
surface,
device,
render_pipeline,
queue,
vertex_buf,
index_buf,
uniforms_buf,
uniforms_bind_group,
diffuse_bind_group
}
}
pub fn window_event(&mut self, event: &WindowEvent, window: &Window) {
match event {
WindowEvent::Resized(new_size) => {
// Reconfigure the surface with the new size
self.surface_config.width = new_size.width;
self.surface_config.height = new_size.height;
self.surface.configure(&self.device, &self.surface_config);
// On macos the window needs to be redrawn manually after resizing
window.request_redraw();
},
_ => {}
}
}
pub fn render(&self, state: &State) {
let frame = self.surface
.get_current_texture()
.expect("Failed to acquire next swap chain texture");
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let mut encoder =
self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: None,
});
{
let mut rpass =
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
rpass.set_pipeline(&self.render_pipeline);
rpass.set_bind_group(0, &self.uniforms_bind_group, &[]);
rpass.set_bind_group(1, &self.diffuse_bind_group, &[]);
rpass.set_vertex_buffer(0, self.vertex_buf.slice(..));
rpass.set_index_buffer(self.index_buf.slice(..), wgpu::IndexFormat::Uint32);
rpass.draw_indexed(0..state.indices.len() as u32, 0, 0..1);
// rpass.draw(0..self.state.vertices.len() as u32, 0..1);
}
self.queue.submit(Some(encoder.finish()));
frame.present();
}
pub fn update(&mut self, state: &State) {
self.queue.write_buffer(&self.vertex_buf, 0, bytemuck::cast_slice(&state.vertices));
self.queue.write_buffer(&self.index_buf, 0, bytemuck::cast_slice(&state.indices));
self.queue.write_buffer(&self.uniforms_buf, 0, bytemuck::cast_slice(&[state.uniforms]));
}
}

View File

@ -1,80 +0,0 @@
use image::GenericImageView;
pub const TEXTURE_DIMENSIONS: [usize; 2] = [64, 64];
pub struct Texture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
}
impl Texture {
pub fn from_bytes(
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
label: &str
) -> Self {
let img = image::load_from_memory(bytes).unwrap();
Self::from_image(device, queue, &img, Some(label))
}
pub fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: &image::DynamicImage,
label: Option<&str>
) -> Self {
let rgba = img.to_rgba8();
let dimensions = img.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(
&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}
);
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
size,
);
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(
&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Nearest,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
}
);
Self { texture, view, sampler }
}
}

View File

@ -1,109 +1,12 @@
// mod graphics;
// mod state;
use bevy::{prelude::*, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}};
use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub mod map;
pub mod camera;
pub mod ui;
use std::{fmt::Debug, sync::Arc};
use log::debug;
// use state::State;
// use graphics::Graphics;
// use winit::{application::ApplicationHandler, dpi::PhysicalSize, event::{Event, WindowEvent}, event_loop::EventLoop, window::{Window, WindowAttributes}};
use bevy::{prelude::*, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}};
use bevy_inspector_egui::quick::WorldInspectorPlugin;
pub fn dbg<V: Debug>(v: V) -> V {
debug!(target: "app", "{:?}", v);
v
}
// struct App<'a> {
// // event_loop: EventLoop<()>,
// window: Option<Arc<Window>>,
// graphics: Option<Graphics<'a>>,
// state: State
// }
// impl App<'_> {
// fn new() -> Self {
// Self {
// window: None,
// graphics: None,
// state: State::new()
// }
// }
// }
// impl ApplicationHandler for App<'_> {
// fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
// #[cfg(not(any(target_family = "wasm", target_os = "android")))]
// let window = event_loop.create_window(
// WindowAttributes::default()
// .with_inner_size(PhysicalSize::new(1080*2/5, 2000*2/5))
// ).unwrap();
// #[cfg(target_os = "android")]
// let window = event_loop.create_window(
// WindowAttributes::default()
// // .with_inner_size(PhysicalSize::new(1080*2/5, 2000*2/5))
// ).unwrap();
// self.window = Some(Arc::new(window));
// self.graphics = Some(pollster::block_on(Graphics::init(&self.state, self.window.clone().unwrap())));
// }
// fn window_event(
// &mut self,
// event_loop: &winit::event_loop::ActiveEventLoop,
// window_id: winit::window::WindowId,
// event: WindowEvent,
// ) {
// match &event {
// WindowEvent::CloseRequested => event_loop.exit(),
// WindowEvent::RedrawRequested => {
// if let Some(g) = &mut self.graphics {
// self.state.update_if_needed();
// self.state.render(self.window.as_ref().unwrap().inner_size());
// g.update(&self.state);
// g.render(&self.state);
// }
// },
// WindowEvent::MouseWheel { delta, .. } => {
// dbg!(delta);
// },
// _ => {}
// }
// self.graphics.as_mut().unwrap().window_event(&event, &self.window.as_ref().unwrap());
// self.state.window_event(&event, &self.window.as_ref().unwrap());
// }
// fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
// if let Some(window) = self.window.as_ref() {
// window.request_redraw();
// }
// }
// }
// #[cfg(not(target_os = "android"))]
#[bevy_main]
pub fn main() {
// #[cfg(target_family = "wasm")]
// let (event_loop, window) = {
// use winit::platform::web::WindowExtWebSys;
// std::panic::set_hook(Box::new(console_error_panic_hook::hook));
// console_log::init().expect("could not initialize logger");
// let event_loop = winit::event_loop::EventLoop::new().unwrap();
// let window = winit::window::WindowBuilder::new().build(&event_loop).unwrap();
// web_sys::window()
// .unwrap()
// .document()
// .unwrap()
// .body()
// .unwrap()
// .append_child(&window.canvas().unwrap())
// .unwrap();
// (event_loop, window)
// };
App::new()
.add_plugins((
DefaultPlugins,
@ -119,25 +22,3 @@ pub fn main() {
))
.run();
}
// #[cfg(target_os = "android")]
// #[no_mangle]
// fn android_main(app: winit::platform::android::activity::AndroidApp) {
// println!("test");
// // use winit::platform::android::{EventLoopBuilderExtAndroid, activity::WindowManagerFlags};
// // android_logger::init_once(
// // android_logger::Config::default()
// // .with_max_level(log::LevelFilter::Debug)
// // .with_filter(android_logger::FilterBuilder::new().parse("app").build())
// // );
// // app.set_window_flags(WindowManagerFlags::KEEP_SCREEN_ON | WindowManagerFlags::FULLSCREEN, WindowManagerFlags::empty());
// // let event_loop = winit::event_loop::EventLoopBuilder::new()
// // .with_android_app(app)
// // .build()
// // .unwrap();
// // event_loop.run_app(&mut App::new()).unwrap();
// }

View File

@ -85,8 +85,8 @@ fn setup(
let mut colors = Vec::new();
let mut indices = Vec::new();
for (c, mut cd) in voronoi.iter_cells().zip(cells_data.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) {
let mut color = cd.color();
for (c, cd) in voronoi.iter_cells().zip(cells_data.iter_mut()).filter(|(_,cd)| cd.kind != CellKind::Forest) {
let color = cd.color();
// if c.site() == selected_tile {
// color[0] = (color[0]+0.4).clamp(0., 1.);
// color[1] = (color[1]+0.4).clamp(0., 1.);

View File

@ -1,52 +0,0 @@
struct Uniforms {
camera: vec2f,
zooms: vec2f
}
@group(0) @binding(0) var<uniform> uniforms : Uniforms;
struct VertexOutput {
@location(0) color: vec4f,
@location(1) effect: u32,
@builtin(position) pos: vec4f
}
@vertex
fn vs_main(
@location(0) pos: vec2f,
@location(1) color: vec4f,
@location(2) effect: u32
) -> VertexOutput {
var screen_pos: vec4f;
if (effect & 1) == 0 {
screen_pos = vec4f(pos, 0, 1);
} else {
screen_pos = vec4f((pos - uniforms.camera) * uniforms.zooms, 0, 1);
}
var out = VertexOutput(
color,
effect,
screen_pos
);
return out;
}
@group(1) @binding(0)
var t_diffuse: texture_2d<f32>;
@group(1) @binding(1)
var s_diffuse: sampler;
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4f {
var color: vec4f;
if (in.effect & 4) == 0 {
color = in.color;
} else {
color = textureSample(t_diffuse, s_diffuse, in.color.xy);
}
// Grayscale
if (in.effect & 2) != 0 {
var v = (color.r*0.299) + (color.g*0.587) + (color.b*0.114);
color = vec4f(v, v, v, color.a);
}
return color;
}

View File

@ -1,368 +0,0 @@
use std::{collections::{BTreeMap, HashMap}, time::{Duration, Instant}};
use log::{debug, trace};
use map::{CellKind, Map};
use rand::prelude::*;
use voronoice::Point;
use winit::{dpi::{PhysicalPosition, PhysicalSize}, event::{DeviceEvent, Event, KeyEvent, MouseButton, MouseScrollDelta, Touch as WTouch, TouchPhase, WindowEvent}, keyboard::{KeyCode, PhysicalKey}, window::Window};
use crate::{dbg, graphics::{Uniforms, Vertex}};
mod entity;
mod map;
mod ui;
use entity::{Entity, EntityKind, ExternOp};
use ui::{Kind, UI};
fn rgba_to_grayscale(c: [f32; 4]) -> [f32; 4] {
let v = (c[0]*0.299) + (c[1]*0.587) + (c[2]*0.114);
[v, v, v, c[3]]
}
struct Touch {
pub pos: PhysicalPosition<f64>,
/// id=1000 is for mouse
pub id: u64,
pub start: Instant
}
impl PartialEq for Touch {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl From<WTouch> for Touch {
fn from(value: WTouch) -> Self {
Self::new(value.location, value.id)
}
}
impl Touch {
pub fn new(pos: PhysicalPosition<f64>, id: u64) -> Self {
Self {
pos,
id,
start: Instant::now()
}
}
}
pub struct State {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
pub uniforms: Uniforms,
zoom: f32,
map: Map,
ui: UI,
start: Instant,
last_frame: Instant,
t: usize, // Time in frames
selected_tile: usize,
framerate: f32, // Update per second
pub entities: BTreeMap<usize, Entity>, // entity id --> Entities
next_eid: usize,
pub cells_entities: HashMap<usize, Vec<usize>>, // cell id --> entities id
/// Also acount for mouse
touches: Vec<Touch>,
mouse_pos: Option<PhysicalPosition<f64>>
}
impl State {
pub fn new() -> Self {
let mut s = Self {
vertices: vec![],
indices: vec![],
uniforms: Uniforms::default(),
zoom: 1.,
start: Instant::now(),
last_frame: Instant::now(),
t: 0,
map: Map::new(0, 0),
ui: UI::new(),
selected_tile: 0,
framerate: 1.,
entities: BTreeMap::new(),
next_eid: 0,
cells_entities: HashMap::new(),
touches: Vec::new(),
mouse_pos: None
};
// Create vertices / indices to estimate vertex / index buffer size
s.render(PhysicalSize::new(1, 1));
s
}
fn set_zoom(&mut self, v: f32) {
self.zoom = v.clamp(1., 10.);
}
fn update_zooms(&mut self, screen_size: PhysicalSize<u32>) {
self.uniforms.zooms = [
(screen_size.height as f32 / screen_size.width as f32).max(1.) * self.zoom,
(screen_size.width as f32 / screen_size.height as f32).max(1.) * self.zoom
];
}
fn handle_click(&mut self, window: &Window, pos: &PhysicalPosition<f64>) {
}
pub fn window_event(&mut self, event: &WindowEvent, window: &Window) {
match event {
WindowEvent::Touch(touch) => {
match touch.phase {
TouchPhase::Started => {
self.touches.push((*touch).into());
},
TouchPhase::Moved => {
let w_size = window.inner_size();
let old_touch_n = self.touches.iter().position(|t| t.id == touch.id).unwrap();
let old_touch = &self.touches[old_touch_n];
let t = [
touch.location.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0],
touch.location.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1]
];
let old_t = [
old_touch.pos.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0],
old_touch.pos.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1]
];
// Handle pinch zoom
if self.touches.len() == 2 {
let old_touch2 = &self.touches[if old_touch_n == 0 {1} else {0}];
let old_t2 = [
old_touch2.pos.x as f32/w_size.width as f32 * Map::WIDTH / self.uniforms.zooms[0],
old_touch2.pos.y as f32/w_size.height as f32 * Map::HEIGHT / self.uniforms.zooms[1]
];
self.set_zoom(self.zoom + (((((t[0]-old_t2[0])*self.zoom).powi(2)+((t[0]-old_t2[0])*self.zoom).powi(2)).sqrt() - (((old_t[0]-old_t2[0])*self.zoom).powi(2)+((old_t[0]-old_t2[0])*self.zoom).powi(2)).sqrt())*4.));
}
self.uniforms.camera[0] -= (t[0] - old_t[0]) / self.touches.len() as f32;
self.uniforms.camera[1] += (t[1] - old_t[1]) / self.touches.len() as f32;
dbg(&self.uniforms.camera);
self.touches[old_touch_n].pos = touch.location;
},
TouchPhase::Ended => {
let old_touch_n = self.touches.iter().position(|t| t.id == touch.id).unwrap();
let old_touch = &self.touches[old_touch_n];
if old_touch.start.elapsed() < Duration::from_millis(500) {
self.handle_click(window, &touch.location);
}
self.touches.remove(self.touches.iter().position(|t| t.id == touch.id).unwrap());
},
TouchPhase::Cancelled => {
self.touches.remove(self.touches.iter().position(|t| t.id == touch.id).unwrap());
}
}
}
WindowEvent::MouseInput { state, button, ..} => {
if state.is_pressed() {
match button {
MouseButton::Left => {
if !self.touches.iter().any(|t| t.id == 1000) {
if let Some(pos) = self.mouse_pos {
self.touches.push(Touch::new(pos, 1000));
}
}
},
MouseButton::Right => {
self.spawn_entity(self.selected_tile, Entity::new(EntityKind::Horse, self.selected_tile));
},
_ => {}
};
} else {
match button {
MouseButton::Left => {
if let Some(i) = self.touches.iter().position(|t| t.id == 1000) {
self.touches.remove(i);
}
},
_ => {}
}
}
},
WindowEvent::CursorLeft { .. } => {
self.mouse_pos = None;
if let Some(i) = self.touches.iter().position(|t| t.id == 1000) {
self.touches.remove(i);
}
},
WindowEvent::CursorMoved { position, .. } => {
self.mouse_pos = Some(*position);
let w_size = window.inner_size();
self.update_zooms(w_size);
let pos = Point {
x: (((position.x / w_size.width as f64)*2.)-1.)/(self.uniforms.zooms[0] as f64) + self.uniforms.camera[0] as f64,
y: -(((position.y / w_size.height as f64)*2.)-1.)/(self.uniforms.zooms[1] as f64) + self.uniforms.camera[1] as f64
};
let c = self.map.voronoi.cell(self.selected_tile);
if let Some(i) = self.touches.iter().position(|t| t.id == 1000) {
self.touches[i].pos = *position;
for i in c.iter_path(pos.clone()) {
self.selected_tile = i;
let cd = &mut self.map.cells_data[self.selected_tile];
if let CellKind::Dirt = cd.kind {
cd.kind = CellKind::Forest;
}
}
} else {
self.selected_tile = c.iter_path(pos).last().unwrap();
}
},
WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(kc), state, .. }, .. } => {
if state.is_pressed() {
match kc {
KeyCode::KeyW => {
self.uniforms.camera[1] += 0.1 * self.zoom;
},
KeyCode::KeyS => {
self.uniforms.camera[1] -= 0.1 / self.zoom;
},
KeyCode::KeyA => {
self.uniforms.camera[0] -= 0.1 / self.zoom;
},
KeyCode::KeyD => {
self.uniforms.camera[0] += 0.1 / self.zoom;
},
KeyCode::KeyR => {
self.set_zoom(self.zoom + 0.1);
},
KeyCode::KeyF => {
self.set_zoom(self.zoom - 0.1);
},
_ => {}
}
self.update_zooms(window.inner_size());
}
},
WindowEvent::MouseWheel { delta, .. } => {
self.framerate -= match delta {
MouseScrollDelta::PixelDelta(pos) => pos.y as f32,
MouseScrollDelta::LineDelta(_, y) => *y
} * 0.1;
self.framerate = self.framerate.max(0.);
}
_ => {}
}
}
pub fn render(&mut self, screen_size: PhysicalSize<u32>) {
trace!("render");
self.update_zooms(screen_size);
self.vertices = Vec::new();
self.indices = Vec::new();
for (c, cd) in self.map.voronoi.iter_cells().zip(self.map.cells_data.iter()).filter(|(_,cd)| cd.kind != CellKind::Forest) {
let mut color = cd.color();
if c.site() == self.selected_tile {
color[0] = (color[0]+0.4).clamp(0., 1.);
color[1] = (color[1]+0.4).clamp(0., 1.);
color[2] = (color[2]+0.4).clamp(0., 1.);
}
let vs = c.iter_vertices().collect::<Vec<_>>();
let i = self.vertices.len() as u32;
for v in vs.iter() {
self.vertices.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1));
}
for v in 1..(vs.len()-1) as u32 {
self.indices.push(i);
self.indices.push(i+v);
self.indices.push(i+v+1);
}
}
for e in self.entities.values() {
e.render(&mut self.vertices, &mut self.indices, &self.map, );
}
for (c, cd) in self.map.voronoi.iter_cells().zip(self.map.cells_data.iter()).filter(|(_,cd)| cd.kind == CellKind::Forest) {
let mut color = cd.color();
if c.site() == self.selected_tile {
color[0] = (color[0]+0.4).clamp(0., 1.);
color[1] = (color[1]+0.4).clamp(0., 1.);
color[2] = (color[2]+0.4).clamp(0., 1.);
}
let vs = c.iter_vertices().collect::<Vec<_>>();
let i = self.vertices.len() as u32;
for v in vs.iter() {
self.vertices.push(Vertex::new_col([v.x as f32, v.y as f32], color, 1));
}
for v in 1..(vs.len()-1) as u32 {
self.indices.push(i);
self.indices.push(i+v);
self.indices.push(i+v+1);
}
}
self.ui.render(&mut self.vertices, &mut self.indices, screen_size);
}
pub fn update_if_needed(&mut self) {
while self.last_frame.elapsed().as_secs_f32() > 1. / self.framerate {
self.update();
}
}
pub fn update(&mut self) {
trace!("update");
self.last_frame = Instant::now();
let mut rng = thread_rng();
let mut new_kind = Vec::new();
for cd in self.map.cells_data.iter_mut() {
cd.update();
}
for cd in self.map.cells_data.iter() {
if cd.kind == CellKind::Forest || cd.kind == CellKind::Grass {
let r = rng.gen::<f32>();
if r < (0.035*cd.moisture) {
let c = self.map.voronoi.cell(cd.cid);
let n = c.iter_neighbors().choose(&mut rng).unwrap();
let k = if r < (0.005*cd.moisture) && (self.map.cells_data[n].kind == CellKind::Dirt || self.map.cells_data[n].kind == CellKind::Grass) && cd.kind == CellKind::Forest {
Some(CellKind::Forest)
} else if self.map.cells_data[n].kind == CellKind::Dirt {
Some(CellKind::Grass)
} else {
None
};
if let Some(k) = k {
new_kind.push((n, k));
}
}
}
}
for (n, k) in new_kind {
let cd = &mut self.map.cells_data[n];
cd.kind = k;
cd.resource = 1.;
}
// if Option is None remove the entity
let mut entities_to_move: Vec<(usize, Option<usize>)> = Vec::new();
for (eid, e) in self.entities.iter_mut() {
match e.update(&mut self.map, self.t, &mut rng) {
Some(ExternOp::Move(cid)) => {
entities_to_move.push((*eid, Some(cid)));
},
Some(ExternOp::Remove) => {
entities_to_move.push((*eid, None));
},
None => {}
}
}
for (eid, new_cid) in entities_to_move {
let entity = self.entities.get_mut(&eid).unwrap();
let cell_entities = self.cells_entities.get_mut(&entity.cid).unwrap();
cell_entities.remove(cell_entities.iter().position(|e| *e == eid).unwrap());
match new_cid {
Some(new_cid) => {
entity.cid = new_cid;
match self.cells_entities.get_mut(&new_cid) {
Some(v) => v.push(eid),
None => {self.cells_entities.insert(new_cid, vec![eid]);}
}
},
None => {self.entities.remove(&eid);}
}
}
self.t += 1;
}
pub fn spawn_entity(&mut self, cid: usize, e: Entity) {
let eid = self.next_eid;
self.next_eid += 1;
self.entities.insert(eid, e);
if let Some(v) = self.cells_entities.get_mut(&cid) {
v.push(eid);
} else {
self.cells_entities.insert(cid, vec![eid]);
}
}
}

View File

@ -1,221 +0,0 @@
use std::time::Instant;
use rand::{rngs::ThreadRng, seq::IteratorRandom, Rng};
use crate::graphics::Vertex;
use super::{map::CellKind, Map};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityKind {
Horse
}
impl EntityKind {
const fn is_herbivore(&self) -> bool {
match self {
Self::Horse => true
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntityState {
Walking(Instant), // Start of walk
Resting
}
#[derive(Debug)]
pub enum ExternOp {
Remove,
Move(usize)
}
#[derive(Debug)]
pub struct Entity {
pub cid: usize,
kind: EntityKind,
start: Instant,
state: EntityState,
health: f32 // between 0 and 1
}
impl Entity {
pub fn new(kind: EntityKind, cid: usize, ) -> Self {
Self {
cid,
kind,
start: Instant::now(),
state: EntityState::Walking(Instant::now()),
health: 1.
}
}
pub fn set_health(&mut self, val: f32) {
self.health = val.clamp(0., 1.);
}
pub fn render(&self, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, map: &Map) {
let pos = &map.voronoi.sites()[self.cid];
match self.kind {
EntityKind::Horse => {
let color = [171./255. * self.health, 122./255. * self.health, 50./255. * self.health, 1.];
let dark = [color[0]-0.3, color[1]-0.3, color[2]-0.3, 1.];
let (vs, is) = match self.state {
EntityState::Walking(now) => {
let now = now.elapsed().as_secs_f32()*5.;
(
[
// back left leg
Vertex::new_col([-0.5, 0.3], dark, 1),
Vertex::new_col([-0.4 + (now.sin()*0.1), -0.7 + (now.cos().max(-0.5)*0.1)], dark, 1),
Vertex::new_col([-0.25, 0.1], dark, 1),
// back right leg
Vertex::new_col([-0.5, 0.3], color, 1),
Vertex::new_col([-0.4 + ((now + 1.).sin()*0.1), -0.7 + ((now + 1.).cos().max(-0.5)*0.1)], color, 1),
Vertex::new_col([-0.25, 0.1], color, 1),
// front left leg
Vertex::new_col([0.3, 0.2], dark, 1),
Vertex::new_col([0.4 + ((now-1.).sin()*0.1), -0.7 + ((now-1.).cos().max(-0.5)*0.1)], dark, 1),
Vertex::new_col([0.5, 0.3], dark, 1),
// front right leg
Vertex::new_col([0.3, 0.2], color, 1),
Vertex::new_col([0.4 + ((now-2.).sin()*0.1), -0.7 + ((now-2.).cos().max(-0.5)*0.1)], color, 1),
Vertex::new_col([0.5, 0.3], color, 1),
// body
// 3
Vertex::new_col([-0.3, 0.], color, 1),
Vertex::new_col([0.4, -0.1], color, 1),
// 11
Vertex::new_col([0.3, 0.4], color, 1),
],
[
0,1,2,
3,4,5,
6,7,8,
9,10,11,
3,12,13,
3,13,11,
3,11,14
]
)
},
EntityState::Resting => {
(
[
// back left leg
Vertex::new_col([-0.5, 0.3], dark, 1),
Vertex::new_col([-0.4, -0.75], dark, 1),
Vertex::new_col([-0.25, 0.1], dark, 1),
// back right leg
Vertex::new_col([-0.5, 0.3], color, 1),
Vertex::new_col([-0.4, -0.75], color, 1),
Vertex::new_col([-0.25, 0.1], color, 1),
// front left leg
Vertex::new_col([0.3, 0.2], dark, 1),
Vertex::new_col([0.4, -0.75], dark, 1),
Vertex::new_col([0.5, 0.3], dark, 1),
// front right leg
Vertex::new_col([0.3, 0.2], color, 1),
Vertex::new_col([0.4, -0.75], color, 1),
Vertex::new_col([0.5, 0.3], color, 1),
// body
// 3
Vertex::new_col([-0.3, 0.], color, 1),
Vertex::new_col([0.4, -0.1], color, 1),
// 11
Vertex::new_col([0.3, 0.4], color, 1),
],
[
0,1,2,
3,4,5,
6,7,8,
9,10,11,
3,12,13,
3,13,11,
3,11,14
]
)
}
};
vertices.reserve(vs.len());
let base = vertices.len() as u32;
for mut v in vs {
v.pos[0] = v.pos[0]/50. + pos.x as f32;
v.pos[1] = (v.pos[1] + 0.75)/50. + pos.y as f32;
vertices.push(v)
}
indices.reserve(is.len());
for i in is {
indices.push(base + i);
}
}
}
}
/// Returns new cell if entity moves
pub fn update(&mut self, map: &mut Map, t: usize, rng: &mut ThreadRng) -> Option<ExternOp> {
// Lets take 0.57 kg of grass / m2
// Lets take 7.5 kg of food / day for a horse
// Lets say that a horse can survive up to 20 days without food
let cd = &mut map.cells_data[self.cid];
if self.kind.is_herbivore() {
let food_needed: f32 = match self.kind {
EntityKind::Horse => 7.5 / (0.57 * Map::CELL_AREA)
}; // in cell resource fraction
match cd.kind {
CellKind::Forest => {}, // Infinite food in forests
CellKind::Grass => {
let food_eaten = food_needed.min(cd.resource);
self.set_health(self.health - ((((food_needed - food_eaten)/food_needed)-0.5)/20.));
cd.set_resource(cd.resource-food_eaten, t);
},
_ => {
self.set_health(self.health - 1./20.);
}
}
}
if self.health == 0. {
return Some(ExternOp::Remove);
}
let r = cd.resource;
if cd.kind != CellKind::Grass {
map.voronoi.cell(cd.cid).iter_neighbors().filter(|n| {
let cd = &map.cells_data[*n];
cd.kind == CellKind::Grass
}).choose(rng).map(|c| ExternOp::Move(c))
} else if r < 0.5 {
let cd = &map.cells_data[self.cid];
Some(ExternOp::Move(
map.voronoi.cell(cd.cid)
.iter_neighbors()
.filter(|n| map.cells_data[*n].kind == CellKind::Grass)
.fold(cd, |acc, c| {
let cd = &map.cells_data[c];
if acc.kind != CellKind::Grass {
cd
} else if cd.kind != CellKind::Grass {
acc
} else if acc.resource > cd.resource {
acc
} else if acc.resource < cd.resource {
cd
} else if rng.gen_bool(0.5) {
cd
} else {
acc
}
}).cid
))
} else {
None
}
}
}

View File

@ -1,48 +0,0 @@
use winit::{dpi::PhysicalSize, event::{Touch, TouchPhase, WindowEvent}};
use crate::graphics::Vertex;
const SECONDARY_COLOR: [f32; 4] = [84./255., 33./255., 32./255., 1.];
const KIND_BUTTON_SIZE: f32 = 0.2;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
None,
Terrain,
Entities
}
pub struct UI {
pub kind_selected: Kind
}
impl UI {
pub fn new() -> Self {
Self {
kind_selected: Kind::Entities
}
}
pub fn render(&self, vertices: &mut Vec<Vertex>, indices: &mut Vec<u32>, screen_size: PhysicalSize<u32>) {
let ratio = screen_size.height as f32/screen_size.width as f32;
let vs = [
// Terrain
Vertex::new_tex([-1. + (0.02*ratio), -1. + (0.02+KIND_BUTTON_SIZE)], [0, 1], 0),
Vertex::new_tex([-1. + (0.02*ratio), -1. + 0.02], [0, 13], 0),
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio), -1. + 0.02], [12, 13], 0),
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio), -1. + (0.02+KIND_BUTTON_SIZE)], [12, 1], 0),
// Entities
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio)+(0.02*ratio), -1. + (0.02+KIND_BUTTON_SIZE)], [12, 1], 0),
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio)+(0.02*ratio), -1. + 0.02], [12, 13], 0),
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio)+((0.02+KIND_BUTTON_SIZE)*ratio), -1. + 0.02], [24, 13], 0),
Vertex::new_tex([-1. + ((0.02+KIND_BUTTON_SIZE)*ratio)+((0.02+KIND_BUTTON_SIZE)*ratio), -1. + (0.02+KIND_BUTTON_SIZE)], [24, 1], 0),
];
let ids = [
0,1,2,
2,3,0,
4,5,6,
6,7,4
];
let i = vertices.len() as u32;
vertices.extend_from_slice(&vs);
indices.extend_from_slice(&ids.map(|id| id+i));
}
}