Files
ngzz-mc/internal/camera/pitch_test.go
NianGao Dev 108c803a75 fix(camera): 视图矩阵俯仰符号翻转——视角不跟随鼠标上下移动的根因
视图矩阵误用正向旋转 R 代替 Rᵀ(RotX(-pitch)·RotY(-yaw)),
低头渲染成抬头、抬头渲染成低头;yaw 因 RotY 对称性侥幸正确。
新增 TestViewPitchSteep/Up/Forward 验证陡俯仰与方向映射。
2026-08-16 10:08:33 +08:00

78 lines
2.9 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.
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)
}
}
}