Add the new package graphics/matrix

This commit is contained in:
Hajime Hoshi 2013-06-21 01:47:39 +09:00
parent 773e3afa1d
commit b430d385bf
15 changed files with 253 additions and 489 deletions

View File

@ -17,6 +17,7 @@ import "C"
import (
"github.com/hajimehoshi/go.ebiten"
"github.com/hajimehoshi/go.ebiten/graphics"
"github.com/hajimehoshi/go.ebiten/graphics/matrix"
"image"
"image/color"
_ "image/png"
@ -116,16 +117,17 @@ func (game *DemoGame) Update() {
func (game *DemoGame) Draw(g graphics.GraphicsContext, offscreen graphics.TextureID) {
g.Fill(&color.RGBA{R: 128, G: 128, B: 255, A: 255})
geometryMatrix := graphics.IdentityGeometryMatrix()
geometryMatrix := matrix.IdentityGeometry()
tx, ty := float64(game.ebitenTexture.Width), float64(game.ebitenTexture.Height)
geometryMatrix = geometryMatrix.Concat(graphics.TranslateMatrix(-tx/2, -ty/2))
geometryMatrix = geometryMatrix.Concat(graphics.RotateMatrix(float64(game.x) / 60))
geometryMatrix = geometryMatrix.Concat(graphics.TranslateMatrix(tx/2, ty/2))
geometryMatrix = geometryMatrix.Concat(graphics.TranslateMatrix(100, 100))
geometryMatrix.Translate(-tx/2, -ty/2)
geometryMatrix.Rotate(float64(game.x) / 60)
geometryMatrix.Translate(tx/2, ty/2)
geometryMatrix.Translate(100, 100)
g.DrawTexture(game.ebitenTexture.ID,
0, 0, int(tx), int(ty),
geometryMatrix,
graphics.IdentityColorMatrix())
matrix.IdentityColor())
}
func main() {

View File

@ -1,112 +0,0 @@
package graphics
type affineMatrixElement float64
type AffineMatrix struct {
elements []affineMatrixElement
dimension int
}
func NewAffineMatrix(dimension int) *AffineMatrix {
if dimension < 0 {
panic("invalid dimension")
}
matrix := &AffineMatrix{}
elementsNumber := dimension * (dimension - 1)
matrix.elements = make([]affineMatrixElement, elementsNumber)
matrix.dimension = dimension
return matrix
}
func IdentityAffineMatrix(dimension int) *AffineMatrix {
if dimension < 0 {
panic("invalid dimension")
}
matrix := NewAffineMatrix(dimension)
for i := 0; i < dimension-1; i++ {
for j := 0; j < dimension; j++ {
if i == j {
matrix.elements[i*dimension+j] = 1
} else {
matrix.elements[i*dimension+j] = 0
}
}
}
return matrix
}
func (matrix *AffineMatrix) Clone() *AffineMatrix {
result := NewAffineMatrix(matrix.dimension)
copy(result.elements, matrix.elements)
return result
}
func (matrix *AffineMatrix) Element(i, j int) float64 {
dimension := matrix.dimension
if i < 0 || dimension <= i {
panic("out of range index i")
}
if j < 0 || dimension <= j {
panic("out of range index j")
}
if i == dimension-1 {
if j == dimension-1 {
return 1
}
return 0
}
return float64(matrix.elements[i*dimension+j])
}
func (matrix *AffineMatrix) SetElement(i, j int, element float64) {
dimension := matrix.dimension
if i < 0 || dimension-1 <= i {
panic("out of range index i")
}
if j < 0 || dimension <= j {
panic("out of range index j")
}
matrix.elements[i*dimension+j] = affineMatrixElement(element)
}
func (matrix *AffineMatrix) IsIdentity() bool {
dimension := matrix.dimension
for i := 0; i < dimension-1; i++ {
for j := 0; j < dimension; j++ {
element := matrix.elements[i*dimension+j]
if i == j && element != 1 {
return false
} else if i != j && element != 0 {
return false
}
}
}
return true
}
/*
* TODO: The arguments' names are strange even though they are not wrong.
*/
func (rhs *AffineMatrix) Concat(lhs *AffineMatrix) *AffineMatrix {
dimension := lhs.dimension
if dimension != rhs.dimension {
panic("diffrent-sized matrices can't be concatenated")
}
result := NewAffineMatrix(dimension)
for i := 0; i < dimension-1; i++ {
for j := 0; j < dimension; j++ {
element := affineMatrixElement(0.0)
for k := 0; k < dimension-1; k++ {
element += lhs.elements[i*dimension+k] *
rhs.elements[k*dimension+j]
}
if j == dimension-1 {
element += lhs.elements[i*dimension+dimension-1]
}
result.elements[i*dimension+j] = element
}
}
return result
}

View File

@ -1,179 +0,0 @@
package graphics_test
import (
. "."
"testing"
)
func setElements(matrix *AffineMatrix, elements [][]float64) {
dimension := len(elements) + 1
for i := 0; i < dimension-1; i++ {
for j := 0; j < dimension; j++ {
matrix.SetElement(i, j, elements[i][j])
}
}
}
func TestAffineMatrixElement(t *testing.T) {
matrix := NewAffineMatrix(4)
matrix.SetElement(0, 0, 1)
matrix.SetElement(0, 1, 2)
matrix.SetElement(0, 2, 3)
expected := [][]float64{
{1, 2, 3, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 1},
}
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
got := matrix.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
matrix.SetElement(1, 0, 4)
matrix.SetElement(1, 1, 5)
matrix.SetElement(2, 3, 6)
expected = [][]float64{
{1, 2, 3, 0},
{4, 5, 0, 0},
{0, 0, 0, 6},
{0, 0, 0, 1},
}
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
got := matrix.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
}
func TestAffineMatrixIsIdentity(t *testing.T) {
matrix := NewAffineMatrix(4)
matrix.SetElement(0, 0, 1)
matrix.SetElement(1, 1, 1)
matrix.SetElement(2, 2, 1)
got := matrix.IsIdentity()
want := true
if want != got {
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
}
matrix2 := matrix.Clone()
got = matrix2.IsIdentity()
want = true
if want != got {
t.Errorf("matrix2.IsIdentity() = %t, want %t", got, want)
}
matrix2.SetElement(0, 1, 1)
got = matrix2.IsIdentity()
want = false
if want != got {
t.Errorf("matrix2.IsIdentity() = %t, want %t", got, want)
}
}
func TestAffineMatrixConcat(t *testing.T) {
matrix1 := IdentityAffineMatrix(3)
matrix2 := IdentityAffineMatrix(3)
setElements(matrix1, [][]float64{
{2, 0, 0},
{0, 2, 0},
})
setElements(matrix2, [][]float64{
{1, 0, 1},
{0, 1, 1},
})
// TODO: 'matrix1x2' may not be a good name.
matrix1x2 := matrix1.Concat(matrix2)
expected := [][]float64{
{2, 0, 1},
{0, 2, 1},
{0, 0, 1},
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
got := matrix1x2.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix1x2.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
matrix2x1 := matrix2.Concat(matrix1)
expected = [][]float64{
{2, 0, 2},
{0, 2, 2},
{0, 0, 1},
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
got := matrix2x1.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix2x1.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
matrix3 := NewAffineMatrix(4)
matrix4 := NewAffineMatrix(4)
setElements(matrix3, [][]float64{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12},
})
setElements(matrix4, [][]float64{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24},
})
matrix3x4 := matrix3.Concat(matrix4)
expected = [][]float64{
{218, 260, 302, 360},
{278, 332, 386, 460},
{338, 404, 470, 560},
{0, 0, 0, 1}}
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
got := matrix3x4.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix3x4.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
matrix4x3 := matrix4.Concat(matrix3)
expected = [][]float64{
{110, 116, 122, 132},
{314, 332, 350, 376},
{518, 548, 578, 620},
{0, 0, 0, 1}}
for i := 0; i < 4; i++ {
for j := 0; j < 4; j++ {
got := matrix4x3.Element(i, j)
want := expected[i][j]
if want != got {
t.Errorf("matrix4x3.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
}

View File

@ -1,23 +0,0 @@
package graphics
type ColorMatrix struct {
AffineMatrix
}
const colorMatrixDimension = 5
func NewColorMatrix() *ColorMatrix {
return &ColorMatrix{*NewAffineMatrix(colorMatrixDimension)}
}
func IdentityColorMatrix() *ColorMatrix {
return &ColorMatrix{*IdentityAffineMatrix(colorMatrixDimension)}
}
func (matrix *ColorMatrix) Concat(other *ColorMatrix) *ColorMatrix {
return &ColorMatrix{*matrix.AffineMatrix.Concat(&other.AffineMatrix)}
}
func (matrix *ColorMatrix) Clone() *ColorMatrix {
return &ColorMatrix{*(matrix.AffineMatrix.Clone())}
}

View File

@ -1,92 +0,0 @@
package graphics
import (
"math"
)
type GeometryMatrix struct {
AffineMatrix
}
const geometryMatrixDimension = 3
func NewGeometryMatrix() *GeometryMatrix {
return &GeometryMatrix{*NewAffineMatrix(geometryMatrixDimension)}
}
func IdentityGeometryMatrix() *GeometryMatrix {
return &GeometryMatrix{*IdentityAffineMatrix(geometryMatrixDimension)}
}
func TranslateMatrix(tx, ty float64) *GeometryMatrix {
matrix := IdentityGeometryMatrix()
matrix.SetTx(tx)
matrix.SetTy(ty)
return matrix
}
func RotateMatrix(theta float64) *GeometryMatrix {
matrix := NewGeometryMatrix()
cos, sin := math.Cos(theta), math.Sin(theta)
matrix.SetA(cos)
matrix.SetB(-sin)
matrix.SetC(sin)
matrix.SetD(cos)
return matrix
}
func (matrix *GeometryMatrix) Concat(other *GeometryMatrix) *GeometryMatrix {
return &GeometryMatrix{*matrix.AffineMatrix.Concat(&other.AffineMatrix)}
}
func (matrix *GeometryMatrix) Clone() *GeometryMatrix {
return &GeometryMatrix{*(matrix.AffineMatrix.Clone())}
}
func (matrix *GeometryMatrix) A() float64 {
return matrix.Element(0, 0)
}
func (matrix *GeometryMatrix) B() float64 {
return matrix.Element(0, 1)
}
func (matrix *GeometryMatrix) C() float64 {
return matrix.Element(1, 0)
}
func (matrix *GeometryMatrix) D() float64 {
return matrix.Element(1, 1)
}
func (matrix *GeometryMatrix) Tx() float64 {
return matrix.Element(0, 2)
}
func (matrix *GeometryMatrix) Ty() float64 {
return matrix.Element(1, 2)
}
func (matrix *GeometryMatrix) SetA(a float64) {
matrix.SetElement(0, 0, a)
}
func (matrix *GeometryMatrix) SetB(b float64) {
matrix.SetElement(0, 1, b)
}
func (matrix *GeometryMatrix) SetC(c float64) {
matrix.SetElement(1, 0, c)
}
func (matrix *GeometryMatrix) SetD(d float64) {
matrix.SetElement(1, 1, d)
}
func (matrix *GeometryMatrix) SetTx(tx float64) {
matrix.SetElement(0, 2, tx)
}
func (matrix *GeometryMatrix) SetTy(ty float64) {
matrix.SetElement(1, 2, ty)
}

View File

@ -1,61 +0,0 @@
package graphics_test
import (
. "."
"testing"
)
func TestGeometryMatrixElements(t *testing.T) {
matrix := NewGeometryMatrix()
matrix.SetA(1)
matrix.SetB(2)
matrix.SetC(3)
matrix.SetD(4)
matrix.SetTx(5)
matrix.SetTy(6)
got := matrix.A()
want := float64(1)
if want != got {
t.Errorf("matrix.A() = %f, want %f", got, want)
}
got = matrix.B()
want = float64(2)
if want != got {
t.Errorf("matrix.B() = %f, want %f", got, want)
}
got = matrix.C()
want = float64(3)
if want != got {
t.Errorf("matrix.C() = %f, want %f", got, want)
}
got = matrix.D()
want = float64(4)
if want != got {
t.Errorf("matrix.D() = %f, want %f", got, want)
}
got = matrix.Tx()
want = float64(5)
if want != got {
t.Errorf("matrix.Tx() = %f, want %f", got, want)
}
got = matrix.Ty()
want = float64(6)
if want != got {
t.Errorf("matrix.Ty() = %f, want %f", got, want)
}
}
func TestGeometryIdentity(t *testing.T) {
matrix := IdentityGeometryMatrix()
got := matrix.IsIdentity()
want := true
if want != got {
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
}
}

View File

@ -1,6 +1,7 @@
package graphics
import (
"github.com/hajimehoshi/go.ebiten/graphics/matrix"
"image"
"image/color"
)
@ -15,7 +16,7 @@ type GraphicsContext interface {
Fill(color color.Color)
DrawTexture(textureId TextureID,
srcX, srcY, srcWidth, srcHeight int,
geometryMatrix *GeometryMatrix, colorMatrix *ColorMatrix)
geometryMatrix matrix.Geometry, colorMatrix matrix.Color)
SetOffscreen(textureId TextureID)
}

43
graphics/matrix/affine.go Normal file
View File

@ -0,0 +1,43 @@
package matrix
type Affine interface {
Dim() int
element(i, j int) float64
setElement(i, j int, element float64)
}
func isIdentity(matrix Affine) bool {
dim := matrix.Dim()
for i := 0; i < dim-1; i++ {
for j := 0; j < dim; j++ {
element := matrix.element(i, j)
if i == j && element != 1 {
return false
} else if i != j && element != 0 {
return false
}
}
}
return true
}
func mul(lhs, rhs, result Affine) {
dim := lhs.Dim()
if dim != rhs.Dim() {
panic("diffrent-sized matrices can't be multiplied")
}
for i := 0; i < dim-1; i++ {
for j := 0; j < dim; j++ {
element := float64(0)
for k := 0; k < dim-1; k++ {
element += lhs.element(i, k) *
rhs.element(k, j)
}
if j == dim-1 {
element += lhs.element(i, j)
}
result.setElement(i, j, element)
}
}
}

View File

@ -0,0 +1,3 @@
package matrix_test

40
graphics/matrix/color.go Normal file
View File

@ -0,0 +1,40 @@
package matrix
const colorDim = 5
type Color struct {
Elements [colorDim - 1][colorDim]float64
}
func IdentityColor() Color {
return Color{
[colorDim - 1][colorDim]float64{
{1, 0, 0, 0, 0},
{0, 1, 0, 0, 0},
{0, 0, 1, 0, 0},
{0, 0, 0, 1, 0},
},
}
}
func (matrix *Color) Dim() int {
return colorDim
}
func (matrix *Color) Concat(other Color) {
result := Color{}
mul(&other, matrix, &result)
*matrix = result
}
func (matrix *Color) IsIdentity() bool {
return isIdentity(matrix)
}
func (matrix *Color) element(i, j int) float64 {
return matrix.Elements[i][j]
}
func (matrix *Color) setElement(i, j int, element float64) {
matrix.Elements[i][j] = element
}

View File

@ -0,0 +1,15 @@
package matrix_test
import (
. "."
"testing"
)
func TestColorIdentity(t *testing.T) {
matrix := IdentityColor()
got := matrix.IsIdentity()
want := true
if want != got {
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
}
}

View File

@ -0,0 +1,59 @@
package matrix
import (
"math"
)
const geometryDim = 3
type Geometry struct {
Elements [geometryDim - 1][geometryDim]float64
}
func IdentityGeometry() Geometry {
return Geometry{
[geometryDim - 1][geometryDim]float64{
{1, 0, 0},
{0, 1, 0},
},
}
}
func (matrix *Geometry) Dim() int {
return geometryDim
}
func (matrix *Geometry) Concat(other Geometry) {
result := Geometry{}
mul(&other, matrix, &result)
*matrix = result
}
func (matrix *Geometry) IsIdentity() bool {
return isIdentity(matrix)
}
func (matrix *Geometry) element(i, j int) float64 {
return matrix.Elements[i][j]
}
func (matrix *Geometry) setElement(i, j int, element float64) {
matrix.Elements[i][j] = element
}
func (matrix *Geometry) Translate(tx, ty float64) {
matrix.Elements[0][2] += tx
matrix.Elements[1][2] += ty
}
func (matrix *Geometry) Rotate(theta float64) {
cos := math.Cos(theta)
sin := math.Sin(theta)
rotate := Geometry{
[2][3]float64{
{cos, -sin, 0},
{sin, cos, 0},
},
}
matrix.Concat(rotate)
}

View File

@ -0,0 +1,63 @@
package matrix_test
import (
. "."
"testing"
)
func TestGeometryIdentity(t *testing.T) {
matrix := IdentityGeometry()
got := matrix.IsIdentity()
want := true
if want != got {
t.Errorf("matrix.IsIdentity() = %t, want %t", got, want)
}
}
func TestGeometryConcat(t *testing.T) {
matrix1 := Geometry{}
matrix2 := Geometry{}
matrix1.Elements = [2][3]float64{
{2, 0, 0},
{0, 2, 0},
}
matrix2.Elements = [2][3]float64{
{1, 0, 1},
{0, 1, 1},
}
// TODO: 'matrix1x2' may not be a good name.
matrix1x2 := matrix1
matrix1x2.Concat(matrix2)
expected := [][]float64{
{2, 0, 1},
{0, 2, 1},
}
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
got := matrix1x2.Elements[i][j]
want := expected[i][j]
if want != got {
t.Errorf("matrix1x2.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
matrix2x1 := matrix2
matrix2x1.Concat(matrix1)
expected = [][]float64{
{2, 0, 2},
{0, 2, 2},
}
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
got := matrix2x1.Elements[i][j]
want := expected[i][j]
if want != got {
t.Errorf("matrix2x1.Element(%d, %d) = %f, want %f",
i, j, got, want)
}
}
}
}

View File

@ -7,6 +7,7 @@ package opengl
import "C"
import (
"github.com/hajimehoshi/go.ebiten/graphics"
"github.com/hajimehoshi/go.ebiten/graphics/matrix"
)
type Device struct {
@ -46,12 +47,17 @@ func (device *Device) Update() {
C.glTexParameteri(C.GL_TEXTURE_2D, C.GL_TEXTURE_MAG_FILTER, C.GL_LINEAR)
g.resetOffscreen()
g.Clear()
geometryMatrix := graphics.IdentityGeometryMatrix()
geometryMatrix.SetA(float64(g.screenScale))
geometryMatrix.SetD(float64(g.screenScale))
scale := float64(g.screenScale)
geometryMatrix := matrix.Geometry{
[2][3]float64{
{scale, 0, 0},
{0, scale, 0},
},
}
g.DrawTexture(device.offscreenTexture.ID,
0, 0, device.screenWidth, device.screenHeight,
geometryMatrix, graphics.IdentityColorMatrix())
geometryMatrix, matrix.IdentityColor())
g.flush()
}

View File

@ -8,6 +8,7 @@ import "C"
import (
"fmt"
"github.com/hajimehoshi/go.ebiten/graphics"
"github.com/hajimehoshi/go.ebiten/graphics/matrix"
"image"
"image/color"
"unsafe"
@ -67,9 +68,7 @@ func (context *GraphicsContext) DrawRect(x, y, width, height int, clr color.Colo
func (context *GraphicsContext) DrawTexture(
textureID graphics.TextureID,
srcX, srcY, srcWidth, srcHeight int,
geometryMatrix *graphics.GeometryMatrix, colorMatrix *graphics.ColorMatrix) {
geometryMatrix = geometryMatrix.Clone()
colorMatrix = colorMatrix.Clone()
geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
texture := context.textures[textureID]
@ -180,7 +179,7 @@ func (context *GraphicsContext) flush() {
// This method should be called on the UI thread.
func (context *GraphicsContext) setShaderProgram(
geometryMatrix *graphics.GeometryMatrix, colorMatrix *graphics.ColorMatrix) {
geometryMatrix matrix.Geometry, colorMatrix matrix.Color) {
program := C.GLuint(0)
if colorMatrix.IsIdentity() {
program = regularShaderProgram
@ -195,12 +194,12 @@ func (context *GraphicsContext) setShaderProgram(
1, C.GL_FALSE,
(*C.GLfloat)(&context.projectionMatrix[0]))
a := float32(geometryMatrix.A())
b := float32(geometryMatrix.B())
c := float32(geometryMatrix.C())
d := float32(geometryMatrix.D())
tx := float32(geometryMatrix.Tx())
ty := float32(geometryMatrix.Ty())
a := float32(geometryMatrix.Elements[0][0])
b := float32(geometryMatrix.Elements[0][1])
c := float32(geometryMatrix.Elements[1][0])
d := float32(geometryMatrix.Elements[1][1])
tx := float32(geometryMatrix.Elements[0][2])
ty := float32(geometryMatrix.Elements[1][2])
glModelviewMatrix := [...]float32{
a, c, 0, 0,
b, d, 0, 0,
@ -220,7 +219,7 @@ func (context *GraphicsContext) setShaderProgram(
e := [4][5]float32{}
for i := 0; i < 4; i++ {
for j := 0; j < 5; j++ {
e[i][j] = float32(colorMatrix.Element(i, j))
e[i][j] = float32(colorMatrix.Elements[i][j])
}
}