Ricordo che un compagno di scuola spesso si divertiva a porre un quesito al quale tutti rispondevamo in modo errato, compreso i professori! Eppure era semplice. Il quesito era questo: «Secondo te come è giusto dire curva "dritta" o curva "diritta"?»
C'era chi riteneva esatte entrambe le risposte e chi l'una o l'altra. Altri invece facevano ricorso a un dizionario. E lui, sorridente, dopo un pò di inutili discussioni, faceva capire che il problema non era idiomatico, ma geometrico! Una curva non può essere nè "dritta" e nè "diritta"!!
Poi una volta, pensieroso mi disse: «Però, pensandoci bene, una linea potrebbe essere considerata "una curva di primo grado"!»
Infatti non si sbagliava.
Tra linee (segmenti) ed archi sembra che la sola differenza sia il fatto che l'arco è "storto", ma tale curvatura, anche se non apparentemente significativa, ha introdotto nella Geometria e nella Filosofia problemi che solo nel più tardi della loro storia sono stati "quasi" risolti. Tanto per dirne una, l'arco è molto più "dispendioso" della linea. Esso per esistere richiede un universo che sia più grande di quello utile a contenere la linea di almeno "l'universo della linea elevato al quadrato". Cioè:
- la linea esiste anche in una sola dimensione, un universo monodimensionale: la retta R
- l'arco, invece, richiede almeno un universo a due dimensioni: il piano XY
Naturalmente la curvatura che viene attribuita all'arco può essere osservata solo da chi vive all'esterno dell'arco. Nel senso che per i punti che giacciono sull'arco potrebbe non essere visibile, percepibile, rilevabile tale curvatura e (per i punti) continuare a credere di giacere su una linea retta. Dunque per essi un universo monodimensionale ma curvo.
Poi nasce anche il problema che pone il seguente quesito: «chi può dire che un vera linea retta (non curva almeno apparentemente) non abbia già una curvatura non rilevabile nemmeno dall'esterno di essa?»Da qui il problema che sia curvo anche lo spazio degli osservatori esterni alla linea o all'arco stesso (curva dritta!).
Ma, a questo punto, è meglio fermarci, altrimenti si rischia di far diventare tale articolo un trattato filosofico. Noi siamo programmatori di computers e ci accontentiamo di guardare la realtà da un punto di vista molto superficiale così come è superficiale il punto di vista della gran parte della gente che utilizzerà i nostri programmi.
Consiglio: Attenzione! Se non sarete superficiali potrebbe essere molto negativo il giudizio attribuito al vostro software. Immaginate un operatore CAD che, utilizzando il vostro software CAD, non trova il comando per disegnare una linea. Dovrebbe sapere che è sufficiente il comado per disegnare archi, ma impostando il raggio a infinito! Non credo che tale software abbia un futuro... almeno tra le persone "normali"...!
Bene! Ritorniamo alla "realtà"!\
A prima vista l'arco di circonferenza sembra possa essere trattato alla stessa stregua delle circonferenza. Questo è vero solo in parte. La difficoltà maggiore che si riscontra nella sua implementazione non è tanto dovuta alla rappresentazione, quanto alla varietà di modi e di circostanze che rendono possibile l'esistenza di un arco. E noi dobbiamo prevederli (quasi) tutti.
C'è da dire subito che un arco, a differenza di una circonferenza, ha una specie di costante: esso per essere univoco necessita siano espresse al minimo TRE informazioni. Dunque la prima terna di informazioni che ci viene in mente che rende possibile l'esistenza di un arco è:
l'arco per 3 punti, cioè punto iniziale, uno intermedio e quello finale.
Oppure punto iniziale, finale e lunghezza del raggio.
Possono esserci casi equivoci, come il secondo appena espresso dove per esempio, stabilito i punti iniziale e finale, gli archi possibili sono due, dei quali il secondo è quello complementare al primo sulla stessa circonferenza su cui essi giacciono. Perciò spesso i nostri methods, nei casi equivoci, tenteranno di costruire sempre l'arco che si sviluppa dal primo al secondo punto e/o nel verso di rotazione antiorario. Spesso, invece, non è controllabile, e se l'arco che risulta dalla nostra progammazione non è quello che ci aspettiamo, basta invertire i punti estremi o cambiare da false a true il valore del, quasi sempre presente, parametro unodeidue.
Allora elenchiamo quali possano essere le condizioni necessarie e sufficienti richieste dalla possibile esistenza di un arco.
- punto iniziale, finale, raggio
- angolo compreso dall'arco, punto iniziale, finale
- punto iniziale, intermedio, finale
- punto iniziale, finale e direzione della tangente dal punto iniziale (trattato sul prossimo articolo)
Di seguito sono descritti i constructor e methods relativi alle condizioni sopra desritte. A differenza degli articoli precedenti, si daranno alcune spiegazioni sul progetto della classe Arco perchè interamente sviluppata grazie alla conoscenza di base fornita in precedenza, dunque, prima di tutto non utilizzanti formule, e poi utile alla comprensione di ciò che stiamo realizzando.
La class Arco è questa:
class Arco extends entita {
Naturalmente un arco ha delle caratteristiche di base come centro, raggio, punti iniziale e finale e quindi un angolo iniziale e uno finale
Punto centro; double raggio; Punto P1, P2; double ainz, afin;
Il primo constructor di Arco avendo come parametri le tre informazioni P1, P2 e raggio, per essere risolto ha bisogno prima di tutto delle coordinate del centro. Il centro di un arco, quando si conoscono i punti estremi ed il raggio, è dato dall'intersezione delle circonferenze con centro gli estremi dell'arco e raggio uguale al raggio dell'arco stesso.
Così è possibile sfruttare le class Circonferenza e duePunti. E, dato che la class duePunti ritorna due punti, il centro da noi richiesto non è univoco, allora verrà ritornato l'uno o l'altro centro a seconda dell'impostazione True o False passata al parametro unodeidue.
Alla fine gli angoli iniziale e finale sono conosciuti senza applicare formule grazie al methods angoloXY() della classe Linea (applicato alle linee-raggio immaginarie tra il centro e i punti P1 e P2) che ritorna appunto l'arcotangente del coefficiente angolare della linea stessa (l'angolo di inclinazione su X), corretto (previsione precedente!) però per angoli da 0 a 2PIgreco anzicchè da -PIgreco/2 a Pigreco/2.
// arco con punti inziale e finale, raggio noti
// gli archi possibili sono due e il parametro
unodeidue
permette la
// definizione dell'uno l'altro
Arco(Punto P1, Punto P2, double raggio, boolean unodeidue)
{
this.P1=P1;
this.P2=P2;
this.raggio=raggio;
duePunti C2p=new duePunti(new Circonferenza(P1, raggio), new Circonferenza(P2, raggio));
if (unodeidue) centro=C2p.P1; else centro=C2p.P2;
ainz =new Linea(centro, P1).angoloXY();
afin =new Linea(centro, P2).angoloXY();
}
Spesso invece si conosce, oltre gli estremi P1 e P2, l'angolo compreso dall'arco. In questo caso non si può fare a meno di applicare una formuletta. Purtorppo quando si ha a che fare con angoli è inevitabile l'uso di formule trigonometriche. La formula usata serve ad ottenere per l'angolo specificato, la lunghezza del raggio dell'arco. Ma senza la base di conoscenza sarebbe in ogni caso necessario l'uso di molte altre formulette per calcolare la posizione del centro. Invece, come nel costructor precedente usiamo similmente le class Circonferenza e duePunti ed il problema è risolto. Come al solito il caso non è univoco e dipende dal parametro passato unodeidue.
// arco noto per i punti iniziale e finale e angolo compreso dall'arco
Arco(double acomp, Punto P1, Punto P2, boolean unodeidue) {
this.P1=P1;
this.P2=P2;
double corda = (new Linea(P1, P2)).lunghezza();
raggio=corda/2/Math.sin(acomp/2);
duePunti C2p= new duePunti(new Circonferenza(P1, raggio), new Circonferenza(P2, raggio));
if (unodeidue) centro=C2p.P1; else centro=C2p.P2;
ainz =new Linea(centro, P1).angoloXY();
afin =new Linea(centro, P2).angoloXY();
}
Questo constructor, che apparentemente sembra il più complesso, si risolve invece in maniera banale grazie al constructor della class Circonferenza passante per tre punti dal quale si estraggono i dati relativi al centro e raggio ed il gioco è fatto.
// arco noto per punti iniziale e finale e uno qualsiasi
Arco(Punto P1, Punto Pi, Punto P2) {
this.P1=P1;
this.P2=P2;
Circonferenza C=new Circonferenza(P1, Pi, P2);
centro=C.centro;
raggio=C.raggio;
ainz =new Linea(C.centro, P1).angoloXY();
afin =new Linea(C.centro, P2).angoloXY();
}
Il punto medio di un arco non è semplice come quello di una linea.
Apparentemente sembra univoco, ma se si riflette sul problema si ci accorge che, nonostante siano dati i punti estremi e raggio, come non è univoco il centro così non è univoco nemmeno l'arco e il suo punto medio. Perciò i punti medi possono essere due e sono ritornati da questo methods di tipo duePunti.
Nemmeno il calcolo della loro posizioni è semplice. Esse sono date dall'intersezione tra la retta passante per il centro perpendicolare alla corda dell'arco e la circonferenza a cui l'arco stesso appartiene.
L'enunciato precedente si risolve in una sola riga nel method successivo
// punto medio
duePunti medi() {
return new duePunti((new Linea(P1, P2)).perp(centro), new Circonferenza(centro, raggio));
}
// area del settore circolare
double area() {
return Math.PI*raggio*raggio*Math.abs(afin-ainz);
}
// Lunghezza dell'arco
double lunghezza() {
return 2*Math.PI*raggio*Math.abs(afin-ainz);
}
// punti di tangenza da un punto esterno all'arco in corso
duePunti tangente(Punto p) {
return (new Circonferenza(centro, raggio)).tangente(p);
}
// sposta l'arco parallelamente allo spostamento Porig - Pdest
void sposta(Punto Porig, Punto Pdest) {
centro.sposta(Porig, Pdest);
}
// ruota di un angolo specificato l'arco intorno ad un punto
void ruota(Punto Prot, double alfa) {
centro.ruota(Prot, alfa);
}
// disegna l'arco
// tale methods ha comportato una modifica alla newGraphics di seguito specificata
void disegna(Color colore, Graphics g) {
newGraphics ng = new newGraphics(g);
g.setColor(colore);
ng.disArco(centro.x, centro.y, raggio, ainz, afin);
}
}
Modifiche alla newGraphics
Il method disegna() della class Circonferenza che abbiamo introdotto negli articoli precedenti, ma ora anche la class Arco, richiamano il method disCirconf() che a sua volta richiama disCircolo(). Ora con l'introduzione degli archi ha subito qualche modifica:
// sono stati aggiunti altri due parametri
ainz
e
afin
(angolo iniziale e angolo finale)
private void disCircolo(double xc, double yc, double ra, double rb, int nl, double rotz, double ainz, double afin) {
// se l'angolo iniziale risulta maggiore di quello finale allora negativizza quello iniziale
// es: arco da 270 a 45 diventa da -90 a 45
if (ainz>afin) ainz=ainz-2*Math.PI;
// il calcolo dell'arco parte dall'angolo di un'eventuale rotazione + l'angolo iniziale
for (double an=rotz+ainz; an<=rotz+afin+.0001; an+=(afin-ainz)/nl) {
xAl = ra*Math.cos(an)+ xc;
yAl = rb*Math.sin(an)+ yc;
if (an>rotz+ainz) g.drawLine((int) xDal, (int) yDal, (int) xAl, (int) yAl);
xDal=xAl;
yDal=yAl;
}
}
L'implementazione degli archi non finisce ancora. Sul prossimo articolo verrano esaminati i problemi di raccordo tra linee ed archi.
Le frequenti modifiche alle classi già presentate sono dovute al continuo miglioramento della loro struttura. E da tener presente, inoltre, che tali articoli non sono già belli e confezionati, ma vengono scritti dall'autore di volta in volta.
Basta, comunque, farsi il download dei files newGraphics.java e vectors6.java e sostituirli a quelli già downloadati in precedenza.
