1//////////////////////////////////////////////////////////////////////
  2// LibFile: color.scad
  3//   HSV and HSL conversion, rainbow() module for coloring multiple objects. 
  4//   The recolor() and color_this() modules allow you to change the color
  5//   of previously colored attachable objects.  
  6// Includes:
  7//   include <BOSL2/std.scad>
  8// FileGroup: Basic Modeling
  9// FileSummary: HSV and HSL conversion, color multiple objects, change color of objects
 10// FileFootnotes: STD=Included in std.scad
 11//////////////////////////////////////////////////////////////////////
 12
 13
 14use <builtins.scad>
 15
 16// Section: Coloring Objects
 17
 18// Module: recolor()
 19// Usage:
 20//   recolor([c]) CHILDREN;
 21// Topics: Attachments
 22// See Also: color_this()
 23// Description:
 24//   Sets the color for attachable children and all their descendants.  This only works with attachables and you cannot
 25//   have any color() modules above it in any parents, only other recolor() or color_this() modules.
 26//   This works by setting the special `$color` variable, which attachable objects make use of to set the color. 
 27// Arguments:
 28//   c = Color name or RGBA vector.  Default: The default color in your color scheme. 
 29// Example:
 30//   cuboid([10,10,5])
 31//     recolor("green")attach(TOP,BOT) cuboid([9,9,4.5])
 32//       attach(TOP,BOT) cuboid([8,8,4])
 33//         recolor("purple") attach(TOP,BOT) cuboid([7,7,3.5])
 34//           attach(TOP,BOT) cuboid([6,6,3])
 35//             recolor("cyan")attach(TOP,BOT) cuboid([5,5,2.5])
 36//               attach(TOP,BOT) cuboid([4,4,2]);
 37module recolor(c="default")
 38{
 39    req_children($children);  
 40    $color=c;
 41    children();
 42}
 43
 44
 45// Module: color_this()
 46// Usage:
 47//   color_this([c]) CHILDREN;
 48// Topics: Attachments
 49// See Also: recolor()
 50// Description:
 51//   Sets the color for children at one level, reverting to the previous color for further descendants.
 52//   This works only with attachables and you cannot have any color() modules above it in any parents,
 53//   only recolor() or other color_this() modules.  This works using the `$color` and `$save_color` variables,
 54//   which attachable objects make use of to set the color. 
 55// Arguments:
 56//   c = Color name or RGBA vector.  Default: the default color in your color scheme
 57// Example:
 58//   cuboid([10,10,5])
 59//     color_this("green")attach(TOP,BOT) cuboid([9,9,4.5])
 60//       attach(TOP,BOT) cuboid([8,8,4])
 61//         color_this("purple") attach(TOP,BOT) cuboid([7,7,3.5])
 62//           attach(TOP,BOT) cuboid([6,6,3])
 63//             color_this("cyan")attach(TOP,BOT) cuboid([5,5,2.5])
 64//               attach(TOP,BOT) cuboid([4,4,2]);
 65module color_this(c="default")
 66{
 67  req_children($children);  
 68  $save_color=default($color,"default");
 69  $color=c;
 70  children();
 71}
 72
 73
 74// Module: rainbow()
 75// Usage:
 76//   rainbow(list,[stride],[maxhues],[shuffle],[seed]) CHILDREN;
 77// Description:
 78//   Iterates the list, displaying children in different colors for each list item.  The color
 79//   is set using the color() module, so this module is not compatible with {{recolor()}} or
 80//   {{color_this()}}.  This is useful for debugging regions or lists of paths. 
 81// Arguments:
 82//   list = The list of items to iterate through.
 83//   stride = Consecutive colors stride around the color wheel divided into this many parts.
 84//   maxhues = max number of hues to use (to prevent lots of indistinguishable hues)
 85//   shuffle = if true then shuffle the hues in a random order.  Default: false
 86//   seed = seed to use for shuffle
 87// Side Effects:
 88//   Sets the color to progressive values along the ROYGBIV spectrum for each item.
 89//   Sets `$idx` to the index of the current item in `list` that we want to show.
 90//   Sets `$item` to the current item in `list` that we want to show.
 91// Example(2D):
 92//   rainbow(["Foo","Bar","Baz"]) fwd($idx*10) text(text=$item,size=8,halign="center",valign="center");
 93// Example(2D):
 94//   rgn = [circle(d=45,$fn=3), circle(d=75,$fn=4), circle(d=50)];
 95//   rainbow(rgn) stroke($item, closed=true);
 96module rainbow(list, stride=1, maxhues, shuffle=false, seed)
 97{
 98    req_children($children);  
 99    ll = len(list);
100    maxhues = first_defined([maxhues,ll]);
101    huestep = 360 / maxhues;
102    huelist = [for (i=[0:1:ll-1]) posmod(i*huestep+i*360/stride,360)];
103    hues = shuffle ? shuffle(huelist, seed=seed) : huelist;
104    for($idx=idx(list)) {
105        $item = list[$idx];
106        hsv(h=hues[$idx]) children();
107    }
108}
109
110
111// Section: Colorspace Conversion
112
113// Function&Module: hsl()
114// Usage:
115//   hsl(h,[s],[l],[a]) CHILDREN;
116//   rgb = hsl(h,[s],[l],[a]);
117// Description:
118//   When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace. If you supply
119//   the `a` value then you'll get a length 4 list [R,G,B,A].  
120//   When called as a module, sets the color using the color() module to the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace.
121// Arguments:
122//   h = The hue, given as a value between 0 and 360.  0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
123//   s = The saturation, given as a value between 0 and 1.  0 = grayscale, 1 = vivid colors.  Default: 1
124//   l = The lightness, between 0 and 1.  0 = black, 0.5 = bright colors, 1 = white.  Default: 0.5
125//   a = Specifies the alpha channel as a value between 0 and 1.  0 = fully transparent, 1=opaque.  Default: 1
126// Example:
127//   hsl(h=120,s=1,l=0.5) sphere(d=60);
128// Example:
129//   rgb = hsl(h=270,s=0.75,l=0.6);
130//   color(rgb) cube(60, center=true);
131function hsl(h,s=1,l=0.5,a) =
132    let(
133        h=posmod(h,360)
134    ) [
135        for (n=[0,8,4])
136          let(k=(n+h/30)%12)
137          l - s*min(l,1-l)*max(min(k-3,9-k,1),-1),
138        if (is_def(a)) a
139    ];
140
141module hsl(h,s=1,l=0.5,a=1)
142{
143  req_children($children);  
144  color(hsl(h,s,l),a) children();
145}
146
147
148// Function&Module: hsv()
149// Usage:
150//   hsv(h,[s],[v],[a]) CHILDREN;
151//   rgb = hsv(h,[s],[v],[a]);
152// Description:
153//   When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and value `v` from the HSV colorspace.  If you supply
154//   the `a` value then you'll get a length 4 list [R,G,B,A].  
155//   When called as a module, sets the color using the color() module to the given hue `h`, saturation `s`, and value `v` from the HSV colorspace.
156// Arguments:
157//   h = The hue, given as a value between 0 and 360.  0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
158//   s = The saturation, given as a value between 0 and 1.  0 = grayscale, 1 = vivid colors.  Default: 1
159//   v = The value, between 0 and 1.  0 = darkest black, 1 = bright.  Default: 1
160//   a = Specifies the alpha channel as a value between 0 and 1.  0 = fully transparent, 1=opaque.  Default: 1
161// Example:
162//   hsv(h=120,s=1,v=1) sphere(d=60);
163// Example:
164//   rgb = hsv(h=270,s=0.75,v=0.9);
165//   color(rgb) cube(60, center=true);
166function hsv(h,s=1,v=1,a) =
167    assert(s>=0 && s<=1)
168    assert(v>=0 && v<=1)
169    assert(is_undef(a) || a>=0 && a<=1)
170    let(
171        h = posmod(h,360),
172        c = v * s,
173        hprime = h/60,
174        x = c * (1- abs(hprime % 2 - 1)),
175        rgbprime = hprime <=1 ? [c,x,0]
176                 : hprime <=2 ? [x,c,0]
177                 : hprime <=3 ? [0,c,x]
178                 : hprime <=4 ? [0,x,c]
179                 : hprime <=5 ? [x,0,c]
180                 : hprime <=6 ? [c,0,x]
181                 : [0,0,0],
182        m=v-c
183    )
184    is_def(a) ? point4d(add_scalar(rgbprime,m),a)
185              : add_scalar(rgbprime,m);
186
187module hsv(h,s=1,v=1,a=1)
188{
189    req_children($children);
190    color(hsv(h,s,v),a) children();
191}    
192
193
194
195// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap