Allow branches to have a shell model that's always rendered and add leafy branches for all wood types.

This commit is contained in:
IntegratedQuantum 2025-03-23 20:21:37 +01:00
parent 8f0693f7b0
commit 42268c4b22
8 changed files with 146 additions and 23 deletions

View File

@ -0,0 +1,12 @@
.{
.tags = .{.leaf},
.blockHealth = 0.5,
.absorbedLight = 0x202830,
.alwaysViewThrough = true,
.rotation = .branch,
.model = .{
.radius = 4,
.shellModel = "cubyz:cube",
.textureSlotOffset = 6,
},
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:baobab_leaves", "cubyz:baobab_branch"}},
},
.texture = "cubyz:baobab_leaves",
.texture6 = "cubyz:branch/baobab/dot",
.texture7 = "cubyz:branch/baobab/half_line",
.texture8 = "cubyz:branch/baobab/line",
.texture9 = "cubyz:branch/baobab/bend",
.texture10 = "cubyz:branch/baobab/intersection",
.texture11 = "cubyz:branch/baobab/cross",
.lodReplacement = "cubyz:baobab_leaves_opaque",
.opaqueVariant = "cubyz:baobab_leaves_opaque",
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:birch_leaves", "cubyz:birch_branch"}},
},
.texture = "cubyz:birch_leaves",
.texture6 = "cubyz:branch/birch/dot",
.texture7 = "cubyz:branch/birch/half_line",
.texture8 = "cubyz:branch/birch/line",
.texture9 = "cubyz:branch/birch/bend",
.texture10 = "cubyz:branch/birch/intersection",
.texture11 = "cubyz:branch/birch/cross",
.lodReplacement = "cubyz:birch_leaves_opaque",
.opaqueVariant = "cubyz:birch_leaves_opaque",
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:mahogany_leaves", "cubyz:mahogany_branch"}},
},
.texture = "cubyz:mahogany_leaves",
.texture6 = "cubyz:branch/mahogany/dot",
.texture7 = "cubyz:branch/mahogany/half_line",
.texture8 = "cubyz:branch/mahogany/line",
.texture9 = "cubyz:branch/mahogany/bend",
.texture10 = "cubyz:branch/mahogany/intersection",
.texture11 = "cubyz:branch/mahogany/cross",
.lodReplacement = "cubyz:mahogany_leaves_opaque",
.opaqueVariant = "cubyz:mahogany_leaves_opaque",
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:oak_leaves", "cubyz:oak_branch"}},
},
.texture = "cubyz:oak_leaves",
.texture6 = "cubyz:branch/oak/dot",
.texture7 = "cubyz:branch/oak/half_line",
.texture8 = "cubyz:branch/oak/line",
.texture9 = "cubyz:branch/oak/bend",
.texture10 = "cubyz:branch/oak/intersection",
.texture11 = "cubyz:branch/oak/cross",
.lodReplacement = "cubyz:oak_leaves_opaque",
.opaqueVariant = "cubyz:oak_leaves_opaque",
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:pine_needles", "cubyz:pine_branch"}},
},
.texture = "cubyz:pine_needles",
.texture6 = "cubyz:branch/pine/dot",
.texture7 = "cubyz:branch/pine/half_line",
.texture8 = "cubyz:branch/pine/line",
.texture9 = "cubyz:branch/pine/bend",
.texture10 = "cubyz:branch/pine/intersection",
.texture11 = "cubyz:branch/pine/cross",
.lodReplacement = "cubyz:pine_needles_opaque",
.opaqueVariant = "cubyz:pine_needles_opaque",
}

View File

@ -0,0 +1,14 @@
.{
.drops = .{
.{.items = .{"cubyz:willow_leaves", "cubyz:willow_branch"}},
},
.texture = "cubyz:willow_leaves",
.texture6 = "cubyz:branch/willow/dot",
.texture7 = "cubyz:branch/willow/half_line",
.texture8 = "cubyz:branch/willow/line",
.texture9 = "cubyz:branch/willow/bend",
.texture10 = "cubyz:branch/willow/intersection",
.texture11 = "cubyz:branch/willow/cross",
.lodReplacement = "cubyz:willow_leaves_opaque",
.opaqueVariant = "cubyz:willow_leaves_opaque",
}

View File

@ -403,7 +403,23 @@ pub const RotationModes = struct {
pub const Branch = struct { // MARK: Branch pub const Branch = struct { // MARK: Branch
pub const id: []const u8 = "branch"; pub const id: []const u8 = "branch";
pub const dependsOnNeighbors = true; pub const dependsOnNeighbors = true;
var branchModels: std.AutoHashMap(u32, ModelIndex) = undefined; var branchModels: std.HashMap(HashMapKey, ModelIndex, HashMapKey, std.hash_map.default_max_load_percentage) = undefined;
const HashMapKey = struct {
radius: u16,
shellModelId: []const u8,
textureSlotOffset: u32,
pub fn hash(_: HashMapKey, val: HashMapKey) u64 {
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHashStrat(&hasher, val, .DeepRecursive);
return hasher.final();
}
pub fn eql(_: HashMapKey, val1: HashMapKey, val2: HashMapKey) bool {
if(val1.radius != val2.radius) return false;
if(val1.textureSlotOffset != val2.textureSlotOffset) return false;
return std.mem.eql(u8, val1.shellModelId, val2.shellModelId);
}
};
const BranchData = packed struct(u6) { const BranchData = packed struct(u6) {
enabledConnections: u6, enabledConnections: u6,
@ -425,7 +441,7 @@ pub const RotationModes = struct {
}; };
fn init() void { fn init() void {
branchModels = .init(main.globalAllocator.allocator); branchModels = .initContext(main.globalAllocator.allocator, undefined);
} }
fn deinit() void { fn deinit() void {
@ -460,7 +476,7 @@ pub const RotationModes = struct {
cross: void, cross: void,
}; };
fn rotateQuad(originalCorners: [4]Vec2f, pattern: Pattern, min: f32, max: f32, side: Neighbor) main.models.QuadInfo { fn rotateQuad(originalCorners: [4]Vec2f, pattern: Pattern, min: f32, max: f32, side: Neighbor, textureSlotOffset: u32) main.models.QuadInfo {
var corners: [4]Vec2f = originalCorners; var corners: [4]Vec2f = originalCorners;
switch(pattern) { switch(pattern) {
@ -498,13 +514,13 @@ pub const RotationModes = struct {
}, },
.cornerUV = originalCorners, .cornerUV = originalCorners,
.normal = @floatFromInt(side.relPos()), .normal = @floatFromInt(side.relPos()),
.textureSlot = @intFromEnum(pattern), .textureSlot = textureSlotOffset + @intFromEnum(pattern),
}; };
return res; return res;
} }
fn addQuads(pattern: Pattern, side: Neighbor, radius: f32, out: *main.List(main.models.QuadInfo)) void { fn addQuads(pattern: Pattern, side: Neighbor, radius: f32, out: *main.List(main.models.QuadInfo), textureSlotOffset: u32) void {
const min: f32 = (8.0 - radius)/16.0; const min: f32 = (8.0 - radius)/16.0;
const max: f32 = (8.0 + radius)/16.0; const max: f32 = (8.0 + radius)/16.0;
switch(pattern) { switch(pattern) {
@ -514,7 +530,7 @@ pub const RotationModes = struct {
.{min, max}, .{min, max},
.{max, min}, .{max, min},
.{max, max}, .{max, max},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
.halfLine => { .halfLine => {
out.append(rotateQuad(.{ out.append(rotateQuad(.{
@ -522,7 +538,7 @@ pub const RotationModes = struct {
.{min, max}, .{min, max},
.{max, 0.0}, .{max, 0.0},
.{max, max}, .{max, max},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
.line => { .line => {
out.append(rotateQuad(.{ out.append(rotateQuad(.{
@ -530,7 +546,7 @@ pub const RotationModes = struct {
.{min, 1.0}, .{min, 1.0},
.{max, 0.0}, .{max, 0.0},
.{max, 1.0}, .{max, 1.0},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
.bend => { .bend => {
out.append(rotateQuad(.{ out.append(rotateQuad(.{
@ -538,7 +554,7 @@ pub const RotationModes = struct {
.{0.0, max}, .{0.0, max},
.{max, 0.0}, .{max, 0.0},
.{max, max}, .{max, max},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
.intersection => { .intersection => {
out.append(rotateQuad(.{ out.append(rotateQuad(.{
@ -546,7 +562,7 @@ pub const RotationModes = struct {
.{0.0, max}, .{0.0, max},
.{1.0, 0.0}, .{1.0, 0.0},
.{1.0, max}, .{1.0, max},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
.cross => { .cross => {
out.append(rotateQuad(.{ out.append(rotateQuad(.{
@ -554,7 +570,7 @@ pub const RotationModes = struct {
.{0.0, 1.0}, .{0.0, 1.0},
.{1.0, 0.0}, .{1.0, 0.0},
.{1.0, 1.0}, .{1.0, 1.0},
}, pattern, min, max, side)); }, pattern, min, max, side, textureSlotOffset));
}, },
} }
} }
@ -638,20 +654,33 @@ pub const RotationModes = struct {
}; };
} }
pub fn createBlockModel(_: Block, _: *u16, zon: ZonElement) ModelIndex { pub fn createBlockModel(_: Block, modeData: *u16, zon: ZonElement) ModelIndex {
const radius = zon.get(f32, "radius", 4); var radius = zon.get(f32, "radius", 4);
if(branchModels.get(@bitCast(radius))) |modelIndex| return modelIndex; const radiusForComparisons = std.math.lossyCast(u16, @round(radius*65536.0/16.0));
radius = @as(f32, @floatFromInt(radiusForComparisons))*16.0/65536.0;
modeData.* = radiusForComparisons;
const shellModelId = zon.get([]const u8, "shellModel", "");
const textureSlotOffset = zon.get(u32, "textureSlotOffset", 0);
if(branchModels.get(.{.radius = radiusForComparisons, .shellModelId = shellModelId, .textureSlotOffset = textureSlotOffset})) |modelIndex| return modelIndex;
var shellQuads = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer shellQuads.deinit();
if(shellModelId.len != 0) {
const shellModel = main.models.getModelIndex(shellModelId).model();
shellModel.getRawFaces(&shellQuads);
}
var modelIndex: ModelIndex = undefined; var modelIndex: ModelIndex = undefined;
for(0..64) |i| { for(0..64) |i| {
var quads = main.List(main.models.QuadInfo).init(main.stackAllocator); var quads = main.List(main.models.QuadInfo).init(main.stackAllocator);
defer quads.deinit(); defer quads.deinit();
quads.appendSlice(shellQuads.items);
for(Neighbor.iterable) |neighbor| { for(Neighbor.iterable) |neighbor| {
const pattern = getPattern(BranchData.init(@intCast(i)), neighbor); const pattern = getPattern(BranchData.init(@intCast(i)), neighbor);
if(pattern) |pat| { if(pattern) |pat| {
addQuads(pat, neighbor, radius, &quads); addQuads(pat, neighbor, radius, &quads, textureSlotOffset);
} }
} }
@ -661,7 +690,7 @@ pub const RotationModes = struct {
} }
} }
branchModels.put(@bitCast(radius), modelIndex) catch unreachable; branchModels.put(.{.radius = radiusForComparisons, .shellModelId = shellModelId, .textureSlotOffset = textureSlotOffset}, modelIndex) catch unreachable;
return modelIndex; return modelIndex;
} }
@ -707,16 +736,15 @@ pub const RotationModes = struct {
neighborBlock: Block, neighborBlock: Block,
blockPlacing: bool, blockPlacing: bool,
) bool { ) bool {
const blockBaseModelIndex = blocks.meshes.modelIndexStart(currentBlock.*); const canConnectToNeighbor = currentBlock.mode() == neighborBlock.mode() and currentBlock.modeData() == neighborBlock.modeData();
const neighborBaseModelIndex = blocks.meshes.modelIndexStart(neighborBlock);
if(blockPlacing or blockBaseModelIndex == neighborBaseModelIndex or neighborBlock.solid()) { if(blockPlacing or canConnectToNeighbor or neighborBlock.solid()) {
const neighborModel = blocks.meshes.model(neighborBlock).model(); const neighborModel = blocks.meshes.model(neighborBlock).model();
var currentData = BranchData.init(currentBlock.data); var currentData = BranchData.init(currentBlock.data);
// Branch block upon placement should extend towards a block it was placed // Branch block upon placement should extend towards a block it was placed
// on if the block is solid or also uses branch model. // on if the block is solid or also uses branch model.
const targetVal = ((neighborBlock.solid() and !neighborBlock.viewThrough()) and (blockBaseModelIndex == neighborBaseModelIndex or neighborModel.isNeighborOccluded[neighbor.?.reverse().toInt()])); const targetVal = ((neighborBlock.solid() and (!neighborBlock.viewThrough() or canConnectToNeighbor)) and (canConnectToNeighbor or neighborModel.isNeighborOccluded[neighbor.?.reverse().toInt()]));
currentData.setConnection(neighbor.?, targetVal); currentData.setConnection(neighbor.?, targetVal);
const result: u16 = currentData.enabledConnections; const result: u16 = currentData.enabledConnections;
@ -729,14 +757,13 @@ pub const RotationModes = struct {
} }
pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool { pub fn updateData(block: *Block, neighbor: Neighbor, neighborBlock: Block) bool {
const blockBaseModel = blocks.meshes.modelIndexStart(block.*); const canConnectToNeighbor = block.mode() == neighborBlock.mode() and block.modeData() == neighborBlock.modeData();
const neighborBaseModel = blocks.meshes.modelIndexStart(neighborBlock);
var currentData = BranchData.init(block.data); var currentData = BranchData.init(block.data);
// Handle joining with other branches. While placed, branches extend in a // Handle joining with other branches. While placed, branches extend in a
// opposite direction than they were placed from, effectively connecting // opposite direction than they were placed from, effectively connecting
// to the block they were placed at. // to the block they were placed at.
if(blockBaseModel == neighborBaseModel) { if(canConnectToNeighbor) {
const neighborData = BranchData.init(neighborBlock.data); const neighborData = BranchData.init(neighborBlock.data);
currentData.setConnection(neighbor, neighborData.isConnected(neighbor.reverse())); currentData.setConnection(neighbor, neighborData.isConnected(neighbor.reverse()));
} else if(!neighborBlock.solid()) { } else if(!neighborBlock.solid()) {