func animateDeadGopher(a *f32.Affine, t clock.Time) { dt := float32(t) a.Scale(a, 1+dt/20, 1+dt/20) a.Translate(a, 0.5, 0.5) a.Rotate(a, dt/math.Pi/-8) a.Translate(a, -0.5, -0.5) }
func (a *arrangerFunc) Arrange(e s.Engine, n *s.Node, t clock.Time) { sprite, _ := a.eng.sprites[n] frameTime := float32(t - a.eng.lastUpdate) updatePosition(sprite, frameTime) screenWidthScaler, screenHeightScaler := a.eng.GetScreenScalers() actualScaleX := screenWidthScaler * sprite.ScaleX actualScaleY := screenHeightScaler * sprite.ScaleY actualPositionX := screenWidthScaler * sprite.X actualPositionY := screenHeightScaler * sprite.Y e.SetSubTex(n, *sprite.GetCurrentFrame().Texture) r := sprite.Rotation * math.Pi / 180 matrix := f32.Affine{ {1, 0, 0}, {0, 1, 0}, } matrix.Translate(&matrix, actualPositionX, actualPositionY) //matrix.Translate(&matrix, sprite.X, sprite.Y) matrix.Rotate(&matrix, r) matrix.Scale(&matrix, actualScaleX, actualScaleY) e.SetTransform(n, matrix) a.eng.lastUpdate = t }
func (e *engine) render(n *sprite.Node, t clock.Time, sz size.Event) { if n.EngineFields.Index == 0 { panic("glsprite: sprite.Node not registered") } if n.Arranger != nil { n.Arranger.Arrange(e, n, t) } // Push absTransforms. // TODO: cache absolute transforms and use EngineFields.Dirty? rel := &e.nodes[n.EngineFields.Index].relTransform m := f32.Affine{} m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) e.absTransforms = append(e.absTransforms, m) if x := n.EngineFields.SubTex; x.T != nil { x.T.(*texture).glImage.Draw( sz, geom.Point{ geom.Pt(m[0][2]), geom.Pt(m[1][2]), }, geom.Point{ geom.Pt(m[0][2] + m[0][0]), geom.Pt(m[1][2] + m[1][0]), }, geom.Point{ geom.Pt(m[0][2] + m[0][1]), geom.Pt(m[1][2] + m[1][1]), }, x.R, ) } for c := n.FirstChild; c != nil; c = c.NextSibling { e.render(c, t, sz) } // Pop absTransforms. e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] }
func TestAffineMask(t *testing.T) { f, err := os.Open("../../../testdata/testpattern.png") if err != nil { t.Fatal(err) } defer f.Close() srcOrig, _, err := image.Decode(f) if err != nil { t.Fatal(err) } b := srcOrig.Bounds() src := image.NewRGBA(b) draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src) mask := image.NewAlpha(b) for y := b.Min.Y; y < b.Max.Y; y++ { for x := b.Min.X; x < b.Max.X; x++ { mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)}) } } want := image.NewRGBA(b) draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src) a := new(f32.Affine) a.Identity() got := image.NewRGBA(b) affine(got, src, b, mask, a, draw.Src) if !imageEq(got, want) { gotPath, err := writeTempPNG("testpattern-mask-got", got) if err != nil { t.Fatal(err) } wantPath, err := writeTempPNG("testpattern-mask-want", want) if err != nil { t.Fatal(err) } t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) } }
func (e *engine) render(n *sprite.Node, t clock.Time) { if n.EngineFields.Index == 0 { panic("portable: sprite.Node not registered") } if n.Arranger != nil { n.Arranger.Arrange(e, n, t) } // Push absTransforms. // TODO: cache absolute transforms and use EngineFields.Dirty? rel := &e.nodes[n.EngineFields.Index].relTransform m := f32.Affine{} m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel) e.absTransforms = append(e.absTransforms, m) if x := n.EngineFields.SubTex; x.T != nil { // Affine transforms work in geom.Pt, which is entirely // independent of the number of pixels in a texture. A texture // of any image.Rectangle bounds rendered with // // Affine{{1, 0, 0}, {0, 1, 0}} // // should have the dimensions (1pt, 1pt). To do this we divide // by the pixel width and height, reducing the texture to // (1px, 1px) of the destination image. Multiplying by // cfg.PixelsPerPt, done in Render above, makes it (1pt, 1pt). dx, dy := x.R.Dx(), x.R.Dy() if dx > 0 && dy > 0 { m.Scale(&m, 1/float32(dx), 1/float32(dy)) // TODO(nigeltao): delete the double-inverse: one here and one // inside func affine. m.Inverse(&m) // See the documentation on the affine function. affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over) } } for c := n.FirstChild; c != nil; c = c.NextSibling { e.render(c, t) } // Pop absTransforms. e.absTransforms = e.absTransforms[:len(e.absTransforms)-1] }
func TestAffine(t *testing.T) { f, err := os.Open("../../../testdata/testpattern.png") if err != nil { t.Fatal(err) } defer f.Close() srcOrig, _, err := image.Decode(f) if err != nil { t.Fatal(err) } src := image.NewRGBA(srcOrig.Bounds()) draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src) const ( pixW = 100 pixH = 100 ptW = geom.Pt(50) ptH = geom.Pt(50) ) cfg := event.Config{ Width: ptW, Height: ptH, PixelsPerPt: float32(pixW) / float32(ptW), } got := image.NewRGBA(image.Rect(0, 0, pixW, pixH)) blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff}) draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src) b := src.Bounds() b.Min.X += 10 b.Max.Y /= 2 var a f32.Affine a.Identity() a.Scale(&a, cfg.PixelsPerPt, cfg.PixelsPerPt) a.Translate(&a, 0, 24) a.Rotate(&a, float32(math.Asin(12./20))) // See commentary in the render method defined in portable.go. a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy())) a.Inverse(&a) affine(got, src, b, nil, &a, draw.Over) ptTopLeft := geom.Point{0, 24} ptBottomRight := geom.Point{12 + 32, 16} drawCross(got, 0, 0) drawCross(got, int(ptTopLeft.X.Px(cfg.PixelsPerPt)), int(ptTopLeft.Y.Px(cfg.PixelsPerPt))) drawCross(got, int(ptBottomRight.X.Px(cfg.PixelsPerPt)), int(ptBottomRight.Y.Px(cfg.PixelsPerPt))) drawCross(got, pixW-1, pixH-1) const wantPath = "../../../testdata/testpattern-window.png" f, err = os.Open(wantPath) if err != nil { t.Fatal(err) } defer f.Close() wantSrc, _, err := image.Decode(f) if err != nil { t.Fatal(err) } want, ok := wantSrc.(*image.RGBA) if !ok { b := wantSrc.Bounds() want = image.NewRGBA(b) draw.Draw(want, b, wantSrc, b.Min, draw.Src) } if !imageEq(got, want) { gotPath, err := writeTempPNG("testpattern-window-got", got) if err != nil { t.Fatal(err) } t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath) } }