go.mobile/f32: add Scale, Translate and Rotate methods to Affine.

Also make Mat4 methods consistent with Affine's methods.

LGTM=crawshaw
R=crawshaw
CC=golang-codereviews
https://golang.org/cl/156000043
This commit is contained in:
Nigel Tao 2014-10-09 13:48:17 +11:00
Родитель 07f3aef267
Коммит 6d01e6fdd5
5 изменённых файлов: 233 добавлений и 37 удалений

Просмотреть файл

@ -18,6 +18,7 @@ func (m Affine) String() string {
m[1][0], m[1][1], m[1][2])
}
// Identity sets m to be the identity transform.
func (m *Affine) Identity() {
*m = Affine{
{1, 0, 0},
@ -25,6 +26,8 @@ func (m *Affine) Identity() {
}
}
// Eq reports whether each component of m is within epsilon of the same
// component in n.
func (m *Affine) Eq(n *Affine, epsilon float32) bool {
for i := range m {
for j := range m[i] {
@ -37,15 +40,15 @@ func (m *Affine) Eq(n *Affine, epsilon float32) bool {
return true
}
// Mul stores a × b in m.
func (m *Affine) Mul(a, b *Affine) {
// Mul sets m to be p × q.
func (m *Affine) Mul(p, q *Affine) {
// Store the result in local variables, in case m == a || m == b.
m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0]
m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1]
m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]
m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0]
m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1]
m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]
m00 := p[0][0]*q[0][0] + p[0][1]*q[1][0]
m01 := p[0][0]*q[0][1] + p[0][1]*q[1][1]
m02 := p[0][0]*q[0][2] + p[0][1]*q[1][2] + p[0][2]
m10 := p[1][0]*q[0][0] + p[1][1]*q[1][0]
m11 := p[1][0]*q[0][1] + p[1][1]*q[1][1]
m12 := p[1][0]*q[0][2] + p[1][1]*q[1][2] + p[1][2]
m[0][0] = m00
m[0][1] = m01
m[0][2] = m02
@ -53,3 +56,35 @@ func (m *Affine) Mul(a, b *Affine) {
m[1][1] = m11
m[1][2] = m12
}
// Scale sets m to be a scale followed by p.
// It is equivalent to m.Mul(p, &Affine{{x,0,0}, {0,y,0}}).
func (m *Affine) Scale(p *Affine, x, y float32) {
m[0][0] = p[0][0] * x
m[0][1] = p[0][1] * y
m[0][2] = p[0][2]
m[1][0] = p[1][0] * x
m[1][1] = p[1][1] * y
m[1][2] = p[1][2]
}
// Translate sets m to be a translation followed by p.
// It is equivalent to m.Mul(p, &Affine{{1,0,x}, {0,1,y}}).
func (m *Affine) Translate(p *Affine, x, y float32) {
m[0][0] = p[0][0]
m[0][1] = p[0][1]
m[0][2] = p[0][0]*x + p[0][1]*y + p[0][2]
m[1][0] = p[1][0]
m[1][1] = p[1][1]
m[1][2] = p[1][0]*x + p[1][1]*y + p[1][2]
}
// Rotate sets m to a rotation in radians followed by p.
// It is equivalent to m.Mul(p, affineRotation).
func (m *Affine) Rotate(p *Affine, radians float32) {
s, c := Sin(radians), Cos(radians)
m.Mul(p, &Affine{
{+c, +s, 0},
{-s, +c, 0},
})
}

113
f32/affine_test.go Normal file
Просмотреть файл

@ -0,0 +1,113 @@
package f32
import (
"math"
"testing"
)
var xyTests = []struct {
x, y float32
}{
{0, 0},
{1, 1},
{2, 3},
{6.5, 4.3},
}
var a = Affine{
{3, 4, 5},
{6, 7, 8},
}
func TestAffineScale(t *testing.T) {
for _, test := range xyTests {
want := a
want.Mul(&want, &Affine{{test.x, 0, 0}, {0, test.y, 0}})
got := a
got.Scale(&got, test.x, test.y)
if !got.Eq(&want, 0.01) {
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
}
}
}
func TestAffineTranslate(t *testing.T) {
for _, test := range xyTests {
want := a
want.Mul(&want, &Affine{{1, 0, test.x}, {0, 1, test.y}})
got := a
got.Translate(&got, test.x, test.y)
if !got.Eq(&want, 0.01) {
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
}
}
}
func TestAffineRotate(t *testing.T) {
want := Affine{
{-4.000, 3.000, 5.000},
{-7.000, 6.000, 8.000},
}
got := a
got.Rotate(&got, math.Pi/2)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π: got %s want %s", got, want)
}
want = a
got = a
got.Rotate(&got, 2*math.Pi)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate 2π: got %s want %s", got, want)
}
got = a
got.Rotate(&got, math.Pi)
got.Rotate(&got, math.Pi)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π then π: got %s want %s", got, want)
}
got = a
got.Rotate(&got, math.Pi/3)
got.Rotate(&got, -math.Pi/3)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π/3 then -π/3: got %s want %s", got, want)
}
}
func TestAffineScaleTranslate(t *testing.T) {
mulVec := func(m *Affine, v [2]float32) (mv [2]float32) {
mv[0] = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]
mv[1] = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]
return mv
}
v := [2]float32{1, 10}
var sThenT Affine
sThenT.Identity()
sThenT.Scale(&sThenT, 13, 17)
sThenT.Translate(&sThenT, 101, 151)
wantSTT := [2]float32{
13 * (101 + 1),
17 * (151 + 10),
}
if got := mulVec(&sThenT, v); got != wantSTT {
t.Errorf("S then T: got %v, want %v", got, wantSTT)
}
var tThenS Affine
tThenS.Identity()
tThenS.Translate(&tThenS, 101, 151)
tThenS.Scale(&tThenS, 13, 17)
wantTTS := [2]float32{
101 + (13 * 1),
151 + (17 * 10),
}
if got := mulVec(&tThenS, v); got != wantTTS {
t.Errorf("T then S: got %v, want %v", got, wantTTS)
}
}

Просмотреть файл

@ -4,13 +4,23 @@
// Package f32 implements some linear algebra and GL helpers for float32s.
//
// Types defined in this package have methods implementing common
// mathematical operations. The common form for these functions is
//
// func (dst *T) Op(lhs, rhs *T)
//
// which reads in traditional mathematical notation as
//
// dst = lhs op rhs.
//
// It is safe to use the destination address as the left-hand side,
// that is, dst *= rhs is dst.Mul(dst, rhs).
//
// WARNING
//
// The interface to this package is not stable. It will change considerably.
// Only use functions that provide package documentation. Semantics are
// non-obvious. Be prepared for the package name to change.
//
// Oh, and it's slow. Sorry.
package f32
import "math"

Просмотреть файл

@ -268,14 +268,14 @@ func TestMat4Rotate(t *testing.T) {
func TestMat4Scale(t *testing.T) {
want := &Mat4{
{0.000, 2.000, 4.000, 6.000},
{12.000, 15.000, 18.000, 21.000},
{32.000, 36.000, 40.000, 44.000},
{12.000, 13.000, 14.000, 15.000},
{0 * 2, 1 * 3, 2 * 4, 3 * 1},
{4 * 2, 5 * 3, 6 * 4, 7 * 1},
{8 * 2, 9 * 3, 10 * 4, 11 * 1},
{12 * 2, 13 * 3, 14 * 4, 15 * 1},
}
got := new(Mat4)
got.Scale(&x4, &Vec3{2, 3, 4})
got.Scale(&x4, 2, 3, 4)
if !got.Eq(want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)
@ -284,14 +284,14 @@ func TestMat4Scale(t *testing.T) {
func TestMat4Translate(t *testing.T) {
want := &Mat4{
{0.000, 1.000, 2.000, 3.000},
{4.000, 5.000, 6.000, 7.000},
{8.000, 9.000, 10.000, 11.000},
{15.200, 16.800, 18.400, 20.000},
{0, 1, 2, 0*0.1 + 1*0.2 + 2*0.3 + 3*1},
{4, 5, 6, 4*0.1 + 5*0.2 + 6*0.3 + 7*1},
{8, 9, 10, 8*0.1 + 9*0.2 + 10*0.3 + 11*1},
{12, 13, 14, 12*0.1 + 13*0.2 + 14*0.3 + 15*1},
}
got := new(Mat4)
got.Translate(&x4, &Vec3{0.1, 0.2, 0.3})
got.Translate(&x4, 0.1, 0.2, 0.3)
if !got.Eq(want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)

Просмотреть файл

@ -79,6 +79,7 @@ func (m *Mat4) Mul(a, b *Mat4) {
m[3][3] = m33
}
// Perspective sets m to be the GL perspective matrix.
func (m *Mat4) Perspective(fov Radian, aspect, near, far float32) {
t := Tan(float32(fov) / 2)
@ -89,33 +90,70 @@ func (m *Mat4) Perspective(fov Radian, aspect, near, far float32) {
m[3][2] = -2 * far * near / (far - near)
}
func (m *Mat4) Scale(src *Mat4, scale *Vec3) {
for i, s := range scale {
m[i][0] = src[i][0] * s
m[i][1] = src[i][1] * s
m[i][2] = src[i][2] * s
m[i][3] = src[i][3] * s
}
m[3] = src[3]
// Scale sets m to be a scale followed by p.
// It is equivalent to
// m.Mul(p, &Mat4{
// {x, 0, 0, 0},
// {0, y, 0, 0},
// {0, 0, z, 0},
// {0, 0, 0, 1},
// }).
func (m *Mat4) Scale(p *Mat4, x, y, z float32) {
m[0][0] = p[0][0] * x
m[0][1] = p[0][1] * y
m[0][2] = p[0][2] * z
m[0][3] = p[0][3]
m[1][0] = p[1][0] * x
m[1][1] = p[1][1] * y
m[1][2] = p[1][2] * z
m[1][3] = p[1][3]
m[2][0] = p[2][0] * x
m[2][1] = p[2][1] * y
m[2][2] = p[2][2] * z
m[2][3] = p[2][3]
m[3][0] = p[3][0] * x
m[3][1] = p[3][1] * y
m[3][2] = p[3][2] * z
m[3][3] = p[3][3]
}
func (m *Mat4) Translate(src *Mat4, v *Vec3) {
*m = *src
m[3][0] = src[0][0]*v[0] + src[1][0]*v[1] + src[2][0]*v[2] + src[3][0]
m[3][1] = src[0][1]*v[0] + src[1][1]*v[1] + src[2][1]*v[2] + src[3][1]
m[3][2] = src[0][2]*v[0] + src[1][2]*v[1] + src[2][2]*v[2] + src[3][2]
m[3][3] = src[0][3]*v[0] + src[1][3]*v[1] + src[2][3]*v[2] + src[3][3]
// Translate sets m to be a translation followed by p.
// It is equivalent to
// m.Mul(p, &Mat4{
// {1, 0, 0, x},
// {0, 1, 0, y},
// {0, 0, 1, z},
// {0, 0, 0, 1},
// }).
func (m *Mat4) Translate(p *Mat4, x, y, z float32) {
m[0][0] = p[0][0]
m[0][1] = p[0][1]
m[0][2] = p[0][2]
m[0][3] = p[0][0]*x + p[0][1]*y + p[0][2]*z + p[0][3]
m[1][0] = p[1][0]
m[1][1] = p[1][1]
m[1][2] = p[1][2]
m[1][3] = p[1][0]*x + p[1][1]*y + p[1][2]*z + p[1][3]
m[2][0] = p[2][0]
m[2][1] = p[2][1]
m[2][2] = p[2][2]
m[2][3] = p[2][0]*x + p[2][1]*y + p[2][2]*z + p[2][3]
m[3][0] = p[3][0]
m[3][1] = p[3][1]
m[3][2] = p[3][2]
m[3][3] = p[3][0]*x + p[3][1]*y + p[3][2]*z + p[3][3]
}
func (m *Mat4) Rotate(src *Mat4, angle Radian, axis *Vec3) {
// Rotate sets m to a rotation in radians around a specified axis, followed by p.
// It is equivalent to m.Mul(p, affineRotation).
func (m *Mat4) Rotate(p *Mat4, angle Radian, axis *Vec3) {
a := *axis
a.Normalize()
c, s := Cos(float32(angle)), Sin(float32(angle))
d := 1 - c
m.Mul(src, &Mat4{{
m.Mul(p, &Mat4{{
c + d*a[0]*a[1],
0 + d*a[0]*a[1] + s*a[2],
0 + d*a[0]*a[1] - s*a[1],