/* 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.planecurve.parametric;
import java.awt.Color;
import java.awt.Cursor;
import org.freehep.graphics2d.VectorGraphics;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.geom.Line2D;
import vmm3d.actions.AbstractActionVMM;
import vmm3d.actions.ActionList;
import vmm3d.core.Complex;
import vmm3d.core.Decoration;
import vmm3d.core.Display;
import vmm3d.core.I18n;
import vmm3d.core.MouseTask;
import vmm3d.core.RealParamAnimateable;
import vmm3d.core.Transform;
import vmm3d.core.View;
/**
* A parabola with a focus on the x-axis. The parabola is defined by its focal length.
*/
public class Parabola extends ConicSection {
private RealParamAnimateable focalLength;
/**
* Construct a parabola with a focus initially at the point (0.75.0). The focal length
* is an animateable parameter with animation start and end values initially set to 0.5 and 1.5.
* The parabola is acutally computed using the parametric equations (t,t*t/(4*f)), where f is the focal
* length and t ranges from -8 to 8.
*
*/
public Parabola() {
focalLength = new RealParamAnimateable("vmm3d.planecurve.parametric.Parabola.FocalLength",0.75,0.5,1.5);
tResolution.setValueAndDefault(192); // should be divisible by (tmin+tmax)
tmin.setValueAndDefault(-8);
tmax.setValueAndDefault(8);
addParameter(focalLength);
setDefaultWindow(-8,8,-8,8);
}
public double xValue(double t) {
return (t * t) / (4 * focalLength.getValue());
}
public double yValue(double t) {
return t;
}
public double xDerivativeValue(double t) {
return t / (2 * focalLength.getValue());
}
public double yDerivativeValue(double t) {
return 1;
}
public double x2ndDerivativeValue(double t) {
return 1 / (2 * focalLength.getValue());
}
public double y2ndDerivativeValue(double t) {
return 0;
}
protected void drawFociAndDirectrix(org.freehep.graphics2d.VectorGraphics g, View view, Transform limits, double t) {
double f = focalLength.getValue();
Color saveColor = g.getColor();
double width = Math.abs(limits.getXmax() - limits.getXmin());
double crossSize = 3*limits.getPixelWidth();
g.setColor(Color.red);
g.draw(new Line2D.Double(f-crossSize, -crossSize, f+crossSize, crossSize));
g.draw(new Line2D.Double(f-crossSize, crossSize, f+crossSize, -crossSize));
g.setColor(Color.blue);
g.draw(new Line2D.Double(-f, limits.getYmin(), -f, limits.getYmax()));
if (Double.isNaN(t)) {
g.setColor(saveColor);
return;
}
double x = t*t / (4*f);
double y = t;
g.setColor(new Color(0,150,0)); // For the tangent line.
double dx = t / (2*f);
double dy = 1;
g.draw(new Line2D.Double(x - width*dx, y - width*dy, x + width*dx, y+width*dy));
g.setColor(Color.red); // for the line from directix thorugh point on curve
if (f > 0)
g.draw(new Line2D.Double(x,y,x+width,y));
else
g.draw(new Line2D.Double(x,y,x-width,y));
g.setColor(Color.magenta); // for the triangle
g.draw(new Line2D.Double(x,y,-f,y));
g.draw(new Line2D.Double(-f,y,f,0));
g.draw(new Line2D.Double(f,0,x,y));
view.drawString("F",f+limits.getPixelWidth(), 4*limits.getPixelHeight());
if (focalLength.getValue() > 0)
view.drawString("S",-f - 10*limits.getPixelWidth(), y);
else
view.drawString("S",-f + 3*limits.getPixelWidth(), y);
g.setColor(saveColor);
}
/**
* Returns a list of actions that are applicable to the exhibit. To the actions
* set up by the superclass, this adds a "Drag Mouse to Show Normals" action that
* lets the user drag the mouse and draws all normal vectors of the parabola that
* pass through the mouse location.
*/
public ActionList getActionsForView(final View view) {
ActionList actions = super.getActionsForView(view);
if (view == null)
return actions;
actions.add(null); // Puts a separator in the menu
actions.add(new AbstractActionVMM(I18n.tr("vmm3d.planecurve.parametric.Parabola.showNormalsWithMouse")) {
public void actionPerformed(ActionEvent evt) {
view.getDisplay().installOneShotMouseTask(new ShowNormalsMouseTask(view));
}
});
return actions;
}
private class ShowNormalsMouseTask extends MouseTask {
View view;
NormalBundleDecoration backgroundDecoration;
NormalsThroughPoint normals = new NormalsThroughPoint();
boolean saveShowAxes;
ShowNormalsMouseTask(View view) {
this.view = view;
}
public void start(Display display,View view) {
backgroundDecoration = new NormalBundleDecoration(Parabola.this);
backgroundDecoration.setLayer(-2);
backgroundDecoration.setNormalsColor(Color.lightGray);
backgroundDecoration.setPointCount(tResolution.getValue()+1);
backgroundDecoration.setEvoluteColor(new Color(0,180,180));
backgroundDecoration.setShowEvolute(true);
view.addDecoration(backgroundDecoration);
saveShowAxes = view.getShowAxes();
view.setShowAxes(true);
}
public void finish(Display display,View view) {
view.removeDecoration(backgroundDecoration);
view.removeDecoration(normals);
view.setShowAxes(saveShowAxes);
}
public boolean doMouseDown(MouseEvent evt, Display display, View view, int width, int height) {
normals = new NormalsThroughPoint();
normals.setLayer(-1);
normals.setPixel(evt.getX(), evt.getY());
view.addDecoration(normals);
return true;
}
public void doMouseDrag(MouseEvent evt, Display display, View view, int width, int height) {
normals.setPixel(evt.getX(), evt.getY());
}
public Cursor getCursor(Display display,View view) {
return Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
}
}
// The shortest of the three normals is too short so that the triple intersection point is not reached
private class NormalsThroughPoint extends Decoration {
int x,y;
Line2D[] lines = new Line2D.Double[3];
double[] params;
Color darkGreen = new Color(0,160,0);
void setPixel(int x, int y) {
if (this.x == x && this.y == y)
return;
this.x = x;
this.y = y;
fireDecorationChangeEvent();
}
Complex[] solveCubic(double p, double q) { // solve reduced cubic x^3 - 3*p*x - q = 0
Complex[] roots = new Complex[3];
double quarterDiscriminant = q*q/4 - p*p*p; // one-quarter the the Discriminant of u^2 - qu + p^3 = 0
Complex halfDiscriminantSqrt = (new Complex(quarterDiscriminant)).integerRoot(2);
Complex quadraticRoot = (new Complex(q/2)).plus(halfDiscriminantSqrt); // r1^3 = (x+v)^3, a root of u^2 - qu + p^3 = 0
Complex xPlusV = quadraticRoot.integerRoot(3);
// the other root r2^3 which is (-v)^3 equals ComplexDifference(RealToComplex(q/2),HalfDiscriminantSqrt)
// but we use instead the fact that r1*r2 = p to get (-v)
Complex minusV = (new Complex(p)).dividedBy(xPlusV); // (-v)
roots[0] = xPlusV.plus(minusV); // x = (x+v) + (-v)
Complex w1 = Complex.polar(1,2*Math.PI/3); // complex 3-rd roots of unity
Complex w2 = Complex.polar(1,4*Math.PI/3);
roots[1] = w1.times(xPlusV).plus(w2.times(minusV));
roots[2] = w2.times(xPlusV).plus(w1.times(minusV));
return roots;
}
public void computeDrawData(View view, boolean needsRedraw, Transform previousLimits, Transform newLimits) {
double xcoord = newLimits.getXmin() + (x - newLimits.getX()) * newLimits.getPixelWidth();
double ycoord = newLimits.getYmax() - (y - newLimits.getY()) * newLimits.getPixelHeight();
double normalLength = 10*Math.abs(newLimits.getXmax() - newLimits.getXmin());
double a = focalLength.getValue();
Complex[] roots = solveCubic((4.0/3.0) * a * xcoord - (8.0/3.0) * a * a, 8 * a * a * ycoord);
for (int i = 0; i < 3; i++) {
if (Math.abs(roots[i].im) > 1e-5) {
lines[i] = null;
}
else {
double x1 = xValue(roots[i].re);
double y1 = yValue(roots[i].re);
double dx = xcoord - x1;
double dy = ycoord - y1;
double length = Math.sqrt(dx*dx + dy*dy);
lines[i] = new Line2D.Double(x1,y1,x1+dx*normalLength/length,y1+dy*normalLength/length);
}
}
if (Math.abs(roots[0].im) > 1e-5 || Math.abs(roots[1].im) > 1e-5 || Math.abs(roots[2].im) > 1e-5)
params = null;
else
params = new double[] { roots[0].re, roots[1].re, roots[2].re };
}
public void doDraw(org.freehep.graphics2d.VectorGraphics g, View view, Transform limits) {
Color saveColor = g.getColor();
if (lines[0] != null) {
g.setColor(Color.red);
g.draw(lines[0]);
}
if (lines[1] != null) {
g.setColor(darkGreen);
g.draw(lines[1]);
}
if (lines[2] != null) {
g.setColor(Color.blue);
g.draw(lines[2]);
}
if (params != null) {
double scale = limits.getPixelWidth();
if (focalLength.getValue() < 0)
scale = -scale;
double gap = limits.getPixelHeight();
g.setColor(Color.red);
g.draw(new Line2D.Double(-scale*90,0,-scale*90,params[0]));
g.setColor(darkGreen);
g.draw(new Line2D.Double(-scale*80,0,-scale*80,params[1]));
g.setColor(Color.blue);
g.draw(new Line2D.Double(-scale*70,0,-scale*70,params[2]));
double p[] = new double[] {Math.abs(params[0]),Math.abs(params[1]),Math.abs(params[2]) };
if (p[0] >= p[1] && p[0] >= p[2]) {
g.setColor(Color.red);
g.draw(new Line2D.Double(-scale*150,0,-scale*150,p[0]));
g.setColor(darkGreen);
g.draw(new Line2D.Double(-scale*145,0,-scale*145,p[1]-gap));
g.setColor(Color.blue);
g.draw(new Line2D.Double(-scale*145,p[1]+gap,-scale*145,p[1]+p[2]));
}
else if (p[1] >= p[0] && p[1] >= p[2]) {
g.setColor(darkGreen);
g.draw(new Line2D.Double(-scale*150,0,-scale*150,p[1]));
g.setColor(Color.red);
g.draw(new Line2D.Double(-scale*145,0,-scale*145,p[0]-gap));
g.setColor(Color.blue);
g.draw(new Line2D.Double(-scale*145,p[0]+gap,-scale*145,p[0]+p[2]));
}
else {
g.setColor(Color.blue);
g.draw(new Line2D.Double(-scale*150,0,-scale*150,p[2]));
g.setColor(darkGreen);
g.draw(new Line2D.Double(-scale*145,0,-scale*145,p[1]-gap));
g.setColor(Color.red);
g.draw(new Line2D.Double(-scale*145,p[1]+gap,-scale*145,p[1]+p[0]));
}
}
g.setColor(saveColor);
}
}
}
Ads help maintain this website.