diff --git a/data.go b/data.go index 774d6bc..b27a8d1 100644 --- a/data.go +++ b/data.go @@ -134,8 +134,8 @@ type PictureData struct { // MakePictureData creates a zero-initialized PictureData covering the given rectangle. func MakePictureData(rect Rect) *PictureData { - w := int(math.Ceil(rect.Pos.X()+rect.Size.X())) - int(math.Floor(rect.Pos.X())) - h := int(math.Ceil(rect.Pos.Y()+rect.Size.Y())) - int(math.Floor(rect.Pos.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, @@ -206,12 +206,12 @@ func PictureDataFromPicture(pic Picture) *PictureData { pd := MakePictureData(bounds) if pic, ok := pic.(PictureColor); ok { - for y := math.Floor(bounds.Pos.Y()); y < bounds.Pos.Y()+bounds.Size.Y(); y++ { - for x := math.Floor(bounds.Pos.X()); x < bounds.Pos.X()+bounds.Size.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.Pos.X()), - math.Max(y, bounds.Pos.Y()), + math.Max(x, bounds.Min.X()), + math.Max(y, bounds.Min.Y()), ) pd.SetColor(at, pic.Color(at)) } @@ -226,10 +226,10 @@ func PictureDataFromPicture(pic Picture) *PictureData { // The resulting image.NRGBA's Bounds will be equivalent of the PictureData's Bounds. func (pd *PictureData) Image() *image.NRGBA { bounds := image.Rect( - int(math.Floor(pd.Rect.Pos.X())), - int(math.Floor(pd.Rect.Pos.Y())), - int(math.Ceil(pd.Rect.Pos.X()+pd.Rect.Size.X())), - int(math.Ceil(pd.Rect.Pos.Y()+pd.Rect.Size.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())), ) nrgba := image.NewNRGBA(bounds) @@ -251,7 +251,7 @@ func (pd *PictureData) Image() *image.NRGBA { } func (pd *PictureData) offset(at Vec) int { - at -= pd.Rect.Pos.Map(math.Floor) + at -= pd.Rect.Min.Map(math.Floor) x, y := int(at.X()), int(at.Y()) return y*pd.Stride + x } @@ -264,7 +264,7 @@ func (pd *PictureData) Bounds() Rect { // Slice returns a sub-Picture of this PictureData inside the supplied rectangle. func (pd *PictureData) Slice(r Rect) Picture { return &PictureData{ - Pix: pd.Pix[pd.offset(r.Pos):], + Pix: pd.Pix[pd.offset(r.Min):], Stride: pd.Stride, Rect: r, Orig: pd.Orig, diff --git a/geometry.go b/geometry.go index b2dfdb7..99504c1 100644 --- a/geometry.go +++ b/geometry.go @@ -83,6 +83,11 @@ func (u Vec) Scaled(c float64) Vec { return u * V(c, 0) } +// ScaledXY returns the vector u multiplied by vector v component-wise. +func (u Vec) ScaledXY(v Vec) Vec { + return V(u.X()*v.X(), u.Y()*v.Y()) +} + // Rotated returns the vector u rotated by the given angle in radians. func (u Vec) Rotated(angle float64) Vec { sin, cos := math.Sincos(angle) @@ -117,19 +122,34 @@ func Lerp(a, b Vec, t float64) Vec { return a.Scaled(1-t) + b.Scaled(t) } -// Rect is a 2D rectangle aligned with the axes of the coordinate system. It has a position -// and a size. +// Rect is a 2D rectangle aligned with the axes of the coordinate system. It is defined by two +// points, Min and Max. // -// You can manipulate the position and the size using the usual vector operations. +// The invariant should hold, that Max's components are greater or equal than Min's components +// respectively. type Rect struct { - Pos, Size Vec + Min, Max Vec } -// R returns a new Rect with given position (x, y) and size (w, h). -func R(x, y, w, h float64) Rect { +// R returns a new Rect with given the Min and Max coordinates. +func R(minX, minY, maxX, maxY float64) Rect { return Rect{ - Pos: V(x, y), - Size: V(w, h), + Min: V(minX, minY), + Max: V(maxX, maxY), + }.Norm() +} + +// 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()), + ), } } @@ -139,43 +159,63 @@ func R(x, y, w, h 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.X(), r.Y(), r.W(), r.H()) -} - -// X returns the x coordinate of the position of the rectangle. -func (r Rect) X() float64 { - return r.Pos.X() -} - -// Y returns the y coordinate of the position of the rectangle -func (r Rect) Y() float64 { - return r.Pos.Y() + return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X(), r.Min.Y(), r.Max.X(), r.Max.Y()) } // W returns the width of the rectangle. func (r Rect) W() float64 { - return r.Size.X() + return r.Max.X() - r.Min.X() } // H returns the height of the rectangle. func (r Rect) H() float64 { - return r.Size.Y() -} - -// XYWH returns all of the four components of the rectangle in four return values. -func (r Rect) XYWH() (x, y, w, h float64) { - return r.X(), r.Y(), r.W(), r.H() + return r.Max.Y() - r.Min.Y() } // Center returns the position of the center of the rectangle. func (r Rect) Center() Vec { - return r.Pos + r.Size.Scaled(0.5) + return (r.Min + r.Max) / 2 +} + +// 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, + } +} + +// Resized returns the Rect resized to the given size while keeping the position of the given anchor. +// r.Resized(r.Min, size) // resizes while keeping the position of the lower-left corner +// r.Resized(r.Max, size) // same with the top-right corner +// r.Resized(r.Center(), size) // resizes around the center +// This function does not make sense for size of zero area and will panic. Use ResizeMin in the case +// of zero area. +func (r Rect) Resized(anchor, size Vec) Rect { + if r.W()*r.H() == 0 || size.X()*size.Y() == 0 { + panic(fmt.Errorf("(%T).Resize: zero area", r)) + } + fraction := size.ScaledXY(V(1/r.W(), 1/r.H())) + return Rect{ + Min: anchor + (r.Min - anchor).ScaledXY(fraction), + Max: anchor + (r.Max - anchor).ScaledXY(fraction), + } +} + +// ResizedMin returns the Rect resized to the given size while keeping the position of the Rect's +// Min. +// +// Sizes of zero area are safe here. +func (r Rect) ResizedMin(size Vec) Rect { + return Rect{ + Min: r.Min, + Max: r.Min + size, + } } // Contains checks whether a vector u is contained within this Rect (including it's borders). 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() + return r.Min.X() <= u.X() && u.X() <= r.Max.X() && r.Min.Y() <= u.Y() && u.Y() <= r.Max.Y() } // Matrix is a 3x3 transformation matrix that can be used for all kinds of spacial transforms, such @@ -200,8 +240,8 @@ func (m Matrix) Move(delta Vec) Matrix { 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 { +// 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.Scale2D(scale.XY()).Mul3(m3) @@ -209,13 +249,13 @@ func (m Matrix) ScaleXY(around Vec, scale Vec) Matrix { 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)) +// Scaled scales everything around a given point by the scale factor. +func (m Matrix) Scaled(around Vec, scale float64) Matrix { + return m.ScaledXY(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 { +// 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.Rotate3DZ(angle).Mul3(m3) diff --git a/graphics.go b/graphics.go index 5702306..a9477a9 100644 --- a/graphics.go +++ b/graphics.go @@ -79,10 +79,10 @@ type IM struct { } type point struct { - position Vec - color NRGBA - picture Vec - intensity float64 + pos Vec + col NRGBA + pic Vec + in float64 width float64 precision int endshape EndShape @@ -130,25 +130,25 @@ func (im *IM) Draw(t Target) { func (im *IM) Push(pts ...Vec) { point := im.opts for _, pt := range pts { - point.position = im.matrix.Project(pt) - point.color = im.mask.Mul(im.opts.color) + point.pos = im.matrix.Project(pt) + point.col = im.mask.Mul(im.opts.col) im.points = append(im.points, point) } } // Color sets the color of the next Pushed points. func (im *IM) Color(color color.Color) { - im.opts.color = NRGBAModel.Convert(color).(NRGBA) + im.opts.col = NRGBAModel.Convert(color).(NRGBA) } // Picture sets the Picture coordinates of the next Pushed points. func (im *IM) Picture(pic Vec) { - im.opts.picture = pic + im.opts.pic = pic } // Intensity sets the picture Intensity of the next Pushed points. func (im *IM) Intensity(in float64) { - im.opts.intensity = in + im.opts.in = in } // Width sets the with property of the next Pushed points. @@ -200,20 +200,20 @@ func (im *IM) FillConvexPolygon() { im.tri.SetLen(im.tri.Len() + 3*(len(points)-2)) for j := 1; j+1 < len(points); j++ { - (*im.tri)[i].Position = points[0].position - (*im.tri)[i].Color = points[0].color - (*im.tri)[i].Picture = points[0].picture - (*im.tri)[i].Intensity = points[0].intensity + (*im.tri)[i].Position = points[0].pos + (*im.tri)[i].Color = points[0].col + (*im.tri)[i].Picture = points[0].pic + (*im.tri)[i].Intensity = points[0].in - (*im.tri)[i+1].Position = points[j].position - (*im.tri)[i+1].Color = points[j].color - (*im.tri)[i+1].Picture = points[j].picture - (*im.tri)[i+1].Intensity = points[j].intensity + (*im.tri)[i+1].Position = points[j].pos + (*im.tri)[i+1].Color = points[j].col + (*im.tri)[i+1].Picture = points[j].pic + (*im.tri)[i+1].Intensity = points[j].in - (*im.tri)[i+2].Position = points[j+1].position - (*im.tri)[i+2].Color = points[j+1].color - (*im.tri)[i+2].Picture = points[j+1].picture - (*im.tri)[i+2].Intensity = points[j+1].intensity + (*im.tri)[i+2].Position = points[j+1].pos + (*im.tri)[i+2].Color = points[j+1].col + (*im.tri)[i+2].Picture = points[j+1].pic + (*im.tri)[i+2].Intensity = points[j+1].in i += 3 } @@ -248,14 +248,14 @@ func (im *IM) FillEllipseArc(radius Vec, low, high float64) { } for _, pt := range points { - im.Push(pt.position) // center + im.Push(pt.pos) // center num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision)) delta := (high - low) / num for i := range im.tmp[:int(num)+1] { angle := low + float64(i)*delta sin, cos := math.Sincos(angle) - im.tmp[i] = pt.position + V( + im.tmp[i] = pt.pos + V( radius.X()*cos, radius.Y()*sin, ) @@ -265,7 +265,3 @@ func (im *IM) FillEllipseArc(radius Vec, low, high float64) { im.FillConvexPolygon() } } - -func (im *IM) Line() { - -} diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index 94bd34d..debfc2b 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -105,8 +105,8 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture { for y := 0; y < bh; y++ { for x := 0; x < bw; x++ { at := pixel.V( - math.Max(float64(bx+x), bounds.Pos.X()), - math.Max(float64(by+y), bounds.Pos.Y()), + math.Max(float64(bx+x), bounds.Min.X()), + math.Max(float64(by+y), bounds.Min.Y()), ) color := p.Color(at) pixels[(y*bw+x)*4+0] = uint8(color.R * 255) @@ -179,7 +179,7 @@ func (c *Canvas) SetBounds(bounds pixel.Rect) { // preserve old content if oldF != nil { relBounds := bounds - relBounds.Pos -= c.orig.borders.Pos + relBounds = relBounds.Moved(-c.orig.borders.Min) ox, oy, ow, oh := intBounds(relBounds) oldF.Blit( c.f, @@ -216,7 +216,7 @@ func (c *Canvas) Smooth() bool { // must be manually called inside mainthread func (c *Canvas) setGlhfBounds() { bounds := c.bounds - bounds.Pos -= c.orig.borders.Pos + bounds.Moved(c.orig.borders.Min) bx, by, bw, bh := intBounds(bounds) glhf.Bounds(bx, by, bw, bh) } @@ -311,8 +311,8 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) { ct.dst.s.Begin() ct.dst.s.SetUniformAttr(canvasBounds, mgl32.Vec4{ - float32(ct.dst.bounds.X()), - float32(ct.dst.bounds.Y()), + float32(ct.dst.bounds.Min.X()), + float32(ct.dst.bounds.Min.Y()), float32(ct.dst.bounds.W()), float32(ct.dst.bounds.H()), }) @@ -327,14 +327,14 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) { tex.Begin() ct.dst.s.SetUniformAttr(canvasTexBorders, mgl32.Vec4{ - float32(borders.X()), - float32(borders.Y()), + float32(borders.Min.X()), + float32(borders.Min.Y()), float32(borders.W()), float32(borders.H()), }) ct.dst.s.SetUniformAttr(canvasTexBounds, mgl32.Vec4{ - float32(bounds.X()), - float32(bounds.Y()), + float32(bounds.Min.X()), + float32(bounds.Min.Y()), float32(bounds.W()), float32(bounds.H()), }) diff --git a/pixelgl/input.go b/pixelgl/input.go index 26c914e..8153534 100644 --- a/pixelgl/input.go +++ b/pixelgl/input.go @@ -342,8 +342,8 @@ func (w *Window) initInput() { w.window.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { w.currInp.mouse = pixel.V( - x+w.bounds.X(), - (w.bounds.H()-y)+w.bounds.Y(), + x+w.bounds.Min.X(), + (w.bounds.H()-y)+w.bounds.Min.Y(), ) }) diff --git a/pixelgl/util.go b/pixelgl/util.go index 0d4195d..a0ed593 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.Pos.X())) - y0 := int(math.Floor(bounds.Pos.Y())) - x1 := int(math.Ceil(bounds.Pos.X() + bounds.Size.X())) - y1 := int(math.Ceil(bounds.Pos.Y() + bounds.Size.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 70dc422..9980295 100644 --- a/pixelgl/window.go +++ b/pixelgl/window.go @@ -2,7 +2,6 @@ package pixelgl import ( "image/color" - "math" "runtime" "github.com/faiface/glhf" @@ -147,14 +146,7 @@ func (w *Window) Destroy() { func (w *Window) Update() { mainthread.Call(func() { wi, hi := w.window.GetSize() - w.bounds.Size = pixel.V(float64(wi), float64(hi)) - // fractional positions end up covering more pixels with less size - if w.bounds.X() != math.Floor(w.bounds.X()) { - w.bounds.Size -= pixel.V(1, 0) - } - if w.bounds.Y() != math.Floor(w.bounds.Y()) { - w.bounds.Size -= pixel.V(0, 1) - } + w.bounds = w.bounds.ResizedMin(pixel.V(float64(wi), float64(hi))) }) w.canvas.SetBounds(w.Bounds())