From c4a4d29a0742cf81a84bd5e7a3eec88233b5dbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 3 Mar 2025 17:00:42 +0100 Subject: [PATCH] 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 --- assets/cubyz/blocks/oak_branch.zig.zon | 11 ++ assets/cubyz/blocks/textures/oak_branch.png | Bin 0 -> 4631 bytes .../blocks/textures/oak_branch_debug.png | Bin 0 -> 4622 bytes assets/cubyz/models/branch.obj | 108 ++++++++++++ src/rotation.zig | 160 ++++++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 assets/cubyz/blocks/oak_branch.zig.zon create mode 100644 assets/cubyz/blocks/textures/oak_branch.png create mode 100644 assets/cubyz/blocks/textures/oak_branch_debug.png create mode 100644 assets/cubyz/models/branch.obj diff --git a/assets/cubyz/blocks/oak_branch.zig.zon b/assets/cubyz/blocks/oak_branch.zig.zon new file mode 100644 index 000000000..c12fdf3d9 --- /dev/null +++ b/assets/cubyz/blocks/oak_branch.zig.zon @@ -0,0 +1,11 @@ +.{ + .tags = .{.wood}, + .blockHealth = 5, + .drops = .{ + .{ .items = .{.auto} }, + }, + .absorbedLight = 0x202830, + .rotation = .branch, + .model = "cubyz:branch", + .texture = "cubyz:oak_branch", +} diff --git a/assets/cubyz/blocks/textures/oak_branch.png b/assets/cubyz/blocks/textures/oak_branch.png new file mode 100644 index 0000000000000000000000000000000000000000..717c3f37808837a5fc05e0d4a0153d2fc3201ec2 GIT binary patch literal 4631 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 literal 0 HcmV?d00001 diff --git a/assets/cubyz/blocks/textures/oak_branch_debug.png b/assets/cubyz/blocks/textures/oak_branch_debug.png new file mode 100644 index 0000000000000000000000000000000000000000..ad9ef91913f7da93ff999bf9c1d0589b2e16ca36 GIT binary patch literal 4622 zcmeHLdsGuw8V`!{P*hl90Tn`YEw9N;-pP<)LxQ{vph>G>t4?MnVV1m11`=?sf^bT) z)~fgbd{DcJTeZ3#t49GXK31((3vyP3t(LWlt`DS0chwd4PC&%7J!g;SZ2u!Ulgxbg z_xnRi(?iwtRMH(bb0OQ_{%2 zv2yXEaZ_@S1RY6@TD!jC5J%kHJImQJ=l2!b+MfGyozKzExSDAM)V_DBXZwY^KR4}( z-?p`B?~bONT}5Kl@8U(xnWTVXlbl0k9pq^^ZCcT#u@0$nAml{%Q(94lgKdM^|hP2k9zi{EUo-| z_Njn#$i>#+qVQg?lXbn#4T{n~9*@*1p7t$_(^(s@?e1C;_N?!kzfOLtRQm)D|N85? zyVZ%%p^0nm+w!woE}5k_SKj$>Ye#62E<&4D+tI5&5#^(~mKnG3?nC2_TXD(B^KZ>R zSm!k@l3?uiK1YJ{Or8i3{}17tR`*Rt-gI;i?${#4TNXs zhp$;~CZ_fMOwM@mcJ-cnc<^n^^WGX{d}@DCt*O0t+_Kw&e_a*&r~P>UkJZ+$vc3uG z=uxd92jf}U>I7M0H*zv~()**YXYs!MLqz}2LZ!8}EGy9Y9I66+m`{OZ7HH<6xYf+X z2&-{HI9+uoIc$nhaOR6Nh{mQObExD(JE<*9*Wrb^ zxQyV;iuaYfP(WZNX^ibMn=B60rQo=EQE<q|S1)5B@%4#RsLavaDK#49Y zpU;W+Wy|e^0iCCwJxBqb6r3EIwxKZWbUL|C0oQ6b!aSKw1|xi!&xZg4auir-%mrB- z(;12Z4mIh(?UaqCtQI!IiRrC*w1UF{AL@KV$Y z4l_SWSaFI#-M3OwDw6OF7{rqagpiOBh#`z15J)UT_@qQ85sG+%VN@v=2aQ>9lA!|R zTng~uV!eP*2rx*G@MMsXbW<1%kW|VS<1&FvD8Y$g6d863bR}jQ9u-4H04k}3ua_BQ zGDuGd2}p>Ld`K$P3n0DTz?ULCQZMG?ZYlytXIt%N43v{HV@49TS&Z%hMsQS_nWEtE zxyUO^rU|1BzyYiQ%0gJ3j#ooE%1ml$jM0-P5h4U+8v-58CD*` z6-eD9Okq%v4v-dOIu#JO?I0UeWhXJ(YS&qAg!5xxjT z_$Xhh6|V2oFVu;jIK^DEMD!X8Ew?14U1!9AN$ew`pKaY00GnPXmu8%DtG_ zZ1=K2F?=8e2bNC`lnZbT4BWsSHOT zkRhJ{twCa9iCcvsc8AqKJ25+{Gy)xgR-i%MTCpPsOBMAx-kC!(bpV8b6dEQO2Odlo zW_FC1*~;Pn(nIbB4BKLW-+&ElU0^4Khql5&y+EwL=4-GHzvdKd_Q)h}r08=sE-bjHr0*`dp|4lC6SBELm0)7EG!BHupY;z(wW{uUSC8=5c%)6$cb_1|@ z(J7iluY04WPIxQQ^?^wW{uVfqqE_mp8Ot}MrHz_9`P^DKwS~9wd>B!tp7hn~nr(;6 z4wiDGqjR*L%S(sH*jZwmFC98ytvm+jfMki7vaI!BGU&@}48wW$v-Z}*6OmM1ot zTe>&E&5@lKXEcvjd2IfE_qV#Rl7H4-S!~{Q&ht539?+YcwsO}o?6Gv;Z*o@Sf4y8MT%n$ybggi%`rzm0vmsAjZSntiwUbV08w!gCvT*!!XFqAP6e z*6-wD{{AH|5Lwg2?{B<&zDz;uR!2v@o%dw>dZA|H@s$skoQjJIZCA%=!~cT zT3cJu+9=p^#wV$ix*lt2JrN_UA9K3;ZvGdEw~I^vo&U{EZMs+P6#obCo2H-u literal 0 HcmV?d00001 diff --git a/assets/cubyz/models/branch.obj b/assets/cubyz/models/branch.obj new file mode 100644 index 000000000..9b66feef6 --- /dev/null +++ b/assets/cubyz/models/branch.obj @@ -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 diff --git a/src/rotation.zig b/src/rotation.zig index 8f9647dd6..6730a83b3 100644 --- a/src/rotation.zig +++ b/src/rotation.zig @@ -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;