3dtrial/renderer4test/hrend/math.go

383 lines
11 KiB
Go
Raw Permalink Normal View History

2024-08-07 03:47:16 +00:00
package hrend
// This is the linear algebra junk? Vectors, matrices, etc
import (
//"log"
"math"
)
type Vec3f struct {
X, Y, Z float32
}
type Vec2i struct {
X, Y int
}
type Vec2f struct {
X, Y float32
}
// A ROW MAJOR matrix
type Mat44f [16]float32
func (m *Mat44f) Set(x int, y int, val float32) {
m[x+y*4] = val
}
func (m *Mat44f) Get(x int, y int) float32 {
return m[x+y*4]
}
func (m *Mat44f) ZeroFill() {
for i := range m {
m[i] = 0
}
}
// Multiply the entire matrix by the given value
func (m *Mat44f) MultiplySelf(f float32) {
for i := range m {
m[i] *= f
}
}
// Copied from https://github.com/go-gl/mathgl/blob/master/mgl32/matrix.go
func (m *Mat44f) Determinant() float32 {
return m[0]*m[5]*m[10]*m[15] - m[0]*m[5]*m[11]*m[14] - m[0]*m[6]*m[9]*m[15] + m[0]*m[6]*m[11]*m[13] + m[0]*m[7]*m[9]*m[14] - m[0]*m[7]*m[10]*m[13] - m[1]*m[4]*m[10]*m[15] + m[1]*m[4]*m[11]*m[14] + m[1]*m[6]*m[8]*m[15] - m[1]*m[6]*m[11]*m[12] - m[1]*m[7]*m[8]*m[14] + m[1]*m[7]*m[10]*m[12] + m[2]*m[4]*m[9]*m[15] - m[2]*m[4]*m[11]*m[13] - m[2]*m[5]*m[8]*m[15] + m[2]*m[5]*m[11]*m[12] + m[2]*m[7]*m[8]*m[13] - m[2]*m[7]*m[9]*m[12] - m[3]*m[4]*m[9]*m[14] + m[3]*m[4]*m[10]*m[13] + m[3]*m[5]*m[8]*m[14] - m[3]*m[5]*m[10]*m[12] - m[3]*m[6]*m[8]*m[13] + m[3]*m[6]*m[9]*m[12]
}
// Copied from https://github.com/go-gl/mathgl/blob/master/mgl32/matrix.go
func (m *Mat44f) Inverse() *Mat44f {
det := m.Determinant()
if det == float32(0.0) {
return &Mat44f{}
}
// How the hell am I supposed to know if this is correct? Oh well...
result := Mat44f{
-m[7]*m[10]*m[13] + m[6]*m[11]*m[13] + m[7]*m[9]*m[14] - m[5]*m[11]*m[14] - m[6]*m[9]*m[15] + m[5]*m[10]*m[15],
m[3]*m[10]*m[13] - m[2]*m[11]*m[13] - m[3]*m[9]*m[14] + m[1]*m[11]*m[14] + m[2]*m[9]*m[15] - m[1]*m[10]*m[15],
-m[3]*m[6]*m[13] + m[2]*m[7]*m[13] + m[3]*m[5]*m[14] - m[1]*m[7]*m[14] - m[2]*m[5]*m[15] + m[1]*m[6]*m[15],
m[3]*m[6]*m[9] - m[2]*m[7]*m[9] - m[3]*m[5]*m[10] + m[1]*m[7]*m[10] + m[2]*m[5]*m[11] - m[1]*m[6]*m[11],
m[7]*m[10]*m[12] - m[6]*m[11]*m[12] - m[7]*m[8]*m[14] + m[4]*m[11]*m[14] + m[6]*m[8]*m[15] - m[4]*m[10]*m[15],
-m[3]*m[10]*m[12] + m[2]*m[11]*m[12] + m[3]*m[8]*m[14] - m[0]*m[11]*m[14] - m[2]*m[8]*m[15] + m[0]*m[10]*m[15],
m[3]*m[6]*m[12] - m[2]*m[7]*m[12] - m[3]*m[4]*m[14] + m[0]*m[7]*m[14] + m[2]*m[4]*m[15] - m[0]*m[6]*m[15],
-m[3]*m[6]*m[8] + m[2]*m[7]*m[8] + m[3]*m[4]*m[10] - m[0]*m[7]*m[10] - m[2]*m[4]*m[11] + m[0]*m[6]*m[11],
-m[7]*m[9]*m[12] + m[5]*m[11]*m[12] + m[7]*m[8]*m[13] - m[4]*m[11]*m[13] - m[5]*m[8]*m[15] + m[4]*m[9]*m[15],
m[3]*m[9]*m[12] - m[1]*m[11]*m[12] - m[3]*m[8]*m[13] + m[0]*m[11]*m[13] + m[1]*m[8]*m[15] - m[0]*m[9]*m[15],
-m[3]*m[5]*m[12] + m[1]*m[7]*m[12] + m[3]*m[4]*m[13] - m[0]*m[7]*m[13] - m[1]*m[4]*m[15] + m[0]*m[5]*m[15],
m[3]*m[5]*m[8] - m[1]*m[7]*m[8] - m[3]*m[4]*m[9] + m[0]*m[7]*m[9] + m[1]*m[4]*m[11] - m[0]*m[5]*m[11],
m[6]*m[9]*m[12] - m[5]*m[10]*m[12] - m[6]*m[8]*m[13] + m[4]*m[10]*m[13] + m[5]*m[8]*m[14] - m[4]*m[9]*m[14],
-m[2]*m[9]*m[12] + m[1]*m[10]*m[12] + m[2]*m[8]*m[13] - m[0]*m[10]*m[13] - m[1]*m[8]*m[14] + m[0]*m[9]*m[14],
m[2]*m[5]*m[12] - m[1]*m[6]*m[12] - m[2]*m[4]*m[13] + m[0]*m[6]*m[13] + m[1]*m[4]*m[14] - m[0]*m[5]*m[14],
-m[2]*m[5]*m[8] + m[1]*m[6]*m[8] + m[2]*m[4]*m[9] - m[0]*m[6]*m[9] - m[1]*m[4]*m[10] + m[0]*m[5]*m[10],
}
result.MultiplySelf(1 / det)
//log.Print(m)
//log.Print(result)
return &result
}
func (m *Mat44f) SetIdentity() {
m.ZeroFill()
for i := range 4 {
m.Set(i, i, 1)
}
}
// NOTE: we use "Set" instead of "Create" for all these so we reuse the matrix
// instead of creating a new one all the time (garbage collection)
// Compute the projection matrix, filling the given matrix. FOV is in degrees
func (m *Mat44f) SetProjection(fov float32, aspect float32, near float32, far float32) {
// Projection matrix is (ROW MAJOR!)
// S 0 0 0
// 0 S 0 0
// 0 0 -f/(f-n) -1
// 0 0 -fn/(f-n) 0
// where S (scale) is 1 / tan(fov / 2) (assuming fov is radians)
// // NOTE: -1 there is actually -1/c, where c is distance from viewer to
// // projection plane. We fix it at 1 for now but...
// m.ZeroFill()
// scale := float32(1 / math.Tan(float64(fov)*0.5*math.Pi/180))
// m.Set(0, 0, scale)
// m.Set(1, 1, scale)
// m.Set(2, 2, -far/(far-near))
// m.Set(3, 2, -1)
// m.Set(2, 3, -far*near/(far-near))
// OK apparently I suck, let's use somebody else's projection matrix:
m.ZeroFill()
fov = fov / 180 * math.Pi // Convert to radians
e := float32(1 / math.Tan(float64(fov/2)))
m.Set(0, 0, e/aspect)
m.Set(1, 1, e)
m.Set(2, 2, (far+near)/(near-far))
m.Set(2, 3, 2*far*near/(near-far))
m.Set(3, 2, -1) // Might need to be swapped
// DEG2RAD := math.Acos(-1.0) / 180.0
// tangent := math.Tan(float64(fov/2.0) * DEG2RAD) // tangent of half fovY
// top := near * float32(tangent) // half height of near plane
// right := top * aspect // half width of near plane
// // Column major maybe???
// // n/r 0 0 0
// // 0 n/t 0 0
// // 0 0 -(f+n)/(f-n) -1
// // 0 0 -(2fn)/(f-n) 0
// m.Set(0, 0, near/right)
// m.Set(1, 1, near/top)
// m.Set(2, 2, -(far+near)/(far-near))
// m.Set(3, 2, -1)
// m.Set(2, 3, -(2*far*near)/(far-near))
}
func (m *Mat44f) SetViewport(tl Vec3f, br Vec3f) { //width, height, depth int) {
m.ZeroFill()
m.Set(0, 0, (br.X-tl.X)/2)
m.Set(1, 1, (tl.Y-br.Y)/2) // Inverted because screen funny
m.Set(2, 2, 1) //(br.Z-tl.Z)/2)
m.Set(3, 3, 1)
m.Set(0, 3, (br.X+tl.X)/2)
m.Set(1, 3, (br.Y+tl.Y)/2)
//m.Set(2, 3, (br.Z+tl.Z)/2)
}
// Convert the point to a viewport point
func (v *Vec3f) ViewportSelf(width, height int) {
v.X = (v.X + 1) / 2 * float32(width)
v.Y = (1 - (v.Y+1)/2) * float32(height)
// Don't touch Z
}
func (m *Mat44f) SetViewportSimple(width, height, depth int) {
var tl Vec3f // All zero
br := Vec3f{
X: float32(width),
Y: float32(height),
Z: float32(depth),
}
m.SetViewport(tl, br)
}
func (m *Mat44f) SetTranslation(x, y, z float32) {
m.SetIdentity()
m.Set(0, 3, x) // Let user decide how to offset x
m.Set(1, 3, y) // Let user decide how to offset x
m.Set(2, 3, z) // Get farther away from the face (user)
}
func (m *Mat44f) ScaleSelf(scale float32) {
m.Set(0, 0, m.Get(0, 0)*scale)
m.Set(1, 1, m.Get(1, 1)*scale)
m.Set(2, 2, m.Get(2, 2)*scale)
}
func (m *Mat44f) SetRotationX(radang float32) {
m.SetIdentity()
m[5] = float32(math.Cos(float64(radang)))
m[10] = m[5]
m[6] = float32(math.Sin(float64(radang)))
m[9] = -m[6]
}
func (m *Mat44f) SetRotationY(radang float32) {
m.SetIdentity()
m[0] = float32(math.Cos(float64(radang)))
m[10] = m[0]
m[8] = float32(math.Sin(float64(radang)))
m[2] = -m[8]
}
func (m *Mat44f) SetRotationZ(radang float32) {
m.SetIdentity()
m[0] = float32(math.Cos(float64(radang)))
m[5] = m[0]
m[4] = float32(math.Sin(float64(radang)))
m[2] = -m[4]
}
// Camera is easier to deal with using yaw and pitch, since we're not supporting roll
func (m *Mat44f) SetCamera(loc *Vec3f, yaw float32, pitch float32, up *Vec3f) Vec3f {
// Use sphere equation to compute lookat vector through the two
// player-controled angles (pitch and yaw)
lookvec := Vec3f{
Z: float32(-math.Sin(float64(pitch)) * math.Cos(float64(yaw))),
X: float32(math.Sin(float64(pitch)) * math.Sin(float64(yaw))),
Y: float32(math.Cos(float64(pitch))),
}
m.SetLookAt(loc, loc.Add(&lookvec), up)
return lookvec
}
// Note: use {0,1,0} for up for normal use
func (m *Mat44f) SetLookAt(from *Vec3f, to *Vec3f, up *Vec3f) {
forward := from.Sub(to).Normalize()
// IDK if you have to normalize but whatever
right := up.CrossProduct(forward).Normalize()
realup := forward.CrossProduct(right)
m.SetIdentity()
m.Set(0, 0, right.X)
m.Set(1, 0, right.Y)
m.Set(2, 0, right.Z)
m.Set(0, 1, realup.X)
m.Set(1, 1, realup.Y)
m.Set(2, 1, realup.Z)
m.Set(0, 2, forward.X)
m.Set(1, 2, forward.Y)
m.Set(2, 2, forward.Z)
m.Set(0, 3, from.X)
m.Set(1, 3, from.Y)
m.Set(2, 3, from.Z)
}
// Homogenous vec3f
type HVec3f struct {
Pos Vec3f
W float32
}
func (h *HVec3f) MakeConventional() Vec3f {
r := h.Pos
if h.W != 1 {
r.X /= h.W
r.Y /= h.W
r.Z /= h.W
}
return r
}
// Multiply the given point by our vector. Remember this is row-major order.
// Point is NOT scaled back
func (m *Mat44f) MultiplyPoint3(p Vec3f) HVec3f {
var out HVec3f
// We hope very much that Go will optimize the function calls for us,
// along with computing the constants.
out.Pos.X = p.X*m.Get(0, 0) + p.Y*m.Get(0, 1) + p.Z*m.Get(0, 2) + m.Get(0, 3)
out.Pos.Y = p.X*m.Get(1, 0) + p.Y*m.Get(1, 1) + p.Z*m.Get(1, 2) + m.Get(1, 3)
out.Pos.Z = p.X*m.Get(2, 0) + p.Y*m.Get(2, 1) + p.Z*m.Get(2, 2) + m.Get(2, 3)
out.W = p.X*m.Get(3, 0) + p.Y*m.Get(3, 1) + p.Z*m.Get(3, 2) + m.Get(3, 3)
return out
}
// Multiply two 4x4 matrices together (not optimized). May
// mess with garbage collector?? IDK
func (m *Mat44f) Multiply(m2 *Mat44f) *Mat44f {
var result Mat44f
// This is the x and y of our resulting matrix
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
for i := 0; i < 4; i++ {
result[x+y*4] += m[i+y*4] * m2[x+i*4]
}
}
}
return &result
}
// Multiply two matrices, storing the result in the first one
// func (m *Mat44f) MultiplyInto(m2 *Mat44f) {
// var orig Mat44f
// for i := 0; i < 16; i++ {
// orig[i] = m[i]
// }
// // This is the x and y of our resulting matrix
// for y := 0; y < 4; y++ {
// for x := 0; x < 4; x++ {
// m[x+y*4] = 0
// for i := 0; i < 4; i++ {
// m[x+y*4] += orig[i+y*4] * m2[x+i*4]
// }
// }
// }
// return &result
// }
func (vi *Vec2i) ToF() Vec2f {
return Vec2f{float32(vi.X), float32(vi.Y)}
}
func (vi *Vec3f) ToVec2i() Vec2i {
return Vec2i{int(vi.X), int(vi.Y)}
}
func (v0 *Vec3f) Add(v1 *Vec3f) *Vec3f {
return &Vec3f{
X: v0.X + v1.X,
Y: v0.Y + v1.Y,
Z: v0.Z + v1.Z,
}
}
func (v0 *Vec3f) Sub(v1 *Vec3f) *Vec3f {
return &Vec3f{
X: v0.X - v1.X,
Y: v0.Y - v1.Y,
Z: v0.Z - v1.Z,
}
}
func (v0 *Vec3f) Scale(s float32) *Vec3f {
return &Vec3f{
X: v0.X * s,
Y: v0.Y * s,
Z: v0.Z * s,
}
}
func (v0 *HVec3f) LerpSelf(v1 *HVec3f, t float32) {
v0.Pos.X = (1-t)*v0.Pos.X + t*v1.Pos.X
v0.Pos.Y = (1-t)*v0.Pos.Y + t*v1.Pos.Y
v0.Pos.Z = (1-t)*v0.Pos.Z + t*v1.Pos.Z
v0.W = (1-t)*v0.W + t*v1.W
}
func LerpVec3f(v0 Vec3f, v1 Vec3f, t float32) Vec3f {
return Vec3f{
X: (1-t)*v0.X + t*v1.X,
Y: (1-t)*v0.Y + t*v1.Y,
Z: (1-t)*v0.Z + t*v1.Z,
}
}
func LerpF32(a, b, t float32) float32 {
return (1-t)*a + t*b
}
func (v0 *Vec3f) CrossProduct(v1 *Vec3f) *Vec3f {
return &Vec3f{
X: v0.Y*v1.Z - v0.Z*v1.Y,
Y: v0.Z*v1.X - v0.X*v1.Z,
Z: v0.X*v1.Y - v0.Y*v1.X,
}
}
//func (v
func (v *Vec3f) Normalize() *Vec3f {
l := float32(math.Sqrt(float64(v.MultSimp(v))))
return &Vec3f{
X: v.X / l,
Y: v.Y / l,
Z: v.Z / l,
}
}
func (v0 *Vec3f) MultSimp(v1 *Vec3f) float32 {
return v0.X*v1.X + v0.Y*v1.Y + v0.Z*v1.Z
}
func Clamp[t float32 | int](v, minv, maxv t) t {
if v < minv {
return minv
} else if v > maxv {
return maxv
} else {
return v
}
}