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  }