M src/blocks.rs => src/blocks.rs +4 -2
@@ 3,12 3,14 @@ use crate::textures::AssetStore;
use crate::types::Direction;
pub enum Block {
- Dirt(Direction)
+ Dirt(Direction),
+ GrassCenter(Direction)
}
impl Block {
pub fn get_texture(&self, assets: &AssetStore) -> Texture2D {
match self {
- Block::Dirt(d) => assets.blocks.dirt.get_dir(d)
+ Block::Dirt(d) => assets.blocks.dirt.get_dir(d),
+ Block::GrassCenter(d) => assets.blocks.grass_center.get_dir(d)
}
}
}
A src/screens/# => src/screens/# +238 -0
@@ 0,0 1,238 @@
+use macroquad::prelude::*;
+use std::collections::HashMap;
+use crate::textures::AssetStore;
+use crate::types::{
+ GameComponent,
+ GameEvent,
+ Pos3
+};
+use crate::blocks::Block;
+
+struct Camera {
+ center: Vec2,
+ scale: f32
+}
+impl Camera {
+ fn new() -> Self {
+ Self {
+ center: Vec2::new(0.0, 0.0),
+ scale: 1.0
+ }
+ }
+}
+
+/// image width
+const TEXTURE_WIDTH: f32 = 256.0;
+/// actual width inside ob cube
+const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216
+/// height of image
+const TEXTURE_HEIGHT: f32 = 352.0;
+/// empty space above texture
+const TEXTURE_Y_WHITESPACE: f32 = 130.0;
+/// actual height of cube
+const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110
+/// height of top surface
+const TEXTURE_DEPTH: f32 = 100.0;
+
+pub struct BuildScreen {
+ grid: HashMap<Pos3, Block>,
+ cam: Camera,
+ mouse_down: bool
+}
+impl BuildScreen {
+ pub fn new() -> Self {
+ let mut this = Self {
+ grid: HashMap::new(),
+ cam: Camera::new(),
+ mouse_down: false
+ };
+
+ this.grid.insert(Pos3::new(0, 0, 0), Block::Dirt(crate::types::Direction::North));
+ this.grid.insert(Pos3::new(1, 0, 0), Block::Dirt(crate::types::Direction::North));
+ this.grid.insert(Pos3::new(2, 0, 0), Block::Dirt(crate::types::Direction::North));
+ this.grid.insert(Pos3::new(2, 1, 0), Block::Dirt(crate::types::Direction::North));
+ this.grid.insert(Pos3::new(2, 1, 1), Block::Dirt(crate::types::Direction::North));
+
+ this
+ }
+}
+
+fn get_screen_coords(pos: &Pos3, scale: f32, center: Vec2) -> (f32, f32) {
+ let w_i = TEXTURE_INNER_WIDTH * scale;
+ let width = TEXTURE_WIDTH * scale;
+ let height = TEXTURE_HEIGHT * scale;
+ let h_i = TEXTURE_INNER_HEIGHT * scale;
+
+ let dx = pos.y - pos.x;
+ let dy = pos.y + pos.x;
+
+ let x = screen_width() / 2.0
+ + dx as f32 * w_i / 2.0
+ + center.x
+ - width / 2.0;
+ let y = screen_height() / 2.0
+ + dy as f32 * h_i / 2.0
+ - pos.z as f32 * h_i
+ + center.y
+ - height / 2.0;
+
+ return (x,y);
+}
+
+/// A block's face
+/// /'\
+/// / A \
+/// |\ /|
+/// | \./ |
+/// \ B|C |
+/// \.|./
+/// A - Top
+/// B- Left
+/// C -Right
+enum Face {
+ Top,
+ Left,
+ Right
+}
+impl Face {
+ /// gets the face (if any) that was clicked
+ /// coordinates have to be normalized,
+ /// the top left should be (0,0)
+ fn from_xy(x: f32, y: f32) -> Option<Self> {
+
+ // test B face
+ {
+ let ox = (TEXTURE_WIDTH / 2.0 - x).abs();
+ let oy = (TEXTURE_Y_WHITESPACE + TEXTURE_DEPTH / 2.0 - y).abs();
+
+ let y_max = TEXTURE_DEPTH / 2.0;
+ let x_max = TEXTURE_WIDTH / 2.0;
+
+ let grow = (0.0 - y_max)/(x_max - 0.0);
+ let cy = grow * ox + y_max;
+
+ if cy <= oy {
+ return Some(Face::Top);
+ }
+
+ }
+
+ None
+ }
+}
+
+impl GameComponent for BuildScreen {
+ fn draw(&self, assets: &AssetStore) {
+ // order: x (inc) -> y (inc) -> z (inc)
+ let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
+ render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );
+
+ for (pos, block) in render_order.iter() {
+ let texture = block.get_texture(&assets);
+ let width = TEXTURE_WIDTH * self.cam.scale;
+ let height = TEXTURE_HEIGHT * self.cam.scale;
+
+ let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);
+
+ if x >= -width && x <= screen_width()
+ && y >= -width && y <= screen_height() {
+ // render block
+ draw_texture_ex(
+ texture,
+ x,
+ y,
+ WHITE,
+ DrawTextureParams {
+ dest_size: Some(Vec2::new(width, height)),
+ source: None,
+ rotation: 0.0,
+ flip_x: false,
+ flip_y: false,
+ pivot: Some(Vec2::new(width / 2.0, height / 2.0))
+ });
+ }
+ }
+ }
+ fn ev_loop(&mut self) -> GameEvent {
+ // mouse input
+ if is_mouse_button_down(MouseButton::Left) {
+ // currently holding button down
+ self.mouse_down = true;
+ } else if self.mouse_down {
+ // button was released
+ self.mouse_down = false;
+ // determine which block / side was clicked
+ let (mx, my) = mouse_position();
+
+ // virtual render cycle
+ let render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
+ // no need to sort the first time
+ //render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );
+
+ // list of positions in render que for given pixel
+ // (mx, my)
+ let mut in_path:Vec<&Pos3> = Vec::new();
+
+ for (pos, _) in render_order.iter() {
+ let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);
+
+ if mx >= x
+ && mx <= x + TEXTURE_WIDTH
+ && my >= y + TEXTURE_Y_WHITESPACE
+ && my <= y + TEXTURE_HEIGHT {
+
+ // check if mouse is above transparent area
+ // and skip block if necessary
+ if Face::from_xy(mx-x, my-y).is_none() {
+ println!("skipping");
+ continue;
+ }
+
+ // block in mouse path
+ in_path.push(pos);
+ }
+ }
+ in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 );
+ if let Some(pos) = in_path.last() {
+ // position of clicked block
+ // because it is the last block in de render queue
+ // for a given pixel
+ let pos = Pos3::new(pos.x, pos.y, pos.z);
+ let (x,y) = get_screen_coords(&pos, self.cam.scale, self.cam.center);
+ let face = Face::from_xy(mx-x, my-y);
+ println!("{:?}", face);
+
+ // TODO: determine side
+ self.grid.insert(pos, Block::GrassCenter(crate::types::Direction::West));
+ }
+ }
+
+ // zoom with Ctrl-MouseWheel
+ if is_key_down(KeyCode::LeftControl) {
+ let mut scale = self.cam.scale;
+ let (_, wy) = mouse_wheel();
+
+ scale += wy as f32 * 1.0/10.0;
+
+ if scale > 0.05 && scale < 6.0 {
+ self.cam.scale = scale;
+ }
+ }
+
+ // keyboard control
+ if is_key_down(KeyCode::Down) {
+ self.cam.center.y += 1.0 * (1.0/self.cam.scale);
+ }
+ if is_key_down(KeyCode::Up) {
+ self.cam.center.y -= 1.0 * (1.0/self.cam.scale);
+ }
+ if is_key_down(KeyCode::Left) {
+ self.cam.center.x -= 1.0 * (1.0/self.cam.scale);
+ }
+ if is_key_down(KeyCode::Right) {
+ self.cam.center.x += 1.0 * (1.0/self.cam.scale);
+ }
+
+ GameEvent::None
+ }
+}
M src/screens/build.rs => src/screens/build.rs +147 -21
@@ 21,16 21,18 @@ impl Camera {
}
}
-const TARGET_RENDER_SIZE:f32 = 128.0;
-
/// image width
const TEXTURE_WIDTH: f32 = 256.0;
/// actual width inside ob cube
const TEXTURE_INNER_WIDTH: f32 = 223.0; // 216
/// height of image
const TEXTURE_HEIGHT: f32 = 352.0;
+/// empty space above texture
+const TEXTURE_Y_WHITESPACE: f32 = 130.0;
/// actual height of cube
const TEXTURE_INNER_HEIGHT: f32 = 108.0; // 110
+/// height of top surface
+const TEXTURE_DEPTH: f32 = 100.0;
pub struct BuildScreen {
grid: HashMap<Pos3, Block>,
@@ 51,33 53,109 @@ impl BuildScreen {
}
}
-impl GameComponent for BuildScreen {
- fn draw(&self, assets: &AssetStore) {
- // order: x (inc) -> y (inc) -> z (inc)
- let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
- render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );
-
- for (pos, block) in render_order.iter() {
- let texture = block.get_texture(&assets);
-
- let w_i = TEXTURE_INNER_WIDTH * self.cam.scale;
- let width = TEXTURE_WIDTH * self.cam.scale;
- let height = TEXTURE_HEIGHT * self.cam.scale;
- let h_i = TEXTURE_INNER_HEIGHT * self.cam.scale;
+fn get_screen_coords(pos: &Pos3, scale: f32, center: Vec2) -> (f32, f32) {
+ let w_i = TEXTURE_INNER_WIDTH * scale;
+ let width = TEXTURE_WIDTH * scale;
+ let height = TEXTURE_HEIGHT * scale;
+ let h_i = TEXTURE_INNER_HEIGHT * scale;
let dx = pos.y - pos.x;
let dy = pos.y + pos.x;
let x = screen_width() / 2.0
+ dx as f32 * w_i / 2.0
- + self.cam.center.x
+ + center.x
- width / 2.0;
let y = screen_height() / 2.0
+ dy as f32 * h_i / 2.0
- pos.z as f32 * h_i
- + self.cam.center.y
+ + center.y
- height / 2.0;
+ return (x,y);
+}
+
+/// A block's face
+/// /'\
+/// / A \
+/// |\ /|
+/// | \./ |
+/// \ B|C |
+/// \.|./
+/// A - Top
+/// B- Left
+/// C -Right
+#[derive(Debug)]
+enum Face {
+ Top,
+ Left,
+ Right
+}
+impl Face {
+ /// gets the face (if any) that was clicked
+ /// coordinates have to be normalized,
+ /// the top left should be (0,0)
+ fn from_xy(x: f32, y: f32, scale: f32) -> Option<Self> {
+
+ // test A face
+ {
+ let ox = (TEXTURE_WIDTH * scale / 2.0 - x).abs();
+ let oy = (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale - y).abs();
+
+ let y_max = TEXTURE_DEPTH * scale / 2.0;
+ let x_max = TEXTURE_WIDTH * scale / 2.0;
+
+ let grow = (0.0 - y_max)/(x_max - 0.0);
+ let cy = grow * ox + y_max;
+
+ if cy >= oy {
+ return Some(Face::Top);
+ }
+ }
+
+ // test B and C faces
+ {
+ let ox = TEXTURE_WIDTH * scale / 2.0 - x;
+ let oy = y - (TEXTURE_DEPTH * scale / 2.0 + TEXTURE_Y_WHITESPACE * scale);
+
+ let ox_abs = ox.abs();
+
+ let y_max = TEXTURE_DEPTH * scale / 2.0;
+ let x_max = TEXTURE_WIDTH * scale / 2.0;
+
+ let grow = (0.0 - y_max)/(x_max - 0.0);
+
+ let y_top = grow * ox_abs + y_max;
+ let y_bottom = grow * ox_abs + y_max + TEXTURE_INNER_HEIGHT * scale;
+
+ if oy >= y_top && oy <= y_bottom {
+ if x > TEXTURE_WIDTH * scale / 2.0 {
+ // C face
+ return Some(Face::Right);
+ } else {
+ // B face
+ return Some(Face::Left);
+ }
+ }
+ }
+
+ None
+ }
+}
+
+impl GameComponent for BuildScreen {
+ fn draw(&self, assets: &AssetStore) {
+ // order: x (inc) -> y (inc) -> z (inc)
+ let mut render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
+ render_order.sort_by_key(|(pos, _)| pos.x + pos.y*2 + pos.z*3 );
+
+ for (pos, block) in render_order.iter() {
+ let texture = block.get_texture(&assets);
+ let width = TEXTURE_WIDTH * self.cam.scale;
+ let height = TEXTURE_HEIGHT * self.cam.scale;
+
+ let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);
+
if x >= -width && x <= screen_width()
&& y >= -width && y <= screen_height() {
// render block
@@ 92,9 170,8 @@ impl GameComponent for BuildScreen {
rotation: 0.0,
flip_x: false,
flip_y: false,
- pivot: None
+ pivot: Some(Vec2::new(width / 2.0, height / 2.0))
});
-
}
}
}
@@ 106,10 183,59 @@ impl GameComponent for BuildScreen {
} else if self.mouse_down {
// button was released
self.mouse_down = false;
- // TODO: determine which block / side was clicked
+ // determine which block / side was clicked
let (mx, my) = mouse_position();
- let size = TARGET_RENDER_SIZE * self.cam.scale;
+ // virtual render cycle
+ let render_order: Vec<(&Pos3, &Block)> = self.grid.iter().collect();
+
+ // list of positions in render que for given pixel
+ // (mx, my)
+ let mut in_path:Vec<&Pos3> = Vec::new();
+
+ for (pos, _) in render_order.iter() {
+ let (x,y) = get_screen_coords(pos, self.cam.scale, self.cam.center);
+
+ if mx >= x
+ && mx <= x + TEXTURE_WIDTH * self.cam.scale
+ && my >= y + TEXTURE_Y_WHITESPACE * self.cam.scale
+ && my <= y + TEXTURE_HEIGHT * self.cam.scale {
+
+ // check if mouse is above transparent area
+ // and skip block if necessary
+ if Face::from_xy(mx-x, my-y, self.cam.scale).is_none() {
+ println!("skipping");
+ continue;
+ }
+
+ // block in mouse path
+ in_path.push(pos);
+ }
+ }
+ // weight axis
+ in_path.sort_by_key(|pos| pos.x + pos.y*2 + pos.z*3 );
+ if let Some(pos) = in_path.last() {
+ // position of clicked block
+ // because it is the last block in de render queue
+ // for a given pixel
+ let mut pos = Pos3::new(pos.x, pos.y, pos.z);
+ let (x,y) = get_screen_coords(&pos, self.cam.scale, self.cam.center);
+ let face = Face::from_xy(mx-x, my-y, self.cam.scale);
+
+ match face.unwrap() {
+ Face::Top => {
+ pos.z+=1;
+ },
+ Face::Left => {
+ pos.x+=1;
+ },
+ Face::Right => {
+ pos.y+=1;
+ }
+ }
+
+ self.grid.insert(pos, Block::GrassCenter(crate::types::Direction::West));
+ }
}
// zoom with Ctrl-MouseWheel
M src/textures.rs => src/textures.rs +4 -2
@@ 63,7 63,8 @@ impl DirectionalTexture {
}
}
pub struct BlockAssetCollection {
- pub dirt: DirectionalTexture
+ pub dirt: DirectionalTexture,
+ pub grass_center: DirectionalTexture
}
impl AssetStore {
@@ 80,7 81,8 @@ impl AssetStore {
&in_ui_folder("PNG/buttonLong_brown_pressed.png"))
.await.expect("Unable to load texture")),
blocks: BlockAssetCollection {
- dirt: DirectionalTexture::from_auto_png("Tiles/dirt_center").await
+ dirt: DirectionalTexture::from_auto_png("Tiles/dirt_center").await,
+ grass_center: DirectionalTexture::from_auto_png("Tiles/grass_center").await
}
}
}