MokaByte Numero 06 - Marzo 1997

 
Grafica vettoriale con Java 
(III parte)
"parlare" con Java...
 
di
Domenico De Riso
 

 

È ora di prepararsi alla nascita di importanti classi di primitive geometriche che ci permetteranno di fare cose straordinarie! Cominciamo dalla primitiva più "primitiva": Il punto.
Sembra che sul punto non ci sia quasi niente da dire, (ma è solo un puntino!) e invece no! Il punto è uno dei fondamenti della Geometria sulla quale si basano tutte le discipline tecniche e scientifiche.

Il punto, in un piano cartesiano, sembra il solito oggetto caratterizzato da due numeri che si chiamano coordinate, ma è anche il risultato di intersezione di due rette qualsiasi. Anzi, le ben note coordinate di un punto sono esse stesse delle rette. Quando si dice che un punto P ha le coordinate x=2 e y=3, tali formule sono equazioni di primo grado, e le equazioni di primo grado, sul piano cartesiano SONO LINEE RETTE. Dunque il punto P è individuato tramite l’intersezione della retta x=2 parallela all’asse y con la retta y=3 parallela all’asse x.
(Per i soli esperti: chi crede che y=3 non abbia l’aspetto di una equazione di retta come y=mx+n la immagini come y=0x+3 che è appunto equivalente a y=3)
Prima di addentrarci nell’OOP e dialogare con esso bisogna fornire la base di conoscenza geometrica almeno riguardo ai punti e alle linee. Il resto viene quasi da solo. Perciò potete sorvolare sulle formule utillizzate che non sono importanti allo scopo di quanto segue, ma solo per fornire la base di conoscenza iniziale.
Tutto il codice presentato in colore nero è contenuto nei files newGraphics.java, che contiene anche il codice dell'articolo dello scorso numero, e vectors3.java contenente il codice necessario alle applet visibili in questa pagina.

Cominciamo a costruire una classe Punto che non sia la solita classe di esempio utilizzata per scopi didattici, ma una classe geometrica completa:

class Punto {

  double x, y;



  Punto() {

  }

Questo è il punto individuato da una coppia di numeri reali dette coordinate:
  Punto(double x, double y) {

    this.x=x;

    this.y=y;

  }

Questo è il punto individuato dall’intersezione di due rette qualsiasi:
  Punto(Linea L1, Linea L2) {

    if (L1.m != L2.m) {

      if (!Double.isInfinite(L1.m) && !Double.isInfinite(L2.m)) {

        x=(L2.n - L1.n) / (L1.m - L2.m);

        y=L2.m * x + L2.n;

      }

      else {

        if (Double.isInfinite(L1.m)) {

          x=L1.P1.x;

          y=L2.m*x + L2.n;

        }

        if (Double.isInfinite(L2.m)) {

          x=L2.P1.x;

          y=L1.m*x + L1.n;

        }

                  }

      }

      else System.out.println("le linee sono parallele");

  }



Questo è il punto individuato dalle rette che hanno come coefficienti angolari m1 ed m2 e come intercette n1 ed n2.
Di tali caratteristiche delle rette ne parleremo tra poco.
  Punto(double m1, double n1, double m2, double n2) {

    Punto p = new Punto(new Linea(m1, n1), new Linea (m2, n2));

    x = p.x;

    y = p.y;

  }

Questo è il punto posto alla distanza ro ed angolo teta relativamente al punto P. Insomma il punto individuato con cordinate polari relative rispetto al punto P.
  Punto(Punto P, double ro, double teta) {

    x=P.x + ro*Math.cos(teta);

    y=P.y + ro*Math.sin(teta);

  }

}

Come al solito non darò spiegazioni sulle formule utilizzate perché esulano dall’argomento, ma sono disponibile tramite e-mail private per qualsiasi chiarimento.

Quando "alcuni" infiniti punti si mettono in riga formano una linea. Una linea è l’insieme di punti tra due punti P1 e P2 detti estremi della linea stessa.
Dunque una linea può essere individuata tramite i suoi due punti estremi. Ma una coppia di punti può avere origine anche dall’intersezione tra una seconda linea ed una circonferenza o tra due circonferenze.

Ecco la class Linea:

class Linea {

  Punto P1, P2;

  double m, n;

Le variabili m ed n sono elementi caratteristici della linea perché m è il coefficiente angolare della retta passante per P1 e P2 ed n è l’intercetta della stessa retta (l'intercetta è il punto d’intersezione tra la retta passante per P1 e P2 e l’asse delle ordinate che genera un punto di coordinate x=0 e y=n).
P1 e P2 servono soprattutto a disegnare la linea, mentre m ed n servono per individuare la retta passante per tali punti.
Ogni constructor provvederà al calcolo di P1 e P2 quando si conoscono m ed n e, viceversa, al calcolo di m ed n quando si conoscono P1 e P2.

Questa è la linea tra due semplici punti:

  Linea(Punto P1, Punto P2) {

    this.P1=P1;

    this.P2=P2;

    m=deltaY()/deltaX();

    n=-m*P1.x + P1.y;

  }

Questa è la linea con coefficiente angolare m ed intercetta n:
  Linea(double m, double n) {

    this.m=m;

    this.n=n;

    P1=new Punto(0, n);

    if (!Double.isInfinite(m)) P2=new Punto(-n/m, 0);

    if (m==0) P2=new Punto(1, n);

  }

di seguito vengono presentati alcuni methods della classe Linea che estraggono informazioni utili per il disegnatore:

Per conoscere la differenza delle ascisse o delle ordinate di P1 e P2 si possono utilizzare i seguenti methods, già utili immediatamente per il calcolo della lunghezza della linea P1P2

  double deltaX() {

    return P2.x - P1.x;

    }

        

  double deltaY() {

    return P2.y - P1.y;

  }

Infatti ecco l’utilizzo dei methods precedenti per il calcolo della lunghezza della linea P1P2:
  double lunghezza() {

    return Math.sqrt(Math.pow(deltaX(), 2) + Math.pow(deltaY(), 2));

  }

Method per il calcolo del punto medio della linea P1P2:
  Punto medio() {

    return new Punto((P1.x + P2.x)/2, (P1.y + P2.y)/2);

  }

Method che ritorna una ulteriore linea da un punto P, esterno alla linea P1P2 e perpendicalare alla stessa P1P2:
  Linea perp(Punto p) {

    if (!Double.isInfinite(m) && (m!=0)) {

      m2 = -1/m;

      n2 = -m2*p.x + p.y;

      x = (n2 - n) / (m - m2);

      y = m2 * x + n2;

      Linea lperp = new Linea(p, new Punto(x, y));

      lperp.m = m2;

      lperp.n = n2;

      return lperp;

    }

    else {

      if (Double.isInfinite(m)) {

        x = P1.x;

        y = p.y;

        Linea lperp = new Linea(p, new Punto(x, y));

        if (lperp.P1==lperp.P2) lperp = new Linea(lperp.P1, new Punto(lperp.P1.x+1, lperp.P1.y));

        lperp.m = 0;

        lperp.n = p.y;

        return lperp;

      }

      else { // (m==0)

        x = p.x;

        y = P1.y;

        Linea lperp = new Linea(p, new Punto(x, y));

        lperp.m = Double.POSITIVE_INFINITY;

        lperp.n = Double.POSITIVE_INFINITY;

        return lperp;

      }

    }

  }



Method che ritorna una ulteriore linea passante per un punto P, esterno alla linea P1P2 e parallela alla P1P2:
  Linea parallela(Punto p) {

    if (!Double.isInfinite(m)) return new Linea(m, -m*p.x + p.y);

    else return new Linea(new Punto(p.x, p.y), new Punto(p.x, P1.y));

  }

Data una linea, si immagini la retta passante per essa. Il method ritorna una linea che è la proiezione di un'altra linea, passata come parametro, su tale retta:
  Linea proiezione(Linea l) {

    Punto Pl1= new Punto(new Linea(P1, P2), perp(l.P1));

    Punto Pl2= new Punto(new Linea(P1, P2), perp(l.P2));

    return new Linea(Pl1, Pl2);

  }

Questo method disegna fisicamente la linea:
  void disegna(Color colore, Graphics g) {

    g.setColor(colore);

    g.drawLine((int) P1.x, (int) P1.y, (int) P2.x, (int) P2.y);

  }

}

E' necessario introdurre una classe simile a Linea, perchè costituita anch'essa da due punti, ma non è una Linea. Essa rappresenta l'insieme di punti (da 0 a 2 punti) generati dall'intersezione tra circonferenze, parabole, ellissi e linee.
class duePunti {

  Punto P1, P2;

Questi sono i punti generati dall’intersezione tra una linea ed una circonferenza:
  duePunti(Linea L, Circonferenza C) {

    double gam=C.centro.x*C.centro.x + C.centro.y*C.centro.y - C.raggio*C.raggio;

    double a=1 + L.m*L.m;

    double b=2*(L.m*L.n - C.centro.x - C.centro.y*L.m);

    double c=L.n*L.n - 2*C.centro.y*L.n + gam;

    eq2grado sol=new eq2grado(a, b, c);

    P1=new Punto(sol.s[0], L.m*sol.s[0] + L.n);

    P2=new Punto(sol.s[1], L.m*sol.s[1] + L.n);

  }

Questi sono i punti generati dall’intersezione tra due circonferenze:
  duePunti(Circonferenza C1, Circonferenza C2) {

    double gam1=C1.centro.x*C1.centro.x + C1.centro.y*C1.centro.y - C1.raggio*C1.raggio;

    double gam2=C2.centro.x*C2.centro.x + C2.centro.y*C2.centro.y - C2.raggio*C2.raggio;

    double a=2*(C2.centro.x - C1.centro.x);

    double b=2*(C2.centro.y - C1.centro.y);

    double c=gam1 - gam2;

    Linea asrad =new Linea(-a/b, -c/b);

    duePunti sec =new duePunti(asrad, C1);

    P1=sec.P1;

    P2=sec.P2;

  }

Questo method converte un duePunti in Linea:
  Linea linea() {

    return new Linea(P1, P2);

  }

}

Dopo aver presentato praticamente SOLO poche classi, Punto e Linea (ma classi di classe!) possiamo realizzare un telegrafo...ehm... possiamo realizzare classi di altre primitive geometriche più complesse senza, come si dice in gergo, saper né leggere e né scrivere:
Cominciamo a creare la classe Circonferenza:
class Circonferenza {

  Punto centro=new Punto();

  double raggio;



  Circonferenza(Punto centro, double raggio) {

    this.centro=centro;

    this.raggio=raggio;

  }

Semplice! Ma il bello non è questo. Il fatto è che una circonferenza può essere individuata anche da soli 3 punti. Noi mica conosciamo le formule di Geometria Analitica per calcolare il centro e il raggio di una circonferenza avendo noti solo 3 punti??? Io NO! Si fa per dire, ma non servono!
Guardate il constructor successivo:
Esso utilizza praticamente la costruzione grafica-geometrica della circonferenza per 3 punti (vedi figura), senza utilizzare formule. Praticamente è come se parlassimo all’OOP e gli dicessimo A PAROLE la sola definizione grafica di circonferenza per 3 punti non allineati:
 

Siano Pa, Pb, Pc 3 punti qualsiasi non allineati.
Si considerino i segmenti PbPa e PbPc.

Tradotto in Java:

  Linea PbPa =new Linea(Pb, Pa);

  Linea PbPc =new Linea(Pb, Pc);

Si considerino gli assi del segmento PbPa e del segmento PbPc. (L’asse del segmento è la retta passante per il punto medio di un segmento e perpendicolare al segmento stesso).

Tradotto in Java:

  Linea asPbPa =PbPa.perp(PbPa.medio());

  Linea asPbPc =PbPc.perp(PbPc.medio());

Se i due assi dei segmenti non sono paralleli allora la loro intersezione rappresenta il centro di una circonferenza.
Tale circonferenza risultante avrà come raggio la distanza tra tale centro e il punto Pa (o Pb o Pc).

Tradotto in Java:

  if (asPbPa.m != asPbPc.m) {

    centro =new Punto(asPbPa, asPbPc);

    raggio =new Linea(centro, Pa).lunghezza();

  }

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

Nel numero scorso dicemmo che noi siamo i programmatori e non i calcolatori, dunque i numeri sono roba per computers. Bene! Da oggi cercheremo di evitare anche formule! Così l’OOP di Java diventa come un linguaggio parlato! Guardate di seguito il constructor completo della circonferenza per 3 punti appena descritto e ditemi se riuscite a vedere un solo numero o una sola formula! Non è straordinario? Questo è il confine tra informatica e filosofia (ora ho esagerato!).
  Circonferenza(Punto Pa, Punto Pb, Punto Pc) {

    Linea PbPa =new Linea(Pb, Pa);

    Linea PbPc =new Linea(Pb, Pc);

    Linea asPbPa=PbPa.perp(PbPa.medio());

    Linea asPbPc=PbPc.perp(PbPc.medio());

    if (asPbPa.m != asPbPc.m) {

      centro =new Punto(asPbPa, asPbPc);

      raggio =new Linea(centro, Pa).lunghezza();

    }

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

  }

Un altro spettacolare method.
I due punti di tangenza ad una circonferenza possibili da un punto esterno, si realizzano molto semplicemnte tramite costruzione grafica-geometrica, senza introdurre alcuna formula. Infatti i punti di tangenza ad una circonferenza C da un punto esterno P sono DUE, e sono gli stessi punti che risultano dall'intersezione tra due circonferenze delle quali la seconda è la circonferenza avente per centro il punto esterno P e per raggio la distanza tra P e uno dei punti di tangenza. Tale raggio è ricavabile con teorema di pitagora applicato alla distanza dei centri e al raggio della circonferenza C che costituiscono l'ipotenusa e un lato di un triangolo rettangolo (retto nel punto di tangenza):

  duePunti tangente(Punto p) {

 // distanza tra P e centro di C

    double pc = new Linea(p, centro).lunghezza();

 // lunghezza della tangente

    double lunLtan = Math.sqrt(pc*pc - raggio*raggio);

 // intersezione delle circonferenze

    return new duePunti(new Circonferenza(p, lunLtan), this);

  }

Come potete osservare neanche qui sono presenti numeri o formule...!
Spesso può essere utile conoscere l’area e/o il perimetro della circonferenza:
  double area() {

    return Math.PI*raggio*raggio;

  }



  double perimetro() {

    return 2*Math.PI*raggio;

  }

Disegna la circonferenza utilizzando il semplice method della newGraphics disCirconf(x, y, r):
  void disegna(Color colore, Graphics g)        {

    newGraphics ng = new newGraphics(g);

    g.setColor(colore);

    ng.disCirconf(centro.x, centro.y, raggio);

  }

}

Inoltre dicemmo che alcuni methods della newGraphics erano solo provvisori. Infatti il disCirconf(x1, y1, x2, y2, x3, y3) che disegnava circonferenze per 3 punti non è più di utilità perché la classe Circonferenza riduce tutto al semplice disCirconf(x, y, r).

Alcuni methods delle classi presentate fanno uso di una classe eq2grado. Essa semplicemente risolve un'equazione di secondo grado i cui coefficienti sono a b c. Le soluzioni trovate vengono conservate nell'array S. Chi non è pratico di equazioni la consideri come una "scatola nera". La classe è questa:

class eq2grado {

  double a, b, c;

  double[] s= new double[2];

  

  eq2grado(double a, double b, double c) {

    this.a=a;

    this.b=b;

    this.c=c;

    double delta=b*b - 4*a*c;

    if (delta>=0) {

      s[0]=(-b + Math.sqrt(delta)) / (2*a);

      s[1]=(-b - Math.sqrt(delta)) / (2*a);

    }

    else System.out.println("non esiste un'intersezione reale");

  }

}

 
 

MokaByte rivista web su Java

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