/* Spline Applet by James Stewart (jstewart@cs.toronto.edu) * * Demonstrates various splines. * * Classes: * * class Splines extends Applet top-level applet * * class DrawXYPanel extends Panel panel on which all drawing is done * class ControlPanel extends Panel panel for control buttons * class CurveCanvas extends Panel panel for a single curve x(t),y(t) * * abstract class Curve extends Object piecewise cubic curve * class CatmullRomCurve1D extends Curve CatmullRom curve p(t) */ import java.awt.*; import java.applet.*; import java.util.*; public class Splines extends Applet { CurvePanel dp, cp; // Set up the two panels public void init() { setBackground( Color.darkGray ); if (getParameter( "basisfunctions" ) != null) dp = new DrawBasisPanel(); else if (getParameter( "spacecurve" ) != null) dp = new DrawXYPanel(); // cp = new ControlPanel( dp ); parse_parameters(); setLayout(new BorderLayout()); add( "Center", dp ); // add( "North", cp ); resize( dp.preferredSize() ); } // main() for applets started from the command line public static void main(String args[]) { Frame frame; Splines splines; frame = new Frame( "Splines" ); frame.resize( 625, 700 ); splines = new Splines(); splines.init(); frame.add( "Center", splines ); frame.show(); } void parse_parameters() { String curve_string; // Read curve curve_string = getParameter( "curve2D" ); if (curve_string != null) dp.parseCurveString2D( curve_string ); } } /* ctrlPt * * A control point, (x,y). Both are doubles. */ class ctrlPt extends Object { double x, y; public ctrlPt( double X, double Y ) { x = X; y = Y; } } /* Curve * * Abstract class that handles piecewise cubic curves, where each * piece is of the from p(t) = T M V. */ abstract class Curve extends Object { int num_points; // number of control points int num_curves; // number of separate curves int points_per_seg; // number of control points used per segment int seg_offset; // offset between segments in control point vector ctrlPt points[]; // control points (size = num_points * num_curves) // all points are stored here, grouped by curve Color point_colors[]; // colors of control points (size = num_points * num_curves) Color seg_colors[]; // colors of curve segments // (size = (1+(num_points-points_per_seg)/seg_offset) // * num_curves) int num_steps = 10; // steps to draw each curve segement int point_diameter = 7; // diameter of a drawn control point boolean draw_ctrl_pts = true; // draw control points boolean draw_segs = true; // draw segments between control points double maxy, miny; // min and max y-values for control points double Mx[][]; // change-of-basis matrix for x(t) double My[][]; // change-of-basis matrix for y(t) double TMx[][] = new double[num_steps+1][4]; // precomputed TM = [t^3 t^2 t 1] M double TMy[][] = new double[num_steps+1][4]; // Precompute T M for t=0..1, for x and y. protected void precomputeTM() { double t; int s, i; t = 0; for (s=0; s= points[i].x * pixels_per_unit - point_diameter/2 - 1 && y <= points[i].y * pixels_per_unit + point_diameter/2 + 1 && y >= points[i].y * pixels_per_unit - point_diameter/2 - 1) return i; return -1; } public void move_point_to( int index, double x, double y ) { if (index < 0 || index >= num_points * num_curves) throw new IndexOutOfBoundsException(); points[index].y = y; points[index].x = x; } } /* CurveCanvas * * Display a curve with a set of axes. Handles mouse drags on the control points. */ class CurveCanvas extends Canvas { Vector curves; // all curves on this canvas Curve current_curve; // curve currently being dragged final int border_offset = 10; // width of empty border around graph int pixels_per_unit; // number of pixels per unit on axis int point_index = -1; // currently dragged control point Font label_font = null; // font for axis labels FontMetrics fm; // metrics for axis labels Dimension canvasSize; // preferred size of canvas String vlabel, hlabel; // graph labels: vertical and horizontal int xOrigin, yOrigin, xMax, yMax, xMin, yMin; // drawing area float xMinClip, xMaxClip; // clipping in x int allow_x_mod = 0; // allow mouse to drag points in x direction int allow_y_mod = 0; // allow mouse to drag points in y direction CurvePanel panel; // panel that contains this curve panel public CurveCanvas( CurvePanel p, int ppu, double minXUnits, double maxXUnits, double minYUnits, double maxYUnits, int allowX, int allowY, String vert_label, String horiz_label ) { curves = new Vector(); pixels_per_unit = ppu; panel = p; allow_x_mod = allowX; allow_y_mod = allowY; label_font = new Font("Times", Font.PLAIN, 14); setFont( label_font ); fm = getFontMetrics(label_font); vlabel = new String( vert_label ); hlabel = new String( horiz_label ); xMax = (int) (maxXUnits * pixels_per_unit); // bounds of drawing area yMax = (int) (maxYUnits * pixels_per_unit); xMin = (int) (minXUnits * pixels_per_unit); // bounds of drawing area yMin = (int) (minYUnits * pixels_per_unit); xMinClip = xMin; xMaxClip = xMax; xOrigin = border_offset + fm.stringWidth("9") + 7 - xMin; // origin of drawing area yOrigin = border_offset + fm.getAscent() + 7 - yMin; canvasSize = new Dimension( 2*border_offset + xMax - xMin + 1 + fm.stringWidth(hlabel) + 4 // hlabel + fm.stringWidth("9") + 7, // vertical numbers 2*border_offset + yMax - yMin + 1 + fm.getAscent() + fm.getDescent() + 4 // vlabel + fm.getAscent()/2 // topmost vertical number + fm.getAscent() + 7 // horizontal numbers ); resize( canvasSize ); } public void addCurve( Curve c ) { curves.addElement( c ); } public Dimension preferredSize() { return canvasSize; } public void paint( Graphics g ) { int i; // Put origin in lower-left corner. Then we just have to negate y-values g.translate( xOrigin, size().height - yOrigin ); // Label the origin g.setColor( Color.white ); setFont( label_font ); g.drawString( Integer.toString(0), -fm.stringWidth(Integer.toString(0))-2, -(-fm.getAscent()-2) ); // Draw horizontal axis, ticks, and numbers g.drawLine( xMin, 0, xMax, 0 ); g.drawString( hlabel, xMax+4, -(-fm.getAscent()/2+2) ); for (i = 1 + xMin/pixels_per_unit; i < 1 + xMax/pixels_per_unit; i++) if (i != 0) { int x = i * pixels_per_unit; g.drawString( Integer.toString(i), x - fm.stringWidth(Integer.toString(i)) / 2, -(-fm.getAscent()-7) ); g.drawLine( x, 0, x, 4 ); } // Draw vertical axis, ticks, and numbers g.drawLine( 0, -yMin, 0, -yMax ); g.drawString( vlabel, -fm.stringWidth(vlabel)/2, -(yMax+4+fm.getDescent()+fm.getAscent()/2) ); for (i = 1 + yMin/pixels_per_unit; i < 1 + yMax/pixels_per_unit; i++) { int y = -(i * pixels_per_unit); g.drawString( Integer.toString(i), -fm.stringWidth(Integer.toString(i))-7, y + fm.getAscent() / 2 ); g.drawLine( -4, y, 0, y ); } // Draw curve g.clipRect( (int)(xMinClip*pixels_per_unit)-3, -(size().height-yOrigin), (int)((xMaxClip-xMinClip)*pixels_per_unit)+6, size().height ); // -3, +6 for ctrl pts for (Enumeration e = curves.elements(); e.hasMoreElements(); ) ((Curve) e.nextElement()).draw( g, pixels_per_unit ); // Notify the panel so that it can update the other curves panel.paintNotify( this ); } /* * mouse events */ public boolean mouseDown( Event event, int x, int y ) { int xx = x - xOrigin; int yy = size().height - y - yOrigin; // Find the curve and control point under the mouse for (Enumeration e = curves.elements(); e.hasMoreElements(); ) { Curve curve = (Curve) e.nextElement(); point_index = curve.find_point( xx, yy, pixels_per_unit ); if (point_index != -1) { current_curve = curve; break; } } return (point_index != -1); } public boolean mouseUp( Event event, int x, int y ) { if (point_index == -1) return false; else { point_index = -1; return true; } } public boolean mouseDrag( Event event, int x, int y ) { if (point_index > -1) { // Compute point in our translated, flipped screen space int xx = x - xOrigin; int yy = size().height - y - yOrigin; // Clip point to rectangle within border if (yy < 0) yy = 0; else if (yy > yMax) yy = yMax; if (xx < 0) xx = 0; else if (xx > xMax) xx = xMax; // If modification to X or Y is forbidden, set that X or Y // to the current point's X or Y value if (allow_x_mod == 0) xx = (int) (current_curve.points[point_index].x * pixels_per_unit); if (allow_y_mod == 0) yy = (int) (current_curve.points[point_index].y * pixels_per_unit); // Update the point current_curve.move_point_to( point_index, xx / (double) pixels_per_unit, yy / (double) pixels_per_unit ); repaint(); } return (point_index != -1); } } /* CatmullRomCurve1D * * A CatmullRom curve of one dimension. y(t) is CatmullRom curve based on the * control points, while x(t) = (1-t) x_i + t x_{i+1}. */ class CatmullRomCurve1D extends Curve { double Linear[][] = { { 0, 0, 0, 0}, { 0, 0, 0, 0}, { 0,-1, 1, 0}, { 0, 1, 0, 0}, }; double CatmullRom[][] = { { -0.5, 1.5, -1.5, 0.5 }, { 1.0, -2.5, 2.0, -0.5 }, { -0.5, 0.0, 0.5, 0.0 }, { 0.0, 1.0, 0.0, 0.0 } }; CatmullRomCurve1D( int num_pts, ctrlPt pts[], Color pt_colors[], Color sg_colors[], int num_crvs ) { Mx = Linear; // x(t) = (1-t) x_i + t x_{i+1} My = CatmullRom; // y(t) = B_0(t) x_{i-1} + ... + B_3(t) x_{i+2} points_per_seg = 4; // four points define each seg: x_{i-1} ... x_{i+2} seg_offset = 1; // starting point, x_{i-1}, is offset by one in adjacent segs num_points = num_pts; // store control points and their colours num_curves = num_crvs; points = pts; point_colors = pt_colors; seg_colors = sg_colors; precomputeTM(); // precompute T M part of p(t) = T M V } } /* CatmullRomCurve2D * * A CatmullRom curve of two dimensions. (x(t),y(t)) is CatmullRom curve * based on the control points. */ class CatmullRomCurve2D extends Curve { double CatmullRom[][] = { { -0.5, 1.5, -1.5, 0.5 }, { 1.0, -2.5, 2.0, -0.5 }, { -0.5, 0.0, 0.5, 0.0 }, { 0.0, 1.0, 0.0, 0.0 } }; CatmullRomCurve2D( int num_pts, ctrlPt pts[], Color pt_colors[], Color sg_colors[] ) { Mx = CatmullRom; // x(t) = B_0(t) x_{i-1} + ... + B_3(t) x_{i+2} My = CatmullRom; // y(t) = B_0(t) y_{i-1} + ... + B_3(t) y_{i+2} points_per_seg = 4; // four points define each seg: x_{i-1} ... x_{i+2} seg_offset = 1; // starting point, x_{i-1}, is offset by one in adjacent segs num_points = num_pts; // store control points and their colours num_curves = 1; points = pts; point_colors = pt_colors; seg_colors = sg_colors; precomputeTM(); // precompute T M part of p(t) = T M V } } abstract class CurvePanel extends Panel { public synchronized void paintNotify( Canvas p ) { } public void parseCurveString2D( String s ) { } public void parseCurveString1D( String s ) { } } /* DrawXYPanel * * Draws three curves simultaneously: x(t), y(t), and (x(t),y(t)). Allows * the mouse to drag control points on any curve, while updating the others. */ class DrawXYPanel extends CurvePanel { int num_points = 7; Color colors[] = { Color.green, Color.cyan, Color.red, Color.orange, Color.magenta, Color.yellow, Color.pink }; double initialPoints[][] = { {0,3}, {1,2}, {3,3}, {2,1}, {4,1}, {2,2}, {3,4} }; int pixels_per_unit = 30; Curve spline_x, spline_y, spline_xy; CurveCanvas xCanvas, yCanvas, xyCanvas; ctrlPt x_points[] = new ctrlPt[num_points]; ctrlPt y_points[] = new ctrlPt[num_points]; ctrlPt xy_points[] = new ctrlPt[num_points]; Color pt_colors[] = new Color[num_points]; Color seg_colors[] = new Color[1 + (num_points-4)/1]; // 4 points per seg, offset 1 int skip_repaint = 0; // see paintNotify() below DrawXYPanel() { int i; double maxX, maxY; // Set up points and colors for (i=0; i maxX) // find max X and Y points maxX = initialPoints[i][0]; if (initialPoints[i][1] > maxY) maxY = initialPoints[i][1]; } // Set up panel for (x(t),t) Spline spline_x = new CatmullRomCurve1D( num_points, x_points, pt_colors, seg_colors, 1 ); xCanvas = new CurveCanvas( this, pixels_per_unit, 0, num_points-1, 0, maxX+1, 0, 1, "x(t)", "t" ); xCanvas.addCurve( spline_x ); // Set up panel for (y(t),t) Spline spline_y = new CatmullRomCurve1D( num_points, y_points, pt_colors, seg_colors, 1 ); yCanvas = new CurveCanvas( this, pixels_per_unit, 0, num_points-1, 0, maxY+1, 0, 1, "y(t)", "t" ); yCanvas.addCurve( spline_y ); // Set up panel for (x(t),y(t)) Spline spline_xy = new CatmullRomCurve2D( num_points, xy_points, pt_colors, seg_colors ); xyCanvas = new CurveCanvas( this, pixels_per_unit, 0, maxX+1, 0, maxY+1, 1, 1, "y(t)", "x(t)" ); xyCanvas.addCurve( spline_xy ); // Arrange on the screen GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout( gridbag ); c.fill = GridBagConstraints.NONE; c.weightx = 0.0; c.weighty = 0.0; c.gridwidth = 1; c.gridheight = 2; c.gridx = 0; c.gridy = 0; gridbag.setConstraints( xyCanvas, c ); add( xyCanvas ); c.gridwidth = 1; c.gridheight = 1; c.gridx = 1; c.gridy = 0; gridbag.setConstraints( xCanvas, c ); add( xCanvas ); c.gridwidth = 1; c.gridheight = 1; c.gridx = 1; c.gridy = 1; gridbag.setConstraints( yCanvas, c ); add( yCanvas ); } public void parseCurveString2D( String string ) { // WARNING: this doesn't work yet! StringTokenizer t = new StringTokenizer( string ); int p = 0; int n = t.countTokens(); if (n % 2 != 0) { System.out.println( "ERROR: parameter `curve' is of odd length" ); return; } num_points = n/2; initialPoints = new double[num_points][2]; // Parse each number in string for (int i=0; i 0) { skip_repaint--; return; } if (p == xCanvas) { // copy x to xy & redraw xy for (i=0; i maxX) maxX = initialPoints[i]; } // Set up panel for x(t) xCanvas = new CurveCanvas( this, PPU, 0, num_points-1, 0, maxX+0.5, 0, 1, "p(t)", "t" ); xCurve = new CatmullRomCurve1D( num_points, xPoints, xPtColors, xSegColors, 1 ); xCanvas.addCurve( xCurve ); // Set up panel for basis curves basisCanvas = new CurveCanvas( this, PPU, 0, num_points-1, 0, maxX+0.5, 0, 0, "xi*Bi(t)", "t" ); // Set up points and colors for basis curves. Each basis curve // uses seven control points; each is 0 except the middle, which // is 1. There are num_points basis curves. basisCurves = new Curve[ num_points ]; basisPoints = new ctrlPt[ num_points ][ 7 ]; basisPtColors = new Color[ num_points ][ 7 ]; basisSegColors = new Color[ num_points ][ 5 ]; for (i=0; i 0) { skip_repaint--; return; } for (i=0; i