-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathfsnipproc.go
More file actions
212 lines (188 loc) · 5 KB
/
fsnipproc.go
File metadata and controls
212 lines (188 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
package graphics2d
import (
"math"
"github.com/jphsd/graphics2d/util"
)
// FSnipProc contains the snip pattern and offset. The snip pattern represents lengths of state0, state1,
// ... stateN-1, and is in the same coordinate system as the path. The offset provides the ability to
// start from anywhere in the pattern.
type FSnipProc struct {
N int
Pattern []float64
Flatten float64
State int
length float64
patind int
delta float64
}
// SnipProc preserves the curve order of the parts. FSnipProc flattens the path so all parts are linear.
// NewFSnipProc creates a new snip path processor with the supplied pattern and offset. If the pattern is
// not N in length then it is replicated to create a mod N length pattern.
func NewFSnipProc(n int, pattern []float64, offs float64) *FSnipProc {
pat := pattern[:]
for len(pat)%n != 0 {
pat = append(pat, pattern...)
}
s := sum(pat)
res := &FSnipProc{n, pat, RenderFlatten, 0, s, 0, 0}
res.Offset(offs)
return res
}
// Offset determines where in the pattern the path processor will start.
func (sp *FSnipProc) Offset(offs float64) {
neg := offs < 0
if neg {
offs = -offs
}
f := offs / sp.length
if f > 1 {
f = math.Floor(f)
offs -= f * sp.length
}
if neg {
offs = sp.length - offs
}
// Figure out initial state, pattern index and delta based on offset.
state := 0
patind := 0 // which part of the pattern we're on
delta := sp.Pattern[patind] // distance to the next state change
// Walk to offset in pattern
for true {
if offs > delta {
offs -= delta
patind++
state++
if state == sp.N {
state = 0
}
delta = sp.Pattern[patind]
continue
}
// offs is > 0 and < delta
delta -= offs
break
}
if util.Equals(delta, 0) {
state++
if state == sp.N {
state = 0
}
patind++
if patind == len(sp.Pattern) {
patind = 0
}
delta = sp.Pattern[patind]
}
sp.State = state
sp.patind = patind
sp.delta = delta
}
// Process implements the PathProcessor interface.
func (sp *FSnipProc) Process(p *Path) []*Path {
// Check path isn't a point
if len(p.Steps()) == 0 {
return []*Path{p}
}
// Flatten the path and get the parts as lines
path := p.Flatten(sp.Flatten).Process(&StepsToLinesProc{false})[0]
parts := path.Parts()
// Pattern state variables
patind := sp.patind
delta := sp.delta
// Part state variables
pi := 0
cur, end := parts[pi][0], parts[pi][1]
avail := math.Hypot(end[0]-cur[0], end[1]-cur[1])
sparts := []Part{}
// Turn parts into snip paths
res := []*Path{}
for {
// Collect parts until we have met the delta
// unless we run out of parts, in which case we're done
// Once delta met, add path to res, bump State, patind and delta
// and continue
if avail < delta {
sparts = append(sparts, Part{cur, end})
delta -= avail
pi++
if pi > len(parts)-1 {
return append(res, PartsToPath(sparts...))
}
cur = end
end = parts[pi][1]
avail = math.Hypot(end[0]-cur[0], end[1]-cur[1])
} else {
// avail >= delta
t := delta / avail
omt := 1 - t
tmp := []float64{cur[0]*omt + end[0]*t, cur[1]*omt + end[1]*t}
sparts = append(sparts, Part{cur, tmp})
avail -= delta
res = append(res, PartsToPath(sparts...))
sparts = nil
patind++
if patind == len(sp.Pattern) {
patind = 0
}
delta = sp.Pattern[patind]
if util.Equals(avail, 0) {
pi++
if pi > len(parts)-1 {
return res
}
cur = end
end = parts[pi][1]
avail = math.Hypot(end[0]-cur[0], end[1]-cur[1])
} else {
cur = tmp
}
}
}
return res
}
// DashProc contains the dash pattern and offset. The dash pattern represents lengths of pen down, pen up,
// ... and is in the same coordinate system as the path. The offset provides the ability to start from
// anywhere in the pattern.
type DashProc struct {
Snip *FSnipProc
}
// NewDashProc creates a new dash path processor with the supplied pattern and offset. If the pattern is
// odd in length then it is replicated to create an even length pattern.
func NewDashProc(pattern []float64, offs float64) DashProc {
return DashProc{NewFSnipProc(2, pattern, offs)}
}
// Process implements the PathProcessor interface.
func (d DashProc) Process(p *Path) []*Path {
paths := d.Snip.Process(p)
np := len(paths)
dp := np / 2
res1 := make([]*Path, 0, dp+1)
res2 := make([]*Path, 0, dp+1)
for i := range np {
if i%2 == 0 {
res1 = append(res1, paths[i])
} else {
res2 = append(res2, paths[i])
}
}
if d.Snip.State == 0 {
return res1
}
return res2
}
// MunchProc contains the munching compound path processor.
type MunchProc struct {
Comp CompoundProc
}
// NewMunchProc creates a munching path processor. It calculates points along a path spaced l apart
// and creates new paths that join the points with lines.
func NewMunchProc(l float64) MunchProc {
if l < 0 {
l = -l
}
return MunchProc{NewCompoundProc(NewFSnipProc(2, []float64{l, l}, 0), PathToLineProc{})}
}
// Process implements the PathProcessor interface.
func (mp MunchProc) Process(p *Path) []*Path {
return p.Process(mp.Comp)
}