1use super::super::cpu::{self, Paint};
2use super::super::icons::Icons;
3use super::super::sdf;
4use super::super::{
5 DASHED, FILL_SOLID, FILL_VERTICAL, Instance, KIND_CIRCLE, KIND_ICON_FILL, KIND_ICON_LINE,
6 KIND_RECT, KIND_RING, PATTERN_MASK, PATTERN_SHIFT,
7};
8
9pub const ROWS: u32 = 4;
10pub const STEPS: u32 = 48;
11pub const FLOOR: f32 = 0.35;
12
13fn distance(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> f32 {
14 let kind = instance.kind();
15 if kind == KIND_ICON_FILL || kind == KIND_ICON_LINE {
16 let [cx, cy, hx, hy] = instance.shape;
17 let c = [p[0].clamp(cx - hx, cx + hx), p[1].clamp(cy - hy, cy + hy)];
18 let outside = ((p[0] - c[0]).powi(2) + (p[1] - c[1]).powi(2)).sqrt();
19 if outside > 0.0 {
20 return outside + 0.5 * instance.stroke[0];
21 }
22 return cpu::field(instance, icons, c)[0];
23 }
24 cpu::field(instance, icons, p)[0]
25}
26
27fn band(instance: &Instance, icons: Option<&Icons>, stroke: bool, p: [f32; 2]) -> f32 {
28 let d = distance(instance, icons, p);
29 if !stroke {
30 return d;
31 }
32 let band = d.abs() - instance.stroke[0] * 0.5;
33 if instance.info[0] & DASHED == 0 {
34 return band;
35 }
36 let along = if instance.kind() == KIND_RECT {
37 sdf::rect_along(p, [instance.shape[0], instance.shape[1]], instance.corners)
38 } else {
39 sdf::circle_along(p, instance.shape[0])
40 };
41 let dash = sdf::dash(
42 along,
43 instance.stroke[1],
44 instance.stroke[2],
45 instance.stroke[3],
46 );
47 band.max(dash[0])
48}
49
50pub fn cdf(x: f32, span: f32) -> f32 {
51 let hi = 0.5 * (span + 1.0);
52 let lo = 0.5 * (span - 1.0).abs();
53 let ramp = span.min(1.0);
54 let height = 1.0 / span.max(1.0);
55 if x <= -hi {
56 0.0
57 } else if x >= hi {
58 1.0
59 } else if x < -lo {
60 let u = x + hi;
61 0.5 * height / ramp * u * u
62 } else if x > lo {
63 let u = hi - x;
64 1.0 - 0.5 * height / ramp * u * u
65 } else {
66 0.5 * height * ramp + height * (x + lo)
67 }
68}
69
70#[allow(clippy::too_many_arguments)]
71fn line(
72 instance: &Instance,
73 icons: Option<&Icons>,
74 stroke: bool,
75 start: [f32; 2],
76 along: [f32; 2],
77 unit: f32,
78 span: f32,
79) -> f32 {
80 let at = |x: f32| [start[0] + along[0] * x, start[1] + along[1] * x];
81 let hi = 0.5 * (span + 1.0);
82 let least = FLOOR.max(2.0 * hi / (STEPS - 8) as f32);
83 let mut x = -hi;
84 let mut s0 = band(instance, icons, stroke, at(x)) / unit;
85 let mut total = 0.0;
86 for _ in 0..STEPS {
87 if x >= hi {
88 break;
89 }
90 let x1 = (x + s0.abs().max(least)).min(hi);
91 let s1 = band(instance, icons, stroke, at(x1)) / unit;
92 if s0 <= 0.0 && s1 <= 0.0 {
93 total += cdf(x1, span) - cdf(x, span);
94 } else if s0 <= 0.0 {
95 let r = x + (x1 - x) * s0 / (s0 - s1);
96 total += cdf(r, span) - cdf(x, span);
97 } else if s1 <= 0.0 {
98 let r = x + (x1 - x) * s0 / (s0 - s1);
99 total += cdf(x1, span) - cdf(r, span);
100 }
101 x = x1;
102 s0 = s1;
103 }
104 if x < hi && s0 <= 0.0 {
105 total += 1.0 - cdf(x, span);
106 }
107 total
108}
109
110type Hit = [f32; 2];
111
112const EMPTY: Hit = [1.0, -1.0];
113
114fn merge(a: Hit, b: Hit) -> Hit {
115 if a[0] >= a[1] {
116 return b;
117 }
118 if b[0] >= b[1] {
119 return a;
120 }
121 [a[0].min(b[0]), a[1].max(b[1])]
122}
123
124fn disc(start: [f32; 2], along: [f32; 2], centre: [f32; 2], radius: f32) -> Hit {
125 if radius <= 0.0 {
126 return EMPTY;
127 }
128 let o = [start[0] - centre[0], start[1] - centre[1]];
129 let aa = along[0] * along[0] + along[1] * along[1];
130 let b = o[0] * along[0] + o[1] * along[1];
131 let c = o[0] * o[0] + o[1] * o[1] - radius * radius;
132 let d = b * b - aa * c;
133 if d <= 0.0 {
134 return EMPTY;
135 }
136 let root = d.sqrt();
137 [(-b - root) / aa, (-b + root) / aa]
138}
139
140fn slab(start: [f32; 2], along: [f32; 2], half: [f32; 2]) -> Hit {
141 if half[0] < 0.0 || half[1] < 0.0 {
142 return EMPTY;
143 }
144 let mut low = -1e30f32;
145 let mut high = 1e30f32;
146 for axis in 0..2 {
147 if along[axis].abs() < 1e-12 {
148 if start[axis].abs() > half[axis] {
149 return EMPTY;
150 }
151 } else {
152 let a = (-half[axis] - start[axis]) / along[axis];
153 let b = (half[axis] - start[axis]) / along[axis];
154 low = low.max(a.min(b));
155 high = high.min(a.max(b));
156 }
157 }
158 [low, high]
159}
160
161fn rounded(start: [f32; 2], along: [f32; 2], half: [f32; 2], radius: f32) -> Hit {
162 if half[0] <= 0.0 || half[1] <= 0.0 {
163 return EMPTY;
164 }
165 let r = radius.clamp(0.0, half[0].min(half[1]));
166 let mut hit = slab(start, along, [half[0], half[1] - r]);
167 hit = merge(hit, slab(start, along, [half[0] - r, half[1]]));
168 if r > 0.0 {
169 let c = [half[0] - r, half[1] - r];
170 for corner in [[c[0], c[1]], [-c[0], c[1]], [c[0], -c[1]], [-c[0], -c[1]]] {
171 hit = merge(hit, disc(start, along, corner, r));
172 }
173 }
174 hit
175}
176
177fn weight(hit: Hit, span: f32) -> f32 {
178 if hit[0] >= hit[1] {
179 return 0.0;
180 }
181 cdf(hit[1], span) - cdf(hit[0], span)
182}
183
184pub fn closed(instance: &Instance, stroke: bool) -> bool {
185 match instance.kind() {
186 KIND_RING => !stroke,
187 KIND_RECT => {
188 instance
189 .corners
190 .iter()
191 .all(|&radius| radius == instance.corners[0])
192 && (!stroke || instance.info[0] & DASHED == 0)
193 }
194 KIND_CIRCLE => !stroke || instance.info[0] & DASHED == 0,
195 _ => false,
196 }
197}
198
199fn row(instance: &Instance, stroke: bool, start: [f32; 2], along: [f32; 2], span: f32) -> f32 {
200 let shape = instance.shape;
201 let w = if stroke {
202 instance.stroke[0] * 0.5
203 } else {
204 0.0
205 };
206 let (outer, inner) = match instance.kind() {
207 KIND_RECT => (
208 rounded(
209 start,
210 along,
211 [shape[0] + w, shape[1] + w],
212 instance.corners[0] + w,
213 ),
214 if stroke {
215 rounded(
216 start,
217 along,
218 [shape[0] - w, shape[1] - w],
219 (instance.corners[0] - w).max(0.0),
220 )
221 } else {
222 EMPTY
223 },
224 ),
225 KIND_CIRCLE => (
226 disc(start, along, [0.0, 0.0], shape[0] + w),
227 if stroke {
228 disc(start, along, [0.0, 0.0], shape[0] - w)
229 } else {
230 EMPTY
231 },
232 ),
233 _ => (
234 disc(start, along, [0.0, 0.0], shape[0] + 0.5 * shape[1]),
235 disc(start, along, [0.0, 0.0], shape[0] - 0.5 * shape[1]),
236 ),
237 };
238 weight(outer, span) - weight(inner, span)
239}
240
241pub fn cover(instance: &Instance, icons: Option<&Icons>, stroke: bool, p: [f32; 2]) -> f32 {
242 cover_with(instance, icons, stroke, p, closed(instance, stroke))
243}
244
245pub fn cover_with(
246 instance: &Instance,
247 icons: Option<&Icons>,
248 stroke: bool,
249 p: [f32; 2],
250 closed: bool,
251) -> f32 {
252 let inverse = instance.inverse();
253 let shift = [instance.material[0], instance.material[1]];
254 let screen = [
255 instance.axes[0] * shift[0] + instance.axes[2] * shift[1],
256 instance.axes[1] * shift[0] + instance.axes[3] * shift[1],
257 ];
258 let span = (screen[0] * screen[0] + screen[1] * screen[1])
259 .sqrt()
260 .max(1e-6);
261 let direction = [screen[0] / span, screen[1] / span];
262 let map = |v: [f32; 2]| {
263 [
264 inverse[0][0] * v[0] + inverse[1][0] * v[1],
265 inverse[0][1] * v[0] + inverse[1][1] * v[1],
266 ]
267 };
268 let along = map(direction);
269 let across = map([-direction[1], direction[0]]);
270 let unit = (along[0] * along[0] + along[1] * along[1])
271 .sqrt()
272 .max(1e-12);
273 let mut total = 0.0;
274 for index in 0..ROWS {
275 let u = (index as f32 + 0.5) / ROWS as f32 - 0.5;
276 let start = [p[0] + across[0] * u, p[1] + across[1] * u];
277 total += if closed {
278 row(instance, stroke, start, along, span)
279 } else {
280 line(instance, icons, stroke, start, along, unit, span)
281 };
282 }
283 (total / ROWS as f32).clamp(0.0, 1.0)
284}
285
286fn premultiply(c: [f32; 4], cover: f32) -> [f32; 4] {
287 [
288 c[0] * c[3] * cover,
289 c[1] * c[3] * cover,
290 c[2] * c[3] * cover,
291 c[3] * cover,
292 ]
293}
294
295pub fn paint(instance: &Instance, icons: Option<&Icons>, p: [f32; 2]) -> Paint {
296 let inverse = instance.inverse();
297 let flags = instance.info[0];
298 let fill_mode = flags & (FILL_SOLID | FILL_VERTICAL);
299 let opacity = instance.opacity();
300 let mut fill = [0.0; 4];
301 let mut fill_cover = 0.0;
302 if fill_mode != 0 {
303 fill_cover = cover(instance, icons, false, p);
304 let t = if fill_mode == FILL_VERTICAL {
305 ((p[1] - instance.origin[2]) / (instance.origin[3] - instance.origin[2]))
306 .clamp(0.0, 1.0)
307 } else {
308 0.0
309 };
310 let colour: [f32; 4] = std::array::from_fn(|i| {
311 instance.fill_top[i] + (instance.fill_bottom[i] - instance.fill_top[i]) * t
312 });
313 let mut base = premultiply(colour, 1.0);
314 let pattern = (flags & PATTERN_MASK) >> PATTERN_SHIFT;
315 if pattern != 0 {
316 let packed = instance.info[2];
317 let colour = std::array::from_fn(|i| ((packed >> (8 * i)) & 255) as f32 / 255.0);
318 let cover = sdf::coverage(sdf::pattern(pattern, instance.icon, p), inverse);
319 let paint = premultiply(colour, cover);
320 base = std::array::from_fn(|i| paint[i] + base[i] * (1.0 - paint[3]));
321 }
322 fill = base.map(|value| value * fill_cover);
323 }
324 let mut stroke = [0.0; 4];
325 let mut stroke_cover = 0.0;
326 if instance.stroke[0] > 0.0 {
327 stroke_cover = cover(instance, icons, true, p);
328 stroke = premultiply(instance.stroke_colour, stroke_cover);
329 }
330 let colour = std::array::from_fn(|i| (stroke[i] + fill[i] * (1.0 - stroke[3])) * opacity);
331 let fill_seen = if fill_mode != 0 && instance.fill_top[3].max(instance.fill_bottom[3]) > 0.0 {
332 fill_cover
333 } else {
334 0.0
335 };
336 let stroke_seen = if instance.stroke_colour[3] > 0.0 {
337 stroke_cover
338 } else {
339 0.0
340 };
341 Paint {
342 colour,
343 footprint: if opacity > 0.0 {
344 fill_seen.max(stroke_seen)
345 } else {
346 0.0
347 },
348 }
349}