Created
March 14, 2017 03:58
-
-
Save thehans/4a920118f9bf7087d81485a4b262ae39 to your computer and use it in GitHub Desktop.
Userspace functions to replicate basic 2d modules as functions over point vectors, plus path drawing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// enable View->Animate (FPS = 60, Steps = 300, for example), | |
// to see function behavior at different angles | |
p0 = [-4,0]; | |
p1 = [4,0]; | |
angle = 360*$t+90; | |
profile = [p0+polar(3,180+angle), p0, p1, p1+polar(3,-angle)]; | |
$fn=100; | |
drawPath(profile, 1, false); | |
//color("red") showPoints(profile); | |
// draw stroke along path provided by points, with a stroke width == w | |
// if 'close' is true, a closed path will be drawn. | |
module drawPath(points, w=1, close=false) { | |
sp = strokePath(points, w, close); | |
polygon(points=sp[0], paths=sp[1]); | |
} | |
// visualize a vector of 2d points | |
module showPoints(points, r=0.1) { | |
if (len(points[0]) == 3) { | |
for (c = points) translate(c) sphere(r=r,$fn=8); | |
} else { | |
for (c = points) translate(c) circle(r=r,$fn=8); | |
} | |
} | |
/* | |
// Draws a stroke of a given width w along a path defined by points. | |
// If the path points are defined in clockwise order, then the stroke lies on the "outside" of the path. | |
// In other words, drawing path from left to right, stroke would be above the path line, which is why I call that side "topPoints" in code. | |
// The purpose of this is to be able to define a profile of some object as a list of points, | |
// and strokePath will generate a "wall" of a given thickness which will conform to that object | |
// The goal is that nothing intersects the given path; strokePath guarantees this (but only for any 3 contiguous path points). | |
// Its still possible to generate invalid self-intersecting polygons if you input pathological paths that already intersect themselses or come very close with extreme angles | |
// Length of segments should be > w if right angles involved | |
// returns [points, paths] vector to be used with polygon module | |
*/ | |
function strokePath(points, w, close=false) = | |
let ( | |
l0 = len(points), | |
c = close && (l0 > 2), | |
p = c ? | |
concat(points, (points[l0-1]==points[0] ? [points[1]] : [points[0],points[1]])) : | |
points, | |
l = len(p), | |
topPoints = c ? | |
flatten([for (i = [l-1:-1:2]) _angleCheck(p[i], p[i-1], p[i-2], w)]) : // closed loop path | |
concat( | |
[p[l-1] - normal2d(unit(p[l-2] - p[l-1])) * w], // path "top" end point | |
flatten([for (i = [l-1:-1:2]) _angleCheck(p[i], p[i-1], p[i-2], w)]), | |
[p[0] - normal2d(unit(p[0] - p[1])) * w] // path "top" end point | |
), | |
paths = c ? | |
[ range(l,l+len(topPoints)-1), range(0,l-3) ] : | |
[ range(0,l+len(topPoints)-1) ] | |
) | |
[concat(p, topPoints), paths]; | |
// checks if the angle between two adjoining line segments is an inner(armpit) or outer(shoulder) | |
// return a list of points to make up the correct side of this bend. | |
// Only for use in strokePath function | |
function _angleCheck(p0,p1,p2,w) = | |
let ( | |
s1 = p0-p1, | |
s2 = p1-p2, | |
a1 = atan2(s1.y, s1.x), // angle of line segment vs positive horizontal axis | |
a2 = atan2(s2.y, s2.x), | |
_a = a2 - a1, | |
a = abs(_a) > 180 ? _a - sign(_a)*360 : _a, // angle between line segments | |
n1 = normal2d(unit(s1)), // perpendicular unit vector | |
n2 = normal2d(unit(s2)), | |
P0 = p0 + n1 * w, | |
P1 = p1 + n1 * w, // S1 is conceptual segment between P0 and P1 | |
P2 = p1 + n2 * w, | |
P3 = p2 + n2 * w, // S2 is conceptual segment between P2 and P3 | |
i1 = line_intersect(P0,P1,P2,P3), // two top segments intersection | |
i2 = line_intersect(P0,P1,P3,p2), // S1 intersect with stroke end segment (when S2 is short vs high angle) | |
i3 = line_intersect(p0,P0,P2,P3), // S2 intersect with stroke end segment (when S1 is short vs high angle) | |
i4 = line_intersect(p0,p0-n1*w,P2,P3), // end segment inside of S2 | |
i5 = line_intersect(p2,p2-n2*w,P0,P1) // end segment inside of S1 | |
) | |
//[a1,a2,_a,a]; | |
(a > 0 ? | |
//[]: | |
arcPath(w, a, 90+a1, c=p1) : | |
(a < 0 ? | |
(i1 == [] ? | |
(i2 == [] ? | |
(i3 == [] ? | |
(i4 == [] ? | |
(i5 == [] ? | |
[] : | |
concat(i5,[p2]) ) : | |
concat([p0],i4) ) : | |
i3) : | |
i2) : | |
i1) : | |
[P1] | |
) | |
); | |
// ported from C function to find intersection of 2 line segments ( http://stackoverflow.com/a/1968345/251068 ) | |
// returns a list containing single intersection point, or an empty list if none | |
function line_intersect(p0,p1,p2,p3) = | |
let ( | |
s1=p1-p0, | |
s2=p3-p2, | |
s=(-s1.y * (p0.x - p2.x) + s1.x * (p0.y - p2.y)) / (-s2.x * s1.y + s1.x * s2.y), | |
t=( s2.x * (p0.y - p2.y) - s2.y * (p0.x - p2.x)) / (-s2.x * s1.y + s1.x * s2.y) | |
) | |
(s >= 0 && s <= 1 && t >= 0 && t <= 1) ? | |
[p0 + t * s1] : | |
[]; | |
// based on get_fragments_from_r documented on wiki | |
// https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/The_OpenSCAD_Language | |
function fragments(r=1) = ($fn > 0) ? | |
($fn >= 3 ? $fn : 3) : | |
ceil(max(min(360.0 / $fa, r*2*PI / $fs), 5)); | |
// Calculate fragments for a linear dimension. | |
// don't factor in radius-based calculations | |
function linear_fragments(l=1) = ($fn > 0) ? | |
($fn >= 3 ? $fn : 3) : | |
ceil(max(l / $fs),5); | |
function arcPath(r=1, angle=360, offsetAngle=0, c=[0,0], center=false) = | |
let ( | |
fragments = ceil((abs(angle) / 360) * fragments(r,$fn)), | |
step = angle / fragments, | |
a = offsetAngle-(center ? angle/2 : 0) | |
) | |
[ for (i = [0:fragments] ) let(a2=i*step+a) c+r*[cos(a2), sin(a2)] ]; | |
// like arcPath but include center point so polygon will fill in the slice | |
function pie_slice(r=1,angle=90,offsetAngle=0,center=false) = | |
concat(arcPath(r,angle,offsetAngle,center),[0,0]); | |
function circle(r=1) = arcPath(r); | |
function square(size=1, center=false,r=0) = | |
let( | |
x = len(size) ? size.x : size, | |
y = len(size) ? size.y : size, | |
o = center ? [-x/2,-y/2] : [0,0], | |
d = r*2 | |
) | |
assert(d <= x && d <= y) | |
translate(o, | |
(r > 0 ? | |
concat( | |
arcPath(r=r, angle=90, offsetAngle=0, c=[x-r,y-r]), | |
arcPath(r=r, angle=90, offsetAngle=90, c=[ r,y-r]), | |
arcPath(r=r, angle=90, offsetAngle=180, c=[ r, r]), | |
arcPath(r=r, angle=90, offsetAngle=270, c=[x-r, r]) | |
) : | |
[[0,0],[0,y],[x,y],[x,0]] | |
) | |
); | |
function translate(v, points) = [for (p = points) p+v]; | |
function scale(v=1, points) = let(s = len(v) ? v : [v,v,v]) | |
len(points) && len(points[0]) == 3 ? | |
[for (p = points) [s.x*p.x,s.y*p.y,s.z*p.z]] : | |
[for (p = points) [s.x*p.x,s.y*p.y]]; | |
// from http://stackoverflow.com/questions/34050929/3d-point-rotation-algorithm | |
// vector v = [xrot,yrot,zrot] in degrees | |
function rotate_pt(v, p) = | |
let ( | |
pz = (p.z == undef) ? 0 : p.z, | |
cosa = cos(v[2]), sina = sin(v[2]), | |
cosb = cos(v[1]), sinb = sin(v[1]), | |
cosc = cos(v[0]), sinc = sin(v[0]), | |
Axx = cosa*cosb, | |
Axy = cosa*sinb*sinc - sina*cosc, | |
Axz = cosa*sinb*cosc + sina*sinc, | |
Ayx = sina*cosb, | |
Ayy = sina*sinb*sinc + cosa*cosc, | |
Ayz = sina*sinb*cosc - cosa*sinc, | |
Azx = -sinb, | |
Azy = cosb*sinc, | |
Azz = cosb*cosc | |
) | |
[Axx*p.x + Axy*p.y + Axz*pz, | |
Ayx*p.x + Ayy*p.y + Ayz*pz, | |
Azx*p.x + Azy*p.y + Azz*pz]; | |
// rotate a list of 3d points by angle vector v | |
// vector v = [pitch,roll,yaw] in degrees | |
function rotate(v, points) = [for (p = points) rotate_pt(v,p)]; | |
// basic utility functions | |
function osc(xmin=-1,xmax=1,freq=1,phase=0) = xmin + (xmax - xmin) * (sin(360*freq*$t+360*phase)+1)/2; | |
function unit(v) = v / norm(v); // convert vector to unit vector | |
function normal2d(v) = [-v.y, v.x]; // normal(perpendicular line) to a 2d vector | |
function flatten(l) = [ for (a = l) for (b = a) b ]; | |
function polar(r,angle) = [r*cos(angle), r*sin(angle)]; | |
function reverse(v) = [ for (i = [0:len(v)-1]) v[len(v) -1 - i] ]; | |
function range(a,b) = [ for (i = [a:b]) i ]; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment