Add branch block type (#1118)

* Add branch block type

* Fix tabs

* Degenerate quads by setting them (.5,.5,.5)

* Add directional branch connecting

* Fix branches connecting to non-solid blocks

* Simplify branchTransform

* No automatic connections to solid blocks

* Use Neighbor to express branch connections

* Allow manual modification of branch connections

* Update branch model to have solid ends

* Tweak block connecting logic

* Tweak comments

* Tweak branch model UV

* Update branch debug texture

* Remove extra faces on connections

* Apply review suggestions

* Exclude viewThrough blocks from branch updateData

* Revert "Exclude viewThrough blocks from branch updateData"

This reverts commit 5942870253e841669c26db959e956fe416d77b9e.

* Apply review suggestions

* Snakes not allowed

* Fix style issues

* Apply review suggestions

* Fix formatting issue
This commit is contained in:
Krzysztof Wiśniewski 2025-03-03 17:00:42 +01:00 committed by GitHub
parent 1f18486c67
commit c4a4d29a07
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 279 additions and 0 deletions

View File

@ -0,0 +1,11 @@
.{
.tags = .{.wood},
.blockHealth = 5,
.drops = .{
.{ .items = .{.auto} },
},
.absorbedLight = 0x202830,
.rotation = .branch,
.model = "cubyz:branch",
.texture = "cubyz:oak_branch",
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -0,0 +1,108 @@
# Blender 4.1.0
# www.blender.org
o branch
v 0.250000 0.750000 0.750000
v 0.250000 0.750000 0.250000
v 0.250000 0.250000 0.250000
v 0.250000 0.250000 0.750000
v 0.750000 0.250000 0.750000
v 0.750000 0.250000 0.250000
v 0.750000 0.750000 0.250000
v 0.750000 0.750000 0.750000
v 0.750000 0.250000 0.750000
v 0.750000 0.250000 0.250000
v 1.000000 0.250000 0.250000
v 1.000000 0.250000 0.750000
v 1.000000 0.750000 0.750000
v 1.000000 0.750000 0.250000
v 0.750000 0.750000 0.250000
v 0.750000 0.750000 0.750000
v 0.750000 0.250000 0.750000
v 0.750000 0.250000 0.250000
v 0.750000 0.000000 0.250000
v 0.750000 0.000000 0.750000
v 0.250000 0.000000 0.750000
v 0.250000 0.000000 0.250000
v 0.250000 0.250000 0.250000
v 0.250000 0.250000 0.750000
v 0.750000 0.250000 0.250000
v 0.250000 0.250000 0.250000
v 0.250000 0.250000 -0.000000
v 0.750000 0.250000 -0.000000
v 0.750000 0.750000 -0.000000
v 0.250000 0.750000 -0.000000
v 0.250000 0.750000 0.250000
v 0.750000 0.750000 0.250000
v 0.750000 0.250000 1.000000
v 0.250000 0.250000 1.000000
v 0.250000 0.250000 0.750000
v 0.750000 0.250000 0.750000
v 0.750000 0.750000 0.750000
v 0.250000 0.750000 0.750000
v 0.250000 0.750000 1.000000
v 0.750000 0.750000 1.000000
v 0.250000 1.000000 0.750000
v 0.250000 1.000000 0.250000
v 0.250000 0.750000 0.250000
v 0.250000 0.750000 0.750000
v 0.750000 0.750000 0.750000
v 0.750000 0.750000 0.250000
v 0.750000 1.000000 0.250000
v 0.750000 1.000000 0.750000
v 0.000000 0.250000 0.750000
v 0.000000 0.250000 0.250000
v 0.250000 0.250000 0.250000
v 0.250000 0.250000 0.750000
v 0.250000 0.750000 0.750000
v 0.250000 0.750000 0.250000
v 0.000000 0.750000 0.250000
v 0.000000 0.750000 0.750000
vn -1.0000 -0.0000 -0.0000
vn 1.0000 -0.0000 -0.0000
vn -0.0000 -1.0000 -0.0000
vn -0.0000 1.0000 -0.0000
vn -0.0000 -0.0000 -1.0000
vn -0.0000 -0.0000 1.0000
vt 0.062500 0.187500
vt 0.062500 0.062500
vt 0.187500 0.062500
vt 0.187500 0.187500
vt 0.000000 0.062500
vt 0.000000 0.187500
vt 0.187500 0.250000
vt 0.062500 0.250000
vt 0.250000 0.062500
vt 0.250000 0.187500
vt 0.187500 0.000000
vt 0.062500 0.000000
s 0
f 1/1/1 2/2/1 3/3/1 4/4/1
f 5/1/2 6/2/2 7/3/2 8/4/2
f 4/4/3 3/3/3 6/2/3 5/1/3
f 8/4/4 7/1/4 2/2/4 1/3/4
f 9/1/3 10/2/3 11/5/3 12/6/3
f 13/7/4 14/8/4 15/1/4 16/4/4
f 10/2/5 15/1/5 14/6/5 11/5/5
f 12/6/6 13/5/6 16/2/6 9/1/6
f 20/8/6 17/1/6 24/4/6 21/7/6
f 20/6/2 19/5/2 18/2/2 17/1/2
f 24/4/1 23/3/1 22/9/1 21/10/1
f 22/11/5 23/3/5 18/2/5 19/12/5
f 5/1/6 8/2/6 1/3/6 4/4/6
f 3/3/5 2/4/5 7/1/5 6/2/5
f 25/2/3 26/3/3 27/11/3 28/12/3
f 29/6/4 30/5/4 31/2/4 32/1/4
f 26/3/1 31/2/1 30/12/1 27/11/1
f 28/12/2 29/11/2 32/3/2 25/2/2
f 33/8/3 34/7/3 35/4/3 36/1/3
f 37/4/4 38/3/4 39/9/4 40/10/4
f 34/7/1 39/8/1 38/1/1 35/4/1
f 36/1/2 37/4/2 40/7/2 33/8/2
f 41/6/1 42/5/1 43/2/1 44/1/1
f 45/4/2 46/3/2 47/9/2 48/10/2
f 43/4/5 42/7/5 47/8/5 46/1/5
f 45/2/6 48/12/6 41/11/6 44/3/6
f 49/10/3 50/9/3 51/3/3 52/4/3
f 53/3/4 54/2/4 55/12/4 56/11/4
f 50/9/5 55/10/5 54/4/5 51/3/5
f 52/4/6 53/3/6 56/9/6 49/10/6

View File

@ -321,6 +321,166 @@ pub const RotationModes = struct {
return true;
}
};
pub const Branch = struct { // MARK: Branch
pub const id: []const u8 = "branch";
pub const dependsOnNeighbors = true;
var branchModels: std.StringHashMap(u16) = undefined;
const BranchData = packed struct(u6) {
enabledConnections: u6,
pub fn init(blockData: u16) BranchData {
return .{.enabledConnections = @truncate(blockData)};
}
pub fn isConnected(self: @This(), neighbor: Neighbor) bool {
return (self.enabledConnections & Neighbor.bitMask(neighbor)) != 0;
}
pub fn setConnection(self: *@This(), neighbor: Neighbor, value: bool) void {
if(value) {
self.enabledConnections |= Neighbor.bitMask(neighbor);
} else {
self.enabledConnections &= ~Neighbor.bitMask(neighbor);
}
}
};
fn init() void {
branchModels = .init(main.globalAllocator.allocator);
}
fn deinit() void {
branchModels.deinit();
}
fn branchTransform(quad: *main.models.QuadInfo, data: BranchData) void {
for(&quad.corners) |*corner| {
if((!data.isConnected(Neighbor.dirNegX) and corner[0] == 0) or
(!data.isConnected(Neighbor.dirPosX) and corner[0] == 1) or
(!data.isConnected(Neighbor.dirNegY) and corner[1] == 0) or
(!data.isConnected(Neighbor.dirPosY) and corner[1] == 1) or
(!data.isConnected(Neighbor.dirDown) and corner[2] == 0) or
(!data.isConnected(Neighbor.dirUp) and corner[2] == 1)) return degenerateQuad(quad);
}
}
fn degenerateQuad(quad: *main.models.QuadInfo) void {
for(&quad.corners) |*corner| {
corner.* = @splat(0.5);
}
}
pub fn createBlockModel(modelId: []const u8) u16 {
if(branchModels.get(modelId)) |modelIndex| return modelIndex;
const baseModelIndex = main.models.getModelIndex(modelId);
const baseModel = main.models.models.items[baseModelIndex];
const modelIndex: u16 = baseModel.transformModel(branchTransform, .{BranchData.init(0)});
for(1..64) |branchData| {
_ = baseModel.transformModel(branchTransform, .{BranchData.init(@truncate(branchData))});
}
branchModels.put(modelId, modelIndex) catch unreachable;
return modelIndex;
}
pub fn model(block: Block) u16 {
return blocks.meshes.modelIndexStart(block) + (block.data & 63);
}
pub fn generateData(
_: *main.game.World,
_: Vec3i,
_: Vec3f,
_: Vec3f,
_: Vec3i,
neighbor: ?Neighbor,
currentBlock: *Block,
neighborBlock: Block,
blockPlacing: bool,
) bool {
const blockBaseModel = blocks.meshes.modelIndexStart(currentBlock.*);
const neighborBaseModel = blocks.meshes.modelIndexStart(neighborBlock);
if(blockPlacing or blockBaseModel == neighborBaseModel or neighborBlock.solid()) {
const neighborModel = blocks.meshes.model(neighborBlock);
var currentData = BranchData.init(currentBlock.data);
// Branch block upon placement should extend towards a block it was placed
// on if the block is solid or also uses branch model.
const targetVal = ((neighborBlock.solid() and !neighborBlock.viewThrough()) and (blockBaseModel == neighborBaseModel or main.models.models.items[neighborModel].isNeighborOccluded[neighbor.?.reverse().toInt()]));
currentData.setConnection(neighbor.?, targetVal);
const result: u16 = currentData.enabledConnections;
if(result == currentBlock.data) return false;
currentBlock.data = result;
return true;
}
return false;
}
pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool {
const blockBaseModel = blocks.meshes.modelIndexStart(block.*);
const neighborBaseModel = blocks.meshes.modelIndexStart(neighborBlock);
var currentData = BranchData.init(block.data);
// Handle joining with other branches. While placed, branches extend in a
// opposite direction than they were placed from, effectively connecting
// to the block they were placed at.
if(blockBaseModel == neighborBaseModel) {
const neighborData = BranchData.init(neighborBlock.data);
currentData.setConnection(neighbor, neighborData.isConnected(neighbor.reverse()));
} else if(!neighborBlock.solid()) {
currentData.setConnection(neighbor, false);
}
const result: u16 = currentData.enabledConnections;
if(result == block.data) return false;
block.data = result;
return true;
}
fn closestRay(block: Block, relativePlayerPos: Vec3f, playerDir: Vec3f) ?u16 {
var closestIntersectionDistance: f64 = std.math.inf(f64);
var resultBitMask: ?u16 = null;
{
const modelIndex = blocks.meshes.modelIndexStart(block);
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
closestIntersectionDistance = intersection.distance;
resultBitMask = 0;
}
}
for(Neighbor.iterable) |direction| {
const directionBitMask = Neighbor.bitMask(direction);
if((block.data & directionBitMask) != 0) {
const modelIndex = blocks.meshes.modelIndexStart(block) + directionBitMask;
if(RotationMode.DefaultFunctions.rayModelIntersection(modelIndex, relativePlayerPos, playerDir)) |intersection| {
if(@abs(closestIntersectionDistance) > @abs(intersection.distance)) {
closestIntersectionDistance = intersection.distance;
resultBitMask = direction.bitMask();
}
}
}
}
return resultBitMask;
}
pub fn onBlockBreaking(_: ?main.items.Item, relativePlayerPos: Vec3f, playerDir: Vec3f, currentData: *Block) void {
if(closestRay(currentData.*, relativePlayerPos, playerDir)) |directionBitMask| {
// If player destroys a central part of branch block, branch block is completely destroyed.
if(directionBitMask == 0) {
currentData.typ = 0;
currentData.data = 0;
return;
}
// Otherwise only the connection player aimed at is destroyed.
currentData.data &= ~directionBitMask;
}
}
};
pub const Stairs = struct { // MARK: Stairs
pub const id: []const u8 = "stairs";
var modelIndex: u16 = 0;