github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/pixel/wide-line/main.go (about) 1 package main 2 3 import ( 4 "image/color" 5 "math" 6 7 "github.com/faiface/pixel" 8 "github.com/faiface/pixel/imdraw" 9 "github.com/faiface/pixel/pixelgl" 10 ) 11 12 func main() { 13 pixelgl.Run(run) 14 } 15 16 func run() { 17 cfg := pixelgl.WindowConfig{ 18 Title: "Flowers!", 19 Bounds: pixel.R(0, 0, 480, 520), 20 Resizable: false, 21 VSync: true, 22 } 23 24 win, err := pixelgl.NewWindow(cfg) 25 if err != nil { 26 panic(err) 27 } 28 29 canvas := imdraw.New(nil) 30 canvas.Color = color.RGBA{0x00, 0x00, 0x00, 0x80} 31 { // internal line 32 y := 64.0 33 for line := 1.0; line < 64; line *= 2 { 34 canvas.Push( 35 pixel.V(50, y), 36 pixel.V(100, y-16), 37 pixel.V(150, y+16), 38 pixel.V(200, y-16), 39 pixel.V(100, y-64), 40 ) 41 canvas.Line(line) 42 y += 80 43 } 44 } 45 46 { // better line 47 y := 64.0 48 for line := 1.0; line < 64; line *= 2 { 49 drawLine( 50 canvas, 51 line, 52 pixel.V(240+50, y), 53 pixel.V(240+100, y-16), 54 pixel.V(240+150, y+16), 55 pixel.V(240+200, y-16), 56 pixel.V(240+100, y-64), 57 ) 58 y += 80 59 } 60 } 61 62 for !win.Closed() { 63 win.SetClosed(win.JustPressed(pixelgl.KeyEscape)) 64 win.Clear(color.RGBA{0xFF, 0xFF, 0xFF, 0xFF}) 65 canvas.Draw(win) 66 win.Update() 67 } 68 } 69 70 func drawLine(m *imdraw.IMDraw, width float64, path ...pixel.Vec) { 71 if len(path) < 2 { 72 return 73 } 74 75 radius := width / 2 76 77 // draw each segment, where 78 // 79 // x1--^------a1-------^----------b1 80 // | xn | | abn | 81 // -----------a - - - - - - - - - b 82 // | | 83 // x2---------a2------------------b2 84 // drawing this segment 85 // |-------------------| 86 // ^__ this will contain a bend 87 // x1, x2, xn are the previous segments end 88 // corners and normal 89 90 a := path[0] 91 var x1, x2, xn pixel.Vec 92 for i, b := range path { 93 if i > 0 && a.Sub(b).Len() < 1 { 94 continue 95 } 96 // calculate 97 abn := ScaleTo(SegmentNormal(a, b), radius) 98 // segment-corners 99 a1, a2 := a.Add(abn), a.Sub(abn) 100 b1, b2 := b.Add(abn), b.Sub(abn) 101 102 // fill the gap between bends 103 if i > 0 && radius > 1.5 { 104 // see which direction is the gap in 105 d := Rotate(xn).Dot(abn) 106 if d < 0 { 107 m.Push(x1, a1, a) 108 } else { 109 m.Push(x2, a2, a) 110 } 111 m.Polygon(0) 112 } 113 114 // draw the segment 115 m.Push(a1, b1, b2, a2) 116 m.Polygon(0) 117 118 // update points 119 a = b 120 x1, x2, xn = b1, b2, abn 121 } 122 } 123 124 func drawClosedLine(m *imdraw.IMDraw, width float64, path ...pixel.Vec) { 125 if len(path) < 2 { 126 return 127 } 128 129 radius := width / 2 130 131 // draw each segment, where 132 // 133 // x1--^------a1-------^----------b1 134 // | xn | | abn | 135 // -----------a - - - - - - - - - b 136 // | | 137 // x2---------a2------------------b2 138 // drawing this segment 139 // |-------------------| 140 // ^__ this will contain a bend 141 // x1, x2, xn are the previous segments end 142 // corners and normal 143 a := path[len(path)-1] 144 xn := ScaleTo(SegmentNormal(path[len(path)-2], a), radius) 145 x1, x2 := a.Add(xn), a.Sub(xn) 146 147 for i, b := range path { 148 if i > 0 && a.Sub(b).Len() < 1 { 149 continue 150 } 151 // calculate 152 abn := ScaleTo(SegmentNormal(a, b), radius) 153 // segment-corners 154 a1, a2 := a.Add(abn), a.Sub(abn) 155 b1, b2 := b.Add(abn), b.Sub(abn) 156 157 // fill the gap between bends 158 if radius > 1.5 { 159 // see which direction is the gap in 160 d := Rotate(xn).Dot(abn) 161 if d < 0 { 162 m.Push(x1, a1, a) 163 } else { 164 m.Push(x2, a2, a) 165 } 166 m.Polygon(0) 167 } 168 169 // draw the segment 170 m.Push(a1, b1, b2, a2) 171 m.Polygon(0) 172 173 // update points 174 a = b 175 x1, x2, xn = b1, b2, abn 176 } 177 } 178 179 const TAU = 2 * math.Pi 180 181 func SegmentNormal(a, b pixel.Vec) pixel.Vec { 182 return Rotate(b.Sub(a)) 183 } 184 185 func Rotate(a pixel.Vec) pixel.Vec { 186 return pixel.V(-a.Y, a.X) 187 } 188 189 func ScaleTo(a pixel.Vec, r float64) pixel.Vec { 190 x := a.Len() 191 return a.Scaled(r / x) 192 }