Output grafico: animazioni.

Nella precedente pagina si è mostrato come una classe dotata dell'interfaccia ActionListener può reagire ad un evento generato da un click su un bottone. Ma eventi da rilevare in base ai quali attivare le opportune procedure, specificate in una funzione actionPerformed(ActionEvent e) possono essere generati in altri modi.

A questo scopo, una classe molto importante è la classe Timer capace di generare un evento dopo ogni intervallo di tempo misurato in millisecondi. Rilevando questi eventi, redigendo opportunamente la funzione actionPerformed(ActionEvent e), si può rinnovare l'output ad intervalli prestabiliti.

Questa caratteristica è utile, ad esempio, per creare animazioni grafiche come quella proposta nel seguente esempio. Si tratta della simulazione di un moto balistico, argomento ben noto fin dai primi passi nello studio della meccanica in cui si impara a calcolare matematicamente l'orbita parabolica di un moto balistico ideale, cioè in assenza di attrito.

Nell'esempio si approfondisce l'argomento considerando anche l'eventuale presenza di attrito del mezzo (aria, acqua,...) sul proiettile. L'attrito è rappresentato dal valore di un parametro che, se è maggiore di 0, modifica sostanzialmente l'orbita rispetto al caso ideale.

La sequenza di preparazione dell'applicazione è la seguente.

Questa è la grafica del progetto leggermente ridotta nelle dimensioni.

fig01.gif

La definizione della classe Balistico va completata con la redazione dei suoi metodi specifici non ereditati dalla classe JFrame che servono allo sviluppo dei calcoli matematici necessari al calcolo delle successive posizioni del proiettile.

 


Esempio.

/*
Applicazione Balistico
Simulazione di un moto balistico
*/

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.image.MemoryImageSource;
import javax.swing.border.*;
//=========================================================================
public class Balistico
extends JFrame
implements ActionListener

{
  Container contenitore;
  PannelloGrafica pannello_grafica;
  Pannello pannello_input, pannello_bottoni, pannello_output;
  CampoInput c_velocita, c_angolo, c_attrito, c_dt, c_gittata, c_vertice, c_durata, 
    c_velf;
  JButton button_start, button_stop, button_reset;
  boolean movimento;
  Timer timer;
  final static double g = 9.81;

//---------------------------------------------------------------------- 
  public Balistico()
    {
      setTitle("Moto balistico");
      setLocation(0,0);
      contenitore = getContentPane();
      contenitore.setPreferredSize(new Dimension(800,500));
      contenitore.setLayout(new BoxLayout(contenitore,BoxLayout.Y_AXIS));

      pannello_grafica = new PannelloGrafica();
      add(pannello_grafica);
      

      pannello_input = new Pannello();

      c_velocita = new CampoInput("v. iniz.",80,"80",Color.cyan);
      pannello_input.add(c_velocita);

      c_angolo = new CampoInput("alzo",80,"45°00'00\"",Color.cyan);
      pannello_input.add(c_angolo);

      c_attrito = new CampoInput("attrito",80,"0",Color.cyan);
      pannello_input.add(c_attrito);

      c_dt = new CampoInput("intervallo",80,"0.1",Color.cyan);
      pannello_input.add(c_dt);
      add(pannello_input);

      pannello_bottoni = new Pannello();

      button_start = new JButton(" START ");
      button_start.setActionCommand("cm_start");
      button_start.addActionListener(this);
      pannello_bottoni.add(button_start);

      button_stop = new JButton(" STOP ");
      button_stop.setActionCommand("cm_stop");
      button_stop.addActionListener(this);
      pannello_bottoni.add(button_stop);

      button_reset = new JButton(" RESET ");
      button_reset.setActionCommand("cm_reset");
      button_reset.addActionListener(this);
      pannello_bottoni.add(button_reset);

      add(pannello_bottoni);

      pannello_output = new Pannello();

      c_gittata = new CampoInput("gittata",120,"0",Color.cyan);
      pannello_output.add(c_gittata);

      c_vertice = new CampoInput("vertice",120,"0",Color.cyan);
      pannello_output.add(c_vertice);

      c_durata = new CampoInput("durata",120,"0",Color.cyan);
      pannello_output.add(c_durata);

      c_velf = new CampoInput("v. finale",120,"0",Color.cyan);
      pannello_output.add(c_velf);

      add(pannello_output);
      timer = new Timer(20,this);
      movimento = false;
    }
//---------------------------------------------------------------------- 
  void inizioAnimazione()
    {
      boolean va_bene = (movimento==false);
      if (va_bene)
        {
          double v = c_velocita.getValue();
          double a = sessRad(c_angolo.campo.getText());
          double b = c_attrito.getValue();
          double dt = c_dt.getValue();
          pannello_grafica.inizio(v,a,b,dt);
          c_gittata.reset();
          c_vertice.reset();
          c_durata.reset();
          c_velf.reset();
          movimento = true;
          timer.start();
        }
    }

//---------------------------------------------------------------------- 
  void fineAnimazione()
    {
      movimento = false;
      timer.stop();
    }

//---------------------------------------------------------------------- 
  void resetAnimazione()
    {
      if (movimento)
        {
          timer.stop();
          movimento = false;
        }
      pannello_grafica.reset();
      c_gittata.reset();
      c_vertice.reset();
      c_durata.reset();
      c_velf.reset();
    }
//---------------------------------------------------------------------- 
  public void actionPerformed(ActionEvent e)
    {
      String comando = e.getActionCommand();
      int caso = 0;
      if (comando=="cm_start") caso = 1;
      if (comando=="cm_stop") caso = 2;
      if (comando=="cm_reset") caso = 3;

      switch(caso)
        {      
          case 1:
            inizioAnimazione();
            break;
          case 2:
            fineAnimazione();
            break;
          case 3:
            resetAnimazione();
            break;
          default:
            if (movimento)
              {
                pannello_grafica.repaint();
              }
        }
    }
//----------------------------------------------------------------------  
  int valoreSess(String ss, int max )
    {
      int risultato = 0;
      try
        {
          risultato = Integer.parseInt(ss);
        }
      catch(NumberFormatException e)
        {
        }
      if (risultato>max) risultato = max;
      if (risultato<0) risultato = 0;
      return risultato;
    }

//----------------------------------------------------------------------  
  double sessRad(String sess)
    {
      int gradi=0, primi=0, secondi=0;
      int pos_g = sess.indexOf('°');
      int pos_p = sess.indexOf('\'');
      int pos_s = sess.indexOf('\"');
      if ((pos_g==0)&&(pos_p==0)&&(pos_s==0)) return 0;
      int caso = 0;
      if (pos_s>0) caso += 1;
      if (pos_p>0) caso += 2;
      if (pos_g>0) caso += 4;
      String[] pezzi = sess.split("\\W");
      switch(caso)
        {
          case 1:
            secondi = valoreSess(pezzi[0],59);
            break;
          case 2:
            primi = valoreSess(pezzi[0],59);
            break;
          case 3:
            primi = valoreSess(pezzi[0],59);
            secondi = valoreSess(pezzi[1],59);
            break;
          case 4:
            gradi = valoreSess(pezzi[0],90);
            break;
          case 5:
            gradi = valoreSess(pezzi[0],90);
            secondi = valoreSess(pezzi[1],59);
            break;
          case 6:
            gradi = valoreSess(pezzi[0],90);
            primi = valoreSess(pezzi[1],59);
            break;
          case 7:
            gradi = valoreSess(pezzi[0],90);
            primi = valoreSess(pezzi[1],59);
            secondi = valoreSess(pezzi[2],59);
        }
      return Math.toRadians(gradi + primi/60. + secondi/3600.);
    }
//----------------------------------------------------------------------  
  double hypot(double x, double y)
    {
      return Math.sqrt(x*x+y*y);
    }
//----------------------------------------------------------------------  
  double arrotonda(double x)
    {
      x *= 100;
      x = Math.round(x);
      x /= 100;
      return x;
    }

//----------------------------------------------------------------------
  public static void main(String args[])
    {
      Balistico b = new Balistico();
      b.pack();
      b.setVisible(true);
    }


//=========================================================================
class PannelloGrafica
extends JPanel

{
  LineBorder bordo;
  Palla palla;
  int lar;
  int alt;

//---------------------------------------------------------------------- 
  PannelloGrafica()
    {
      setBackground(Color.white);
      bordo = new LineBorder(Color.blue,2);
      setBorder(bordo);
      setPreferredSize(new Dimension(800,400));
      palla =  new Palla(this);
    }
//---------------------------------------------------------------------- 
  public void inizio(double v, double a, double b,double dt)
    {
      palla.inizio(v,a,b,dt);
    }
//---------------------------------------------------------------------- 
  public void reset()
    {
      palla.inizio(0,0,0,0);
    }

//----------------------------------------------------------------------
  public void paint(Graphics g)
    {
      super.paintComponent(g);
      alt = getHeight();
      lar = getWidth();
      bordo.paintBorder(this,g,0,0,lar,alt);
      if (palla.spostamento())
        {
          g.setXORMode(Color.white);
          palla.img.paintIcon(this,g,palla.xg,palla.yg);
        } 
      else
        {
          fineAnimazione();
          c_gittata.setValue(arrotonda(palla.x));
          c_vertice.setValue(arrotonda(palla.vert));
          c_durata.setValue(arrotonda(palla.t));
          c_velf.setValue(arrotonda(palla.v));
        }
    }
}

//=========================================================================
class Pannello
extends JPanel
{
  Pannello()
    {
      setBorder(BorderFactory.createRaisedBevelBorder());
      setPreferredSize(new Dimension(800,40));
      setLayout(new BoxLayout(this,BoxLayout.X_AXIS));
    }
}

//=========================================================================
class Palla
{
  double x,y,v,vx,vy,ax,ay,b,t,dt,vert;
  int xg, yg, dim_img;
  ImageIcon img;
  PannelloGrafica pannello;

//----------------------------------------------------------------------  
  public Palla(PannelloGrafica pn)
    {
      dim_img = 16;
      pannello = pn;
      img = creaFigura(pannello);
      inizio(0,0,0,0);
    }

//----------------------------------------------------------------------
  void inizio(double v_iniz, double a, double f, double intervallo)
    {
      dt = intervallo;
      t = 0;
      x = 0;
      b = f;
      y = 0;
      v = v_iniz;
      vx = v*Math.cos(a);
      vy = v*Math.sin(a);
      ax = 0;
      ay = -g;
      posizioneGrafica();
      pannello.repaint();
      vert = 0;
    }

//----------------------------------------------------------------------
  ImageIcon creaFigura(JPanel pannello)
    {
      int bianco = (255<<24)|(255<<16)|(255<<8)|255;
      int rosso = (255<<24)|(255<<16);
      int d = dim_img;
      double dm = d/2;
      int px[] = new int[d*d];
      int ix = 0;
      for (int i = 0; i < d; i++)
        for (int j = 0; j < d; j++)       
          {
            if (hypot(i-dm,j-dm) < dm)
              px[ix] = rosso;
            else
              px[ix] = bianco;
            ix++;
          }
      Image img = pannello.createImage(new MemoryImageSource(d,d,px,0,d));
      return new ImageIcon(img);
    }

//----------------------------------------------------------------------
  public boolean spostamento()
    { 
      if (y<0) return false;

      ax = -b*vx;
      ay = -(g+b*vy);
      vx += ax*dt;
      vy += ay*dt;
      v = hypot(vx,vy);
      x += vx*dt;
      y += vy*dt;
      t += dt;
      vert = Math.max(y,vert);
      posizioneGrafica();  
      return true;
    }

//----------------------------------------------------------------------
  public void posizioneGrafica()
    {
      int dm = dim_img/2;
      xg = (int)(Math.round(x)-dm);
      yg = pannello.alt-(int)(Math.round(y)+dm);
    }
//----------------------------------------------------------------------
  
}

/**************************************************************************************/
class CampoInput
extends JPanel
{
  TitledBorder titolo;
  JTextField campo;

//---------------------------------------------------------------
//Costruttore
//---------------------------------------------------------------
  CampoInput(String t, int w, String valore, Color colore)
    {
      setPreferredSize(new Dimension(w+16,32));
      setBackground(colore);
      setBorder(BorderFactory.createRaisedBevelBorder());
      titolo = BorderFactory.createTitledBorder(BorderFactory.createLineBorder(Color.black),t); 
      titolo.setTitleFont(new Font("SansSerif",Font.PLAIN,10));
      setBorder(titolo);
      setLayout(new FlowLayout(FlowLayout.CENTER,0,0));
      campo = new JTextField(valore);
      campo.setFont(new Font("SansSerif",Font.PLAIN,12));
      campo.setPreferredSize(new Dimension(w,16));
      add(campo);
    }
//---------------------------------------------------------------
  double getValue()
    {
      return Double.parseDouble(campo.getText());
    }
//---------------------------------------------------------------
  void setValue(double v)
    {
      campo.setText(String.valueOf(v));
    }
//---------------------------------------------------------------
  void reset()
    {
      campo.setText("");
    }
}

/**************************************************************************************/
}

Il sorgente può essere scaricato da Balistico.zip e scompattato in una cartella del proprio sistema.