/* This file is part of the source code for 3D-XplorMath-J, Version 1.0 (January 2008).
* Copyright (c) 2008 The 3D-XplorMath Consortium (http://3d-xplormath.org).
* This source code is released under a BSD License, which allows redistribution
* in source and binary form, with or without modification, provided copyright
* and license information are included, and with no warranty or guarantee of
* any kind. For details, see http://3d-xplormath.org/j/source/BSDLicense.txt
*/
package vmm.planecurve.parametric;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.geom.Point2D;
import vmm.actions.AbstractActionVMM;
import vmm.actions.ActionList;
import vmm.actions.ToggleAction;
import vmm.core.Animation;
import vmm.core.Display;
import vmm.core.I18n;
import vmm.core.IntegerParam;
import vmm.core.RealParam;
import vmm.core.SaveAndRestore;
import vmm.core.TimerAnimation;
import vmm.core.Transform;
import vmm.core.VMMSave;
import vmm.core.View;
import vmm.planecurve.PlaneCurve;
/**
* A curve in the plane that is defined by differentiable functions x(t) and y(t).
*/
abstract public class PlaneCurveParametric extends PlaneCurve {
/**
* The number of t-values for points on the curve. Points of the form (x(t),y(t))
* are computed for 1 plus this many values of t, and these points are joined by line segments
* to draw the curve. Note that the starting and ending values of t are given by
* the values of {@link #tmin} and {@link #tmax}. This interval is divided into
* tResolution sub-intervals, giving tResolution+1 points.
*/
protected IntegerParam tResolution;
/**
* The minimum valur of t for points on the curve.
*/
protected RealParam tmin;
/**
* The maximum value of t for points on the curve.
*/
protected RealParam tmax;
/**
* The t-values used to compute the points (x(t),y(t)) on the curve. These are
* evenly spaced between the minimum and maximum t-values.
*/
protected double[] tVals; // input t-values for computing the points on the curve.
/**
* Computes x(t) for a given value of t.
*/
abstract public double xValue(double t);
/**
* Computes y(t) for a given value of t.
*/
abstract public double yValue(double t);
/**
* Computes x'(t) for a given value of t. The default version defined in this class computes an approximate
* value numerically, using values of the function itself at points near t.
*/
public double xDerivativeValue(double t) {
double dx = 0.001; // TODO: decide what value to use here and in second derivative
double f1 = xValue(t + dx);
double f2 = xValue(t + 2*dx);
double g1 = xValue(t - dx);
double g2 = xValue(t - 2*dx);
return ((8 * f1 + g2) - (f2 + 8 * g1))/(12 * dx);
}
/**
* Computes y'(t) for a given value of t. The default version defined in this class computes an approximate
* value numerically, using values of the function itself at points near t.
*/
public double yDerivativeValue(double t) {
double dx = 0.001;
double f1 = yValue(t + dx);
double f2 = yValue(t + 2*dx);
double g1 = yValue(t - dx);
double g2 = yValue(t - 2*dx);
return ((8 * f1 + g2) - (f2 + 8 * g1))/(12 * dx);
}
/**
* Computes x''(t) for a given value of t. The default version defined in this class computes an approximate
* value numerically, using values of the function itself at points near t.
*/
public double x2ndDerivativeValue(double t) {
double dx = 0.001;
double f0 = xValue(t);
double f1 = xValue(t + dx);
double f2 = xValue(t + 2*dx);
double g1 = xValue(t - dx);
double g2 = xValue(t - 2*dx);
return ((16 * f1 + 16 * g1) - 30 * f0 - f2 - g2) / (12 * dx * dx);
}
/**
* Computes y''(t) for a given value of t. The default version defined in this class computes an approximate
* value numerically, using values of the function itself at points near t.
*/
public double y2ndDerivativeValue(double t) {
double dx = 0.001;
double f0 = yValue(t);
double f1 = yValue(t + dx);
double f2 = yValue(t + 2*dx);
double g1 = yValue(t - dx);
double g2 = yValue(t - 2*dx);
return ((16 * f1 + 16 * g1) - 30 * f0 - f2 - g2) / (12 * dx * dx);
}
/**
* Construct a plane curve with Parameters tmin, tmax, and tResolution. The
* curve will be defined by the functions xValue and yValue
* in a concrete subclass.
*/
public PlaneCurveParametric() {
tmin = new RealParam("vmm.planecurve.parametric.PlaneCurveParameteric.tmin",-5);
tmax = new RealParam("vmm.planecurve.parametric.PlaneCurveParameteric.tmax",5);
tResolution = new IntegerParam("vmm.planecurve.parametric.PlaneCurveParameteric.tResolution",100);
tResolution.setMinimumValueForInput(4);
tResolution.setMaximumValueForInput(2000);
addParameter(tResolution);
addParameter(tmax);
addParameter(tmin);
}
/**
* Returns the number of intervals into which the curve is divided. Note that the number
* of points that are drawn on the curve is one more than the tResolution.
*/
public int getTResolution() {
return tResolution.getValue();
}
/**
* Return the t-value for one of the points on the curve. The number of points
* is getTResolution() + 1, and they are numbered from 0 to getTResolution().
* If this method is called before the curve has been
* drawn, the return value will be Double.NaN. The return value is also Double.NaN
* if the specified index is not in the range 0 to getTResolution(), inclusive.
* The corresponding point on the curve can be obtained by calling xValue() and yValue()
*
The value returned by this method is only valid after the curve has been drawn; * if it is called before that time, the return value will be Double.NaN. * @param index A position in the array of t-values that specifies which t-value shoud * be returned. * @see #getTResolution() * @see #xValue(double) * @see #yValue(double) */ public double getT(int index) { if (tVals == null || index < 0 || index > tVals.length) return Double.NaN; else return tVals[index]; } /** * parametrized circle to draw circle arcs in different colors. */ public Point2D[] myCircle(double mx, double my, double rad, int numPoints){ Point2D[] circPts = new Point2D[numPoints+1]; double s = 0.0; for (int i = 0; i <= numPoints; i++) { s = i*2*Math.PI/numPoints; circPts[i] = new Point2D.Double(mx +rad*Math.cos(s),my +rad*Math.sin(s)); } return circPts; } /** * Calcululates the array of points for this curve using the functions {@link #xValue(double)} and * {@link #yValue(double)} to compute the points at equally spaced t-values between {@link #tmin} and {@link #tmax}. * The number of points is one plus the value of {@link #tResolution}. */ protected void makePoints() { int subintervals = tResolution.getValue(); tVals = new double[subintervals+1]; points = new Point2D[subintervals+1]; double t = tmin.getValue(); double dt = (tmax.getValue() - t)/subintervals; for (int i = 0; i <= subintervals; i++) { tVals[i] = t + dt*i; } for (int i = 0; i <= subintervals; i++) { double x = xValue(tVals[i]); double y = yValue(tVals[i]); if (Double.isNaN(x) || Double.isNaN(y) || Double.isInfinite(x) || Double.isInfinite(y)) points[i] = null; else points[i] = new Point2D.Double(x,y); } } /** * Draw the curve in a specified View. If the View belogs to the nested class * {@link PlaneCurveParametricView} (which is the default), then only a fraction of the curve * might be drawn, as specified in the View; this feature is used in the * creation animation for the curve. */ public void doDraw(Graphics2D g, View view, Transform transform) { if (points.length == 0) return; int pointCt = points.length; if (view instanceof PlaneCurveParametricView) { double fraction = ((PlaneCurveParametricView)view).fractionToDraw; if (fraction >= 0 && fraction < 1) pointCt = (int)(fraction*pointCt); if (pointCt == 0) pointCt = 1; } view.drawCurve(points,pointCt); } /** * Returns an animation that shows the curve being drawn bit-by-bit. * @param view The View where the creation animation will be shown. If this * is null or if it is not an instance of {@link PlaneCurveParametricView}, then the return * value is null. */ public Animation getCreateAnimation(final View view) { if (view == null || (! (view instanceof PlaneCurveParametricView))) return null; return new TimerAnimation(50,20) { // 50 frames, 20 mulliseconds per frame protected void drawFrame() { ((PlaneCurveParametricView)view).fractionToDraw = frameNumber/50.0; forceRedraw(); } public void animationStarting() { ((PlaneCurveParametricView)view).fractionToDraw = 0; } public void animationEnding() { ((PlaneCurveParametricView)view).fractionToDraw = 1; forceRedraw(); } }; } /** * Returns a new instance of the nested class {@link PlaneCurveParametricView}. */ public View getDefaultView() { View view = new PlaneCurveParametricView(); view.setShowAxes(true); return view; } /** * Returns a list of actions that can be applied to a PlaneCurveParametric. * This adds a separator and four actions that run various animations to * the list obtained from super.getActionsForView(view). * @param view The view for which the actions should apply. If this is null, * then no new actions are added to those inherited from the superclass. */ public ActionList getActionsForView(final View view) { ActionList actions = super.getActionsForView(view); if (view == null) return actions; actions.add(null); double[] window = getDefaultWindow(); final double width = Math.max(Math.abs(window[1] - window[0]), Math.abs(window[3] - window[2])); actions.add( new AbstractActionVMM(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.showParallelCurves")) { public void actionPerformed(ActionEvent evt) { Display display = view.getDisplay(); display.installAnimation(new ParallelCurveAnimation(view, PlaneCurveParametric.this, false, width, width/200)); } }); actions.add( new AbstractActionVMM(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.showParallelCurvesWithNormals")) { public void actionPerformed(ActionEvent evt) { Display display = view.getDisplay(); display.installAnimation(new ParallelCurveAnimation(view, PlaneCurveParametric.this, true, width, width/200)); } }); actions.add( new AbstractActionVMM(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.showOsculatingCircles")) { public void actionPerformed(ActionEvent evt) { Display display = view.getDisplay(); display.installAnimation(new OsculatingCircleAnimation(view, PlaneCurveParametric.this, false, true)); } }); actions.add( new AbstractActionVMM(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.showOsculatingCirclesWithNormals")) { public void actionPerformed(ActionEvent evt) { Display display = view.getDisplay(); display.installAnimation(new OsculatingCircleAnimation(view, PlaneCurveParametric.this)); } }); actions.add( new AbstractActionVMM(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.showTangentsAndNormals")) { public void actionPerformed(ActionEvent evt) { Display display = view.getDisplay(); display.installAnimation( new TimerAnimation(getTResolution(),40) { TangentAndNormalDecoration dec = new TangentAndNormalDecoration(); protected void animationEnding() { view.removeDecoration(dec); } protected void animationStarting() { dec.setCurve(PlaneCurveParametric.this); view.addDecoration(dec); } protected void drawFrame() { dec.setIndex(getFrameNumber()); } }); } }); if (view instanceof PlaneCurveParametricView) { actions.add(null); actions.add(((PlaneCurveParametricView)view).showEvoluteAction); } return actions; } /** * Defines the default View of a PlaneCurveParametric. The only differences * between this and the top-level class is that PlaneCurveParametricView is antialiased by default * and it includes a member variable "fractionToDraw" that tells what fraction * of the curve should actually be drawn in the view; this feature is only * used in the creation animation, which shows a sequence of frames in which the * fractionToDraw is gradually increased. *
NOTE: This muste be public static so that it can be accessed when an object of this * type is created by {@link SaveAndRestore} while reading a file. */ public static class PlaneCurveParametricView extends View { @VMMSave private boolean showEvolute; private ToggleAction showEvoluteAction; private NormalBundleDecoration evolute; // for displaying the evolute; double fractionToDraw = -1; public PlaneCurveParametricView() { setAntialiased(true); showEvoluteAction = new ToggleAction(I18n.tr("vmm.planecurve.parametric.PlaneCurveParameteric.DisplayEvolute")) { public void actionPerformed(ActionEvent evt) { setShowEvolute( getState() ); } }; } public boolean getShowEvolute() { return showEvolute; } public void setShowEvolute(boolean showEvolute) { if (this.showEvolute == showEvolute) return; this.showEvolute = showEvolute; showEvoluteAction.setState(showEvolute); if (showEvolute) { evolute = new NormalBundleDecoration(); evolute.setShowEvolute(true); addDecoration(evolute); } else { removeDecoration(evolute); evolute = null; } forceRedraw(); } NormalBundleDecoration getEvoluteDecoration() { // for use in ParallelCurveAnimation and OsculatingCircleAnimation return evolute; } } }