From e7fe6837c163713475b29058b739bda5209cb8aa Mon Sep 17 00:00:00 2001 From: yello Date: Sun, 27 Sep 2026 17:32:26 +0200 Subject: [PATCH 1/5] Place blocks against actual hit face on non-full blocks --- mods/cubyz/rotations/carpet.zig | 1 + src/renderer.zig | 22 ++++++++++++++++++---- src/rotation.zig | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/mods/cubyz/rotations/carpet.zig b/mods/cubyz/rotations/carpet.zig index 7660c6d7fe..6f81538e63 100644 --- a/mods/cubyz/rotations/carpet.zig +++ b/mods/cubyz/rotations/carpet.zig @@ -18,6 +18,7 @@ const ZonElement = main.ZonElement; const torch = @import("torch.zig"); pub const naturalStandard: u16 = 0b10000; +pub const useAccuratePlacementNormal = false; var rotatedModels: std.StringHashMap(ModelIndex) = undefined; const CarpetData = packed struct(u6) { negX: bool, diff --git a/src/renderer.zig b/src/renderer.zig index c572640f7e..60546bca97 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -993,6 +993,17 @@ pub const MeshSelection = struct { // MARK: MeshSelection // TODO: Test entities } + fn dominantAxisNeighbor(normal: Vec3f) Vec3i { + const absNormal = @abs(normal); + if (absNormal[0] >= absNormal[1] and absNormal[0] >= absNormal[2]) { + return .{if (normal[0] >= 0) 1 else -1, 0, 0}; + } else if (absNormal[1] >= absNormal[2]) { + return .{0, if (normal[1] >= 0) 1 else -1, 0}; + } else { + return .{0, 0, if (normal[2] >= 0) 1 else -1}; + } + } + fn canPlaceBlock(pos: Vec3i, block: main.blocks.Block) bool { if (main.physics.collision.collideWithBlock(block, pos[0], pos[1], pos[2], main.game.Player.getPosBlocking() + main.game.Player.outerBoundingBox.center(), main.game.Player.outerBoundingBox.extent(), .{0, 0, 0}) != null) { return false; @@ -1025,14 +1036,17 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } // Check the block in front of it: - const neighborPos = posBeforeBlock; - neighborDir = selectedPos - posBeforeBlock; + const useAccurateNormal = oldBlock.mode().useAccuratePlacementNormal; + const roundedNormal = dominantAxisNeighbor(selectionNormal); + const neighborPos = if (useAccurateNormal) selectedPos + roundedNormal else posBeforeBlock; + neighborDir = if (useAccurateNormal) -roundedNormal else selectedPos - posBeforeBlock; + const neighborOfSelectionValue = if (useAccurateNormal) chunk.Neighbor.fromRelPos(neighborDir).? else neighborOfSelection; const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const neighborBlock = block; oldBlock = mesh_storage.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; block = oldBlock; if (block.typ == itemBlock) { - if (rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, false)) { + if (rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelectionValue, &block, neighborBlock, false)) { if (!canPlaceBlock(neighborPos, block)) return; updateBlockAndSendUpdate(inventory, slot, neighborPos, oldBlock, block); return; @@ -1041,7 +1055,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection if (!block.replaceable()) return; block.typ = itemBlock; block.data = 0; - if (rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, true)) { + if (rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelectionValue, &block, neighborBlock, true)) { if (!canPlaceBlock(neighborPos, block)) return; updateBlockAndSendUpdate(inventory, slot, neighborPos, oldBlock, block); return; diff --git a/src/rotation.zig b/src/rotation.zig index 8b68e539f6..503f2656e4 100644 --- a/src/rotation.zig +++ b/src/rotation.zig @@ -84,7 +84,7 @@ pub const RotationMode = struct { // MARK: RotationMode } if (minimum != null) { // Invert the normal if the player is behind the face (eg. cross model) - if (vec.dot(normal.?, relativePlayerPos) < 0.0) { + if (vec.dot(normal.?, playerDir) > 0.0) { normal = -normal.?; } return .{ @@ -145,6 +145,8 @@ pub const RotationMode = struct { // MARK: RotationMode /// if the block should be destroyed or changed when a certain neighbor is removed. dependsOnNeighbors: bool = false, + useAccuratePlacementNormal: bool = true, + /// The default rotation data intended for generation algorithms naturalStandard: u16 = 0, From 5f2ff981e838a6c71b04cb314022bb6483cb211a Mon Sep 17 00:00:00 2001 From: yello Date: Fri, 2 Oct 2026 21:49:00 +0200 Subject: [PATCH 2/5] Make grid-face placement block property instead of rotation mode flag --- assets/cubyz/blocks/cloth/_defaults.zig.zon | 1 + assets/cubyz/blocks/daisies.zig.zon | 1 + assets/cubyz/blocks/dandelions.zig.zon | 1 + assets/cubyz/blocks/ivy.zig.zon | 1 + assets/cubyz/blocks/osier.zig.zon | 1 + mods/cubyz/rotations/carpet.zig | 1 - src/blocks.zig | 6 ++++++ src/renderer.zig | 2 +- src/rotation.zig | 2 -- 9 files changed, 12 insertions(+), 4 deletions(-) diff --git a/assets/cubyz/blocks/cloth/_defaults.zig.zon b/assets/cubyz/blocks/cloth/_defaults.zig.zon index 436ea7c316..4d35887c2e 100644 --- a/assets/cubyz/blocks/cloth/_defaults.zig.zon +++ b/assets/cubyz/blocks/cloth/_defaults.zig.zon @@ -7,5 +7,6 @@ .collide = false, .model = "cubyz:carpet", .rotation = "cubyz:carpet", + .useGridFacePlacement = true, .degradable = true, } diff --git a/assets/cubyz/blocks/daisies.zig.zon b/assets/cubyz/blocks/daisies.zig.zon index 3645996d61..8297d0d587 100644 --- a/assets/cubyz/blocks/daisies.zig.zon +++ b/assets/cubyz/blocks/daisies.zig.zon @@ -12,6 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", + .useGridFacePlacement = true, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/assets/cubyz/blocks/dandelions.zig.zon b/assets/cubyz/blocks/dandelions.zig.zon index 8fd742b0cc..c61811794f 100644 --- a/assets/cubyz/blocks/dandelions.zig.zon +++ b/assets/cubyz/blocks/dandelions.zig.zon @@ -12,6 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", + .useGridFacePlacement = true, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/assets/cubyz/blocks/ivy.zig.zon b/assets/cubyz/blocks/ivy.zig.zon index d7084a756a..46cc380922 100644 --- a/assets/cubyz/blocks/ivy.zig.zon +++ b/assets/cubyz/blocks/ivy.zig.zon @@ -12,6 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", + .useGridFacePlacement = true, .texture = "cubyz:ivy", .item = .{ .texture = "ivy.png", diff --git a/assets/cubyz/blocks/osier.zig.zon b/assets/cubyz/blocks/osier.zig.zon index 61cf66ccb5..5007b3d4e3 100644 --- a/assets/cubyz/blocks/osier.zig.zon +++ b/assets/cubyz/blocks/osier.zig.zon @@ -12,6 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", + .useGridFacePlacement = true, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/mods/cubyz/rotations/carpet.zig b/mods/cubyz/rotations/carpet.zig index 6f81538e63..7660c6d7fe 100644 --- a/mods/cubyz/rotations/carpet.zig +++ b/mods/cubyz/rotations/carpet.zig @@ -18,7 +18,6 @@ const ZonElement = main.ZonElement; const torch = @import("torch.zig"); pub const naturalStandard: u16 = 0b10000; -pub const useAccuratePlacementNormal = false; var rotatedModels: std.StringHashMap(ModelIndex) = undefined; const CarpetData = packed struct(u6) { negX: bool, diff --git a/src/blocks.zig b/src/blocks.zig index 571699df88..e6ee0ae956 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -117,6 +117,7 @@ var _degradable: [maxBlockCount]bool = undefined; var _viewThrough: [maxBlockCount]bool = undefined; var _alwaysViewThrough: [maxBlockCount]bool = undefined; var _hasBackFace: [maxBlockCount]bool = undefined; +var _useGridFacePlacement: [maxBlockCount]bool = undefined; var _tags: [maxBlockCount][]Tag = undefined; var _light: [maxBlockCount]u32 = undefined; /// How much light this block absorbs if it is transparent @@ -185,6 +186,7 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 { _alwaysViewThrough[size] = zon.get(bool, "alwaysViewThrough") orelse false; _viewThrough[size] = (zon.get(bool, "viewThrough") orelse false) or _transparent[size] or _alwaysViewThrough[size]; _hasBackFace[size] = zon.get(bool, "hasBackFace") orelse false; + _useGridFacePlacement[size] = zon.get(bool, "useGridFacePlacement") orelse false; _friction[size] = zon.get(f32, "friction") orelse 20; _bounciness[size] = zon.get(f32, "bounciness") orelse 0.0; _density[size] = zon.get(f32, "density") orelse main.physics.airDensity; @@ -478,6 +480,10 @@ pub const Block = packed struct(u32) { // MARK: Block return _hasBackFace[self.typ]; } + pub inline fn useGridFacePlacement(self: Block) bool { + return _useGridFacePlacement[self.typ]; + } + pub inline fn tags(self: Block) []const Tag { return _tags[self.typ]; } diff --git a/src/renderer.zig b/src/renderer.zig index 60546bca97..e7b91862a0 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -1036,7 +1036,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } // Check the block in front of it: - const useAccurateNormal = oldBlock.mode().useAccuratePlacementNormal; + const useAccurateNormal = !oldBlock.useGridFacePlacement(); const roundedNormal = dominantAxisNeighbor(selectionNormal); const neighborPos = if (useAccurateNormal) selectedPos + roundedNormal else posBeforeBlock; neighborDir = if (useAccurateNormal) -roundedNormal else selectedPos - posBeforeBlock; diff --git a/src/rotation.zig b/src/rotation.zig index 503f2656e4..44a062aa3a 100644 --- a/src/rotation.zig +++ b/src/rotation.zig @@ -145,8 +145,6 @@ pub const RotationMode = struct { // MARK: RotationMode /// if the block should be destroyed or changed when a certain neighbor is removed. dependsOnNeighbors: bool = false, - useAccuratePlacementNormal: bool = true, - /// The default rotation data intended for generation algorithms naturalStandard: u16 = 0, From 31c37b71311687a33998c4680ad79329e7ad84a7 Mon Sep 17 00:00:00 2001 From: yello Date: Fri, 2 Oct 2026 22:14:52 +0200 Subject: [PATCH 3/5] Replace placement bool with PlacementMode enum --- assets/cubyz/blocks/cloth/_defaults.zig.zon | 2 +- assets/cubyz/blocks/daisies.zig.zon | 2 +- assets/cubyz/blocks/dandelions.zig.zon | 2 +- assets/cubyz/blocks/ivy.zig.zon | 2 +- assets/cubyz/blocks/osier.zig.zon | 2 +- src/blocks.zig | 13 +++++++++---- src/renderer.zig | 2 +- 7 files changed, 15 insertions(+), 10 deletions(-) diff --git a/assets/cubyz/blocks/cloth/_defaults.zig.zon b/assets/cubyz/blocks/cloth/_defaults.zig.zon index 4d35887c2e..5bb0dea0b5 100644 --- a/assets/cubyz/blocks/cloth/_defaults.zig.zon +++ b/assets/cubyz/blocks/cloth/_defaults.zig.zon @@ -7,6 +7,6 @@ .collide = false, .model = "cubyz:carpet", .rotation = "cubyz:carpet", - .useGridFacePlacement = true, + .placementMode = .gridNeighbor, .degradable = true, } diff --git a/assets/cubyz/blocks/daisies.zig.zon b/assets/cubyz/blocks/daisies.zig.zon index 8297d0d587..a726760c5c 100644 --- a/assets/cubyz/blocks/daisies.zig.zon +++ b/assets/cubyz/blocks/daisies.zig.zon @@ -12,7 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", - .useGridFacePlacement = true, + .placementMode = .gridNeighbor, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/assets/cubyz/blocks/dandelions.zig.zon b/assets/cubyz/blocks/dandelions.zig.zon index c61811794f..346dc6b228 100644 --- a/assets/cubyz/blocks/dandelions.zig.zon +++ b/assets/cubyz/blocks/dandelions.zig.zon @@ -12,7 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", - .useGridFacePlacement = true, + .placementMode = .gridNeighbor, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/assets/cubyz/blocks/ivy.zig.zon b/assets/cubyz/blocks/ivy.zig.zon index 46cc380922..96cadbc490 100644 --- a/assets/cubyz/blocks/ivy.zig.zon +++ b/assets/cubyz/blocks/ivy.zig.zon @@ -12,7 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", - .useGridFacePlacement = true, + .placementMode = .gridNeighbor, .texture = "cubyz:ivy", .item = .{ .texture = "ivy.png", diff --git a/assets/cubyz/blocks/osier.zig.zon b/assets/cubyz/blocks/osier.zig.zon index 5007b3d4e3..9ec8969ec4 100644 --- a/assets/cubyz/blocks/osier.zig.zon +++ b/assets/cubyz/blocks/osier.zig.zon @@ -12,7 +12,7 @@ .absorbedLight = 0x121012, .model = "cubyz:plane", .rotation = "cubyz:carpet", - .useGridFacePlacement = true, + .placementMode = .gridNeighbor, .onUpdate = .{ .type = .check_support_blocks, }, diff --git a/src/blocks.zig b/src/blocks.zig index e6ee0ae956..85211ab160 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -53,6 +53,11 @@ pub const Ore = struct { seed: u64, }; +const PlacementMode = enum { + faceNormal, + gridNeighbor, +}; + const SelectionCapabilities = union(enum) { always: void, custom: packed struct(u1) { @@ -117,7 +122,7 @@ var _degradable: [maxBlockCount]bool = undefined; var _viewThrough: [maxBlockCount]bool = undefined; var _alwaysViewThrough: [maxBlockCount]bool = undefined; var _hasBackFace: [maxBlockCount]bool = undefined; -var _useGridFacePlacement: [maxBlockCount]bool = undefined; +var _placementMode: [maxBlockCount]PlacementMode = undefined; var _tags: [maxBlockCount][]Tag = undefined; var _light: [maxBlockCount]u32 = undefined; /// How much light this block absorbs if it is transparent @@ -186,7 +191,7 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 { _alwaysViewThrough[size] = zon.get(bool, "alwaysViewThrough") orelse false; _viewThrough[size] = (zon.get(bool, "viewThrough") orelse false) or _transparent[size] or _alwaysViewThrough[size]; _hasBackFace[size] = zon.get(bool, "hasBackFace") orelse false; - _useGridFacePlacement[size] = zon.get(bool, "useGridFacePlacement") orelse false; + _placementMode[size] = zon.get(PlacementMode, "placementMode") orelse .faceNormal; _friction[size] = zon.get(f32, "friction") orelse 20; _bounciness[size] = zon.get(f32, "bounciness") orelse 0.0; _density[size] = zon.get(f32, "density") orelse main.physics.airDensity; @@ -480,8 +485,8 @@ pub const Block = packed struct(u32) { // MARK: Block return _hasBackFace[self.typ]; } - pub inline fn useGridFacePlacement(self: Block) bool { - return _useGridFacePlacement[self.typ]; + pub inline fn placementMode(self: Block) PlacementMode { + return _placementMode[self.typ]; } pub inline fn tags(self: Block) []const Tag { diff --git a/src/renderer.zig b/src/renderer.zig index e7b91862a0..5a50f3a2d5 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -1036,7 +1036,7 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } // Check the block in front of it: - const useAccurateNormal = !oldBlock.useGridFacePlacement(); + const useAccurateNormal = oldBlock.placementMode() == .faceNormal; const roundedNormal = dominantAxisNeighbor(selectionNormal); const neighborPos = if (useAccurateNormal) selectedPos + roundedNormal else posBeforeBlock; neighborDir = if (useAccurateNormal) -roundedNormal else selectedPos - posBeforeBlock; From 48acc8b91a58c4d3fdfdec89d41a1d56b991a1f7 Mon Sep 17 00:00:00 2001 From: yello Date: Fri, 2 Oct 2026 23:25:35 +0200 Subject: [PATCH 4/5] Fall back to grid neighbor placement for tilted faces --- src/renderer.zig | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/renderer.zig b/src/renderer.zig index 5a50f3a2d5..2b2100e925 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -993,15 +993,16 @@ pub const MeshSelection = struct { // MARK: MeshSelection // TODO: Test entities } - fn dominantAxisNeighbor(normal: Vec3f) Vec3i { - const absNormal = @abs(normal); - if (absNormal[0] >= absNormal[1] and absNormal[0] >= absNormal[2]) { - return .{if (normal[0] >= 0) 1 else -1, 0, 0}; - } else if (absNormal[1] >= absNormal[2]) { - return .{0, if (normal[1] >= 0) 1 else -1, 0}; - } else { - return .{0, 0, if (normal[2] >= 0) 1 else -1}; + fn axisAlignedNormal(normal: Vec3f) ?Vec3i { + const tolerance = 0.999; + inline for (0..3) |axis| { + if (@abs(normal[axis]) >= tolerance) { + var result: Vec3i = .{0, 0, 0}; + result[axis] = if (normal[axis] > 0) 1 else -1; + return result; + } } + return null; } fn canPlaceBlock(pos: Vec3i, block: main.blocks.Block) bool { @@ -1036,11 +1037,10 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } // Check the block in front of it: - const useAccurateNormal = oldBlock.placementMode() == .faceNormal; - const roundedNormal = dominantAxisNeighbor(selectionNormal); - const neighborPos = if (useAccurateNormal) selectedPos + roundedNormal else posBeforeBlock; - neighborDir = if (useAccurateNormal) -roundedNormal else selectedPos - posBeforeBlock; - const neighborOfSelectionValue = if (useAccurateNormal) chunk.Neighbor.fromRelPos(neighborDir).? else neighborOfSelection; + const faceNormal: ?Vec3i = if (oldBlock.placementMode() == .faceNormal) axisAlignedNormal(selectionNormal) else null; + const neighborPos = if (faceNormal) |normal| selectedPos + normal else posBeforeBlock; + neighborDir = if (faceNormal) |normal| -normal else selectedPos - posBeforeBlock; + const neighborOfSelectionValue = if (faceNormal != null) chunk.Neighbor.fromRelPos(neighborDir).? else neighborOfSelection; const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const neighborBlock = block; oldBlock = mesh_storage.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return; From ec253dd7eef0effc64ea528d5f6c5bf7e754cc58 Mon Sep 17 00:00:00 2001 From: yello Date: Sat, 3 Oct 2026 17:29:37 +0200 Subject: [PATCH 5/5] Place blocks based on selection bounding box face --- src/blocks.zig | 4 ++-- src/renderer.zig | 30 +++++++++++++++++++----------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/blocks.zig b/src/blocks.zig index 85211ab160..3deb4032fd 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -54,7 +54,7 @@ pub const Ore = struct { }; const PlacementMode = enum { - faceNormal, + boundingBox, gridNeighbor, }; @@ -191,7 +191,7 @@ pub fn register(_: []const u8, id: []const u8, zon: ZonElement) u16 { _alwaysViewThrough[size] = zon.get(bool, "alwaysViewThrough") orelse false; _viewThrough[size] = (zon.get(bool, "viewThrough") orelse false) or _transparent[size] or _alwaysViewThrough[size]; _hasBackFace[size] = zon.get(bool, "hasBackFace") orelse false; - _placementMode[size] = zon.get(PlacementMode, "placementMode") orelse .faceNormal; + _placementMode[size] = zon.get(PlacementMode, "placementMode") orelse .boundingBox; _friction[size] = zon.get(f32, "friction") orelse 20; _bounciness[size] = zon.get(f32, "bounciness") orelse 0.0; _density[size] = zon.get(f32, "density") orelse main.physics.airDensity; diff --git a/src/renderer.zig b/src/renderer.zig index 2b2100e925..02a9ad8db2 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -993,16 +993,23 @@ pub const MeshSelection = struct { // MARK: MeshSelection // TODO: Test entities } - fn axisAlignedNormal(normal: Vec3f) ?Vec3i { - const tolerance = 0.999; + fn boundingBoxFaceNormal(relativePos: Vec3f, dir: Vec3f, min: Vec3f, max: Vec3f) ?Vec3i { + var tEnter: f32 = -std.math.inf(f32); + var result: ?Vec3i = null; inline for (0..3) |axis| { - if (@abs(normal[axis]) >= tolerance) { - var result: Vec3i = .{0, 0, 0}; - result[axis] = if (normal[axis] > 0) 1 else -1; - return result; + if (dir[axis] != 0) { + const t1 = (min[axis] - relativePos[axis])/dir[axis]; + const t2 = (max[axis] - relativePos[axis])/dir[axis]; + const t = @min(t1, t2); + if (t > tEnter) { + tEnter = t; + var normal: Vec3i = .{0, 0, 0}; + normal[axis] = if (dir[axis] > 0) -1 else 1; + result = normal; + } } } - return null; + return if (tEnter >= 0) result else null; } fn canPlaceBlock(pos: Vec3i, block: main.blocks.Block) bool { @@ -1037,10 +1044,11 @@ pub const MeshSelection = struct { // MARK: MeshSelection } } // Check the block in front of it: - const faceNormal: ?Vec3i = if (oldBlock.placementMode() == .faceNormal) axisAlignedNormal(selectionNormal) else null; - const neighborPos = if (faceNormal) |normal| selectedPos + normal else posBeforeBlock; - neighborDir = if (faceNormal) |normal| -normal else selectedPos - posBeforeBlock; - const neighborOfSelectionValue = if (faceNormal != null) chunk.Neighbor.fromRelPos(neighborDir).? else neighborOfSelection; + const selectedRelPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos))); + const boxFaceNormal: ?Vec3i = if (oldBlock.placementMode() == .boundingBox) boundingBoxFaceNormal(selectedRelPos, lastDir, selectionMin, selectionMax) else null; + const neighborPos = if (boxFaceNormal) |normal| selectedPos + normal else posBeforeBlock; + neighborDir = if (boxFaceNormal) |normal| -normal else selectedPos - posBeforeBlock; + const neighborOfSelectionValue = if (boxFaceNormal != null) chunk.Neighbor.fromRelPos(neighborDir).? else neighborOfSelection; const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos))); const neighborBlock = block; oldBlock = mesh_storage.getBlockFromRenderThread(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return;