import java.awt.*; import java.util.Vector; class Spline extends Object3D implements java.io.Serializable { Spline() { type = 1; ctrlPnts = new Vector(0,0); ctrlPnts.addElement(LA.newVector(1, -1, 0)); ctrlPnts.addElement(LA.newVector(1, 1, 0)); } Object3D deepCopy() { Spline spline = new Spline(); deepCopySelf(spline); return spline; } protected void deepCopySelf(Object3D other) { super.deepCopySelf(other); Spline spline = (Spline)other; spline.type = type; int count = ctrlPnts.size(); spline.ctrlPnts = new Vector(0,0); for (int i=0; i < count; i++) { cVector p = (cVector)ctrlPnts.elementAt(i); cVector q = new cVector(); LA.vecCopy(p, q); spline.ctrlPnts.addElement(q); } } void generatePOV(StringBuffer buffer) { int nPoints = ctrlPnts.size(); int degree = type; if (degree > nPoints - 1) degree = nPoints - 1; generateIndent(buffer); buffer.append(" "); switch (degree) { case 1: // '\001' buffer.append("linear_spline "); break; case 2: // '\002' buffer.append("quadratic_spline "); break; case 3: // '\003' buffer.append("cubic_spline "); break; } buffer.append(nPoints); buffer.append("\n"); for (int i=0; i < nPoints; i++) { cVector p = (cVector)ctrlPnts.elementAt(i); generateIndent(buffer); buffer.append(" <"); buffer.append(p.x); buffer.append(','); buffer.append(p.y); buffer.append('>'); if (i < nPoints - 1) buffer.append(','); buffer.append('\n'); } } void getCoeffs(int seg, int degree, double a[], double b[], double c[], double d[]) { cVector p2 = null; cVector p3 = null; cVector p0 = (cVector)ctrlPnts.elementAt(seg); cVector p1 = (cVector)ctrlPnts.elementAt(seg + 1); if (degree == 2 || degree == 3) p2 = (cVector)ctrlPnts.elementAt(seg + 2); if (degree == 3) p3 = (cVector)ctrlPnts.elementAt(seg + 3); //a[2] = b[2] = c[2] = d[2] = 0; for (int i=0; i < 2; i++) switch (degree) { case 1: // '\001' a[i] = 0; b[i] = 0; c[i] = -1 * p0.get(i) + 1 * p1.get(i); d[i] = 1 * p0.get(i); break; case 2: // '\002' a[i] = 0; b[i] = (0.5 * p0.get(i) - 1 * p1.get(i)) + 0.5 * p2.get(i); c[i] = -0.5 * p0.get(i) + 0.5 * p2.get(i); d[i] = 1 * p1.get(i); break; case 3: // '\003' a[i] = ((-0.5 * p0.get(i) + 1.5 * p1.get(i)) - 1.5 * p2.get(i)) + 0.5 * p3.get(i); b[i] = ((1 * p0.get(i) - 2.5 * p1.get(i)) + 2 * p2.get(i)) - 0.5 * p3.get(i); c[i] = -0.5 * p0.get(i) + 0.5 * p2.get(i); d[i] = 1 * p1.get(i); break; } } void solve(double t, double a[], double b[], double c[], double d[], cVector output) { output.x = a[0] * t * t * t + b[0] * t * t + c[0] * t + d[0]; output.y = a[1] * t * t * t + b[1] * t * t + c[1] * t + d[1]; output.z = 0; } Vertex paramFunction(double t) { int nPoints = ctrlPnts.size(); int degree = type; if (degree > nPoints - 1) degree = nPoints - 1; int nSegs = nPoints - degree; double interval = 1 / (double)nSegs; int seg = (int)(t / interval); if (seg >= nSegs) seg = nSegs - 1; double base = (double)seg * interval; t = (t - base) / interval; getCoeffs(seg, degree, a, b, c, d); Vertex temp = new Vertex(); //temp.pos = new cVector(); solve(t, a, b, c, d, temp/*.pos*/); return temp; } void draw(ClickInfo info, int level, boolean select) { cVector sample = new cVector(); info.g.setColor(Color.black); int nPoints = ctrlPnts.size(); int degree = type; if (degree > nPoints - 1) degree = nPoints - 1; int nSegs = nPoints - degree; for (int i=0; i < nSegs; i++) { getCoeffs(i, degree, a, b, c, d); solve(0, a, b, c, d, sample); Point prev = new Point(0, 0); Point curr = new Point(0, 0); Rectangle dummy = new Rectangle(); calcHotSpot(sample, //info, prev, dummy); for (double t = 0.1; t < 1.01; t += 0.1) { solve(t, a, b, c, d, sample); calcHotSpot(sample, //info, curr, dummy); info.g.drawLine(prev.x, prev.y, curr.x, curr.y); info.g.drawLine(prev.x, prev.y + 1, curr.x, curr.y + 1); info.g.drawLine(prev.x + 1, prev.y, curr.x + 1, curr.y); info.g.drawLine(prev.x + 1, prev.y + 1, curr.x + 1, curr.y + 1); prev.x = curr.x; prev.y = curr.y; } } } void drawEditHandles(ClickInfo info, int level) { info.g.setColor(Color.red); int count = ctrlPnts.size(); for (int i=0; i < count; i++) { cVector p = (cVector)ctrlPnts.elementAt(i); Rectangle spot = calcHotSpot(p); //, info); info.g.fillRect(spot.x, spot.y, spot.width, spot.height); } } boolean doEditClick(ClickInfo info, int level) { boolean modified = (info.modifiers & CameraPane.SHIFT) != 0; // Was META startX = info.x; startY = info.y; int nPoints = ctrlPnts.size(); hitSomething = false; for (int i=0; i < nPoints; i++) { cVector p = (cVector)ctrlPnts.elementAt(i); Rectangle r = calcHotSpot(p); //, info); if (r.contains(info.x, info.y)) { hitSomething = true; hitIndex = i; LA.vecCopy(p, startVec); } } if (hitSomething) if (modified) { if (nPoints > 2) ctrlPnts.removeElementAt(hitIndex); return false; } else { return true; } if (!modified) return false; int degree = type; if (degree > nPoints - 1) degree = nPoints - 1; int nSegs = nPoints - degree; if (degree == 1) hitIndex = 1; else hitIndex = 2; cVector sample = new cVector(); Point pnt = new Point(0, 0); Rectangle rect = new Rectangle(); for (int i=0; i < nSegs && !hitSomething;) { getCoeffs(i, degree, a, b, c, d); for (double t = 0; t < 1.001; t += 0.01) { solve(t, a, b, c, d, sample); calcHotSpot(sample, //info, pnt, rect); if (!rect.contains(info.x, info.y)) continue; hitSomething = true; for (int j = 0; j < 3; j++) sample.set(j, (int)sample.get(j)); ctrlPnts.insertElementAt(sample, hitIndex); LA.vecCopy(sample, startVec); info.pane.repaint(); break; } i++; hitIndex++; } hitIndex--; return hitSomething; } void doEditDrag(ClickInfo info) { if (!hitSomething) return; cVector delta = LA.newVector(info.x - startX, startY - info.y, 0); LA.xformDir(delta, info.camera.fromScreen, delta); delta.x /= 100 * info.camera.SCALE / info.camera.Distance(); delta.y /= 100 * info.camera.SCALE / info.camera.Distance(); delta.z /= 100 * info.camera.SCALE / info.camera.Distance(); cVector p = (cVector)ctrlPnts.elementAt(hitIndex); LA.vecCopy(startVec, p); LA.vecAdd(p, delta, p); if (p.x < 0) p.x = 0; info.pane.repaint(); } void getBounds(cVector minima, cVector maxima, boolean xform) { minima.z = maxima.z = 0; minima.x = minima.y = 1E+20; maxima.x = maxima.y = -1E+20; int nPoints = ctrlPnts.size(); for (int i=0; i < nPoints; i++) { cVector p = (cVector)ctrlPnts.elementAt(i); for (int j = 0; j < 2; j++) { if (p.get(j) > maxima.get(j)) maxima.set(j, p.get(j)); if (p.get(j) < minima.get(j)) minima.set(j, p.get(j)); } } } int intersectHorizontal(double x, double y) { int nPoints = ctrlPnts.size(); int degree = type; if (degree > nPoints - 1) degree = nPoints - 1; int nSegs = nPoints - degree; int count = 0; double domain[] = { 0, 1 }; for (int i=0; i < nSegs; i++) { getCoeffs(i, degree, a, b, c, d); count += intersectOne(x, y, domain); } return count; } int intersectOne(double x, double y, double domain[]) { Interval.boxCube(domain, io1); Interval.boxScale(io1, a[1]); Interval.boxSquare(domain, io2); Interval.boxScale(io2, b[1]); Interval.boxAdd(io1, io2, io1); io2[0] = domain[0]; io2[1] = domain[1]; Interval.boxScale(io2, c[1]); Interval.boxAdd(io1, io2, io1); io1[0] += d[1]; io1[1] += d[1]; if (io1[0] > y || io1[1] < y) return 0; double t = (domain[0] + domain[1]) / 2; double x2 = a[0] * t * t * t + b[0] * t * t + c[0] * t + d[0]; if (domain[1] - domain[0] < 0.0625) { return x2 <= x ? 0 : 1; } else { double d1[] = new double[2]; double d2[] = new double[2]; d1[0] = domain[0]; d2[1] = domain[1]; d1[1] = d2[0] = t; return intersectOne(x, y, d1) + intersectOne(x, y, d2); } } public static final int LINEAR = 1; public static final int QUADRATIC = 2; public static final int CUBIC = 3; int type; Vector ctrlPnts; private static double a[] = new double[2]; private static double b[] = new double[2]; private static double c[] = new double[2]; private static double d[] = new double[2]; private static int startX; private static int startY; private static boolean hitSomething; private static int hitIndex; private static cVector startVec = new cVector(); private static double io1[] = new double[2]; private static double io2[] = new double[2]; }