From b643296fd8e1055aaf6d04df976f049cbec12cfb Mon Sep 17 00:00:00 2001 From: "John M. Penn" Date: Fri, 17 Dec 2021 16:03:43 -0600 Subject: [PATCH] Hot-air balloon simulation. #1210 --- .../SIM_balloon/Modified_data/realtime.py | 10 + trick_sims/SIM_balloon/RUN_test/input.py | 24 + trick_sims/SIM_balloon/S_define | 30 + trick_sims/SIM_balloon/S_overrides.mk | 2 + .../models/atmosphere/include/atmosphere.h | 24 + .../models/atmosphere/src/atmosphere.c | 71 +++ .../models/balloon/include/Balloon.hh | 49 ++ .../models/balloon/src/Balloon.cpp | 134 ++++ .../models/graphics/src/BalloonDisplay.java | 598 ++++++++++++++++++ 9 files changed, 942 insertions(+) create mode 100755 trick_sims/SIM_balloon/Modified_data/realtime.py create mode 100644 trick_sims/SIM_balloon/RUN_test/input.py create mode 100644 trick_sims/SIM_balloon/S_define create mode 100644 trick_sims/SIM_balloon/S_overrides.mk create mode 100644 trick_sims/SIM_balloon/models/atmosphere/include/atmosphere.h create mode 100755 trick_sims/SIM_balloon/models/atmosphere/src/atmosphere.c create mode 100755 trick_sims/SIM_balloon/models/balloon/include/Balloon.hh create mode 100755 trick_sims/SIM_balloon/models/balloon/src/Balloon.cpp create mode 100755 trick_sims/SIM_balloon/models/graphics/src/BalloonDisplay.java diff --git a/trick_sims/SIM_balloon/Modified_data/realtime.py b/trick_sims/SIM_balloon/Modified_data/realtime.py new file mode 100755 index 00000000..acf31873 --- /dev/null +++ b/trick_sims/SIM_balloon/Modified_data/realtime.py @@ -0,0 +1,10 @@ + +trick.real_time_enable() +trick.exec_set_software_frame(0.1) +trick.itimer_enable() + +trick.exec_set_enable_freeze(True) +trick.exec_set_freeze_command(True) + +simControlPanel = trick.SimControlPanel() +trick.add_external_application(simControlPanel) diff --git a/trick_sims/SIM_balloon/RUN_test/input.py b/trick_sims/SIM_balloon/RUN_test/input.py new file mode 100644 index 00000000..87cd8838 --- /dev/null +++ b/trick_sims/SIM_balloon/RUN_test/input.py @@ -0,0 +1,24 @@ +execfile("Modified_data/realtime.py") + +dyn.balloon.pos[0] = 0 +#dyn.balloon.pos[1] = 136.89 +dyn.balloon.vel[0] = 0.0 +dyn.balloon.vel[1] = 0.0 +dyn.balloon.envelope_air_temperature = 92.0 + +# ========================================== +# Start the Satellite Graphics Client +# ========================================== +varServerPort = trick.var_server_get_port(); +BalloonDisplay_path = "models/graphics/dist/BalloonDisplay.jar" + +if (os.path.isfile(BalloonDisplay_path)) : + BalloonDisplay_cmd = "java -jar " \ + + BalloonDisplay_path \ + + " " + str(varServerPort) + " &" ; + print(BalloonDisplay_cmd) + os.system( BalloonDisplay_cmd); +else : + print('==================================================================================') + print('BalloonDisplay needs to be built. Please \"cd\" into ../models/graphics and type \"make\".') + print('==================================================================================') diff --git a/trick_sims/SIM_balloon/S_define b/trick_sims/SIM_balloon/S_define new file mode 100644 index 00000000..f83826b0 --- /dev/null +++ b/trick_sims/SIM_balloon/S_define @@ -0,0 +1,30 @@ +/************************************************************ +PURPOSE: + ( Simulate a hot-air balloon. ) +LIBRARY DEPENDENCIES: + ((balloon/src/Balloon.cpp) + (atmosphere/src/atmosphere.c)) +*************************************************************/ +#include "sim_objects/default_trick_sys.sm" +##include "balloon/include/Balloon.hh" + +class BalloonSimObject : public Trick::SimObject { + public: + Balloon balloon; + + BalloonSimObject() { + ("default_data") balloon.default_data() ; + ("initialization") balloon.state_init() ; + ("derivative") balloon.state_deriv() ; + (0.1, "scheduled") balloon.control() ; + ("integration") trick_ret = balloon.state_integ() ; + ("post_integration") balloon.check_ground_contact() ; + } +}; + +BalloonSimObject dyn; +IntegLoop dyn_integloop(0.1) dyn; + +void create_connections() { + dyn_integloop.getIntegrator(Runge_Kutta_4, 4); +} diff --git a/trick_sims/SIM_balloon/S_overrides.mk b/trick_sims/SIM_balloon/S_overrides.mk new file mode 100644 index 00000000..e1f6cccd --- /dev/null +++ b/trick_sims/SIM_balloon/S_overrides.mk @@ -0,0 +1,2 @@ +TRICK_CFLAGS += -Imodels +TRICK_CXXFLAGS += -Imodels diff --git a/trick_sims/SIM_balloon/models/atmosphere/include/atmosphere.h b/trick_sims/SIM_balloon/models/atmosphere/include/atmosphere.h new file mode 100644 index 00000000..4d0bac64 --- /dev/null +++ b/trick_sims/SIM_balloon/models/atmosphere/include/atmosphere.h @@ -0,0 +1,24 @@ +#ifndef ATMOSPHERE_H +#define ATMOSPHERE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Returns atmospheric density (kg/m^3) at the given altitude (m).*/ +double US_STD_density ( double alt_m ); + +/* Returns acceleration of gravity (m/s^2) at the given altitude (m).*/ +double US_STD_gravity( double alt_m); + +/* Returns atmospheric temperature (℃) at the given altitude (m).*/ +double US_STD_temperature( double alt_m ); + +/* Returns atmospheric pressure (pascals) at the given altitude (m).*/ +double US_STD_pressure( double alt_m); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/trick_sims/SIM_balloon/models/atmosphere/src/atmosphere.c b/trick_sims/SIM_balloon/models/atmosphere/src/atmosphere.c new file mode 100755 index 00000000..fd29b8b6 --- /dev/null +++ b/trick_sims/SIM_balloon/models/atmosphere/src/atmosphere.c @@ -0,0 +1,71 @@ +#include + +static double interpolate( double x, const double xa[], const double fa[], int N_elems ) { + int ii; + for (ii=0 ; ((ii+2 < N_elems ) && (x > xa[ii+1])) ; ii++ ) ; + double x_lower = xa[ii]; + double x_upper = xa[ii+1]; + double f_lower = fa[ii]; + double f_upper = fa[ii+1]; + if (x < x_lower) { + fprintf(stderr, "Interpolation x is out of range low.\n"); + return(f_lower); + } + if (x > x_upper) { + fprintf(stderr, "Interpolation x is out of range high.\n"); + return(f_upper); + } + double f = (x_upper - x)/(x_upper - x_lower) * f_lower + + (x - x_lower)/(x_upper - x_lower) * f_upper ; + return(f); +} + +#define NUM_ELEMENTS 21 +// Units = meters (above sea level). +const double altitude_array[NUM_ELEMENTS] = { + -1000.0, 0.0, 1000.0, 2000.0, 3000.0, 4000.0, 5000.0, 6000.0, + 7000.0, 8000.0, 9000.0, 10000.0, 15000.0, 20000.0, 25000.0, 30000.0, + 40000.0, 50000.0, 60000.0, 70000.0, 80000.0 }; + +// Units = kilograms per cubic meter. +const double US_STD_density_array[NUM_ELEMENTS] = { + 1.347, 1.225, 1.112, 1.007, 0.9093, 0.8194, 0.7364, 0.6601, + 0.5900, 0.5258, 0.4671, 0.4135, 0.1948, 0.08891, 0.04008, 0.01841, + 0.003996, 0.001027, 0.0003097, 0.00008283, 0.00001846 }; + +// Units = meters per second squared. +const double US_STD_gravity_array[NUM_ELEMENTS] = { +9.810, 9.807, 9.804, 9.801, 9.797, 9.794, 9.791, 9.788, +9.785, 9.782, 9.779, 9.776, 9.761, 9.745, 9.730, 9.715, +9.684, 9.654, 9.624, 9.594, 9.564 +}; + +// Units = celsius +const double US_STD_temperature_array[NUM_ELEMENTS] = { + 21.50, 15.00, 8.50, 2.00, -4.49, -10.98, -17.47, -23.96, +-30.45, -36.94, -43.42, -49.90, -56.50, -56.50, -51.60, -46.64, +-22.80, -2.5, -26.13, -53.57, -74.51 +}; + +// Units = pascals +const double US_STD_pressure_array[NUM_ELEMENTS] = { +113900.0, 101325.0, 89880.0, 79500.0, 70120.0, 61660.0, 54050.0, 47220.0, + 41110.0, 35650.0, 30800.0, 26500.0, 12110.0, 5529.0, 2549.0, 1197.0, + 287.00, 79.78, 21.96, 5.20, 1.10 +}; + +double US_STD_density ( double alt_m ) { + return interpolate( alt_m, altitude_array, US_STD_density_array, NUM_ELEMENTS ); +} + +double US_STD_gravity( double alt_m) { + return interpolate( alt_m, altitude_array, US_STD_gravity_array, NUM_ELEMENTS ); +} + +double US_STD_temperature( double alt_m ) { + return interpolate( alt_m, altitude_array, US_STD_temperature_array, NUM_ELEMENTS ); +} + +double US_STD_pressure( double alt_m) { + return interpolate( alt_m, altitude_array, US_STD_pressure_array, NUM_ELEMENTS ); +} diff --git a/trick_sims/SIM_balloon/models/balloon/include/Balloon.hh b/trick_sims/SIM_balloon/models/balloon/include/Balloon.hh new file mode 100755 index 00000000..8c41a11a --- /dev/null +++ b/trick_sims/SIM_balloon/models/balloon/include/Balloon.hh @@ -0,0 +1,49 @@ +/************************************************************************ +PURPOSE: (Simulate a hor-air balloon.) +LIBRARY DEPENDENCIES: + ((balloon/src/Balloon.o)) +**************************************************************************/ +#ifndef BALLOON_HH +#define BALLOON_HH + +class Balloon { + public: + // State Variables (Uncalculated Variables) + double pos[2]; + double vel[2]; + double envelope_mass; + double basket_mass; + double burner_system_mass; + double payload_mass; + double envelope_air_temperature; + double envelope_radius; + double envelope_theta; + double Cd; + + // Calculated Variables + double acc[2]; + double envelope_volume; + double fixed_mass; + + // Control Variable + int temperature_change_command; + + // Methods + int default_data(); + int state_init(); + int state_deriv(); + int state_integ(); + int check_ground_contact(); + int control(); + double calc_fixed_mass(); + double calc_envelope_volume(); + double calc_total_mass(); + double calc_envelope_air_mass(); + double calc_heated_air_density(); + double calc_buoyancy_force(); + double calc_displaced_air_mass(); + double calc_drag_force(); + double volume_of_a_spherical_dome( double r, double h); + double volume_of_a_cone( double r, double h); +}; +#endif diff --git a/trick_sims/SIM_balloon/models/balloon/src/Balloon.cpp b/trick_sims/SIM_balloon/models/balloon/src/Balloon.cpp new file mode 100755 index 00000000..53b7fbde --- /dev/null +++ b/trick_sims/SIM_balloon/models/balloon/src/Balloon.cpp @@ -0,0 +1,134 @@ +/********************************* TRICK HEADER ******************************* +PURPOSE: ( Simulate a hot-air balloon. ) +LIBRARY DEPENDENCY: + ((Balloon.o)) +*******************************************************************************/ +#include "balloon/include/Balloon.hh" +#include "atmosphere/include/atmosphere.h" + +#include "trick/integrator_c_intf.h" +#include +#include +#include + +int Balloon::default_data() { + + pos[0] = 0.0; + pos[1] = 2.0; + vel[0] = 0.0; + vel[1] = 0.0; + + envelope_mass = 113.4; + basket_mass = 63.5; + burner_system_mass = 206.4; + payload_mass = 300.0; + + envelope_air_temperature = 80; + envelope_radius = 8.5; + envelope_theta = 45 * (M_PI/180.0); + + Cd = 0.5; + + return (0); +} + +int Balloon::state_init() { + envelope_volume = calc_envelope_volume(); + fixed_mass = calc_fixed_mass(); + return (0); +} + +int Balloon::state_deriv() { + double total_mass = calc_total_mass(); + double F_gravity = total_mass * (-US_STD_gravity( pos[1])); /* Equation #2 */ + double F_buoyancy = calc_buoyancy_force(); + double F_drag = calc_drag_force(); + acc[0] = 0.0; + acc[1] = (F_gravity + F_buoyancy + F_drag) / total_mass; /* Equation #1 */ + return(0); +} + +int Balloon::state_integ() { + int integration_step; + load_state ( &pos[0], &pos[1], &vel[0], &vel[1], (double*)0); + load_deriv ( &vel[0], &vel[1], &acc[0], &acc[1], (double*)0); + integration_step = integrate(); + unload_state( &pos[0], &pos[1], &vel[0], &vel[1], (double*)0); + return(integration_step); +} + +double Balloon::calc_total_mass() { + return (fixed_mass + calc_envelope_air_mass()); +} + +double Balloon::calc_envelope_air_mass() { + double heated_air_density = calc_heated_air_density(); + return ( heated_air_density * envelope_volume ); /* Equation #4 with density of heated air. */ +} + +double Balloon::calc_envelope_volume() { + double h; + + h = envelope_radius * (1.0 + sin( envelope_theta)); + double v_dome = volume_of_a_spherical_dome(envelope_radius, h); + + double r = envelope_radius * cos(envelope_theta); + h = r / tan(envelope_theta); + double v_cone = volume_of_a_cone(r, h); + + return (v_dome + v_cone); /* Equation #7 */ +} + +double Balloon::volume_of_a_spherical_dome( double r, double h) { + return (M_PI*((r*h*h)-((h*h*h)/3.0))); /* Equation #5 */ +} + +double Balloon::volume_of_a_cone( double r, double h) { + return ((M_PI*r*r*h)/3.0); /* Equation #6 */ +} + +double Balloon::calc_heated_air_density() { + double t_k = envelope_air_temperature + 273.15; + double rho = US_STD_pressure(pos[1]) / (287.055 * t_k); /* Equation #8 */ + return rho; +} + +double Balloon::calc_buoyancy_force() { + return( calc_displaced_air_mass() * US_STD_gravity( pos[1])); /* Equation 9 */ +} + +double Balloon::calc_displaced_air_mass() { + return( US_STD_density(pos[1]) * envelope_volume); /* Equation #4 with standard air density. */ +} + +double Balloon::calc_fixed_mass() { + return (envelope_mass + basket_mass + burner_system_mass + payload_mass); +} + +double Balloon::calc_drag_force() { + + double A = M_PI * envelope_radius * envelope_radius; /* Equation 11 */ + return (- Cd * 0.5 * US_STD_density(pos[1]) * abs(vel[1]) * vel[1] * A); /* Equation 10 */ +} + +int Balloon::control() { + if ( temperature_change_command != 0) { + envelope_air_temperature += temperature_change_command; + } + if (envelope_air_temperature > 120.0) { + envelope_air_temperature = 120.0; + } + if (envelope_air_temperature < 80.0) { + envelope_air_temperature = 80.0; + } + return(0); +} + +int Balloon::check_ground_contact() { + if (pos[1] < 2.0) { + pos[1] = 2.0; + vel[0] = 0.0; + vel[1] = 0.0; + } + return(0); +} diff --git a/trick_sims/SIM_balloon/models/graphics/src/BalloonDisplay.java b/trick_sims/SIM_balloon/models/graphics/src/BalloonDisplay.java new file mode 100755 index 00000000..116c038d --- /dev/null +++ b/trick_sims/SIM_balloon/models/graphics/src/BalloonDisplay.java @@ -0,0 +1,598 @@ +/* + * Trick + * 2021 (c) National Aeronautics and Space Administration (NASA) + */ + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Graphics; +import java.awt.RenderingHints; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.Socket; +import java.util.*; +import javax.swing.BoxLayout; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSlider; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import javax.sound.sampled.*; +import java.net.URL; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.geom.AffineTransform; + +import javax.swing.text.NumberFormatter; +import java.text.NumberFormat; +import javax.swing.JFormattedTextField; +import java.awt.Dimension; + +import javax.swing.BorderFactory; +import javax.swing.border.EtchedBorder; +import java.awt.Component; +/** + * + * @author penn + */ + +class ScenePoly { + public Color color; + public int n; + public double[] x; + public double[] y; +} + +class RangeView extends JPanel { + + private int scale; + private Color skyColor; + private Color groundColor; + private Color envelope_color_1; + private Color envelope_color_2; + private Color basket_color; + private Color envelope_phong; + + // Origin of world coordinates in jpanel coordinates. + private int worldOriginX; + private int worldOriginY; + + private double[] balloonPos; + private double[] balloonVel; + private int envelopeAirTemp; /* degrees C */ + private double envelope_radius; + private double envelope_theta; + + private ScenePoly cone; + private ScenePoly upright; // The frame that connects the basket, burner, and balloon + private ScenePoly basket; + + private int[] workPolyX, workPolyY; + + // Controls + private int deltaTemp ; + + /** + * Class constructor. + */ + public RangeView( int mapScale) { + + setScale(mapScale); + + deltaTemp = 0; + + skyColor = new Color(184,202,231); + groundColor = new Color(100,140, 60); + + balloonPos = new double[] + {0.0, 2.0}; + balloonVel = new double[] + {0.0, 0.0}; + + envelope_color_1 = new Color(254,181, 36); + envelope_color_2 = new Color(251, 0, 21); + basket_color = new Color(220,180,120); + envelope_phong = new Color(220,220,220,80); + + envelope_radius = 8.5; + envelope_theta = Math.toRadians(45.0); + + envelopeAirTemp = 0; + + cone = new ScenePoly(); + cone.color = envelope_color_1; + cone.x = new double[4]; + cone.y = new double[4]; + cone.n = 4; + + upright = new ScenePoly(); + upright.color = Color.BLACK; + upright.x = new double[] {-0.40, 0.40, 0.50, 0.40, 0.30,-0.30,-0.40,-0.50}; + upright.y = new double[] { 0.00, 0.00,-1.00,-1.00,-0.10,-0.10,-1.00,-1.00}; + upright.n = 8; + + basket = new ScenePoly(); + basket.color = basket_color; + basket.x = new double[] {-0.50, 0.50, 0.50,-0.50}; + basket.y = new double[] {-1.00,-1.00,-2.00,-2.00}; + basket.n = 4; + + workPolyX = new int[30]; + workPolyY = new int[30]; + } + + // |jpanel_x| = |origin_x| + |scale 0 | * |cos(angle) -sin(angle)| * |world_x| + // |jpanel_y| |origin_y| | 0 -scale| |sin(angle) cos(angle)| |world_y| + + public void drawScenePoly(Graphics2D g, ScenePoly p, double angle_r , double x, double y) { + for (int ii = 0; ii < p.n; ii++) { + workPolyX[ii] = (int)(worldOriginX + scale * + ( Math.cos(angle_r) * p.x[ii] - Math.sin(angle_r) * p.y[ii] + x)); + workPolyY[ii] = (int)(worldOriginY - scale * + ( Math.sin(angle_r) * p.x[ii] + Math.cos(angle_r) * p.y[ii] + y)); + } + g.setPaint(p.color); + g.fillPolygon(workPolyX, workPolyY, p.n); + } + + public void drawSceneOval(Graphics2D g2d, Color color, double x, double y, double w, double h) { + g2d.setPaint(color); + g2d.fillOval( (int)(worldOriginX+scale*(x-w/2)), (int)(worldOriginY-scale*(y+h/2)), (int)(scale*w), (int)(scale*h)); + } + + public void incTemperature() { deltaTemp = 1; } + public void decTemperature() { deltaTemp = -1; } + public void resetDeltaTemp() { deltaTemp = 0; } + public int getDeltaTemp() { return deltaTemp; } + + public void setballoonPos(double x, double y) { + balloonPos[0] = x; + balloonPos[1] = y; + } + public void setBalloonVel(double vx, double vy) { + balloonVel[0] = vx; + balloonVel[1] = vy; + } + public void setAirTemp(int temperature) { + envelopeAirTemp = temperature; + } + + public void setScale (int mapScale) { + if (mapScale < 2) { + scale = 2; + } else if (mapScale > 128) { + scale = 128; + } else { + scale = mapScale; + } + repaint(); + } + + public int getScale() { + return scale; + } + + private void doDrawing(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + + RenderingHints rh = new RenderingHints( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + rh.put(RenderingHints.KEY_RENDERING, + RenderingHints.VALUE_RENDER_QUALITY); + + int ii, jj; + int width = getWidth(); + int height = getHeight(); + + // |jpanel_x| = |origin_x| + |scale 0 | * |cos(angle) -sin(angle)| * |world_x| + // |jpanel_y| |origin_y| | 0 -scale| |sin(angle) cos(angle)| |world_y| + + worldOriginX = (width/2) - (int)(scale * balloonPos[0]); + worldOriginY = (height/2) + (int)(scale * balloonPos[1]); + + // =============================================================================== + // Draw Sky + // =============================================================================== + g2d.setPaint(skyColor); + g2d.fillRect(0, 0, width, worldOriginY); + + // =============================================================================== + // Draw ground. + // =============================================================================== + g2d.setPaint(groundColor); + g2d.fillRect(0, worldOriginY, width, height); + + // =============================================================================== + // Draw Balloon + // =============================================================================== + + double r_cone_top = envelope_radius * Math.cos(envelope_theta); + double h_cone = r_cone_top / Math.tan(envelope_theta); + double h = envelope_radius * Math.sin(envelope_theta) + h_cone; + double balloon_diameter = 2.0 * envelope_radius; + + // Draw Dome + drawSceneOval(g2d, envelope_color_1, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians( 0.0)), balloon_diameter); + drawSceneOval(g2d, envelope_color_2, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians(22.5)), balloon_diameter); + drawSceneOval(g2d, envelope_color_1, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians(37.5)), balloon_diameter); + drawSceneOval(g2d, envelope_color_2, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians(52.5)), balloon_diameter); + drawSceneOval(g2d, envelope_color_1, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians(67.5)), balloon_diameter); + drawSceneOval(g2d, envelope_color_2, balloonPos[0], balloonPos[1]+h, balloon_diameter*Math.cos(Math.toRadians(82.5)), balloon_diameter); + drawSceneOval(g2d, envelope_phong, balloonPos[0], balloonPos[1]+h+4.0, balloon_diameter*Math.cos(Math.toRadians( 40.0)), balloon_diameter*Math.cos(Math.toRadians( 60.0))); + + // Draw Cone + cone.color = envelope_color_1; + double rt = r_cone_top * Math.cos( Math.toRadians( 0.0)); + cone.x[0] = -rt; + cone.y[0] = h_cone; + cone.x[1] = rt; + cone.y[1] = h_cone; + cone.x[2] = 0.0; + cone.y[2] = 0.0; + cone.x[3] = 0.0; + cone.y[3] = 0.0; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + cone.color = envelope_color_2; + rt = r_cone_top * Math.cos( Math.toRadians(22.5)); + cone.x[0] = -rt; + cone.x[1] = rt; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + cone.color = envelope_color_1; + rt = r_cone_top * Math.cos( Math.toRadians(37.5)); + cone.x[0] = -rt; + cone.x[1] = rt; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + cone.color = envelope_color_2; + rt = r_cone_top * Math.cos( Math.toRadians(52.5)); + cone.x[0] = -rt; + cone.x[1] = rt; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + cone.color = envelope_color_1; + rt = r_cone_top * Math.cos( Math.toRadians(67.5)); + cone.x[0] = -rt; + cone.x[1] = rt; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + cone.color = envelope_color_2; + rt = r_cone_top * Math.cos( Math.toRadians(82.5)); + cone.x[0] = -rt; + cone.x[1] = rt; + drawScenePoly(g2d, cone, 0.0, balloonPos[0], balloonPos[1]); + + // Draw Upright + drawScenePoly(g2d, upright, 0.0, balloonPos[0], balloonPos[1]); + + // Draw Basket + drawScenePoly(g2d, basket, 0.0, balloonPos[0], balloonPos[1]); + // =============================================================================== + // Draw range markers. + // =============================================================================== + int tickRange = 50; + if (scale >= 8) tickRange = 20; + if (scale >= 16) tickRange = 10; + if (scale >= 32) tickRange = 5; + if (scale >= 64) tickRange = 1; + + int lower = ((int)(( - worldOriginX)/(scale * tickRange)) + 1) * tickRange; + int upper = ((int)((width - worldOriginX)/(scale * tickRange)) + 1) * tickRange; + + g2d.setPaint(Color.WHITE); + + for (ii = lower ; ii < upper ; ii += tickRange) { + int mx = (int)(worldOriginX + scale * ii); + g2d.drawLine( mx, worldOriginY, mx, worldOriginY + 20); + g2d.drawString ( String.format("%d",ii), mx, worldOriginY + 15); + } + + // =============================================================================== + // Draw Information + // =============================================================================== + g2d.drawString ( String.format("SCALE: %d pixels/meter",scale), 20,20); + g2d.drawString ( String.format("Envelope Air-Temp (°C) : [%d]", envelopeAirTemp), 20,40); + g2d.drawString ( String.format("Balloon Pos: [%.2f, %.2f]", balloonPos[0], balloonPos[1]), 20,60); + g2d.drawString ( String.format("Balloon Vel: [%.2f, %.2f]", balloonVel[0], balloonVel[1]), 20,80); + + } + + @Override + public void paintComponent(Graphics g) { + super.paintComponent(g); + doDrawing(g); + } +} + +class TrickSimMode { + public static final int INIT = 0; + public static final int FREEZE = 1; + public static final int RUN = 5; +} + +class TemperatureCtrlPanel extends JPanel implements ActionListener { + private RangeView rangeView; + private JButton increaseTempButton, decreaseTempButton; + + public TemperatureCtrlPanel(RangeView view) { + rangeView = view; + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + setBorder( BorderFactory.createEtchedBorder(EtchedBorder.RAISED)); + + increaseTempButton = new JButton("\u25b2"); + increaseTempButton.addActionListener(this); + increaseTempButton.setActionCommand("increaseTemp"); + increaseTempButton.setToolTipText("Increase Temperature"); + + decreaseTempButton = new JButton("\u25bc"); + decreaseTempButton.addActionListener(this); + decreaseTempButton.setActionCommand("decreaseTemp"); + decreaseTempButton.setToolTipText("Decrease Temperature"); + + add(increaseTempButton); + add(decreaseTempButton); + + } + + public void actionPerformed(ActionEvent e) { + String s = e.getActionCommand(); + switch (s) { + case "increaseTemp": + rangeView.incTemperature(); + break; + case "decreaseTemp": + rangeView.decTemperature(); + break; + default: + System.out.println("Unknown Action Command:" + s); + break; + } + } +} + +class ControlPanel extends JPanel implements ActionListener { + + private RangeView rangeView; + private JButton zoomOutButton, zoomInButton; + private JButton shutDownButton; + private TemperatureCtrlPanel temperatureCtrlPanel; + + public ControlPanel(RangeView view) { + + rangeView = view; + setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); + + JPanel labeledTemperatureCtrlPanel = new JPanel(); + labeledTemperatureCtrlPanel.setLayout(new BoxLayout(labeledTemperatureCtrlPanel, BoxLayout.Y_AXIS)); + JLabel temperatureControlLabel = new JLabel("Temperature Control"); + temperatureControlLabel.setAlignmentX(Component.CENTER_ALIGNMENT); + labeledTemperatureCtrlPanel.add(temperatureControlLabel); + temperatureCtrlPanel = new TemperatureCtrlPanel(rangeView); + labeledTemperatureCtrlPanel.add( temperatureCtrlPanel ); + add(labeledTemperatureCtrlPanel); + + zoomOutButton = new JButton("Zoom Out"); + zoomOutButton.addActionListener(this); + zoomOutButton.setActionCommand("zoomout"); + zoomOutButton.setToolTipText("Zoom Out"); + add(zoomOutButton); + + zoomInButton = new JButton("Zoom In"); + zoomInButton.addActionListener(this); + zoomInButton.setActionCommand("zoomin"); + zoomInButton.setToolTipText("Zoom In"); + add(zoomInButton); + + } + + public void actionPerformed(ActionEvent e) { + String s = e.getActionCommand(); + switch (s) { + case "zoomout": + rangeView.setScale( rangeView.getScale() / 2 ); + break; + case "zoomin": + rangeView.setScale( rangeView.getScale() * 2 ); + break; + default: + System.out.println("Unknown Action Command:" + s); + break; + } + } +} // class ControlPanel + +public class BalloonDisplay extends JFrame { + + private RangeView rangeView; + private BufferedReader in; + private DataOutputStream out; + private JPanel panelGroup0; + private JPanel panelGroup1; + private ControlPanel controlPanel; + + public BalloonDisplay(RangeView arena) { + setTitle("Balloon Range"); + + rangeView = arena; + + panelGroup1 = new JPanel(); + panelGroup1.setLayout(new BoxLayout(panelGroup1, BoxLayout.X_AXIS)); + panelGroup1.add(rangeView); + + controlPanel = new ControlPanel(rangeView); + + panelGroup0 = new JPanel(); + panelGroup0.setLayout(new BoxLayout(panelGroup0, BoxLayout.Y_AXIS)); + panelGroup0.add(panelGroup1); + panelGroup0.add(controlPanel); + + add(panelGroup0); + + rangeView.setScale(8); + rangeView.setballoonPos (0.0, 2.0); + + setSize(800, 500); + setLocationRelativeTo(null); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setFocusable(true); + + } + + public void connectToServer(String host, int port ) throws IOException { + Socket socket = new Socket(host, port); + in = new BufferedReader( new InputStreamReader( socket.getInputStream())); + out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); + } + + public void drawRangeView() { + rangeView.repaint(); + } + + private static void printHelpText() { + System.out.println( + "----------------------------------------------------------------------\n" + + "usage: java jar BalloonDisplay.jar \n" + + "----------------------------------------------------------------------\n" + ); + } + + public enum ModelState { INACTIVE, READY, ACTIVE } + + public static void main(String[] args) throws IOException, InterruptedException { + + String host = "localHost"; + int port = 0; + boolean boom = false; + + // ========================================================== + // Handle program arguments. + // ========================================================== + int ii = 0; + while (ii < args.length) { + switch (args[ii]) { + case "-help" : + case "--help" : { + printHelpText(); + System.exit(0); + } break; + default : { + port = (Integer.parseInt(args[ii])); + } break; + } + ++ii; + } + + boolean go = true; + double dt = 0.100; // Time between updates (seconds). + double posx = 0.0; + double posy = 0.0; + double velx = 0.0; + double vely = 0.0; + int airtemp = 0; + + // Outbound command variables + int temperature_change_command; + + int simMode = 0; + boolean standalone = false; + + int mapScale = 32 ; // pixels per meter. + + RangeView rangeView = new RangeView( mapScale); + BalloonDisplay balloonDisplay = new BalloonDisplay( rangeView); + balloonDisplay.setVisible(true); + balloonDisplay.drawRangeView(); + + if (port == 0) { + System.out.println("No variable server port specified."); + printHelpText(); + System.exit(0); + } + + // Connect to the Trick simulation's variable server + System.out.println("Connecting to: " + host + ":" + port); + balloonDisplay.connectToServer(host, port); + + balloonDisplay.out.writeBytes("trick.var_set_client_tag(\"BalloonDisplay\") \n"); + balloonDisplay.out.flush(); + + // Have the Variable Server send us the simulation mode ONCE. + balloonDisplay.out.writeBytes( "trick.var_add(\"trick_sys.sched.mode\")\n" + + "trick.var_send() \n" + + "trick.var_clear() \n"); + balloonDisplay.out.flush(); + + // Read the response and extract the simulation mode. + try { + String line; + String field[]; + line = balloonDisplay.in.readLine(); + field = line.split("\t"); + simMode = Integer.parseInt( field[1]); + } catch (IOException | NullPointerException e ) { + go = false; + } + + // Configure the Variable Server to cyclically send us the following varibales. + // Tell the variable server: + // 1) We want the values of the following variables: + balloonDisplay.out.writeBytes( "trick.var_pause() \n" + + "trick.var_add(\"dyn.balloon.pos[0]\")\n" + + "trick.var_add(\"dyn.balloon.pos[1]\")\n" + + "trick.var_add(\"dyn.balloon.vel[0]\")\n" + + "trick.var_add(\"dyn.balloon.vel[1]\")\n" + + "trick.var_add(\"dyn.balloon.envelope_air_temperature\")\n" + + "trick.var_add(\"trick_sys.sched.mode\")\n" + + // 2) We want the responses in ASCII: + "trick.var_ascii() \n" + + // 3) We want values to be updated at the specified rate: + String.format("trick.var_cycle(%.3f)\n", dt) + + // 4) Start sending values as specified. + "trick.var_unpause() \n" ); + balloonDisplay.out.flush(); + + while (go) { + + // Recieve and parse periodic data response from the variable server. + try { + String line; + String field[]; + line = balloonDisplay.in.readLine(); + field = line.split("\t"); + posx = Double.parseDouble( field[1]); + posy = Double.parseDouble( field[2]); + velx = Double.parseDouble( field[3]); + vely = Double.parseDouble( field[4]); + airtemp = Integer.parseInt( field[5]); + simMode = Integer.parseInt( field[6]); + } catch (IOException | NullPointerException e ) { + go = false; + } + + // Update the display data. + rangeView.setballoonPos(posx, posy); + rangeView.setBalloonVel(velx, vely); + rangeView.setAirTemp(airtemp); + + temperature_change_command = rangeView.getDeltaTemp(); + balloonDisplay.out.writeBytes( String.format("dyn.balloon.temperature_change_command = %d ;\n", temperature_change_command )); + rangeView.resetDeltaTemp(); + + balloonDisplay.out.flush(); + + // Update the scene. + balloonDisplay.drawRangeView(); + + } // while + } // main +} // class