From ecdd8462bb2422ffc32ad25cb3fb6feca546794a Mon Sep 17 00:00:00 2001
From: faiface <faiface@ksp.sk>
Date: Sun, 21 May 2017 19:25:06 +0200
Subject: [PATCH] replace complex128 Vec with a struct

---
 data.go                                       |  26 +--
 .../main.go                                   |   2 +-
 .../05_drawing_efficiently_with_batch/main.go |   2 +-
 examples/lights/main.go                       |  16 +-
 examples/platformer/main.go                   |  56 ++---
 examples/smoke/main.go                        |  15 +-
 examples/xor/main.go                          |  10 +-
 geometry.go                                   | 201 ++++++++----------
 imdraw/imdraw.go                              | 150 ++++++-------
 pixelgl/canvas.go                             |   4 +-
 pixelgl/glframe.go                            |   2 +-
 pixelgl/glpicture.go                          |   6 +-
 pixelgl/gltriangles.go                        |   4 +-
 pixelgl/input.go                              |   9 +-
 pixelgl/util.go                               |   8 +-
 pixelgl/window.go                             |   4 +-
 sprite.go                                     |  18 +-
 text/atlas.go                                 |  20 +-
 text/text.go                                  |  30 +--
 19 files changed, 278 insertions(+), 305 deletions(-)

diff --git a/data.go b/data.go
index 437d88d..4e6c528 100644
--- a/data.go
+++ b/data.go
@@ -45,7 +45,7 @@ func (td *TrianglesData) SetLen(len int) {
 				Color     RGBA
 				Picture   Vec
 				Intensity float64
-			}{V(0, 0), Alpha(1), V(0, 0), 0})
+			}{ZV, Alpha(1), ZV, 0})
 		}
 	}
 	if len < td.Len() {
@@ -136,8 +136,8 @@ type PictureData struct {
 
 // MakePictureData creates a zero-initialized PictureData covering the given rectangle.
 func MakePictureData(rect Rect) *PictureData {
-	w := int(math.Ceil(rect.Max.X())) - int(math.Floor(rect.Min.X()))
-	h := int(math.Ceil(rect.Max.Y())) - int(math.Floor(rect.Min.Y()))
+	w := int(math.Ceil(rect.Max.X)) - int(math.Floor(rect.Min.X))
+	h := int(math.Ceil(rect.Max.Y)) - int(math.Floor(rect.Min.Y))
 	pd := &PictureData{
 		Stride: w,
 		Rect:   rect,
@@ -205,12 +205,12 @@ func PictureDataFromPicture(pic Picture) *PictureData {
 	pd := MakePictureData(bounds)
 
 	if pic, ok := pic.(PictureColor); ok {
-		for y := math.Floor(bounds.Min.Y()); y < bounds.Max.Y(); y++ {
-			for x := math.Floor(bounds.Min.X()); x < bounds.Max.X(); x++ {
+		for y := math.Floor(bounds.Min.Y); y < bounds.Max.Y; y++ {
+			for x := math.Floor(bounds.Min.X); x < bounds.Max.X; x++ {
 				// this together with the Floor is a trick to get all of the pixels
 				at := V(
-					math.Max(x, bounds.Min.X()),
-					math.Max(y, bounds.Min.Y()),
+					math.Max(x, bounds.Min.X),
+					math.Max(y, bounds.Min.Y),
 				)
 				col := pic.Color(at)
 				pd.Pix[pd.Index(at)] = color.RGBA{
@@ -231,10 +231,10 @@ func PictureDataFromPicture(pic Picture) *PictureData {
 // The resulting image.RGBA's Bounds will be equivalent of the PictureData's Bounds.
 func (pd *PictureData) Image() *image.RGBA {
 	bounds := image.Rect(
-		int(math.Floor(pd.Rect.Min.X())),
-		int(math.Floor(pd.Rect.Min.Y())),
-		int(math.Ceil(pd.Rect.Max.X())),
-		int(math.Ceil(pd.Rect.Max.Y())),
+		int(math.Floor(pd.Rect.Min.X)),
+		int(math.Floor(pd.Rect.Min.Y)),
+		int(math.Ceil(pd.Rect.Max.X)),
+		int(math.Ceil(pd.Rect.Max.Y)),
 	)
 	rgba := image.NewRGBA(bounds)
 
@@ -257,8 +257,8 @@ func (pd *PictureData) Image() *image.RGBA {
 
 // Index returns the index of the pixel at the specified position inside the Pix slice.
 func (pd *PictureData) Index(at Vec) int {
-	at -= pd.Rect.Min.Map(math.Floor)
-	x, y := int(at.X()), int(at.Y())
+	at = at.Sub(pd.Rect.Min.Map(math.Floor))
+	x, y := int(at.X), int(at.Y)
 	return y*pd.Stride + x
 }
 
diff --git a/examples/guide/04_pressing_keys_and_clicking_mouse/main.go b/examples/guide/04_pressing_keys_and_clicking_mouse/main.go
index 3c41512..d6577ba 100644
--- a/examples/guide/04_pressing_keys_and_clicking_mouse/main.go
+++ b/examples/guide/04_pressing_keys_and_clicking_mouse/main.go
@@ -51,7 +51,7 @@ func run() {
 	}
 
 	var (
-		camPos       = pixel.V(0, 0)
+		camPos       = ZV
 		camSpeed     = 500.0
 		camZoom      = 1.0
 		camZoomSpeed = 1.2
diff --git a/examples/guide/05_drawing_efficiently_with_batch/main.go b/examples/guide/05_drawing_efficiently_with_batch/main.go
index ff913d0..80ba261 100644
--- a/examples/guide/05_drawing_efficiently_with_batch/main.go
+++ b/examples/guide/05_drawing_efficiently_with_batch/main.go
@@ -53,7 +53,7 @@ func run() {
 	}
 
 	var (
-		camPos       = pixel.V(0, 0)
+		camPos       = pixel.ZV
 		camSpeed     = 500.0
 		camZoom      = 1.0
 		camZoomSpeed = 1.2
diff --git a/examples/lights/main.go b/examples/lights/main.go
index b3d4f9d..93d1a8f 100644
--- a/examples/lights/main.go
+++ b/examples/lights/main.go
@@ -44,17 +44,17 @@ func (cl *colorlight) apply(dst pixel.ComposeTarget, center pixel.Vec, src, nois
 	if cl.imd == nil {
 		imd := imdraw.New(nil)
 		imd.Color = pixel.Alpha(1)
-		imd.Push(0)
+		imd.Push(pixel.ZV)
 		imd.Color = pixel.Alpha(0)
 		for angle := -cl.spread / 2; angle <= cl.spread/2; angle += cl.spread / 64 {
-			imd.Push(pixel.X(1).Rotated(angle))
+			imd.Push(pixel.V(1, 0).Rotated(angle))
 		}
 		imd.Polygon(0)
 		cl.imd = imd
 	}
 
 	// draw the light arc
-	dst.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, cl.angle).Moved(cl.point))
+	dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
 	dst.SetColorMask(pixel.Alpha(1))
 	dst.SetComposeMethod(pixel.ComposePlus)
 	cl.imd.Draw(dst)
@@ -70,7 +70,7 @@ func (cl *colorlight) apply(dst pixel.ComposeTarget, center pixel.Vec, src, nois
 	src.Draw(dst, pixel.IM.Moved(center))
 
 	// draw the light reflected from the dust
-	dst.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, cl.angle).Moved(cl.point))
+	dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
 	dst.SetColorMask(cl.color.Mul(pixel.Alpha(cl.dust)))
 	dst.SetComposeMethod(pixel.ComposePlus)
 	cl.imd.Draw(dst)
@@ -107,10 +107,10 @@ func run() {
 	}
 
 	points := []pixel.Vec{
-		pixel.V(win.Bounds().Min.X(), win.Bounds().Min.Y()),
-		pixel.V(win.Bounds().Max.X(), win.Bounds().Min.Y()),
-		pixel.V(win.Bounds().Max.X(), win.Bounds().Max.Y()),
-		pixel.V(win.Bounds().Min.X(), win.Bounds().Max.Y()),
+		{X: win.Bounds().Min.X, Y: win.Bounds().Min.Y},
+		{X: win.Bounds().Max.X, Y: win.Bounds().Min.Y},
+		{X: win.Bounds().Max.X, Y: win.Bounds().Max.Y},
+		{X: win.Bounds().Min.X, Y: win.Bounds().Max.Y},
 	}
 
 	angles := []float64{
diff --git a/examples/platformer/main.go b/examples/platformer/main.go
index 09253b3..5828eeb 100644
--- a/examples/platformer/main.go
+++ b/examples/platformer/main.go
@@ -42,7 +42,7 @@ func loadAnimationSheet(sheetPath, descPath string, frameWidth float64) (sheet p
 
 	// create a slice of frames inside the spritesheet
 	var frames []pixel.Rect
-	for x := 0.0; x+frameWidth <= sheet.Bounds().Max.X(); x += frameWidth {
+	for x := 0.0; x+frameWidth <= sheet.Bounds().Max.X; x += frameWidth {
 		frames = append(frames, pixel.R(
 			x,
 			0,
@@ -104,37 +104,37 @@ type gopherPhys struct {
 func (gp *gopherPhys) update(dt float64, ctrl pixel.Vec, platforms []platform) {
 	// apply controls
 	switch {
-	case ctrl.X() < 0:
-		gp.vel = gp.vel.WithX(-gp.runSpeed)
-	case ctrl.X() > 0:
-		gp.vel = gp.vel.WithX(+gp.runSpeed)
+	case ctrl.X < 0:
+		gp.vel.X = -gp.runSpeed
+	case ctrl.X > 0:
+		gp.vel.X = +gp.runSpeed
 	default:
-		gp.vel = gp.vel.WithX(0)
+		gp.vel.X = 0
 	}
 
 	// apply gravity and velocity
-	gp.vel += pixel.Y(gp.gravity).Scaled(dt)
+	gp.vel.Y += gp.gravity * dt
 	gp.rect = gp.rect.Moved(gp.vel.Scaled(dt))
 
 	// check collisions against each platform
 	gp.ground = false
-	if gp.vel.Y() <= 0 {
+	if gp.vel.Y <= 0 {
 		for _, p := range platforms {
-			if gp.rect.Max.X() <= p.rect.Min.X() || gp.rect.Min.X() >= p.rect.Max.X() {
+			if gp.rect.Max.X <= p.rect.Min.X || gp.rect.Min.X >= p.rect.Max.X {
 				continue
 			}
-			if gp.rect.Min.Y() > p.rect.Max.Y() || gp.rect.Min.Y() < p.rect.Max.Y()+gp.vel.Y()*dt {
+			if gp.rect.Min.Y > p.rect.Max.Y || gp.rect.Min.Y < p.rect.Max.Y+gp.vel.Y*dt {
 				continue
 			}
-			gp.vel = gp.vel.WithY(0)
-			gp.rect = gp.rect.Moved(pixel.Y(p.rect.Max.Y() - gp.rect.Min.Y()))
+			gp.vel.Y = 0
+			gp.rect = gp.rect.Moved(pixel.V(0, p.rect.Max.Y-gp.rect.Min.Y))
 			gp.ground = true
 		}
 	}
 
 	// jump if on the ground and the player wants to jump
-	if gp.ground && ctrl.Y() > 0 {
-		gp.vel = gp.vel.WithY(gp.jumpSpeed)
+	if gp.ground && ctrl.Y > 0 {
+		gp.vel.Y = gp.jumpSpeed
 	}
 }
 
@@ -188,7 +188,7 @@ func (ga *gopherAnim) update(dt float64, phys *gopherPhys) {
 		i := int(math.Floor(ga.counter / ga.rate))
 		ga.frame = ga.anims["Run"][i%len(ga.anims["Run"])]
 	case jumping:
-		speed := phys.vel.Y()
+		speed := phys.vel.Y
 		i := int((-speed/phys.jumpSpeed + 1) / 2 * float64(len(ga.anims["Jump"])))
 		if i < 0 {
 			i = 0
@@ -200,8 +200,8 @@ func (ga *gopherAnim) update(dt float64, phys *gopherPhys) {
 	}
 
 	// set the facing direction of the gopher
-	if phys.vel.X() != 0 {
-		if phys.vel.X() > 0 {
+	if phys.vel.X != 0 {
+		if phys.vel.X > 0 {
 			ga.dir = +1
 		} else {
 			ga.dir = -1
@@ -216,11 +216,11 @@ func (ga *gopherAnim) draw(t pixel.Target, phys *gopherPhys) {
 	// draw the correct frame with the correct position and direction
 	ga.sprite.Set(ga.sheet, ga.frame)
 	ga.sprite.Draw(t, pixel.IM.
-		ScaledXY(0, pixel.V(
+		ScaledXY(pixel.ZV, pixel.V(
 			phys.rect.W()/ga.sprite.Frame().W(),
 			phys.rect.H()/ga.sprite.Frame().H(),
 		)).
-		ScaledXY(0, pixel.V(-ga.dir, 1)).
+		ScaledXY(pixel.ZV, pixel.V(-ga.dir, 1)).
 		Moved(phys.rect.Center()),
 	)
 }
@@ -326,7 +326,7 @@ func run() {
 	imd := imdraw.New(sheet)
 	imd.Precision = 32
 
-	camPos := pixel.V(0, 0)
+	camPos := pixel.ZV
 
 	last := time.Now()
 	for !win.Closed() {
@@ -335,7 +335,7 @@ func run() {
 
 		// lerp the camera position towards the gopher
 		camPos = pixel.Lerp(camPos, phys.rect.Center(), 1-math.Pow(1.0/128, dt))
-		cam := pixel.IM.Moved(-camPos)
+		cam := pixel.IM.Moved(camPos.Scaled(-1))
 		canvas.SetMatrix(cam)
 
 		// slow motion with tab
@@ -345,20 +345,20 @@ func run() {
 
 		// restart the level on pressing enter
 		if win.JustPressed(pixelgl.KeyEnter) {
-			phys.rect = phys.rect.Moved(-phys.rect.Center())
-			phys.vel = 0
+			phys.rect = phys.rect.Moved(phys.rect.Center().Scaled(-1))
+			phys.vel = pixel.ZV
 		}
 
 		// control the gopher with keys
-		ctrl := pixel.V(0, 0)
+		ctrl := pixel.ZV
 		if win.Pressed(pixelgl.KeyLeft) {
-			ctrl -= pixel.X(1)
+			ctrl.X--
 		}
 		if win.Pressed(pixelgl.KeyRight) {
-			ctrl += pixel.X(1)
+			ctrl.X++
 		}
 		if win.JustPressed(pixelgl.KeyUp) {
-			ctrl = ctrl.WithY(1)
+			ctrl.Y = 1
 		}
 
 		// update the physics and animation
@@ -378,7 +378,7 @@ func run() {
 
 		// stretch the canvas to the window
 		win.Clear(colornames.White)
-		win.SetMatrix(pixel.IM.Scaled(0,
+		win.SetMatrix(pixel.IM.Scaled(pixel.ZV,
 			math.Min(
 				win.Bounds().W()/canvas.Bounds().W(),
 				win.Bounds().H()/canvas.Bounds().H(),
diff --git a/examples/smoke/main.go b/examples/smoke/main.go
index 4b366c2..013c16c 100644
--- a/examples/smoke/main.go
+++ b/examples/smoke/main.go
@@ -57,8 +57,8 @@ func (p *particles) DrawAll(t pixel.Target) {
 		part.Sprite.DrawColorMask(
 			t,
 			pixel.IM.
-				Scaled(0, part.Scale).
-				Rotated(0, part.Rot).
+				Scaled(pixel.ZV, part.Scale).
+				Rotated(pixel.ZV, part.Rot).
 				Moved(part.Pos),
 			part.Mask,
 		)
@@ -86,7 +86,7 @@ func (ss *smokeSystem) Generate() *particle {
 	sd := new(smokeData)
 	for _, base := range ss.VelBasis {
 		c := math.Max(0, 1+rand.NormFloat64()*ss.VelDist)
-		sd.Vel += base.Scaled(c)
+		sd.Vel = sd.Vel.Add(base.Scaled(c))
 	}
 	sd.Vel = sd.Vel.Scaled(1 / float64(len(ss.VelBasis)))
 	sd.Life = math.Max(0, ss.LifeAvg+rand.NormFloat64()*ss.LifeDist)
@@ -108,7 +108,7 @@ func (ss *smokeSystem) Update(dt float64, p *particle) bool {
 
 	frac := sd.Time / sd.Life
 
-	p.Pos += sd.Vel.Scaled(dt)
+	p.Pos = p.Pos.Add(sd.Vel.Scaled(dt))
 	p.Scale = 0.5 + frac*1.5
 
 	const (
@@ -188,7 +188,7 @@ func run() {
 
 	ss := &smokeSystem{
 		Rects:    rects,
-		Orig:     0,
+		Orig:     pixel.ZV,
 		VelBasis: []pixel.Vec{pixel.V(-100, 100), pixel.V(100, 100), pixel.V(0, 100)},
 		VelDist:  0.1,
 		LifeAvg:  7,
@@ -212,7 +212,10 @@ func run() {
 		p.UpdateAll(dt)
 
 		win.Clear(colornames.Aliceblue)
-		win.SetMatrix(pixel.IM.Moved(win.Bounds().Center() - pixel.Y(win.Bounds().H()/2)))
+
+		orig := win.Bounds().Center()
+		orig.Y -= win.Bounds().H() / 2
+		win.SetMatrix(pixel.IM.Moved(orig))
 
 		batch.Clear()
 		p.DrawAll(batch)
diff --git a/examples/xor/main.go b/examples/xor/main.go
index 94c2c09..0e6364a 100644
--- a/examples/xor/main.go
+++ b/examples/xor/main.go
@@ -40,28 +40,28 @@ func run() {
 		// red circle
 		imd.Clear()
 		imd.Color = pixel.RGB(1, 0, 0)
-		imd.Push(win.Bounds().Center() - pixel.X(offset))
+		imd.Push(win.Bounds().Center().Add(pixel.V(-offset, 0)))
 		imd.Circle(200, 0)
 		imd.Draw(canvas)
 
 		// blue circle
 		imd.Clear()
 		imd.Color = pixel.RGB(0, 0, 1)
-		imd.Push(win.Bounds().Center() + pixel.X(offset))
+		imd.Push(win.Bounds().Center().Add(pixel.V(offset, 0)))
 		imd.Circle(150, 0)
 		imd.Draw(canvas)
 
 		// yellow circle
 		imd.Clear()
 		imd.Color = pixel.RGB(1, 1, 0)
-		imd.Push(win.Bounds().Center() - pixel.Y(offset))
+		imd.Push(win.Bounds().Center().Add(pixel.V(0, -offset)))
 		imd.Circle(100, 0)
 		imd.Draw(canvas)
 
 		// magenta circle
 		imd.Clear()
-		imd.Color=pixel.RGB(1, 0, 1)
-		imd.Push(win.Bounds().Center() + pixel.Y(offset))
+		imd.Color = pixel.RGB(1, 0, 1)
+		imd.Push(win.Bounds().Center().Add(pixel.V(0, offset)))
 		imd.Circle(50, 0)
 		imd.Draw(canvas)
 
diff --git a/geometry.go b/geometry.go
index 8837300..207ab27 100644
--- a/geometry.go
+++ b/geometry.go
@@ -3,50 +3,38 @@ package pixel
 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
-// Go does not allow operator overloading, implementing vector as a struct leads to a bunch of
-// methods for addition, subtraction and multiplication of vectors. With complex128, much of
-// this functionality is given through operators.
+// Vec is a 2D vector type with X and Y coordinates.
 //
 // Create vectors with the V constructor:
 //
 //   u := pixel.V(1, 2)
 //   v := pixel.V(8, -3)
 //
-// Add and subtract them using the standard + and - operators:
+// Use various methods to manipulate them:
 //
-//   w := u + v
-//   fmt.Println(w)     // Vec(9, -1)
-//   fmt.Println(u - v) // Vec(-7, 5)
-//
-// Additional standard vector operations can be obtained with methods:
-//
-//   u := pixel.V(2, 3)
-//   v := pixel.V(8, 1)
-//   if u.X() < 0 {
+//   w := u.Add(v)
+//   fmt.Println(w)        // Vec(9, -1)
+//   fmt.Println(u.Sub(v)) // Vec(-7, 5)
+//   u = pixel.V(2, 3)
+//   v = pixel.V(8, 1)
+//   if u.X < 0 {
 //	     fmt.Println("this won't happen")
 //   }
 //   x := u.Unit().Dot(v.Unit())
-type Vec complex128
-
-// V returns a new 2D vector with the given coordinates.
-func V(x, y float64) Vec {
-	return Vec(complex(x, y))
+type Vec struct {
+	X, Y float64
 }
 
-// X returns a 2D vector with coordinates (x, 0).
-func X(x float64) Vec {
-	return V(x, 0)
-}
+// ZV is a zero vector.
+var ZV = Vec{0, 0}
 
-// Y returns a 2D vector with coordinates (0, y).
-func Y(y float64) Vec {
-	return V(0, y)
+// V returns a new 2D vector with the given coordinates.
+func V(x, y float64) Vec {
+	return Vec{x, y}
 }
 
 // String returns the string representation of the vector u.
@@ -55,76 +43,75 @@ func Y(y float64) Vec {
 //   u.String()     // returns "Vec(4.5, -1.3)"
 //   fmt.Println(u) // Vec(4.5, -1.3)
 func (u Vec) String() string {
-	return fmt.Sprintf("Vec(%v, %v)", u.X(), u.Y())
+	return fmt.Sprintf("Vec(%v, %v)", u.X, u.Y)
 }
 
-// X returns the x coordinate of the vector u.
-func (u Vec) X() float64 {
-	return real(u)
+// XY returns the components of the vector in two return values.
+func (u Vec) XY() (x, y float64) {
+	return u.X, u.Y
 }
 
-// Y returns the y coordinate of the vector u.
-func (u Vec) Y() float64 {
-	return imag(u)
+// Add returns the sum of vectors u and v.
+func (u Vec) Add(v Vec) Vec {
+	return Vec{
+		u.X + v.X,
+		u.Y + v.Y,
+	}
 }
 
-// XY returns the components of the vector in two return values.
-func (u Vec) XY() (x, y float64) {
-	return real(u), imag(u)
+// Sub returns the difference betweeen vectors u and v.
+func (u Vec) Sub(v Vec) Vec {
+	return Vec{
+		u.X - v.X,
+		u.Y - v.Y,
+	}
+}
+
+// Scaled returns the vector u multiplied by c.
+func (u Vec) Scaled(c float64) Vec {
+	return Vec{u.X * c, u.Y * c}
+}
+
+// ScaledXY returns the vector u multiplied by the vector v component-wise.
+func (u Vec) ScaledXY(v Vec) Vec {
+	return Vec{u.X * v.X, u.Y * v.Y}
 }
 
 // Len returns the length of the vector u.
 func (u Vec) Len() float64 {
-	return cmplx.Abs(complex128(u))
+	return math.Hypot(u.X, u.Y)
 }
 
 // Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
 func (u Vec) Angle() float64 {
-	return cmplx.Phase(complex128(u))
+	return math.Atan2(u.Y, u.X)
 }
 
 // Unit returns a vector of length 1 facing the direction of u (has the same angle).
 func (u Vec) Unit() Vec {
-	if u == 0 {
-		return 1
+	if u.X == 0 && u.Y == 0 {
+		return Vec{1, 0}
 	}
-	return u / V(u.Len(), 0)
-}
-
-// Scaled returns the vector u multiplied by c.
-func (u Vec) Scaled(c float64) Vec {
-	return u * V(c, 0)
-}
-
-// ScaledXY returns the vector u multiplied by the vector v component-wise.
-func (u Vec) ScaledXY(v Vec) Vec {
-	return V(u.X()*v.X(), u.Y()*v.Y())
+	return u.Scaled(1 / u.Len())
 }
 
 // Rotated returns the vector u rotated by the given angle in radians.
 func (u Vec) Rotated(angle float64) Vec {
 	sin, cos := math.Sincos(angle)
-	return u * V(cos, sin)
-}
-
-// WithX return the vector u with the x coordinate changed to the given value.
-func (u Vec) WithX(x float64) Vec {
-	return V(x, u.Y())
-}
-
-// WithY returns the vector u with the y coordinate changed to the given value.
-func (u Vec) WithY(y float64) Vec {
-	return V(u.X(), y)
+	return Vec{
+		u.X*cos - u.Y*sin,
+		u.X*sin + u.Y*cos,
+	}
 }
 
 // Dot returns the dot product of vectors u and v.
 func (u Vec) Dot(v Vec) float64 {
-	return u.X()*v.X() + u.Y()*v.Y()
+	return u.X*v.X + u.Y*v.Y
 }
 
 // Cross return the cross product of vectors u and v.
 func (u Vec) Cross(v Vec) float64 {
-	return u.X()*v.Y() - v.X()*u.Y()
+	return u.X*v.Y - v.X*u.Y
 }
 
 // Map applies the function f to both x and y components of the vector u and returns the modified
@@ -133,10 +120,10 @@ func (u Vec) Cross(v Vec) float64 {
 //   u := pixel.V(10.5, -1.5)
 //   v := u.Map(math.Floor)   // v is Vec(10, -2), both components of u floored
 func (u Vec) Map(f func(float64) float64) Vec {
-	return V(
-		f(u.X()),
-		f(u.Y()),
-	)
+	return Vec{
+		f(u.X),
+		f(u.Y),
+	}
 }
 
 // Lerp returns a linear interpolation between vectors a and b.
@@ -145,7 +132,7 @@ func (u Vec) Map(f func(float64) float64) Vec {
 // If t is 0, then a will be returned, if t is 1, b will be returned. Anything between 0 and 1 will
 // return the appropriate point between a and b and so on.
 func Lerp(a, b Vec, t float64) Vec {
-	return a.Scaled(1-t) + b.Scaled(t)
+	return a.Scaled(1 - t).Add(b.Scaled(t))
 }
 
 // Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two
@@ -171,31 +158,31 @@ func R(minX, minY, maxX, maxY float64) Rect {
 //   r.String()     // returns "Rect(100, 50, 200, 300)"
 //   fmt.Println(r) // Rect(100, 50, 200, 300)
 func (r Rect) String() string {
-	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X(), r.Min.Y(), r.Max.X(), r.Max.Y())
+	return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X, r.Min.Y, r.Max.X, r.Max.Y)
 }
 
 // Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
 func (r Rect) Norm() Rect {
 	return Rect{
-		Min: V(
-			math.Min(r.Min.X(), r.Max.X()),
-			math.Min(r.Min.Y(), r.Max.Y()),
-		),
-		Max: V(
-			math.Max(r.Min.X(), r.Max.X()),
-			math.Max(r.Min.Y(), r.Max.Y()),
-		),
+		Min: Vec{
+			math.Min(r.Min.X, r.Max.X),
+			math.Min(r.Min.Y, r.Max.Y),
+		},
+		Max: Vec{
+			math.Max(r.Min.X, r.Max.X),
+			math.Max(r.Min.Y, r.Max.Y),
+		},
 	}
 }
 
 // W returns the width of the Rect.
 func (r Rect) W() float64 {
-	return r.Max.X() - r.Min.X()
+	return r.Max.X - r.Min.X
 }
 
 // H returns the height of the Rect.
 func (r Rect) H() float64 {
-	return r.Max.Y() - r.Min.Y()
+	return r.Max.Y - r.Min.Y
 }
 
 // Size returns the vector of width and height of the Rect.
@@ -205,34 +192,14 @@ func (r Rect) Size() Vec {
 
 // Center returns the position of the center of the Rect.
 func (r Rect) Center() Vec {
-	return (r.Min + r.Max) / 2
+	return Lerp(r.Min, r.Max, 0.5)
 }
 
 // Moved returns the Rect moved (both Min and Max) by the given vector delta.
 func (r Rect) Moved(delta Vec) Rect {
 	return Rect{
-		Min: r.Min + delta,
-		Max: r.Max + delta,
-	}
-}
-
-// WithMin returns the Rect with it's Min changed to the given position.
-//
-// Note, that the Rect is not automatically normalized.
-func (r Rect) WithMin(min Vec) Rect {
-	return Rect{
-		Min: min,
-		Max: r.Max,
-	}
-}
-
-// WithMax returns the Rect with it's Max changed to the given position.
-//
-// Note, that the Rect is not automatically normalized.
-func (r Rect) WithMax(max Vec) Rect {
-	return Rect{
-		Min: r.Min,
-		Max: max,
+		Min: r.Min.Add(delta),
+		Max: r.Max.Add(delta),
 	}
 }
 
@@ -249,10 +216,10 @@ func (r Rect) Resized(anchor, size Vec) Rect {
 	if r.W()*r.H() == 0 {
 		panic(fmt.Errorf("(%T).Resize: zero area", r))
 	}
-	fraction := V(size.X()/r.W(), size.Y()/r.H())
+	fraction := Vec{size.X / r.W(), size.Y / r.H()}
 	return Rect{
-		Min: anchor + (r.Min - anchor).ScaledXY(fraction),
-		Max: anchor + (r.Max - anchor).ScaledXY(fraction),
+		Min: anchor.Add(r.Min.Sub(anchor)).ScaledXY(fraction),
+		Max: anchor.Add(r.Max.Sub(anchor)).ScaledXY(fraction),
 	}
 }
 
@@ -263,22 +230,22 @@ func (r Rect) Resized(anchor, size Vec) Rect {
 func (r Rect) ResizedMin(size Vec) Rect {
 	return Rect{
 		Min: r.Min,
-		Max: r.Min + size,
+		Max: r.Min.Add(size),
 	}
 }
 
 // Contains checks whether a vector u is contained within this Rect (including it's borders).
 func (r Rect) Contains(u Vec) bool {
-	return r.Min.X() <= u.X() && u.X() <= r.Max.X() && r.Min.Y() <= u.Y() && u.Y() <= r.Max.Y()
+	return r.Min.X <= u.X && u.X <= r.Max.X && r.Min.Y <= u.Y && u.Y <= r.Max.Y
 }
 
 // Union returns a minimal Rect which covers both r and s. Rects r and s should be normalized.
 func (r Rect) Union(s Rect) Rect {
 	return R(
-		math.Min(r.Min.X(), s.Min.X()),
-		math.Min(r.Min.Y(), s.Min.Y()),
-		math.Max(r.Max.X(), s.Max.X()),
-		math.Max(r.Max.Y(), s.Max.Y()),
+		math.Min(r.Min.X, s.Min.X),
+		math.Min(r.Min.Y, s.Min.Y),
+		math.Max(r.Max.X, s.Max.X),
+		math.Max(r.Max.Y, s.Max.Y),
 	)
 }
 
@@ -320,7 +287,7 @@ func (m Matrix) Moved(delta Vec) Matrix {
 // ScaledXY scales everything around a given point by the scale factor in each axis respectively.
 func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
 	m3 := mgl64.Mat3(m)
-	m3 = mgl64.Translate2D((-around).XY()).Mul3(m3)
+	m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3)
 	m3 = mgl64.Scale2D(scale.XY()).Mul3(m3)
 	m3 = mgl64.Translate2D(around.XY()).Mul3(m3)
 	return Matrix(m3)
@@ -334,7 +301,7 @@ func (m Matrix) Scaled(around Vec, scale float64) Matrix {
 // Rotated rotates everything around a given point by the given angle in radians.
 func (m Matrix) Rotated(around Vec, angle float64) Matrix {
 	m3 := mgl64.Mat3(m)
-	m3 = mgl64.Translate2D((-around).XY()).Mul3(m3)
+	m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3)
 	m3 = mgl64.Rotate3DZ(angle).Mul3(m3)
 	m3 = mgl64.Translate2D(around.XY()).Mul3(m3)
 	return Matrix(m3)
@@ -353,7 +320,7 @@ func (m Matrix) Chained(next Matrix) Matrix {
 // Time complexity is O(1).
 func (m Matrix) Project(u Vec) Vec {
 	m3 := mgl64.Mat3(m)
-	proj := m3.Mul3x1(mgl64.Vec3{u.X(), u.Y(), 1})
+	proj := m3.Mul3x1(mgl64.Vec3{u.X, u.Y, 1})
 	return V(proj.X(), proj.Y())
 }
 
@@ -363,6 +330,6 @@ func (m Matrix) Project(u Vec) Vec {
 func (m Matrix) Unproject(u Vec) Vec {
 	m3 := mgl64.Mat3(m)
 	inv := m3.Inv()
-	unproj := inv.Mul3x1(mgl64.Vec3{u.X(), u.Y(), 1})
+	unproj := inv.Mul3x1(mgl64.Vec3{u.X, u.Y, 1})
 	return V(unproj.X(), unproj.Y())
 }
diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go
index 3664459..626cb2f 100644
--- a/imdraw/imdraw.go
+++ b/imdraw/imdraw.go
@@ -111,7 +111,7 @@ func (imd *IMDraw) Clear() {
 func (imd *IMDraw) Reset() {
 	imd.points = nil
 	imd.Color = pixel.Alpha(1)
-	imd.Picture = 0
+	imd.Picture = pixel.ZV
 	imd.Intensity = 0
 	imd.Precision = 64
 	imd.EndShape = NoEndShape
@@ -280,15 +280,15 @@ func (imd *IMDraw) fillRectangle() {
 	for i, j := 0, off; i+1 < len(points); i, j = i+1, j+6 {
 		a, b := points[i], points[i+1]
 		c := point{
-			pos: pixel.V(a.pos.X(), b.pos.Y()),
+			pos: pixel.V(a.pos.X, b.pos.Y),
 			col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
-			pic: pixel.V(a.pic.X(), b.pic.Y()),
+			pic: pixel.V(a.pic.X, b.pic.Y),
 			in:  (a.in + b.in) / 2,
 		}
 		d := point{
-			pos: pixel.V(b.pos.X(), a.pos.Y()),
+			pos: pixel.V(b.pos.X, a.pos.Y),
 			col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
-			pic: pixel.V(b.pic.X(), a.pic.Y()),
+			pic: pixel.V(b.pic.X, a.pic.Y),
 			in:  (a.in + b.in) / 2,
 		}
 
@@ -318,9 +318,9 @@ func (imd *IMDraw) outlineRectangle(thickness float64) {
 		mid.in = (a.in + b.in) / 2
 
 		imd.pushPt(a.pos, a)
-		imd.pushPt(pixel.V(a.pos.X(), b.pos.Y()), mid)
+		imd.pushPt(pixel.V(a.pos.X, b.pos.Y), mid)
 		imd.pushPt(b.pos, b)
-		imd.pushPt(pixel.V(b.pos.X(), a.pos.Y()), mid)
+		imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid)
 		imd.polyline(thickness, true)
 	}
 }
@@ -360,24 +360,24 @@ func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) {
 
 		for i := range (*imd.tri)[off:] {
 			(*imd.tri)[off+i].Color = pt.col
-			(*imd.tri)[off+i].Picture = 0
+			(*imd.tri)[off+i].Picture = pixel.ZV
 			(*imd.tri)[off+i].Intensity = 0
 		}
 
 		for i, j := 0.0, off; i < num; i, j = i+1, j+3 {
 			angle := low + i*delta
 			sin, cos := math.Sincos(angle)
-			a := pt.pos + pixel.V(
-				radius.X()*cos,
-				radius.Y()*sin,
-			)
+			a := pt.pos.Add(pixel.V(
+				radius.X*cos,
+				radius.Y*sin,
+			))
 
 			angle = low + (i+1)*delta
 			sin, cos = math.Sincos(angle)
-			b := pt.pos + pixel.V(
-				radius.X()*cos,
-				radius.Y()*sin,
-			)
+			b := pt.pos.Add(pixel.V(
+				radius.X*cos,
+				radius.Y*sin,
+			))
 
 			(*imd.tri)[j+0].Position = pt.pos
 			(*imd.tri)[j+1].Position = a
@@ -401,7 +401,7 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
 
 		for i := range (*imd.tri)[off:] {
 			(*imd.tri)[off+i].Color = pt.col
-			(*imd.tri)[off+i].Picture = 0
+			(*imd.tri)[off+i].Picture = pixel.ZV
 			(*imd.tri)[off+i].Intensity = 0
 		}
 
@@ -409,26 +409,26 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
 			angle := low + i*delta
 			sin, cos := math.Sincos(angle)
 			normalSin, normalCos := pixel.V(sin, cos).ScaledXY(radius).Unit().XY()
-			a := pt.pos + pixel.V(
-				radius.X()*cos-thickness/2*normalCos,
-				radius.Y()*sin-thickness/2*normalSin,
-			)
-			b := pt.pos + pixel.V(
-				radius.X()*cos+thickness/2*normalCos,
-				radius.Y()*sin+thickness/2*normalSin,
-			)
+			a := pt.pos.Add(pixel.V(
+				radius.X*cos-thickness/2*normalCos,
+				radius.Y*sin-thickness/2*normalSin,
+			))
+			b := pt.pos.Add(pixel.V(
+				radius.X*cos+thickness/2*normalCos,
+				radius.Y*sin+thickness/2*normalSin,
+			))
 
 			angle = low + (i+1)*delta
 			sin, cos = math.Sincos(angle)
 			normalSin, normalCos = pixel.V(sin, cos).ScaledXY(radius).Unit().XY()
-			c := pt.pos + pixel.V(
-				radius.X()*cos-thickness/2*normalCos,
-				radius.Y()*sin-thickness/2*normalSin,
-			)
-			d := pt.pos + pixel.V(
-				radius.X()*cos+thickness/2*normalCos,
-				radius.Y()*sin+thickness/2*normalSin,
-			)
+			c := pt.pos.Add(pixel.V(
+				radius.X*cos-thickness/2*normalCos,
+				radius.Y*sin-thickness/2*normalSin,
+			))
+			d := pt.pos.Add(pixel.V(
+				radius.X*cos+thickness/2*normalCos,
+				radius.Y*sin+thickness/2*normalSin,
+			))
 
 			(*imd.tri)[j+0].Position = a
 			(*imd.tri)[j+1].Position = b
@@ -443,18 +443,18 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
 
 		if doEndShape {
 			lowSin, lowCos := math.Sincos(low)
-			lowCenter := pt.pos + pixel.V(
-				radius.X()*lowCos,
-				radius.Y()*lowSin,
-			)
+			lowCenter := pt.pos.Add(pixel.V(
+				radius.X*lowCos,
+				radius.Y*lowSin,
+			))
 			normalLowSin, normalLowCos := pixel.V(lowSin, lowCos).ScaledXY(radius).Unit().XY()
 			normalLow := pixel.V(normalLowCos, normalLowSin).Angle()
 
 			highSin, highCos := math.Sincos(high)
-			highCenter := pt.pos + pixel.V(
-				radius.X()*highCos,
-				radius.Y()*highSin,
-			)
+			highCenter := pt.pos.Add(pixel.V(
+				radius.X*highCos,
+				radius.Y*highSin,
+			))
 			normalHighSin, normalHighCos := pixel.V(highSin, highCos).ScaledXY(radius).Unit().XY()
 			normalHigh := pixel.V(normalHighCos, normalHighSin).Angle()
 
@@ -467,21 +467,21 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
 			case NoEndShape:
 				// nothing
 			case SharpEndShape:
-				thick := pixel.X(thickness / 2).Rotated(normalLow)
-				imd.pushPt(lowCenter+thick, pt)
-				imd.pushPt(lowCenter-thick, pt)
-				imd.pushPt(lowCenter-thick.Rotated(math.Pi/2*orientation), pt)
+				thick := pixel.V(thickness/2, 0).Rotated(normalLow)
+				imd.pushPt(lowCenter.Add(thick), pt)
+				imd.pushPt(lowCenter.Sub(thick), pt)
+				imd.pushPt(lowCenter.Sub(thick.Rotated(math.Pi/2*orientation)), pt)
 				imd.fillPolygon()
-				thick = pixel.X(thickness / 2).Rotated(normalHigh)
-				imd.pushPt(highCenter+thick, pt)
-				imd.pushPt(highCenter-thick, pt)
-				imd.pushPt(highCenter+thick.Rotated(math.Pi/2*orientation), pt)
+				thick = pixel.V(thickness/2, 0).Rotated(normalHigh)
+				imd.pushPt(highCenter.Add(thick), pt)
+				imd.pushPt(highCenter.Sub(thick), pt)
+				imd.pushPt(highCenter.Add(thick.Rotated(math.Pi/2*orientation)), pt)
 				imd.fillPolygon()
 			case RoundEndShape:
 				imd.pushPt(lowCenter, pt)
-				imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normalLow, normalLow-math.Pi*orientation)
+				imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normalLow, normalLow-math.Pi*orientation)
 				imd.pushPt(highCenter, pt)
-				imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normalHigh, normalHigh+math.Pi*orientation)
+				imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normalHigh, normalHigh+math.Pi*orientation)
 			}
 		}
 	}
@@ -500,25 +500,25 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 
 	// first point
 	j, i := 0, 1
-	normal := (points[i].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+	normal := points[i].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
 
 	if !closed {
 		switch points[j].endshape {
 		case NoEndShape:
 			// nothing
 		case SharpEndShape:
-			imd.pushPt(points[j].pos+normal, points[j])
-			imd.pushPt(points[j].pos-normal, points[j])
-			imd.pushPt(points[j].pos+normal.Rotated(math.Pi/2), points[j])
+			imd.pushPt(points[j].pos.Add(normal), points[j])
+			imd.pushPt(points[j].pos.Sub(normal), points[j])
+			imd.pushPt(points[j].pos.Add(normal.Rotated(math.Pi/2)), points[j])
 			imd.fillPolygon()
 		case RoundEndShape:
 			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normal.Angle(), normal.Angle()+math.Pi)
+			imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()+math.Pi)
 		}
 	}
 
-	imd.pushPt(points[j].pos+normal, points[j])
-	imd.pushPt(points[j].pos-normal, points[j])
+	imd.pushPt(points[j].pos.Add(normal), points[j])
+	imd.pushPt(points[j].pos.Sub(normal), points[j])
 
 	// middle points
 	for i := 0; i < len(points); i++ {
@@ -536,16 +536,16 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 			k %= len(points)
 		}
 
-		ijNormal := (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
-		jkNormal := (points[k].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+		ijNormal := points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+		jkNormal := points[k].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
 
 		orientation := 1.0
 		if ijNormal.Cross(jkNormal) > 0 {
 			orientation = -1.0
 		}
 
-		imd.pushPt(points[j].pos-ijNormal, points[j])
-		imd.pushPt(points[j].pos+ijNormal, points[j])
+		imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
+		imd.pushPt(points[j].pos.Add(ijNormal), points[j])
 		imd.fillPolygon()
 
 		switch points[j].endshape {
@@ -553,28 +553,28 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 			// nothing
 		case SharpEndShape:
 			imd.pushPt(points[j].pos, points[j])
-			imd.pushPt(points[j].pos+ijNormal.Scaled(orientation), points[j])
-			imd.pushPt(points[j].pos+jkNormal.Scaled(orientation), points[j])
+			imd.pushPt(points[j].pos.Add(ijNormal.Scaled(orientation)), points[j])
+			imd.pushPt(points[j].pos.Add(jkNormal.Scaled(orientation)), points[j])
 			imd.fillPolygon()
 		case RoundEndShape:
 			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, ijNormal.Angle(), ijNormal.Angle()-math.Pi)
+			imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()-math.Pi)
 			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, jkNormal.Angle(), jkNormal.Angle()+math.Pi)
+			imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), jkNormal.Angle(), jkNormal.Angle()+math.Pi)
 		}
 
 		if !closing {
-			imd.pushPt(points[j].pos+jkNormal, points[j])
-			imd.pushPt(points[j].pos-jkNormal, points[j])
+			imd.pushPt(points[j].pos.Add(jkNormal), points[j])
+			imd.pushPt(points[j].pos.Sub(jkNormal), points[j])
 		}
 	}
 
 	// last point
 	i, j = len(points)-2, len(points)-1
-	normal = (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+	normal = points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
 
-	imd.pushPt(points[j].pos-normal, points[j])
-	imd.pushPt(points[j].pos+normal, points[j])
+	imd.pushPt(points[j].pos.Sub(normal), points[j])
+	imd.pushPt(points[j].pos.Add(normal), points[j])
 	imd.fillPolygon()
 
 	if !closed {
@@ -582,13 +582,13 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 		case NoEndShape:
 			// nothing
 		case SharpEndShape:
-			imd.pushPt(points[j].pos+normal, points[j])
-			imd.pushPt(points[j].pos-normal, points[j])
-			imd.pushPt(points[j].pos+normal.Rotated(-math.Pi/2), points[j])
+			imd.pushPt(points[j].pos.Add(normal), points[j])
+			imd.pushPt(points[j].pos.Sub(normal), points[j])
+			imd.pushPt(points[j].pos.Add(normal.Rotated(-math.Pi/2)), points[j])
 			imd.fillPolygon()
 		case RoundEndShape:
 			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normal.Angle(), normal.Angle()-math.Pi)
+			imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()-math.Pi)
 		}
 	}
 }
diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go
index 038bcc0..070a382 100644
--- a/pixelgl/canvas.go
+++ b/pixelgl/canvas.go
@@ -285,8 +285,8 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
 
 		dstBounds := ct.dst.Bounds()
 		shader.SetUniformAttr(canvasBounds, mgl32.Vec4{
-			float32(dstBounds.Min.X()),
-			float32(dstBounds.Min.Y()),
+			float32(dstBounds.Min.X),
+			float32(dstBounds.Min.Y),
 			float32(dstBounds.W()),
 			float32(dstBounds.H()),
 		})
diff --git a/pixelgl/glframe.go b/pixelgl/glframe.go
index 790fe5c..ab35725 100644
--- a/pixelgl/glframe.go
+++ b/pixelgl/glframe.go
@@ -70,7 +70,7 @@ func (gf *GLFrame) Color(at pixel.Vec) pixel.RGBA {
 		return pixel.Alpha(0)
 	}
 	bx, by, bw, _ := intBounds(gf.bounds)
-	x, y := int(at.X())-bx, int(at.Y())-by
+	x, y := int(at.X)-bx, int(at.Y)-by
 	off := y*bw + x
 	return pixel.RGBA{
 		R: float64(gf.pixels[off*4+0]) / 255,
diff --git a/pixelgl/glpicture.go b/pixelgl/glpicture.go
index c249b60..659f014 100644
--- a/pixelgl/glpicture.go
+++ b/pixelgl/glpicture.go
@@ -42,8 +42,8 @@ func NewGLPicture(p pixel.Picture) GLPicture {
 		for y := 0; y < bh; y++ {
 			for x := 0; x < bw; x++ {
 				at := pixel.V(
-					math.Max(float64(bx+x), bounds.Min.X()),
-					math.Max(float64(by+y), bounds.Min.Y()),
+					math.Max(float64(bx+x), bounds.Min.X),
+					math.Max(float64(by+y), bounds.Min.Y),
 				)
 				color := p.Color(at)
 				off := (y*bw + x) * 4
@@ -87,7 +87,7 @@ func (gp *glPicture) Color(at pixel.Vec) pixel.RGBA {
 		return pixel.Alpha(0)
 	}
 	bx, by, bw, _ := intBounds(gp.bounds)
-	x, y := int(at.X())-bx, int(at.Y())-by
+	x, y := int(at.X)-bx, int(at.Y)-by
 	off := y*bw + x
 	return pixel.RGBA{
 		R: float64(gp.pixels[off*4+0]) / 255,
diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go
index 3e6879a..64b7355 100644
--- a/pixelgl/gltriangles.go
+++ b/pixelgl/gltriangles.go
@@ -135,8 +135,8 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
 	if t, ok := t.(pixel.TrianglesPicture); ok {
 		for i := 0; i < gt.Len(); i++ {
 			pic, intensity := t.Picture(i)
-			gt.data[i*gt.vs.Stride()+6] = float32(pic.X())
-			gt.data[i*gt.vs.Stride()+7] = float32(pic.Y())
+			gt.data[i*gt.vs.Stride()+6] = float32(pic.X)
+			gt.data[i*gt.vs.Stride()+7] = float32(pic.Y)
 			gt.data[i*gt.vs.Stride()+8] = float32(intensity)
 		}
 	}
diff --git a/pixelgl/input.go b/pixelgl/input.go
index ea6c7fd..ff98805 100644
--- a/pixelgl/input.go
+++ b/pixelgl/input.go
@@ -356,13 +356,14 @@ func (w *Window) initInput() {
 
 		w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) {
 			w.tempInp.mouse = pixel.V(
-				x+w.bounds.Min.X(),
-				(w.bounds.H()-y)+w.bounds.Min.Y(),
+				x+w.bounds.Min.X,
+				(w.bounds.H()-y)+w.bounds.Min.Y,
 			)
 		})
 
 		w.window.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) {
-			w.tempInp.scroll += pixel.V(xoff, yoff)
+			w.tempInp.scroll.X += xoff
+			w.tempInp.scroll.Y += yoff
 		})
 
 		w.window.SetCharCallback(func(_ *glfw.Window, r rune) {
@@ -380,6 +381,6 @@ func (w *Window) updateInput() {
 	w.currInp = w.tempInp
 
 	w.tempInp.repeat = [KeyLast + 1]bool{}
-	w.tempInp.scroll = 0
+	w.tempInp.scroll = pixel.ZV
 	w.tempInp.typed = ""
 }
diff --git a/pixelgl/util.go b/pixelgl/util.go
index a0ed593..c11ea3c 100644
--- a/pixelgl/util.go
+++ b/pixelgl/util.go
@@ -7,9 +7,9 @@ import (
 )
 
 func intBounds(bounds pixel.Rect) (x, y, w, h int) {
-	x0 := int(math.Floor(bounds.Min.X()))
-	y0 := int(math.Floor(bounds.Min.Y()))
-	x1 := int(math.Ceil(bounds.Max.X()))
-	y1 := int(math.Ceil(bounds.Max.Y()))
+	x0 := int(math.Floor(bounds.Min.X))
+	y0 := int(math.Floor(bounds.Min.Y))
+	x1 := int(math.Ceil(bounds.Max.X))
+	y1 := int(math.Ceil(bounds.Max.Y))
 	return x0, y0, x1 - x0, y1 - y0
 }
diff --git a/pixelgl/window.go b/pixelgl/window.go
index 29b28fd..afbd5f7 100644
--- a/pixelgl/window.go
+++ b/pixelgl/window.go
@@ -160,10 +160,10 @@ func (w *Window) Update() {
 	mainthread.Call(func() {
 		_, _, oldW, oldH := intBounds(w.bounds)
 		newW, newH := w.window.GetSize()
-		w.bounds = w.bounds.ResizedMin(w.bounds.Size() + pixel.V(
+		w.bounds = w.bounds.ResizedMin(w.bounds.Size().Add(pixel.V(
 			float64(newW-oldW),
 			float64(newH-oldH),
-		))
+		)))
 	})
 
 	w.canvas.SetBounds(w.bounds)
diff --git a/sprite.go b/sprite.go
index ac12eac..0808759 100644
--- a/sprite.go
+++ b/sprite.go
@@ -90,20 +90,20 @@ func (s *Sprite) DrawColorMask(t Target, matrix Matrix, mask color.Color) {
 func (s *Sprite) calcData() {
 	var (
 		center     = s.frame.Center()
-		horizontal = X(s.frame.W() / 2)
-		vertical   = Y(s.frame.H() / 2)
+		horizontal = V(s.frame.W()/2, 0)
+		vertical   = V(0, s.frame.H()/2)
 	)
 
-	(*s.tri)[0].Position = -horizontal - vertical
-	(*s.tri)[1].Position = +horizontal - vertical
-	(*s.tri)[2].Position = +horizontal + vertical
-	(*s.tri)[3].Position = -horizontal - vertical
-	(*s.tri)[4].Position = +horizontal + vertical
-	(*s.tri)[5].Position = -horizontal + vertical
+	(*s.tri)[0].Position = Vec{}.Sub(horizontal).Sub(vertical)
+	(*s.tri)[1].Position = Vec{}.Add(horizontal).Sub(vertical)
+	(*s.tri)[2].Position = Vec{}.Add(horizontal).Add(vertical)
+	(*s.tri)[3].Position = Vec{}.Sub(horizontal).Sub(vertical)
+	(*s.tri)[4].Position = Vec{}.Add(horizontal).Add(vertical)
+	(*s.tri)[5].Position = Vec{}.Sub(horizontal).Add(vertical)
 
 	for i := range *s.tri {
 		(*s.tri)[i].Color = s.mask
-		(*s.tri)[i].Picture = center + (*s.tri)[i].Position
+		(*s.tri)[i].Picture = center.Add((*s.tri)[i].Position)
 		(*s.tri)[i].Intensity = 1
 	}
 
diff --git a/text/atlas.go b/text/atlas.go
index c7e189e..cf9231c 100644
--- a/text/atlas.go
+++ b/text/atlas.go
@@ -73,13 +73,13 @@ func NewAtlas(face font.Face, runeSets ...[]rune) *Atlas {
 		mapping[r] = Glyph{
 			Dot: pixel.V(
 				i2f(fg.dot.X),
-				bounds.Max.Y()-(i2f(fg.dot.Y)-bounds.Min.Y()),
+				bounds.Max.Y-(i2f(fg.dot.Y)-bounds.Min.Y),
 			),
 			Frame: pixel.R(
 				i2f(fg.frame.Min.X),
-				bounds.Max.Y()-(i2f(fg.frame.Min.Y)-bounds.Min.Y()),
+				bounds.Max.Y-(i2f(fg.frame.Min.Y)-bounds.Min.Y),
 				i2f(fg.frame.Max.X),
-				bounds.Max.Y()-(i2f(fg.frame.Max.Y)-bounds.Min.Y()),
+				bounds.Max.Y-(i2f(fg.frame.Max.Y)-bounds.Min.Y),
 			).Norm(),
 			Advance: i2f(fg.advance),
 		}
@@ -149,24 +149,24 @@ func (a *Atlas) DrawRune(prevR, r rune, dot pixel.Vec) (rect, frame, bounds pixe
 	}
 
 	if prevR >= 0 {
-		dot += pixel.X(a.Kern(prevR, r))
+		dot.X += a.Kern(prevR, r)
 	}
 
 	glyph := a.Glyph(r)
 
-	rect = glyph.Frame.Moved(dot - glyph.Dot)
+	rect = glyph.Frame.Moved(dot.Sub(glyph.Dot))
 	bounds = rect
 
 	if bounds.W()*bounds.H() != 0 {
 		bounds = pixel.R(
-			bounds.Min.X(),
-			dot.Y()-a.Descent(),
-			bounds.Max.X(),
-			dot.Y()+a.Ascent(),
+			bounds.Min.X,
+			dot.Y-a.Descent(),
+			bounds.Max.X,
+			dot.Y+a.Ascent(),
 		)
 	}
 
-	dot += pixel.X(glyph.Advance)
+	dot.X += glyph.Advance
 
 	return rect, glyph.Frame, bounds, dot
 }
diff --git a/text/text.go b/text/text.go
index 553bc74..ea6d194 100644
--- a/text/text.go
+++ b/text/text.go
@@ -39,7 +39,7 @@ func RangeTable(table *unicode.RangeTable) []rune {
 // Text allows for effiecient and convenient text drawing.
 //
 // To create a Text object, use the New constructor:
-//   txt := text.New(pixel.V(0, 0), text.NewAtlas(face, text.ASCII))
+//   txt := text.New(pixel.ZV, text.NewAtlas(face, text.ASCII))
 //
 // As suggested by the constructor, a Text object is always associated with one font face and a
 // fixed set of runes. For example, the Text we created above can draw text using the font face
@@ -274,17 +274,17 @@ func (txt *Text) DrawColorMask(t pixel.Target, matrix pixel.Matrix, mask color.C
 func (txt *Text) controlRune(r rune, dot pixel.Vec) (newDot pixel.Vec, control bool) {
 	switch r {
 	case '\n':
-		dot -= pixel.Y(txt.LineHeight)
-		dot = dot.WithX(txt.Orig.X())
+		dot.X = txt.Orig.X
+		dot.Y -= txt.LineHeight
 	case '\r':
-		dot = dot.WithX(txt.Orig.X())
+		dot.X = txt.Orig.X
 	case '\t':
-		rem := math.Mod(dot.X()-txt.Orig.X(), txt.TabWidth)
+		rem := math.Mod(dot.X-txt.Orig.X, txt.TabWidth)
 		rem = math.Mod(rem, rem+txt.TabWidth)
 		if rem == 0 {
 			rem = txt.TabWidth
 		}
-		dot += pixel.X(rem)
+		dot.X += rem
 	default:
 		return dot, false
 	}
@@ -316,16 +316,18 @@ func (txt *Text) drawBuf() {
 
 		txt.prevR = r
 
-		rv := [...]pixel.Vec{pixel.V(rect.Min.X(), rect.Min.Y()),
-			pixel.V(rect.Max.X(), rect.Min.Y()),
-			pixel.V(rect.Max.X(), rect.Max.Y()),
-			pixel.V(rect.Min.X(), rect.Max.Y()),
+		rv := [...]pixel.Vec{
+			{X: rect.Min.X, Y: rect.Min.Y},
+			{X: rect.Max.X, Y: rect.Min.Y},
+			{X: rect.Max.X, Y: rect.Max.Y},
+			{X: rect.Min.X, Y: rect.Max.Y},
 		}
 
-		fv := [...]pixel.Vec{pixel.V(frame.Min.X(), frame.Min.Y()),
-			pixel.V(frame.Max.X(), frame.Min.Y()),
-			pixel.V(frame.Max.X(), frame.Max.Y()),
-			pixel.V(frame.Min.X(), frame.Max.Y()),
+		fv := [...]pixel.Vec{
+			{X: frame.Min.X, Y: frame.Min.Y},
+			{X: frame.Max.X, Y: frame.Min.Y},
+			{X: frame.Max.X, Y: frame.Max.Y},
+			{X: frame.Min.X, Y: frame.Max.Y},
 		}
 
 		for i, j := range [...]int{0, 1, 2, 0, 2, 3} {
-- 
GitLab