diff --git a/examples/lights/main.go b/examples/lights/main.go new file mode 100644 index 0000000..f778184 --- /dev/null +++ b/examples/lights/main.go @@ -0,0 +1,210 @@ +package main + +import ( + "fmt" + "image" + "math" + "os" + "time" + + _ "image/jpeg" + _ "image/png" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" +) + +func loadPicture(path string) (pixel.Picture, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + return pixel.PictureDataFromImage(img), nil +} + +type drawer interface { + Draw(pixel.Target) +} + +type colorlight struct { + color pixel.RGBA + point pixel.Vec + angle float64 + radius float64 + dust float64 + + spread float64 + + imd *imdraw.IMDraw +} + +func (cl *colorlight) apply(src, noise drawer, tmp, dst *pixelgl.Canvas) { + // create the light arc if not created already + if cl.imd == nil { + imd := imdraw.New(nil) + imd.Color(pixel.Alpha(1)) + imd.Push(0) + imd.Color(pixel.Alpha(0)) + for angle := -cl.spread / 2; angle <= cl.spread/2; angle += cl.spread / 64 { + imd.Push(pixel.X(1).Rotated(angle)) + } + imd.Polygon(0) + cl.imd = imd + } + + // draw the light arc + tmp.Clear(pixel.Alpha(0)) + tmp.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, cl.angle).Moved(cl.point)) + tmp.SetColorMask(pixel.Alpha(1)) + tmp.SetComposeMethod(pixel.ComposeCopy) + cl.imd.Draw(tmp) + + // draw the noise inside the light + tmp.SetMatrix(pixel.IM) + tmp.SetComposeMethod(pixel.ComposeIn) + noise.Draw(tmp) + + // draw an image inside the noisy light + tmp.SetColorMask(cl.color) + tmp.SetComposeMethod(pixel.ComposeIn) + src.Draw(tmp) + + // draw the light reflected from the dust + tmp.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, cl.angle).Moved(cl.point)) + tmp.SetColorMask(cl.color.Mul(pixel.Alpha(cl.dust))) + tmp.SetComposeMethod(pixel.ComposeOver) + cl.imd.Draw(tmp) + + // draw the result to the dst + dst.SetColorMask(pixel.Alpha(1)) + dst.SetComposeMethod(pixel.ComposePlus) + tmp.Draw(dst) +} + +func run() { + pandaPic, err := loadPicture("panda.png") + if err != nil { + panic(err) + } + noisePic, err := loadPicture("noise.png") + if err != nil { + panic(err) + } + + cfg := pixelgl.WindowConfig{ + Title: "Color Light", + Bounds: pixel.R(0, 0, 1024, 768), + VSync: true, + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + panda := pixel.NewSprite(pandaPic, pandaPic.Bounds()) + panda.SetMatrix(pixel.IM.Moved(win.Bounds().Center())) + noise := pixel.NewSprite(noisePic, noisePic.Bounds()) + noise.SetMatrix(pixel.IM.Moved(win.Bounds().Center())) + + colors := []pixel.RGBA{ + pixel.RGB(1, 0, 0), + pixel.RGB(0, 1, 0), + pixel.RGB(0, 0, 1), + pixel.RGB(1/math.Sqrt2, 1/math.Sqrt2, 0), + } + + points := []pixel.Vec{ + pixel.V(win.Bounds().Min.X(), win.Bounds().Min.Y()), + pixel.V(win.Bounds().Max.X(), win.Bounds().Min.Y()), + pixel.V(win.Bounds().Max.X(), win.Bounds().Max.Y()), + pixel.V(win.Bounds().Min.X(), win.Bounds().Max.Y()), + } + + angles := []float64{ + math.Pi / 4, + math.Pi/4 + math.Pi/2, + math.Pi/4 + 2*math.Pi/2, + math.Pi/4 + 3*math.Pi/2, + } + + lights := make([]colorlight, 4) + for i := range lights { + lights[i] = colorlight{ + color: colors[i], + point: points[i], + angle: angles[i], + radius: 800, + dust: 0.3, + spread: math.Pi / math.E, + } + } + + speed := []float64{11.0 / 23, 13.0 / 23, 17.0 / 23, 19.0 / 23} + + tmp := pixelgl.NewCanvas(win.Bounds()) + dst := pixelgl.NewCanvas(win.Bounds()) + + var ( + frames = 0 + second = time.Tick(time.Second) + fps30 = time.Tick(time.Second / 30) + ) + + start := time.Now() + for !win.Closed() { + if win.Pressed(pixelgl.KeyW) { + for i := range lights { + lights[i].dust += 0.05 + if lights[i].dust > 1 { + lights[i].dust = 1 + } + } + } + if win.Pressed(pixelgl.KeyS) { + for i := range lights { + lights[i].dust -= 0.05 + if lights[i].dust < 0 { + lights[i].dust = 0 + } + } + } + + since := time.Since(start).Seconds() + for i := range lights { + lights[i].angle = angles[i] + math.Sin(since*speed[i])*math.Pi/8 + } + + win.Clear(pixel.RGB(0, 0, 0)) + dst.Clear(pixel.Alpha(0)) + + dst.SetColorMask(pixel.Alpha(0.4)) + dst.SetComposeMethod(pixel.ComposeOver) + panda.Draw(dst) + + for i := range lights { + lights[i].apply(panda, noise, tmp, dst) + } + + dst.Draw(win) + win.Update() + + <-fps30 // maintain 30 fps, because my computer couldn't handle 60 here + frames++ + select { + case <-second: + win.SetTitle(fmt.Sprintf("%s | FPS: %d", cfg.Title, frames)) + frames = 0 + default: + } + } +} + +func main() { + pixelgl.Run(run) +} diff --git a/examples/lights/noise.png b/examples/lights/noise.png new file mode 100644 index 0000000..4e52c8c Binary files /dev/null and b/examples/lights/noise.png differ diff --git a/examples/lights/panda.png b/examples/lights/panda.png new file mode 100644 index 0000000..9a987d3 Binary files /dev/null and b/examples/lights/panda.png differ