PageRenderTime 49ms CodeModel.GetById 35ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 1ms

/qcs/qcs.go

https://code.google.com/p/goxscr/
Go | 241 lines | 195 code | 34 blank | 12 comment | 29 complexity | e108646cc14f4c7bc2309e19306a4e91 MD5 | raw file
  1// QuasiCrystals
  2package main
  3
  4import (
  5	"code.google.com/p/x-go-binding/ui"
  6	"code.google.com/p/x-go-binding/ui/x11"
  7	"flag"
  8	"fmt"
  9	"image"
 10	"image/color"
 11	"image/draw"
 12	"math"
 13	"math/rand"
 14	"os"
 15	"runtime"
 16	"time"
 17)
 18
 19const Degree = math.Pi / 180
 20
 21var palette = make([]image.Image, 256)
 22
 23type point struct {
 24	x, y float64
 25}
 26
 27var workers = flag.Int("w", 3, "workers")
 28var frames = flag.Int64("f", 30, "max framerate")
 29var randomize = flag.Bool("r", false, "randomize size, scale, degree and phi")
 30
 31var phi = flag.Float64("phi", 5, "step phase change")
 32var size = flag.Int("size", 300, "crystal size")
 33var scale = flag.Float64("scale", 30, "scale")
 34var degree = flag.Int("degree", 5, "degree")
 35
 36type Work struct {
 37	e    int        // frame number to compute.
 38	img  draw.Image // image to write to.
 39	done chan bool  // send on this channel when work is done.
 40}
 41
 42func init() {
 43	for i := range palette {
 44		palette[i] = image.NewUniform(color.Gray{byte(i)})
 45	}
 46}
 47
 48func pt(x, y int) point {
 49	denom := float64(*size) - 1
 50	X := *scale * ((float64(2*x) / denom) - 1)
 51	Y := *scale * ((float64(2*y) / denom) - 1)
 52	return point{X, Y}
 53}
 54
 55func transform(θ float64, p point) point {
 56	sin, cos := math.Sincos(θ)
 57	p.x = p.x*cos - p.y*sin
 58	p.y = p.x*sin + p.y*cos
 59	return p
 60}
 61
 62func worker(wc <-chan *Work) {
 63	buf := make([]byte, *size**size)
 64	sz := *size
 65
 66	for w := range wc {
 67		r := w.img.Bounds()
 68		dx := r.Dx()
 69		dy := r.Dy()
 70
 71		stridex := 1 + dx/sz // how big is each pixel from our crystal
 72		stridey := 1 + dy/sz
 73
 74		ϕ := float64(w.e) * (*phi) * Degree
 75		quasicrystal(sz, *degree, ϕ, buf)
 76
 77		for y := 0; y < sz; y++ {
 78			if y*stridey > dy {
 79				break
 80			}
 81			for x := 0; x < sz; x++ {
 82				if x*stridex > dx {
 83					break
 84				}
 85				nr := image.Rect(x*stridex, y*stridey, x*stridex+stridex, y*stridey+stridey)
 86				draw.Draw(w.img, nr, palette[buf[y*sz+x]], image.ZP, draw.Src)
 87			}
 88		}
 89		w.done <- true
 90	}
 91}
 92
 93func main() {
 94	flag.Parse()
 95
 96	runtime.GOMAXPROCS(*workers + 1)
 97
 98	rand.Seed(time.Now().UnixNano())
 99
100	if *randomize {
101		*phi = rand.Float64() * 10
102		*size = 100 + rand.Intn(200)
103		*scale = 25 + rand.Float64()*10
104		*degree = 3 + rand.Intn(5)
105	}
106
107	window, err := x11.NewWindow()
108	if err != nil {
109		fmt.Fprintf(os.Stderr, "error:", err.Error())
110		return
111	}
112	quit := make(chan chan<- stats)
113	go painter(window, quit)
114
115loop:
116	for e := range window.EventChan() {
117		switch f := e.(type) {
118		case ui.MouseEvent:
119		case ui.KeyEvent:
120			if f.Key == 65307 { // ESC
121				break loop
122			}
123		case ui.ConfigEvent:
124			// nothing for now
125		case ui.ErrEvent:
126			break loop
127		}
128	}
129
130	c := make(chan stats)
131	quit <- c
132	st := <-c
133	fmt.Printf("fps: %.1f, spf %.0fms, dev %.0fms\n", 1e9/st.mean, st.mean/1e6, st.stddev/1e6)
134}
135
136type stats struct {
137	mean   float64
138	stddev float64
139}
140
141func painter(win ui.Window, quit <-chan chan<- stats) {
142	ticker := time.NewTicker(1e9 / time.Duration(*frames))
143	screen := win.Screen()
144	r := screen.Bounds()
145
146	// make more work items than workers so that we
147	// can keep a worker busy even when the last frame
148	// that it has computed has not yet been retrieved by the
149	// painter loop.
150	work := make([]Work, *workers*2)
151	workChan := make(chan *Work)
152
153	for i := 0; i < *workers; i++ {
154		go worker(workChan)
155	}
156
157	e := 0
158	frames := 0
159
160	now := time.Now()
161	start := now
162	var sumdt2 float64
163
164	// continuously cycle through the array of work items,
165	// waiting for each to be done in turn.
166	for {
167		for i := range work {
168			w := &work[i]
169			if w.img == nil {
170				// If this is the first time we've used a work item, so make the image
171				// and the done channel. There's no image calculated yet.
172				w.img = image.NewRGBA(screen.Bounds())
173				w.done = make(chan bool, 1)
174			} else {
175				<-w.done
176				draw.Draw(screen, r, w.img, image.ZP, draw.Src)
177				win.FlushImage()
178				frames++
179				// wait for the next tick event or to be asked to quit.
180				select {
181				case t := <-ticker.C:
182					dt := t.Sub(now)
183					sumdt2 += float64(dt * dt)
184					now = t
185				case c := <-quit:
186					mean := float64((now.Sub(start)) / time.Duration(frames))
187					c <- stats{
188						mean:   mean,
189						stddev: math.Sqrt((sumdt2 / float64(frames)) - mean*mean),
190					}
191					return
192				}
193			}
194
195			// start the new work item running on any worker that's available.
196			w.e = e
197			e++
198			workChan <- w
199		}
200	}
201}
202
203func wave(ϕ, θ float64, p point) float64 {
204	sin, cos := math.Sincos(θ)
205	return (math.Cos(cos*p.x+sin*p.y+ϕ) + 1.0) / 2.0
206}
207
208func wave1(ϕ, θ float64, p point) float64 {
209	if θ != 0.0 {
210		p = transform(θ, p)
211	}
212	sin, cos := math.Sincos(ϕ)
213	return (math.Cos(cos*p.x+sin*p.y) + 1.0) / 2.0
214}
215
216func wave2(ϕ, θ float64, p point) float64 {
217	if θ != 0.0 {
218		p = transform(θ, p)
219	}
220	return (math.Cos(ϕ+p.y) + 1.) / 2.0
221}
222
223func quasicrystal(size, degree int, ϕ float64, buf []byte) {
224	for y := 0; y < size; y++ {
225		for x := 0; x < size; x++ {
226			θ := 0 * Degree
227			p := pt(x, y)
228			acc := wave(ϕ, θ, p)
229			for d := 1; d < degree; d++ {
230				θ += 180 * Degree / float64(degree)
231				if d%2 == 1 {
232					acc += 1 - wave(ϕ, θ, p)
233				} else {
234					acc += wave(ϕ, θ, p)
235				}
236			}
237			buf[y*size+x] = byte(acc * 255.0)
238		}
239	}
240	return
241}