/** 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] }