1package basemap23import (4 "math"56 "golang.org/x/image/vector"7)89// kappa is the control-point offset that makes four cubic Béziers spell a10// circle as closely as cubics can.11const kappa = 0.55228474983079361213// strokeSegment adds one capsule to the path: the segment widened to its14// stroke, with a semicircular cap at each end. Every capsule winds the same15// way, and the rasterizer's saturating accumulation renders the union --16// overlapping capsules at a joint read as one continuous line, no seams.17// Coordinates are tile-local pixels.18func strokeSegment(r *vector.Rasterizer, a, b [2]float64, width float64) {19 half := width / 220 dx, dy := b[0]-a[0], b[1]-a[1]21 length := math.Hypot(dx, dy)22 if length == 0 {23 return24 }25 // The unit direction and left normal, scaled.26 ux, uy := dx/length, dy/length27 nx, ny := -uy*half, ux*half28 k := kappa * half2930 moveTo(r, a[0]+nx, a[1]+ny)31 lineTo(r, b[0]+nx, b[1]+ny)32 // The far cap: from b's left shoulder, around the nose at b+u*half, to33 // b's right shoulder, two quarter arcs. A tangent control point moves34 // k along the tangent; n is already half long, so n scaled by kappa is35 // the same k in the normal's direction.36 cubeTo(r,37 b[0]+nx+ux*k, b[1]+ny+uy*k,38 b[0]+ux*half+nx*kappa, b[1]+uy*half+ny*kappa,39 b[0]+ux*half, b[1]+uy*half)40 cubeTo(r,41 b[0]+ux*half-nx*kappa, b[1]+uy*half-ny*kappa,42 b[0]-nx+ux*k, b[1]-ny+uy*k,43 b[0]-nx, b[1]-ny)44 lineTo(r, a[0]-nx, a[1]-ny)45 // The near cap, back around a-u*half.46 cubeTo(r,47 a[0]-nx-ux*k, a[1]-ny-uy*k,48 a[0]-ux*half-nx*kappa, a[1]-uy*half-ny*kappa,49 a[0]-ux*half, a[1]-uy*half)50 cubeTo(r,51 a[0]-ux*half+nx*kappa, a[1]-uy*half+ny*kappa,52 a[0]+nx-ux*k, a[1]+ny-uy*k,53 a[0]+nx, a[1]+ny)54 r.ClosePath()55}5657// strokeDashed walks a segment in the dash rhythm, stroking the on-runs.58// The phase rides along the polyline so the rhythm survives vertices, and59// the caller threads it between segments.60func strokeDashed(r *vector.Rasterizer, a, b [2]float64, width float64, dash [2]float64, phase float64) float64 {61 on, off := dash[0], dash[1]62 period := on + off63 if period <= 0 {64 strokeSegment(r, a, b, width)65 return phase66 }67 dx, dy := b[0]-a[0], b[1]-a[1]68 length := math.Hypot(dx, dy)69 if length == 0 {70 return phase71 }72 ux, uy := dx/length, dy/length73 at := 0.074 for at < length {75 place := math.Mod(phase+at, period)76 if place < on {77 run := math.Min(on-place, length-at)78 from := [2]float64{a[0] + ux*at, a[1] + uy*at}79 to := [2]float64{a[0] + ux*(at+run), a[1] + uy*(at+run)}80 strokeSegment(r, from, to, width)81 at += run82 } else {83 at += period - place84 }85 }86 return math.Mod(phase+length, period)87}8889func moveTo(r *vector.Rasterizer, x, y float64) { r.MoveTo(float32(x), float32(y)) }90func lineTo(r *vector.Rasterizer, x, y float64) { r.LineTo(float32(x), float32(y)) }91func cubeTo(r *vector.Rasterizer, x1, y1, x2, y2, x3, y3 float64) {92 r.CubeTo(float32(x1), float32(y1), float32(x2), float32(y2), float32(x3), float32(y3))93}