/** This program extrudes 3-D models along a parametric curve. It is quite
smart and creates its own adaptive step size to move along the parameter
with a minimum of steps but without gaps in the tool's position.
TODO: Add a two-variable parametric equation which is trickier. Ideally,
the function passed in should be differentiable by Frink which would allow
intelligent step sizes to be calculated directly.
*/
/** This calculates the tool path to move a tool along a parametric curve
specified by a function f that takes two arguments:
f[t, data]
where t is a value that increases from t0 to t1 and data is an
arbitrary expression that can be used to pass additional data to the
function. The function should return an array of [x, y, z] values with
dimensions of length.
res: The resolution of the model with dimensions of inverse length, e.g.,
254/inch
This function is smart in that it adaptively adjusts the step used to move
the parameter t with a minimum of steps but without gaps in the tool's
position.
This returns a frink.graphics.Point3DIntList which specifies 3-D integer
coordinates where the tool should pass through. The tool's path can be
instantiated into a 3-D model using VoxelArray.paintAlongPath
*/
calculatePath[f, data, t0, t1, res] :=
{
points = newJava["frink.graphics.Point3DIntList", []]
tstep = (t1-t0)/1000. // This timestep will be auto-adjusted.
t = t0
[x, y, z] = f[t, data]
// println["$x, $y, $z"]
ix = round[x res]
iy = round[y res]
iz = round[z res]
points.addPoint[ix, iy, iz]
// Last coordinates
lx = x
ly = y
lz = z
while t <= t1
{
do
{
tryagain = false
[x, y, z] = f[t+tstep, data]
ix = round[x res]
iy = round[y res]
iz = round[z res]
dx = abs[x - lx] res
dy = abs[y - ly] res
dz = abs[z - lz] res
idx = round[dx]
idy = round[dy]
idz = round[dz]
// Check to see if the voxel didn't move or if it moved by more than
// 1 pixel on any axis. If so, adjust the step mathematically.
if ((idx== 0) and (idy == 0) and (idz==0)) or (abs[idx]>1) or (abs[idy]>1) or (abs[idz]>1)
{
d = sqrt[dx^2 + dy^2 + dz^2]
//d = max[[dx, dy, dz]]
if d > .98 and d < 1.02 // Prevent too-small adjustments
d = d^2
tstep = tstep / d
println["Adjusting tstep to $tstep"]
tryagain = true
} else
{
// println["$ix $iy $iz"]
points.addPoint[ix, iy, iz]
}
} while tryagain
lx = x
ly = y
lz = z
t = t + tstep
}
// Make sure we contain the exact last point.
[x, y, z] = f[t1, data]
ix = round[x res]
iy = round[y res]
iz = round[z res]
points.addPoint[ix, iy, iz]
return points
}
/** Sample parametric function to draw a helix. The "data" parameter is
[radius, pitch, angle0] where angle0 indicates the "start"
of the curve.
*/
helix[t, data] :=
{
[radius, pitch, angle0] = data
if angle0 == undef
angle0 = 0 deg
tt = t + angle0
x = radius cos[tt]
y = radius sin[tt]
z = (t / (2 pi)) pitch
return [x, y, z]
}
// Sample parametric function to draw a Moebius strip. See
// https://mathworld.wolfram.com/MoebiusStrip.html
// s should vary from -w to w where w is the half-width
// T should vary from 0 to 2 pi
MobiusStrip[t, data] :=
{
[R, s] = data
x = (R + s cos[1/2 t]) cos[t]
y = (R + s cos[1/2 t]) sin[t]
z = s sin[1/2 t]
return [x,y,z]
}