Curve Polari
Ieri mi sono talmente divertito a disegnare la Rhodonea in Flash CS, che stamattina, mentre firmavo la camicia del mio ultimo esame (EVVAI!!!!), pensavo che avrei potuto adattare quel codice e renderlo in grado di disegnare qualsiasi curva data la sua equazione polare.
Bhè ora vi faccio vedere come ho fatto.
Innanzitutto la struttura delle directory è la stessa del progetto della Rhodonea, quindi cartella org contenente le cartelle Main e Geometria. La Document Class è quindi sempre nella cartella Main e si chiama questa volta PolarMain.as.
Prima di vedere la Document Class devo precisare alcune cose riguardo al .fla del progetto:
- ho rinominato le istanze di due degli Slider per renderli più generici, i nuovi nomi sono rispettivamente, dall’alto al basso: raggio_g, arg1,arg2
- ho aggiunto un Componente ComboBox (istanziato come curves_box) per permettere la scelta tra più curve polari.
- ho rinominato quello che era il pulsante stop in reset (e quindi l’istanza in reset_bt).
Detto ciò, vediamo in particolare come è diventata la classe PolarMain.as:
package org.Main{
import flash.display.MovieClip;
import flash.events.*;
import fl.events.ColorPickerEvent;
import org.Geometria.*;
import fl.controls.ComboBox;
import fl.data.DataProvider;
I package importati sono gli stessi che nella Rhodonea, in più ci sono gli ultimi due, relativi al ComboBox che ci permetterà di scegliere quale curva disegnare e al DataProvider, un oggetto usato per riempire il ComboBox.
Gli attributi della Document Class si sono ridotti a 2:
private var curveArr:Array=new Array();
private var lineColor:uint=0;
L’array curveArr conserva i puntatori (sic!) alle varie curve disegnate. Ho infatti pensato che fosse più carino poter disegnare varie curve una sull’altra..
lineColor era già presente nella Rhodonea quindi sorvolerò….
Vediamo ora il costruttore:
public function PolarMain() {
var dp:DataProvider=new DataProvider();
dp.addItem({label:"Rhodonea"});
dp.addItem({label:"Bifoliate"});
dp.addItem({label:"Butterfly"});
dp.addItem({label:"Hippopede"});
curves_box.dataProvider=dp;
curves_box.addEventListener(Event.CHANGE,curve_func);
line_pic.addEventListener(ColorPickerEvent.CHANGE, colorLine);
start_bt.addEventListener(MouseEvent.CLICK,start_func);
reset_bt.addEventListener(MouseEvent.CLICK,reset_func);
setCurveSlider();
}
Qui l’unica cosa diversa è la presenza del DataProvider, un oggetto che altro non è che un contenitore di oggetti che poi verrà associato al ComboBox. Può sembrare un po’ laboriosa come procedura, ma ha i sui vantaggi: ad esempio tramite il DataProvider è possibile aggiungere e/o rimuovere elementi anche a runtime ed è possibile associare lo stesso DataProvider a oggetti diversi, ma che magari hanno necessità di condividere i dati. Nel nostro caso non userò nessuna di queste proprietà, assegno i dati UNA volta al DataProvider e lo assegno ad UN componente (il ComboBox), ma tant’è…
Registro poi le funzioni che faranno da listener ai vari componenti, proprio come avevo fatto per la Rhodonea (ce n’è giusto uno in piu relativo al ComboBox) e richiamo la funzione setCurveSlider() che a seconda di quale curva è correntemente selezionata all’interno del ComboBox imposta i parametri dei 3 Slider.
La funzione start_func() (quella richiamata dal bottone start) è anch’essa cambiata poco:
private function start_func(event:Event) {
var currentCurve:PolarCurve=null;
var args:Array=new Array(raggio_g.value,arg1.value,arg2.value);
switch (curves_box.selectedLabel) {
case "Rhodonea" :
currentCurve=new Rhodonea(args);
break;
case "Bifoliate" :
currentCurve=new Bifoliate(args);
break;
case "Butterfly" :
currentCurve=new Butterfly(args);
break;
case "Hippopede" :
currentCurve=new Hippopede(args);
break;
}
currentCurve.setColor(lineColor);
currentCurve.x=stage.stageWidth/2;
currentCurve.y=stage.stageHeight/2-50;
addChild(currentCurve);
curveArr.push(currentCurve);
currentCurve.disegna();
}
La cosa da notare qui è la prima riga, cioè la dichiarazione dell’oggetto currentCurve di tipo PolarCurve. Infatti ogni curva polare sarà un oggetto che erediterà da PolarCurve, vedremo tra poco questo cosa significa.
Per il resto la funzione non fa altro che raccogliere i valori dei 3 Slider e inserirli nell’array args che sarà utilizzato come parametro dal costruttore di tutte le curve. A seguire a seconda di quale curva è selezionata nel ComboBox si istanzia l’oggetto relativo. Il seguito è identico a quanto c’era nel progetto della Rhodonea.
La funzione reset_func() (che nel progetto della Rhodonea si chiamava stop_func) è cambiata un po’, ma essenzialmente fa le stesse cose di prima:
private function reset_func(event:Event):void {
if (curveArr.length>0) {
var curve:PolarCurve=null;
while ((curve=curveArr.pop())!=null) {
curve.stoppa();
removeChild(curve);
}
}
}
Controlla che l’array di curve non sia vuoto, estrae quindi tutti gli elementi da tale array (il metodo pop() estrae un elemento dall’array, facendone quindi anche diminuire la dimensione, fino a quando sarà vuoto). In più rispetto a prima cancello anche le curve rimuovendole con removeChild().
Segue poi la funzione colorLine() il cui funzionamento e scopo sono rimasti gli stessi.
Vediamo ora come abbiamo trasformato il codice del precedente articolo relativo alla classe Rhodonea per far si che rappresenti una generica curva polare. Vediamo quindi il file PolarCurve.as:
vediamo innanzitutto gli attributi della classe PolarCurve:
protected var teta:Number=0;
protected var maxTeta:Number=uint.MAX_VALUE;
protected var speed:Number=0.1;
private var old,current:Point=null;
private var lineColor:uint=0;
Sono stati rimossi gli attributi caratterizzanti la rhodonea (cioè r,omega,n e d) visto che saranno gestiti dalle singole curve in modo differente. Ho però aggiunto l’attributo maxTeta, visto che il parametro angolare della funzione che calcola la coordinata polare ha massimi diversi per le varie curve, inoltre l’ho inizializzato a uint.MAX_VALUE un numero cioè enorme (controllate sul manuale il valore preciso, ricordate F1 è vostro amico!), così che se non viene settato esplicitamente mantiene un valore sufficiente alto per qualsiasi curva.
Qualcuno avrà notato che alcuni attributi sono dichiarati protected e altri private, spenderò qualche parola sull’argomento.
Gli attributi di una classe (così come i metodi) posso essere di 3 tipi:
- public
- private
- protected
Un oggetto dichiarato public è accessibile dall’esterno della classe. Ad esempio all’interno di questa classe il metodo disegna() è dichiarato public sarà quindi possibile richiamarlo dalla Document Class (come infatti avviene).
Un oggetto dichiarato private è accessibile solo all’interno della classe di appartenenza. Ad esempio la variabile lineColor che è stata dichiarata private non è accessibile se non dall’interno della classe PolarCurve, e infatti per settarla ho dovuto scrivere un metodo dedicato (setColor()).
Infine un oggetto dichiarato protected è inaccessibile dall’esterno, come se fosse private, ma in più è accessibile dalle classi che derivano da quella in cui è dichiarato. In questo caso ad esempio le varie classi relative alle curve polari che erediteranno da questa potranno accedere direttamente ai 3 attributi protetti, cosa che non sarebbe potuta essere possibile se fossero stati privati.
Tornando alla classe PolarCurve troviamo che le funzioni disegna() , stoppa() e disegna_() sono rimaste pressocchè uguali a come erano state definite nella progetto precedente della Rhodonea:
public function PolarCurve() {
}
public function disegna():void {
addEventListener(Event.ENTER_FRAME,disegna_);
}
public function stoppa():void {
removeEventListener(Event.ENTER_FRAME,disegna_);
}
public function setColor(col:uint) {
lineColor=col;
}
private function disegna_(event:Event):void {
current=new Point();
var effe:Number=f();
current.x=effe*Math.cos(teta);
current.y=effe*Math.sin(teta);
if (old!=null) {
var linea:Shape=new Shape();
linea.graphics.lineStyle(0,lineColor);
linea.graphics.moveTo(old.x,old.y);
linea.graphics.lineTo(current.x,current.y);
addChild(linea);
}
old=current;
teta+=speed;
if (teta>maxTeta) {
removeEventListener(Event.ENTER_FRAME,disegna_);
}
current=null;
}
Di nuovo c’è che il costruttore è rimasto vuoto.
l’ultimo metodo, ma forse il più importante dell’intera classe è f() :
protected function f():Number {
return 0;
}
qualcuno potrebbe obbiettare (e sempre ad obbiettare state?!) che è forse un po’ scarna come metodo più importante di una classe, eppure il ruolo di questa funzione è indubbio, essa infatti calcola il valore della prossima coordinata polare.
Ma come? io non vedo calcoli! la funzione restituisce semplicemente 0!! direte voi.. (o forse anche no…)
La questione è che tale funzione sarà ridefinita da ogni classe che erediterà da PolarCurve e lì si che ci sarà il calcolo della prossima coordinata.
Vediamo ad esempio come è diventata la classe Rhodonea.as :
package org.Geometria{
import flash.display.*;
import flash.utils.*;
import flash.geom.Point;
import flash.events.*;
public class Rhodonea extends PolarCurve {
var r,omega:Number=1;
public function Rhodonea(args:Array) {
if (args.length<3) {
trace("numero di argomenti sbagliato:"+args.length+"(3)");
} else {
super();
r=args[0];
omega=args[1]/args[2];
maxTeta=(args[1]*args[2]*Math.PI)+1;
}
}
override protected function f():Number {
return r * Math.cos(omega * teta);
}
}
}
Da notare che la classe Rhodonea extende ora PolarCurve. Questo significa che un oggetto di tipo Rhodonea è ora un oggetto di tipo PolarCurve con in più un certo qualcosa (voglio sperare che il concetto di ereditarietà non vi sia totalmente oscuro, in caso contrario vi invito nuovamente a provvedere!). Questo certo qualcosa in più sono innanzitutto i due attributi r e omega che se ricordate sono necessari nell’equazione polare della Rhodonea e soprattutto la funzione f() che in effetti non è in più, ma che semplicemente ridefinisce quella funzione f() che nella classe PolarCurve restituiva sempre 0. Al fine di poter ridefinire una funzione è necessario utilizzare la parola chiave override, come ho appunto fatto.
Bhè questo è quanto. Nel esempio all’inizio dell’articolo ho implementato 3 o 4 curve, ma le possibilità sono innumerevoli (sì il numero di curve polari possibili è infinito…)
cmq ho messo tutto nel solito .rar
