diff --git a/batch.go b/batch.go index b52e129..f830be1 100644 --- a/batch.go +++ b/batch.go @@ -3,9 +3,8 @@ package pixel import ( "fmt" "image/color" - "math" - "github.com/go-gl/mathgl/mgl32" + "github.com/go-gl/mathgl/mgl64" ) // Batch is a Target that allows for efficient drawing of many objects with the same Picture (but @@ -16,7 +15,7 @@ import ( type Batch struct { cont Drawer - mat mgl32.Mat3 + mat Matrix col NRGBA } @@ -45,9 +44,9 @@ func (b *Batch) Draw(t Target) { b.cont.Draw(t) } -// SetTransform sets transforms used in the following draws onto the Batch. -func (b *Batch) SetTransform(t ...Transform) { - b.mat = transformToMat(t...) +// SetMatrix sets a Matrix that every point will be projected by. +func (b *Batch) SetMatrix(m Matrix) { + b.mat = m } // SetColorMask sets a mask color used in the following draws onto the Batch. @@ -74,10 +73,12 @@ func (b *Batch) MakeTriangles(t Triangles) TargetTriangles { // MakePicture returns a specialized copy of the provided Picture that draws onto this Batch. func (b *Batch) MakePicture(p Picture) TargetPicture { - return &batchPicture{ + bp := &batchPicture{ Picture: p, b: b, } + bp.original = bp + return bp } type batchTriangles struct { @@ -89,15 +90,17 @@ type batchTriangles struct { func (bt *batchTriangles) draw(bp *batchPicture) { for i := range *bt.trans { - transPos := bt.b.mat.Mul3x1(mgl32.Vec3{ - float32((*bt.orig)[i].Position.X()), - float32((*bt.orig)[i].Position.Y()), + transPos := mgl64.Mat3(bt.b.mat).Mul3x1(mgl64.Vec3{ + (*bt.orig)[i].Position.X(), + (*bt.orig)[i].Position.Y(), 1, }) (*bt.trans)[i].Position = V(float64(transPos.X()), float64(transPos.Y())) (*bt.trans)[i].Color = (*bt.orig)[i].Color.Mul(bt.b.col) + (*bt.trans)[i].Picture = (*bt.orig)[i].Picture + (*bt.trans)[i].Intensity = (*bt.orig)[i].Intensity if bp == nil { - (*bt.trans)[i].Picture = V(math.Inf(+1), math.Inf(+1)) + (*bt.trans)[i].Intensity = 0 } } @@ -116,15 +119,21 @@ func (bt *batchTriangles) Draw() { type batchPicture struct { Picture - b *Batch + original *batchPicture + b *Batch } func (bp *batchPicture) Slice(r Rect) Picture { return &batchPicture{ - Picture: bp.Picture.Slice(r), + Picture: bp.Picture.Slice(r), + original: bp.original, } } +func (bp *batchPicture) Original() Picture { + return bp.original +} + func (bp *batchPicture) Draw(t TargetTriangles) { bt := t.(*batchTriangles) if bp.b != bt.b { diff --git a/geometry.go b/geometry.go index c6e1c69..b2dfdb7 100644 --- a/geometry.go +++ b/geometry.go @@ -4,6 +4,8 @@ import ( "fmt" "math" "math/cmplx" + + "github.com/go-gl/mathgl/mgl64" ) // Vec is a 2D vector type. It is unusually implemented as complex128 for convenience. Since @@ -175,3 +177,63 @@ func (r Rect) Contains(u Vec) bool { min, max := r.Pos, r.Pos+r.Size return min.X() <= u.X() && u.X() <= max.X() && min.Y() <= u.Y() && u.Y() <= max.Y() } + +// Matrix is a 3x3 transformation matrix that can be used for all kinds of spacial transforms, such +// as movement, scaling and rotations. +// +// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For +// example: +// +// pixel.ZM.Move(pixel.V(100, 200)).Rotate(0, math.Pi/2) +// +// This code creates a Matrix that first moves everything by 100 units horizontaly and 200 units +// vertically and then rotates everything by 90 degrees around the origin. +type Matrix [9]float64 + +// ZM stands for Zero-Matrix which is the identity matrix. Does nothing, no transformation. +var ZM = Matrix(mgl64.Ident3()) + +// Move moves everything by the delta vector. +func (m Matrix) Move(delta Vec) Matrix { + m3 := mgl64.Mat3(m) + m3 = mgl64.Translate2D(delta.XY()).Mul3(m3) + return Matrix(m3) +} + +// ScaleXY scales everything around a given point by the scale factor in each axis respectively. +func (m Matrix) ScaleXY(around Vec, scale Vec) Matrix { + m3 := mgl64.Mat3(m) + m3 = mgl64.Translate2D((-around).XY()).Mul3(m3) + m3 = mgl64.Scale2D(scale.XY()).Mul3(m3) + m3 = mgl64.Translate2D(around.XY()).Mul3(m3) + return Matrix(m3) +} + +// Scale scales everything around a given point by the scale factor. +func (m Matrix) Scale(around Vec, scale float64) Matrix { + return m.ScaleXY(around, V(scale, scale)) +} + +// Rotate rotates everything around a given point by the given angle in radians. +func (m Matrix) Rotate(around Vec, angle float64) Matrix { + m3 := mgl64.Mat3(m) + m3 = mgl64.Translate2D((-around).XY()).Mul3(m3) + m3 = mgl64.Rotate3DZ(angle).Mul3(m3) + m3 = mgl64.Translate2D(around.XY()).Mul3(m3) + return Matrix(m3) +} + +// Project applies all transformations added to the Matrix to a vector u and returns the result. +func (m Matrix) Project(u Vec) Vec { + m3 := mgl64.Mat3(m) + proj := m3.Mul3x1(mgl64.Vec3{u.X(), u.Y(), 1}) + return V(proj.X(), proj.Y()) +} + +// Unproject does the inverse operation to Project. +func (m Matrix) Unproject(u Vec) Vec { + m3 := mgl64.Mat3(m) + inv := m3.Inv() + unproj := inv.Mul3x1(mgl64.Vec3{u.X(), u.Y(), 1}) + return V(unproj.X(), unproj.Y()) +} diff --git a/interface.go b/interface.go index d58cee7..a61c247 100644 --- a/interface.go +++ b/interface.go @@ -29,11 +29,10 @@ type Target interface { type BasicTarget interface { Target - // SetTransform sets a Transform that transforms the TrianglesPosition property of all - // Triangles. - SetTransform(...Transform) + // SetMatrix sets a Matrix that every point will be projected by. + SetMatrix(Matrix) - // SetMColorMask sets a color that will be multiplied with the TrianglesColor property of all + // SetColorMask sets a color that will be multiplied with the TrianglesColor property of all // Triangles. SetColorMask(color.Color) } diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index 16ae1b2..d0bd0d8 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -116,9 +116,11 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture { return cp } -// SetTransform sets a set of Transforms that every position in triangles will be put through. -func (c *Canvas) SetTransform(t ...pixel.Transform) { - c.mat = transformToMat(t...) +// SetMatrix sets a Matrix that every point will be projected by. +func (c *Canvas) SetMatrix(m pixel.Matrix) { + for i := range m { + c.mat[i] = float32(m[i]) + } } // SetColorMask sets a color that every color in triangles or a picture will be multiplied by. diff --git a/pixelgl/util.go b/pixelgl/util.go index 94138ee..af05410 100644 --- a/pixelgl/util.go +++ b/pixelgl/util.go @@ -4,17 +4,8 @@ import ( "math" "github.com/faiface/pixel" - "github.com/go-gl/mathgl/mgl32" ) -func transformToMat(t ...pixel.Transform) mgl32.Mat3 { - mat := mgl32.Ident3() - for i := range t { - mat = mat.Mul3(t[i].Mat()) - } - return mat -} - func discreteBounds(bounds pixel.Rect) (x, y, w, h int) { x0 := int(math.Floor(bounds.Pos.X())) y0 := int(math.Floor(bounds.Pos.Y())) diff --git a/pixelgl/window.go b/pixelgl/window.go index 5cd63ec..6d194f4 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -377,11 +377,9 @@ func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture { return w.canvas.MakePicture(p) } -// SetTransform sets a global transformation matrix for the Window. -// -// Transforms are applied right-to-left. -func (w *Window) SetTransform(t ...pixel.Transform) { - w.canvas.SetTransform(t...) +// SetMatrix sets a Matrix that every point will be projected by. +func (w *Window) SetMatrix(m pixel.Matrix) { + w.canvas.SetMatrix(m) } // SetColorMask sets a global color mask for the Window. diff --git a/transform.go b/transform.go deleted file mode 100644 index e1798a9..0000000 --- a/transform.go +++ /dev/null @@ -1,190 +0,0 @@ -package pixel - -import "github.com/go-gl/mathgl/mgl32" - -// Transform holds space transformation information. Concretely, a transformation is specified -// by position, anchor, scale and rotation. -// -// All points are first rotated around the anchor. Then they are multiplied by the scale. If -// the scale factor is 2, the object becomes 2x bigger. Finally, all points are moved, so that -// the original anchor is located precisely at the position. -// -// Create a Transform object with Position/Anchor/Rotation/... function. This sets the position -// one of it's properties. Then use methods, like Scale and Rotate to change scale, rotation and -// achor. The order in which you apply these methods is irrelevant. -// -// pixel.Position(pixel.V(100, 100)).Rotate(math.Pi / 3).Scale(1.5) -// -// Also note, that no method changes the Transform. All simply return a new, changed Transform. -type Transform struct { - pos, anc, sca Vec - rot float64 -} - -// ZT stands for Zero-Transform. This Transform is a neutral Transform, does not change anything. -var ZT = Transform{}.Scale(1) - -// Position returns a Zero-Transform with Position set to pos. -func Position(pos Vec) Transform { - return ZT.Position(pos) -} - -// Anchor returns a Zero-Transform with Anchor set to anchor. -func Anchor(anchor Vec) Transform { - return ZT.Anchor(anchor) -} - -// Scale returns a Zero-Transform with Scale set to scale. -func Scale(scale float64) Transform { - return ZT.Scale(scale) -} - -// ScaleXY returns a Zero-Transform with ScaleXY set to scale. -func ScaleXY(scale Vec) Transform { - return ZT.ScaleXY(scale) -} - -// Rotation returns a Zero-Transform with Rotation set to angle (in radians). -func Rotation(angle float64) Transform { - return ZT.Rotation(angle) -} - -// Position moves an object by the specified vector. A zero vector will end up precisely at pos. -func (t Transform) Position(pos Vec) Transform { - t.pos = pos - return t -} - -// AddPosition adds delta to the existing Position of this Transform. -func (t Transform) AddPosition(delta Vec) Transform { - t.pos += delta - return t -} - -// Anchor specifies the zero vector, point originally located at anchor will be treated as zero. -// This affects Rotation and Position. -func (t Transform) Anchor(anchor Vec) Transform { - t.anc = anchor - return t -} - -// AddAnchor adds delta to the existing Anchor of this Transform. -func (t Transform) AddAnchor(delta Vec) Transform { - t.anc += delta - return t -} - -// Scale specifies a factor by which an object will be scaled around it's Anchor. -// -// Same as: -// t.ScaleXY(pixel.V(scale, scale)). -func (t Transform) Scale(scale float64) Transform { - t.sca = V(scale, scale) - return t -} - -// MulScale multiplies the existing Scale of this Transform by factor. -// -// Same as: -// t.MulScaleXY(pixel.V(factor, factor)). -func (t Transform) MulScale(factor float64) Transform { - t.sca = t.sca.Scaled(factor) - return t -} - -// ScaleXY specifies a factor in each dimension, by which an object will be scaled around it's -// Anchor. -func (t Transform) ScaleXY(scale Vec) Transform { - t.sca = scale - return t -} - -// MulScaleXY multiplies the existing ScaleXY of this Transform by factor, component-wise. -func (t Transform) MulScaleXY(factor Vec) Transform { - t.sca = V( - t.sca.X()*factor.X(), - t.sca.Y()*factor.Y(), - ) - return t -} - -// Rotation specifies an angle by which an object will be rotated around it's Anchor. -// -// The angle is in radians. -func (t Transform) Rotation(angle float64) Transform { - t.rot = angle - return t -} - -// AddRotation adds delta to the existing Angle of this Transform. -// -// The delta is in radians. -func (t Transform) AddRotation(delta float64) Transform { - t.rot += delta - return t -} - -// GetPosition returns the Position of the Transform. -func (t Transform) GetPosition() Vec { - return t.pos -} - -// GetAnchor returns the Anchor of the Transform. -func (t Transform) GetAnchor() Vec { - return t.anc -} - -// GetScaleXY returns the ScaleXY of the Transform. -func (t Transform) GetScaleXY() Vec { - return t.sca -} - -// GetRotation returns the Rotation of the Transform. -func (t Transform) GetRotation() float64 { - return t.rot -} - -// Project transforms a vector by a transform. -func (t Transform) Project(v Vec) Vec { - mat := t.Mat() - vec := mgl32.Vec3{float32(v.X()), float32(v.Y()), 1} - pro := mat.Mul3x1(vec) - return V(float64(pro.X()), float64(pro.Y())) -} - -// Unproject does the inverse operation to Project. -func (t Transform) Unproject(v Vec) Vec { - mat := t.InvMat() - vec := mgl32.Vec3{float32(v.X()), float32(v.Y()), 1} - unp := mat.Mul3x1(vec) - return V(float64(unp.X()), float64(unp.Y())) -} - -// Mat returns a transformation matrix that satisfies previously set transform properties. -func (t Transform) Mat() mgl32.Mat3 { - mat := mgl32.Ident3() - mat = mat.Mul3(mgl32.Translate2D(float32(t.pos.X()), float32(t.pos.Y()))) - mat = mat.Mul3(mgl32.Rotate3DZ(float32(t.rot))) - mat = mat.Mul3(mgl32.Scale2D(float32(t.sca.X()), float32(t.sca.Y()))) - mat = mat.Mul3(mgl32.Translate2D(float32(-t.anc.X()), float32(-t.anc.Y()))) - return mat -} - -// InvMat returns an inverse transformation matrix to the matrix returned by Mat3 method. -func (t Transform) InvMat() mgl32.Mat3 { - mat := mgl32.Ident3() - mat = mat.Mul3(mgl32.Translate2D(float32(t.anc.X()), float32(t.anc.Y()))) - mat = mat.Mul3(mgl32.Scale2D(float32(1/t.sca.X()), float32(1/t.sca.Y()))) - mat = mat.Mul3(mgl32.Rotate3DZ(float32(-t.rot))) - mat = mat.Mul3(mgl32.Translate2D(float32(-t.pos.X()), float32(-t.pos.Y()))) - return mat -} - -// Camera is a convenience function, that returns a Transform that acts like a camera. Center is -// the position in the world coordinates, that will be projected onto the center of the screen. -// One unit in world coordinates will be projected onto zoom pixels. -// -// It is possible to apply additional rotations, scales and moves to the returned transform. -func Camera(center, zoom, screenSize Vec) Transform { - return Anchor(center).ScaleXY(2 * zoom).MulScaleXY(V(1/screenSize.X(), 1/screenSize.Y())) -} diff --git a/util.go b/util.go index ce46294..24e6199 100644 --- a/util.go +++ b/util.go @@ -1,7 +1,5 @@ package pixel -import "github.com/go-gl/mathgl/mgl32" - func clamp(x, low, high float64) float64 { if x < low { return low @@ -11,11 +9,3 @@ func clamp(x, low, high float64) float64 { } return x } - -func transformToMat(t ...Transform) mgl32.Mat3 { - mat := mgl32.Ident3() - for i := range t { - mat = mat.Mul3(t[i].Mat()) - } - return mat -}