diff --git a/shapes2d.scad b/shapes2d.scad index 45fda07..4b5c036 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1929,7 +1929,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) = // Usage: As Function // path = reuleaux_polygon(n, r|d=, ...); // Description: -// When called as a module, reates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring. +// When called as a module, creates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring. // When called as a function, returns a 2D path for a Reulaux Polygon. // Arguments: // n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3 @@ -1988,6 +1988,133 @@ function reuleaux_polygon(n=3, r, d, anchor=CENTER, spin=0) = +// Function&Module: squircle() +// Synopsis: Creates a shape between a circle and a square, centered on the origin. +// SynTags: Geom, Path +// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable +// See Also: circle(), square(), supershape() +// Usage: As Module +// squircle(size, [squareness], [style]) [ATTACHMENTS]; +// Usage: As Function +// path = squircle(size, [squareness], [style]); +// Description: +// A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse.Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles. +// . +// There are multiple approaches to constructing a squircle. One approach is a special case of superellipse (shown in {{supershape}} example 3), and uses exponents between 2 and infinity to adjust the shape. Another, the Fernández-Guasti squircle or FG squircle, arises from work in optics and uses a "squareness" parameter between 0 and 1 to adjust the shape. We use the same squareness parameter for both types, adjusting the internal FG parameter or superellipse exponent as needed to achieve the same squircle corner extents. +// . +// The FG style and superellipse style squircles are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG for a given value of squareness. Either style requires just the two parameters `squareness` and `size`. The vertex distribution is adjusted to be more dense at the corners for smoothness at low values of `$fn`. +// . +// When called as a module, creates a 2D squircle with the desired squareness. +// When called as a function, returns a 2D path for a squircle. +// Arguments: +// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`. +// squareness = Value between 0 and 1. Controls the shape, setting the location of a squircle "corner" at the specified interpolated position between a circle and a square. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. For the "superellipse" style, the special case where the superellipse exponent is 4 (also known as *Lamé's quartic curve*) results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786. Default: 0.5 +// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg" +// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners +// $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated so they are more dense around sharper curves. Default if not set: 48 +// Examples(2D): +// squircle(size=50, squareness=0.4); +// squircle([80,60], 0.7, $fn=64); +// Example(2D): Ten increments of squareness parameter for a superellipse squircle +// for(sq=[0:0.1:1]) +// stroke(squircle(100, sq, style="superellipse", $fn=128), closed=true, width=0.5); +// Example(2D): Standard vector anchors are based on the bounding box +// squircle(50, 0.6) show_anchors(); +// Example(2D): Perimeter anchors, anchoring at bottom left and spinning 20° +// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20) +// show_anchors(); + +module squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) { + check = assert(squareness >= 0 && squareness <= 1); + anchorchk = assert(in_list(atype, ["box", "perim"])); + size = is_num(size) ? [size,size] : point2d(size); + assert(all_positive(size), "All components of size must be positive."); + path = squircle(size, squareness, style, atype="box"); + if (atype == "box") { + attachable(anchor, spin, two_d=true, size=size, extent=false) { + polygon(path); + children(); + } + } else { // atype=="perim" + attachable(anchor, spin, two_d=true, extent=true, path=path) { + polygon(path); + children(); + } + } +} + + +function squircle(size, squareness=0.5, style="fg", atype="box", anchor=CENTER, spin=0) = + assert(squareness >= 0 && squareness <= 1) + assert(is_num(size) || is_vector(size,2)) + assert(in_list(atype, ["box", "perim"])) + let( + size = is_num(size) ? [size,size] : point2d(size), + path = style == "fg" ? _squircle_fg(size, squareness) + : style == "superellipse" ? _squircle_se(size, squareness) + : assert(false, "Style must be \"fg\" or \"superellipse\"") + ) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=atype=="box"?undef:path, p=path, extent=true); + + +/* FG squircle functions */ + +function _squircle_fg(size, squareness) = [ + let( + sq = _linearize_squareness(squareness), + size = is_num(size) ? [size,size] : point2d(size), + aspect = size[1] / size[0], + r = 0.5 * size[0], + astep = $fn>=12 ? 90/round($fn/4) : 360/48 + ) for(a=[360:-astep:0.01]) let( + theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners + p = squircle_radius_fg(sq, r, theta) + ) p*[cos(theta), aspect*sin(theta)] +]; + +function squircle_radius_fg(squareness, r, angle) = let( + s2a = abs(squareness*sin(2*angle)) + ) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r; + +function _linearize_squareness(s) = + // from Chamberlain Fong (2016). "Squircular Calculations". arXiv. + // https://arxiv.org/pdf/1604.02174v5 + let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s) + 2 * sqrt((1+c)*s*s - c*s) / (d*d); + + +/* Superellipse squircle functions */ + +function _squircle_se(size, squareness) = [ + let( + n = _squircle_se_exponent(squareness), + size = is_num(size) ? [size,size] : point2d(size), + ra = 0.5*size[0], + rb = 0.5*size[1], + astep = $fn>=12 ? 90/round($fn/4) : 360/48, + fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta + ) for(a=[360:-astep:0.01]) let( + theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners + x = cos(theta), + y = sin(theta), + r = (abs(x)^n + abs(y)^n)^(1/n) // superellipse + //r = _superformula(theta=theta, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1) + ) [ra*x, rb*y] / r +]; + +function squircle_radius_se(n, r, angle) = let( + x = cos(angle), + y = sin(angle) +) (abs(x)^n + abs(y)^n)^(1/n) / r; + +function _squircle_se_exponent(squareness) = let( + // limit squareness; error if >0.99889, limit is smaller for r>1 + s=min(0.998,squareness), + rho = 1 + s*(sqrt(2)-1), + x = rho / sqrt(2) +) log(0.5) / log(x); + + + // Section: Text // Module: text()