From 2360b2930fa77a25871a94b874648b1d6501e06d Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Tue, 12 Jun 2018 10:33:09 +0900 Subject: [PATCH] graphics: Add DrawTriangles Fixes #624 --- examples/triangle/main.go | 120 ++++++++++++++++++++++++++++++ image.go | 78 +++++++++++++++++++ internal/graphics/shader.go | 13 +++- internal/graphicsutil/vertices.go | 34 +++++++++ internal/shareable/shareable.go | 9 +++ 5 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 examples/triangle/main.go diff --git a/examples/triangle/main.go b/examples/triangle/main.go new file mode 100644 index 000000000..2863f4419 --- /dev/null +++ b/examples/triangle/main.go @@ -0,0 +1,120 @@ +// Copyright 2018 The Ebiten Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build example jsgo + +package main + +import ( + "fmt" + "image/color" + "log" + "math" + + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/ebitenutil" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +var ( + emptyImage, _ = ebiten.NewImage(16, 16, ebiten.FilterDefault) +) + +func init() { + emptyImage.Fill(color.White) +} + +var ( + vertices []ebiten.Vertex + indices []uint16 +) + +func init() { + const ( + num = 120 + centerX = screenWidth / 2 + centerY = screenHeight / 2 + r = 160 + ) + + for i := 0; i < num; i++ { + theta := float64(i) / num * 2 * math.Pi + cr := float32(0) + cg := float32(0) + cb := float32(0) + if 0 <= i && i < 2*num/3 { + cr = 2 * float32(i) / float32(num/3) + } + if num/3 <= i && i < 2*num/3 { + cr = 2 - 2*float32(i-num/3)/float32(num/3) + } + if num/3 <= i && i < 2*num/3 { + cg = 2 * float32(i-num/3) / float32(num/3) + } + if 2*num/3 <= i && i < num { + cg = 2 - 2*float32(i-2*num/3)/float32(num/3) + } + if 2*num/3 <= i && i < num { + cb = 2 * float32(i-2*num/3) / float32(num/3) + } + if 0 <= i && i < num/3 { + cb = 2 - 2*float32(i)/float32(num/3) + } + vertices = append(vertices, ebiten.Vertex{ + DstX: float32(r*math.Cos(theta)) + centerX, + DstY: float32(r*math.Sin(theta)) + centerY, + SrcX: 0, + SrcY: 0, + ColorR: cr, + ColorG: cg, + ColorB: cb, + ColorA: 1, + }) + } + + vertices = append(vertices, ebiten.Vertex{ + DstX: centerX, + DstY: centerY, + SrcX: 0, + SrcY: 0, + ColorR: 1, + ColorG: 1, + ColorB: 1, + ColorA: 1, + }) + for i := 0; i < num; i++ { + indices = append(indices, uint16(i), uint16(i+1)%num, num) + } +} + +func update(screen *ebiten.Image) error { + if ebiten.IsDrawingSkipped() { + return nil + } + + op := &ebiten.DrawTrianglesOptions{} + screen.DrawTriangles(vertices, indices, emptyImage, op) + ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f", ebiten.CurrentTPS())) + return nil +} + +func main() { + if err := ebiten.Run(update, screenWidth, screenHeight, 1, "Triangle (Ebiten Demo)"); err != nil { + log.Fatal(err) + } +} diff --git a/image.go b/image.go index 7d02d27f2..032499f22 100644 --- a/image.go +++ b/image.go @@ -338,6 +338,84 @@ func (i *Image) drawImage(img *Image, options *DrawImageOptions) { i.disposeMipmaps() } +// Vertex represents a vertex passed to DrawTriangles. +// +// Note that this API is experimental. +type Vertex struct { + // DstX and DstY represents a point on a destination image. + DstX float32 + DstY float32 + + // SrcX and SrcY represents a point on a source image. + SrcX float32 + SrcY float32 + + // ColorR/ColorG/ColorB/ColorA represents color scaling values. + // 1 means the original source image color is used. + // 0 means a transparent color is used. + ColorR float32 + ColorG float32 + ColorB float32 + ColorA float32 +} + +// DrawTrianglesOptions represents options to render triangles on an image. +// +// Note that this API is experimental. +type DrawTrianglesOptions struct { + // ColorM is a color matrix to draw. + // The default (zero) value is identity, which doesn't change any color. + // ColorM is applied before vertex color scale is applied. + ColorM ColorM + + // CompositeMode is a composite mode to draw. + // The default (zero) value is regular alpha blending. + CompositeMode CompositeMode + + // Filter is a type of texture filter. + // The default (zero) value is FilterDefault. + Filter Filter +} + +// DrawTriangles draws a triangle with the specified vertices and their indices. +// +// If len(indices) is not multiple of 3, DrawTriangles panics. +// +// The rule in which DrawTriangles works effectively is same as DrawImage's. +// +// In contrast to DrawImage, DrawTriangles doesn't care source image edges. +// This means that you might need to add 1px gap on a source region when you render an image by DrawTriangles. +// Note that Ebiten creates texture atlases internally, so you still have to care this even when +// you render a single image. +// +// Note that this API is experimental. +func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, options *DrawTrianglesOptions) { + if len(indices)%3 != 0 { + panic("ebiten: len(indices) % 3 must be 0") + } + // TODO: Check the maximum value of indices and len(vertices)? + + if options == nil { + options = &DrawTrianglesOptions{} + } + + mode := opengl.CompositeMode(options.CompositeMode) + + filter := graphics.FilterNearest + if options.Filter != FilterDefault { + filter = graphics.Filter(options.Filter) + } else if img.filter != FilterDefault { + filter = graphics.Filter(img.filter) + } + + vs := []float32{} + src := img.shareableImages[0] + for _, v := range vertices { + vs = append(vs, src.Vertex(float32(v.DstX), float32(v.DstY), v.SrcX, v.SrcY, v.ColorR, v.ColorG, v.ColorB, v.ColorA)...) + } + i.shareableImages[0].DrawImage(img.shareableImages[0], vs, indices, options.ColorM.impl, mode, filter) +} + // Bounds returns the bounds of the image. func (i *Image) Bounds() image.Rectangle { w, h := i.Size() diff --git a/internal/graphics/shader.go b/internal/graphics/shader.go index d6543d848..3b27b5593 100644 --- a/internal/graphics/shader.go +++ b/internal/graphics/shader.go @@ -56,10 +56,19 @@ varying vec2 varying_tex_coord_min; varying vec2 varying_tex_coord_max; varying vec4 varying_color_scale; +bool isNaN(float x) { + return x != x; +} + void main(void) { varying_tex_coord = vec2(tex_coord[0], tex_coord[1]); - varying_tex_coord_min = vec2(min(tex_coord[0], tex_coord[2]), min(tex_coord[1], tex_coord[3])); - varying_tex_coord_max = vec2(max(tex_coord[0], tex_coord[2]), max(tex_coord[1], tex_coord[3])); + if (!isNaN(tex_coord[2]) && !isNaN(tex_coord[3])) { + varying_tex_coord_min = vec2(min(tex_coord[0], tex_coord[2]), min(tex_coord[1], tex_coord[3])); + varying_tex_coord_max = vec2(max(tex_coord[0], tex_coord[2]), max(tex_coord[1], tex_coord[3])); + } else { + varying_tex_coord_min = vec2(0, 0); + varying_tex_coord_max = vec2(1, 1); + } varying_color_scale = color_scale; gl_Position = projection_matrix * vec4(vertex, 0, 1); } diff --git a/internal/graphicsutil/vertices.go b/internal/graphicsutil/vertices.go index f57895212..785361d56 100644 --- a/internal/graphicsutil/vertices.go +++ b/internal/graphicsutil/vertices.go @@ -15,6 +15,8 @@ package graphicsutil import ( + "math" + "github.com/hajimehoshi/ebiten/internal/graphics" "github.com/hajimehoshi/ebiten/internal/opengl" ) @@ -135,3 +137,35 @@ var ( func QuadIndices() []uint16 { return quadIndices } + +var ( + nan32 = float32(math.NaN()) +) + +func Vertex(width, height int, dx, dy, sx, sy float32, cr, cg, cb, ca float32) []float32 { + if !isPowerOf2(width) { + panic("not reached") + } + if !isPowerOf2(height) { + panic("not reached") + } + + wf := float32(width) + hf := float32(height) + + // Specifying a range explicitly here is redundant but this helps optimization + // to eliminate boundry checks. + vs := theVerticesBackend.sliceForOneQuad()[0:10] + vs[0] = dx + vs[1] = dy + vs[2] = sx / wf + vs[3] = sy / hf + vs[4] = nan32 + vs[5] = nan32 + vs[6] = cr + vs[7] = cg + vs[8] = cb + vs[9] = ca + + return vs +} diff --git a/internal/shareable/shareable.go b/internal/shareable/shareable.go index 03db8324e..d54d7bd75 100644 --- a/internal/shareable/shareable.go +++ b/internal/shareable/shareable.go @@ -193,6 +193,15 @@ func (i *Image) QuadVertices(sx0, sy0, sx1, sy1 int, a, b, c, d, tx, ty float32, return graphicsutil.QuadVertices(w, h, sx0+ox, sy0+oy, sx1+ox, sy1+oy, a, b, c, d, tx, ty, cr, cg, cb, ca) } +func (i *Image) Vertex(dx, dy, sx, sy float32, cr, cg, cb, ca float32) []float32 { + if i.backend == nil { + i.allocate(true) + } + ox, oy, _, _ := i.region() + w, h := i.backend.restorable.SizePowerOf2() + return graphicsutil.Vertex(w, h, dx, dy, sx+float32(ox), sy+float32(oy), cr, cg, cb, ca) +} + const MaxCountForShare = 10 func (i *Image) DrawImage(img *Image, vertices []float32, indices []uint16, colorm *affine.ColorM, mode opengl.CompositeMode, filter graphics.Filter) {