Snap brick to mouse on rotate

And fix crashing on moving outside of window
This commit is contained in:
2026-03-07 17:41:25 +01:00
parent 1a647eced2
commit 15d830987e
3 changed files with 208 additions and 24 deletions
+16 -3
View File
@@ -656,14 +656,25 @@ fn wlKeyboardHandleKey(data: ?*anyopaque, keyboard: ?*c.wl_keyboard, serial: u32
c.XKB_KEY_Escape => {
wl_state.running = false;
},
c.XKB_KEY_space => {
wl_state.app_input.key.space.down = pressed;
c.XKB_KEY_f => {
const already_pressed: bool = wl_state.app_input.key.f.down;
wl_state.app_input.key.f.down = pressed;
wl_state.app_input.key.f.pressing = pressed and already_pressed;
},
c.XKB_KEY_r => {
const already_pressed: bool = wl_state.app_input.key.r.down;
wl_state.app_input.key.r.down = pressed;
wl_state.app_input.key.r.pressing = pressed and already_pressed;
},
c.XKB_KEY_q => {
const already_pressed: bool = wl_state.app_input.key.q.down;
wl_state.app_input.key.q.down = pressed;
wl_state.app_input.key.q.pressing = pressed and already_pressed;
},
c.XKB_KEY_space => {
const already_pressed: bool = wl_state.app_input.key.space.down;
wl_state.app_input.key.space.down = pressed;
wl_state.app_input.key.space.pressing = pressed and already_pressed;
},
else => {},
}
@@ -929,13 +940,15 @@ pub fn main() u8 {
const render_start_time: u64 = timer.read();
wl_state.app_input.delta_time = frame_target_time_s;
const app_offscreen_buffer: puzzle.platform.OffscreenBuffer = .{
.width = wl_state.window_width,
.height = wl_state.window_height,
.stride = wl_state.window_width * 4,
.data = shared_memory_pool_data,
};
puzzle.updateAndRender(&app_memory, app_offscreen_buffer, wl_state.app_input);
puzzle.updateAndRender(&app_memory, app_offscreen_buffer, &wl_state.app_input);
const render_end_time: u64 = timer.read();
const render_time = render_end_time - render_start_time;
+7 -1
View File
@@ -8,17 +8,23 @@ pub fn megaBytes(bytes: usize) usize {
return kiloBytes(bytes) * 1024;
}
const Key = struct {
pub const Key = struct {
down: bool,
pressing: bool,
frame_count: u32,
};
const Keyboard = struct {
esc: Key,
f: Key,
r: Key,
q: Key,
space: Key,
};
pub const AppInput = struct {
delta_time: f64,
mouse_present: bool = false,
mouse_left_down: bool = false,
mouse_x: f64,
+185 -20
View File
@@ -1,5 +1,6 @@
//! By convention, root.zig is the root source file when making a library.
const std = @import("std");
const builtin = @import("builtin");
pub const platform = @import("platform.zig");
@@ -9,6 +10,9 @@ const V2 = struct {
x: f32,
y: f32,
const max = V2{ .x = std.math.floatMax(f32), .y = std.math.floatMax(f32) };
const zero = V2{ .x = 0.0, .y = 0.0 };
fn add(self: V2, other: V2) V2 {
return .{
.x = self.x + other.x,
@@ -24,6 +28,12 @@ const V2 = struct {
}
};
const BBox = struct {
min: V2,
max: V2,
center: V2,
};
const Camera = struct {
pos: V2,
offset: V2,
@@ -33,8 +43,27 @@ const Brick = struct {
rotating: bool,
pos: V2,
color: u32,
rotating_time: f64,
tiles: [4][4]u32,
fn transpose(self: Brick) [4][4]u32 {
var result = [4][4]u32{
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
.{ 0, 0, 0, 0 },
};
for (0..self.tiles.len) |i| {
const row = self.tiles[i];
for (0..row.len) |j| {
result[i][j] = self.tiles[j][i];
}
}
return result;
}
fn rotate(self: Brick) [4][4]u32 {
var result = [4][4]u32{
.{ 0, 0, 0, 0 },
@@ -61,6 +90,11 @@ const State = struct {
bricks: [8]Brick,
};
const tile_size = V2{
.x = TILE_SIZE,
.y = TILE_SIZE,
};
fn pointInsideRect(p: V2, rect_start: V2, rect_size: V2) bool {
var result: bool = false;
@@ -74,6 +108,67 @@ fn pointInsideRect(p: V2, rect_start: V2, rect_size: V2) bool {
return result;
}
fn findBrickBoundingBox(brick: *const Brick) BBox {
var result = BBox{ .min = V2.max, .max = V2.zero, .center = V2.zero };
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
if (tile > 0) {
const tile_start = V2{
.x = @floatFromInt(j * TILE_SIZE),
.y = @floatFromInt(i * TILE_SIZE),
};
const tile_end = tile_start.add(tile_size);
const start = brick.pos.add(tile_start);
const end = brick.pos.add(tile_end);
if (start.x < result.min.x) result.min.x = start.x;
if (start.y < result.min.y) result.min.y = start.y;
if (result.max.x < end.x) result.max.x = end.x;
if (result.max.y < end.y) result.max.y = end.y;
}
}
}
result.center = .{
.x = (result.min.x + result.max.x) / 2.0,
.y = (result.min.y + result.max.y) / 2.0,
};
return result;
}
fn drawRect(buffer: platform.OffscreenBuffer, pos: V2, size: V2, color: u32, line_width: i32) void {
var pixels: []u32 = @ptrCast(@alignCast(buffer.data));
const start_x: i32 = if (pos.x < 0) 0 else @intFromFloat(pos.x);
const start_y: i32 = if (pos.y < 0) 0 else @intFromFloat(pos.y);
const end = pos.add(size);
const end_x: i32 = if (end.x < 0) 0 else @intFromFloat(end.x);
const end_y: i32 = if (end.y < 0) 0 else @intFromFloat(end.y);
const min_x: usize = @intCast(if (buffer.width < start_x) buffer.width else start_x);
const min_y: usize = @intCast(if (buffer.height < start_y) buffer.height else start_y);
const max_x: usize = @intCast(if (buffer.width < end_x) buffer.width else end_x);
const max_y: usize = @intCast(if (buffer.height < end_y) buffer.height else end_y);
for (min_y..max_y) |y| {
for (min_x..max_x) |x| {
const left = x < start_x + line_width;
const top = y < start_y + line_width;
const right = end_x - line_width - 1 < x;
const bottom = end_y - line_width - 1 < y;
if (left or top or right or bottom) {
const idx: usize = y * @as(usize, @intCast(buffer.width)) + x;
pixels[idx] = color;
}
}
}
}
fn fillRect(
buffer: platform.OffscreenBuffer,
pos_x: i32,
@@ -91,6 +186,11 @@ fn fillRect(
if (start_x < 0) start_x = 0;
if (start_y < 0) start_y = 0;
if (buffer.width < start_x) start_x = buffer.width;
if (buffer.height < start_y) start_y = buffer.height;
if (end_x < 0) end_x = 0;
if (end_y < 0) end_y = 0;
if (buffer.width < end_x) end_x = buffer.width;
if (buffer.height < end_y) end_y = buffer.height;
@@ -113,7 +213,22 @@ fn fillSquare(
fillRect(buffer, x, y, size, size, color);
}
pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: platform.AppInput) void {
fn keyPressed(key: *platform.Key) bool {
var result = false;
if (key.down) {
if (key.frame_count == 0) {
result = true;
}
key.frame_count += 1;
} else {
key.frame_count = 0;
}
return result;
}
pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBuffer, input: *platform.AppInput) void {
var state: *State = @ptrCast(@alignCast(memory.permanent_storage));
const mid = V2{
@@ -125,6 +240,7 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[0] = Brick{
.rotating = false,
.rotating_time = 0,
.pos = V2{ .x = 0, .y = 50 },
.color = 0xFFFF0000,
.tiles = [4][4]u32{
@@ -137,8 +253,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[1] = Brick{
.rotating = false,
.pos = V2{ .x = 32, .y = 50 },
.color = 0xFFEF0000,
.rotating_time = 0,
.pos = V2{ .x = 256, .y = 50 },
.color = 0xFF665566,
.tiles = [4][4]u32{
.{ 2, 2, 2, 0 },
.{ 2, 2, 0, 0 },
@@ -149,8 +266,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[2] = Brick{
.rotating = false,
.pos = V2{ .x = 64, .y = 50 },
.color = 0xFFDF0000,
.rotating_time = 0,
.pos = V2{ .x = 384, .y = 50 },
.color = 0xFF33A000,
.tiles = [4][4]u32{
.{ 3, 3, 3, 0 },
.{ 0, 0, 3, 3 },
@@ -161,8 +279,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[3] = Brick{
.rotating = false,
.pos = V2{ .x = 96, .y = 50 },
.color = 0xFFCF0000,
.rotating_time = 0,
.pos = V2{ .x = 512, .y = 50 },
.color = 0xFFCF6666,
.tiles = [4][4]u32{
.{ 4, 0, 0, 0 },
.{ 4, 4, 4, 0 },
@@ -173,8 +292,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[4] = Brick{
.rotating = false,
.pos = V2{ .x = 128, .y = 150 },
.color = 0xFFBF0000,
.rotating_time = 0,
.pos = V2{ .x = 384, .y = 150 },
.color = 0xFF00FFAA,
.tiles = [4][4]u32{
.{ 5, 5, 5, 5 },
.{ 0, 5, 0, 0 },
@@ -185,8 +305,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[5] = Brick{
.rotating = false,
.rotating_time = 0,
.pos = V2{ .x = 160, .y = 250 },
.color = 0xFFAF0000,
.color = 0xFFAF0F0F,
.tiles = [4][4]u32{
.{ 6, 6, 6, 0 },
.{ 6, 0, 0, 0 },
@@ -197,8 +318,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[6] = Brick{
.rotating = false,
.rotating_time = 0,
.pos = V2{ .x = 192, .y = 350 },
.color = 0xFF9F0000,
.color = 0xFF9FFF00,
.tiles = [4][4]u32{
.{ 7, 7, 7, 0 },
.{ 7, 7, 7, 0 },
@@ -209,8 +331,9 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
state.bricks[7] = Brick{
.rotating = false,
.rotating_time = 0,
.pos = V2{ .x = 224, .y = 450 },
.color = 0xFF8F0000,
.color = 0xFF8F00FF,
.tiles = [4][4]u32{
.{ 8, 8, 8, 0 },
.{ 8, 0, 8, 0 },
@@ -225,14 +348,17 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
// Update
const mouse_pos = V2{ .x = @floatCast(input.mouse_x), .y = @floatCast(input.mouse_y) };
const tile_size = V2{
.x = TILE_SIZE,
.y = TILE_SIZE,
};
const mouse_world_pos = mouse_pos.sub(state.camera.pos);
var mouse_on_brick = false;
for (&state.bricks) |*brick| {
brick.rotating = false;
if (brick.rotating) {
brick.rotating_time += input.delta_time;
if (brick.rotating_time > 0.25) {
brick.rotating = false;
brick.rotating_time = 0.0;
}
}
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
@@ -249,11 +375,25 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
const diff = mouse_pos.sub(state.mouse_pos);
brick.pos = brick.pos.add(diff);
if (input.key.space.down and !brick.rotating) {
// TODO: Rotate
if (keyPressed(&input.key.space)) {
brick.rotating = true;
const rotated = brick.rotate();
brick.tiles = rotated;
const bbox = findBrickBoundingBox(brick);
const center = brick.pos.sub(bbox.center);
brick.pos = mouse_world_pos.add(center);
}
// Flip
if (keyPressed(&input.key.f)) {
brick.rotating = true;
const flipped = brick.transpose();
brick.tiles = flipped;
const bbox = findBrickBoundingBox(brick);
const center = brick.pos.sub(bbox.center);
brick.pos = mouse_world_pos.add(center);
}
mouse_on_brick = true;
@@ -294,6 +434,8 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
}
for (state.bricks) |brick| {
const bounding_box = findBrickBoundingBox(&brick);
for (brick.tiles, 0..) |row, i| {
for (row, 0..) |tile, j| {
if (tile > 0) {
@@ -307,11 +449,34 @@ pub fn updateAndRender(memory: *platform.AppMemory, buffer: platform.OffscreenBu
@intFromFloat(camera_pos.x),
@intFromFloat(camera_pos.y),
30,
if (brick.rotating) 0xFF00FF00 else brick.color ,
if (brick.rotating) 0xFF00FF00 else brick.color,
);
}
}
}
if (comptime builtin.mode == std.builtin.OptimizeMode.Debug) {
{
const camera_pos = brick.pos.add(state.camera.pos);
drawRect(buffer, camera_pos, .{ .x = 4 * TILE_SIZE, .y = 4 * TILE_SIZE }, 0xFFFF0000, 1);
}
{
const bbox_size = bounding_box.max.sub(bounding_box.min);
const camera_pos = bounding_box.min.add(state.camera.pos);
drawRect(buffer, camera_pos, bbox_size, 0xFFFF0000, 1);
}
{
const size = 8;
const half = @divTrunc(size, 2);
const camera_pos = bounding_box.center.add(state.camera.pos);
const x: i32 = @intFromFloat(camera_pos.x);
const y: i32 = @intFromFloat(camera_pos.y);
fillSquare(buffer, x - half, y - half, 8, 0xFFFF0000);
}
}
}
state.frame_count += 1;