/*  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 vmm3d.spacecurve.parametric;

import java.awt.Color;

import org.freehep.graphics2d.VectorGraphics;

import java.awt.event.ActionEvent;

import vmm3d.actions.AbstractActionVMM;
import vmm3d.actions.ActionList;
import vmm3d.actions.ToggleAction;
import vmm3d.core.Animation;
import vmm3d.core.Decoration;
import vmm3d.core.Display;
import vmm3d.core.I18n;
import vmm3d.core.IntegerParam;
import vmm3d.core.RealParam;
import vmm3d.core.SaveAndRestore;
import vmm3d.core.TimerAnimation;
import vmm3d.core.Transform;
import vmm3d.core.VMMSave;
import vmm3d.core.View;
import vmm3d.core3D.Grid3D;
import vmm3d.core3D.Transform3D;
import vmm3d.core3D.Vector3D;
import vmm3d.core3D.View3D;
import vmm3d.core3D.View3DLit;
import vmm3d.spacecurve.SpaceCurve;
import vmm3d.spacecurve.SpaceCurveView;

/**
 * A space curve that is defined by parametric equations, giving points of the form (x(t),y(t),z(t)).
 * A parametric curve can be rendered as a "tube" that surrounds the curve; this tube has square cross section,
 * with the orientation of the square at a given point depending on the torsion of the curve at that point.
 * A SpaceCurveParametric renders itself as a tube if it is being drawn in a {@link vmm3d.core3D.View3DLit}.  In a plain
 * {@link vmm3d.core3D.View3D}, it renders itself as a curve.
 */
abstract public class SpaceCurveParametric extends SpaceCurve {
	
	/**
	 * The number of t-values for points on the curve.  Points of the form (x(t),y(t),z(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.  The default value is 200.
	 */
	protected IntegerParam tResolution;
	
	/**
	 * The minimum value of t for points on the curve.  The default value is -5.
	 */
	protected RealParam tmin;
	
	/**
	 * The maximum value of t for points on the curve.  The default value is 5.
	 */
	protected RealParam tmax;
	
	/**
	 * The t-values used to compute the points (x(t),y(t),z(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.

	/**
	 * Represents the surface of the tube, when this exhibit is rendered as a tube.
	 */
	protected Grid3D tube;
	
	/**
	 * The length of one of the square cross sections of the tube, when this exhibit is rendered
	 * as a tube.  The default value is 0.25.
	 */
	protected RealParam tubeSize;
	
	/**
	 * The number of sides around the tube, from 3 to 20.
	 */
	protected IntegerParam tubeSides;


	public SpaceCurveParametric() {
		tmin = new RealParam("vmm.spacecurve.parametric.SpaceCurveParameteric.tmin",-5);
		tmax = new RealParam("vmm.spacecurve.parametric.SpaceCurveParameteric.tmax",5);
		tResolution = new IntegerParam("vmm.spacecurve.parametric.SpaceCurveParameteric.tResolution",200);
		tResolution.setMinimumValueForInput(4);
		tResolution.setMaximumValueForInput(2000);
		tubeSize = new RealParam("vmm.spacecurve.parametric.SpaceCurveParameteric.TubeSize",0.25);
		tubeSize.setMinimumValueForInput(0.01);
		tubeSides = new IntegerParam("vmm.spacecurve.parametric.SpaceCurveParametric.TubeSides",4);
		tubeSides.setMinimumValueForInput(3);
		tubeSides.setMaximumValueForInput(20);
		addParameter(tubeSides);
		addParameter(tubeSize);
		addParameter(tResolution);
		addParameter(tmax);
		addParameter(tmin);
		setFramesForMorphing(25);
		setUseFilmstripForMorphing(true);
	}
	
	/**
	 * Computes the array of points on the curve, using the {@link #value(double)} function and the
	 * values of the tmin, tmax, and tResolution parameters.  Subclasses should not need to override this method.
	 */
	protected void makePoints() {
		int subintervals = tResolution.getValue();
		tVals = new double[subintervals+1];
		points = new Vector3D[subintervals+1];
		double t = tmin.getValue();
		double dt = (tmax.getValue() - t)/subintervals;
		for (int i = 0; i <= subintervals; i++) {
			tVals[i] = t + dt*i;
			points[i] = value(tVals[i]);
			if (Double.isNaN(points[i].x) || Double.isInfinite(points[i].x) || Double.isNaN(points[i].y) || 
					Double.isInfinite(points[i].y) || Double.isNaN(points[i].z) || Double.isInfinite(points[i].z))
				points[i] = null;
		}
	}
	/**
	 * Returns the center of the first numPoints of the array pointSet.
	 */
	protected Vector3D getCenterOfPoints(Vector3D[] pointSet, int numPoints){
		double x=0.0, y=0.0, z=0.0;
		for (int i = 0; i < numPoints; i++) {
			x = x + pointSet[i].x;
			y = y + pointSet[i].y;
			z = z + pointSet[i].z;
		}
		return new Vector3D(x/numPoints, y/numPoints, z/numPoints);
	}
	
	/**
	 * Returns a point (x(t), y(t), z(t)) on the curve, given a value of t.
	 * The return value can be null, indicating a break in the curve.  Subclasses must
	 * define this method to specify a particular curve.
	 */
	abstract protected Vector3D value(double t);
	
	/**
	 * Finds the derivative (x'(t),y'(t),z'(t)) at a given point on the curve.  This class
	 * defines the derivative using an approximation based on nearby points on the curve.  While
	 * this is probably good enough, subclasses can override this if they want, to provide an
	 * exact formula for the derivative.  The return value can be null if the curve or its
	 * derivative is undefined at the specified value of t.
	 */
	protected Vector3D deriv1(double t) {
		double dx = 0.00005;
		Vector3D f1 = value(t + dx);
		Vector3D f2 = value(t + 2*dx);
		Vector3D g1 = value(t - dx);
		Vector3D g2 = value(t - 2*dx);
		f1.x = ((8 * f1.x + g2.x) - (f2.x + 8 * g1.x))/(12 * dx);
		f1.y = ((8 * f1.y + g2.y) - (f2.y + 8 * g1.y))/(12 * dx);
		f1.z = ((8 * f1.z + g2.z) - (f2.z + 8 * g1.z))/(12 * dx);
		if (f1 == null || f2 == null || g1 == null || g2 == null)
			return null;
		return f1;
	}
	
	/**
	 * Finds the second derivative (x''(t),y''(t),z''(t)) at a given point on the curve.  This class
	 * defines the second derivative using an approximation based on nearby points on the curve.  While
	 * this is probably good enough, subclasses can override this if they want, to provide an
	 * exact formula for the derivative.  The return value can be null if the curve or its second
	 * derivative is undefined at the specified value of t.
	 */
	protected Vector3D deriv2(double t) {
		double dx = 0.00005;
		Vector3D f0 = value(t);
		Vector3D f1 = value(t + dx);
		Vector3D f2 = value(t + 2*dx);
		Vector3D g1 = value(t - dx);
		Vector3D g2 = value(t - 2*dx);
		if (f0 == null || f1 == null || f2 == null || g1 == null || g2 == null)
			return null;
		f0.x = ((16 * f1.x  + 16 * g1.x) - 30 * f0.x - f2.x - g2.x) / (12 * dx * dx);
		f0.y = ((16 * f1.y  + 16 * g1.y) - 30 * f0.y - f2.y - g2.y) / (12 * dx * dx);
		f0.z = ((16 * f1.z  + 16 * g1.z) - 30 * f0.z - f2.z - g2.z) / (12 * dx * dx);
		return f0;
	}
	
	/**
	 * Computes spherical coordinates (x,y,z)(theta, phi).
     * The z-axis is the pol-direction, theta = 0 gives (0,0,1), theta \in  [0, pi],   phi \in  [0, 2 pi].
	 */
	public Vector3D geographicCoordinates(double theta, double phi) {
		double x = Math.sin(theta)*Math.cos(phi);
		double y = Math.sin(theta)*Math.sin(phi);
		double z = Math.cos(theta);
		return new Vector3D(x,y,z);
	}
	
	/**
	 * Returns the t-value used to calculate the i-th point (x(t),y(t),z(t)) on the curve.
	 */
	public double getT(int index) {
		return tVals[index];
	}
	
	/**
	 * Returns the t-resolution, the number of subintervals into which the interval is divided.
	 * The size of the array that defines the curve is one plus this value.
	 */
	public int getTResolution() {
		return tResolution.getValue();
	}
	
	/**
	 * Returns an array of four vectors representing the Repere Mobile to the curve at a specified t value.
	 * The array has length 4.  The first vector is the point on the curve, and the other three vectors are
	 * the unit tangent, unit normal, and unit bi-normal to the curve at that point.  The return value can
	 * be null, if the curve or its first or second derivative is not defined at the specified point.
	 * If the return value is non-null, then all four vectors in the returned array are non-null.
	 */
	public Vector3D[] makeRepereMobile(double t) {
		Vector3D point = value(t);
		Vector3D deriv = deriv1(t);
		Vector3D deriv2 = deriv2(t);
		if (point == null || deriv == null || deriv2 == null)
			return null;
		Vector3D B = deriv.cross(deriv2);
		deriv.normalize();
		B.normalize();
		Vector3D N = B.cross(deriv);
		if (Double.isNaN(N.x) || Double.isInfinite(N.y) || Double.isNaN(N.y) || Double.isInfinite(N.y) || Double.isNaN(N.z) || Double.isInfinite(N.z))
			return null;
		return new Vector3D[] { point, deriv, N, B };
	}
	
	/**
	 * Returns a View of type {@link SpaceCurveParametricView} as the default view of this curve.  In this view,
	 * the curve appears as a curve.
	 */
	public View getDefaultView() {
		View view = new SpaceCurveParametricView();
		view.setName("vmm.spacecurve.parametric.SpaceCurveParametric.view.ViewAsCurve");
		return view;
	}
	
	/**
	 * Returns an array containing a single item, which is a View of type {@link View3DLit}.  This is an alternative
	 * view of the curve in which the curve appears as a tube that surrounds the curve itself.
	 */
	public View[] getAlternativeViews() {
		View view = new SpaceCurveParametricViewAsTube();
		view.setAntialiased(true);
		view.setName("vmm.spacecurve.parametric.SpaceCurveParametric.view.ViewAsTube");
		return new View[] { view };
	}
	
	/**
	 * Returns a list of actions that can be applied to this exhibit in the specified view.  The list
	 * depends on whether the view is of type SpaceCurveParametricView or View3DLit.
	 */
	public ActionList getActionsForView(final View view) {
		ActionList commands = super.getActionsForView(view);
		if (view instanceof SpaceCurveParametricView) {
			commands.add(null);
			commands.add( new AbstractActionVMM(I18n.tr("vmm.spacecurve.parametric.SpaceCurveParameteric.showRepereMobile")) {
				public void actionPerformed(ActionEvent evt) {
					Display display = view.getDisplay();
					display.installAnimation( new TimerAnimation(getTResolution(),40) {
						RepereMobileDecoration dec = new RepereMobileDecoration();
						protected void animationEnding() {
							view.removeDecoration(dec);
						}

						protected void animationStarting() {
							dec.setCurve(SpaceCurveParametric.this);
							view.addDecoration(dec);
						}

						protected void drawFrame() {
							dec.setIndex(getFrameNumber());
						}
					});
				}
			});
			commands.add(((SpaceCurveParametricView)view).showOsculatingCirclesAction);
			commands.add(((SpaceCurveParametricView)view).showEvoluteToggle);
		}
		else if (view instanceof SpaceCurveParametricViewAsTube) {
			commands.add(null);
			commands.add(((SpaceCurveParametricViewAsTube)view).showGridToggle);
		}
		return commands;
	}
	
	/**
	 * 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 SpaceCurveParametricView}, then the return
	 * value is null.  The create animation is not used for a tube view of the curve;
	 * a build animation is used instead.  A curce can be shown in a plaine View3D,
	 * but in that case no creation or build animation is used.
	 * @see #getBuildAnimation(View)
	 */
	public Animation getCreateAnimation(final View view) {
		if (view == null || (! (view instanceof SpaceCurveParametricView)))
			return null;
		return new TimerAnimation(75,20) { // 75 frames, 20 mulliseconds per frame
			protected void drawFrame() { 
				((SpaceCurveParametricView)view).fractionToDraw = frameNumber/75.0;
				view.forceRedraw();
			}
			public void animationStarting() {
				((SpaceCurveParametricView)view).fractionToDraw = 0;
			}
			public void animationEnding() {
				((SpaceCurveParametricView)view).fractionToDraw = 1;
				view.forceRedraw();
			}
		};
	}
	
	/**
	 * Returns a build animation of the tube view of the curve, that shows the tube being constructed
	 * from back to front.  If the view is not a View3DLit, then the return value is null.
	 * Note that a build animation is used only for the tube view of the curve; for the
	 * regular view, a create animation is used instead.
	 * @see #getCreateAnimation(View)
	 */
	public Animation getBuildAnimation(View view) {
		if ( ! (view instanceof View3DLit) )
			return null;
		if ( ((View3DLit)view).getRenderingStyle() == View3DLit.WIREFRAME_RENDERING )
			return null;
		final View3DLit view3D = (View3DLit)view;
		return new TimerAnimation(0,20) {
			private double percentDrawn;
			private double batchSize = 1.0/50.0 + 0.0001;
			protected void drawFrame() {
				if (percentDrawn > 1) {
					cancel();
					return;
				}
				if (! view3D.beginDrawToOffscreenImage())
					return;
				view3D.drawSurface(tube,percentDrawn,percentDrawn+batchSize);
				view3D.endDrawToOffscreenImage();
				view3D.getDisplay().repaint();
				percentDrawn += batchSize;
			}
		};
	}

	/**
	 * Computes the data needed to render the exhibit.  For a regular curve view, the computeDrawData method from the superclass
	 * is called.  For a tube view in a View3DLit, the data for the tube surface is computed.
	 */
	protected void computeDrawData3D(View3D view, boolean exhibitNeedsRedraw, Transform3D previousTransform, Transform3D newTransform) {
		super.computeDrawData3D(view,exhibitNeedsRedraw,previousTransform,newTransform);
		if (tube == null || exhibitNeedsRedraw) {
			int uPatches = tResolution.getValue();
			int vPatches = tubeSides.getValue();
			if (tube == null || tube.getUPatchCount() != uPatches || tube.getVPatchCount() != vPatches)
				tube = new Grid3D(uPatches,vPatches,1);
			for (int i = 0; i <= uPatches; i++) {
				Vector3D[] rm = makeRepereMobile(tVals[i]);  // array contains newly constructed vectors, so it's OK to modify them
				if (rm == null) {
					for (int j = 0; j <= vPatches; j++)
						tube.setVertex(i,j,null);
				}
				else {
					Vector3D N = rm[2];
					Vector3D B = rm[3];
					N = N.times(tubeSize.getValue() / 2);
					B = B.times(tubeSize.getValue() / 2);
					for (int j = 0; j <= vPatches; j++) {
						double angle = j * 2*Math.PI / vPatches;
						Vector3D v = rm[0].plus(N.times(Math.cos(angle))).plus(B.times(Math.sin(angle)));
						tube.setVertex(i,j,v);
					}
				}
			}
		}
		boolean showGrid = true;
		if (view instanceof SpaceCurveParametricViewAsTube)
			showGrid = ((SpaceCurveParametricViewAsTube)view).getShowGrid();
		tube.setUCurveIncrement(showGrid? 1 : 0);
		tube.setVCurveIncrement(showGrid? 1 : 0);
	}
	
	/**
	 * Draws either a tube view or a regular curve view of this exhibit, depending on the type of View in
	 * which the curve is being rendered.
	 */
	protected void doDraw3D(org.freehep.graphics2d.VectorGraphics g, View3D view, Transform3D transform) {
		if (view instanceof View3DLit) {
			((View3DLit)view).drawSurface(tube);
		}
		else {
			if (points.length == 0)
				return;
			int pointCt = points.length;
			if (view instanceof SpaceCurveParametricView) {
				double fraction = ((SpaceCurveParametricView)view).fractionToDraw;
				if (fraction >= 0 && fraction < 1)
					pointCt = (int)(fraction*pointCt);
				if (pointCt == 0)
					pointCt = 1;
			}
			boolean useReverseCollar = false;
			if (view instanceof SpaceCurveView)
				useReverseCollar = ((SpaceCurveView)view).getUseReverseCollar();
			view.drawCollaredCurve(points,pointCt,useReverseCollar);
		}
	}

	/**
	 * Defines the default View of a SpaceCurveParametric.  The only differences
	 * between this and a regular View3D is that SpaceCurveParametricView 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 has to be a public static class so that objects of this type * can be created when a saved exbhit is restored in {@link SaveAndRestore}. */ public class SpaceCurveParametricView extends SpaceCurveView { public SpaceCurveParametricView() { setAntialiased(true); } double fractionToDraw = -1; @VMMSave private boolean showEvolute; EvoluteDecoration evolute; ToggleAction showEvoluteToggle = new ToggleAction(I18n.tr("vmm.spacecurve.parametric.SpaceCurveParametric.ShowEvolute")) { public void actionPerformed(ActionEvent evt) { setShowEvolute(getState()); } }; AbstractActionVMM showOsculatingCirclesAction = new AbstractActionVMM(I18n.tr("vmm.spacecurve.parametric.SpaceCurveParametric.ShowOsculatingCircles")) { public void actionPerformed(ActionEvent evt) { getDisplay().installAnimation(new TimerAnimation() { protected void drawFrame() { if (evolute == null) cancel(); else { evolute.circleIndex++; if (evolute.circleIndex >= getPointCount()) cancel(); else forceRedraw(); } } protected void animationEnding() { evolute.showCompleteEvolute = true; forceRedraw(); } protected void animationStarting() { if (!showEvolute) evolute = new EvoluteDecoration(); evolute.showCompleteEvolute = false; evolute.showCompleteEvolute = false; evolute.circleIndex = 0; setShowEvolute(true); forceRedraw(); } }); } }; public boolean getShowEvolute() { return showEvolute; } public void setShowEvolute(boolean showEvolute) { if (this.showEvolute == showEvolute) return; this.showEvolute = showEvolute; showEvoluteToggle.setState(showEvolute); if (showEvolute) { if (evolute == null) // evolute can be set up by the osculatingCircleAnimation evolute = new EvoluteDecoration(); addDecoration(evolute); } else { removeDecoration(evolute); evolute = null; } } } /** * Defines a tube view of a SpaceCurveParametric. Adds to View3DLit a ToggleAction * to determine whether the grid on the curve is shown. */ public static class SpaceCurveParametricViewAsTube extends View3DLit { private ToggleAction showGridToggle = new ToggleAction(I18n.tr("vmm.spacecurve.parametric.SpaceCurveParametric.ShowTubeGrid"),true) { public void actionPerformed(ActionEvent evt) { setShowGrid(getState()); } }; @VMMSave private boolean showGrid = true; public boolean getShowGrid() { return showGrid; } public void setShowGrid(boolean showGrid) { if (showGrid == this.showGrid) return; this.showGrid = showGrid; showGridToggle.setState(showGrid); forceRedraw(); } } private class EvoluteDecoration extends Decoration { private final int POINTS_ON_CIRCLE = 180; private Vector3D[] evolutePoints; private boolean showCompleteEvolute = true; private int circleIndex; private final Color COLOR = Color.BLUE; public void computeDrawData(View view, boolean exhibitNeedsRedraw, Transform previousTransform, Transform newTransform) { if (exhibitNeedsRedraw || decorationNeedsRedraw) { int curvePointCount = getPointCount(); if (curvePointCount == 0) { evolutePoints = null; return; } evolutePoints = new Vector3D[curvePointCount]; int pts; if (showCompleteEvolute) pts = curvePointCount; else { pts = circleIndex; if (pts > curvePointCount) pts = curvePointCount; } for (int i = 0; i < pts; i++) evolutePoints[i] = centerOfOsculatingCircle(i); } } public void doDraw(org.freehep.graphics2d.VectorGraphics g, View view, Transform transform) { if (evolutePoints == null) return; // nothing to draw View3D v3 = (View3D)view; Color saveColor = view.getColor(); view.setColor( v3.getViewStyle() == View3D.RED_GREEN_STEREO_VIEW ? Color.LIGHT_GRAY : COLOR); if (showCompleteEvolute) { view.setStrokeSizeMultiplier(2); v3.drawCurve(evolutePoints); view.setStrokeSizeMultiplier(1); } else { if (circleIndex >= 0 && circleIndex < evolutePoints.length) evolutePoints[circleIndex] = drawOsculatingCircle(v3); if (circleIndex > 0) { view.setStrokeSizeMultiplier(2); v3.drawCurve(evolutePoints,circleIndex+1); view.setStrokeSizeMultiplier(1); } } view.setColor(saveColor); } private Vector3D drawOsculatingCircle(View3D view) { // returns the center of the circle double t = getT(circleIndex); Vector3D pt = value(t); Vector3D deriv = deriv1(t); Vector3D deriv2 = deriv2(t); if (pt == null || deriv == null || deriv2 == null) return null; double kappa = deriv.cross(deriv2).norm() / Math.pow(deriv.norm(),3); double radius = 1/kappa; if (Double.isNaN(radius) || radius > 10000) return null; Vector3D B = deriv.cross(deriv2); deriv.normalize(); B.normalize(); Vector3D N = B.cross(deriv); if (Double.isNaN(N.x) || Double.isInfinite(N.y) || Double.isNaN(N.y) || Double.isInfinite(N.y) || Double.isNaN(N.z) || Double.isInfinite(N.z)) return null; N.normalize(); Vector3D v1 = N.times(radius); Vector3D v2 = deriv.times(radius); Vector3D center = pt.plus(v1); v1.negate(); Vector3D pt1 = center.plus(v1); for (int i = 1; i <= POINTS_ON_CIRCLE; i++) { double angle = (2*Math.PI*i)/POINTS_ON_CIRCLE; Vector3D pt2 = center.plus(v1.times(Math.cos(angle))).plus(v2.times(Math.sin(angle))); view.drawLine(pt1,pt2); pt1 = pt2; } view.drawLine(center,pt); return center; } private Vector3D centerOfOsculatingCircle(int index) { double t = getT(index); Vector3D pt = value(t); Vector3D deriv = deriv1(t); Vector3D deriv2 = deriv2(t); if (pt == null || deriv == null || deriv2 == null) return null; double kappa = deriv.cross(deriv2).norm() / Math.pow(deriv.norm(),3); double radius = 1/kappa; if (Double.isNaN(radius) || radius > 10000) return null; Vector3D B = deriv.cross(deriv2); deriv.normalize(); B.normalize(); Vector3D N = B.cross(deriv); N.normalize(); if (Double.isNaN(N.x) || Double.isInfinite(N.y) || Double.isNaN(N.y) || Double.isInfinite(N.y) || Double.isNaN(N.z) || Double.isInfinite(N.z)) return null; Vector3D center = pt.plus(N.times(radius)); return center; } } }

Ads help maintain this website.