420 lines
11 KiB
Go
420 lines
11 KiB
Go
package pixel
|
|
|
|
import (
|
|
"image/color"
|
|
"math"
|
|
|
|
"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
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// LineColor a line shape (with sharp ends) filled with a single color.
|
|
type LineColor struct {
|
|
parent pixelgl.Doer
|
|
color color.Color
|
|
a, b Vec
|
|
width float64
|
|
va *pixelgl.VertexArray
|
|
}
|
|
|
|
// 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 {
|
|
lc := &LineColor{
|
|
parent: parent,
|
|
color: c,
|
|
a: a,
|
|
b: b,
|
|
width: width,
|
|
}
|
|
|
|
parent.Do(func(ctx pixelgl.Context) {
|
|
var err error
|
|
lc.va, err = pixelgl.NewVertexArray(
|
|
parent,
|
|
ctx.Shader().VertexFormat(),
|
|
pixelgl.TriangleStripDrawMode,
|
|
pixelgl.DynamicUsage,
|
|
4,
|
|
)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "failed to create line"))
|
|
}
|
|
})
|
|
|
|
lc.va.SetVertexAttributeVec4(0, pixelgl.Color, mgl32.Vec4{1, 1, 1, 1})
|
|
lc.va.SetVertexAttributeVec4(1, pixelgl.Color, mgl32.Vec4{1, 1, 1, 1})
|
|
lc.va.SetVertexAttributeVec4(2, pixelgl.Color, mgl32.Vec4{1, 1, 1, 1})
|
|
lc.va.SetVertexAttributeVec4(3, pixelgl.Color, mgl32.Vec4{1, 1, 1, 1})
|
|
|
|
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.SetVertexAttributeVec2(i, pixelgl.Position, mgl32.Vec2{float32(p.X()), float32(p.Y())})
|
|
}
|
|
}
|
|
|
|
// SetColor changes the color of a line.
|
|
func (lc *LineColor) SetColor(c color.Color) {
|
|
lc.color = c
|
|
}
|
|
|
|
// Color returns the current color of a line.
|
|
func (lc *LineColor) Color() color.Color {
|
|
return lc.color
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Draw draws a line transformed by the supplied transforms applied in the reverse order.
|
|
func (lc *LineColor) Draw(t ...Transform) {
|
|
mat := mgl32.Ident3()
|
|
for i := range t {
|
|
mat = mat.Mul3(t[i].Mat3())
|
|
}
|
|
|
|
lc.parent.Do(func(ctx pixelgl.Context) {
|
|
r, g, b, a := colorToRGBA(lc.color)
|
|
ctx.Shader().SetUniformVec4(pixelgl.MaskColor, mgl32.Vec4{r, g, b, a})
|
|
ctx.Shader().SetUniformMat3(pixelgl.Transform, mat)
|
|
ctx.Shader().SetUniformInt(pixelgl.IsTexture, 0)
|
|
})
|
|
|
|
lc.va.Draw()
|
|
}
|
|
|
|
// Delete destroys a line shape and releases it's video memory. Do not use this shape after calling Delete.
|
|
func (lc *LineColor) Delete() {
|
|
lc.va.Delete()
|
|
}
|
|
|
|
// PolygonColor is a polygon shape filled with a single color.
|
|
type PolygonColor struct {
|
|
parent pixelgl.Doer
|
|
color color.Color
|
|
points []Vec
|
|
va *pixelgl.VertexArray
|
|
}
|
|
|
|
// NewPolygonColor creates a new polygon shape filled with a single color. Parent is an object that this shape belongs to,
|
|
// such as a window, or a graphics effect.
|
|
func NewPolygonColor(parent pixelgl.Doer, c color.Color, points ...Vec) *PolygonColor {
|
|
pc := &PolygonColor{
|
|
parent: parent,
|
|
color: c,
|
|
points: points,
|
|
}
|
|
|
|
parent.Do(func(ctx pixelgl.Context) {
|
|
var err error
|
|
pc.va, err = pixelgl.NewVertexArray(
|
|
parent,
|
|
ctx.Shader().VertexFormat(),
|
|
pixelgl.TriangleFanDrawMode,
|
|
pixelgl.DynamicUsage,
|
|
len(points),
|
|
)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "failed to create polygon"))
|
|
}
|
|
})
|
|
|
|
for i, p := range points {
|
|
pc.va.SetVertexAttributeVec2(
|
|
i,
|
|
pixelgl.Position,
|
|
mgl32.Vec2{
|
|
float32(p.X()),
|
|
float32(p.Y()),
|
|
},
|
|
)
|
|
pc.va.SetVertexAttributeVec4(
|
|
i,
|
|
pixelgl.Color,
|
|
mgl32.Vec4{1, 1, 1, 1},
|
|
)
|
|
}
|
|
|
|
return pc
|
|
}
|
|
|
|
// Count returns the number of points in a polygon.
|
|
func (pc *PolygonColor) Count() int {
|
|
return len(pc.points)
|
|
}
|
|
|
|
// SetColor changes the color of a polygon to c.
|
|
func (pc *PolygonColor) SetColor(c color.Color) {
|
|
pc.color = c
|
|
}
|
|
|
|
// Color returns the current color of a polygon.
|
|
func (pc *PolygonColor) Color() color.Color {
|
|
return pc.color
|
|
}
|
|
|
|
// 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.SetVertexAttributeVec2(i, pixelgl.Position, 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]
|
|
}
|
|
|
|
// Draw draws a polygon transformed by the supplied transforms applied in the reverse order.
|
|
func (pc *PolygonColor) Draw(t ...Transform) {
|
|
mat := mgl32.Ident3()
|
|
for i := range t {
|
|
mat = mat.Mul3(t[i].Mat3())
|
|
}
|
|
|
|
pc.parent.Do(func(ctx pixelgl.Context) {
|
|
r, g, b, a := colorToRGBA(pc.color)
|
|
ctx.Shader().SetUniformVec4(pixelgl.MaskColor, mgl32.Vec4{r, g, b, a})
|
|
ctx.Shader().SetUniformMat3(pixelgl.Transform, mat)
|
|
ctx.Shader().SetUniformInt(pixelgl.IsTexture, 0)
|
|
})
|
|
|
|
pc.va.Draw()
|
|
}
|
|
|
|
// Delete destroys a polygon shape and releases it's video memory. Do not use this shape after calling Delete.
|
|
func (pc *PolygonColor) Delete() {
|
|
pc.va.Delete()
|
|
}
|
|
|
|
// EllipseColor is an ellipse shape filled with a single color.
|
|
type EllipseColor struct {
|
|
parent pixelgl.Doer
|
|
color color.Color
|
|
radius Vec
|
|
fill float64
|
|
va *pixelgl.VertexArray
|
|
}
|
|
|
|
// 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 {
|
|
const n = 256
|
|
|
|
ec := &EllipseColor{
|
|
parent: parent,
|
|
color: c,
|
|
radius: radius,
|
|
fill: fill,
|
|
}
|
|
|
|
parent.Do(func(ctx pixelgl.Context) {
|
|
var err error
|
|
ec.va, err = pixelgl.NewVertexArray(
|
|
parent,
|
|
ctx.Shader().VertexFormat(),
|
|
pixelgl.TriangleStripDrawMode,
|
|
pixelgl.DynamicUsage,
|
|
(n+1)*2,
|
|
)
|
|
if err != nil {
|
|
panic(errors.Wrap(err, "failed to create circle"))
|
|
}
|
|
})
|
|
|
|
for k := 0; k < n+1; k++ {
|
|
i, j := k*2, k*2+1
|
|
angle := math.Pi * 2 * float64(k%n) / n
|
|
ec.va.SetVertexAttributeVec2(
|
|
i,
|
|
pixelgl.Position,
|
|
mgl32.Vec2{
|
|
float32(math.Cos(angle)),
|
|
float32(math.Sin(angle)),
|
|
},
|
|
)
|
|
ec.va.SetVertexAttributeVec4(
|
|
i,
|
|
pixelgl.Color,
|
|
mgl32.Vec4{1, 1, 1, 1},
|
|
)
|
|
ec.va.SetVertexAttributeVec2(
|
|
j,
|
|
pixelgl.Position,
|
|
mgl32.Vec2{
|
|
float32(math.Cos(angle) * (1 - fill)),
|
|
float32(math.Sin(angle) * (1 - fill)),
|
|
},
|
|
)
|
|
ec.va.SetVertexAttributeVec4(
|
|
j,
|
|
pixelgl.Color,
|
|
mgl32.Vec4{1, 1, 1, 1},
|
|
)
|
|
}
|
|
|
|
return ec
|
|
}
|
|
|
|
// SetColor changes the color of an ellipse.
|
|
func (ec *EllipseColor) SetColor(c color.Color) {
|
|
ec.color = c
|
|
}
|
|
|
|
// Color returns the current color of an ellipse.
|
|
func (ec *EllipseColor) Color() color.Color {
|
|
return ec.color
|
|
}
|
|
|
|
// SetRadius sets the radius (which can be different in X and Y axis) of an ellipse.
|
|
func (ec *EllipseColor) SetRadius(radius Vec) {
|
|
ec.radius = radius
|
|
}
|
|
|
|
// Radius returns the current radius of an ellipse.
|
|
func (ec *EllipseColor) Radius() Vec {
|
|
return ec.radius
|
|
}
|
|
|
|
// Draw dras an ellipse transformed by the supplied transforms applied in the reverse order.
|
|
func (ec *EllipseColor) Draw(t ...Transform) {
|
|
mat := mgl32.Ident3()
|
|
for i := range t {
|
|
mat = mat.Mul3(t[i].Mat3())
|
|
}
|
|
mat = mat.Mul3(mgl32.Scale2D(float32(ec.radius.X()), float32(ec.radius.Y())))
|
|
|
|
ec.parent.Do(func(ctx pixelgl.Context) {
|
|
r, g, b, a := colorToRGBA(ec.color)
|
|
ctx.Shader().SetUniformVec4(pixelgl.MaskColor, mgl32.Vec4{r, g, b, a})
|
|
ctx.Shader().SetUniformMat3(pixelgl.Transform, mat)
|
|
ctx.Shader().SetUniformInt(pixelgl.IsTexture, 0)
|
|
})
|
|
|
|
ec.va.Draw()
|
|
}
|
|
|
|
// Delete destroys an ellipse shape and releases it's video memory. Do not use this shape after calling Delete.
|
|
func (ec *EllipseColor) Delete() {
|
|
ec.va.Delete()
|
|
}
|