/** * @(#)Javavelo.java * * climb and glide distance and altitude of flying landyacht * * @author Richard Loftin * @version 1.00 06/08/04 */ import java.awt.*; import java.applet.*; import java.awt.event.*; import java.text.*; public class Javavelo extends Applet implements ActionListener, ItemListener { double YachtWeight; double YachtVelocity; double WindVelocity; double Mph2Fps = 1.4666; double ApparentWindVelocity; double ApparentWindAngle; double ClimbAngle; double CAtemp; double GlideAngle; double GAtemp; double Deg2Rad = 3.1415 / 180; double WingArea; double AspectRatio; double PIxAR; double CLwing; double CLstall; double CLsafe; double CDwing; double CDmin; double WingLift; double WingDrag; double StallSpeed; double LiftDragResultantMagnitude; double LiftDragResultantAngle; double LiftDragResultantMagnitudeCosine; double FuselageDrag; double FrontalArea; double CDfuselage; double VelocityChange; double FlightDistance; double Altitude; double AerodynamicForce; double GravityForce; double GridScale = 1; boolean Demo = true; double LoopCount; double SpeedColor; int PlotColor; Choice GraphScaleChoice; Button StartButton; Button ClearGraphButton; Button ConfigureButton; Button FlightButton; TextField ClimbInput; TextField GlideInput; TextField YachtSpeedInput; TextField WindSpeedInput; TextField CLstallInput; TextField CLsafeInput; TextField WingAreaInput; TextField AspectRatioInput; TextField FrontalAreaInput; TextField BodyCDInput; TextField CDminInput; TextField WeightInput; Panel FlightPanel; Panel YachtPanel; Panel CardPanel; Panel SwitchPanel; Panel ControlPanel; CardLayout CardLayoutSwitch; Color Palette[]; Font PLAIN_font; Graphics g; DecimalFormat nf = (DecimalFormat)NumberFormat.getInstance(); public void init() { PLAIN_font = new Font("", Font.PLAIN , 11); setFont(PLAIN_font); nf.applyPattern("####"); nf.setMaximumFractionDigits(1); CardLayoutSwitch = new CardLayout(); FlightPanel = new Panel(new GridLayout(2, 6)); YachtPanel = new Panel(new GridLayout(2, 6)); CardPanel = new Panel(CardLayoutSwitch); SwitchPanel = new Panel(new GridLayout(2, 3)); ControlPanel = new Panel(); SwitchPanel.add(new Label("Graph Scale")); ClearGraphButton = new Button("Clear"); ClearGraphButton.addActionListener(this); ClearGraphButton.setForeground(Color.black); SwitchPanel.add(ClearGraphButton); ConfigureButton = new Button("Yacht"); ConfigureButton.addActionListener(this); ConfigureButton.setForeground(Color.black); SwitchPanel.add(ConfigureButton); GraphScaleChoice = new Choice(); GraphScaleChoice.setForeground(Color.black); GraphScaleChoice.add("1/6X"); GraphScaleChoice.add("1/5X"); GraphScaleChoice.add("1/4X"); GraphScaleChoice.add("1/3X"); GraphScaleChoice.add("1/2X"); GraphScaleChoice.add("1X"); GraphScaleChoice.add("2X"); GraphScaleChoice.add("3X"); GraphScaleChoice.add("4X"); GraphScaleChoice.add("5X"); GraphScaleChoice.add("6X"); GraphScaleChoice.select(5); GraphScaleChoice.addItemListener(this); SwitchPanel.add(GraphScaleChoice); StartButton = new Button("Start"); StartButton.addActionListener(this); StartButton.setForeground(Color.black); SwitchPanel.add(StartButton); FlightButton = new Button("Flight"); FlightButton.addActionListener(this); FlightButton.setForeground(Color.black); SwitchPanel.add(FlightButton); setBackground(Color.black); setForeground(Color.white); FlightPanel.add(new Label("Climb Angle")); FlightPanel.add(new Label("Glide Angle")); FlightPanel.add(new Label("CL Safe")); FlightPanel.add(new Label("CL Stall")); FlightPanel.add(new Label("Yacht mph")); FlightPanel.add(new Label("Wind mph")); ClimbInput = new TextField("30", 5); ClimbInput.setForeground(Color.black); FlightPanel.add(ClimbInput); GlideInput = new TextField("30", 5); GlideInput.setForeground(Color.black); FlightPanel.add(GlideInput); CLsafeInput = new TextField("1.0", 5); CLsafeInput.setForeground(Color.black); FlightPanel.add(CLsafeInput); CLstallInput = new TextField("1.5", 5); CLstallInput.setForeground(Color.black); FlightPanel.add(CLstallInput); YachtSpeedInput = new TextField("60", 5); YachtSpeedInput.setForeground(Color.black); FlightPanel.add(YachtSpeedInput); WindSpeedInput = new TextField("20", 5); WindSpeedInput.setForeground(Color.black); FlightPanel.add(WindSpeedInput); YachtPanel.add(new Label("Wing Area")); YachtPanel.add(new Label("Aspect Ratio")); YachtPanel.add(new Label("CD Min")); YachtPanel.add(new Label("Frontal Area")); YachtPanel.add(new Label("Fuselage CD")); YachtPanel.add(new Label("Weight lbs")); WingAreaInput = new TextField("60", 5); WingAreaInput.setForeground(Color.black); YachtPanel.add(WingAreaInput); AspectRatioInput = new TextField("6", 5); AspectRatioInput.setForeground(Color.black); YachtPanel.add(AspectRatioInput); CDminInput = new TextField(".01", 5); CDminInput.setForeground(Color.black); YachtPanel.add(CDminInput); FrontalAreaInput = new TextField("10", 5); FrontalAreaInput.setForeground(Color.black); YachtPanel.add(FrontalAreaInput); BodyCDInput = new TextField(".12", 5); BodyCDInput.setForeground(Color.black); YachtPanel.add(BodyCDInput); WeightInput = new TextField("500", 5); WeightInput.setForeground(Color.black); YachtPanel.add(WeightInput); CardPanel.add("flight", FlightPanel); CardPanel.add("yacht", YachtPanel); ControlPanel.add(SwitchPanel); CardLayoutSwitch.show(CardPanel, "flight"); ControlPanel.add(CardPanel); add(ControlPanel); g = getGraphics(); g.setFont(PLAIN_font); getPalette(); setBackground(Color.black); setForeground(Color.white); } public void paint(Graphics g){ DrawGrid(630*GridScale); if(Demo){ for(double i = 10; i < 45; i += 5){ GAtemp = CAtemp = i * Deg2Rad; Flight(); } } else Flight(); } public void itemStateChanged(ItemEvent e){ double fraction = 1; GridScale = GraphScaleChoice.getSelectedIndex() - 4; if(GridScale < 1){ fraction = Math.abs(GridScale) + 2.0; GridScale = 1.0 / fraction; } DrawGrid(630 * GridScale); if(Demo)repaint(); else Flight(); } public void actionPerformed(ActionEvent e){ if(e.getActionCommand() == "Start"){ Demo = false; SpeedScalePlot(); Flight(); } if(e.getActionCommand() == "Clear"){ Demo = false; DrawGrid(630 * GridScale); } if(e.getActionCommand() == "Yacht"){ CardLayoutSwitch.show(CardPanel, "yacht"); } if(e.getActionCommand() == "Flight"){ CardLayoutSwitch.show(CardPanel, "flight"); } } public void SpeedScalePlot(){ g.clearRect(25, 80, 300, 20); g.setColor(Color.white); CLstall = Double.valueOf(CLstallInput.getText()).doubleValue(); WingArea = Double.valueOf(WingAreaInput.getText()).doubleValue(); YachtWeight = Double.valueOf(WeightInput.getText()).doubleValue(); StallSpeed = Math.sqrt(YachtWeight / (CLstall * .5 * .002378 * WingArea)); g.drawString(String.valueOf(Math.round(StallSpeed / 1.4666)), 68, 90); g.drawString("Airspeed mph",157, 90); YachtVelocity = Double.valueOf(YachtSpeedInput.getText()).doubleValue() * Mph2Fps; WindVelocity = Double.valueOf(WindSpeedInput.getText()).doubleValue() * Mph2Fps; g.drawString(String.valueOf(Math.round((YachtVelocity + WindVelocity) / Mph2Fps)),307, 90); g.fillOval(25, 65, 40, 20); g.setColor(Color.red); g.drawOval(25, 65, 40, 20); g.drawString("STALL", 28, 79); for(int i = 0; i < 255; i++){ g.setColor(Palette[i]); g.fillRect((i) + 65, 75, 1, 3); } } // draw flight path grid according to grid scaling factor public void DrawGrid(double size){ g.clearRect(0, 80, 725, 200); SpeedScalePlot(); g.setColor(Color.darkGray); double xx = 0; for(int x = 25; x < 635; x += 30){ //g.setColor(Color.white); g.setColor(Color.darkGray); g.drawLine(x, 100, x, 250); } for(int x = 25; x < 635; x += 60){ g.setColor(Color.white); g.drawString(nf.format(xx), x, 265); xx += size / 10.5; } xx = 0; for(int y = 250; y > 100; y -= 15){ g.setColor(Color.white); g.drawString(nf.format(xx), 665, y + 3); xx += size / 42; g.setColor(Color.darkGray); g.drawLine(25, y, 655, y); } g.setColor(Color.white); g.drawRect(25, 100, 630, 150); g.drawString("Distance feet", 310, 280); g.drawString("Altitude", 650, 85); g.drawString("feet", 660, 95); } // calculate and plot yacht glidepath public void Flight(){ // retrieve input variables if(!Demo)CAtemp = Double.valueOf(ClimbInput.getText()).doubleValue() * Deg2Rad; if(!Demo)GAtemp = Double.valueOf(GlideInput.getText()).doubleValue() * Deg2Rad; YachtVelocity = Double.valueOf(YachtSpeedInput.getText()).doubleValue() * Mph2Fps; WindVelocity = Double.valueOf(WindSpeedInput.getText()).doubleValue() * Mph2Fps; CLstall = Double.valueOf(CLstallInput.getText()).doubleValue(); CLsafe = Double.valueOf(CLsafeInput.getText()).doubleValue(); FrontalArea = Double.valueOf(FrontalAreaInput.getText()).doubleValue(); CDfuselage = Double.valueOf(BodyCDInput.getText()).doubleValue(); CDmin = Double.valueOf(CDminInput.getText()).doubleValue(); WingArea = Double.valueOf(WingAreaInput.getText()).doubleValue(); AspectRatio = Double.valueOf(AspectRatioInput.getText()).doubleValue(); YachtWeight = Double.valueOf(WeightInput.getText()).doubleValue(); StallSpeed = Math.sqrt(YachtWeight / (CLstall * .5 * .002378 * WingArea)); PIxAR = 3.1415 * AspectRatio; LoopCount = 0; SpeedColor = (YachtVelocity + WindVelocity) - StallSpeed; VelocityChange = 0; FlightDistance = 0; Altitude = 0; ClimbAngle = 0; GlideAngle = 0; // calculate apparent wind velocity and apparent wind angle // according to input variable values ApparentWindVelocity = Math.sqrt(Math.pow((Math.cos(ClimbAngle) * YachtVelocity) + WindVelocity, 2) + Math.pow(Math.sin(ClimbAngle) * YachtVelocity, 2)); ApparentWindAngle = Math.atan((Math.sin(ClimbAngle) * YachtVelocity) / (Math.cos(ClimbAngle) * YachtVelocity + WindVelocity)); // break out of loop in case anything goes wrong while(LoopCount < 100){ // Climb // precalculate value to speed up the following loop double calc = .5 * .002378 * Math.pow(ApparentWindVelocity, 2); FuselageDrag = CDfuselage * calc * FrontalArea; // estimate wing CL value CLwing = (Math.cos(ClimbAngle) * YachtWeight) / (calc * WingArea); // find correct wing CL value to maintain specified climb angle do{ // break out of loop in case anything goes wrong if(CLwing < 0 || CLwing > CLstall)break; // increase wing CL until component of lift drag resultant magnitude perpendicular // to climb angle equals component of yacht weight perpendicular to climb angle CLwing += .001; CDwing = (Math.pow(CLwing, 2) / PIxAR) + CDmin; WingLift = CLwing * calc * WingArea; WingDrag = CDwing * calc * WingArea; LiftDragResultantMagnitude = Math.sqrt(Math.pow(WingDrag + FuselageDrag, 2) + Math.pow(WingLift, 2)); LiftDragResultantAngle = Math.atan((WingDrag + FuselageDrag) / WingLift); LiftDragResultantMagnitudeCosine = LiftDragResultantMagnitude * Math.cos(ClimbAngle - (LiftDragResultantAngle + ApparentWindAngle)); } while(Math.cos(ClimbAngle) * YachtWeight >= LiftDragResultantMagnitudeCosine); if(CAtemp > 0){ // smoothly increase climb angle from zero to full value after takoff if(CLwing < CLsafe && ClimbAngle < CAtemp)ClimbAngle += CAtemp *.01; // smoothly decrease climb angle to zero when CL Safe is reached else if(CLwing > CLsafe && ClimbAngle > 0)ClimbAngle -= CAtemp *.01; } // break out of loop at the top of the climb if(ClimbAngle < 0)break; // break out of loop in case any thing goes wrong if(Altitude < 0 || CLwing > CLstall)break; // component of lift drag resultant parallel to climb angle AerodynamicForce = LiftDragResultantMagnitude * Math.sin(ClimbAngle - (LiftDragResultantAngle + ApparentWindAngle)); // component of yacht weight parallel to climb angle GravityForce = Math.sin(ClimbAngle) * YachtWeight; // aerodynamic force minus gravity force gives velocity change VelocityChange = 32.2 * ((AerodynamicForce - GravityForce) / YachtWeight); // find new yacht velocity, apparent wind velocity, // apparent wind angle, flight distance and altitude YachtVelocity += VelocityChange * .01; ApparentWindVelocity = Math.sqrt(Math.pow((Math.cos(ClimbAngle) * YachtVelocity) + WindVelocity, 2) + Math.pow(Math.sin(ClimbAngle) * YachtVelocity, 2)); ApparentWindAngle = Math.atan((Math.sin(ClimbAngle) * YachtVelocity) / (Math.cos(ClimbAngle) * YachtVelocity + WindVelocity)); FlightDistance += (Math.cos(ClimbAngle) * (YachtVelocity + Math.abs(VelocityChange * .005))) * .01; Altitude += (Math.sin(ClimbAngle) * (YachtVelocity + Math.abs(VelocityChange * .005))) * .01; // set graph plot color according to airspeed PlotColor = (int)((ApparentWindVelocity - StallSpeed) * (255.0 / SpeedColor)); if(PlotColor > 255)PlotColor = 255; else if(PlotColor < 0)PlotColor = 0; g.setColor(Palette[PlotColor]); // plot glidepath keep the plot line inside the grid if((FlightDistance / GridScale) + 25 < 654 && 248 - (Altitude / GridScale) > 100) g.drawRect((int)Math.round(FlightDistance / GridScale) + 26, 248 - (int)Math.round(Altitude / GridScale), 1, 1); // loop count gives flight duration LoopCount += .01; } // end Climb double tempAlt = Altitude / 7; // break out of loop in case anything goes wrong while(LoopCount < 100){ // Glide // precalculate values to speed up the following loop double calc = .5 * .002378 * Math.pow(ApparentWindVelocity, 2); FuselageDrag = CDfuselage * calc * FrontalArea; // estimate wing CL value CLwing = (Math.cos(GlideAngle) * YachtWeight) / (calc * WingArea); if(GAtemp > 0) { // smoothly increase glide angle from zero to full value to desend if(Altitude > tempAlt && GlideAngle < GAtemp)GlideAngle += GAtemp *.01; // smoothly decrease glide angle from full value to zero to land else if(Altitude < tempAlt && GAtemp > 3 * Deg2Rad) GlideAngle = GAtemp * ((Altitude + 1) / tempAlt); } // find correct wing CL value to maintain specified glide angle do{ // break out of loop in case anything goes wrong if(CLwing < 0 || CLwing > CLstall)break; // increase wing CL until component of lift drag resultant magnitude perpendicular // to glide angle equals component of yacht weight perpendicular to glide angle CLwing += .001; CDwing = (Math.pow(CLwing, 2) / PIxAR) + CDmin; WingLift = CLwing * calc * WingArea; WingDrag = CDwing * calc * WingArea; LiftDragResultantMagnitude = Math.sqrt(Math.pow(WingDrag + FuselageDrag, 2) + Math.pow(WingLift, 2)); LiftDragResultantAngle = Math.atan((WingDrag + FuselageDrag) / WingLift); LiftDragResultantMagnitudeCosine = LiftDragResultantMagnitude * Math.cos(GlideAngle - (ApparentWindAngle - LiftDragResultantAngle)); } while(Math.cos(GlideAngle) * YachtWeight >= LiftDragResultantMagnitudeCosine); // component of lift drag resultant parallel to glide angle AerodynamicForce = LiftDragResultantMagnitude * Math.sin(GlideAngle - (ApparentWindAngle - LiftDragResultantAngle)); // component of yacht weight parallel to glide angle GravityForce = Math.sin(GlideAngle) * YachtWeight; // gravity force minus aerodynamic force gives velocity change VelocityChange = 32.2 * ((GravityForce - AerodynamicForce) / YachtWeight); // find new yacht velocity, apparent wind velocity, // apparent wind angle, flight distance and altitude YachtVelocity += VelocityChange * .01; ApparentWindVelocity = Math.sqrt(Math.pow((Math.cos(GlideAngle) * YachtVelocity) + WindVelocity, 2) + Math.pow(Math.sin(GlideAngle) * YachtVelocity, 2)); ApparentWindAngle = Math.atan((Math.sin(GlideAngle) * YachtVelocity) / (Math.cos(GlideAngle) * YachtVelocity + WindVelocity)); FlightDistance += (Math.cos(GlideAngle) * (YachtVelocity + Math.abs(VelocityChange * .005))) * .01; Altitude -= (Math.sin(GlideAngle) * (YachtVelocity + Math.abs(VelocityChange * .005))) * .01; // set graph plot color according to airspeed PlotColor = (int)((ApparentWindVelocity - StallSpeed) * (256.0 / SpeedColor)); if(PlotColor > 255)PlotColor = 255; else if(PlotColor < 0)PlotColor = 0; g.setColor(Palette[PlotColor]); // plot glidepath keep the plot line inside the grid if((FlightDistance / GridScale) + 25 < 654 && (248 - (Altitude / GridScale) > 100)){ g.drawRect((int)Math.round(FlightDistance/GridScale) + 26, 248 - (int)Math.round(Altitude / GridScale), 1, 1); // if stall print stall tag if(CLwing > CLstall && Altitude > 0){ g.setColor(Color.white); g.fillOval((int)Math.round(FlightDistance / GridScale) + 6, 228 - (int)Math.round(Altitude / GridScale),40,20); g.setColor(Palette[PlotColor]); g.drawOval((int)Math.round(FlightDistance / GridScale) + 6, 228 - (int)Math.round(Altitude / GridScale),40,20); g.setColor(Palette[PlotColor]); g.drawString("STALL",(int)Math.round(FlightDistance / GridScale) + 10, 242 - (int)Math.round(Altitude / GridScale)); } } // loop count gives flight duration LoopCount += .01; // break out of loop if stall or landing and print flight duration tag if(Altitude < 0 || CLwing > CLstall){ if((FlightDistance / GridScale) + 25 < 654 + 18 && (248 - (Altitude / GridScale) > 100)){ nf.setMinimumFractionDigits(1); g.setColor(Color.black); g.fillRect((int)Math.round(FlightDistance / GridScale) - 18, 232 - (int)Math.round(Altitude/GridScale), nf.format(LoopCount).length() * 7, 13); g.setColor(Color.white); g.drawRect((int)Math.round(FlightDistance / GridScale) - 18, 232 - (int)Math.round(Altitude/GridScale), nf.format(LoopCount).length() * 7, 13); g.setColor(Color.white); g.drawString(nf.format(LoopCount),(int)Math.round(FlightDistance / GridScale) - 14, 243 - (int)Math.round(Altitude/GridScale)); nf.setMinimumFractionDigits(0); } break; } } // end Glide } // end Flight() // create rainbow palette for airspeed color coded graph plot public void getPalette() { double red = 0, green = 0, blue = 0, n1 = 65, n2 = 820, n3 = 600; Palette = new Color[256]; for (int i = 0; i < 256; i++) { if(i <= 128) { red = 255 - (Math.pow(i,2) / n1); green = 255 - (Math.pow(128 - i,2.5) / n2); blue = (Math.pow(128 - i, 2) / n3); } else if(i>128) { red = (Math.pow(128 - (i), 2) / n3); green = 255 - (Math.pow((i - 128), 2.5) / n2); blue = 255 - (Math.pow(128 - (i - 128), 2) / n1); } Palette[i] = new Color((int)red, (int)green, (int)blue); } } }