#!/usr/bin/env python3
from tkinter import *
from numpy import *
from struct import *
import getopt
import sys
from tkinter import filedialog as fd
from tkinter import messagebox as ms
#####################################################################
#
# rimbalzi.py 2022.06.08 Sergio Steffe' Pisa
# port in pyton del programma rimbalzi per Mac in Tthink-C del 1993,
# scritto come esercizio di python del corso di
# Algoritmi di Spettrografia del Prof. Giovanni Moruzzi 2021/22.
# 
#
####################################################################
# versioni:
# versione 1.0:    2022.06.08 versione iniziale
# versione 1.1:    2022.06.16
#   pulizia dal software delle righe usate per il debugging
#   aggiunti tasti SaveAll e LoadAll, aggiornato help
#   aggiunta scalatura dei dati dei files tra -s e -b e normale
#   aggiunte finestre apertura files e segnalazione errori io files
#   aggiunta logica per disabilitare/abilitare i bottoni
#   il Fast puo' dare lampeggiamenti del disegno sul canvas  
# versione 1.2:    2022.06.22
#   corretto errore in riga 580 di calcola: era scritto denom=yyas*xxs invece di +
#   essendo un fattore di normalizzazione saltavano fuori errori solo nelle rare
#   condizioni in cui veniva denom=0 !
# versione 1.3:     2022.07.01
#   corretto misprints qui e li, ingranditi i punti di ancora 
# versione 1.3c:    2022.07.01
#   versione per cellulari: effetto ottico per confermare il grab e punti piu' grossi.
#
###################################################################
# rimbalzi accetta i flag -s (small) -b (big) -S (Slow) -F (Fast) -v (versione) e -h (help)
#
def main(argv):
 global big,small,Slow,Fast
 try:
  opts,args = getopt.getopt(argv,"bhsSFv")
 except getopt.GetoptError:
  print ('rimbalzi:\n\
   unknown option !\n\
   -b  big\n\
   -s  small\n\
   -S  Slow\n\
   -F  Fast\n\
   -v  version\n\
   -h  help\n')
  exit(1)
 big=False
 small=False
 Slow=False
 Fast=False
 for opt,arg in opts:
  if opt == '-h':
   print ('rimbalzi:\n\
   -b  big\n\
   -s  small\n\
   -S  Slow\n\
   -F  Fast\n\
   -v  version\n\
   -h  help\n')
   exit()
  elif opt in ('-b'):
   big=True
  elif opt in ('-s'):
   small=True
  elif opt in ('-S'):
   Slow=True
  elif opt in ('-F'):
   Fast=True
  elif opt in ('-v'):
   print('rimbalzi 1.3 2022.07.01\n')
   exit()
#----------------------------------------
if __name__ == "__main__":
   main(sys.argv[1:])
#---------------------------------------
#
# senza la parte con le chiamate a getopt occorre definire tra le globali anche
#        big=False/True e small=False/True e Slow=False/True
#
# globali
RunAll=True  
IsSetup=True # nel setup si possono modificare le posizioni
RunMotion=False # nel runmotion non si possono modificare le posizioni
GetData=False  # per inserimento diretto numeri nelle finestre
CenterGrabbed=RadiusGrabbed=PointGrabbed=VectorGrabbed=False # per modificare graficamente le posizioni
IsLoadedall=False # i dati e le traiettorie sono stati caricati da file.
IsLoaded=False # i dati iniziali sono stati caricati da file
# due canvas e una zona comandi e finestre dati
# canvas cerchi
# quadrato di lato cl; il centro e' Ox,Oy; il cerchio fisso ha lato CR=cl/2
# se x,y sono coordinate naturali, vanno disegnate in Ox+x,Oy-y ! e analogamente vanno convertiti gli eventi.
#
cl=800	 #  per canvas cerchi, quadrata, di lato cl
if big:
 cl=1200
if small:
 cl=400
CR=cl/2  #  raggio cerchio fisso inscritto 
Ox=400   #  centro del canvasc
Oy=400   #  centro del canvasc
if big:
 Ox=600
 Oy=600
if small:
 Ox=200
 Oy=200
c1=cl/5  # lunghezza vettore iniziale
cp=5	 # raggio punti degli handle
# per canvas fasi : due quadrati 400x400 per rappresentare 0~180 x -90~+90 posizione e angolo rimbalzo.
fw=400
fh=800  #fh=cl
#
if big:
 fw=600
 fh=1200 #fh=cl
if small:
 fw=200
 fh=400 #fh=cl
#----------------------
Cr=cl/4		#raggio cerchio interno
Ca=cl/8		#centro cerchio interno
X0=x0=-cl/4	#punto iniziale (copia fissa e copia operativa)
Y0=y0=-cl/4	#punto iniziale (copia fissa e copia operativa)
xx=0            #variabili per il calcolo del rimbalzo
yy=0            #variabili per il calcolo del rimbalzo
angolo=0
angolov=0
#X0,Y0,VX0,VY0 sono quelli del save e load e appaiono nelle caselle.
#x0,y0,vx0,vy0 sono usati nei calcoli e cambiano man mano, 
# e sono usati da start e restart.
vx0=30          #velocita iniziale
vy0=30          #velocita iniziale
# normalizzazione velocita
vs=sqrt(vx0*vx0+vy0*vy0)
VX0=vx0=vx0/vs
VY0=vy0=vy0/vs
#
rr=[]          #punti di rimbalzo nel canvas cerchi - coppie x,y
ff=[]          #punti di rimbalzo nel canvas fasi - coppie angolo, angolov
rextern=0      # numero di rimbalzi su cerchio esterno 
rintern=0      # numero di rimbalzi su cerchio interno
#
# ............................................................ InRange
def InRange(xmin,x,xmax):
  if x>=xmax:
    return xmax
  if x<=xmin:
    return xmin
  return x
# ................................................. Start/Stop motion
def StartStop():
  global RunMotion,IsSetup,IsLoadedall,IsLoaded
  RunMotion=not RunMotion
  if RunMotion:
    StartButton["text"]="Stop"
  else:
     if IsSetup:
      StartButton["text"]="Start"
     else:
      StartButton["text"]="Restart"
   
# ...................................................... Exit program
def StopAll():
  global RunAll
  RunAll=False
#........................................................
def Setup():
 global IsSetup,rr,ff,RunMotion,x0,y0,xx,yy,vx0,vy0,rr,ff,rextern,rintern,cl,X0,Y0,VX0,VY0,IsLoadedall,IsLoaded
 IsLoaded=False
 IsLoadedall=False
 if RunMotion==False:
  IsSetup=True
  xx=0
  yy=0
  x0=X0 #riinizializzazione valori operativi
  y0=Y0
  vx0=VX0
  vy0=VY0
  rr=[]
  ff=[]
  rextern=0   
  rintern=0
#
  Entryrext.delete(0,END)
  Entryrint.delete(0,END)
  Entryrtot.delete(0,END)
  Entryrrapp.delete(0,END)
#
  StartButton["text"]="Start"
#.......................................................
def Help():
 newWindow=Toplevel(root)
 newWindow.title("Help")
 newWindow.geometry("600x600")
 Label(newWindow,text="Rimbalzi in una corona circolare asimmetrica. v.1.1\n\
\n\
Nella fase di SETUP, usando il mouse (cursore blu) sui punti neri, si puo' spostare\n\
il centro del cerchio interno e cambiarne il raggio e posizionare\n\
il vettore iniziale su cui iniziera' a muoversi un punto.\n\
In alternativa, invece di fissare punto e vettore iniziale, si puo' scegliere\n\
una 'fase' di partenza (cursore rosso) e in tal caso appena si clicca il\n\
programma effettua uno START partendo dal punto della circonferenza scelto e con\n\
l'angolo scelto con la normale al cerchio.(preview in verde mentre si muove il cursore)\n\
All'apertura il programma e' nel modo di SETUP\n\
\n\
Allo START il punto inizia a rimbalzare elasticamente sulle due circonferenze.\n\
Si puo' fermare con STOP e rilanciare con RESTART, ma se si chiama il SETUP\n\
i dati dei rimbalzi vengono cancellati e riinizializzati.\n\
\n\
SAVE salva i dati di partenza (centro e raggio del cerchio interno Ca e Cr, \n\
e posizione e velocita' iniziale del punto, X0,Y0,VX0,VY0) nel file \n\
rimbalzi-data.txt in formato binario, e LOAD li carica da detto file.\n\
\n\
E' possibile durante il SETUP immettere direttamente questi dati da tastiera,\n\
ma occorre dare un ENTER per farli accettare dal programma\n\
\n\
SAVEALL salva tutto nel file rimbalzi-alldata.txt in formato binario,\n\
e LOADALL li carica da detto file. (tasti non disponibili nel formato small (-s))\n\
\n\
Nella parte di sinistra si vedono i cerchi e le traiettorie del punto\n\
in movimento. Nella parte di destra sia per il cerchio EXTerno che per\n\
quello INTerno sono segnati i punti di contatto sul cerchio (tra 0 e 360 gradi)\n\
e l'angolo di rimbalzo rispetto la normale (tra -90 e +90 gradi).\n\
\n\
Questo programma e' il port in python3 con tkinter dell'analogo programma\n\
scritto per il macintosh nel 1993 in Tkink-C.\n\
\n\
Sergio Steffe' Pisa 16/6/2022  ").pack()
#......................................................Save CR,Cr,Ca,X0,Y0,VX0,VY0
def Save():
 global CR,Cr,Ca,X0,Y0,VX0,VY0
 try:
  hnd=fd.asksaveasfile(mode="wb")
  s=pack("7d",CR,Cr,Ca,X0,Y0,VX0,VY0)
  hnd.write(s) 
 except :
   ms.showerror(title='errore',message='non ho potuto salvare i dati')
 #  uuu=sys.exc_info()[0]
 #  ms.showerror(title='errore',message=uuu)
 finally:
   hnd.close()
#.......................................................Load CR,Cr,Ca,X0,Y0,VX0,VY0
def Load():
 global CR,Cr,Ca,x0,y0,vx0,vy0,CR,X0,Y0,VX0,VY0,IsLoaded,rr,ff,rextern,rintern,RunMotion,IsLoaded,IsSetup,IsLoadedall
 filename=fd.askopenfilename()
 if len(filename)==0:
  return None
 try:
  hnd=open(filename,"rb")
  s=hnd.read(7*8)    # 8 bytes ogni numero, 7 numeri
  xin=array(unpack('d'*7, s))
 # ora vanno controllati i limiti per ciascuna variabile:
 # se fuori limiti viene fatta una scelta diversa.
  CRR=xin[0]    # serve per scalare i dati al CR effettivo (che varia a seconda di Small, Medium o Big)
  if CRR==0:
   ms.showerror(title='errore',message='CR=0 ! Dati inconsistenti.')
   return None 
  z=CR/CRR      # fattore di scala: con CR tra 200 e 600 puo' andare da 3 a 1/3
  if (z<0.2)or(z>5):
   ms.showerror(title='errore',message='fattore di scala inconsistente.')
   return None
  Crr=xin[1]*z
  Crrr=InRange(0,Crr,CR*0.98) # non vogliamo cerchi quasi uguali 
  Caa=xin[2]*z
  Ca=InRange(Crrr-CR,Caa,CR-Crrr)
  Cr=InRange(0,Crr,min(CR-Ca,CR+Ca))
  EntryCa.delete(0,END)
  EntryCa.insert(0,'{:.5f}'.format(Ca))
  EntryCr.delete(0,END)
  EntryCr.insert(0,'{:.5f}'.format(Cr))
  x0u=(xin[3])*z
  y0u=(xin[4])*z
# controllare nan error !
  su=sqrt(x0u*x0u+y0u*y0u)
  if (su>1200)or(su<300):
   ms.showerror(title='errore',message='punto iniziale inconsistente.')
   return None
  if su>CR:
     x0u=x0u*CR/su
     y0u=y0u*CR/su
  su=sqrt(x0u*x0u+(y0u-Ca)*(y0u-Ca))
  if su<Cr:
     x0u=x0u*Cr/su
     y0u=Ca+(y0u-Ca)*Cr/su
  X0=x0=x0u
  Y0=y0=y0u
  vx0=xin[5]
  vy0=xin[6]
  su=sqrt(vx0*vx0+vy0*vy0)
  if su==0:
    VX0=vx0=1/sqrt(2)
    VY0=vy0=1/sqrt(2)
    su=1
  else:
   VX0=vx0=vx0/su
   VY0=vy0=vy0/su
   rr=[]
   ff=[]
   rextern=rintern=0
   IsLoaded=True
   IsLoadedall=False
   IsSetup=True
   RunMotion=False
   StartButton["text"]="Start"
 except :
   ms.showerror(title='errore',message='non ho potuto leggere i dati')
 finally :
   hnd.close() 
#....................................................... SaveAll
def SaveAll():
    global CR,Ca,Cr,X0,Y0,VX0,VY0,rr,ff,angolo,angolov,rextern,rintern,x0,y0,vx0,vy0
    #filename=fd.askdirectory()
    #filename=fd.asksaveasfile()
    #hnd=open(filename,"wb")
    try:
      hnd=fd.asksaveasfile(mode="wb")
      s=pack("13d",CR,Cr,Ca,X0,Y0,VX0,VY0,x0,y0,vx0,vy0,angolo,angolov)
      hnd.write(s)
      nr=len(rr)
      nf=len(ff)
      s=pack("4i",rextern,rintern,nr,nf)
      hnd.write(s)
      s=pack(nr*"d",*rr)
      hnd.write(s)
      s=pack(nf*"d",*ff)
      hnd.write(s)
    except:
      ms.showerror(title='errore',message='non ho potuto salvare i dati')
    finally:
      hnd.close()
#....................................................... LoadAll
def LoadAll():
   global CR,Ca,Cr,X0,Y0,VX0,VY0,rr,ff,angolo,angolov,rextern,rintern,x0,y0,vx0,vy0,RunMotion,IsSetup,IsLoadedall,RunMotion
   filename=fd.askopenfilename()
   if len(filename)==0:
      return None
   try: 
    hnd=open(filename,"rb")
    s=hnd.read(13*8)    # 8 bytes ogni numero double real, 13 numeri
    xin=array(unpack('d'*13, s))
    CRR=xin[0]   
    if CRR==0:
      ms.showerror(title='errore',message='CR=0 ! Dati inconsistenti')
      return 
    z=CR/CRR	# fattore di scala.  Supponiamo inoltre che fh=cl cosi vale sia per rr che per ff
#  qui andrebbe controllata la coerenza dei dati 
    Cr=xin[1]*z
    Ca=xin[2]*z
    X0=xin[3]*z
    Y0=xin[4]*z
    VX0=xin[5]
    VY0=xin[6]
    x0 =  xin[7]*z
    y0 =  xin[8]*z
    vx0 = xin[9]
    vy0 = xin[10]
    angolo  = xin[11]
    angolov = xin[12]
    s=hnd.read(4*4)
    xin=array(unpack('i'*4, s))
    rextern = int(xin[0])
    rintern = int(xin[1])
    nr= int(xin[2])
    nf= int(xin[3])
    s=hnd.read(nr*8)
    rr=list(unpack('d'*nr,s))
    s=hnd.read(nf*8)
    ff=list(unpack('d'*nf,s))
    #problema: porebbe capitare angolo=angolov=rextern=rintern=nr=nf=0 se non e' stato fatto un run dopo il setup 
    #
    if nr>=2:
     IsLoadedall=True
     IsSetup=False
     StartButton["text"]="Restart"
     #vanno riscalati rr e ff !
     for i in range(len(ff)):
       ff[i]=ff[i]*z
     for i in range(len(rr)):
       rr[i]=rr[i]*z
    else:
     IsLoaded=True
     IsSetup=True
     StartButton["text"]="Start"
     RunMotion=False
     rr=[]
     ff=[]
     rextern=rintern=angolo=angolov=0
   except:
     ms.showerror(title='errore',message='non ho potuto caricare i dati')
     # contyrollare di non aver sporcato dei dati ...
   finally:
     hnd.close()
# ...................................................... Read entries
def ReadData(*arg):
  global GetData,IsSetup
  if IsSetup:
    GetData=True
#........................................................Grab center
def GrabSome(event):
 global CenterGrabbed,RadiusGrabbed,PointGrabbed,VectorGrabbed,Ox,Oy,Ca,Cr,x0,y0,vx0,vy0,IsSetup,cp
 if IsSetup:
  CenterGrabbed=((Ox-event.x)**2+(Oy-Ca -event.y)**2)<cp*cp
  if not CenterGrabbed:
   RadiusGrabbed=((Ox-event.x+Cr/sqrt(2))**2+(Oy-Ca-event.y+Cr/sqrt(2))**2)<cp*cp
   if not RadiusGrabbed:
    PointGrabbed=((Ox+x0-event.x)**2+(Oy-y0-event.y))**2 <cp*cp
    if not PointGrabbed:
     VectorGrabbed=((Ox+x0-event.x+vx0*c1)**2 + (Oy-y0+-event.y-vy0*c1)**2)<cp*cp
#........................................................Release center
def ReleaseSome(event):
 global CenterGrabbed,RadiusGrabbed,PointGrabbed,VectorGrabbed
 CenterGRabbed=False
 RadiusGrabbed=False
 PointGrabbed=False
 VectorGrabbed=False
 MouseChange=True 
#........................................................
def DragSome(event):
 global CenterGrabbed,RadiusGrabbed,PointGrabbed,VectorGrabbed,Ox,Oy,Ca,Cr,x0,y0,vx0,vy0,c1,X0,Y0,VX0,VY0,IsSetup,CR
 if IsSetup:
  if CenterGrabbed:
   Caa=Oy-event.y
   Ca=InRange(Cr-CR,Caa,CR-Cr)
   EntryCa.delete(0,END)
   EntryCa.insert(0,'{:.5f}'.format(Ca/CR))
  if RadiusGrabbed:
   Crr=sqrt((Ox-event.x)**2+(Oy-event.y-Ca)**2)
   Cr=InRange(0,Crr,min(CR-Ca,CR+Ca))
   EntryCr.delete(0,END)
   EntryCr.insert(0,'{:.5f}'.format(Cr/CR))
  if PointGrabbed:
   x0u=event.x-Ox
   y0u=Oy-event.y
   su=sqrt(x0u*x0u+y0u*y0u)
   if su>CR:
     x0u=x0u*CR/su
     y0u=y0u*CR/su
   su=sqrt(x0u*x0u+(y0u-Ca)*(y0u-Ca))
   if su<Cr:
     x0u=x0u*Cr/su
     y0u=Ca+(y0u-Ca)*Cr/su
   X0=x0=x0u
   Y0=y0=y0u
   Entryx0.delete(0,END)
   Entryx0.insert(0,'{:.5f}'.format(x0/CR))
   Entryy0.delete(0,END)
   Entryy0.insert(0,'{:.5f}'.format(y0/CR))
  if VectorGrabbed:
   evx=InRange(0,event.x,2*Ox)
   evy=InRange(0,event.y,2*Oy)
   vx0=evx-x0-Ox
   vy0=Oy-y0-evy
   c1=sqrt(vx0*vx0+vy0*vy0)
   VX0=vx0=vx0/c1
   VY0=vy0=vy0/c1
   Entryvx0.delete(0,END)
   Entryvx0.insert(0,'{:.5f}'.format(vx0))
   Entryvy0.delete(0,END)
   Entryvy0.insert(0,'{:.5f}'.format(vy0))
#.......................................................
def Clicca(event):
 global x0,y0,vx0,vy0,fw,fh,CR,Cr,Ca,IsSetup,Cinterno,RunMotion,rintern,rextern,X0,Y0,VX0,VY0,CR
 if IsSetup:
 # partenza da punto scelto su canvasf
   ff.append(event.x)
   ff.append(event.y)
   if event.y > fw:
       Cinterno=True
       rintern=1
       rextern=0
       aavv=event.y-fw
       aaff=event.x*2*pi/fw
       aavv=(aavv-fw/2)*pi/fw
       x0=Cr*sin(aaff)
       y0=Ca+Cr*cos(aaff)
       vx0=sin(aaff+aavv)
       vy0=cos(aaff+aavv)
       rintern=1
       rextern=0
   else:
       Cinterno=False
       rintern=0
       rextern=1
       aavv=event.y
       aaff=event.x*2*pi/fw
       aavv=(aavv-fw/2)*pi/fw
       x0=CR*sin(aaff)
       y0=CR*cos(aaff)
       vx0=-sin(aaff+aavv)
       vy0=-cos(aaff+aavv)
   rr.append(Ox+x0)
   rr.append(Oy-y0)
   X0=x0
   Y0=y0
   VX0=vx0
   VY0=vy0
   IsSetup=False
   RunMotion=True
   StartButton["text"]="Stop"
#------------------------------------------------------
def Mostra(event):
   global fw,fh,IsSetup,Cinterno,RunMotion,CR,Ca,Cr,x1,y1,vx1,vy1,cp
   if event.y > fw:
       aavv=event.y-fw
       cinterno=True
   else:
       aavv=event.y
       cinterno=False
   aaff=event.x*360/fw
   aavv=(aavv-fw/2)*180/fw
   Entryposang.delete(0,END)
   Entryposang.insert(0,'{:.5f}'.format(aaff))
   Entryvelang.delete(0,END)
   Entryvelang.insert(0,'{:.5f}'.format(aavv))
#  qui mostro il punto partenza e il vettore nel canvas dei cerchi
#  ma solo nel SETUP:
   if IsSetup:
    if cinterno:
       x1=Ox+Cr*sin(aaff*pi/180)
       y1=Oy-Ca-Cr*cos(aaff*pi/180)
       x2=Ox+(Cr+CR*0.1)*sin(aaff*pi/180)
       y2=Oy-Ca-(Cr+CR*0.1)*cos(aaff*pi/180)
       x3=Ox+Cr*sin(aaff*pi/180)+CR*0.1*sin(aaff*pi/180+aavv*pi/180)
       y3=Oy-Ca-Cr*cos(aaff*pi/180)- CR*0.1*cos(aaff*pi/180+aavv*pi/180)
    else:
       x1=Ox+CR*sin(aaff*pi/180)
       y1=Oy-CR*cos(aaff*pi/180)
       x2=Ox+CR*sin(aaff*pi/180)*0.9
       y2=Oy-CR*cos(aaff*pi/180)*0.9
       x3=Ox+CR*sin(aaff*pi/180)-CR*0.1*sin(aaff*pi/180+aavv*pi/180)
       y3=Oy-CR*cos(aaff*pi/180)+CR*0.1*cos(aaff*pi/180+aavv*pi/180)
#    canvasc.create_oval(x1-cp,y1-cp,x1+cp,y1+cp,fill="green")  evidenzia il punto del cerchio
    canvasc.create_line(x1,y1,x2,y2,fill="red") # perpendicolare
    canvasc.create_line(x1,y1,x3,y3,fill="green",arrow=LAST)
#.......................................................
def calcola():
 global x0,y0,vx0,vy0,CR,Cr,Ca,xx,yy,vxx,vyy,Cinterno,fw,angolo,angolov,rextern,rintern
# posizione iniziale x0,y0, velocita iniziale vx0 vy0
# CR raggio esterno, Cr raggio interno, Ca posizione centro interno
# punto di rimbalzo xx,yy con velocita vxx,vyy
# Cinterno = True se rimbalza sul cerchio interno e non esterno
# Il calcolo del rimbalzo non usa la trigonometria, che serve solo a visualizzare le fasi
#print(x0,y0,vx0,vy0,CR,Cr,Ca)
 vs=sqrt(vx0*vx0+vy0*vy0)
 provv1=(x0*vx0+y0*vy0)/vs
 delta1=provv1*provv1-x0*x0-y0*y0+CR*CR
 t1= -provv1+sqrt(delta1)
 t2= -provv1-sqrt(delta1)
 tm12=max(t1,t2)
 tm=tm12 
# istante contatto circonferenza esterna se non esistesse interna
 alfa=0
 Cinterno=False
#
 provv2=provv1-Ca*vy0/vs
 delta2=provv2*provv2-x0*x0-y0*y0-Ca*Ca+2*y0*Ca+Cr*Cr
 if delta2 > 0 : # la retta interseca la circonferenza interna
  t3=-provv2+sqrt(delta2)
  t4=-provv2-sqrt(delta2)
  if ((t3<0) or (t4<0)) : # ma a tempi negativi - rimbalzo su circonferenza esterna
     alfa=0
     tm=tm12
  else:
    tm34=min(t3,t4)
    if (tm34<0): # accade solo se sto partendo dalla circonferenza interna
      alfa=0
      tm=tm12
    else:
     alfa=Ca
     Cinterno=True
     tm=tm34
 if delta2==0 :
  alfa=0
  tm=tm12
 xx=x0+vx0*tm/vs
 yy=y0+vy0*tm/vs
 yya=yy-alfa
 yyas=yya*yya
 xxs=xx*xx
 denom=yyas+xxs
 vyy=(vy0*(xxs-yyas)-2*vx0*xx*yya)/denom/vs
 vxx=(vx0*(yyas-xxs)-2*vy0*xx*yya)/denom/vs
 denom=sqrt(vxx*vxx+vyy*vyy)
 vxx=vxx/denom
 vyy=vyy/denom
# calcolo fasi : in alto cerchio esterno, in basso cerchio interno
 if Cinterno:
   angolo=arctan2(xx,yy-Ca)
 else:
   angolo=arctan2(xx,yy)
 if angolo <0:
     angolo=angolo+2*pi
 # ora angolo sta tra 0 e 2*pi e rappresenta il punto di rimbalzo. 
 angolov=arctan2(vxx,vyy)-angolo
 angolov=arcsin(sin(angolov))
 # ora angolov sta tra -pi/2 e +pi/2
 # va cambiato segno di angolov esterno (perche esterno punta dentro)
 if Cinterno==False:
   angolov=-angolov
 if Cinterno:
  angolov=(angolov+pi/2)*fw/pi + fw
 else:
  angolov=(angolov+pi/2)*fw/pi 
 angolo=angolo*fw/(2*pi)
# angolov ora sta tra 0 e fw per cerchio esterno, tra fw e 2*fw per cerchio interno
# angolo sta tra 0 e fw
# aggiorno il numero dei rimbalzi fatti 
 if Cinterno:
     rintern=rintern+1
 else:
     rextern=rextern+1
###########################################################################
#
# ................................................ Create root window
root=Tk()
root.title("rimbalzi")
root.resizable(width=False,height=False)
root.bind('<Return>',ReadData) #occorre dare un ENTER per fare leggere i dati
# ......................................... Add canvas to root window
# ..................canvasc per i cerchi  e canvasf per le fasi
canvasc=Canvas(root, width=cl, height=cl, background='white')
canvasc.grid(row=0,column=0)
# ..................................................... mouse buttons
canvasc.bind("<Button-1>",GrabSome)
canvasc.bind("<B1-Motion>",DragSome)
canvasc.bind("<ButtonRelease-1>",ReleaseSome)
#
canvasf=Canvas(root, width=fw, height=fh, background='white')
canvasf.grid(row=0,column=1)
#
canvasf.bind("<Button-1>",Clicca)
canvasf.bind("<Motion>",Mostra)
#
toolbar=Frame(root)
toolbar.grid(row=0,column=2,sticky=N)
# ................................................... Toolbar buttons
SetupButton=Button(toolbar,text="Setup",command=Setup,width=7)
SetupButton.grid(row=0,column=0)
StartButton=Button(toolbar,text="Start",command=StartStop,width=7)
StartButton.grid(row=0,column=1)
SaveButton=Button(toolbar, text="Save", command=Save,width=7)
SaveButton.grid(row=1,column=0)
LoadButton=Button(toolbar, text="Load", command=Load,width=7)
LoadButton.grid(row=1,column=1)
# nella versione small non c'e' spazio per i distanziatori 
if small==False:        
 Labspazio1=Label(toolbar,text="         ")
 Labspazio1.grid(row=2,column=0)
#
# ........................................ Toolbar labels and entries
LabCr=Label(toolbar,text="Cr/CR")
LabCr.grid(row=3,column=0)
EntryCr=Entry(toolbar,bd=5,width=10)
EntryCr.grid(row=3,column=1)
#
LabCa=Label(toolbar,text="Ca/CR")
LabCa.grid(row=4,column=0)
EntryCa=Entry(toolbar,bd=5,width=10)
EntryCa.grid(row=4,column=1)
#
Labx0=Label(toolbar,text="X0/CR")
Labx0.grid(row=5,column=0)
Entryx0=Entry(toolbar,bd=5,width=10)
Entryx0.grid(row=5,column=1)
#
Laby0=Label(toolbar,text="Y0/CR")
Laby0.grid(row=6,column=0)
Entryy0=Entry(toolbar,bd=5,width=10)
Entryy0.grid(row=6,column=1)
#
Labvx0=Label(toolbar,text="VX0")
Labvx0.grid(row=7,column=0)
Entryvx0=Entry(toolbar,bd=5,width=10)
Entryvx0.grid(row=7,column=1)
#
Labvy0=Label(toolbar,text="VY0")
Labvy0.grid(row=8,column=0)
Entryvy0=Entry(toolbar,bd=5,width=10)
Entryvy0.grid(row=8,column=1)
# nella versione small non c'e' spazio per i distanziatori 
if small==False:
 Labspazio2=Label(toolbar,text="         ")
 Labspazio2.grid(row=9,column=0)
#
CloseButton=Button(toolbar, text="Quit", command=StopAll,width=7)
CloseButton.grid(row=10,column=0)
HelpButton=Button(toolbar, text="Help", command=Help,width=7)
HelpButton.grid(row=10,column=1)
# nella versione small non c'e' spazio per i distanziatori 
if small==False:
 Labspazio3=Label(toolbar,text="         ")
 Labspazio3.grid(row=11,column=0)
#
Labrext=Label(toolbar,text="rimb. ext")
Labrext.grid(row=12,column=0)
Entryrext=Entry(toolbar,bd=5,width=10)
Entryrext.grid(row=12,column=1)
Labrint=Label(toolbar,text="rimb. int")
Labrint.grid(row=13,column=0)
Entryrint=Entry(toolbar,bd=5,width=10)
Entryrint.grid(row=13,column=1)
#
Labrtot=Label(toolbar,text="rimb. tot")
Labrtot.grid(row=14,column=0)
Entryrtot=Entry(toolbar,bd=5,width=10)
Entryrtot.grid(row=14,column=1)
Labrrapp=Label(toolbar,text="rimb. int %")
Labrrapp.grid(row=15,column=0)
Entryrrapp=Entry(toolbar,bd=5,width=10)
Entryrrapp.grid(row=15,column=1)
# nella versione small non c'e' spazio per i distanziatori 
if small==False:
 Labspazio4=Label(toolbar,text="         ")
 Labspazio4.grid(row=16,column=0)
#
Labposang=Label(toolbar,text="posiz. angolare")
Labposang.grid(row=17,column=0)
Entryposang=Entry(toolbar,bd=5,width=6)
Entryposang.grid(row=17,column=1)
Labvelang=Label(toolbar,text="angolo rimbalzo")
Labvelang.grid(row=18,column=0)
Entryvelang=Entry(toolbar,bd=5,width=6)
Entryvelang.grid(row=18,column=1)
#
# nella versione small non c'e' spazio per questi bottoni
if small==False:
 Labspazio5=Label(toolbar,text="         ")
 Labspazio5.grid(row=19,column=0)
#
SaveAllButton=Button(toolbar, text="SaveAll", command=SaveAll,width=7)
SaveAllButton.grid(row=20,column=0)
LoadAllButton=Button(toolbar, text="LoadAll", command=LoadAll,width=7)
LoadAllButton.grid(row=20,column=1)
#
# scrittura valori iniziali
EntryCa.insert(0,'{:.5f}'.format(Ca/CR))
EntryCr.insert(0,'{:.5f}'.format(Cr/CR))
Entryx0.insert(0,'{:.5f}'.format(X0/CR))
Entryy0.insert(0,'{:.5f}'.format(Y0/CR))
Entryvx0.insert(0,'{:.5f}'.format(VX0))
Entryvy0.insert(0,'{:.5f}'.format(VY0))
#......................................................... Variables
delay=20 #milliseconds
if Slow:
    delay=200
if Fast:
    delay=5
while RunAll:
# .............................................. Draw  on canvas ---- Main Loop
#
# codiche che abilita o disabilita i bottoni a secondo delle variabili 
  if RunMotion:
  # mentre gira non voglio poter entrare in setup, save, saveall, load, loadall : restano lo stop, lo help e il quit
    SetupButton['state']=DISABLED
    SaveButton['state']=DISABLED
    SaveAllButton['state']=DISABLED
    LoadButton['state']=DISABLED
    LoadAllButton['state']=DISABLED
  else:
    SetupButton['state']=NORMAL
    SaveButton['state']=NORMAL
    SaveAllButton['state']=NORMAL
    LoadButton['state']=NORMAL
    LoadAllButton['state']=NORMAL
   
  if IsLoadedall or IsLoaded:
    canvasf.delete(ALL)
    canvasc.delete(ALL)
#    canvasc.create_oval(Ox-cp,Oy-Ca-cp,Ox+cp,Oy-Ca+cp,fill="black") # centro cerchio interno, mobile
#    canvasc.create_oval(Ox-cp+Cr/sqrt(2),Oy-Ca-cp+Cr/sqrt(2),Ox+cp+Cr/sqrt(2),Oy-Ca+cp+Cr/sqrt(2),fill="black") # maniglia per cambiare raggio
#    canvasc.create_oval(Ox+x0-cp,Oy-y0-cp,Ox+x0+cp,Oy-y0+cp,fill="black") # maniglia per spostare il punto di partenza
#    canvasc.create_oval(Ox+x0-cp+vx0*c1,Oy-y0-cp-vy0*c1,Ox+x0+cp+vx0*c1,Oy-y0+cp-vy0*c1,fill="black") # maniglia per spostare il vettore di partenza
    if IsLoaded:
        canvasc.create_line(Ox+x0,Oy-y0,Ox+x0+vx0*c1,Oy-y0-vy0*c1,arrow=LAST) # freccia del vettore iniziale
    canvasc.config(cursor="target blue") #cursore tondo blu sul canvas dei cerchi
    canvasf.config(cursor="target red")  #cursore tondo rosso sul canvas delle fasi

  if IsSetup:
    canvasf.delete(ALL)
    canvasc.delete(ALL)
    canvasc.create_oval(Ox-cp,Oy-Ca-cp,Ox+cp,Oy-Ca+cp,fill="black") # centro cerchio interno, mobile 
    canvasc.create_oval(Ox-cp+Cr/sqrt(2),Oy-Ca-cp+Cr/sqrt(2),Ox+cp+Cr/sqrt(2),Oy-Ca+cp+Cr/sqrt(2),fill="black") # maniglia per cambiare raggio
    canvasc.create_oval(Ox+x0-cp,Oy-y0-cp,Ox+x0+cp,Oy-y0+cp,fill="black") # maniglia per spostare il punto di partenza  
    canvasc.create_oval(Ox+x0-cp+vx0*c1,Oy-y0-cp-vy0*c1,Ox+x0+cp+vx0*c1,Oy-y0+cp-vy0*c1,fill="black") # maniglia per spostare il vettore di partenza
    canvasc.create_line(Ox+x0,Oy-y0,Ox+x0+vx0*c1,Oy-y0-vy0*c1,arrow=LAST) # freccia del vettore iniziale  
    canvasc.config(cursor="target blue") #cursore tondo blu sul canvas dei cerchi
    canvasf.config(cursor="target red")  #cursore tondo rosso sul canvas delle fasi
    if GetData:         #input dei dati permesso solo nella fase di SETUP
# va controllato che i dati siano consistenti
      try:
        eCa=float(EntryCa.get())
      except ValueError:
        pass
      try:
        eCr=float(EntryCr.get())
      except ValueError:
        pass
      try:
        ex0=float(Entryx0.get())
      except ValueError:
        pass
      try:
        ey0=float(Entryy0.get())
      except ValueError:
        pass
      try:
        evx0=float(Entryvx0.get())
      except ValueError:
        pass
      try:
        evy0=float(Entryvy0.get())
      except ValueError:
        pass
      Ca=InRange(Cr-CR,eCa,CR-Cr)
      Cr=InRange(0,eCr,min(CR-Ca,CR+Ca))
      su=sqrt(ex0*ex0+ey0*ey0)
      if su>CR:
         ex0=ex0*CR/su
         ey0=ey0*CR/su
      su=sqrt(ex0*ex0+(ey0-Ca)*(ey0-Ca))
      if su<Cr:
         ex0=ex0*Cr/su
         ey0=Ca+(ey0-Ca)*Cr/su
      x0=X0=ex0
      y0=Y0=ey0
      if (evx0*evx0+evy0*evy0==0):
          evx0=evy0=1.0
      su=sqrt(evx0*evx0+evy0*evy0)
      vx0=VX0=evx0/su
      vy0=VY0=evy0/su
# ................................ Write variable values into entries
      EntryCa.delete(0,END)
      EntryCa.insert(0,'{:.5f}'.format(Ca/CR))
      EntryCr.delete(0,END)
      EntryCr.insert(0,'{:.5f}'.format(Cr/CR))
      Entryx0.delete(0,END)
      Entryx0.insert(0,'{:.5f}'.format(X0/CR))
      Entryy0.delete(0,END)
      Entryy0.insert(0,'{:.5f}'.format(Y0/CR))
      Entryvx0.delete(0,END)
      Entryvx0.insert(0,'{:.5f}'.format(VX0))
      Entryvy0.delete(0,END)
      Entryvy0.insert(0,'{:.5f}'.format(VY0))
      GetData=False     #fine della fase di letture dei dati 
#
  if RunMotion:
    if IsSetup or IsLoaded:
        IsSetup = False  # se si clicca il cursore rosso durante il setup, IsSetup e RunMotion sono entrambi True!
        IsLoaded = False # anche se si passa a dare Load e poi Start
        rr.append(Ox+x0)
        rr.append(Oy-y0)
    calcola()
    rr.append(Ox+xx)
    rr.append(Oy-yy)
    ff.append(angolo)
    ff.append(angolov)
    x0=xx
    y0=yy
    vx0=vxx
    vy0=vyy
    canvasc.delete(ALL) ##########################################################<<<<<<<<<<<<<<<<<<<<
    canvasc.create_line(rr)
#   questo e' comando che disegna tutte le traiettorie del rimbalzo !
    Entryrext.delete(0,END)
    Entryrext.insert(0,'{:10d}'.format(rextern))
    Entryrint.delete(0,END)
    Entryrint.insert(0,'{:10d}'.format(rintern))
    Entryrtot.delete(0,END)
    Entryrtot.insert(0,'{:10d}'.format(rintern+rextern))
    Entryrrapp.delete(0,END)
    Entryrrapp.insert(0,'{:.8f}'.format(rintern/(rintern+rextern)))
    EntryCa.delete(0,END)
    EntryCa.insert(0,'{:.5f}'.format(Ca/CR))
    EntryCr.delete(0,END)
    EntryCr.insert(0,'{:.5f}'.format(Cr/CR))
    Entryx0.delete(0,END)
    Entryx0.insert(0,'{:.5f}'.format(X0/CR))
    Entryy0.delete(0,END)
    Entryy0.insert(0,'{:.5f}'.format(Y0/CR))
    Entryvx0.delete(0,END)
    Entryvx0.insert(0,'{:.5f}'.format(VX0))
    Entryvy0.delete(0,END)
    Entryvy0.insert(0,'{:.5f}'.format(VY0))
#
    canvasf.delete(ALL)  ##########################################################<<<<<<<<<<<<<<<<<<<<
    i=0
    while i < len(ff):
      canvasf.create_oval(ff[i],ff[i+1],ff[i],ff[i+1]) # sembra sia il solo modo per disegnare un singolo punto in tkinter !
      i=i+2
    canvasf.create_oval(ff[len(ff)-2]-cp,ff[len(ff)-1]-cp,ff[len(ff)-2]+cp,ff[len(ff)-1]+cp,fill="yellow")   #il punto attuale evidenziato in giallo
# ora vanno disegnati i vari elementi geometrici sui due canvas !
#
  canvasc.create_oval(1,1,2*CR,2*CR,outline="red")      # cerchio esterno, fisso
  # tics per il cerchio esterno, ogni 45 gradi
  for i in range(0,8):
      f=i*pi/4
      canvasc.create_line(Ox+CR*cos(f),Oy-CR*sin(f),Ox+CR*cos(f)*0.96,Oy-CR*sin(f)*0.96,fill="red")
  canvasc.create_text(Ox+CR*0.95,Oy,text='90',font=('Times','12', 'italic'))
  canvasc.create_text(Ox,Oy+CR*0.95,text='180',font=('Times','12', 'italic'))
  canvasc.create_text(Ox-CR*0.95,Oy,text='270',font=('Times','12', 'italic'))
  canvasc.create_text(Ox,Oy-CR*0.95,text='0-360',font=('Times','12', 'italic'))
  canvasc.create_oval(Ox-Cr,Oy-Ca-Cr,Ox+Cr,Oy-Ca+Cr,outline="red") # cerchio interno, mobile
  for i in range(0,8):
      f=i*pi/4
      canvasc.create_line(Ox+Cr*cos(f),Oy-Ca-Cr*sin(f),Ox+Cr*cos(f)*1.06,Oy-Ca-Cr*sin(f)*1.06,fill="red")
# canvas delle fasi
  canvasf.create_line(0,fw,fw,fw,fill="red") # separatore tra le due fasi
  canvasf.create_line(1,0,1,2*fw,fill="red")
  canvasf.create_line(fw,0,fw,2*fw,fill="red")
  canvasf.create_line(1,1,fw,1,fill="red")
  canvasf.create_line(1,2*fw,fw,2*fw,fill="red")
  canvasf.create_text(20,20,text='EXT',font=('Times','12', 'italic')) # circonferenza esterna
  canvasf.create_text(20,20+fw,text='INT',font=('Times','12', 'italic')) # circonferenza interna
  # tics e scala per il punto sulle circonferenze
  canvasf.create_text(3*cp,fw-3*cp,text='0',font=('Times','12', 'italic'))
  canvasf.create_line(fw/2,0,fw/2,2*fw,fill="blue") 
  canvasf.create_text(fw/2,fw-3*cp,text='180',font=('Times','12', 'italic'))
  canvasf.create_line(fw/4,0,fw/4,2*fw,fill="blue")
  canvasf.create_text(fw/4,fw-3*cp,text='90',font=('Times','12', 'italic'))
  canvasf.create_line(3*fw/4,0,3*fw/4,2*fw,fill="blue")
  canvasf.create_text(3*fw/4,fw-3*cp,text='270',font=('Times','12', 'italic'))
  canvasf.create_text(fw-4*cp,fw-3*cp,text='360',font=('Times','12', 'italic'))
  # tics e scala per l'angolo di rimbalzo rispetto la normale delle circonferenze
  canvasf.create_line(0,fw/2,fw,fw/2,fill="blue")
  canvasf.create_text(7*cp,fw/2,text='0',font=('Times','12', 'italic'))
  canvasf.create_line(0,fw+fw/2,fw,fw+fw/2,fill="blue")
  canvasf.create_text(7*cp,fw+fw/2,text='0',font=('Times','12', 'italic'))
  #
  canvasf.create_line(0,fw/4,fw,fw/4,fill="blue")
  canvasf.create_text(7*cp,fw/4-2*cp,text='-45',font=('Times','12', 'italic'))
  canvasf.create_line(0,fw+fw/4,fw,fw+fw/4,fill="blue")
  canvasf.create_text(7*cp,fw+fw/4-2*cp,text='-45',font=('Times','12', 'italic'))
  #
  canvasf.create_line(0,3*fw/4,fw,3*fw/4,fill="blue")
  canvasf.create_text(7*cp,3*fw/4-cp,text='+45',font=('Times','12', 'italic'))
  canvasf.create_line(0,fw+3*fw/4,fw,fw+3*fw/4,fill="blue")
  canvasf.create_text(7*cp,fw+3*fw/4-cp,text='+45',font=('Times','12', 'italic'))
  #
  if IsLoaded:
    EntryCa.delete(0,END)
    EntryCa.insert(0,'{:.5f}'.format(Ca/CR))
    EntryCr.delete(0,END)
    EntryCr.insert(0,'{:.5f}'.format(Cr/CR))
    Entryx0.delete(0,END)
    Entryx0.insert(0,'{:.5f}'.format(X0/CR))
    Entryy0.delete(0,END)
    Entryy0.insert(0,'{:.5f}'.format(Y0/CR))
    Entryvx0.delete(0,END)
    Entryvx0.insert(0,'{:.5f}'.format(VX0))
    Entryvy0.delete(0,END)
    Entryvy0.insert(0,'{:.5f}'.format(VY0))

  if IsLoadedall:
       canvasc.create_line(rr)
       i=0
       while i < len(ff):
          canvasf.create_oval(ff[i],ff[i+1],ff[i],ff[i+1]) # sembra sia il solo modo per disegnare un singolo punto in tkinter !
          i=i+2
       canvasf.create_oval(ff[len(ff)-2]-cp,ff[len(ff)-1]-cp,ff[len(ff)-2]+cp,ff[len(ff)-1]+cp,fill="yellow")   #il punto attuale evidenziato in giallo
       Entryrext.delete(0,END)
       Entryrext.insert(0,'{:10d}'.format(rextern))
       Entryrint.delete(0,END)
       Entryrint.insert(0,'{:10d}'.format(rintern))
       Entryrtot.delete(0,END)
       Entryrtot.insert(0,'{:10d}'.format(rintern+rextern))
       Entryrrapp.delete(0,END)
       Entryrrapp.insert(0,'{:.8f}'.format(rintern/(rintern+rextern)))
       EntryCa.delete(0,END)
       EntryCa.insert(0,'{:.25}'.format(Ca/CR))
       EntryCr.delete(0,END)
       EntryCr.insert(0,'{:.25}'.format(Cr/CR))
       Entryx0.delete(0,END)
       Entryx0.insert(0,'{:.25}'.format(X0/CR))
       Entryy0.delete(0,END)
       Entryy0.insert(0,'{:.25}'.format(Y0/CR))
       Entryvx0.delete(0,END)
       Entryvx0.insert(0,'{:.5f}'.format(VX0))
       Entryvy0.delete(0,END)
       Entryvy0.insert(0,'{:.5f}'.format(VY0))
  #
  canvasf.update()
  canvasc.update()
  #................................................. Wait delay time
  canvasc.after(delay)
  canvasf.after(delay)
  #-------------------------
root.destroy()
#
##############################################
# todo:
# reverse motion
# clear trajectories
# clear phases
# colorize trajectories
###############################################
