Files
ngzz-mc/internal/render/overlay_gl.go
2026-09-19 14:21:08 +08:00

498 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build gl
// 覆盖层渲染破坏裂纹叠加destroy_stage_0..9)与第一人称手持方块
// (方块交互与动画.md §4、渲染.md §5.2)。
// 与区块网格共用顶点格式pos+uv+light与着色器通过 uOffset 平移定位。
package render
import (
"image"
"math"
"unsafe"
"mc/internal/mesh"
"github.com/go-gl/gl/v3.3-core/gl"
)
// cubeFaces 单位立方体 6 面角顺序(与 mesh.faceDef 一致,
// 窗口空间 CW 绕序为 FrontFace(CW) 的正面;窗口坐标 y 向上)。
var cubeFaces = [6][4][3]float32{
{{0, 1, 0}, {1, 1, 0}, {1, 1, 1}, {0, 1, 1}}, // 顶
{{0, 0, 1}, {1, 0, 1}, {1, 0, 0}, {0, 0, 0}}, // 底
{{1, 0, 0}, {1, 0, 1}, {1, 1, 1}, {1, 1, 0}}, // 东(+x)
{{0, 0, 1}, {0, 0, 0}, {0, 1, 0}, {0, 1, 1}}, // 西(-x)
{{1, 0, 1}, {0, 0, 1}, {0, 1, 1}, {1, 1, 1}}, // 南(+z)
{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}, // 北(-z)
}
// cubeVerts 按六面 UV 构建单位立方体顶点mesh.Vertex 格式pos+uv+light 字节,
// 光照满亮——覆盖层不受世界光照影响。inflate 沿角外扩(裂纹盒防 z-fighting
func cubeVerts(uvs [6][4]float32, inflate float32) []mesh.Vertex {
var verts []mesh.Vertex
for fi, face := range cubeFaces {
u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3]
for _, c := range face {
// 角 UV 权重(同 mesh.cornerUV
var cu, cv float32
switch fi {
case 0, 1: // 顶/底x→uz→v
cu, cv = c[0], c[2]
case 2, 3: // 东/西z→uy→vv 翻转)
cu, cv = c[2], 1-c[1]
default: // 南/北x→uy→vv 翻转)
cu, cv = c[0], 1-c[1]
}
verts = append(verts, mesh.Vertex{
X: (c[0]-0.5)*(1+inflate) + 0.5,
Y: (c[1]-0.5)*(1+inflate) + 0.5,
Z: (c[2]-0.5)*(1+inflate) + 0.5,
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
Sky: 15, Block: 15,
})
}
}
return verts
}
// cubeIndices 24 顶点 → 36 索引(每面 2 三角形)。
func cubeIndices() []uint32 {
idx := make([]uint32, 0, 36)
for base := 0; base < 24; base += 4 {
b := uint32(base)
idx = append(idx, b, b+1, b+2, b+2, b+3, b)
}
return idx
}
// uploadMesh 上传顶点/索引并绑定顶点属性chunk 顶点布局,渲染.md §5
func uploadMesh(verts []mesh.Vertex, idx []uint32) (vao, vbo, ibo uint32) {
gl.GenVertexArrays(1, &vao)
gl.GenBuffers(1, &vbo)
gl.GenBuffers(1, &ibo)
gl.BindVertexArray(vao)
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.STATIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.STATIC_DRAW)
stride := int32(unsafe.Sizeof(mesh.Vertex{}))
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(0))
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(3*4))
gl.EnableVertexAttribArray(2)
gl.VertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, stride, gl.PtrOffset(5*4))
gl.BindVertexArray(0)
return
}
// buildCubeVAO 构建立方体 VAO顶点格式与 chunk 一致pos+uv+light
// uvs 六面 UV顺序同 cubeFacesinflate 沿角外扩;立方体锚点为原点,边长为 1。
func buildCubeVAO(uvs [6][4]float32, inflate float32) uint32 {
vao, _, _ := uploadMesh(cubeVerts(uvs, inflate), cubeIndices())
return vao
}
// SetCrackAtlas 上传裂纹图集10 阶段 destroy_stage并记录各阶段 UV。
// img 为调用方打包好的图集rects[i] 为阶段 i 的像素矩形(图集坐标系)。
func (r *Renderer) SetCrackAtlas(img image.Image, rects [10]image.Rectangle) {
if r.crackTex == 0 {
gl.GenTextures(1, &r.crackTex)
}
gl.BindTexture(gl.TEXTURE_2D, r.crackTex)
b := img.Bounds()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, int32(b.Dx()), int32(b.Dy()), 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.Ptr(imageBytes(img)))
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
W, H := float32(b.Dx()), float32(b.Dy())
const inset = 0.5 // 防渗色(渲染.md §4
for i, rc := range rects {
r.crackUVs[i] = [4]float32{
(float32(rc.Min.X) + inset) / W, (float32(rc.Min.Y) + inset) / H,
(float32(rc.Max.X) - inset) / W, (float32(rc.Max.Y) - inset) / H,
}
}
}
// DrawCrackOverlay 在目标方块 (x,y,z) 叠加 stage 阶段裂纹盒
// (方块交互与动画.md §409六面同图盒体略外扩防 z-fighting
func (r *Renderer) DrawCrackOverlay(x, y, z float32, stage int) {
if r.crackTex == 0 {
return
}
if stage < 0 {
stage = 0
}
if stage > 9 {
stage = 9
}
if r.crackCubes[stage] == 0 {
uv := r.crackUVs[stage]
r.crackCubes[stage] = buildCubeVAO([6][4]float32{uv, uv, uv, uv, uv, uv}, 0.004)
}
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.crackTex)
vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00"))
var vp [16]float32
multiply4(&vp, &r.proj, &r.view)
gl.UniformMatrix4fv(vpLoc, 1, false, &vp[0])
offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00"))
gl.Uniform3f(offLoc, x, y, z)
cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00"))
gl.Uniform1f(cutLoc, 0)
billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00"))
gl.Uniform2f(billLoc, 1, 0) // 非广告牌路径
uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00"))
uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00"))
gl.Uniform2f(uvMinLoc, 0, 0)
gl.Uniform2f(uvMaxLoc, 1, 1)
tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00"))
gl.Uniform4f(tintLoc, 1, 1, 1, 1)
gl.Enable(gl.POLYGON_OFFSET_FILL)
gl.PolygonOffset(-1, -1)
gl.BindVertexArray(r.crackCubes[stage])
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
gl.Disable(gl.POLYGON_OFFSET_FILL)
gl.Uniform3f(offLoc, 0, 0, 0) // 复位平移(后续绘制依赖 0 偏移)
}
// SetHandCube 上传/更新手持方块立方体(六面 UV 顺序同 cubeFaces
// pos/scale 为视图空间下的立方体中心与边长(渲染.md §5.2)。
func (r *Renderer) SetHandCube(uvs [6][4]float32, pos [3]float32, scale float32) {
r.ClearHand()
// 直接按视图空间坐标构建顶点(顶点含 pos 偏移)
var verts []mesh.Vertex
for fi, face := range cubeFaces {
u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3]
for _, c := range face {
var cu, cv float32
switch fi {
case 0, 1:
cu, cv = c[0], c[2]
case 2, 3:
cu, cv = c[2], 1-c[1]
default:
cu, cv = c[0], 1-c[1]
}
verts = append(verts, mesh.Vertex{
X: pos[0] + (c[0]-0.5)*scale,
Y: pos[1] + (c[1]-0.5)*scale,
Z: pos[2] + (c[2]-0.5)*scale,
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
Sky: 15, Block: 15,
})
}
}
r.handVao, r.handVbo, r.handIbo = uploadMesh(verts, cubeIndices())
}
// ClearHand 释放手持方块网格(手持物品为空时调用)。
func (r *Renderer) ClearHand() {
if r.handVao != 0 {
gl.DeleteVertexArrays(1, &r.handVao)
r.handVao = 0
}
if r.handVbo != 0 {
gl.DeleteBuffers(1, &r.handVbo)
r.handVbo = 0
}
if r.handIbo != 0 {
gl.DeleteBuffers(1, &r.handIbo)
r.handIbo = 0
}
}
// DrawHand 绘制手持方块顶点已是视图空间仅乘投影矩阵tint 肤色乘数)。
func (r *Renderer) DrawHand(tint [3]float32) {
if r.handVao == 0 {
return
}
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.atlas)
vpLoc := gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00"))
gl.UniformMatrix4fv(vpLoc, 1, false, &r.proj[0])
offLoc := gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00"))
gl.Uniform3f(offLoc, 0, 0, 0)
cutLoc := gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00"))
gl.Uniform1f(cutLoc, 0)
billLoc := gl.GetUniformLocation(r.prog, gl.Str("uBill\x00"))
gl.Uniform2f(billLoc, 1, 0)
uvMinLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00"))
uvMaxLoc := gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00"))
gl.Uniform2f(uvMinLoc, 0, 0)
gl.Uniform2f(uvMaxLoc, 1, 1)
tintLoc := gl.GetUniformLocation(r.prog, gl.Str("uTint\x00"))
gl.Uniform4f(tintLoc, tint[0], tint[1], tint[2], 1)
gl.BindVertexArray(r.handVao)
gl.DrawElements(gl.TRIANGLES, 36, gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
}
// ensureHandItemQuad 惰性构建手持物品四边形 VAO顶点每帧重传位置随挥动变化
func (r *Renderer) ensureHandItemQuad() {
if r.handItemVao != 0 {
return
}
gl.GenVertexArrays(1, &r.handItemVao)
gl.GenBuffers(1, &r.handItemVbo)
gl.GenBuffers(1, &r.handItemIbo)
gl.BindVertexArray(r.handItemVao)
gl.BindBuffer(gl.ARRAY_BUFFER, r.handItemVbo)
stride := int32(unsafe.Sizeof(mesh.Vertex{}))
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(0))
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(3*4))
gl.EnableVertexAttribArray(2)
gl.VertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, stride, gl.PtrOffset(5*4))
idx := []uint32{0, 1, 2, 0, 2, 3}
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.handItemIbo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.STATIC_DRAW)
gl.BindVertexArray(0)
}
// DrawHandItem 绘制手持物品图标(非放置物品,如工具/食物):视图空间小四边形,
// 轻微倾斜原版手持角度双面、alpha test贴实体图集内物品贴图渲染.md §5.2)。
// pos 为视图空间中心scale 为边长。
func (r *Renderer) DrawHandItem(uv [4]float32, pos [3]float32, scale float32) {
if r.entityTex == 0 {
return
}
r.ensureHandItemQuad()
// 四角局部坐标(-0.5..0.5),绕 Y 转 yaw、绕 X 抬 pitch烘焙进视图空间顶点
const yaw, pitch = -0.6, 0.25
cy, sy := float32(math.Cos(yaw)), float32(math.Sin(yaw))
cp, sp := float32(math.Cos(pitch)), float32(math.Sin(pitch))
corners := [4][2]float32{{-0.5, -0.5}, {0.5, -0.5}, {0.5, 0.5}, {-0.5, 0.5}}
verts := make([]mesh.Vertex, 0, 4)
for _, c := range corners {
lx, ly := c[0]*scale, c[1]*scale
rx := lx * cy
rz := -lx * sy
ry := ly*cp - rz*sp
rz2 := ly*sp + rz*cp
verts = append(verts, mesh.Vertex{
X: pos[0] + rx, Y: pos[1] + ry, Z: pos[2] + rz2,
U: c[0] + 0.5, V: 0.5 - c[1], // 图集 tilev=0 为图像顶,图标保持正立
Sky: 15, Block: 15,
})
}
gl.BindBuffer(gl.ARRAY_BUFFER, r.handItemVbo)
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.DYNAMIC_DRAW)
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, r.entityTex)
gl.UniformMatrix4fv(gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00")), 1, false, &r.proj[0])
gl.Uniform3f(gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00")), 0, 0, 0)
gl.Uniform1f(gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00")), 1) // alpha test 剔透明
gl.Uniform4f(gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")), 1, 1, 1, 1)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")), 1, 0)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), uv[0], uv[1])
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), uv[2], uv[3])
gl.Disable(gl.CULL_FACE) // 双面(背面为图标反相)
gl.BindVertexArray(r.handItemVao)
gl.DrawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
gl.Enable(gl.CULL_FACE)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), 0, 0)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), 1, 1)
}
func rotX(p [3]float32, a float32) [3]float32 {
c, s := float32(math.Cos(float64(a))), float32(math.Sin(float64(a)))
return [3]float32{p[0], p[1]*c - p[2]*s, p[1]*s + p[2]*c}
}
func rotY(p [3]float32, a float32) [3]float32 {
c, s := float32(math.Cos(float64(a))), float32(math.Sin(float64(a)))
return [3]float32{p[0]*c + p[2]*s, p[1], -p[0]*s + p[2]*c}
}
func rotZ(p [3]float32, a float32) [3]float32 {
c, s := float32(math.Cos(float64(a))), float32(math.Sin(float64(a)))
return [3]float32{p[0]*c - p[1]*s, p[0]*s + p[1]*c, p[2]}
}
func add3(a, b [3]float32) [3]float32 { return [3]float32{a[0] + b[0], a[1] + b[1], a[2] + b[2]} }
func swingChop(swing float32) float32 {
if swing <= 0 {
return 0
}
t := 1 - swing
if t < 0.45 {
return t / 0.45 * 1.15
}
return (1 - (t-0.45)/0.55) * 1.15
}
func mapSkinUV(tile [4]float32, uv [4]float32) [4]float32 {
tw, th := tile[2]-tile[0], tile[3]-tile[1]
return [4]float32{
tile[0] + uv[0]*tw, tile[1] + uv[1]*th,
tile[0] + uv[2]*tw, tile[1] + uv[3]*th,
}
}
func appendBox(verts []mesh.Vertex, uvs [6][4]float32, xf func([3]float32) [3]float32) []mesh.Vertex {
for fi, face := range cubeFaces {
u0, v0, u1, v1 := uvs[fi][0], uvs[fi][1], uvs[fi][2], uvs[fi][3]
for _, c := range face {
var cu, cv float32
switch fi {
case 0, 1:
cu, cv = c[0], c[2]
case 2, 3:
cu, cv = c[2], 1-c[1]
default:
cu, cv = c[0], 1-c[1]
}
p := xf([3]float32{c[0], c[1], c[2]})
verts = append(verts, mesh.Vertex{
X: p[0], Y: p[1], Z: p[2],
U: u0 + (u1-u0)*cu, V: v0 + (v1-v0)*cv,
Sky: 15, Block: 15,
})
}
}
return verts
}
func (r *Renderer) drawViewMesh(verts []mesh.Vertex, tex uint32, tint [3]float32, cutout float32) {
if len(verts) == 0 {
return
}
if r.animalVao == 0 {
gl.GenVertexArrays(1, &r.animalVao)
gl.GenBuffers(1, &r.animalVbo)
gl.GenBuffers(1, &r.animalIbo)
gl.BindVertexArray(r.animalVao)
gl.BindBuffer(gl.ARRAY_BUFFER, r.animalVbo)
stride := int32(unsafe.Sizeof(mesh.Vertex{}))
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointer(0, 3, gl.FLOAT, false, stride, gl.PtrOffset(0))
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointer(1, 2, gl.FLOAT, false, stride, gl.PtrOffset(3*4))
gl.EnableVertexAttribArray(2)
gl.VertexAttribPointer(2, 2, gl.UNSIGNED_BYTE, false, stride, gl.PtrOffset(5*4))
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.animalIbo)
gl.BindVertexArray(0)
}
idx := make([]uint32, 0, len(verts)/4*6)
for base := 0; base+3 < len(verts); base += 4 {
b := uint32(base)
idx = append(idx, b, b+1, b+2, b+2, b+3, b)
}
gl.BindBuffer(gl.ARRAY_BUFFER, r.animalVbo)
gl.BufferData(gl.ARRAY_BUFFER, len(verts)*int(unsafe.Sizeof(mesh.Vertex{})), gl.Ptr(verts), gl.DYNAMIC_DRAW)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, r.animalIbo)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(idx)*4, gl.Ptr(idx), gl.DYNAMIC_DRAW)
gl.UseProgram(r.prog)
gl.ActiveTexture(gl.TEXTURE0)
if tex != 0 {
gl.BindTexture(gl.TEXTURE_2D, tex)
}
gl.UniformMatrix4fv(gl.GetUniformLocation(r.prog, gl.Str("uViewProj\x00")), 1, false, &r.proj[0])
gl.Uniform3f(gl.GetUniformLocation(r.prog, gl.Str("uOffset\x00")), 0, 0, 0)
gl.Uniform1f(gl.GetUniformLocation(r.prog, gl.Str("uCutout\x00")), cutout)
gl.Uniform4f(gl.GetUniformLocation(r.prog, gl.Str("uTint\x00")), tint[0], tint[1], tint[2], 1)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uBill\x00")), 1, 0)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMin\x00")), 0, 0)
gl.Uniform2f(gl.GetUniformLocation(r.prog, gl.Str("uUVMax\x00")), 1, 1)
gl.BindVertexArray(r.animalVao)
gl.DrawElements(gl.TRIANGLES, int32(len(idx)), gl.UNSIGNED_INT, gl.PtrOffset(0))
gl.BindVertexArray(0)
}
// DrawFirstPerson 第一人称右手:皮肤手臂 + 可选方块/挤出物品,绕肩挥动(渲染.md §5.2)。
func (r *Renderer) DrawFirstPerson(armTile [4]float32, hasSkin bool, swing float32, blockUVs [6][4]float32, hasBlock bool, itemUV [4]float32, hasItem bool) {
shoulder := [3]float32{0.30, -0.18, -0.50}
chop := swingChop(swing)
xf := func(local [3]float32) [3]float32 {
p := rotZ(rotX(local, 0.55), -0.38)
p = rotX(p, -chop)
return add3(p, shoulder)
}
armUVs := cuboidUV(40, 16, 4, 12, 4)
if hasSkin {
for i := range armUVs {
armUVs[i] = mapSkinUV(armTile, armUVs[i])
}
}
armVerts := appendBox(nil, armUVs, func(c [3]float32) [3]float32 {
// 肩在 y=1指尖朝 -Y4×12×4 像素
lx := (c[0] - 0.5) * 0.10
ly := (c[1] - 1.0) * 0.30
lz := (c[2] - 0.5) * 0.10
return xf([3]float32{lx, ly, lz})
})
tint := [3]float32{1, 1, 1}
tex := r.entityTex
if !hasSkin {
tint = [3]float32{0.85, 0.65, 0.5}
if tex == 0 {
tex = r.atlas
}
}
r.drawViewMesh(armVerts, tex, tint, 0)
if hasBlock {
held := appendBox(nil, blockUVs, func(c [3]float32) [3]float32 {
p := [3]float32{(c[0] - 0.5) * 0.16, (c[1] - 0.5) * 0.16, (c[2] - 0.5) * 0.16}
p = rotY(rotX(p, 0.35), -0.55)
p = add3(p, [3]float32{0.02, -0.30, 0.04})
return xf(p)
})
r.drawViewMesh(held, r.atlas, [3]float32{1, 1, 1}, 0)
}
if hasItem && r.entityTex != 0 {
u0, v0, u1, v1 := itemUV[0], itemUV[1], itemUV[2], itemUV[3]
thick := float32(0.018)
half := float32(0.11)
corners := [4][2]float32{{-half, -half}, {half, -half}, {half, half}, {-half, half}}
var verts []mesh.Vertex
push := func(x, y, z, u, v float32) {
p := rotY(rotX([3]float32{x, y, z}, 0.25), -0.55)
p = add3(p, [3]float32{0.01, -0.30, 0.03})
p = xf(p)
verts = append(verts, mesh.Vertex{X: p[0], Y: p[1], Z: p[2], U: u, V: v, Sky: 15, Block: 15})
}
// 前
push(corners[0][0], corners[0][1], thick, u0, v1)
push(corners[1][0], corners[1][1], thick, u1, v1)
push(corners[2][0], corners[2][1], thick, u1, v0)
push(corners[3][0], corners[3][1], thick, u0, v0)
// 后
push(corners[1][0], corners[1][1], -thick, u0, v1)
push(corners[0][0], corners[0][1], -thick, u1, v1)
push(corners[3][0], corners[3][1], -thick, u1, v0)
push(corners[2][0], corners[2][1], -thick, u0, v0)
// 四边挤出(取边缘像素)
edges := [][6]float32{
{corners[0][0], corners[0][1], corners[1][0], corners[1][1], u0, v1},
{corners[1][0], corners[1][1], corners[2][0], corners[2][1], u1, (v0 + v1) / 2},
{corners[2][0], corners[2][1], corners[3][0], corners[3][1], u0, v0},
{corners[3][0], corners[3][1], corners[0][0], corners[0][1], u0, (v0 + v1) / 2},
}
for _, e := range edges {
push(e[0], e[1], thick, e[4], e[5])
push(e[2], e[3], thick, e[4], e[5])
push(e[2], e[3], -thick, e[4], e[5])
push(e[0], e[1], -thick, e[4], e[5])
}
r.drawViewMesh(verts, r.entityTex, [3]float32{1, 1, 1}, 1)
}
}