14 Commits

Author SHA1 Message Date
6adefa81f7 Add Clamp 2024-05-14 06:06:00 +04:00
b9faa2e59e New NewTrMatXYZ 2024-05-05 00:27:05 +04:00
3eb372dec3 Remove more pointers+better NewXYZ funcs 2024-05-05 00:24:44 +04:00
e6bf7aee10 Remove pointers from .Col() 2024-05-04 23:30:40 +04:00
fa25c1f551 Update workflow name 2024-05-04 22:59:30 +04:00
9efe1a98b1 Update readme 2024-05-04 22:54:40 +04:00
81c90ca4e9 Fix workflow 2024-05-04 22:51:38 +04:00
90d3e8e870 Fix workflow 2024-05-04 22:49:58 +04:00
5d2cfa0329 NewXYZ funcs for Quat+github workflows 2024-05-04 22:44:48 +04:00
afb3bbfe75 Get rid of pointer returns when creating new objects 2024-05-04 22:21:41 +04:00
95005baf22 Mat4.InvertAndTranspose()+ ToMat3 and ToMat2 functions 2024-05-04 21:37:24 +04:00
c08e9d8610 NewXYZ funcs for ease of use and new ScaleXYZ funcs for vectors 2024-05-04 21:10:57 +04:00
cc5e7dcbce Mat4 inverse and determinant 2024-05-04 20:45:40 +04:00
ca55a67100 Mat2 and Mat3 determinant and inverse matrices 2024-05-04 04:49:27 +04:00
23 changed files with 1293 additions and 275 deletions

21
.github/workflows/test-gglm.yml vendored Executable file
View File

@ -0,0 +1,21 @@
name: "Build and Tests"
on:
create:
workflow_dispatch:
jobs:
test-gglm-windows:
runs-on: [windows-latest]
steps:
- name: Install golang
uses: actions/setup-go@v3
with:
go-version: ">=1.17"
- name: Clone gglm
run: git clone https://github.com/bloeys/gglm
- name: Test gglm
working-directory: gglm
run: go test ./... -v

View File

@ -1,5 +1,7 @@
# gglm
[![Tests](https://github.com/bloeys/gglm/actions/workflows/test-gglm.yml/badge.svg)](https://github.com/bloeys/gglm/actions/workflows/test-gglm.yml)
Fast OpenGL/Graphics focused Mathematics library in Go inspired by the c++ library [glm](https://github.com/g-truc/glm).
gglm currently has the following:
@ -18,6 +20,8 @@ gglm currently has the following:
## Installation
Note: gglm requires Go 1.18 or higher.
`go get github.com/bloeys/gglm`
## Usage
@ -52,5 +56,5 @@ func main() {
## Notes
You can check compiler inlining decisions using `go run -gcflags "-m" .`. Some functions look a bit weird compared to similar ones
You can check compiler inlining decisions using `go run -gcflags "-m" .`. Some functions look a bit weird
because we are trying to reduce function complexity so the compiler inlines.

View File

@ -18,8 +18,8 @@ func DotQuat(q1, q2 *Quat) float32 {
return q1.X()*q2.X() + q1.Y()*q2.Y() + q1.Z()*q2.Z() + q1.W()*q2.W()
}
func Cross(v1, v2 *Vec3) *Vec3 {
return &Vec3{
func Cross(v1, v2 *Vec3) Vec3 {
return Vec3{
Data: [3]float32{
v1.Data[1]*v2.Data[2] - v1.Data[2]*v2.Data[1],
v1.Data[2]*v2.Data[0] - v1.Data[0]*v2.Data[2],
@ -81,12 +81,12 @@ func SqrDistVec4(v1, v2 *Vec4) float32 {
// ReflectVec2 returns the reflected vector of the incoming vector 'v', and the surface normal 'n'.
//
// Note: n must be normalized or you will get wrong results
func ReflectVec2(v, n *Vec2) *Vec2 {
func ReflectVec2(v, n *Vec2) Vec2 {
//reflectedVec = v 2*dot(v, norm)*norm
d := 2 * (v.Data[0]*n.Data[0] + v.Data[1]*n.Data[1])
return &Vec2{
return Vec2{
Data: [2]float32{
v.Data[0] - d*n.Data[0],
v.Data[1] - d*n.Data[1],
@ -97,12 +97,12 @@ func ReflectVec2(v, n *Vec2) *Vec2 {
// ReflectVec3 returns the reflected vector of the incoming vector 'v', and the surface normal 'n'.
//
// Note: n must be normalized or you will get wrong results
func ReflectVec3(v, n *Vec3) *Vec3 {
func ReflectVec3(v, n *Vec3) Vec3 {
//reflectedVec = v 2*dot(v, norm)*norm
d := 2 * (v.Data[0]*n.Data[0] + v.Data[1]*n.Data[1] + v.Data[2]*n.Data[2])
return &Vec3{
return Vec3{
Data: [3]float32{
v.Data[0] - d*n.Data[0],
v.Data[1] - d*n.Data[1],

View File

@ -9,8 +9,8 @@ import (
var (
dotVec2Result, distVec2Result float32
dotVec3Result, distVec3Result float32
reflectVec2Result *gglm.Vec2
crossResult, reflectVec3Result *gglm.Vec3
reflectVec2Result gglm.Vec2
crossResult, reflectVec3Result gglm.Vec3
)
func TestDotVec2(t *testing.T) {

View File

@ -28,8 +28,8 @@ func (m *Mat2) String() string {
return fmt.Sprintf("\n| %+-9.3f %+-9.3f |\n| %+-9.3f %+-9.3f |\n", m.Data[0][0], m.Data[0][1], m.Data[1][0], m.Data[1][1])
}
func (m *Mat2) Col(c int) *Vec2 {
return &Vec2{Data: m.Data[c]}
func (m *Mat2) Col(c int) Vec2 {
return Vec2{Data: m.Data[c]}
}
// Add m += m2
@ -84,7 +84,6 @@ func (m *Mat2) Eq(m2 *Mat2) bool {
}
func (m *Mat2) Transpose() *Mat2 {
m.Data = [2][2]float32{
{m.Data[0][0], m.Data[1][0]},
{m.Data[0][1], m.Data[1][1]},
@ -93,9 +92,30 @@ func (m *Mat2) Transpose() *Mat2 {
return m
}
func (m *Mat2) Determinant() float32 {
return (m.Data[0][0] * m.Data[1][1]) - (m.Data[0][1] * m.Data[1][0])
}
// Invert inverts this matrix.
//
// Note that the inverse is not defined if the determinant is zero or extremely small.
// In the case the determinant is zero the matrix will (usually) get filled with infinities
func (m *Mat2) Invert() *Mat2 {
// https://www.cuemath.com/algebra/inverse-of-2x2-matrix/
inverseDet := 1 / m.Determinant()
m.Data = [2][2]float32{
{m.Data[1][1] * inverseDet, -m.Data[0][1] * inverseDet}, // Col0
{-m.Data[1][0] * inverseDet, m.Data[0][0] * inverseDet}, // Col1
}
return m
}
// AddMat2 m3 = m1 + m2
func AddMat2(m1, m2 *Mat2) *Mat2 {
return &Mat2{
func AddMat2(m1, m2 *Mat2) Mat2 {
return Mat2{
Data: [2][2]float32{
{
m1.Data[0][0] + m2.Data[0][0],
@ -110,8 +130,8 @@ func AddMat2(m1, m2 *Mat2) *Mat2 {
}
// SubMat2 m3 = m1 - m2
func SubMat2(m1, m2 *Mat2) *Mat2 {
return &Mat2{
func SubMat2(m1, m2 *Mat2) Mat2 {
return Mat2{
Data: [2][2]float32{
{
m1.Data[0][0] - m2.Data[0][0],
@ -126,8 +146,8 @@ func SubMat2(m1, m2 *Mat2) *Mat2 {
}
// MulMat2 m3 = m1 * m2
func MulMat2(m1, m2 *Mat2) *Mat2 {
return &Mat2{
func MulMat2(m1, m2 *Mat2) Mat2 {
return Mat2{
Data: [2][2]float32{
{
m1.Data[0][0]*m2.Data[0][0] + m1.Data[1][0]*m2.Data[0][1],
@ -142,8 +162,8 @@ func MulMat2(m1, m2 *Mat2) *Mat2 {
}
// MulMat2Vec2 v2 = m1 * v1
func MulMat2Vec2(m1 *Mat2, v1 *Vec2) *Vec2 {
return &Vec2{
func MulMat2Vec2(m1 *Mat2, v1 *Vec2) Vec2 {
return Vec2{
Data: [2]float32{
m1.Data[0][0]*v1.Data[0] + m1.Data[1][0]*v1.Data[1],
m1.Data[0][1]*v1.Data[0] + m1.Data[1][1]*v1.Data[1],
@ -152,11 +172,47 @@ func MulMat2Vec2(m1 *Mat2, v1 *Vec2) *Vec2 {
}
// NewMat2Id returns the 2x2 identity matrix
func NewMat2Id() *Mat2 {
return &Mat2{
func NewMat2Id() Mat2 {
return Mat2{
Data: [2][2]float32{
{1, 0},
{0, 1},
},
}
}
func NewMat2Diag(diagVal float32) Mat2 {
return Mat2{
Data: [2][2]float32{
{diagVal, 0},
{0, diagVal},
},
}
}
func NewMat2DiagArr(diag [2]float32) Mat2 {
return Mat2{
Data: [2][2]float32{
{diag[0], 0},
{0, diag[1]},
},
}
}
func NewMat2Vec2(col0, col1 *Vec2) Mat2 {
return Mat2{
Data: [2][2]float32{
col0.Data,
col1.Data,
},
}
}
func NewMat2Arr(col0, col1 [2]float32) Mat2 {
return Mat2{
Data: [2][2]float32{
col0,
col1,
},
}
}

View File

@ -152,29 +152,69 @@ func TestTransposeMat2(t *testing.T) {
m := gglm.NewMat2Id()
ans := gglm.NewMat2Id()
if !m.Transpose().Transpose().Eq(ans) {
if !m.Transpose().Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m = &gglm.Mat2{
Data: [2][2]float32{
{00, 01},
{10, 11},
},
m.Data = [2][2]float32{
{00, 01},
{10, 11},
}
ans = &gglm.Mat2{
Data: [2][2]float32{
{00, 10},
{01, 11},
},
ans.Data = [2][2]float32{
{00, 10},
{01, 11},
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
}
func TestDeterminantMat2(t *testing.T) {
m := gglm.NewMat2Id()
ans := float32(1)
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
m.Data = [2][2]float32{
{1, 8},
{5, 31},
}
ans = -9
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
}
func TestInvertMat2(t *testing.T) {
m := gglm.NewMat2Id()
ans := gglm.NewMat2Id()
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m.Data = [2][2]float32{
{1, 8},
{5, 31},
}
ans.Data = [2][2]float32{
{-31 / 9.0, 8 / 9.0},
{5 / 9.0, -1 / 9.0},
}
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
}
@ -185,6 +225,6 @@ func BenchmarkMulMat2(b *testing.B) {
m2 := gglm.NewMat2Id()
for i := 0; i < b.N; i++ {
m1.Mul(m2)
m1.Mul(&m2)
}
}

View File

@ -31,8 +31,8 @@ func (m *Mat3) String() string {
)
}
func (m *Mat3) Col(c int) *Vec3 {
return &Vec3{Data: m.Data[c]}
func (m *Mat3) Col(c int) Vec3 {
return Vec3{Data: m.Data[c]}
}
// Add m += m2
@ -145,9 +145,68 @@ func (m *Mat3) Transpose() *Mat3 {
return m
}
func (m *Mat3) Determinant() float32 {
x := m.Data[0][0] * (m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2])
y := m.Data[1][0] * (m.Data[2][1]*m.Data[0][2] - m.Data[0][1]*m.Data[2][2])
z := m.Data[2][0] * (m.Data[0][1]*m.Data[1][2] - m.Data[1][1]*m.Data[0][2])
return x + y + z
}
// Invert inverts this matrix.
//
// Note that the inverse is not defined if the determinant is zero or extremely small.
// In the case the determinant is zero the matrix will (usually) get filled with infinities
func (m *Mat3) Invert() *Mat3 {
// https://www.cuemath.com/algebra/inverse-of-3x3-matrix/
// Manually inline the determinant function because go doesn't
x := m.Data[0][0] * (m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2])
y := m.Data[1][0] * (m.Data[2][1]*m.Data[0][2] - m.Data[0][1]*m.Data[2][2])
z := m.Data[2][0] * (m.Data[0][1]*m.Data[1][2] - m.Data[1][1]*m.Data[0][2])
inverseDet := 1 / (x + y + z)
m.Data = [3][3]float32{
// Col0
{
(m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2]) * inverseDet,
-(m.Data[0][1]*m.Data[2][2] - m.Data[2][1]*m.Data[0][2]) * inverseDet,
(m.Data[0][1]*m.Data[1][2] - m.Data[1][1]*m.Data[0][2]) * inverseDet,
},
// Col1
{
-(m.Data[1][0]*m.Data[2][2] - m.Data[2][0]*m.Data[1][2]) * inverseDet,
(m.Data[0][0]*m.Data[2][2] - m.Data[2][0]*m.Data[0][2]) * inverseDet,
-(m.Data[0][0]*m.Data[1][2] - m.Data[1][0]*m.Data[0][2]) * inverseDet,
},
// Col2
{
(m.Data[1][0]*m.Data[2][1] - m.Data[2][0]*m.Data[1][1]) * inverseDet,
-(m.Data[0][0]*m.Data[2][1] - m.Data[2][0]*m.Data[0][1]) * inverseDet,
(m.Data[0][0]*m.Data[1][1] - m.Data[1][0]*m.Data[0][1]) * inverseDet,
},
}
return m
}
// ToMat2 returns a Mat2 that contains the top-left 2x2 section of the Mat3.
// That is, column 2 and row 2 are dropped.
func (m *Mat3) ToMat2() Mat2 {
return Mat2{
Data: [2][2]float32{
{m.Data[0][0], m.Data[0][1]},
{m.Data[1][0], m.Data[1][1]},
},
}
}
// AddMat3 m3 = m1 + m2
func AddMat3(m1, m2 *Mat3) *Mat3 {
return &Mat3{
func AddMat3(m1, m2 *Mat3) Mat3 {
return Mat3{
Data: [3][3]float32{
{
m1.Data[0][0] + m2.Data[0][0],
@ -169,8 +228,8 @@ func AddMat3(m1, m2 *Mat3) *Mat3 {
}
// SubMat3 m3 = m1 - m2
func SubMat3(m1, m2 *Mat3) *Mat3 {
return &Mat3{
func SubMat3(m1, m2 *Mat3) Mat3 {
return Mat3{
Data: [3][3]float32{
{
m1.Data[0][0] - m2.Data[0][0],
@ -192,7 +251,7 @@ func SubMat3(m1, m2 *Mat3) *Mat3 {
}
// MulMat3 m3 = m1 * m2
func MulMat3(m1, m2 *Mat3) *Mat3 {
func MulMat3(m1, m2 *Mat3) Mat3 {
m00 := m1.Data[0][0]
m01 := m1.Data[0][1]
@ -206,7 +265,7 @@ func MulMat3(m1, m2 *Mat3) *Mat3 {
m21 := m1.Data[2][1]
m22 := m1.Data[2][2]
return &Mat3{
return Mat3{
Data: [3][3]float32{
{
m00*m2.Data[0][0] + m10*m2.Data[0][1] + m20*m2.Data[0][2],
@ -228,8 +287,8 @@ func MulMat3(m1, m2 *Mat3) *Mat3 {
}
// MulMat3Vec3 v2 = m1 * v1
func MulMat3Vec3(m1 *Mat3, v1 *Vec3) *Vec3 {
return &Vec3{
func MulMat3Vec3(m1 *Mat3, v1 *Vec3) Vec3 {
return Vec3{
Data: [3]float32{
m1.Data[0][0]*v1.Data[0] + m1.Data[1][0]*v1.Data[1] + m1.Data[2][0]*v1.Data[2],
m1.Data[0][1]*v1.Data[0] + m1.Data[1][1]*v1.Data[1] + m1.Data[2][1]*v1.Data[2],
@ -239,8 +298,8 @@ func MulMat3Vec3(m1 *Mat3, v1 *Vec3) *Vec3 {
}
// NewMat3Id returns the 3x3 identity matrix
func NewMat3Id() *Mat3 {
return &Mat3{
func NewMat3Id() Mat3 {
return Mat3{
Data: [3][3]float32{
{1, 0, 0},
{0, 1, 0},
@ -248,3 +307,43 @@ func NewMat3Id() *Mat3 {
},
}
}
func NewMat3Diag(diagVal float32) Mat3 {
return Mat3{
Data: [3][3]float32{
{diagVal, 0, 0},
{0, diagVal, 0},
{0, 0, diagVal},
},
}
}
func NewMat3DiagArr(diag [3]float32) Mat3 {
return Mat3{
Data: [3][3]float32{
{diag[0], 0, 0},
{0, diag[1], 0},
{0, 0, diag[2]},
},
}
}
func NewMat3Vec3(col0, col1, col2 *Vec3) Mat3 {
return Mat3{
Data: [3][3]float32{
col0.Data,
col1.Data,
col2.Data,
},
}
}
func NewMat3Arr(col0, col1, col2 [3]float32) Mat3 {
return Mat3{
Data: [3][3]float32{
col0,
col1,
col2,
},
}
}

View File

@ -167,31 +167,74 @@ func TestTransposeMat3(t *testing.T) {
m := gglm.NewMat3Id()
ans := gglm.NewMat3Id()
if !m.Transpose().Transpose().Eq(ans) {
if !m.Transpose().Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m = &gglm.Mat3{
Data: [3][3]float32{
{00, 01, 02},
{10, 11, 12},
{20, 21, 22},
},
m.Data = [3][3]float32{
{00, 01, 02},
{10, 11, 12},
{20, 21, 22},
}
ans = &gglm.Mat3{
Data: [3][3]float32{
{00, 10, 20},
{01, 11, 21},
{02, 12, 22},
},
ans.Data = [3][3]float32{
{00, 10, 20},
{01, 11, 21},
{02, 12, 22},
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
}
func TestDeterminantMat3(t *testing.T) {
m := gglm.NewMat3Id()
ans := float32(1)
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
m.Data = [3][3]float32{
{1, 8, 2},
{5, 3, 5},
{9, 6, 7},
}
ans = 77
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
}
func TestInvertMat3(t *testing.T) {
m := gglm.NewMat3Id()
ans := gglm.NewMat3Id()
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m.Data = [3][3]float32{
{1, 8, 2},
{5, 3, 5},
{9, 6, 7},
}
ans.Data = [3][3]float32{
{-9 / 77.0, -4 / 7.0, 34 / 77.0},
{10 / 77.0, -1 / 7.0, 5 / 77.0},
{3 / 77.0, 6 / 7.0, -37 / 77.0},
}
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
}
@ -202,6 +245,6 @@ func BenchmarkMulMat3(b *testing.B) {
m2 := gglm.NewMat3Id()
for i := 0; i < b.N; i++ {
m1.Mul(m2)
m1.Mul(&m2)
}
}

View File

@ -32,8 +32,8 @@ func (m *Mat4) String() string {
)
}
func (m *Mat4) Col(c int) *Vec4 {
return &Vec4{Data: m.Data[c]}
func (m *Mat4) Col(c int) Vec4 {
return Vec4{Data: m.Data[c]}
}
// Add m += m2
@ -192,9 +192,311 @@ func (m *Mat4) Transpose() *Mat4 {
return m
}
func (m *Mat4) Determinant() float32 {
// Many thanks to the C++ GLM project here :)
coef00 := m.Data[2][2]*m.Data[3][3] - m.Data[3][2]*m.Data[2][3]
coef02 := m.Data[1][2]*m.Data[3][3] - m.Data[3][2]*m.Data[1][3]
coef03 := m.Data[1][2]*m.Data[2][3] - m.Data[2][2]*m.Data[1][3]
coef04 := m.Data[2][1]*m.Data[3][3] - m.Data[3][1]*m.Data[2][3]
coef06 := m.Data[1][1]*m.Data[3][3] - m.Data[3][1]*m.Data[1][3]
coef07 := m.Data[1][1]*m.Data[2][3] - m.Data[2][1]*m.Data[1][3]
coef08 := m.Data[2][1]*m.Data[3][2] - m.Data[3][1]*m.Data[2][2]
coef10 := m.Data[1][1]*m.Data[3][2] - m.Data[3][1]*m.Data[1][2]
coef11 := m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2]
coef12 := m.Data[2][0]*m.Data[3][3] - m.Data[3][0]*m.Data[2][3]
coef14 := m.Data[1][0]*m.Data[3][3] - m.Data[3][0]*m.Data[1][3]
coef15 := m.Data[1][0]*m.Data[2][3] - m.Data[2][0]*m.Data[1][3]
coef16 := m.Data[2][0]*m.Data[3][2] - m.Data[3][0]*m.Data[2][2]
coef18 := m.Data[1][0]*m.Data[3][2] - m.Data[3][0]*m.Data[1][2]
coef19 := m.Data[1][0]*m.Data[2][2] - m.Data[2][0]*m.Data[1][2]
coef20 := m.Data[2][0]*m.Data[3][1] - m.Data[3][0]*m.Data[2][1]
coef22 := m.Data[1][0]*m.Data[3][1] - m.Data[3][0]*m.Data[1][1]
coef23 := m.Data[1][0]*m.Data[2][1] - m.Data[2][0]*m.Data[1][1]
fac0 := NewVec4(coef00, coef00, coef02, coef03)
fac1 := NewVec4(coef04, coef04, coef06, coef07)
fac2 := NewVec4(coef08, coef08, coef10, coef11)
fac3 := NewVec4(coef12, coef12, coef14, coef15)
fac4 := NewVec4(coef16, coef16, coef18, coef19)
fac5 := NewVec4(coef20, coef20, coef22, coef23)
vec0 := NewVec4(m.Data[1][0], m.Data[0][0], m.Data[0][0], m.Data[0][0])
vec1 := NewVec4(m.Data[1][1], m.Data[0][1], m.Data[0][1], m.Data[0][1])
vec2 := NewVec4(m.Data[1][2], m.Data[0][2], m.Data[0][2], m.Data[0][2])
vec3 := NewVec4(m.Data[1][3], m.Data[0][3], m.Data[0][3], m.Data[0][3])
inv0 := NewVec4(
vec1.X()*fac0.X()-vec2.X()*fac1.X()+vec3.X()*fac2.X(),
vec1.Y()*fac0.Y()-vec2.Y()*fac1.Y()+vec3.Y()*fac2.Y(),
vec1.Z()*fac0.Z()-vec2.Z()*fac1.Z()+vec3.Z()*fac2.Z(),
vec1.W()*fac0.W()-vec2.W()*fac1.W()+vec3.W()*fac2.W(),
)
inv1 := NewVec4(
vec0.X()*fac0.X()-vec2.X()*fac3.X()+vec3.X()*fac4.X(),
vec0.Y()*fac0.Y()-vec2.Y()*fac3.Y()+vec3.Y()*fac4.Y(),
vec0.Z()*fac0.Z()-vec2.Z()*fac3.Z()+vec3.Z()*fac4.Z(),
vec0.W()*fac0.W()-vec2.W()*fac3.W()+vec3.W()*fac4.W(),
)
inv2 := NewVec4(
vec0.X()*fac1.X()-vec1.X()*fac3.X()+vec3.X()*fac5.X(),
vec0.Y()*fac1.Y()-vec1.Y()*fac3.Y()+vec3.Y()*fac5.Y(),
vec0.Z()*fac1.Z()-vec1.Z()*fac3.Z()+vec3.Z()*fac5.Z(),
vec0.W()*fac1.W()-vec1.W()*fac3.W()+vec3.W()*fac5.W(),
)
inv3 := NewVec4(
vec0.X()*fac2.X()-vec1.X()*fac4.X()+vec2.X()*fac5.X(),
vec0.Y()*fac2.Y()-vec1.Y()*fac4.Y()+vec2.Y()*fac5.Y(),
vec0.Z()*fac2.Z()-vec1.Z()*fac4.Z()+vec2.Z()*fac5.Z(),
vec0.W()*fac2.W()-vec1.W()*fac4.W()+vec2.W()*fac5.W(),
)
signA := NewVec4(+1, -1, +1, -1)
signB := NewVec4(-1, +1, -1, +1)
inverse := NewMat4Arr(
inv0.ScaleVec(&signA).Data,
inv1.ScaleVec(&signB).Data,
inv2.ScaleVec(&signA).Data,
inv3.ScaleVec(&signB).Data,
)
row0 := NewVec4(inverse.Data[0][0], inverse.Data[1][0], inverse.Data[2][0], inverse.Data[3][0])
dot0 := NewVec4Arr(row0.ScaleArr(m.Data[0]).Data)
det := (dot0.X() + dot0.Y()) + (dot0.Z() + dot0.W())
return det
}
// Invert inverts this matrix.
//
// Note that the inverse is not defined if the determinant is zero or extremely small.
// In the case the determinant is zero the matrix will (usually) get filled with infinities
func (m *Mat4) Invert() *Mat4 {
// Many thanks to the C++ GLM project here :)
coef00 := m.Data[2][2]*m.Data[3][3] - m.Data[3][2]*m.Data[2][3]
coef02 := m.Data[1][2]*m.Data[3][3] - m.Data[3][2]*m.Data[1][3]
coef03 := m.Data[1][2]*m.Data[2][3] - m.Data[2][2]*m.Data[1][3]
coef04 := m.Data[2][1]*m.Data[3][3] - m.Data[3][1]*m.Data[2][3]
coef06 := m.Data[1][1]*m.Data[3][3] - m.Data[3][1]*m.Data[1][3]
coef07 := m.Data[1][1]*m.Data[2][3] - m.Data[2][1]*m.Data[1][3]
coef08 := m.Data[2][1]*m.Data[3][2] - m.Data[3][1]*m.Data[2][2]
coef10 := m.Data[1][1]*m.Data[3][2] - m.Data[3][1]*m.Data[1][2]
coef11 := m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2]
coef12 := m.Data[2][0]*m.Data[3][3] - m.Data[3][0]*m.Data[2][3]
coef14 := m.Data[1][0]*m.Data[3][3] - m.Data[3][0]*m.Data[1][3]
coef15 := m.Data[1][0]*m.Data[2][3] - m.Data[2][0]*m.Data[1][3]
coef16 := m.Data[2][0]*m.Data[3][2] - m.Data[3][0]*m.Data[2][2]
coef18 := m.Data[1][0]*m.Data[3][2] - m.Data[3][0]*m.Data[1][2]
coef19 := m.Data[1][0]*m.Data[2][2] - m.Data[2][0]*m.Data[1][2]
coef20 := m.Data[2][0]*m.Data[3][1] - m.Data[3][0]*m.Data[2][1]
coef22 := m.Data[1][0]*m.Data[3][1] - m.Data[3][0]*m.Data[1][1]
coef23 := m.Data[1][0]*m.Data[2][1] - m.Data[2][0]*m.Data[1][1]
fac0 := NewVec4(coef00, coef00, coef02, coef03)
fac1 := NewVec4(coef04, coef04, coef06, coef07)
fac2 := NewVec4(coef08, coef08, coef10, coef11)
fac3 := NewVec4(coef12, coef12, coef14, coef15)
fac4 := NewVec4(coef16, coef16, coef18, coef19)
fac5 := NewVec4(coef20, coef20, coef22, coef23)
vec0 := NewVec4(m.Data[1][0], m.Data[0][0], m.Data[0][0], m.Data[0][0])
vec1 := NewVec4(m.Data[1][1], m.Data[0][1], m.Data[0][1], m.Data[0][1])
vec2 := NewVec4(m.Data[1][2], m.Data[0][2], m.Data[0][2], m.Data[0][2])
vec3 := NewVec4(m.Data[1][3], m.Data[0][3], m.Data[0][3], m.Data[0][3])
inv0 := NewVec4(
vec1.X()*fac0.X()-vec2.X()*fac1.X()+vec3.X()*fac2.X(),
vec1.Y()*fac0.Y()-vec2.Y()*fac1.Y()+vec3.Y()*fac2.Y(),
vec1.Z()*fac0.Z()-vec2.Z()*fac1.Z()+vec3.Z()*fac2.Z(),
vec1.W()*fac0.W()-vec2.W()*fac1.W()+vec3.W()*fac2.W(),
)
inv1 := NewVec4(
vec0.X()*fac0.X()-vec2.X()*fac3.X()+vec3.X()*fac4.X(),
vec0.Y()*fac0.Y()-vec2.Y()*fac3.Y()+vec3.Y()*fac4.Y(),
vec0.Z()*fac0.Z()-vec2.Z()*fac3.Z()+vec3.Z()*fac4.Z(),
vec0.W()*fac0.W()-vec2.W()*fac3.W()+vec3.W()*fac4.W(),
)
inv2 := NewVec4(
vec0.X()*fac1.X()-vec1.X()*fac3.X()+vec3.X()*fac5.X(),
vec0.Y()*fac1.Y()-vec1.Y()*fac3.Y()+vec3.Y()*fac5.Y(),
vec0.Z()*fac1.Z()-vec1.Z()*fac3.Z()+vec3.Z()*fac5.Z(),
vec0.W()*fac1.W()-vec1.W()*fac3.W()+vec3.W()*fac5.W(),
)
inv3 := NewVec4(
vec0.X()*fac2.X()-vec1.X()*fac4.X()+vec2.X()*fac5.X(),
vec0.Y()*fac2.Y()-vec1.Y()*fac4.Y()+vec2.Y()*fac5.Y(),
vec0.Z()*fac2.Z()-vec1.Z()*fac4.Z()+vec2.Z()*fac5.Z(),
vec0.W()*fac2.W()-vec1.W()*fac4.W()+vec2.W()*fac5.W(),
)
signA := NewVec4(+1, -1, +1, -1)
signB := NewVec4(-1, +1, -1, +1)
inverse := NewMat4Arr(
inv0.ScaleVec(&signA).Data,
inv1.ScaleVec(&signB).Data,
inv2.ScaleVec(&signA).Data,
inv3.ScaleVec(&signB).Data,
)
row0 := NewVec4(inverse.Data[0][0], inverse.Data[1][0], inverse.Data[2][0], inverse.Data[3][0])
dot0 := NewVec4Arr(row0.ScaleArr(m.Data[0]).Data)
det := (dot0.X() + dot0.Y()) + (dot0.Z() + dot0.W())
inverseDet := 1.0 / det
m.Data = inverse.Scale(inverseDet).Data
return m
}
// InvertAndTranspose is equivalent to m.Invert().Transpose(), that is invert first, then transpose the inverted matrix.
//
// This function is provided as a convenience and as a small optimization, as it inlines the invert and transpose functions which means we only
// have 1 function call.
//
// Additionally, the inverse of the matrix is written to the matrix immediately transposed instead of writing the inverse and then transposing it in a second operation.
func (m *Mat4) InvertAndTranspose() *Mat4 {
// Many thanks to the C++ GLM project here :)
coef00 := m.Data[2][2]*m.Data[3][3] - m.Data[3][2]*m.Data[2][3]
coef02 := m.Data[1][2]*m.Data[3][3] - m.Data[3][2]*m.Data[1][3]
coef03 := m.Data[1][2]*m.Data[2][3] - m.Data[2][2]*m.Data[1][3]
coef04 := m.Data[2][1]*m.Data[3][3] - m.Data[3][1]*m.Data[2][3]
coef06 := m.Data[1][1]*m.Data[3][3] - m.Data[3][1]*m.Data[1][3]
coef07 := m.Data[1][1]*m.Data[2][3] - m.Data[2][1]*m.Data[1][3]
coef08 := m.Data[2][1]*m.Data[3][2] - m.Data[3][1]*m.Data[2][2]
coef10 := m.Data[1][1]*m.Data[3][2] - m.Data[3][1]*m.Data[1][2]
coef11 := m.Data[1][1]*m.Data[2][2] - m.Data[2][1]*m.Data[1][2]
coef12 := m.Data[2][0]*m.Data[3][3] - m.Data[3][0]*m.Data[2][3]
coef14 := m.Data[1][0]*m.Data[3][3] - m.Data[3][0]*m.Data[1][3]
coef15 := m.Data[1][0]*m.Data[2][3] - m.Data[2][0]*m.Data[1][3]
coef16 := m.Data[2][0]*m.Data[3][2] - m.Data[3][0]*m.Data[2][2]
coef18 := m.Data[1][0]*m.Data[3][2] - m.Data[3][0]*m.Data[1][2]
coef19 := m.Data[1][0]*m.Data[2][2] - m.Data[2][0]*m.Data[1][2]
coef20 := m.Data[2][0]*m.Data[3][1] - m.Data[3][0]*m.Data[2][1]
coef22 := m.Data[1][0]*m.Data[3][1] - m.Data[3][0]*m.Data[1][1]
coef23 := m.Data[1][0]*m.Data[2][1] - m.Data[2][0]*m.Data[1][1]
fac0 := NewVec4(coef00, coef00, coef02, coef03)
fac1 := NewVec4(coef04, coef04, coef06, coef07)
fac2 := NewVec4(coef08, coef08, coef10, coef11)
fac3 := NewVec4(coef12, coef12, coef14, coef15)
fac4 := NewVec4(coef16, coef16, coef18, coef19)
fac5 := NewVec4(coef20, coef20, coef22, coef23)
vec0 := NewVec4(m.Data[1][0], m.Data[0][0], m.Data[0][0], m.Data[0][0])
vec1 := NewVec4(m.Data[1][1], m.Data[0][1], m.Data[0][1], m.Data[0][1])
vec2 := NewVec4(m.Data[1][2], m.Data[0][2], m.Data[0][2], m.Data[0][2])
vec3 := NewVec4(m.Data[1][3], m.Data[0][3], m.Data[0][3], m.Data[0][3])
inv0 := NewVec4(
vec1.X()*fac0.X()-vec2.X()*fac1.X()+vec3.X()*fac2.X(),
vec1.Y()*fac0.Y()-vec2.Y()*fac1.Y()+vec3.Y()*fac2.Y(),
vec1.Z()*fac0.Z()-vec2.Z()*fac1.Z()+vec3.Z()*fac2.Z(),
vec1.W()*fac0.W()-vec2.W()*fac1.W()+vec3.W()*fac2.W(),
)
inv1 := NewVec4(
vec0.X()*fac0.X()-vec2.X()*fac3.X()+vec3.X()*fac4.X(),
vec0.Y()*fac0.Y()-vec2.Y()*fac3.Y()+vec3.Y()*fac4.Y(),
vec0.Z()*fac0.Z()-vec2.Z()*fac3.Z()+vec3.Z()*fac4.Z(),
vec0.W()*fac0.W()-vec2.W()*fac3.W()+vec3.W()*fac4.W(),
)
inv2 := NewVec4(
vec0.X()*fac1.X()-vec1.X()*fac3.X()+vec3.X()*fac5.X(),
vec0.Y()*fac1.Y()-vec1.Y()*fac3.Y()+vec3.Y()*fac5.Y(),
vec0.Z()*fac1.Z()-vec1.Z()*fac3.Z()+vec3.Z()*fac5.Z(),
vec0.W()*fac1.W()-vec1.W()*fac3.W()+vec3.W()*fac5.W(),
)
inv3 := NewVec4(
vec0.X()*fac2.X()-vec1.X()*fac4.X()+vec2.X()*fac5.X(),
vec0.Y()*fac2.Y()-vec1.Y()*fac4.Y()+vec2.Y()*fac5.Y(),
vec0.Z()*fac2.Z()-vec1.Z()*fac4.Z()+vec2.Z()*fac5.Z(),
vec0.W()*fac2.W()-vec1.W()*fac4.W()+vec2.W()*fac5.W(),
)
signA := NewVec4(+1, -1, +1, -1)
signB := NewVec4(-1, +1, -1, +1)
inverse := NewMat4Arr(
inv0.ScaleVec(&signA).Data,
inv1.ScaleVec(&signB).Data,
inv2.ScaleVec(&signA).Data,
inv3.ScaleVec(&signB).Data,
)
row0 := NewVec4(inverse.Data[0][0], inverse.Data[1][0], inverse.Data[2][0], inverse.Data[3][0])
dot0 := NewVec4Arr(row0.ScaleArr(m.Data[0]).Data)
det := (dot0.X() + dot0.Y()) + (dot0.Z() + dot0.W())
inverseDet := 1.0 / det
inverse.Scale(inverseDet)
// Manually inline transpose
m.Data = [4][4]float32{
{inverse.Data[0][0], inverse.Data[1][0], inverse.Data[2][0], inverse.Data[3][0]},
{inverse.Data[0][1], inverse.Data[1][1], inverse.Data[2][1], inverse.Data[3][1]},
{inverse.Data[0][2], inverse.Data[1][2], inverse.Data[2][2], inverse.Data[3][2]},
{inverse.Data[0][3], inverse.Data[1][3], inverse.Data[2][3], inverse.Data[3][3]},
}
return m
}
// ToMat2 returns a Mat2 that contains the top-left 2x2 section of the Mat4.
// That is, columns 2 and 3, and rows 2 and 3, are dropped.
func (m *Mat4) ToMat2() Mat2 {
return Mat2{
Data: [2][2]float32{
{m.Data[0][0], m.Data[0][1]},
{m.Data[1][0], m.Data[1][1]},
},
}
}
// ToMat3 returns a Mat3 that contains the top-left 3x3 section of the Mat4.
// That is, column 3 and row 3 are dropped.
func (m *Mat4) ToMat3() Mat3 {
return Mat3{
Data: [3][3]float32{
{m.Data[0][0], m.Data[0][1], m.Data[0][2]},
{m.Data[1][0], m.Data[1][1], m.Data[1][2]},
{m.Data[2][0], m.Data[2][1], m.Data[2][2]},
},
}
}
// AddMat4 m3 = m1 + m2
func AddMat4(m1, m2 *Mat4) *Mat4 {
return &Mat4{
func AddMat4(m1, m2 *Mat4) Mat4 {
return Mat4{
Data: [4][4]float32{
{
m1.Data[0][0] + m2.Data[0][0],
@ -225,8 +527,8 @@ func AddMat4(m1, m2 *Mat4) *Mat4 {
}
// SubMat4 m3 = m1 - m2
func SubMat4(m1, m2 *Mat4) *Mat4 {
return &Mat4{
func SubMat4(m1, m2 *Mat4) Mat4 {
return Mat4{
Data: [4][4]float32{
{
m1.Data[0][0] - m2.Data[0][0],
@ -257,7 +559,7 @@ func SubMat4(m1, m2 *Mat4) *Mat4 {
}
// MulMat4 m3 = m1 * m2
func MulMat4(m1, m2 *Mat4) *Mat4 {
func MulMat4(m1, m2 *Mat4) Mat4 {
m00 := m1.Data[0][0]
m01 := m1.Data[0][1]
@ -279,7 +581,7 @@ func MulMat4(m1, m2 *Mat4) *Mat4 {
m32 := m1.Data[3][2]
m33 := m1.Data[3][3]
return &Mat4{
return Mat4{
Data: [4][4]float32{
{
m00*m2.Data[0][0] + m10*m2.Data[0][1] + m20*m2.Data[0][2] + m30*m2.Data[0][3],
@ -310,8 +612,8 @@ func MulMat4(m1, m2 *Mat4) *Mat4 {
}
// MulMat4Vec4 v2 = m1 * v1
func MulMat4Vec4(m1 *Mat4, v1 *Vec4) *Vec4 {
return &Vec4{
func MulMat4Vec4(m1 *Mat4, v1 *Vec4) Vec4 {
return Vec4{
Data: [4]float32{
m1.Data[0][0]*v1.Data[0] + m1.Data[1][0]*v1.Data[1] + m1.Data[2][0]*v1.Data[2] + m1.Data[3][0]*v1.Data[3],
m1.Data[0][1]*v1.Data[0] + m1.Data[1][1]*v1.Data[1] + m1.Data[2][1]*v1.Data[2] + m1.Data[3][1]*v1.Data[3],
@ -322,8 +624,8 @@ func MulMat4Vec4(m1 *Mat4, v1 *Vec4) *Vec4 {
}
// NewMat4Id returns the 4x4 identity matrix
func NewMat4Id() *Mat4 {
return &Mat4{
func NewMat4Id() Mat4 {
return Mat4{
Data: [4][4]float32{
{1, 0, 0, 0},
{0, 1, 0, 0},
@ -332,3 +634,47 @@ func NewMat4Id() *Mat4 {
},
}
}
func NewMat4Diag(diagVal float32) Mat4 {
return Mat4{
Data: [4][4]float32{
{diagVal, 0, 0, 0},
{0, diagVal, 0, 0},
{0, 0, diagVal, 0},
{0, 0, 0, diagVal},
},
}
}
func NewMat4DiagArr(diag [4]float32) Mat4 {
return Mat4{
Data: [4][4]float32{
{diag[0], 0, 0, 0},
{0, diag[1], 0, 0},
{0, 0, diag[2], 0},
{0, 0, 0, diag[3]},
},
}
}
func NewMat4Vec4(col0, col1, col2, col3 *Vec4) Mat4 {
return Mat4{
Data: [4][4]float32{
col0.Data,
col1.Data,
col2.Data,
col3.Data,
},
}
}
func NewMat4Arr(col0, col1, col2, col3 [4]float32) *Mat4 {
return &Mat4{
Data: [4][4]float32{
col0,
col1,
col2,
col3,
},
}
}

View File

@ -7,7 +7,7 @@ import (
)
var (
mulMat4Vec4Res *gglm.Vec4
mulMat4Vec4Res gglm.Vec4
)
func TestMat4GetSet(t *testing.T) {
@ -187,44 +187,119 @@ func TestTransposeMat4(t *testing.T) {
m := gglm.NewMat4Id()
ans := gglm.NewMat4Id()
if !m.Transpose().Transpose().Eq(ans) {
if !m.Transpose().Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m = &gglm.Mat4{
Data: [4][4]float32{
{00, 01, 02, 03},
{10, 11, 12, 13},
{20, 21, 22, 23},
{30, 31, 32, 33},
},
m.Data = [4][4]float32{
{00, 01, 02, 03},
{10, 11, 12, 13},
{20, 21, 22, 23},
{30, 31, 32, 33},
}
ans = &gglm.Mat4{
Data: [4][4]float32{
{00, 10, 20, 30},
{01, 11, 21, 31},
{02, 12, 22, 32},
{03, 13, 23, 33},
},
ans.Data = [4][4]float32{
{00, 10, 20, 30},
{01, 11, 21, 31},
{02, 12, 22, 32},
{03, 13, 23, 33},
}
if !m.Transpose().Eq(ans) {
if !m.Transpose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
}
func TestDeterminantMat4(t *testing.T) {
m := gglm.NewMat4Id()
ans := float32(1)
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
m.Data = [4][4]float32{
{1, 0, 2, 1},
{2, 1, 3, 0},
{3, 0, 4, 1},
{4, 1, 5, 1},
}
ans = -2
if m.Determinant() != ans {
t.Errorf("Got: %f; Expected: %f", m.Determinant(), ans)
}
}
func TestInvertMat4(t *testing.T) {
m := gglm.NewMat4Id()
ans := gglm.NewMat4Id()
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m.Data = [4][4]float32{
{1, 0, 2, 1},
{2, 1, 3, 0},
{3, 0, 4, 1},
{4, 1, 5, 1},
}
ans.Data = [4][4]float32{
{-1, -1, 0, 1},
{0.5, 0, -3 / 2.0, 1},
{0.5, 1, 0.5, -1},
{1, -1, -1, 1},
}
if !m.Invert().Eq(&ans) {
t.Errorf("Got: %v\nExpected: %v;\n", m.String(), ans.String())
}
}
func TestInvertAndTransposeMat4(t *testing.T) {
m := gglm.NewMat4Id()
ans := gglm.NewMat4Id()
ans.Transpose()
if !m.InvertAndTranspose().Eq(&ans) {
t.Errorf("Got: %v; Expected: %v", m.String(), ans.String())
}
m.Data = [4][4]float32{
{1, 0, 2, 1},
{2, 1, 3, 0},
{3, 0, 4, 1},
{4, 1, 5, 1},
}
ans.Data = [4][4]float32{
{-1, -1, 0, 1},
{0.5, 0, -3 / 2.0, 1},
{0.5, 1, 0.5, -1},
{1, -1, -1, 1},
}
if !m.InvertAndTranspose().Eq(ans.Transpose()) {
t.Errorf("Got: %v\nExpected: %v;\n", m.String(), ans.String())
}
}
func BenchmarkMulMat4(b *testing.B) {
m1 := gglm.NewMat4Id()
m2 := gglm.NewMat4Id()
for i := 0; i < b.N; i++ {
m1.Mul(m2)
m1.Mul(&m2)
}
}
@ -234,6 +309,17 @@ func BenchmarkMulMat4Vec4(b *testing.B) {
v1 := gglm.Vec4{}
for i := 0; i < b.N; i++ {
mulMat4Vec4Res = gglm.MulMat4Vec4(m1, &v1)
mulMat4Vec4Res = gglm.MulMat4Vec4(&m1, &v1)
}
}
var mat4InvertRes *gglm.Mat4
func BenchmarkMat4Invert(b *testing.B) {
m1 := gglm.NewMat4Id()
for i := 0; i < b.N; i++ {
mat4InvertRes = m1.Invert()
}
}

View File

@ -32,51 +32,30 @@ func (q *Quat) Angle() float32 {
}
// Axis returns the rotation axis represented by this quaternion
func (q *Quat) Axis() *Vec3 {
func (q *Quat) Axis() Vec3 {
var t float32 = 1 - q.Data[3]*q.Data[3]
if t <= 0 {
return &Vec3{Data: [3]float32{0, 0, 1}}
return Vec3{Data: [3]float32{0, 0, 1}}
}
t = 1 / Sqrt32(t)
return &Vec3{Data: [3]float32{
return Vec3{Data: [3]float32{
q.Data[0] * t,
q.Data[1] * t,
q.Data[2] * t,
}}
}
// Euler takes rotations in radians and produces a rotation that
// NewQuatEulerVec takes rotations in radians and produces a rotation that
// rotates around the z-axis, y-axis and lastly x-axis.
func NewQuatEuler(v *Vec3) *Quat {
//Some other common terminology: x=roll, y=pitch, z=yaw
sinX, cosX := Sincos32(v.Data[0] * 0.5)
sinY, cosY := Sincos32(v.Data[1] * 0.5)
sinZ, cosZ := Sincos32(v.Data[2] * 0.5)
//This produces a z->y->x multiply order, but its written as XYZ.
//This is due to XYZ meaning independent rotation matrices, so Z is applied
//first, then Y matrix and lastly X.
//See this for more info: https://github.com/godotengine/godot/issues/6816#issuecomment-254592170
//
//Note: On most conversion tools putting the multiply order (e.g. ZYX for us) is required.
return &Quat{
Vec4: Vec4{
Data: [4]float32{
sinX*cosY*cosZ - cosX*sinY*sinZ,
cosX*sinY*cosZ + sinX*cosY*sinZ,
cosX*cosY*sinZ - sinX*sinY*cosZ,
cosX*cosY*cosZ + sinX*sinY*sinZ,
},
},
}
func NewQuatEulerVec(v *Vec3) Quat {
return NewQuatEuler(v.X(), v.Y(), v.Z())
}
// Euler takes rotations in radians and produces a rotation that
// NewQuatEuler takes rotations in radians and produces a rotation that
// rotates around the z-axis, y-axis and lastly x-axis.
func NewQuatEulerXYZ(x, y, z float32) *Quat {
func NewQuatEuler(x, y, z float32) Quat {
//Some other common terminology: x=roll, y=pitch, z=yaw
sinX, cosX := Sincos32(x * 0.5)
@ -89,7 +68,7 @@ func NewQuatEulerXYZ(x, y, z float32) *Quat {
//See this for more info: https://github.com/godotengine/godot/issues/6816#issuecomment-254592170
//
//Note: On most conversion tools putting the multiply order (e.g. ZYX for us) is required.
return &Quat{
return Quat{
Vec4: Vec4{
Data: [4]float32{
sinX*cosY*cosZ - cosX*sinY*sinZ,
@ -101,26 +80,55 @@ func NewQuatEulerXYZ(x, y, z float32) *Quat {
}
}
// NewQuatAngleAxisVec produces a quaternion thats rotates rotRad radians around the *normalized* vector rotAxisNorm
func NewQuatAngleAxisVec(rotRad float32, rotAxisNorm *Vec3) Quat {
return NewQuatAngleAxis(rotRad, rotAxisNorm.X(), rotAxisNorm.Y(), rotAxisNorm.Z())
}
// NewQuatAngleAxis produces a quaternion thats rotates rotRad radians around the *normalized* vector rotAxisNorm
func NewQuatAngleAxis(rotRad float32, rotAxisNorm *Vec3) *Quat {
func NewQuatAngleAxis(rotRad float32, rotAxisNormX, rotAxisNormY, rotAxisNormZ float32) Quat {
s, c := Sincos32(rotRad * 0.5)
return &Quat{
return Quat{
Vec4: Vec4{
Data: [4]float32{
rotAxisNorm.Data[0] * s,
rotAxisNorm.Data[1] * s,
rotAxisNorm.Data[2] * s,
rotAxisNormX * s,
rotAxisNormY * s,
rotAxisNormZ * s,
c,
},
},
}
}
func NewQuatId() *Quat {
return &Quat{
func NewQuatId() Quat {
return Quat{
Vec4: Vec4{
Data: [4]float32{0, 0, 0, 1},
},
}
}
func NewQuat(x, y, z, w float32) Quat {
return Quat{
Vec4: Vec4{
Data: [4]float32{x, y, z, w},
},
}
}
func NewQuatArr(arr [4]float32) Quat {
return Quat{
Vec4: Vec4{
Data: arr,
},
}
}
func NewQuatVec(v *Vec4) Quat {
return Quat{
Vec4: Vec4{
Data: v.Data,
},
}
}

View File

@ -8,14 +8,16 @@ import (
func TestNewQuatEuler(t *testing.T) {
q := gglm.NewQuatEuler(gglm.NewVec3(180, 180, 180).AsRad())
ans := &gglm.Quat{Vec4: *gglm.NewVec4(0, 0, 0, 1)}
degs := gglm.NewVec3(180, 180, 180)
degs.Data = degs.AsRad().Data
q := gglm.NewQuatEulerVec(&degs)
ans := &gglm.Quat{Vec4: gglm.NewVec4(0, 0, 0, 1)}
if !gglm.EqF32(q.X(), ans.X()) || !gglm.EqF32(q.Y(), ans.Y()) || !gglm.EqF32(q.Z(), ans.Z()) || !gglm.EqF32(q.W(), ans.W()) {
t.Errorf("Got: %v; Expected: %v", q.String(), ans.String())
}
q = gglm.NewQuatEulerXYZ(180*gglm.Deg2Rad, 180*gglm.Deg2Rad, 180*gglm.Deg2Rad)
q = gglm.NewQuatEuler(180*gglm.Deg2Rad, 180*gglm.Deg2Rad, 180*gglm.Deg2Rad)
if !gglm.EqF32(q.X(), ans.X()) || !gglm.EqF32(q.Y(), ans.Y()) || !gglm.EqF32(q.Z(), ans.Z()) || !gglm.EqF32(q.W(), ans.W()) {
t.Errorf("Got: %v; Expected: %v", q.String(), ans.String())
@ -24,8 +26,9 @@ func TestNewQuatEuler(t *testing.T) {
func TestNewQuatAngleAxis(t *testing.T) {
q := gglm.NewQuatAngleAxis(180*gglm.Deg2Rad, gglm.NewVec3(0, 1, 0))
ans := &gglm.Quat{Vec4: *gglm.NewVec4(0, 1, 0, 0)}
rotAxis := gglm.NewVec3(0, 1, 0)
q := gglm.NewQuatAngleAxisVec(180*gglm.Deg2Rad, &rotAxis)
ans := &gglm.Quat{Vec4: gglm.NewVec4(0, 1, 0, 0)}
if !gglm.EqF32(q.X(), ans.X()) || !gglm.EqF32(q.Y(), ans.Y()) || !gglm.EqF32(q.Z(), ans.Z()) || !gglm.EqF32(q.W(), ans.W()) {
t.Errorf("Got: %v; Expected: %v", q.String(), ans.String())
@ -34,21 +37,27 @@ func TestNewQuatAngleAxis(t *testing.T) {
func TestQuatAngle(t *testing.T) {
a := gglm.NewQuatAngleAxis(180*gglm.Deg2Rad, gglm.NewVec3(0, 1, 0)).Angle()
rotAxis := gglm.NewVec3(0, 1, 0)
quat := gglm.NewQuatAngleAxisVec(180*gglm.Deg2Rad, &rotAxis)
a := quat.Angle()
var ans float32 = 180.0 * gglm.Deg2Rad
if !gglm.EqF32(a, ans) {
t.Errorf("Got: %v; Expected: %v", a, ans)
}
a = gglm.NewQuatAngleAxis(90*gglm.Deg2Rad, gglm.NewVec3(1, 1, 0).Normalize()).Angle()
rotAxis = gglm.NewVec3(1, 1, 0)
quat = gglm.NewQuatAngleAxisVec(90*gglm.Deg2Rad, rotAxis.Normalize())
a = quat.Angle()
ans = 90 * gglm.Deg2Rad
if !gglm.EqF32(a, ans) {
t.Errorf("Got: %v; Expected: %v", a, ans)
}
a = gglm.NewQuatAngleAxis(125*gglm.Deg2Rad, gglm.NewVec3(1, 1, 0).Normalize()).Angle()
rotAxis = gglm.NewVec3(1, 1, 0)
quat = gglm.NewQuatAngleAxisVec(125*gglm.Deg2Rad, rotAxis.Normalize())
a = quat.Angle()
ans = 125 * gglm.Deg2Rad
if !gglm.EqF32(a, ans) {
@ -58,22 +67,30 @@ func TestQuatAngle(t *testing.T) {
func TestQuatAxis(t *testing.T) {
a := gglm.NewQuatAngleAxis(1, gglm.NewVec3(0, 1, 0)).Axis()
rotAxis := gglm.NewVec3(0, 1, 0)
quat := gglm.NewQuatAngleAxisVec(1, &rotAxis)
a := quat.Axis()
ans := gglm.NewVec3(0, 1, 0)
if !gglm.EqF32(a.X(), ans.X()) || !gglm.EqF32(a.Y(), ans.Y()) || !gglm.EqF32(a.Z(), ans.Z()) {
t.Errorf("Got: %v; Expected: %v", a.String(), ans.String())
}
a = gglm.NewQuatAngleAxis(1, gglm.NewVec3(1, 1, 0).Normalize()).Axis()
ans = gglm.NewVec3(1, 1, 0).Normalize()
rotAxis = gglm.NewVec3(1, 1, 0)
quat = gglm.NewQuatAngleAxisVec(1, rotAxis.Normalize())
a = quat.Axis()
ans = gglm.NewVec3(1, 1, 0)
ans.Normalize()
if !gglm.EqF32(a.X(), ans.X()) || !gglm.EqF32(a.Y(), ans.Y()) || !gglm.EqF32(a.Z(), ans.Z()) {
t.Errorf("Got: %v; Expected: %v", a.String(), ans.String())
}
a = gglm.NewQuatAngleAxis(1, gglm.NewVec3(67, 46, 32).Normalize()).Axis()
ans = gglm.NewVec3(67, 46, 32).Normalize()
rotAxis = gglm.NewVec3(67, 46, 32)
quat = gglm.NewQuatAngleAxisVec(1, rotAxis.Normalize())
a = quat.Axis()
ans = gglm.NewVec3(67, 46, 32)
ans.Normalize()
if !gglm.EqF32(a.X(), ans.X()) || !gglm.EqF32(a.Y(), ans.Y()) || !gglm.EqF32(a.Z(), ans.Z()) {
t.Errorf("Got: %v; Expected: %v", a.String(), ans.String())

View File

@ -1,6 +1,10 @@
package gglm
import "math"
import (
"math"
"golang.org/x/exp/constraints"
)
// EqF32 true if abs(f1-f2) <= F32Epsilon
func EqF32(f1, f2 float32) bool {
@ -52,3 +56,21 @@ func Abs32(x float32) float32 {
func Sqrt32(x float32) float32 {
return float32(math.Sqrt(float64(x)))
}
// Clamp returns:
//
// min if x<min
// max if x>max
// x if x>=min && x<=max
func Clamp[T constraints.Ordered](x, min, max T) T {
if x < min {
return min
}
if x > max {
return max
}
return x
}

64
gglm/scalar_test.go Executable file
View File

@ -0,0 +1,64 @@
package gglm_test
import (
"testing"
"github.com/bloeys/gglm/gglm"
)
func TestClamp(t *testing.T) {
x := 5
ans := 5
if gglm.Clamp(x, 0, 10) != ans {
t.Errorf("Got: %v; Expected: %v", x, ans)
}
x = 10
ans = 10
if gglm.Clamp(x, 0, 10) != ans {
t.Errorf("Got: %v; Expected: %v", x, ans)
}
x = 20
ans = 10
if gglm.Clamp(x, 0, 10) != ans {
t.Errorf("Got: %v; Expected: %v", x, ans)
}
x = -10
ans = 0
if gglm.Clamp(x, 0, 10) != ans {
t.Errorf("Got: %v; Expected: %v", x, ans)
}
xf := 1.5
ansf := 1.5
if gglm.Clamp(xf, 0, 10) != ansf {
t.Errorf("Got: %v; Expected: %v", xf, ansf)
}
xf = 15
ansf = 10
if gglm.Clamp(xf, 0, 10) != ansf {
t.Errorf("Got: %v; Expected: %v", xf, ansf)
}
xf = -1.5
ansf = 0
if gglm.Clamp(xf, 0, 10) != ansf {
t.Errorf("Got: %v; Expected: %v", xf, ansf)
}
xf = 2
ansf = 1.5
if gglm.Clamp(xf, 0.5, 1.5) != ansf {
t.Errorf("Got: %v; Expected: %v", xf, ansf)
}
xf = 1.2
ansf = 1.2
if gglm.Clamp(xf, 0.5, 1.5) != ansf {
t.Errorf("Got: %v; Expected: %v", xf, ansf)
}
}

View File

@ -13,24 +13,36 @@ type TrMat struct {
Mat4
}
// TranslateVec adds v to the translation components of the transformation matrix
func (t *TrMat) TranslateVec(v *Vec3) *TrMat {
return t.Translate(v.X(), v.Y(), v.Z())
}
// Translate adds v to the translation components of the transformation matrix
func (t *TrMat) Translate(v *Vec3) *TrMat {
t.Data[3][0] += v.Data[0]
t.Data[3][1] += v.Data[1]
t.Data[3][2] += v.Data[2]
func (t *TrMat) Translate(x, y, z float32) *TrMat {
t.Data[3][0] += x
t.Data[3][1] += y
t.Data[3][2] += z
return t
}
// ScaleVec multiplies the scale components of the transformation matrix by v
func (t *TrMat) ScaleVec(v *Vec3) *TrMat {
return t.Scale(v.X(), v.Y(), v.Z())
}
// Scale multiplies the scale components of the transformation matrix by v
func (t *TrMat) Scale(v *Vec3) *TrMat {
t.Data[0][0] *= v.Data[0]
t.Data[1][1] *= v.Data[1]
t.Data[2][2] *= v.Data[2]
func (t *TrMat) Scale(x, y, z float32) *TrMat {
t.Data[0][0] *= z
t.Data[1][1] *= y
t.Data[2][2] *= z
return t
}
// Rotate takes a *normalized* axis and angles in radians to rotate around the given axis
func (t *TrMat) Rotate(rads float32, axis *Vec3) *TrMat {
// RotateVec takes a *normalized* axis and angles in radians to rotate around the given axis
func (t *TrMat) RotateVec(rads float32, axis *Vec3) *TrMat {
// Manually inlined
s := Sin32(rads)
c := Cos32(rads)
@ -51,20 +63,85 @@ func (t *TrMat) Rotate(rads float32, axis *Vec3) *TrMat {
rotate.Data[2][1] = temp.Data[2]*axis.Data[1] - s*axis.Data[0]
rotate.Data[2][2] = c + temp.Data[2]*axis.Data[2]
result := &Mat4{}
result.Data[0] = t.Col(0).Scale(rotate.Data[0][0]).
Add(t.Col(1).Scale(rotate.Data[0][1])).
Add(t.Col(2).Scale(rotate.Data[0][2])).
result := Mat4{}
col0 := t.Col(0)
col1 := t.Col(1)
col2 := t.Col(2)
result.Data[0] = col0.Scale(rotate.Data[0][0]).
Add(col1.Scale(rotate.Data[0][1])).
Add(col2.Scale(rotate.Data[0][2])).
Data
result.Data[1] = t.Col(0).Scale(rotate.Data[1][0]).
Add(t.Col(1).Scale(rotate.Data[1][1])).
Add(t.Col(2).Scale(rotate.Data[1][2])).
col0 = t.Col(0)
col1 = t.Col(1)
col2 = t.Col(2)
result.Data[1] = col0.Scale(rotate.Data[1][0]).
Add(col1.Scale(rotate.Data[1][1])).
Add(col2.Scale(rotate.Data[1][2])).
Data
result.Data[2] = t.Col(0).Scale(rotate.Data[2][0]).
Add(t.Col(1).Scale(rotate.Data[2][1])).
Add(t.Col(2).Scale(rotate.Data[2][2])).
col0 = t.Col(0)
col1 = t.Col(1)
col2 = t.Col(2)
result.Data[2] = col0.Scale(rotate.Data[2][0]).
Add(col1.Scale(rotate.Data[2][1])).
Add(col2.Scale(rotate.Data[2][2])).
Data
t.Data[0] = result.Data[0]
t.Data[1] = result.Data[1]
t.Data[2] = result.Data[2]
return t
}
// Rotate takes a *normalized* axis and angles in radians to rotate around the given axis
func (t *TrMat) Rotate(rads float32, axisX, axisY, axisZ float32) *TrMat {
s := Sin32(rads)
c := Cos32(rads)
axis := NewVec3(axisX, axisY, axisZ)
axis.Normalize()
temp := axis.Clone().Scale(1 - c)
rotate := TrMat{}
rotate.Data[0][0] = c + temp.Data[0]*axis.Data[0]
rotate.Data[0][1] = temp.Data[0]*axis.Data[1] + s*axis.Data[2]
rotate.Data[0][2] = temp.Data[0]*axis.Data[2] - s*axis.Data[1]
rotate.Data[1][0] = temp.Data[1]*axis.Data[0] - s*axis.Data[2]
rotate.Data[1][1] = c + temp.Data[1]*axis.Data[1]
rotate.Data[1][2] = temp.Data[1]*axis.Data[2] + s*axis.Data[0]
rotate.Data[2][0] = temp.Data[2]*axis.Data[0] + s*axis.Data[1]
rotate.Data[2][1] = temp.Data[2]*axis.Data[1] - s*axis.Data[0]
rotate.Data[2][2] = c + temp.Data[2]*axis.Data[2]
result := Mat4{}
col0 := t.Col(0)
col1 := t.Col(1)
col2 := t.Col(2)
result.Data[0] = col0.Scale(rotate.Data[0][0]).
Add(col1.Scale(rotate.Data[0][1])).
Add(col2.Scale(rotate.Data[0][2])).
Data
col0 = t.Col(0)
col1 = t.Col(1)
col2 = t.Col(2)
result.Data[1] = col0.Scale(rotate.Data[1][0]).
Add(col1.Scale(rotate.Data[1][1])).
Add(col2.Scale(rotate.Data[1][2])).
Data
col0 = t.Col(0)
col1 = t.Col(1)
col2 = t.Col(2)
result.Data[2] = col0.Scale(rotate.Data[2][0]).
Add(col1.Scale(rotate.Data[2][1])).
Add(col2.Scale(rotate.Data[2][2])).
Data
t.Data[0] = result.Data[0]
@ -88,8 +165,8 @@ func (t *TrMat) Clone() *TrMat {
}
}
func NewTranslationMat(v *Vec3) *TrMat {
return &TrMat{
func NewTranslationMatVec(v *Vec3) TrMat {
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{1, 0, 0, 0},
@ -101,8 +178,21 @@ func NewTranslationMat(v *Vec3) *TrMat {
}
}
func NewScaleMat(v *Vec3) *TrMat {
return &TrMat{
func NewTranslationMat(posX, posY, posZ float32) TrMat {
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{posX, posY, posZ, 1},
},
},
}
}
func NewScaleMatVec(v *Vec3) TrMat {
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{v.Data[0], 0, 0, 0},
@ -114,7 +204,20 @@ func NewScaleMat(v *Vec3) *TrMat {
}
}
func NewRotMat(q *Quat) *TrMat {
func NewScaleMat(scaleX, scaleY, scaleZ float32) TrMat {
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{scaleX, 0, 0, 0},
{0, scaleY, 0, 0},
{0, 0, scaleZ, 0},
{0, 0, 0, 1},
},
},
}
}
func NewRotMatQuat(q *Quat) TrMat {
//Based on: https://www.weizmann.ac.il/sci-tea/benari/sites/sci-tea.benari/files/uploads/softwareAndLearningMaterials/quaternion-tutorial-2-0-1.pdf
//Note: in the reference p0,p1,p2,p3 == w,x,y,z
@ -133,7 +236,7 @@ func NewRotMat(q *Quat) *TrMat {
zw := q.Data[2] * q.Data[3]
return &TrMat{
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{2*(ww+xx) - 1, 2 * (zw + xy), 2 * (xz - yw), 0},
@ -147,19 +250,21 @@ func NewRotMat(q *Quat) *TrMat {
// LookAtRH does a right-handed coordinate system lookAt (RH is the default for OpenGL).
// Can be used to create the view matrix
func LookAtRH(pos, targetPos, worldUp *Vec3) *TrMat {
func LookAtRH(pos, targetPos, worldUp *Vec3) TrMat {
forward := SubVec3(targetPos, pos).Normalize()
right := Cross(forward, worldUp).Normalize()
up := Cross(right, forward)
forward := SubVec3(targetPos, pos)
forward.Normalize()
right := Cross(&forward, worldUp)
right.Normalize()
up := Cross(&right, &forward)
return &TrMat{
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{right.Data[0], up.Data[0], -forward.Data[0], 0},
{right.Data[1], up.Data[1], -forward.Data[1], 0},
{right.Data[2], up.Data[2], -forward.Data[2], 0},
{-DotVec3(pos, right), -DotVec3(pos, up), DotVec3(pos, forward), 1},
{-DotVec3(pos, &right), -DotVec3(pos, &up), DotVec3(pos, &forward), 1},
},
},
}
@ -167,28 +272,30 @@ func LookAtRH(pos, targetPos, worldUp *Vec3) *TrMat {
// LookAtLH does a left-handed coordinate system lookAt.
// Can be used to create the view matrix
func LookAtLH(pos, targetPos, worldUp *Vec3) *TrMat {
func LookAtLH(pos, targetPos, worldUp *Vec3) TrMat {
forward := SubVec3(targetPos, pos).Normalize()
right := Cross(worldUp, forward).Normalize()
up := Cross(forward, right)
forward := SubVec3(targetPos, pos)
forward.Normalize()
right := Cross(worldUp, &forward)
right.Normalize()
up := Cross(&forward, &right)
return &TrMat{
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{right.Data[0], up.Data[0], forward.Data[0], 0},
{right.Data[1], up.Data[1], forward.Data[1], 0},
{right.Data[2], up.Data[2], forward.Data[2], 0},
{-DotVec3(pos, right), -DotVec3(pos, up), -DotVec3(pos, forward), 1},
{-DotVec3(pos, &right), -DotVec3(pos, &up), -DotVec3(pos, &forward), 1},
},
},
}
}
// Perspective creates a perspective projection matrix
func Perspective(fov, aspectRatio, nearClip, farClip float32) *Mat4 {
func Perspective(fov, aspectRatio, nearClip, farClip float32) Mat4 {
halfFovTan := float32(math.Tan(float64(fov * 0.5)))
return &Mat4{
return Mat4{
Data: [4][4]float32{
{1 / (aspectRatio * halfFovTan), 0, 0, 0},
{0, 1 / halfFovTan, 0, 0},
@ -199,8 +306,8 @@ func Perspective(fov, aspectRatio, nearClip, farClip float32) *Mat4 {
}
// Perspective creates an orthographic projection matrix
func Ortho(left, right, top, bottom, nearClip, farClip float32) *TrMat {
return &TrMat{
func Ortho(left, right, top, bottom, nearClip, farClip float32) TrMat {
return TrMat{
Mat4: Mat4{
Data: [4][4]float32{
{2 / (right - left), 0, 0, 0},
@ -212,8 +319,26 @@ func Ortho(left, right, top, bottom, nearClip, farClip float32) *TrMat {
}
}
func NewTrMatId() *TrMat {
return &TrMat{
Mat4: *NewMat4Id(),
func NewTrMatId() TrMat {
return TrMat{
Mat4: NewMat4Id(),
}
}
func NewTrMatWithPos(x, y, z float32) TrMat {
tr := TrMat{
Mat4: NewMat4Id(),
}
tr.Translate(x, y, z)
return tr
}
func NewTrMatWithPosVec(pos *Vec3) TrMat {
tr := TrMat{
Mat4: NewMat4Id(),
}
tr.TranslateVec(pos)
return tr
}

View File

@ -27,7 +27,7 @@ func TestNewTrMatId(t *testing.T) {
func TestNewTranslationMat(t *testing.T) {
m := gglm.NewTranslationMat(gglm.NewVec3(1, 2, 3))
m := gglm.NewTranslationMat(1, 2, 3)
ans := &gglm.TrMat{
Mat4: gglm.Mat4{
Data: [4][4]float32{
@ -46,7 +46,7 @@ func TestNewTranslationMat(t *testing.T) {
func TestNewScaleMat(t *testing.T) {
m := gglm.NewScaleMat(gglm.NewVec3(1, 2, 3))
m := gglm.NewScaleMat(1, 2, 3)
ans := &gglm.TrMat{
Mat4: gglm.Mat4{
Data: [4][4]float32{
@ -65,7 +65,8 @@ func TestNewScaleMat(t *testing.T) {
func TestNewRotMat(t *testing.T) {
m := gglm.NewRotMat(gglm.NewQuatId())
quat := gglm.NewQuatId()
m := gglm.NewRotMatQuat(&quat)
ans := &gglm.TrMat{
Mat4: gglm.Mat4{
Data: [4][4]float32{

View File

@ -93,6 +93,20 @@ func (v *Vec2) Scale(x float32) *Vec2 {
return v
}
// ScaleVec v *= v2 (element wise multiplication)
func (v *Vec2) ScaleVec(v2 *Vec2) *Vec2 {
v.Data[0] *= v2.X()
v.Data[1] *= v2.Y()
return v
}
// ScaleArr v *= arr (element wise multiplication)
func (v *Vec2) ScaleArr(arr [2]float32) *Vec2 {
v.Data[0] *= arr[0]
v.Data[1] *= arr[1]
return v
}
// Add v += v2
func (v *Vec2) Add(v2 *Vec2) *Vec2 {
v.Data[0] += v2.X()
@ -137,8 +151,8 @@ func (v *Vec2) Clone() *Vec2 {
}
// AddVec2 v3 = v1 + v2
func AddVec2(v1, v2 *Vec2) *Vec2 {
return &Vec2{
func AddVec2(v1, v2 *Vec2) Vec2 {
return Vec2{
Data: [2]float32{
v1.X() + v2.X(),
v1.Y() + v2.Y(),
@ -147,8 +161,8 @@ func AddVec2(v1, v2 *Vec2) *Vec2 {
}
// SubVec2 v3 = v1 - v2
func SubVec2(v1, v2 *Vec2) *Vec2 {
return &Vec2{
func SubVec2(v1, v2 *Vec2) Vec2 {
return Vec2{
Data: [2]float32{
v1.X() - v2.X(),
v1.Y() - v2.Y(),
@ -156,11 +170,17 @@ func SubVec2(v1, v2 *Vec2) *Vec2 {
}
}
func NewVec2(x, y float32) *Vec2 {
return &Vec2{
func NewVec2(x, y float32) Vec2 {
return Vec2{
[2]float32{
x,
y,
},
}
}
func NewVec2Arr(arr [2]float32) Vec2 {
return Vec2{
Data: arr,
}
}

View File

@ -140,6 +140,22 @@ func (v *Vec3) Scale(x float32) *Vec3 {
return v
}
// ScaleVec v *= v2 (element wise multiplication)
func (v *Vec3) ScaleVec(v2 *Vec3) *Vec3 {
v.Data[0] *= v2.X()
v.Data[1] *= v2.Y()
v.Data[2] *= v2.Z()
return v
}
// ScaleArr v *= arr (element wise multiplication)
func (v *Vec3) ScaleArr(arr [3]float32) *Vec3 {
v.Data[0] *= arr[0]
v.Data[1] *= arr[1]
v.Data[2] *= arr[2]
return v
}
func (v *Vec3) Add(v2 *Vec3) *Vec3 {
v.Data[0] += v2.X()
@ -197,11 +213,12 @@ func (v *Vec3) RotByQuat(q *Quat) *Vec3 {
// vprime = t1 + t2 + t3
u := NewVec3(q.X(), q.Y(), q.Z())
t1 := u.Clone().Scale(2 * DotVec3(u, v))
t2 := v.Clone().Scale(q.W()*q.W() - DotVec3(u, u))
t3 := Cross(u, v).Scale(2 * q.W())
t1 := u.Clone().Scale(2 * DotVec3(&u, v))
t2 := v.Clone().Scale(q.W()*q.W() - DotVec3(&u, &u))
t3 := Cross(&u, v)
t3.Scale(2 * q.W())
v.Data = t1.Add(t2).Add(t3).Data
v.Data = t1.Add(t2).Add(&t3).Data
return v
}
@ -221,8 +238,8 @@ func (v *Vec3) AsRad() *Vec3 {
}
// AddVec3 v3 = v1 + v2
func AddVec3(v1, v2 *Vec3) *Vec3 {
return &Vec3{
func AddVec3(v1, v2 *Vec3) Vec3 {
return Vec3{
Data: [3]float32{
v1.X() + v2.X(),
v1.Y() + v2.Y(),
@ -232,8 +249,8 @@ func AddVec3(v1, v2 *Vec3) *Vec3 {
}
// SubVec3 v3 = v1 - v2
func SubVec3(v1, v2 *Vec3) *Vec3 {
return &Vec3{
func SubVec3(v1, v2 *Vec3) Vec3 {
return Vec3{
Data: [3]float32{
v1.X() - v2.X(),
v1.Y() - v2.Y(),
@ -242,8 +259,8 @@ func SubVec3(v1, v2 *Vec3) *Vec3 {
}
}
func NewVec3(x, y, z float32) *Vec3 {
return &Vec3{
func NewVec3(x, y, z float32) Vec3 {
return Vec3{
[3]float32{
x,
y,
@ -251,3 +268,9 @@ func NewVec3(x, y, z float32) *Vec3 {
},
}
}
func NewVec3Arr(arr [3]float32) Vec3 {
return Vec3{
Data: arr,
}
}

View File

@ -193,6 +193,24 @@ func (v *Vec4) Scale(x float32) *Vec4 {
return v
}
// ScaleVec v *= v2 (element wise multiplication)
func (v *Vec4) ScaleVec(v2 *Vec4) *Vec4 {
v.Data[0] *= v2.X()
v.Data[1] *= v2.Y()
v.Data[2] *= v2.Z()
v.Data[3] *= v2.W()
return v
}
// ScaleArr v *= arr (element wise multiplication)
func (v *Vec4) ScaleArr(arr [4]float32) *Vec4 {
v.Data[0] *= arr[0]
v.Data[1] *= arr[1]
v.Data[2] *= arr[2]
v.Data[3] *= arr[3]
return v
}
func (v *Vec4) Add(v2 *Vec4) *Vec4 {
v.Data[0] += v2.X()
v.Data[1] += v2.Y()
@ -244,8 +262,8 @@ func (v *Vec4) Clone() *Vec4 {
}
// AddVec4 v3 = v1 + v2
func AddVec4(v1, v2 *Vec4) *Vec4 {
return &Vec4{
func AddVec4(v1, v2 *Vec4) Vec4 {
return Vec4{
Data: [4]float32{
v1.X() + v2.X(),
v1.Y() + v2.Y(),
@ -256,8 +274,8 @@ func AddVec4(v1, v2 *Vec4) *Vec4 {
}
// SubVec4 v3 = v1 - v2
func SubVec4(v1, v2 *Vec4) *Vec4 {
return &Vec4{
func SubVec4(v1, v2 *Vec4) Vec4 {
return Vec4{
Data: [4]float32{
v1.X() - v2.X(),
v1.Y() - v2.Y(),
@ -267,8 +285,8 @@ func SubVec4(v1, v2 *Vec4) *Vec4 {
}
}
func NewVec4(x, y, z, w float32) *Vec4 {
return &Vec4{
func NewVec4(x, y, z, w float32) Vec4 {
return Vec4{
[4]float32{
x,
y,
@ -277,3 +295,9 @@ func NewVec4(x, y, z, w float32) *Vec4 {
},
}
}
func NewVec4Arr(arr [4]float32) Vec4 {
return Vec4{
Data: arr,
}
}

View File

@ -107,7 +107,7 @@ func TestVecSwizzleSet(t *testing.T) {
v2.SetX(1)
v2.SetY(2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
@ -116,7 +116,7 @@ func TestVecSwizzleSet(t *testing.T) {
v2.SetR(11)
v2.SetG(22)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
@ -124,7 +124,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans2 = gglm.NewVec2(1, 2)
v2.SetXY(1, 2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
@ -132,7 +132,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans2 = gglm.NewVec2(11, 22)
v2.SetRG(11, 22)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
@ -144,7 +144,7 @@ func TestVecSwizzleSet(t *testing.T) {
v3.SetY(2)
v3.SetZ(3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -155,7 +155,7 @@ func TestVecSwizzleSet(t *testing.T) {
v3.SetG(22)
v3.SetB(33)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -163,7 +163,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans3 = gglm.NewVec3(1, 2, 1)
v3.SetXY(1, 2)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -171,7 +171,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans3 = gglm.NewVec3(1, 2, 1)
v3.SetRG(1, 2)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -179,7 +179,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans3 = gglm.NewVec3(1, 2, 3)
v3.SetXYZ(1, 2, 3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -187,7 +187,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans3 = gglm.NewVec3(1, 2, 3)
v3.SetRGB(1, 2, 3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -195,21 +195,23 @@ func TestVecSwizzleSet(t *testing.T) {
v3 = gglm.NewVec3(1, 0, 0)
v32 := gglm.NewVec3(1, 0, 0)
angleV3 := gglm.AngleVec3(v3, v32) * gglm.Rad2Deg
angleV3 := gglm.AngleVec3(&v3, &v32) * gglm.Rad2Deg
if angleV3 != 0 {
t.Errorf("Got: %v; Expected: %v", v3.String(), 0)
}
v32.SetXY(0, 1)
angleV3 = gglm.AngleVec3(v3, v32) * gglm.Rad2Deg
angleV3 = gglm.AngleVec3(&v3, &v32) * gglm.Rad2Deg
if angleV3 != 90 {
t.Errorf("Got: %v; Expected: %v", v3.String(), 0)
}
// Test rot by quat
v32.SetXY(1, 0)
v32.RotByQuat(gglm.NewQuatAngleAxis(90*gglm.Deg2Rad, gglm.NewVec3(0, 1, 0)))
angleV3 = gglm.AngleVec3(v3, v32) * gglm.Rad2Deg
rotAxis := gglm.NewVec3(0, 1, 0)
quat := gglm.NewQuatAngleAxisVec(90*gglm.Deg2Rad, &rotAxis)
v32.RotByQuat(&quat)
angleV3 = gglm.AngleVec3(&v3, &v32) * gglm.Rad2Deg
if angleV3 != 90 {
t.Errorf("Got: %v; Expected: %v", v3.String(), 0)
}
@ -223,7 +225,7 @@ func TestVecSwizzleSet(t *testing.T) {
v4.SetZ(3)
v4.SetW(4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -235,7 +237,7 @@ func TestVecSwizzleSet(t *testing.T) {
v4.SetB(33)
v4.SetA(44)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -243,7 +245,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 1, 1)
v4.SetXY(1, 2)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -251,7 +253,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 1, 1)
v4.SetRG(1, 2)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -259,7 +261,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 3, 1)
v4.SetXYZ(1, 2, 3)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -267,7 +269,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 3, 1)
v4.SetRGB(1, 2, 3)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -275,7 +277,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 3, 4)
v4.SetXYZW(1, 2, 3, 4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -283,7 +285,7 @@ func TestVecSwizzleSet(t *testing.T) {
ans4 = gglm.NewVec4(1, 2, 3, 4)
v4.SetRGBA(1, 2, 3, 4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
}
@ -295,26 +297,26 @@ func TestVecSwizzleAdd(t *testing.T) {
ans2 := gglm.NewVec2(2, 3)
v2.AddX(1)
v2.AddY(2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
v2 = gglm.NewVec2(1, 1)
v2.AddR(1)
v2.AddG(2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
v2 = gglm.NewVec2(1, 1)
v2.AddXY(1, 2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
v2 = gglm.NewVec2(1, 1)
v2.AddRG(1, 2)
if !v2.Eq(ans2) {
if !v2.Eq(&ans2) {
t.Errorf("Got: %v; Expected: %v", v2.String(), ans2.String())
}
@ -324,7 +326,7 @@ func TestVecSwizzleAdd(t *testing.T) {
v3.AddX(1)
v3.AddY(2)
v3.AddZ(3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -332,33 +334,33 @@ func TestVecSwizzleAdd(t *testing.T) {
v3.AddR(1)
v3.AddG(2)
v3.AddB(3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
v3 = gglm.NewVec3(1, 1, 1)
ans3 = gglm.NewVec3(2, 3, 1)
v3.AddXY(1, 2)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
v3 = gglm.NewVec3(1, 1, 1)
v3.AddRG(1, 2)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
v3 = gglm.NewVec3(1, 1, 1)
ans3 = gglm.NewVec3(2, 3, 4)
v3.AddXYZ(1, 2, 3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
v3 = gglm.NewVec3(1, 1, 1)
v3.AddRGB(1, 2, 3)
if !v3.Eq(ans3) {
if !v3.Eq(&ans3) {
t.Errorf("Got: %v; Expected: %v", v3.String(), ans3.String())
}
@ -369,7 +371,7 @@ func TestVecSwizzleAdd(t *testing.T) {
v4.AddY(2)
v4.AddZ(3)
v4.AddW(4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
@ -378,46 +380,46 @@ func TestVecSwizzleAdd(t *testing.T) {
v4.AddG(2)
v4.AddB(3)
v4.AddA(4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
ans4 = gglm.NewVec4(2, 3, 1, 1)
v4.AddXY(1, 2)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
v4.AddRG(1, 2)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
ans4 = gglm.NewVec4(2, 3, 4, 1)
v4.AddXYZ(1, 2, 3)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
v4.AddRGB(1, 2, 3)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
ans4 = gglm.NewVec4(2, 3, 4, 5)
v4.AddXYZW(1, 2, 3, 4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
v4 = gglm.NewVec4(1, 1, 1, 1)
v4.AddRGBA(1, 2, 3, 4)
if !v4.Eq(ans4) {
if !v4.Eq(&ans4) {
t.Errorf("Got: %v; Expected: %v", v4.String(), ans4.String())
}
}

4
go.mod
View File

@ -1,3 +1,5 @@
module github.com/bloeys/gglm
go 1.17
go 1.18
require golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842

2
go.sum
View File

@ -0,0 +1,2 @@
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=

47
main.go
View File

@ -53,7 +53,7 @@ func main() {
m4.Mul(m5)
println(m4.String())
println(m6.String())
println(m4.Eq(m6))
println(m4.Eq(&m6))
// Vec2
v1 := &gglm.Vec2{Data: [2]float32{1, 2}}
@ -82,7 +82,9 @@ func main() {
println(v3.Eq(v4))
println(gglm.DotVec3(v3, v4))
println(gglm.Cross(v3, v4).String())
v3v4Cross := gglm.Cross(v3, v4)
println(v3v4Cross.String())
println("V3: " + v3.String())
v3.Normalize()
@ -117,7 +119,8 @@ func main() {
}
vec2A := gglm.Vec2{Data: [2]float32{1, 2}}
println(gglm.MulMat2Vec2(&mat2A, &vec2A).String())
mat2Vec2Mul := gglm.MulMat2Vec2(&mat2A, &vec2A)
println(mat2Vec2Mul.String())
// Mat3Vec3
mat3A := gglm.Mat3{
@ -140,21 +143,25 @@ func main() {
// Quaternion
vRot := &gglm.Vec3{Data: [3]float32{60, 30, 20}}
q := gglm.NewQuatEuler(vRot.AsRad())
q := gglm.NewQuatEulerVec(vRot.AsRad())
println("\n" + vRot.AsRad().String())
println(q.String(), "\n", q.Mag())
q = gglm.NewQuatAngleAxis(60*gglm.Deg2Rad, vRot.Normalize())
q = gglm.NewQuatAngleAxisVec(60*gglm.Deg2Rad, vRot.Normalize())
println("\n" + vRot.Normalize().String())
println(q.String())
// Transform
translationMat := gglm.NewTranslationMat(&gglm.Vec3{Data: [3]float32{1, 2, 3}})
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(60, 30, 20).AsRad()))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(1, 1, 1))
translationMat := gglm.NewTranslationMatVec(&gglm.Vec3{Data: [3]float32{1, 2, 3}})
rotDegs := gglm.NewVec3(60, 30, 20)
quat := gglm.NewQuatEulerVec(rotDegs.AsRad())
rotMat := gglm.NewRotMatQuat(&quat)
scaleMat := gglm.NewScaleMat(1, 1, 1)
modelMat := gglm.NewTrMatId()
modelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
modelMat.Mul(translationMat.Mul(rotMat.Mul(&scaleMat)))
println("\n\n\n", modelMat.String())
@ -165,22 +172,28 @@ func main() {
println("\n\n", v2Orig.String(), "; ", v2Clone.String())
// Clone TrMat
trMatOrig := gglm.NewTranslationMat(gglm.NewVec3(1, 2, 3))
trMatOrig := gglm.NewTranslationMat(1, 2, 3)
trMatClone := trMatOrig.Clone()
trMatClone.Scale(gglm.NewVec3(2, 2, 2))
trMatClone.Translate(gglm.NewVec3(9, 0, 0))
trMatCloneScale := gglm.NewVec3(2, 2, 2)
trMatClone.ScaleVec(&trMatCloneScale)
trMatClone.Translate(9, 0, 0)
println("\n\n", trMatOrig.String(), "; ", trMatClone.String())
// Quat geo
q1 := gglm.NewQuatEuler(gglm.NewVec3(180, 0, 0).AsRad())
q2 := gglm.NewQuatEuler(gglm.NewVec3(0, 180, 0).AsRad())
println(gglm.AngleQuat(q1, q2) * gglm.Rad2Deg)
q1Degs := gglm.NewVec3(180*gglm.Deg2Rad, 0, 0)
q1 := gglm.NewQuatEulerVec(&q1Degs)
q2Degs := gglm.NewVec3(0, 180*gglm.Deg2Rad, 0)
q2 := gglm.NewQuatEulerVec(&q2Degs)
println(gglm.AngleQuat(&q1, &q2) * gglm.Rad2Deg)
// LookAt
camPos := gglm.NewVec3(0, 0, 3)
worldUp := gglm.NewVec3(0, 1, 0)
targetPos := gglm.NewVec3(0, 0, 0)
viewMat := gglm.LookAtRH(camPos, targetPos, worldUp)
viewMat := gglm.LookAtRH(&camPos, &targetPos, &worldUp)
println(viewMat.String())
// Mat2Col
@ -200,5 +213,5 @@ func main() {
{2, 4},
}}
println(mc2.Mul(mc).String())
println(mc2.Mul(&mc).String())
}