Files
xczjhk/_gen_box_of_nails_icon.py
T
2026-10-09 18:23:57 +08:00

96 lines
3.6 KiB
Python

# -*- coding: utf-8 -*-
"""由 box_of_nails 方块贴图(64x64 UV展开图)生成 16x16 的物品图标。
采用等轴测投影组合盒子可见的三个面(顶面 + 正面 + 右侧面),
源贴图中三个面所在的像素区域(由 blockbench 模型 UV 推出):
正面 north px (8, 8) - (18, 13) 10 x 5
右侧 east px (0, 8) - (8, 13) 8 x 5
顶面 up px (8, 13) - (18, 21) 10 x 8
盒子模型尺寸: 宽 10 单位(x) x 高 5 单位(y) x 深 8 单位(z),1 单位 = 1 贴图像素。
"""
import math
import os
from PIL import Image
ROOT = r"e:\MCStudioDownload\work\3027246179@qq.com\Cpp\AddOn\0aaaa4f2f16c46e8964025a5ab0ecc02"
RES = os.path.join(ROOT, "resource_pack_t7eJjrxY")
SRC = os.path.join(RES, "textures", "blocks", "box_of_nails.png")
DST = os.path.join(RES, "textures", "items", "box_of_nails.png")
OUT = 16 # 最终图标边长
SS = 4 # 超采样倍数
SIZE = OUT * SS # 渲染画布边长
TEX_FRONT = (8, 8, 18, 13) # 正面 north
TEX_WEST = (18, 8, 26, 13) # 左侧面 west
TEX_TOP = (8, 13, 18, 21) # 顶面 up
W, H, D = 10.0, 5.0, 8.0 # 盒子宽/高/深(模型单位)
def rasterize(dst, src, origin, uvec, vvec, rect):
"""把源贴图矩形 rect 以平行四边形(origin + s*uvec + t*vvec)填到 dst。"""
x0, y0, x1, y1 = rect
ox, oy = origin
ux, uy = uvec
vx, vy = vvec
det = ux * vy - uy * vx
if abs(det) < 1e-9:
return
corners = [origin, (ox + ux, oy + uy), (ox + ux + vx, oy + uy + vy), (ox + vx, oy + vy)]
minx = max(0, int(math.floor(min(c[0] for c in corners))))
maxx = min(dst.width - 1, int(math.ceil(max(c[0] for c in corners))))
miny = max(0, int(math.floor(min(c[1] for c in corners))))
maxy = min(dst.height - 1, int(math.ceil(max(c[1] for c in corners))))
px = dst.load()
spx = src.load()
sw, sh = src.size
for py in range(miny, maxy + 1):
dy = py + 0.5 - oy
for pxx in range(minx, maxx + 1):
dx = pxx + 0.5 - ox
s = (vy * dx - vx * dy) / det
t = (-uy * dx + ux * dy) / det
if s < 0.0 or s >= 1.0 or t < 0.0 or t >= 1.0:
continue
sx = int(x0 + s * (x1 - x0))
sy = int(y0 + t * (y1 - y0))
sx = min(max(sx, 0), sw - 1)
sy = min(max(sy, 0), sh - 1)
px[pxx, py] = spx[sx, sy]
def main():
src = Image.open(SRC).convert("RGBA")
dst = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
cx = math.cos(math.radians(30.0))
cy = math.sin(math.radians(30.0))
# 画布内每模型单位对应的像素数(保证盒子完整落在画布内)
margin = 4
u = min((SIZE - 2 * margin) / ((W + D) * cx),
(SIZE - 2 * margin) / ((W + D) * cy + H))
wv = (W * u * cx, -W * u * cy) # 宽度方向 x(右后上)
dv = (-D * u * cx, -D * u * cy) # 深度方向 z(左后上)
vv = (0.0, H * u) # 竖直向下
# 前上顶点 F(俯视时最靠近观察者的角),整体居中
content_w = (W + D) * u * cx
content_h = (W + D) * u * cy + H * u
fx = (SIZE - content_w) / 2.0 + D * u * cx
fy = (SIZE - content_h) / 2.0 + (W + D) * u * cy
f = (fx, fy)
rasterize(dst, src, f, dv, vv, TEX_WEST) # 左侧面
rasterize(dst, src, f, wv, vv, TEX_FRONT) # 正面
rasterize(dst, src, f, wv, dv, TEX_TOP) # 顶面
icon = dst.resize((OUT, OUT), Image.BOX)
os.makedirs(os.path.dirname(DST), exist_ok=True)
icon.save(DST)
print("icon ->", DST, icon.size)
if __name__ == "__main__":
main()