Files
ngzz-mc/internal/camera/camera.go

73 lines
2.1 KiB
Go
Raw Normal View History

// Package camera 第一人称相机:欧拉角(yaw/pitch)与视图/投影矩阵(渲染.md 配套)。
//
// 约定:yaw=0 朝向 -Z(GL 习惯),pitch>0 抬头;视图矩阵为列主序(GL 风格)。
package camera
import "math"
// Camera 第一人称相机。
type Camera struct {
Pos [3]float64 // 眼睛位置
Yaw float64 // 水平角(弧度)
Pitch float64 // 俯仰角(弧度)
FOV float64 // 垂直视场角(度)
}
// New 创建相机。
func New(pos [3]float64, yaw, pitch, fov float64) *Camera {
return &Camera{Pos: pos, Yaw: yaw, Pitch: pitch, FOV: fov}
}
// Forward 前方向(单位向量)。
func (c *Camera) Forward() [3]float64 {
cp, sp := math.Cos(c.Pitch), math.Sin(c.Pitch)
cy, sy := math.Cos(c.Yaw), math.Sin(c.Yaw)
return [3]float64{-sy * cp, sp, -cy * cp}
}
// Right 右方向(单位向量,水平)。
func (c *Camera) Right() [3]float64 {
cy, sy := math.Cos(c.Yaw), math.Sin(c.Yaw)
return [3]float64{cy, 0, -sy}
}
// View 视图矩阵(列主序 4×4,GL 用)。
func (c *Camera) View() [16]float32 {
// 旋转 = RotX(-pitch) × RotY(-yaw),再平移 -pos
cp, sp := math.Cos(-c.Pitch), math.Sin(-c.Pitch)
cy, sy := math.Cos(-c.Yaw), math.Sin(-c.Yaw)
// R = RotY(-yaw) × RotX(-pitch) 的计算结果(行主序推导后转列主序)
// 列主序布局: m[col*4+row]
m := [16]float32{}
m[0] = float32(cy)
m[1] = float32(-sy * sp)
m[2] = float32(-sy * cp)
m[4] = float32(sy)
m[5] = float32(cy * sp)
m[6] = float32(cy * cp)
m[8] = 0
m[9] = float32(cp)
m[10] = float32(-sp)
m[15] = 1
// 平移部分:t = -R·pos
px, py, pz := float32(c.Pos[0]), float32(c.Pos[1]), float32(c.Pos[2])
m[12] = -(m[0]*px + m[4]*py + m[8]*pz)
m[13] = -(m[1]*px + m[5]*py + m[9]*pz)
m[14] = -(m[2]*px + m[6]*py + m[10]*pz)
return m
}
// Projection 透视投影矩阵(列主序,GL 用)。
func (c *Camera) Projection(aspect, near, far float64) [16]float32 {
f := 1.0 / math.Tan(c.FOV*math.Pi/360.0)
var m [16]float32
m[0] = float32(f / aspect)
m[5] = float32(f)
m[10] = float32((far + near) / (near - far))
m[11] = -1
m[14] = float32(2 * far * near / (near - far))
return m
}