Skip to content

Instantly share code, notes, and snippets.

@thehans
Created March 14, 2017 03:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thehans/4a920118f9bf7087d81485a4b262ae39 to your computer and use it in GitHub Desktop.
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
// 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