Cleanup: Grey --> Gray
[blender.git] / release / scripts / freestyle / modules / freestyle / shaders.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 #  Filename : shaders.py
20 #  Authors  : Fredo Durand, Stephane Grabli, Francois Sillion, Emmanuel Turquin
21 #  Date     : 11/08/2005
22 #  Purpose  : Stroke shaders to be used for creation of stylized strokes
23
24 """
25 This module contains stroke shaders used for creation of stylized
26 strokes.  It is also intended to be a collection of examples for
27 shader definition in Python.
28
29 User-defined stroke shaders inherit the
30 :class:`freestyle.types.StrokeShader` class.
31 """
32
33 __all__ = (
34     "BackboneStretcherShader",
35     "BezierCurveShader",
36     "BlenderTextureShader",
37     "CalligraphicShader",
38     "ColorNoiseShader",
39     "ConstantColorShader",
40     "ConstantThicknessShader",
41     "ConstrainedIncreasingThicknessShader",
42     "GuidingLinesShader",
43     "IncreasingColorShader",
44     "IncreasingThicknessShader",
45     "PolygonalizationShader",
46     "RoundCapShader",
47     "SamplingShader",
48     "SmoothingShader",
49     "SpatialNoiseShader",
50     "SquareCapShader",
51     "StrokeTextureStepShader",
52     "ThicknessNoiseShader",
53     "TipRemoverShader",
54     "py2DCurvatureColorShader",
55     "pyBackboneStretcherNoCuspShader",
56     "pyBackboneStretcherShader",
57     "pyBluePrintCirclesShader",
58     "pyBluePrintDirectedSquaresShader",
59     "pyBluePrintEllipsesShader",
60     "pyBluePrintSquaresShader",
61     "pyConstantColorShader",
62     "pyConstantThicknessShader",
63     "pyConstrainedIncreasingThicknessShader",
64     "pyDecreasingThicknessShader",
65     "pyDepthDiscontinuityThicknessShader",
66     "pyDiffusion2Shader",
67     "pyFXSVaryingThicknessWithDensityShader",
68     "pyGuidingLineShader",
69     "pyHLRShader",
70     "pyImportance2DThicknessShader",
71     "pyImportance3DThicknessShader",
72     "pyIncreasingColorShader",
73     "pyIncreasingThicknessShader",
74     "pyInterpolateColorShader",
75     "pyLengthDependingBackboneStretcherShader",
76     "pyMaterialColorShader",
77     "pyModulateAlphaShader",
78     "pyNonLinearVaryingThicknessShader",
79     "pyPerlinNoise1DShader",
80     "pyPerlinNoise2DShader",
81     "pyRandomColorShader",
82     "pySLERPThicknessShader",
83     "pySamplingShader",
84     "pySinusDisplacementShader",
85     "pyTVertexRemoverShader",
86     "pyTVertexThickenerShader",
87     "pyTimeColorShader",
88     "pyTipRemoverShader",
89     "pyZDependingThicknessShader",
90     )
91
92
93 # module members
94 from _freestyle import (
95     BackboneStretcherShader,
96     BezierCurveShader,
97     BlenderTextureShader,
98     CalligraphicShader,
99     ColorNoiseShader,
100     ConstantColorShader,
101     ConstantThicknessShader,
102     ConstrainedIncreasingThicknessShader,
103     GuidingLinesShader,
104     IncreasingColorShader,
105     IncreasingThicknessShader,
106     PolygonalizationShader,
107     SamplingShader,
108     SmoothingShader,
109     SpatialNoiseShader,
110     StrokeTextureStepShader,
111     ThicknessNoiseShader,
112     TipRemoverShader,
113     )
114
115 # constructs for shader definition in Python
116 from freestyle.types import (
117     Interface0DIterator,
118     Nature,
119     Noise,
120     StrokeAttribute,
121     StrokeShader,
122     StrokeVertexIterator,
123     StrokeVertex,
124     )
125 from freestyle.functions import (
126     Curvature2DAngleF0D,
127     DensityF0D,
128     GetProjectedZF0D,
129     MaterialF0D,
130     Normal2DF0D,
131     Orientation2DF1D,
132     ZDiscontinuityF0D,
133     )
134 from freestyle.predicates import (
135     pyVertexNatureUP0D,
136     pyUEqualsUP0D,
137     )
138
139 from freestyle.utils import (
140     bound,
141     BoundingBox,
142     pairwise,
143     phase_to_direction,
144     )
145
146 from freestyle.utils import ContextFunctions as CF
147
148 import bpy
149 import random
150
151 from math import atan, cos, pi, sin, sinh, sqrt
152 from mathutils import Vector
153 from random import randint
154
155
156 # -- Thickness Stroke Shaders -- #
157
158
159 class pyDepthDiscontinuityThicknessShader(StrokeShader):
160     """
161     Assigns a thickness to the stroke based on the stroke's distance
162     to the camera (Z-value).
163     """
164     def __init__(self, min, max):
165         StrokeShader.__init__(self)
166         self.a = max - min
167         self.b = min
168         self.func = ZDiscontinuityF0D()
169
170     def shade(self, stroke):
171         it = Interface0DIterator(stroke)
172         for svert in it:
173             z = self.func(it)
174             thickness = self.a * z + self.b
175             svert.attribute.thickness = (thickness, thickness)
176
177
178 class pyConstantThicknessShader(StrokeShader):
179     """
180     Assigns a constant thickness along the stroke.
181     """
182     def __init__(self, thickness):
183         StrokeShader.__init__(self)
184         self._thickness = thickness / 2.0
185
186     def shade(self, stroke):
187         for svert in stroke:
188             svert.attribute.thickness = (self._thickness, self._thickness)
189
190
191 class pyFXSVaryingThicknessWithDensityShader(StrokeShader):
192     """
193     Assigns thickness to a stroke based on the density of the diffuse map.
194     """
195     def __init__(self, wsize, threshold_min, threshold_max, thicknessMin, thicknessMax):
196         StrokeShader.__init__(self)
197         self._func = DensityF0D(wsize)
198         self.threshold_min = threshold_min
199         self.threshold_max = threshold_max
200         self._thicknessMin = thicknessMin
201         self._thicknessMax = thicknessMax
202
203     def shade(self, stroke):
204         it = Interface0DIterator(stroke)
205         delta_threshold = self.threshold_max - self.threshold_min
206         delta_thickness = self._thicknessMax - self._thicknessMin
207
208         for svert in it:
209             c = self._func(it)
210             c = bound(self.threshold_min, c, self.threshold_max)
211             t = (self.threshold_max - c) / delta_threshold * delta_thickness + self._thicknessMin
212             svert.attribute.thickness = (t / 2.0, t / 2.0)
213
214
215 class pyIncreasingThicknessShader(StrokeShader):
216     """
217     Increasingly thickens the stroke.
218     """
219     def __init__(self, thicknessMin, thicknessMax):
220         StrokeShader.__init__(self)
221         self._thicknessMin = thicknessMin
222         self._thicknessMax = thicknessMax
223
224     def shade(self, stroke):
225         n = len(stroke)
226         for i, svert in enumerate(stroke):
227             c = i / n
228             if i < (n * 0.5):
229                 t = (1.0 - c) * self._thicknessMin + c * self._thicknessMax
230             else:
231                 t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin
232             svert.attribute.thickness = (t / 2.0, t / 2.0)
233
234
235 class pyConstrainedIncreasingThicknessShader(StrokeShader):
236     """
237     Increasingly thickens the stroke, constrained by a ratio of the
238     stroke's length.
239     """
240     def __init__(self, thicknessMin, thicknessMax, ratio):
241         StrokeShader.__init__(self)
242         self._thicknessMin = thicknessMin
243         self._thicknessMax = thicknessMax
244         self._ratio = ratio
245
246     def shade(self, stroke):
247         n = len(stroke)
248         maxT = min(self._ratio * stroke.length_2d, self._thicknessMax)
249
250         for i, svert in enumerate(stroke):
251             c = i / n
252             if i < (n * 0.5):
253                 t = (1.0 - c) * self._thicknessMin + c * maxT
254             else:
255                 t = (1.0 - c) * maxT + c * self._thicknessMin
256
257             if i == (n - 1):
258                 svert.attribute.thickness = (self._thicknessMin / 2.0, self._thicknessMin / 2.0)
259             else:
260                 svert.attribute.thickness = (t / 2.0, t / 2.0)
261
262
263 class pyDecreasingThicknessShader(StrokeShader):
264     """
265     Inverse of pyIncreasingThicknessShader, decreasingly thickens the stroke.
266     """
267     def __init__(self, thicknessMin, thicknessMax):
268         StrokeShader.__init__(self)
269         self._thicknessMin = thicknessMin
270         self._thicknessMax = thicknessMax
271
272     def shade(self, stroke):
273         l = stroke.length_2d
274         n = len(stroke)
275         tMax = min(self._thicknessMax, 0.33 * l)
276         tMin = min(self._thicknessMin, 0.10 * l)
277
278         for i, svert in enumerate(stroke):
279             c = i / n
280             t = (1.0 - c) * tMax + c * tMin
281             svert.attribute.thickness = (t / 2.0, t / 2.0)
282
283
284 class pyNonLinearVaryingThicknessShader(StrokeShader):
285     """
286     Assigns thickness to a stroke based on an exponential function.
287     """
288     def __init__(self, thicknessExtremity, thicknessMiddle, exponent):
289         self._thicknessMin = thicknessMiddle
290         self._thicknessMax = thicknessExtremity
291         self._exp = exponent
292         StrokeShader.__init__(self)
293
294     def shade(self, stroke):
295         n = len(stroke)
296         for i, svert in enumerate(stroke):
297             c = (i / n) if (i < n / 2.0) else ((n - i) / n)
298             c = pow(c, self._exp) * pow(2.0, self._exp)
299             t = (1.0 - c) * self._thicknessMax + c * self._thicknessMin
300             svert.attribute.thickness = (t / 2.0, t / 2.0)
301
302
303 class pySLERPThicknessShader(StrokeShader):
304     """
305     Assigns thickness to a stroke based on spherical linear interpolation.
306     """
307     def __init__(self, thicknessMin, thicknessMax, omega=1.2):
308         StrokeShader.__init__(self)
309         self._thicknessMin = thicknessMin
310         self._thicknessMax = thicknessMax
311         self.omega = omega
312
313     def shade(self, stroke):
314         n = len(stroke)
315         maxT = min(self._thicknessMax, 0.33 * stroke.length_2d)
316         omega = self.omega
317         sinhyp = sinh(omega)
318         for i, svert in enumerate(stroke):
319             c = i / n
320             if i < (n * 0.5):
321                 t = sin((1-c) * omega) / sinhyp * self._thicknessMin + sin(c * omega) / sinhyp * maxT
322             else:
323                 t = sin((1-c) * omega) / sinhyp * maxT + sin(c * omega) / sinhyp * self._thicknessMin
324             svert.attribute.thickness = (t / 2.0, t / 2.0)
325
326
327 class pyTVertexThickenerShader(StrokeShader):
328     """
329     Thickens TVertices (visual intersections between two edges).
330     """
331     def __init__(self, a=1.5, n=3):
332         StrokeShader.__init__(self)
333         self._a = a
334         self._n = n
335
336     def shade(self, stroke):
337         n = self._n
338         a = self._a
339
340         term = (a - 1.0) / (n - 1.0)
341
342         if (stroke[0].nature & Nature.T_VERTEX):
343             for count, svert in zip(range(n), stroke):
344                 r = term * (n / (count + 1.0) - 1.0) + 1.0
345                 (tr, tl) = svert.attribute.thickness
346                 svert.attribute.thickness = (r * tr, r * tl)
347
348         if (stroke[-1].nature & Nature.T_VERTEX):
349             for count, svert in zip(range(n), reversed(stroke)):
350                 r = term * (n / (count + 1.0) - 1.0) + 1.0
351                 (tr, tl) = svert.attribute.thickness
352                 svert.attribute.thickness = (r * tr, r * tl)
353
354
355 class pyImportance2DThicknessShader(StrokeShader):
356     """
357     Assigns thickness based on distance to a given point in 2D space.
358     the thickness is inverted, so the vertices closest to the
359     specified point have the lowest thickness.
360     """
361     def __init__(self, x, y, w, kmin, kmax):
362         StrokeShader.__init__(self)
363         self._origin = Vector((x, y))
364         self._w = w
365         self._kmin, self._kmax = kmin, kmax
366
367     def shade(self, stroke):
368         for svert in stroke:
369             d = (svert.point_2d - self._origin).length
370             k = (self._kmin if (d > self._w) else
371                 (self._kmax * (self._w-d) + self._kmin * d) / self._w)
372
373             (tr, tl) = svert.attribute.thickness
374             svert.attribute.thickness = (k*tr/2.0, k*tl/2.0)
375
376
377 class pyImportance3DThicknessShader(StrokeShader):
378     """
379     Assigns thickness based on distance to a given point in 3D space.
380     """
381     def __init__(self, x, y, z, w, kmin, kmax):
382         StrokeShader.__init__(self)
383         self._origin = Vector((x, y, z))
384         self._w = w
385         self._kmin, self._kmax = kmin, kmax
386
387     def shade(self, stroke):
388         for svert in stroke:
389             d = (svert.point_3d - self._origin).length
390             k = (self._kmin if (d > self._w) else
391                 (self._kmax * (self._w-d) + self._kmin * d) / self._w)
392
393             (tr, tl) = svert.attribute.thickness
394             svert.attribute.thickness = (k*tr/2.0, k*tl/2.0)
395
396
397 class pyZDependingThicknessShader(StrokeShader):
398     """
399     Assigns thickness based on an object's local Z depth (point
400     closest to camera is 1, point furthest from camera is zero).
401     """
402     def __init__(self, min, max):
403         StrokeShader.__init__(self)
404         self.__min = min
405         self.__max = max
406         self.func = GetProjectedZF0D()
407
408     def shade(self, stroke):
409         it = Interface0DIterator(stroke)
410         z_indices = tuple(self.func(it) for _ in it)
411         z_min, z_max = min(1, *z_indices), max(0, *z_indices)
412         z_diff = 1 / (z_max - z_min)
413
414         for svert, z_index in zip(stroke, z_indices):
415             z = (z_index - z_min) * z_diff
416             thickness = (1 - z) * self.__max + z * self.__min
417             svert.attribute.thickness = (thickness, thickness)
418
419
420 # -- Color & Alpha Stroke Shaders -- #
421
422
423 class pyConstantColorShader(StrokeShader):
424     """
425     Assigns a constant color to the stroke.
426     """
427     def __init__(self,r,g,b, a = 1):
428         StrokeShader.__init__(self)
429         self._color = (r, g, b)
430         self._a = a
431     def shade(self, stroke):
432         for svert in stroke:
433             svert.attribute.color = self._color
434             svert.attribute.alpha = self._a
435
436
437 class pyIncreasingColorShader(StrokeShader):
438     """
439     Fades from one color to another along the stroke.
440     """
441     def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2):
442         StrokeShader.__init__(self)
443         # use 4d vector to simplify math
444         self._c1 = Vector((r1, g1 ,b1, a1))
445         self._c2 = Vector((r2, g2, b2, a2))
446
447     def shade(self, stroke):
448         n = len(stroke) - 1
449
450         for i, svert in enumerate(stroke):
451             c = i / n
452             color = (1 - c) * self._c1 + c * self._c2
453             svert.attribute.color = color[:3]
454             svert.attribute.alpha = color[3]
455
456
457 class pyInterpolateColorShader(StrokeShader):
458     """
459     Fades from one color to another and back.
460     """
461     def __init__(self,r1,g1,b1,a1, r2,g2,b2,a2):
462         StrokeShader.__init__(self)
463         # use 4d vector to simplify math
464         self._c1 = Vector((r1, g1 ,b1, a1))
465         self._c2 = Vector((r2, g2, b2, a2))
466
467     def shade(self, stroke):
468         n = len(stroke) - 1
469         for i, svert in enumerate(stroke):
470             c = 1.0 - 2.0 * abs((i / n) - 0.5)
471             color = (1.0 - c) * self._c1 + c * self._c2
472             svert.attribute.color = color[:3]
473             svert.attribute.alpha = color[3]
474
475
476 class pyModulateAlphaShader(StrokeShader):
477     """
478     Limits the stroke's alpha between a min and max value.
479     """
480     def __init__(self, min=0, max=1):
481         StrokeShader.__init__(self)
482         self.__min = min
483         self.__max = max
484     def shade(self, stroke):
485         for svert in stroke:
486             alpha = svert.attribute.alpha
487             alpha = bound(self.__min, alpha * svert.point.y * 0.0025, self.__max)
488             svert.attribute.alpha = alpha
489
490
491 class pyMaterialColorShader(StrokeShader):
492     """
493     Assigns the color of the underlying material to the stroke.
494     """
495     def __init__(self, threshold=50):
496         StrokeShader.__init__(self)
497         self._threshold = threshold
498         self._func = MaterialF0D()
499
500     def shade(self, stroke):
501         xn = 0.312713
502         yn = 0.329016
503         Yn = 1.0
504         un = 4.0 * xn / (-2.0 * xn + 12.0 * yn + 3.0)
505         vn = 9.0 * yn / (-2.0 * xn + 12.0 * yn + 3.0)
506
507         it = Interface0DIterator(stroke)
508         for svert in it:
509             mat = self._func(it)
510
511             r, g, b, *_ = mat.diffuse
512
513             X = 0.412453 * r + 0.35758 * g + 0.180423 * b
514             Y = 0.212671 * r + 0.71516 * g + 0.072169 * b
515             Z = 0.019334 * r + 0.11919 * g + 0.950227 * b
516
517             if not any((X, Y, Z)):
518                 X = Y = Z = 0.01
519
520             u = 4.0 * X / (X + 15.0 * Y + 3.0 * Z)
521             v = 9.0 * Y / (X + 15.0 * Y + 3.0 * Z)
522
523             L= 116. * pow((Y/Yn),(1./3.)) - 16
524             U = 13. * L * (u - un)
525             V = 13. * L * (v - vn)
526
527             if L > self._threshold:
528                 L /= 1.3
529                 U += 10.
530             else:
531                 L = L + 2.5 * (100-L) * 0.2
532                 U /= 3.0
533                 V /= 3.0
534
535             u = U / (13.0 * L) + un
536             v = V / (13.0 * L) + vn
537
538             Y = Yn * pow(((L+16.)/116.), 3.)
539             X = -9. * Y * u / ((u - 4.)* v - u * v)
540             Z = (9. * Y - 15*v*Y - v*X) /( 3. * v)
541
542             r = 3.240479 * X - 1.53715 * Y - 0.498535 * Z
543             g = -0.969256 * X + 1.875991 * Y + 0.041556 * Z
544             b = 0.055648 * X - 0.204043 * Y + 1.057311 * Z
545
546             r = max(0, r)
547             g = max(0, g)
548             b = max(0, b)
549
550             svert.attribute.color = (r, g, b)
551
552
553 class pyRandomColorShader(StrokeShader):
554     """
555     Assigns a color to the stroke based on given seed.
556     """
557     def __init__(self, s=1):
558         StrokeShader.__init__(self)
559         random.seed = s
560
561     def shade(self, stroke):
562         c = (random.uniform(15, 75) * 0.01,
563              random.uniform(15, 75) * 0.01,
564              random.uniform(15, 75) * 0.01)
565         for svert in stroke:
566             svert.attribute.color = c
567
568
569 class py2DCurvatureColorShader(StrokeShader):
570     """
571     Assigns a color (grayscale) to the stroke based on the curvature.
572     A higher curvature will yield a brighter color.
573     """
574     def shade(self, stroke):
575         func = Curvature2DAngleF0D()
576         it = Interface0DIterator(stroke)
577         for svert in it:
578             c = func(it)
579             if c < 0 and bpy.app.debug_freestyle:
580                 print("py2DCurvatureColorShader: negative 2D curvature")
581             color = 10.0 * c / pi
582             svert.attribute.color = (color, color, color)
583
584
585 class pyTimeColorShader(StrokeShader):
586     """
587     Assigns a grayscale value that increases for every vertex.
588     The brightness will increase along the stroke.
589     """
590     def __init__(self, step=0.01):
591         StrokeShader.__init__(self)
592         self._step = step
593     def shade(self, stroke):
594         for i, svert in enumerate(stroke):
595             c = i * self._step
596             svert.attribute.color = (c, c, c)
597
598
599 # -- Geometry Stroke Shaders -- #
600
601
602 class pySamplingShader(StrokeShader):
603     """
604     Resamples the stroke, which gives the stroke the amount of
605     vertices specified.
606     """
607     def __init__(self, sampling):
608         StrokeShader.__init__(self)
609         self._sampling = sampling
610
611     def shade(self, stroke):
612         stroke.resample(float(self._sampling))
613         stroke.update_length()
614
615
616 class pyBackboneStretcherShader(StrokeShader):
617     """
618     Stretches the stroke's backbone by a given length (in pixels).
619     """
620     def __init__(self, l):
621         StrokeShader.__init__(self)
622         self._l = l
623
624     def shade(self, stroke):
625         # get start and end points
626         v0, vn = stroke[0], stroke[-1]
627         p0, pn = v0.point, vn.point
628         # get the direction
629         d1 = (p0 - stroke[ 1].point).normalized()
630         dn = (pn - stroke[-2].point).normalized()
631         v0.point += d1 * self._l
632         vn.point += dn * self._l
633         stroke.update_length()
634
635
636 class pyLengthDependingBackboneStretcherShader(StrokeShader):
637     """
638     Stretches the stroke's backbone proportional to the stroke's length
639     NOTE: you'll probably want an l somewhere between (0.5 - 0). A value that
640     is too high may yield unexpected results.
641     """
642     def __init__(self, l):
643         StrokeShader.__init__(self)
644         self._l = l
645     def shade(self, stroke):
646         # get start and end points
647         v0, vn = stroke[0], stroke[-1]
648         p0, pn = v0.point, vn.point
649         # get the direction
650         d1 = (p0 - stroke[ 1].point).normalized()
651         dn = (pn - stroke[-2].point).normalized()
652         v0.point += d1 * self._l * stroke.length_2d
653         vn.point += dn * self._l * stroke.length_2d
654         stroke.update_length()
655
656
657 class pyGuidingLineShader(StrokeShader):
658     def shade(self, stroke):
659         # get the tangent direction
660         t = stroke[-1].point - stroke[0].point
661         # look for the stroke middle vertex
662         itmiddle = iter(stroke)
663         while itmiddle.object.u < 0.5:
664             itmiddle.increment()
665         center_vertex = itmiddle.object
666         # position all the vertices along the tangent for the right part
667         it = StrokeVertexIterator(itmiddle)
668         for svert in it:
669             svert.point = center_vertex.point + t * (svert.u - center_vertex.u)
670         # position all the vertices along the tangent for the left part
671         it = StrokeVertexIterator(itmiddle).reversed()
672         for svert in it:
673             svert.point = center_vertex.point - t * (center_vertex.u - svert.u)
674         stroke.update_length()
675
676
677 class pyBackboneStretcherNoCuspShader(StrokeShader):
678     """
679     Stretches the stroke's backbone, excluding cusp vertices (end junctions).
680     """
681     def __init__(self, l):
682         StrokeShader.__init__(self)
683         self._l = l
684
685     def shade(self, stroke):
686
687         v0, v1 = stroke[0], stroke[1]
688         vn, vn_1 = stroke[-1], stroke[-2]
689
690         if not (v0.nature & v1.nature & Nature.CUSP):
691             d1 = (v0.point - v1.point).normalized()
692             v0.point += d1 * self._l
693
694         if not (vn.nature & vn_1.nature & Nature.CUSP):
695             dn = (vn.point - vn_1.point).normalized()
696             vn.point += dn * self._l
697
698         stroke.update_length()
699
700
701 class pyDiffusion2Shader(StrokeShader):
702     """
703     Iteratively adds an offset to the position of each stroke vertex
704     in the direction perpendicular to the stroke direction at the
705     point. The offset is scaled by the 2D curvature (i.e. how quickly
706     the stroke curve is) at the point.
707     """
708     def __init__(self, lambda1, nbIter):
709         StrokeShader.__init__(self)
710         self._lambda = lambda1
711         self._nbIter = nbIter
712         self._normalInfo = Normal2DF0D()
713         self._curvatureInfo = Curvature2DAngleF0D()
714
715     def shade(self, stroke):
716         for i in range (1, self._nbIter):
717             it = Interface0DIterator(stroke)
718             for svert in it:
719                 svert.point += self._normalInfo(it) * self._lambda * self._curvatureInfo(it)
720         stroke.update_length()
721
722
723 class pyTipRemoverShader(StrokeShader):
724     """
725     Removes the tips of the stroke.
726     """
727     def __init__(self, l):
728         StrokeShader.__init__(self)
729         self._l = l
730
731     @staticmethod
732     def check_vertex(v, length):
733         # Returns True if the given strokevertex is less than self._l away
734         # from the stroke's tip and therefore should be removed.
735         return (v.curvilinear_abscissa < length or v.stroke_length-v.curvilinear_abscissa < length)
736
737     def shade(self, stroke):
738         n = len(stroke)
739         if n < 4:
740             return
741
742         verticesToRemove = tuple(svert for svert in stroke if self.check_vertex(svert, self._l))
743         # explicit conversion to StrokeAttribute is needed
744         oldAttributes = (StrokeAttribute(svert.attribute) for svert in stroke)
745
746         if n - len(verticesToRemove) < 2:
747             return
748
749         for sv in verticesToRemove:
750             stroke.remove_vertex(sv)
751
752         stroke.update_length()
753         stroke.resample(n)
754         if len(stroke) != n and bpy.app.debug_freestyle:
755             print("pyTipRemover: Warning: resampling problem")
756
757         for svert, a in zip(stroke, oldAttributes):
758             svert.attribute = a
759         stroke.update_length()
760
761
762 class pyTVertexRemoverShader(StrokeShader):
763     """
764     Removes t-vertices from the stroke.
765     """
766     def shade(self, stroke):
767         if len(stroke) < 4:
768             return
769
770         v0, vn = stroke[0], stroke[-1]
771         if (v0.nature & Nature.T_VERTEX):
772             stroke.remove_vertex(v0)
773         if (vn.nature & Nature.T_VERTEX):
774             stroke.remove_vertex(vn)
775         stroke.update_length()
776
777
778 class pyHLRShader(StrokeShader):
779     """
780     Controls visibility based upon the quantitative invisibility (QI)
781     based on hidden line removal (HLR).
782     """
783     def shade(self, stroke):
784         if len(stroke) < 4:
785             return
786
787         it = iter(stroke)
788         for v1, v2 in zip(it, it.incremented()):
789             if (v1.nature & Nature.VIEW_VERTEX):
790                 visible = (v1.get_fedge(v2).viewedge.qi != 0)
791             v1.attribute.visible = not visible
792
793
794 class pySinusDisplacementShader(StrokeShader):
795     """
796     Displaces the stroke in the shape of a sine wave.
797     """
798     def __init__(self, f, a):
799         StrokeShader.__init__(self)
800         self._f = f
801         self._a = a
802         self._getNormal = Normal2DF0D()
803
804     def shade(self, stroke):
805         it = Interface0DIterator(stroke)
806         for svert in it:
807             normal = self._getNormal(it)
808             a = self._a * (1 - 2 * (abs(svert.u - 0.5)))
809             n = normal * a * cos(self._f * svert.u * 6.28)
810             svert.point += n
811         stroke.update_length()
812
813
814 class pyPerlinNoise1DShader(StrokeShader):
815     """
816     Displaces the stroke using the curvilinear abscissa.  This means
817     that lines with the same length and sampling interval will be
818     identically distorded.
819     """
820     def __init__(self, freq=10, amp=10, oct=4, seed=-1):
821         StrokeShader.__init__(self)
822         self.__noise = Noise(seed)
823         self.__freq = freq
824         self.__amp = amp
825         self.__oct = oct
826
827     def shade(self, stroke):
828         for svert in stroke:
829             s = svert.projected_x + svert.projected_y
830             nres = self.__noise.turbulence1(s, self.__freq, self.__amp, self.__oct)
831             svert.point = (svert.projected_x + nres, svert.projected_y + nres)
832         stroke.update_length()
833
834
835 class pyPerlinNoise2DShader(StrokeShader):
836     """
837     Displaces the stroke using the strokes coordinates.  This means
838     that in a scene no strokes will be distorded identically.
839
840     More information on the noise shaders can be found at:
841     freestyleintegration.wordpress.com/2011/09/25/development-updates-on-september-25/
842     """
843     def __init__(self, freq=10, amp=10, oct=4, seed=-1):
844         StrokeShader.__init__(self)
845         self.__noise = Noise(seed)
846         self.__freq = freq
847         self.__amp = amp
848         self.__oct = oct
849
850     def shade(self, stroke):
851         for svert in stroke:
852             nres = self.__noise.turbulence2(svert.point_2d, self.__freq, self.__amp, self.__oct)
853             svert.point = (svert.projected_x + nres, svert.projected_y + nres)
854         stroke.update_length()
855
856
857 class pyBluePrintCirclesShader(StrokeShader):
858     """
859     Draws the silhouette of the object as a circle.
860     """
861     def __init__(self, turns=1, random_radius=3, random_center=5):
862         StrokeShader.__init__(self)
863         self.__turns = turns
864         self.__random_center = random_center
865         self.__random_radius = random_radius
866
867     def shade(self, stroke):
868         # get minimum and maximum coordinates
869         p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
870
871         stroke.resample(32 * self.__turns)
872         sv_nb = len(stroke) // self.__turns
873         center = (p_min + p_max) / 2
874         radius = (center.x - p_min.x + center.y - p_min.y) / 2
875         R = self.__random_radius
876         C = self.__random_center
877
878         # The directions (and phases) are calculated using a separate
879         # function decorated with an lru-cache. This guarantees that
880         # the directions (involving sin and cos) are calculated as few
881         # times as possible.
882         #
883         # This works because the phases and directions are only
884         # dependant on the stroke length, and the chance that
885         # stroke.resample() above produces strokes of the same length
886         # is quite high.
887         #
888         # In tests, the amount of calls to sin() and cos() went from
889         # over 21000 to just 32 times, yielding a speedup of over 100%
890         directions = phase_to_direction(sv_nb)
891
892         it = iter(stroke)
893
894         for j in range(self.__turns):
895             prev_radius = radius
896             prev_center = center
897             radius += randint(-R, R)
898             center += Vector((randint(-C, C), randint(-C, C)))
899
900             for (phase, direction), svert in zip(directions, it):
901                 r = prev_radius + (radius - prev_radius) * phase
902                 c = prev_center + (center - prev_center) * phase
903                 svert.point = c + r * direction
904
905         if not it.is_end:
906             it.increment()
907             for sv in tuple(it):
908                 stroke.remove_vertex(sv)
909
910         stroke.update_length()
911
912
913 class pyBluePrintEllipsesShader(StrokeShader):
914     def __init__(self, turns=1, random_radius=3, random_center=5):
915         StrokeShader.__init__(self)
916         self.__turns = turns
917         self.__random_center = random_center
918         self.__random_radius = random_radius
919
920     def shade(self, stroke):
921         p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
922
923         stroke.resample(32 * self.__turns)
924         sv_nb = len(stroke) // self.__turns
925
926         center = (p_min + p_max) / 2
927         radius = center - p_min
928
929         R = self.__random_radius
930         C = self.__random_center
931
932         # for description of the line below, see pyBluePrintCirclesShader
933         directions = phase_to_direction(sv_nb)
934         it = iter(stroke)
935         for j in range(self.__turns):
936             prev_radius = radius
937             prev_center = center
938             radius = radius + Vector((randint(-R, R), randint(-R, R)))
939             center = center + Vector((randint(-C, C), randint(-C, C)))
940
941             for (phase, direction), svert in zip(directions, it):
942                 r = prev_radius + (radius - prev_radius) * phase
943                 c = prev_center + (center - prev_center) * phase
944                 svert.point = (c.x + r.x * direction.x, c.y + r.y * direction.y)
945
946         # remove excess vertices
947         if not it.is_end:
948             it.increment()
949             for sv in tuple(it):
950                 stroke.remove_vertex(sv)
951
952         stroke.update_length()
953
954
955 class pyBluePrintSquaresShader(StrokeShader):
956     def __init__(self, turns=1, bb_len=10, bb_rand=0):
957         StrokeShader.__init__(self)
958         self.__turns = turns # does not have any effect atm
959         self.__bb_len = bb_len
960         self.__bb_rand = bb_rand
961
962     def shade(self, stroke):
963         # this condition will lead to errors later, end now
964         if len(stroke) < 1:
965             return
966
967         # get minimum and maximum coordinates
968         p_min, p_max = BoundingBox.from_sequence(svert.point for svert in stroke).corners
969
970         stroke.resample(32 * self.__turns)
971         num_segments = len(stroke) // self.__turns
972         f = num_segments // 4
973         # indices of the vertices that will form corners
974         first, second, third, fourth = (f, f * 2, f * 3, num_segments)
975
976         # construct points of the backbone
977         bb_len = self.__bb_len
978         points = (
979             Vector((p_min.x - bb_len, p_min.y)),
980             Vector((p_max.x + bb_len, p_min.y)),
981             Vector((p_max.x, p_min.y - bb_len)),
982             Vector((p_max.x, p_max.y + bb_len)),
983             Vector((p_max.x + bb_len, p_max.y)),
984             Vector((p_min.x - bb_len, p_max.y)),
985             Vector((p_min.x, p_max.y + bb_len)),
986             Vector((p_min.x, p_min.y - bb_len)),
987             )
988
989         # add randomization to the points (if needed)
990         if self.__bb_rand:
991             R, r = self.__bb_rand, self.__bb_rand // 2
992
993             randomization_mat = (
994                 Vector((randint(-R, R), randint(-r, r))),
995                 Vector((randint(-R, R), randint(-r, r))),
996                 Vector((randint(-r, r), randint(-R, R))),
997                 Vector((randint(-r, r), randint(-R, R))),
998                 Vector((randint(-R, R), randint(-r, r))),
999                 Vector((randint(-R, R), randint(-r, r))),
1000                 Vector((randint(-r, r), randint(-R, R))),
1001                 Vector((randint(-r, r), randint(-R, R))),
1002                 )
1003
1004             # combine both tuples
1005             points = tuple(p + rand for (p, rand) in zip(points, randomization_mat))
1006
1007
1008         # subtract even from uneven; result is length four tuple of vectors
1009         it = iter(points)
1010         old_vecs = tuple(next(it) - current for current in it)
1011
1012         it = iter(stroke)
1013         verticesToRemove = list()
1014         for j in range(self.__turns):
1015             for i, svert in zip(range(num_segments), it):
1016                 if i < first:
1017                     svert.point = points[0] + old_vecs[0] * i / (first - 1)
1018                     svert.attribute.visible = (i != first - 1)
1019                 elif i < second:
1020                     svert.point = points[2] + old_vecs[1] * (i - first) / (second - first - 1)
1021                     svert.attribute.visible = (i != second - 1)
1022                 elif i < third:
1023                     svert.point = points[4] + old_vecs[2] * (i - second) / (third - second - 1)
1024                     svert.attribute.visible = (i != third - 1)
1025                 elif i < fourth:
1026                     svert.point = points[6] + old_vecs[3] * (i - third) / (fourth - third - 1)
1027                     svert.attribute.visible = (i != fourth - 1)
1028                 else:
1029                     # special case; remove these vertices
1030                     verticesToRemove.append(svert)
1031
1032         # remove excess vertices (if any)
1033         if not it.is_end:
1034             it.increment()
1035             verticesToRemove += [svert for svert in it]
1036             for sv in verticesToRemove:
1037                 stroke.remove_vertex(sv)
1038         stroke.update_length()
1039
1040
1041 class pyBluePrintDirectedSquaresShader(StrokeShader):
1042     """
1043     Replaces the stroke with a directed square.
1044     """
1045     def __init__(self, turns=1, bb_len=10, mult=1):
1046         StrokeShader.__init__(self)
1047         self.__mult = mult
1048         self.__turns = turns
1049         self.__bb_len = 1 + bb_len * 0.01
1050
1051     def shade(self, stroke):
1052         stroke.resample(32 * self.__turns)
1053         n = len(stroke)
1054
1055         p_mean = (1 / n) * sum((svert.point for svert in stroke), Vector((0.0, 0.0)))
1056         p_var = Vector((0, 0))
1057         p_var_xy = 0.0
1058         for d in (svert.point - p_mean for svert in stroke):
1059             p_var += Vector((d.x ** 2, d.y ** 2))
1060             p_var_xy += d.x * d.y
1061
1062         # divide by number of vertices
1063         p_var /= n
1064         p_var_xy /= n
1065         trace = p_var.x + p_var.y
1066         det = p_var.x * p_var.y - pow(p_var_xy, 2)
1067
1068         sqrt_coeff = sqrt(trace * trace - 4 * det)
1069         lambda1, lambda2 = (trace + sqrt_coeff) / 2, (trace - sqrt_coeff) / 2
1070         # make sure those numbers aren't to small, if they are, rooting them will yield complex numbers
1071         lambda1, lambda2 = max(1e-12, lambda1), max(1e-12, lambda2)
1072         theta = atan(2 * p_var_xy / (p_var.x - p_var.y)) / 2
1073
1074         if p_var.y > p_var.x:
1075             e1 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda1) * self.__mult
1076             e2 = Vector((cos(theta + pi    ), sin(theta + pi    ))) * sqrt(lambda2) * self.__mult
1077         else:
1078             e1 = Vector((cos(theta), sin(theta)))                   * sqrt(lambda1) * self.__mult
1079             e2 = Vector((cos(theta + pi / 2), sin(theta + pi / 2))) * sqrt(lambda2) * self.__mult
1080
1081         # partition the stroke
1082         num_segments = len(stroke) // self.__turns
1083         f = num_segments // 4
1084         # indices of the vertices that will form corners
1085         first, second, third, fourth = (f, f * 2, f * 3, num_segments)
1086
1087         bb_len1 = self.__bb_len
1088         bb_len2 = 1 + (bb_len1 - 1) * sqrt(lambda1 / lambda2)
1089         points = (
1090             p_mean - e1 - e2 * bb_len2,
1091             p_mean - e1 * bb_len1 + e2,
1092             p_mean + e1 + e2 * bb_len2,
1093             p_mean + e1 * bb_len1 - e2,
1094             )
1095
1096         old_vecs = (
1097             e2 * bb_len2 * 2,
1098             e1 * bb_len1 * 2,
1099            -e2 * bb_len2 * 2,
1100            -e1 * bb_len1 * 2,
1101             )
1102
1103         it = iter(stroke)
1104         verticesToRemove = list()
1105         for j in range(self.__turns):
1106             for i, svert in zip(range(num_segments), it):
1107                 if i < first:
1108                     svert.point = points[0] + old_vecs[0] * i / (first - 1)
1109                     svert.attribute.visible = (i != first - 1)
1110                 elif i < second:
1111                     svert.point = points[1] + old_vecs[1] * (i - first) / (second - first - 1)
1112                     svert.attribute.visible = (i != second - 1)
1113                 elif i < third:
1114                     svert.point = points[2] + old_vecs[2] * (i - second) / (third - second - 1)
1115                     svert.attribute.visible = (i != third - 1)
1116                 elif i < fourth:
1117                     svert.point = points[3] + old_vecs[3] * (i - third) / (fourth - third - 1)
1118                     svert.attribute.visible = (i != fourth - 1)
1119                 else:
1120                     # special case; remove these vertices
1121                     verticesToRemove.append(svert)
1122
1123         # remove excess vertices
1124         if not it.is_end:
1125             it.increment()
1126             verticesToRemove += [svert for svert in it]
1127             for sv in verticesToRemove:
1128                 stroke.remove_vertex(sv)
1129         stroke.update_length()
1130
1131
1132 # -- various (used in the parameter editor) -- #
1133
1134
1135 def iter_stroke_vertices(stroke, epsilon=1e-6):
1136     yield stroke[0]
1137     for prev, svert in pairwise(stroke):
1138         if (prev.point - svert.point).length > epsilon:
1139             yield svert
1140
1141
1142 class RoundCapShader(StrokeShader):
1143     def round_cap_thickness(self, x):
1144         x = max(0.0, min(x, 1.0))
1145         return pow(1.0 - (x ** 2.0), 0.5)
1146
1147     def shade(self, stroke):
1148         # save the location and attribute of stroke vertices
1149         buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute))
1150                        for sv in iter_stroke_vertices(stroke))
1151         nverts = len(buffer)
1152         if nverts < 2:
1153             return
1154         # calculate the number of additional vertices to form caps
1155         thickness_beg = sum(stroke[0].attribute.thickness)
1156         caplen_beg = thickness_beg / 2.0
1157         nverts_beg = max(5, int(thickness_beg))
1158
1159         thickness_end = sum(stroke[-1].attribute.thickness)
1160         caplen_end = (thickness_end) / 2.0
1161         nverts_end = max(5, int(thickness_end))
1162
1163         # adjust the total number of stroke vertices
1164         stroke.resample(nverts + nverts_beg + nverts_end)
1165         # restore the location and attribute of the original vertices
1166         for i, (p, attr) in enumerate(buffer):
1167             stroke[nverts_beg + i].point = p
1168             stroke[nverts_beg + i].attribute = attr
1169         # reshape the cap at the beginning of the stroke
1170         q, attr = buffer[1]
1171         p, attr = buffer[0]
1172         direction = (p - q).normalized() * caplen_beg
1173         n = 1.0 / nverts_beg
1174         R, L = attr.thickness
1175         for t, svert in zip(range(nverts_beg, 0, -1), stroke):
1176             r = self.round_cap_thickness((t + 1) * n)
1177             svert.point = p + direction * t * n
1178             svert.attribute = attr
1179             svert.attribute.thickness = (R * r, L * r)
1180         # reshape the cap at the end of the stroke
1181         q, attr = buffer[-2]
1182         p, attr = buffer[-1]
1183         direction = (p - q).normalized() * caplen_beg
1184         n = 1.0 / nverts_end
1185         R, L = attr.thickness
1186         for t, svert in zip(range(nverts_end, 0, -1), reversed(stroke)):
1187             r = self.round_cap_thickness((t + 1) * n)
1188             svert.point = p + direction * t * n
1189             svert.attribute = attr
1190             svert.attribute.thickness = (R * r, L * r)
1191         # update the curvilinear 2D length of each vertex
1192         stroke.update_length()
1193
1194
1195 class SquareCapShader(StrokeShader):
1196     def shade(self, stroke):
1197         # save the location and attribute of stroke vertices
1198         buffer = tuple((Vector(sv.point), StrokeAttribute(sv.attribute))
1199                        for sv in iter_stroke_vertices(stroke))
1200         nverts = len(buffer)
1201         if nverts < 2:
1202             return
1203         # calculate the number of additional vertices to form caps
1204         caplen_beg = sum(stroke[0].attribute.thickness) / 2.0
1205         nverts_beg = 1
1206
1207         caplen_end = sum(stroke[-1].attribute.thickness) / 2.0
1208         nverts_end = 1
1209         # adjust the total number of stroke vertices
1210         stroke.resample(nverts + nverts_beg + nverts_end)
1211         # restore the location and attribute of the original vertices
1212         for i, (p, attr) in zip(range(nverts), buffer):
1213             stroke[nverts_beg + i].point = p
1214             stroke[nverts_beg + i].attribute = attr
1215         # reshape the cap at the beginning of the stroke
1216         q, attr = buffer[1]
1217         p, attr = buffer[0]
1218         stroke[0].point += (p - q).normalized() * caplen_beg
1219         stroke[0].attribute = attr
1220         # reshape the cap at the end of the stroke
1221         q, attr = buffer[-2]
1222         p, attr = buffer[-1]
1223         stroke[-1].point += (p - q).normalized() * caplen_end
1224         stroke[-1].attribute = attr
1225         # update the curvilinear 2D length of each vertex
1226         stroke.update_length()