Newer
Older
package pixel
import (
"image/color"
"github.com/faiface/pixel/pixelgl"
"github.com/go-gl/mathgl/mgl32"
"github.com/pkg/errors"
)
// Drawer is anything that can be drawn. It's by no means a drawer inside your table.
//
// Drawer consists of a single methods: Draw. Draw methods takes any number of Transform arguments. It applies these
// transforms in the reverse order and finally draws something transformed by these transforms.
//
// Example:
//
// // object is a drawer
// object.Draw(pixel.Position(pixel.V(100, 100).Rotate(math.Pi / 2)))
// camera := pixel.Camera(pixel.V(0, 0), pixel.V(500, 500), pixel.V(window.Size()))
// object.Draw(camera, pixel.Position(0).Scale(0.5))
type Drawer interface {
Draw(t ...Transform)
}
// Deleter is anything that can be deleted. All graphics objects that have some associated video memory
// are deleters. It is necessary to call Delete when you're done with an object, otherwise you're going
// to have video memory leaks.
type Deleter interface {
Delete()
}
// DrawDeleter combines Drawer and Deleter interfaces.
type DrawDeleter interface {
Drawer
Deleter
}
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Group is used to effeciently handle a collection of objects with a common parent. Usually many objects share a parent,
// using a group can significantly increase performance in these cases.
//
// To use a group, first, create a group and as it's parent use the common parent of the collection of objects:
//
// group := pixel.NewGroup(commonParent)
//
// Then, when creating the objects, use the group as their parent, instead of the original common parent, but, don't forget
// to put everything into a With block, like this:
//
// group.With(func() {
// object := newArbitratyObject(group, ...) // group is the parent of the object
// })
//
// When dealing with objects associated with a group, it's always necessary to wrap that into a With block:
//
// group.With(func() {
// for _, obj := range objectsWithCommonParent {
// // do something with obj
// }
// })
//
// That's all!
type Group struct {
parent pixelgl.Doer
context pixelgl.Context
}
// NewGroup creates a new group with the specified parent.
func NewGroup(parent pixelgl.Doer) *Group {
return &Group{
parent: parent,
}
}
// With enables the parent of a group and executes sub.
func (g *Group) With(sub func()) {
g.parent.Do(func(ctx pixelgl.Context) {
g.context = ctx
sub()
})
}
// Do just passes a cached context to sub.
func (g *Group) Do(sub func(pixelgl.Context)) {
sub(g.context)
}
// Shape is a general drawable shape constructed from vertices.
// Vertices are specified in the vertex array of a shape. A shape can have a picture, a color (mask) and a static
// transform.
//
// Usually you use this type only indirectly throught other specific shapes (sprites, polygons, ...) embedding it.
type Shape struct {
// NewShape creates a new shape with specified parent, picture, color, transform and vertex array.
func NewShape(parent pixelgl.Doer, picture Picture, c color.Color, transform Transform, va *pixelgl.VertexArray) *Shape {
return &Shape{
color: c,
transform: transform,
va: va,
// Delete deletes the underlying
func (s *Shape) Delete() {
// SetPicture changes the picture of a shape.
func (s *Shape) SetPicture(picture Picture) {
s.picture = picture
}
// Picture returns the current picture of a shape.
func (s *Shape) Picture() Picture {
// SetColor changes the color (mask) of a shape.
func (s *Shape) SetColor(c color.Color) {
// Color returns the current color (mask) of a shape.
func (s *Shape) Color() color.Color {
// SetTransform changes the ("static") transform of a shape.
func (s *Shape) SetTransform(transform Transform) {
s.transform = transform
// Transform returns the current ("static") transform of a shape.
func (s *Shape) Transform() Transform {
// VertexArray changes the underlying vertex array of a shape.
func (s *Shape) VertexArray() *pixelgl.VertexArray {
return s.va
}
// Draw draws a sprite transformed by the supplied transforms applied in the reverse order.
func (s *Shape) Draw(t ...Transform) {
mat := mgl32.Ident3()
for i := range t {
mat = mat.Mul3(t[i].Mat3())
}
mat = mat.Mul3(s.transform.Mat3())
s.parent.Do(func(ctx pixelgl.Context) {
r, g, b, a := colorToRGBA(s.color)
ctx.Shader().SetUniformAttr(maskColorVec4, mgl32.Vec4{r, g, b, a})
ctx.Shader().SetUniformAttr(transformMat3, mat)
if s.picture.Texture() != nil {
s.picture.Texture().Do(func(pixelgl.Context) {
s.va.Draw()
})
} else {
s.va.Draw()
}
})
}
// Sprite is a picture that can be drawn on the screen. Optionally it can be color masked or tranformed.
//
// Usually, you only transform objects when you're drawing them (by passing transforms to the Draw method).
// With sprites however, it can be useful to also transform them "statically". For example, sprites are
// anchor by their bottom-left corner by default. Setting a transform can change this anchor to the center,
// or wherever you want.
type Sprite struct {
*Shape
}
// NewSprite creates a new sprite with the supplied picture. The sprite's size is the size of the supplied picture.
// If you want to change the sprite's size, change it's transform.
func NewSprite(parent pixelgl.Doer, picture Picture) *Sprite {
var va *pixelgl.VertexArray
parent.Do(func(ctx pixelgl.Context) {
var err error
va, err = pixelgl.NewVertexArray(
ctx.Shader().VertexFormat(),
4,
[]int{0, 1, 2, 0, 2, 3},
)
if err != nil {
panic(errors.Wrap(err, "failed to create sprite"))
}
w, h := picture.Bounds().Size.XY()
for i, p := range []Vec{V(0, 0), V(w, 0), V(w, h), V(0, h)} {
texCoord := V(
(picture.Bounds().X()+p.X())/float64(picture.Texture().Width()),
(picture.Bounds().Y()+p.Y())/float64(picture.Texture().Height()),
)
va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(p.X()), float32(p.Y())})
va.SetVertexAttr(i, colorVec4, mgl32.Vec4{1, 1, 1, 1})
va.SetVertexAttr(i, texCoordVec2, mgl32.Vec2{float32(texCoord.X()), float32(texCoord.Y())})
}
return &Sprite{NewShape(parent, picture, color.White, Position(0), va)}
// LineColor a line shape (with sharp ends) filled with a single color.
type LineColor struct {
*Shape
a, b Vec
width float64
}
// NewLineColor creates a new line shape between points A and B filled with a single color. Parent is an object
// that this shape belongs to, such as a window, or a graphics effect.
func NewLineColor(parent pixelgl.Doer, c color.Color, a, b Vec, width float64) *LineColor {
va, err = pixelgl.NewVertexArray(
ctx.Shader().VertexFormat(),
4,
)
if err != nil {
panic(errors.Wrap(err, "failed to create line"))
}
for i := 0; i < 4; i++ {
va.SetVertexAttr(i, colorVec4, mgl32.Vec4{1, 1, 1, 1})
va.SetVertexAttr(i, texCoordVec2, mgl32.Vec2{-1, -1})
lc := &LineColor{NewShape(parent, Picture{}, c, Position(0), va), a, b, width}
lc.setPoints()
return lc
}
// setPoints updates the vertex array data according to A, B and width.
func (lc *LineColor) setPoints() {
r := (lc.b - lc.a).Unit().Scaled(lc.width / 2).Rotated(math.Pi / 2)
for i, p := range []Vec{lc.a - r, lc.a + r, lc.b - r, lc.b + r} {
lc.va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(p.X()), float32(p.Y())})
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
}
}
// SetA changes the position of the first endpoint of a line.
func (lc *LineColor) SetA(a Vec) {
lc.a = a
lc.setPoints()
}
// A returns the current position of the first endpoint of a line.
func (lc *LineColor) A() Vec {
return lc.a
}
// SetB changes the position of the second endpoint of a line.
func (lc *LineColor) SetB(b Vec) {
lc.b = b
lc.setPoints()
}
// B returns the current position of the second endpoint of a line.
func (lc *LineColor) B() Vec {
return lc.b
}
// SetWidth changes the width of a line.
func (lc *LineColor) SetWidth(width float64) {
lc.width = width
lc.setPoints()
}
// Width returns the current width of a line.
func (lc *LineColor) Width() float64 {
return lc.width
}
// PolygonColor is a convex polygon shape filled with a single color.
points []Vec
}
// NewPolygonColor creates a new polygon shape filled with a single color. Parent is an object that this shape belongs to,
func NewPolygonColor(parent pixelgl.Doer, c color.Color, points ...Vec) *PolygonColor {
var indices []int
for i := 2; i < len(points); i++ {
indices = append(indices, 0, i-1, i)
}
parent.Do(func(ctx pixelgl.Context) {
va, err = pixelgl.NewVertexArray(
ctx.Shader().VertexFormat(),
len(points),
)
if err != nil {
panic(errors.Wrap(err, "failed to create polygon"))
}
})
for i, p := range points {
va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(p.X()), float32(p.Y())})
va.SetVertexAttr(i, colorVec4, mgl32.Vec4{1, 1, 1, 1})
va.SetVertexAttr(i, texCoordVec2, mgl32.Vec2{-1, -1})
return &PolygonColor{NewShape(parent, Picture{}, c, Position(0), va), points}
// PointNum returns the number of points in a polygon.
func (pc *PolygonColor) PointNum() int {
return len(pc.points)
}
// SetPoint changes the position of a point in a polygon.
//
// If the index is out of range, this function panics.
func (pc *PolygonColor) SetPoint(i int, point Vec) {
pc.points[i] = point
pc.va.SetVertexAttr(i, positionVec2, mgl32.Vec2{float32(point.X()), float32(point.Y())})
}
// Point returns the position of a point in a polygon.
//
// If the index is out of range, this function panics.
func (pc *PolygonColor) Point(i int) Vec {
return pc.points[i]
}
// EllipseColor is an ellipse shape filled with a single color.
type EllipseColor struct {
radius Vec
fill float64
}
// NewEllipseColor creates a new ellipse shape filled with a single color. Parent is an object that this shape belongs to,
// such as a window, or a graphics effect. Fill should be a number between 0 and 1 which specifies how much of the ellipse will
// be filled (from the outside). The value of 1 means that the whole ellipse is filled. The value of 0 means that none of the
// ellipse is filled (which makes the ellipse invisible).
func NewEllipseColor(parent pixelgl.Doer, c color.Color, radius Vec, fill float64) *EllipseColor {
var indices []int
for i := 2; i < (n+1)*2; i++ {
indices = append(indices, i-2, i-1, i)
}
va, err = pixelgl.NewVertexArray(
ctx.Shader().VertexFormat(),
(n+1)*2,
panic(errors.Wrap(err, "failed to create ellipse"))
})
for k := 0; k < n+1; k++ {
i, j := k*2, k*2+1
angle := math.Pi * 2 * float64(k%n) / n
va.SetVertexAttr(i, positionVec2, mgl32.Vec2{
float32(math.Cos(angle) * radius.X()),
float32(math.Sin(angle) * radius.Y()),
})
va.SetVertexAttr(i, colorVec4, mgl32.Vec4{1, 1, 1, 1})
va.SetVertexAttr(i, texCoordVec2, mgl32.Vec2{-1, -1})
va.SetVertexAttr(j, positionVec2, mgl32.Vec2{
float32(math.Cos(angle) * radius.X() * (1 - fill)),
float32(math.Sin(angle) * radius.Y() * (1 - fill)),
})
va.SetVertexAttr(j, colorVec4, mgl32.Vec4{1, 1, 1, 1})
va.SetVertexAttr(j, texCoordVec2, mgl32.Vec2{-1, -1})
}
return &EllipseColor{NewShape(parent, Picture{}, c, Position(0), va), radius, fill}
// Radius returns the radius of an ellipse.
func (ec *EllipseColor) Radius() Vec {
return ec.radius
}
// Fill returns the fill ratio of an ellipse.
func (ec *EllipseColor) Fill() float64 {
return ec.fill