Files
ngzz-mc/internal/camera/pitch_test.go
NianGao Dev f25ae15077 feat(game): 音频接入、原版掉落、动物攻击、村庄村民、种子/灵敏度配置与设置界面
- 音频(音频.md §2):Session.Sound 事件回调,破坏/放置/攻击/村民环境音接入,
  客户端 audio.Manager 3D 衰减 + 监听器跟随 + 音量可调
- 原版掉落:草方块→泥土、石头→圆石、树叶无掉落(no_drop);掉落物实体
  用物品图标小广告牌渲染(物品纹理并入实体图集)
- 动物攻击(动物体系.md §5):左键优先射线命中实体(半径 0.6 球体判定),
  伤害/击退/死亡移除+肉类掉落;僵尸掉腐肉
- 村庄村民(村民系统.md §2):Generator.VillageAt 定位村庄中心,会话扫描
  已加载区块在村庄生成 3 名村民
- 种子与灵敏度配置:client.yaml world.seed/name、mouse_sensitivity;
  存档与种子联动;鼠标左右反转修复(右移应右转)
- 设置界面(UI与输入.md §6):屏幕状态 4,灵敏度/音量滑条、世界种子显示、
  按键教程多行列表(cjkfont 支持 \\n)、完成按钮;主菜单/暂停菜单入口
- 准星指向方块名:左下角 CJK 显示(lang 本地化,tick 光栅化渲染线程上传)
- 截图统一存项目根目录 jietu/(paths.RootDir;dev 构建识别 bin/ 布局)
2026-08-16 20:18:52 +08:00

78 lines
2.9 KiB
Go
Raw Permalink 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.
package camera
import (
"math"
"testing"
)
// apply 用视图矩阵变换世界点(列主序语义,与 GL 一致)。
func apply(m *[16]float32, wx, wy, wz float64) (vx, vy, vz float64) {
vx = float64(m[0])*wx + float64(m[4])*wy + float64(m[8])*wz + float64(m[12])
vy = float64(m[1])*wx + float64(m[5])*wy + float64(m[9])*wz + float64(m[13])
vz = float64(m[2])*wx + float64(m[6])*wy + float64(m[10])*wz + float64(m[14])
return
}
// TestViewPitchSteep 验证陡俯仰角(低头 60°)下:
// 相机正下方地面 → 相机前方(z<0)且屏幕下方(y<0);
// 相机前方地面 → 相机前方;远处地面 → 相机前方且低于视轴下方。
// 旧实现俯仰符号翻转:正下方/正前方地面点被映射到相机背后(z>0),
// 低头时画面只剩天空——「视角不跟随鼠标」的根因。
func TestViewPitchSteep(t *testing.T) {
c := New([3]float64{8.5, 66.66, 8.5}, 0, -60*math.Pi/180, 70)
m := c.View()
cases := []struct {
name string
wx, wy, wz float64
wantBelow bool // 期望屏幕中心下方
}{
{"below", 8.5, 64.0, 8.5, true}, // 正下方地面
{"ahead", 8.5, 64.0, 5.0, false}, // 前方地面
{"far-ahead", 8.5, 64.0, 0.0, false}, // 远处地面
}
for _, tc := range cases {
vx, vy, vz := apply(&m, tc.wx, tc.wy, tc.wz)
t.Logf("%s: view=(%.2f,%.2f,%.2f)", tc.name, vx, vy, vz)
if vz >= 0 {
t.Errorf("%s: 地面点应在相机前方 (z<0),得到 z=%.2f", tc.name, vz)
}
if tc.wantBelow && vy >= 0 {
t.Errorf("%s: 正下方地面应在屏幕下方 (y<0),得到 y=%.2f", tc.name, vy)
}
}
}
// TestViewPitchUp 抬头 60°:远处地面应仍在相机前方;正下方(与视线夹角 150°)应在相机后方。
func TestViewPitchUp(t *testing.T) {
c := New([3]float64{8.5, 66.66, 8.5}, 0, 60*math.Pi/180, 70)
m := c.View()
// 远处地面点(平视前方远处,y 比眼睛低)
_, _, vz := apply(&m, 8.5, 66.0, -50)
if vz >= 0 {
t.Errorf("抬头时远处地面应在相机前方 (z<0),得到 z=%.2f", vz)
}
// 正下方点:与视线方向夹角 150°>90°,应在相机后方(z>0)
_, _, vz = apply(&m, 8.5, 50, 8.5)
if vz <= 0 {
t.Errorf("抬头时正下方应在相机后方 (z>0),得到 z=%.2f", vz)
}
}
// TestViewForward 相机前方向(Forward() 与世界原点前方一点)应映射到视图空间 -z 且 x≈0。
func TestViewForward(t *testing.T) {
for _, tc := range []struct{ yaw, pitch float64 }{
{0, 0}, {0, -0.6}, {0.9, 0.3}, {-2.1, 0.77},
} {
c := New([3]float64{1, 2, 3}, tc.yaw, tc.pitch, 70)
f := c.Forward()
m := c.View()
vx, vy, vz := apply(&m, 1+f[0]*5, 2+f[1]*5, 3+f[2]*5)
if vz >= 0 {
t.Errorf("yaw=%.2f pitch=%.2f: 前方点应在相机前方,得到 z=%.2f", tc.yaw, tc.pitch, vz)
}
if math.Abs(vx) > 1e-3 || math.Abs(vy) > 1e-3 {
t.Errorf("yaw=%.2f pitch=%.2f: 前方点应位于视轴中心,得到 (%.3f,%.3f)", tc.yaw, tc.pitch, vx, vy)
}
}
}