In rete: http://www.elegio.it/svg/ellisse.html
Una approssimazione utile nella grafica
Qui dimostro che, per valori della eccentricità non elevatissimi, l'approssimazione di fare girare l'asteroide a velocità angolare costante NON attorno al fuoco occupato dall' astro ma attorno al fuoco NON occupato dall'astro, funziona ottimamente ossia evita di dovere risolvere l'equazione di Keplero che, come è noto, bisogna risolvere in modo iterativo. Questa approssimazione può servire anche per inizializzare bene la soluzione iterativa dell'equazione di Keplero.. e qui, per maniaco amore di alta precisione faccio anche pochissime iterazioni per migliorare, inutilmente dati gli scopi solo grafici, la precisione del calcolo...
Si possono fare varie simulazioni specificando a ossia il valore del semiasse maggiore che non deve essere maggiore di 2000 se non si vuole sconfinare dal disegno, va sarebbe la velocità all'apoastro e vp la velocità al periastro ma questi due valori servono solo a definire la eccentricità dell'ellisse ossia un numero adimensionale che deve essere maggiore di zero e minore di uno. Si può anche fissare la rotazione in gradi dell'ellisse attorno al centro definito come il punto equidistante dai due fuochi, quello occupato dall'astro e l'altro, e sulla loro congiungente.
a= va= vp= g°=
http://www.elegio.it/mc2/moto-ellittico-201308.html per approfondire gli aspetti teorici.
http://www.elegio.it/svg/tanteparabole.html per imparare ad usare archi di parabola per disegnare traiettorie prive di spigli.
http://www.elegio.it/svg/traiettoria-precisa.js.html La funzione richiamabile ed usabile localmente a patto di essere connessi in rete...
Libreria di servizio per disegnare traiettorie non spigolose
Invece di nascondere nel documento le funzioni Javascript che vengono usate qui, a scopo esplicativo e didattico, le faccio vedere come semplici testi HTML e con una apposita function copio ed attivo questi testi HTML che dunque sono esattamente i sorgenti Javascript che verranno usati qui...
// // Seleziona la coppia di coordinate da usare per fare // il disegno bidimensionale e memorizza il risultato // nel vettore contenente i quattro punti necessari // per costruire i due archi di parabola definiti con // l'algoritmo di Bézier. // Da notare che il secondo ed il quarto punto servono // solo per definire la direzione di spostamento rettilineo // ovvero tangenziale, dal primo e dal terzo punto e dunque // Pa, quando la fase vale 2, potrebbe essere non un punto // vicinissimo al punto 1 o, se la fase vale 4, un punto // vicinissimo al punto 3 , ma potrebbe anche essere la // velocita' possedute dalla particella nel primo o nel // terzo punto e quindi andrebbe aggiunto al valore del // primo punto se la fase vale 2 o del terzo se // la fase vale 4. // ---- // Ho scelto questa impostazione per consentire, nel // caso di traiettorie tridimensionali, la creazione // di disegni ovviamente bidimensionali, ottenuti // "fotografando" gli spostamenti in x,y da un punto // di vista a z infinito o gli spostamenti x,z da un // punto di vista a y infinito o gli spostamenti y,z // da un punto di vista a x infinito. // function bidim(Pxy,Pa,fase){ Pxy[fase]=["xy",Pa[1],Pa[2]]; // alert(fase+") "+Pxy); return true; } // // Ad ogni coppia di punti consecutivi associa due // archi di parabola fornendo i punti di controllo // della quadrica di Bézier usata nella // grafica SVG. // Se la sequenza di punti viene interrotta ossia // se un elemento della lista dei punti non e' un // punto, la traiettoria viene considerata terminata // e gli eventuali punti successivi servono a // definire una nuova traiettoria di un'altra particella. // function fatraiettoriaq(punti,fraz){ var Q,j=0,nn=punti.length; var disegno=["Disegno "],nd=0; var fase=1,Pxy=["xy",[1],[2],[3],[4]]; var dn,dx12,dy12,dx13,dy13,dx34,dy34; var Pix,Piy,P2x,P2y,P3x,P3y,P4x,P4y; if(xagtb(2,nn)) return disegno; for(j=1;xagtb(nn,j);j++){ if(Array.isArray(punti[j])){ bidim(Pxy,punti[j],fase); if(fase==2){ dx12=Pxy[2][1]-Pxy[1][1]; dy12=Pxy[2][2]-Pxy[1][2]; dn=Math.sqrt(dx12*dx12+dy12*dy12+1.e-100); dx12=dx12/dn; dy12=dy12/dn; Pix=Pxy[1][1]; Piy=Pxy[1][2]; nd++;disegno[nd]="M"; nd++;disegno[nd]=[0,Pix,Piy]; Q="Q"; } else if(fase==4){ dx34=Pxy[4][1]-Pxy[3][1]; dy34=Pxy[4][2]-Pxy[3][2]; dn=Math.sqrt(dx34*dx34+dy34*dy34+1.e-100); dx34=dx34/dn; dy34=dy34/dn; dx13=Pxy[3][1]-Pix; dy13=Pxy[3][2]-Piy; dn=fraz*Math.sqrt(dx13*dx13+dy13*dy13+1.e-100); P2x=Pix+dn*dx12; P2y=Piy+dn*dy12; P4x=Pxy[3][1]-dn*dx34; P4y=Pxy[3][2]-dn*dy34; P3x=(P2x+P4x)/2; P3y=(P2y+P4y)/2; Pix=Pxy[3][1]; Piy=Pxy[3][2]; nd++;disegno[nd]=Q; nd++;disegno[nd]=[0,P2x,P2y]; nd++;disegno[nd]=[0,P3x,P3y]; nd++;disegno[nd]=[0,P4x,P4y]; nd++;disegno[nd]=[0,Pix,Piy]; dx12=dx34; dy12=dy34; Q=" "; fase=2; } fase++; } else fase=1; } // // Il disegno e' un vettore di punti... // return disegno; } // // Trasforma la traiettoria ad archi di parabola in una spezzata. // Non la uso qui ma comunque potrebbe servire... // function faspezzata(lista){ var j,las=["Spezzata "],nn=lista.length; for(j=1;xagtb(nn,j);j++){ if(Array.isArray(lista[j]))las[j]=lista[j]; else if(lista[j]=="Q")las[j]="L"; else las[j]=lista[j]; } return las; } // // Assegnata la lista dei punti da disegnare, // ossia il disegno, fa in modo che le coordinate // dei punti stiano in un rettangolo prescelto. // Non la uso qui ma puo'servire... // function inquadra(punti,width,height){ var j,fa,xm,ym,nn=punti.length; var xmin=1.e100,xmax=-1.e100; var ymin=1.e100,ymax=-1.e100; var pnorma=["Punti inquadrati"]; // for(j=1;xagtb(nn,j);j++){ pnorma[j]=punti[j]; if(Array.isArray(punti[j])){ xmin=Math.min(xmin,punti[j][1]); xmax=Math.max(xmax,punti[j][1]); ymin=Math.min(ymin,punti[j][2]); ymax=Math.max(ymax,punti[j][2]); } } // if(xageb(xmin,xmax))return pnorma; fa=width/(xmax-xmin); xm=(xmax+xmin)/2; if(xageb(ymin,ymax))return pnorma; fa=2*Math.min(fa,height/(ymax-ymin)); ym=(ymax+ymin)/2; for(j=1;xagtb(nn,j);j++){ if(Array.isArray(pnorma[j])){ pnorma[j][1]=Math.round(width+fa*(pnorma[j][1]-xm))/2; pnorma[j][2]=Math.round(height+fa*(pnorma[j][2]-ym))/2; } } return pnorma; } // // Trascrive la lista di punti e stringhe di controllo // arrotondando le coordinate dei punti dato che e' inutile // specificare frazioni di pixel... // Qui uso una altra funzione di arrotondamento... // function ntond(x){ return (Math.round(2*x)/2);} // function fastringatonda(lista){ var j,ss="",nn=lista.length; for(j=1;xagtb(nn,j);j++){ if(Array.isArray(lista[j])) ss+= ntond(lista[j][1])+" "+ntond(lista[j][2])+" "; else ss+=lista[j]+" "; } return ss; } // // Nota l'id del path da modificare, gli da' il disegno: // function facambiopath(idpath,disegno){ var ilpath=document.getElementById(idpath); ilpath.setAttribute("d",disegno); } // // IMPORTANTE FUNZIONE APPLICATA QUI: // Fonde due traiettorie in modo da avere coppie di punti molto // vicini tra loro. // // Questa fusione serve perche' l'interpolazione parabolica ha bisogno // di dedurre la derivata e quindi ha bisogno di coppie di punti // vicini tra loro. // Se dunque calcolo due traiettorie identiche ma una con punti // leggermente anticipati e l'altra con unti leggermente // posticipati e fondo le due traiettorie piloto molto bene // l'algoritmo del calcolo della quadrica di Bezier. // function fondetra(tra,trb){ var j,tfuso=[],nn=tra.length; if(trb.length!=nn) return tfuso; for(j=0;xagtb(nn,j);j++){ if(Array.isArray(tra[j])){ tfuso[tfuso.length]=tra[j]; tfuso[tfuso.length]=trb[j]; } else{tfuso[tfuso.length]=tra[j]+" "+trb[j];} } return tfuso; }Questa è la parte VERAMENTE importante, DEDICATA AL CALCOLO DEL MOTO KEPLERIANO
// // fa la rotazione delle coordinate in gradi ( 180 un semigiro ). // function giragira(x,y,wgradi){ var cs,sn,rad=Math.PI*wgradi/180; cs=Math.cos(rad); sn=Math.sin(rad); return ["ruotato di "+rad+" radianti: ",cs*x+sn*y,cs*y-sn*x]; } // // Calcola l'anomalia eccentrica in radianti tra 0 e 2*PI // essendo assegnato il tempo. // DUNQUE risolve l'equazione di Keplero usando le velocita'. // La velocita' al periastro=vp e all'apoastro=va sono // legate a G*M dalla nota relazione G*M=a*va*vp // dove il semiasse maggiore e' indicato da a. // // L'eccentricita' che e' un parametro classico, adimensionale, // si ottiene dai valori assoluti di vp e da va minore di vp, // con la formula ec=(vp-va)/(vp+va). // // Il periodo di rivoluzione=P si ottiene dividendo la // circonferenza per una opportuna velocita' ossia P = 2*PI*a/vb // dove vb e' la velocita' bilanciata ossia vb=sqrt(va*vp) // ossia la velocita' della particella qualora facesse una // orbita perfettamente circolare. // function fanomaliav(t,a,va,vp){ var j,ec,vm,P,trad,ea,ev,ermax=1.e-9; // // Un calcolo iterativo ( l'equazione di Keplero ) // esagerando nella altissima precisione imposta. // with(Math){ ec=abs(abs(vp)-abs(va))/(abs(vp)+abs(va)); P=2*PI*abs(a)/sqrt(abs(va*vp)); trad=t*sqrt(abs(va*vp))/abs(a); ev=trad; ea=trad+ec*sin(trad); for(j=0;xagtb(1000,j);j++){ if(xagtb(ermax,abs(ea-ev))) break; ev=ea; ea=ev+(trad-ev+ec*sin(ev))/(1-ec*cos(ev)); } } return ["Eccentricanomalia,ec,errore ",ea,t%P,ec,ea-ev]; } // // Calcola il tempo essendo assegnata l'anomalia eccentrica // in radianti ( inversa della funzione fanomaliav // che uso solo per dedurre da t il valore di ea con // una altissima precisione per calcoli matematici // ma non necessariamente graficati ) // function fatempospazio(ea,a,va,vp){ // // Attenzione ! L'origine delle coordinate sta nel // fuoco dove sta l'astro. // var ec,vb,t,x,y,P; with(Math){ ec=abs(abs(vp)-abs(va))/(abs(vp)+abs(va)); vb=sqrt(abs(va*vp)); P=2*PI*abs(a)/vb; t=(ea-ec*sin(ea))*abs(a)/vb; x=abs(a)*(cos(ea)-ec); y=2*abs(a)*vb*sin(ea)/(abs(va)+abs(vp)); } return ["t,x,y ",t,x,y,P,ec,ea]; } // // In grafica e' inutile usare troppi decimali... // Qui sono gia' troppo pignolo... // function arrotonda(x){ return (Math.round(1000*x)/1000); } // // Questa funzione e' stata pensata per uso grafico // ed e' la parte "innovativa" di questa libreria. // function fapprossimato(t,a,va,vp){ // // L'origine delle coordinate sta nel fuoco // dove sta l'astro, non nel centro dell'ellisse ! // var b,P,ea,ec,cs,sn,x,y; with(Math){ ec=abs(abs(vp)-abs(va))/(abs(vp)+abs(va)); b=abs(a)*sqrt(1-ec*ec); P=2*PI*abs(a)/sqrt(abs(va*vp)); cs=cos(2*PI*t/P); sn=sin(2*PI*t/P); rr=a*(1-ec*ec)/(1-ec*cs); x=rr*cs-2*ec*a; y=rr*sn; ea=2*PI*t/P+y*ec/b; // // Gia' ora x ed y sono graficamente buoni con // eccentricita' minore di 0.8 ma se voglio // migliorare la precisione posso ripetere // la seguente coppia di istruzioni varie volte... // y=b*sin(ea); ea=2*PI*t/P+y*ec/b; y=b*sin(ea); ea=2*PI*t/P+y*ec/b; y=b*sin(ea); ea=2*PI*t/P+y*ec/b; // // Direi che ho gia' esagerato come precisione... // x=a*(cos(ea)-ec); y=b*sin(ea); } return ["t,x,y ",t,x,y,P,ec,ea]; } // // Una tabella HTML per confrontare i valori esatti con // quelli approssimati ottenuti facendo girare a velocita' // angolare costante l'asteroide attorno al secondo fuoco // dove NON STA L'astro. // function fatabella(a,va,vp,nn){ var xugt="\u003c",xult="\u003e"; var xulg=xult+xugt; var j,ss=xugt+"table"; var t,P,ec,vea,vap,vin=fatempospazio(0,a,va,vp); P=vin[4]; ec=vin[5]; for(j=0;xagtb(nn,j);j++){ t=P*j/(nn-1); vea=fanomaliav(t,a,va,vp); vin=fatempospazio(vea[1],a,va,vp); vap=fapprossimato(t,a,va,vp); ss=ss+xulg+"tr"+xulg+ "td"+xult+j+xugt+"/td"+xulg+ "td"+xult+"Ea= "+arrotonda(vea[1]*180/Math.PI)+xugt+"/td"+xulg+ "td"+xult+"t = "+arrotonda(t)+xugt+"/td"+xulg+ "td"+xult+"tc= "+arrotonda(vin[1])+xugt+"/td"+xulg+ "td"+xult+"x = "+arrotonda(vin[2])+xugt+"/td"+xulg+ "td"+xult+"y = "+arrotonda(vin[3])+xugt+"/td"+xulg+ "td"+xult+"x approx= "+arrotonda(vap[2])+xugt+"/td"+xulg+ "td"+xult+"y approx= "+arrotonda(vap[3])+xugt+"/td"+xulg+ "td"+xult+"ea approx= "+arrotonda(vap[6]*180/Math.PI)+xugt+"/td"+xulg+ "/tr"; } ss=ss+xulg+"/table"+xult+"Periodo = "+P+ "; assemaggiore = "+a+"; eccentricità = "+ec+";"+ "Vel.apoastro = "+va+"; Vel.periastro = "+vp; return ss; } // // Per disegnare pallini di dimensione voluta. // function cerchietto(x,y,r){ return " M "+x+" "+(y+r)+" a "+r+" "+r+" 0 0 0 0 "+ (-2*r)+" "+r+" "+r+" 0 0 0 0 "+(2*r)+" m 0 "+(-r)+" "; } // // Visualizza la traiettoria dell'asteroide con tanti // pallini a passo costante nel tempo e dunque raggruppati // attorno all'apoastro e molto distanziati attorno al // periastro. // // Si puo' scegliere il calcolo rigoroso o il calcolo // ottenuto con la funzione approssimata ( velocita' // angolare costante attorno al fuoco dove NON STA l'astro ) // ma qui verono lo impongo false per vedere cosa // succede nel calcolo approssimato... // function fatraiettoria(a,va,vp,nn,xc,yc,wg,dt,verono){ var j,vs=["Traiettoria"]; var t,x,y,P,ec,vea,vap,vin=fatempospazio(0,a,va,vp); P=vin[4]; ec=vin[5]; for(j=0;xagtb(nn,j);j++){ t=dt+P*j/(nn-1); vea=fanomaliav(t,a,va,vp); vin=fatempospazio(vea[1],a,va,vp); // // Se verono e' false usa il metodo approssimato. // vap=fapprossimato(t,a,va,vp); // // Trasla l'ellisse in modo che il centro sia non nel // fuoco ma equidistante tra i due fuochi e poi // trasla il disegno di xc,yc. // if(verono){x=vin[2]+a*ec;y=vin[3];} else {x=vap[2]+a*ec;y=vap[3];} xy=giragira(x,y,wg); vs[vs.length]=[(j+1)+" ",xy[1]+xc,xy[2]+yc]; } return vs; } // // Disegna la traiettoria usando cerchietti di raggio rc. // function traiettoriacerchi(vtra,rc){ var j,nn=vtra.length,ss=""; for(j=0;xagtb(nn,j);j++){ if(Array.isArray(vtra[j])){ ss+=cerchietto(vtra[j][1],vtra[j][2],rc); } } return ss; } // // Disegna la traiettoria usando segmenti di vario tipo. // function traiettoriasegmenti(vtra,ini,sini){ var j,nn=vtra.length,ss=sini; for(j=ini;xagtb(nn,j);j++){ if(!Array.isArray(vtra[j]))break; ss+=" "+vtra[j][1]+" "+vtra[j][2]; } return ss; } // var numerofase=0; var contatore,idpath,idbispath,idp,idbisp,idpercorso; // // Serve ad inizializzare la simulazione del movimento // function cambiapath(){ var va,vp,vtra,vtrb,vtrf,ptra,dd,listavvio; // listavvio=document.getElementsByName("avviamento"); amag=eval(listavvio[1].value); va=eval(listavvio[2].value); vp=eval(listavvio[3].value); vperi=Math.max(Math.abs(va),Math.abs(vp)); vapoa=Math.min(Math.abs(va),Math.abs(vp)); wgradi=eval(listavvio[4].value); // contatore=0; idp=document.getElementById("resoconto"); idbisp=document.getElementById("bisresoconto"); idpath=document.getElementById("pathaggiunti"); idbispath=document.getElementById("bispathaggiunti"); idpercorso=document.getElementById("percorso"); // if(xagtb(numerofase,0)){ // nonripeto=false; idbisp.innerHTML=fatabella(1500,vapoa,vperi,30); // Punti della traiettoria vtra=fatraiettoria( amag,vapoa,vperi,60,xor,yor,wgradi,-0.2,false); vtrb=fatraiettoria( amag,vapoa,vperi,60,xor,yor,wgradi,0.2,false); vtrf=fondetra(vtra,vtrb); // // A cerchietti // // idpercorso.setAttribute("d",traiettoriacerchi(vtra,8)); // // A linee continue... if(casuale%2==0){ ptra=fatraiettoriaq(vtrf,1/4); dd=fastringatonda(ptra); idbispath.setAttribute("d","M "+vtrf[1][1]+" "+vtrf[1][2]+dd); } else { idbispath.setAttribute("d",traiettoriasegmenti( vtra,2,"M "+vtrf[1][1]+" "+vtrf[1][2]+" L ")); } casuale++; numerofase=0; // } else{ if(numerofase==0) pochecose(); idp.innerHTML="Fase "+numerofase; numerofase++; return numerofase; } // idpath.setAttribute("d","M 1000 1000 L 100 100"); setTimeout("ripeto()",200); // } // // Per vedere come va all'inizio della simulazione del moto... // function pochecose(){ var vtra,obj; if(xagtb(casuale,4))casuale=Math.round(2.1*Math.random()); obj=document.getElementById("tipotraiettoria"); if(casuale%2==0) obj.innerHTML="Usa una traiettoria ad archi di parabola"; else obj.innerHTML="Non usa parabole ma una spezzata per la traiettoria"; vtra=fatraiettoria(amag,vapoa,vperi,60,xor,yor,wgradi,0,false); idpercorso.setAttribute("d",traiettoriacerchi(vtra,8)); nonripeto=true; ripeto(); return true; } // // Questa e' la funzione che si riattiva continuamente // per fare la simulazione del moto dell'asteroide... // function ripeto(){ var radianti,Periodo,ecce,sn,cs,rr,xfuo,xnfuo,t,x,y,xy,ss; rr=fapprossimato(0,amag,vapoa,vperi); Periodo=rr[4]; ecce=rr[5]; with(Math){ radianti=PI*contatore/90; t=Periodo*radianti/4; xfuo=xor-ecce*amag; xnfuo=xor+ecce*amag; rr=fapprossimato(t,amag,vapoa,vperi); // // Fa in modo che x,y siano il centro tradizionale // ossia il punto equidistante dai due fuochi. // x=rr[2]+ecce*amag; y=rr[3]; xy=giragira(x,y,wgradi); x=xy[1]; y=xy[2]; xy=giragira(ecce*amag,0,wgradi); xfuo=xor+xy[1]; xnfuo=xor-xy[1]; ss=" M "+xnfuo+" "+(yor-xy[2])+" L "+(x+xor)+ " "+(y+yor)+cerchietto(x+xor,y+yor,20)+ " L "+xfuo+" "+(yor+xy[2])+cerchietto(xfuo,yor+xy[2],60)+ cerchietto(xfuo,yor+xy[2],64); idpath.setAttribute("d",ss); vxmin=min(x,vxmin); vxmax=max(x,vxmax); idp.innerHTML=" "+vxmin+" "+vxmax+" "+contatore+") x="+x+" y="+y; // } if(nonripeto) return false; contatore=0.2+contatore; setTimeout("ripeto()",100); } // // Variabili globali // var casuale=0; // per disegnare la traiettoria con parabole o segmenti. var vperi=40; var vapoa=8; var amag=1000; var xor=2000,yor=1000,wgradi=45; var nonripeto=true; var vxmin=1000000;vxmax=-100000; //Se Javascript funziona questi script vengono trascritti automaticamente e resi operativi...
Come ampliare un documento arricchendolo dinamicamente con sorgenti Javascript.
Trascrivo qui le funzioni che innescano la trasformazione del documento e dunque devono essere già installate fin dall'inizio. Ma meritano di essere riutilizzate in tante altre occasioni.
// //Questa e' la funzione fondamentale per ampliare il documento... // function ampliajs() { var j,scritti,nn=0,ss=""; var armadio=document.getElementsByTagName("head")[0]; var nuovojs=document.createElement("script"); nuovojs.setAttribute("type","text/javascript"); if(arguments.length>0) nuovojs.setAttribute('src',arguments[0]); else{ scritti=document.getElementsByName("xscript"); nn=scritti.length; for(j=0;nn>j;j++)ss+=scritti[j].innerHTML; nuovojs.innerHTML=ss; } armadio.appendChild(nuovojs); return nn; } // // Queste due funzioni servono per evitare di dovere usare // nei sorgenti Javascript visualizzati come normali // testi HTML, certi caratteri SPECIALI per l'HTML // come il < e il > // function xagtb(a,b){ return (a>b); } function xageb(a,b){ return (a>=b); } //