MokaByte Numero 05 - Febbraio 1997

 
Grafica vettoriale con Java 
parte II
 
di
Domenico De Riso
soluzioni temporanee

 



Dopo l'introduzione alla grafica vettoriale dello scorso numero finiscono le chiacchiere e si passa alla pratica con la creazione di nuovi oggetti grafici utilissimi, per il disegno sulle applet, a coloro che NON vogliono rivedere od imparare formule matematiche e di geometria analitica, ma vogliono semplicemente vivere tranquilli e, nello stesso tempo, ricavare da Java le massime potenzialità di grafica vettoriale.
Dopo uno sguardo all'overview di Javasoft ho constatato che ci stanno copiando! Ma non credo siano pronti prima della fine del 1997 (almeno così hanno "labellato" gli argomenti Java2D e Java3D).


Nell'articolo precedente è stata sottolineata l'importanza di "aggiustare" alcuni methods della classe awt.Graphics, come drawOval(...) e drawArc(...), per il fatto che essi trattano le circonferenze circolari ed ellittiche in un modo un pò strano.
Dicemmo che disegnare una circonferenza con il drawOval(...) attuale bisogna prepararsi a dei "calcoli mentali" e, visto che noi siamo i programmatori e non i calcolatori o, per dirla in modo fantascientifico, noi siamo i "creativi" e non i "programmi", il drawOval(...) deve essere bandito.
Vediamo perchè:
drawOval(int x, int y, int width, int height) non e’ altro che il bounding box o rettangolo di contenimento di una ciconferenza o di un’ellisse.
Nella vita quotidiana, quando un programmatore o un disegnatore ha una circonferenza da disegnare, nel 99% dei casi i dati sono costituiti dalle coordinate del punto centro e la lunghezza del raggio. Ma nella awt.Graphics NON è implementato qualcoosa di simile a un drawCircle(int xc, int yc, int r).
Sempre nell’articolo precedente fu fatto un esempio pratico per disegnare due circonferenze concentriche delle quali si conosceva il centro comune (125,103) e i raggi 28 e 15.

Guardate cosa bisognerebbe scrivere: drawOval(97, 75, 56, 56) e drawOval(110, 88, 30, 30). Praticamente nessuno dei dati iniziali può essere passato come parametro, ma solo dopo un calcolo "cervellotico"...
Inoltre non capisco perché tutti i parametri dei methods grafici sono di tipo int. Il mio parere è che debbano essere assolutamente di tipo double o almeno float. Spesso capita di dover disegnare grafici con coordinate frazionali e comprese tra i numeri intorno allo zero.
Invece un disCirconf(double xc, double yc, double r) sarebbe semplice e versatile. Per l’esempio precedente bisognerebbe solo digitare disCirconf(125, 103, 28) e disCirconf(125, 103, 15) che sono tutti i dati iniziali.
Anzi, visto che la nostra disCirconf(...) non deve essere da meno della drawOval(...) attuale che disegna anche ellissi, è meglio prevedere un methods private disCircolo(...) e ulteriori methods public che fanno riferimento al primo a seconda dei casi in cui si debba disegnare poligoni regolari o ellittici, circonferenze od ellissi.

Prima creiamo una classe del tipo newGraphics in un file del tipo newGraphics.java, che conterrà methods sia nuovi che alternativi alla awt.Graphics.
Non voglio dilungarmi sul fatto che conviene o meno che la newGraphics sia una extends della awt.Graphics. A questo ci pensate voi dipendentemente dalle circostanze. A me interessa solo che sia più facile disegnare per cui la newGraphics sarà contenuta in un file newGraphics.java ed è questa:

 

import java.awt.*;
 
 

public class newGraphics {

  Graphics g;

  double xDal=0, yDal=0, xAl, yAl;
 
 

  newGraphics(Graphics g) {

    this.g=g;

  }
 

La variabile g è quella corrispondente che viene passata dalla Paint. Le variabili xDal, yDal, xAl, yAl sono i valori fisici che le coordinate dei punti assumono per essere fisicamente disegnati. La nostra disCircolo(...) sarà un method private della nuova classe newGraphics:
 

  private void disCircolo(double xc, double yc, double ra, double rb, int nl, double rotz) {

    for (double an=rotz; an<=2*Math.PI+rotz+.0001; an+=2*Math.PI/nl) {

      xAl = ra*Math.cos(an)+ xc;

      yAl = rb*Math.sin(an)+ yc;

      if (an>rotz) g.drawLine((int) xDal, (int) yDal, (int) xAl, (int) yAl);

      xDal=xAl;

      yDal=yAl;

    }

  }
 

disCircolo(...) disegna un'ellisse con centro in xc e yc e semiassi ra ed rb. Se si tratta di un Poligono allora sarà di nl lati e se è un poligono regolare potrà essere ruotato di rotz gradi.
Naturalmente disCircolo(...) non può essere direttamente utilizzata dall'esterno perchè private, ma i methods public successivi oltre a fornire forme diverse di accesso alla disCircolo(...), ostacolano la possibilità di chiamate errate alla disCircolo(...) stessa, come ad esempio il disegno di una circonferenza ruotata (che non ha senso). Infatti il method per il disegno delle circonferenze disCIrconf(...) non da la possibilità di rotazione... perchè disegna automaticamente un poligono di 128 lati (per lo schermo praticamente una circonferenza) e passa 0 come rotazione...
disCircolo(...) è costituita da un ciclo di for che incrementa un angolo an da rotz a rotz+2pigreco radianti con un passo che dipende dal numero di lati nl della circonferenza-poligono. Quindi vengono calcolati il seno e il coseno della'angolo an e rispettivamente moltiplicati per il primo e il secondo semiasse ra ed rb. L'insieme delle coppie x y che si ottengono è l'insieme dei punti di un'ellisse. Quando ra è uguale ad rb si ha il caso particolare di una circonferenza che è appunto una "particolare ellisse".
Questi sono i methods che "approfittano" di disCIrcolo(...):
 

// poligono ellittico non ruotato

  public void disPoligonoE(double xc, double yc, double ra, double rb, int nl) {

    disCircolo(xc, yc, ra, rb, nl, 0);

  }

// poligono regolare non ruotato

  public void disPoligonoR(double xc, double yc, double r, int nl) {

    disCircolo(xc, yc, r, r, nl, 0);

  }

// poligono regolare ruotato

  public void disPoligonoR(double xc, double yc, double r, int nl, double rotz) {

    disCircolo(xc, yc, r, r, nl, rotz);

  }

// ellisse

  public void disEllisse(double xc, double yc, double ra, double rb) {

     disCircolo(xc, yc, ra, rb, 128, 0);

   }

// circonferenza con noti centro e raggio

  public void disCirconf(double xc, double yc, double r) {

    disCircolo(xc, yc, r, r, 128, 0);

  }

// circonferenza passante per 3 punti noti

  public void disCirconf(double xa, double ya, double xb, double yb, double xc, double yc) {

    double m1 = -(xb - xa) / (yb - ya);

    double m2 = -(xb - xc) / (yb - yc);

    double n1 = ((xb*xb - xa*xa) + (yb*yb - ya*ya)) / 2 / (yb - ya);

    double n2 = ((xb*xb - xc*xc) + (yb*yb - yc*yc)) / 2 / (yb - yc);
 
 

    if (m1 != m2) {

      double x = (n2 - n1) / (m1 - m2);

      double y = m2 * x + n2;

      double r = Math.sqrt((x - xb)*(x - xb) + (y - yb)*(y - yb));

      disCircolo(x, y, r, r, 128, 0);

    }

    else System.out.println("i punti sono allinati");

    }

  }
 

Ulteriori chiarimenti di geometria analitica sui methods precedenti possono essere richiesti direttamente all'autore in forma privata perchè esulano dall'argomento corrente.

Sappiate, però, che questi methods verranno modificati o abbandonati perché utili solo alla comprensione di come dovrebbe essere meglio strutturata la parte grafica di un linguaggio. Successivamente le figure geometriche disegnate saranno considerate oggetti dell’OOP dai quali trarre informazioni prima o anche dopo la loro apparizione sullo schermo. Perchè?
Tutti i linguaggi di programmazione hanno sempre trattato le figure geometriche disegnabili come eventi che, una volta procurati modifiche nella RAM video, restano lì finchè un ClearScreen o un Cls li cancella, mentre sarebbe opportuno che, una volta creata una figura geometrica, essa venisse considerata come un oggetto. Questo accade nelle applicazioni CAD, ma non nei linguaggi di programmazione a livello di codice.
Un esempio semplice: bisogna disegnare una linea che unisce i centri di due circonferenze C1 e C2 di tipo Circonferenza. Una volta creati gli oggetti C1 e C2, per disegnare le circonferenza si userà un methods della classe Circonferenza come disegna(). Dunque C1.disegna() e C2.disegna() disegnerà le due circonferenze.
Per disegnare la linea di unione dei centri, invece, esisterà un method di una classe Linea che creerà una linea tra due oggetti di tipo Punto(double x, double y) che avrà la forma Linea(Punto p1, Punto p2) e il suo relativo method disegna(). Allora la linea L tra i centri sarà new Linea(C1.centro, C2.centro) e per tracciare tale linea L basta L.disegna().
Ma questo verrà spiegato nel prossimo articolo.
Per ora accontentiamoci delle semplici soluzioni temporanee precedenti già abbastanza efficienti.

Non dimenticate di chiudere la newGraphics con:

}
Allora per disegnare qualcosa create la vostra applet personale così:

import java.awt.*;
 
 

public class miApplet extends java.applet.Applet {
 
 

  public void paint(Graphics g) {

    newGraphics ng = new newGraphics(g);
 
 

// disegna due circonferenze concentriche di raggio 28 e 15 e un ellisse

    g.setColor(Color.blue);

    ng.disCirconf(125, 103, 28);

    ng.disCirconf(125, 103, 15);

    ng.disEllisse(125, 103, 60, 25);
 
 

// disegna una circonferenza per tre punti

    ng.disCirconf(100, 150, 70, 120, 80, 190);
 
 

// disegna due dodecagoni ellittici e un ottagono regolare

    g.setColor(Color.red);

    ng.disPoligonoE(125, 103, 78, 35, 12);

    ng.disPoligonoE(125, 103, 35, 78, 12);

    ng.disPoligonoR(125, 103, 78, 8);
 
 

//  disegna un esagono ruotato di PI/16 inscritto in una circonferenza

    g.setColor(Color.black);

    ng.disPoligonoR(125, 103, 100, 6, Math.PI/16);

    ng.disCirconf(125, 103, 100);

  }
 
 

}
 

Dal prossimo numero, invece di limitarci a disegnare figure geometriche, cominceremo prima a creare gli oggetti con tale forma di figure geometriche e poi penseremo alla loro "meno importante e semplice" rappresentazione grafica. Infatti è più interessante avere un insieme di oggetti non disegnati, ma interrogabili riguardo le loro caratteristiche, che non una loro immagine statica dalla quale non si può trarre alcuna informazione. Questo sarà un vantaggio enorme perchè ci permetterà di disegnare grafici geometrici con perfezione matematica-geometrica senza utilizzare formule.
Uno dei primi esempi sul prissimo numero sarà quello della circonferenza per tre punti senza formule e numeri. Praticamente il sostituto del method presentato prima, ma senza nessuna formula!
Domenico De Riso
 
 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it