498 lines
18 KiB
Go
498 lines
18 KiB
Go
//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→u,z→v
|
||
cu, cv = c[0], c[2]
|
||
case 2, 3: // 东/西:z→u,y→v(v 翻转)
|
||
cu, cv = c[2], 1-c[1]
|
||
default: // 南/北:x→u,y→v(v 翻转)
|
||
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(顺序同 cubeFaces);inflate 沿角外扩;立方体锚点为原点,边长为 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 §4:0–9,六面同图,盒体略外扩防 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], // 图集 tile:v=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,指尖朝 -Y;4×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)
|
||
}
|
||
}
|