mouse controls

This commit is contained in:
2025-03-31 22:51:48 -04:00
parent 34292dbe82
commit f1a91b1404
14 changed files with 121 additions and 142 deletions

View File

@@ -17,9 +17,9 @@ It's normal rust, so `cargo run` should fully compile and start it. It requires
## Controls
- WASD for movement
- WASD or left click & drag for movement
- Scroll to zoom
- Q to take a snapshot
- Q or right click to take a snapshot
Snapshots will copy the current texture and let you view it as the new one generates, which is very important for your sanity when you zoom in really far; the undecided regions will be replaced with a darkened version of your snapshot, so you can still know where you are and move around.
@@ -48,8 +48,6 @@ not in order of priority
- add auto snapshot; hard to figure out exactly when to take; maybe wait until at iter threshold dependent on zoom?
- add ability to have multiple snapshots at once, so you can easily navigate around; also fade out snapshots that are far away zoom wise; also maybe save manual ones to disk so you can easily contiune exploring areas
- add checkpointing that somehow lets you save & return to locations; even if this isn't added, add camera reset to easily get back to initial state; would also let you share locations with other people
- zoom in on mouse
- others controls for mouse; click & drag to move, maybe right click could be snapshot
## Cool Screenshots

View File

@@ -1,5 +1,5 @@
use nalgebra::Vector2;
use std::ops::AddAssign;
use std::ops::{AddAssign, Neg};
use crate::util::FixedDec;
@@ -15,20 +15,44 @@ pub struct Zoom {
pub struct Camera {
pub pos: Vector2<FixedDec>,
pub zoom: Zoom,
pub size: Vector2<u32>,
}
impl Camera {
pub fn scale(&self, size: &Vector2<u32>) -> Vector2<f32> {
let fsize: Vector2<f32> = size.cast();
if size.x < size.y {
pub fn world_pos(&self, screen_pos: Vector2<f32>) -> Vector2<FixedDec> {
let mut pos = screen_pos
.component_div(&self.size.cast())
.add_scalar(-0.5)
.component_mul(&self.stretch())
.map(FixedDec::from);
pos.y.negate();
pos *= self.zoom.mult().clone();
pos += &self.pos;
pos
}
pub fn world_delta(&self, screen_delta: Vector2<f32>) -> Vector2<FixedDec> {
let mut pos = screen_delta
.component_div(&self.size.cast())
.component_mul(&(self.stretch() * 1.5))
.map(FixedDec::from);
pos.y.negate();
pos *= self.zoom.mult().clone();
pos
}
pub fn stretch(&self) -> Vector2<f32> {
let fsize: Vector2<f32> = self.size.cast();
if self.size.x < self.size.y {
Vector2::new(fsize.x / fsize.y, 1.0)
} else {
Vector2::new(1.0, fsize.y / fsize.x)
}
}
pub fn inv_scale(&self, size: &Vector2<u32>) -> Vector2<f32> {
let fsize: Vector2<f32> = size.cast();
if size.x < size.y {
pub fn inv_stretch(&self) -> Vector2<f32> {
let fsize: Vector2<f32> = self.size.cast();
if self.size.x < self.size.y {
Vector2::new(fsize.y / fsize.x, 1.0)
} else {
Vector2::new(1.0, fsize.x / fsize.y)
@@ -39,6 +63,7 @@ impl Camera {
impl Default for Camera {
fn default() -> Self {
Self {
size: Vector2::zeros(),
pos: Vector2::new(-0.5, 0.0).map(FixedDec::from),
zoom: Zoom::new(0, 2.1),
}

View File

@@ -1,18 +1,46 @@
use std::time::Duration;
use winit::keyboard::KeyCode as K;
use winit::{event::MouseButton, keyboard::KeyCode as K};
use crate::util::FixedDec;
use super::Client;
pub struct InputHandling {
pub snapshot: bool,
}
impl InputHandling {
pub fn new() -> Self {
Self { snapshot: false }
}
}
impl Client<'_> {
pub fn handle_input(&mut self, delta: Duration) {
let Client { input, camera, .. } = self;
let Client {
input,
camera,
handling,
..
} = self;
if delta > Duration::from_secs_f32(0.5) {
// skip input handling if lag spike so you don't go flying
return;
}
let per_sec = delta.as_secs_f32();
if input.scroll_delta != 0.0 {
let old_pos = camera.world_pos(input.mouse_pos);
camera.zoom += input.scroll_delta / 5.0;
let new_pos = camera.world_pos(input.mouse_pos);
camera.pos += old_pos - new_pos;
}
if input.mouse_pressed(MouseButton::Left)
&& (input.mouse_delta.x != 0.0 || input.mouse_delta.y != 0.0)
{
camera.pos -= camera.world_delta(input.mouse_delta);
}
let speed = FixedDec::from(per_sec * 0.5) * camera.zoom.mult();
@@ -28,8 +56,8 @@ impl Client<'_> {
if input.pressed(K::KeyD) {
camera.pos.x += &speed;
}
if input.pressed(K::KeyQ) {
self.snapshot = true;
if input.just_pressed(K::KeyQ) || input.mouse_just_pressed(MouseButton::Right) {
handling.snapshot = true;
}
}
}

View File

@@ -7,7 +7,7 @@ use winit::{
};
pub struct Input {
pub mouse_pixel_pos: Vector2<f32>,
pub mouse_pos: Vector2<f32>,
pub mouse_delta: Vector2<f32>,
pressed: HashSet<KeyCode>,
@@ -23,7 +23,7 @@ pub struct Input {
impl Input {
pub fn new() -> Self {
Self {
mouse_pixel_pos: Vector2::zeros(),
mouse_pos: Vector2::zeros(),
mouse_delta: Vector2::zeros(),
pressed: HashSet::new(),
just_pressed: HashSet::new(),
@@ -67,11 +67,10 @@ impl Input {
};
}
WindowEvent::CursorLeft { .. } => {
self.pressed.clear();
self.mouse_pressed.clear();
self.clear();
}
WindowEvent::CursorMoved { position, .. } => {
self.mouse_pixel_pos = Vector2::new(position.x as f32, position.y as f32);
self.mouse_pos = Vector2::new(position.x as f32, position.y as f32);
}
WindowEvent::MouseInput { button, state, .. } => match state {
ElementState::Pressed => {

View File

@@ -1,6 +1,7 @@
use std::{sync::Arc, time::Instant};
use camera::Camera;
use handle_input::InputHandling;
use input::Input;
use render::Renderer;
use winit::{
@@ -23,7 +24,7 @@ pub struct Client<'a> {
exit: bool,
prev_update: Instant,
renderer: Renderer<'a>,
snapshot: bool,
handling: InputHandling,
}
impl Client<'_> {
@@ -41,7 +42,7 @@ impl Client<'_> {
exit: false,
prev_update: Instant::now(),
renderer,
snapshot: false,
handling: InputHandling::new(),
}
}
@@ -60,16 +61,17 @@ impl Client<'_> {
pub fn window_event(&mut self, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => self.exit = true,
WindowEvent::Resized(size) => self.renderer.resize(size),
WindowEvent::Resized(size) => {
self.renderer.resize(size);
self.camera.size = *self.renderer.size();
}
WindowEvent::RedrawRequested => {
self.renderer.render(&self.camera, self.snapshot);
self.snapshot = false;
self.renderer.render(&self.camera, self.handling.snapshot);
self.handling.snapshot = false;
self.window.request_redraw();
}
WindowEvent::CursorLeft { .. } => {
self.input.clear();
}
_ => self.input.update_window(event),
_ => (),
}
self.input.update_window(event);
}
}

View File

@@ -19,7 +19,7 @@ impl ComputeView {
impl Default for ComputeView {
fn default() -> Self {
let val = FixedDec::from_parts(false, 0, vec![0, 0, 0]);
Self::new(true, Vector2::zeros(), 0, &val, &val, &val)
Self::new(true, Vector2::zeros(), Vector2::zeros(), 0, &val, &val, &val)
}
}
@@ -27,6 +27,7 @@ impl ComputeView {
fn new(
reset: bool,
dims: Vector2<u32>,
stretch: Vector2<f32>,
level: i32,
scale: &FixedDec,
x: &FixedDec,
@@ -36,6 +37,7 @@ impl ComputeView {
bytes.extend((reset as u32).to_le_bytes());
bytes.extend(level.to_le_bytes());
bytes.extend(bytemuck::cast_slice(&[dims.x, dims.y]));
bytes.extend(bytemuck::cast_slice(&[stretch.x, stretch.y]));
scale.to_bytes(&mut bytes);
x.to_bytes(&mut bytes);
y.to_bytes(&mut bytes);
@@ -46,7 +48,7 @@ impl ComputeView {
Self { bytes }
}
pub fn from_camera_size(camera: &Camera, size: &Vector2<u32>, reset: bool, len: usize) -> Self {
pub fn from_camera(camera: &Camera, reset: bool, len: usize) -> Self {
let mut x = camera.pos.x.clone();
x.set_whole_len(1);
x.set_dec_len(len as i32 - 1);
@@ -54,17 +56,11 @@ impl ComputeView {
y.set_whole_len(1);
y.set_dec_len(len as i32 - 1);
let fsize: Vector2<f32> = size.cast();
let stretch = if size.x < size.y {
Vector2::new(fsize.x / fsize.y, 1.0)
} else {
Vector2::new(1.0, fsize.y / fsize.x)
};
let stretch = camera.stretch();
let mut scale = camera.zoom.mult().clone();
scale.set_precision(len);
Self::new(reset, *size, camera.zoom.level(), &scale, &x, &y)
Self::new(reset, camera.size, stretch, camera.zoom.level(), &scale, &x, &y)
}
}

View File

@@ -36,10 +36,9 @@ impl ComputePipeline {
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
camera: &Camera,
size: &Vector2<u32>,
len: usize,
) {
let mut view = ComputeView::from_camera_size(camera, size, false, len);
let mut view = ComputeView::from_camera(camera, false, len);
if view != self.old_view {
for (i, b) in 1u32.to_le_bytes().iter().enumerate() {
view.bytes[i] = *b;
@@ -49,7 +48,7 @@ impl ComputePipeline {
println!("new len: {}", len);
self.old_len = len;
self.pipeline = self.pipeline(device, &Self::shader(device, len));
self.work.set(work_vec(size.x, size.y, len));
self.work.set(work_vec(camera.size.x, camera.size.y, len));
}
let updated = self.work.update(device, encoder, belt)
| self.view.update(device, encoder, belt, view.bytes());
@@ -66,14 +65,7 @@ impl ComputePipeline {
pass.dispatch_workgroups(240, 135, 1);
}
pub fn resize(
&mut self,
device: &wgpu::Device,
encoder: &mut wgpu::CommandEncoder,
belt: &mut wgpu::util::StagingBelt,
size: Vector2<u32>,
len: usize,
) {
pub fn resize(&mut self, device: &wgpu::Device, size: Vector2<u32>, len: usize) {
self.work.set(work_vec(size.x, size.y, len));
self.old_len = len;
self.output.resize(

View File

@@ -9,6 +9,7 @@ struct View {
reset: u32,
level: i32,
dims: vec2<u32>,
stretch: vec2<f32>,
scale: FixedDec,
corner_x: FixedDec,
corner_y: FixedDec,
@@ -28,30 +29,14 @@ fn main(
if id.x > view.dims.x - 1 || id.y > view.dims.y - 1 {
return;
}
// TODO: actually use width
let varwidth = LEN + 2;
let workwidth = varwidth * 2 + 1;
let worki = (id.x * view.dims.y + id.y) * workwidth;
let xidx = worki + 1;
let yidx = xidx + varwidth;
// let dec = view.corner_x.dec;
// var rel_x = FixedDec(POS, dec, array<u32, LEN>());
// rel_x.parts[0] = id.x;
// rel_x = shr(rel_x, view.level);
// var rel_y = FixedDec(POS, dec, array<u32, LEN>());
// rel_y.parts[0] = id.y;
// rel_y = shr(rel_y, view.level);
// let cx = add(view.corner_x, rel_x);
// let cy = add(view.corner_y, rel_y);
let fdims = vec2<f32>(view.dims);
var stretch: vec2<f32>;
if fdims.x < fdims.y {
stretch = vec2(fdims.x / fdims.y, 1.0);
} else {
stretch = vec2(1.0, fdims.y / fdims.x);
}
let fpos = (vec2<f32>(id.xy) / fdims - 0.5) * stretch;
let fpos = (vec2<f32>(id.xy) / fdims - 0.5) * view.stretch;
let cx = add(mul(from_f32(fpos.x), view.scale), view.corner_x);
let cy = add(mul(from_f32(fpos.y), view.scale), view.corner_y);
var x: FixedDec;

View File

@@ -122,17 +122,14 @@ impl Renderer<'_> {
}
pub fn render(&mut self, camera: &Camera, snapshot: bool) {
// this comes from the fact that I want (0, 2) and (30, 4)
// probably a much better formula, or better yet let the user select
self.len = (camera.zoom.level() as f32 / 32.0 + 2.0).round() as usize;
// at level 0 I want 2, and should increase respective to bits needed for positioning
self.len = (camera.zoom.level() / 32) as usize + 2;
self.compute_pipeline.update(
&self.device,
&mut self.encoder,
&mut self.staging_belt,
camera,
&self.size,
self.len,
);
self.chunk_view.update(camera, &self.size, snapshot);
@@ -168,8 +165,10 @@ impl Renderer<'_> {
self.config.width = size.width;
self.config.height = size.height;
self.surface.configure(&self.device, &self.config);
self.compute_pipeline.resize(&self.device, &mut self.encoder, &mut self.staging_belt, self.size, self.len);
self.render_pipeline.resize(&self.device, self.size, &self.compute_pipeline.output);
self.compute_pipeline
.resize(&self.device, self.size, self.len);
self.render_pipeline
.resize(&self.device, self.size, &self.compute_pipeline.output);
}
fn create_encoder(device: &wgpu::Device) -> wgpu::CommandEncoder {
@@ -177,4 +176,8 @@ impl Renderer<'_> {
label: Some("Render Encoder"),
})
}
pub fn size(&self) -> &Vector2<u32> {
&self.size
}
}

View File

@@ -1,7 +1,5 @@
use nalgebra::Vector2;
use crate::client::render::CHUNK_POW;
use super::{Camera, CHUNK_WIDTH};
#[repr(C, align(8))]
@@ -44,10 +42,12 @@ impl WindowView {
// let stretch = size.cast::<f32>() * camera.zoom.rel_zoom() / (CHUNK_WIDTH as f32);
let (pos, stretch) = if let Some(ss_cam) = ss_cam {
let aspect = camera.inv_scale(size) * 2.0;
let aspect = camera.inv_stretch() * 2.0;
let s = camera.zoom.mult() * ss_cam.zoom.inv_mult();
(
((&camera.pos - &ss_cam.pos) * ss_cam.zoom.inv_mult().clone()).map(f32::from).component_mul(&aspect),
((&camera.pos - &ss_cam.pos) * ss_cam.zoom.inv_mult().clone())
.map(f32::from)
.component_mul(&aspect),
Vector2::from_element(f32::from(s)),
)
} else {

View File

@@ -21,9 +21,8 @@ var ss_s: sampler;
struct VertexOutput {
@builtin(position) vertex_pos: vec4<f32>,
@location(0) world_pos: vec2<f32>,
@location(1) tex_pos: vec2<f32>,
@location(2) ss_pos: vec2<f32>,
@location(0) tex_pos: vec2<f32>,
@location(1) ss_pos: vec2<f32>,
};
@vertex
@@ -35,22 +34,16 @@ fn vs_main(
let pos = vec2<f32>(
f32(vi % 2u),
f32(vi / 2u),
f32(1 - vi / 2u),
) * 2.0 - 1.0;
out.vertex_pos = vec4<f32>(pos.x, -pos.y, 0.0, 1.0);
out.world_pos = pos / 2.0;
out.world_pos.y *= -1.0;
out.world_pos *= view.stretch;
out.world_pos += view.pos;
out.vertex_pos = vec4<f32>(pos.x, pos.y, 0.0, 1.0);
let pos2 = vec2<f32>(
f32(vi % 2u),
f32(vi / 2u),
f32(1 - vi / 2u),
);
out.tex_pos = pos2;
out.tex_pos.y = 1.0 - out.tex_pos.y;
let pos3 = vec2(pos.x, -pos.y);
out.ss_pos = pos3 * view.stretch + view.pos;
out.ss_pos = pos * view.stretch + view.pos;
out.ss_pos = (out.ss_pos + 1.0) / 2.0;
return out;
@@ -60,16 +53,6 @@ fn vs_main(
fn fs_main(
in: VertexOutput,
) -> @location(0) vec4<f32> {
// let a = textureLoad(chunks, vec2<u32>(0), 0, 0);
// let rc = vec2<i32>(view.rendered_chunks);
// let rcf = vec2<f32>(rc);
// let cposi = vec2<i32>(floor(in.world_pos));
// let cposu = vec2<i32>(
// rem_euclid(cposi.x, rc.x),
// rem_euclid(cposi.y, rc.y)
// );
// let cposf = vec2<f32>(cposu);
// return vec4(cposf / rcf, 0.0, 1.0);
let cur = textureSample(tex, sam, in.tex_pos);
let snp_bounds = all(in.ss_pos >= vec2(0.0)) && all(in.ss_pos <= vec2(1.0));
if all(cur.rgb == vec3(0.0)) && snp_bounds {

View File

@@ -2,15 +2,13 @@ use std::collections::HashSet;
use nalgebra::Vector2;
use crate::{client::camera::Camera, util::FixedDec};
use crate::client::camera::Camera;
use super::{output::WindowView, CHUNK_POW};
use super::output::WindowView;
#[derive(Default)]
pub struct ChunkView {
pub render: WindowView,
pub chunk_queue: HashSet<Vector2<FixedDec>>,
pub visible_chunks: HashSet<Vector2<FixedDec>>,
pub snapshot: Option<Camera>,
}
@@ -29,42 +27,5 @@ impl ChunkView {
return;
}
self.render = render;
let corner_offset = ((size / 2).cast() * camera.zoom.rel_zoom())
.map(|x| FixedDec::from(x) >> camera.zoom.level());
let bot_left = &camera.pos - &corner_offset;
let top_right = &camera.pos + &corner_offset;
let mult = FixedDec::one() >> (CHUNK_POW as i32 - camera.zoom.level());
let blc = bot_left
.component_mul(&Vector2::from_element(mult.clone()))
.map(FixedDec::floor);
let trc = top_right
.component_mul(&Vector2::from_element(mult))
.map(FixedDec::floor);
let mut visible = HashSet::new();
let mut x = blc.x.clone();
// while x <= trc.x {
// let mut y = blc.y.clone();
// while y <= trc.y {
// visible.insert(Vector2::new(x.clone(), y.clone()));
// y += FixedDec::one();
// }
// x += FixedDec::one();
// }
let new = visible
.difference(&self.visible_chunks)
.cloned()
.collect::<Vec<_>>();
let old = self
.visible_chunks
.difference(&visible)
.cloned()
.collect::<Vec<_>>();
self.chunk_queue.retain(|p| !old.contains(p));
self.chunk_queue.extend(new);
self.visible_chunks = visible;
}
}

View File

@@ -1,5 +1,6 @@
#![feature(bigint_helper_methods)]
#![feature(int_roundings)]
#![feature(let_chains)]
use client::ClientApp;

View File

@@ -139,6 +139,12 @@ impl FixedDec {
pub fn parts(&self) -> &[u32] {
&self.parts
}
pub fn negate(&mut self) {
if !self.is_zero() {
self.sign = !self.sign;
}
}
}
impl Display for FixedDec {