############################################
#classe vecteur
############################################
class Vecteur():

    #initialise un vecteur 0,0
    def __init__(self):
        self.x=0
        self.y=0

    #rend le vecteur unitaire
    def normalize(self):
        d=self.x*self.x+self.y*self.y
        from math import sqrt
        d=sqrt(d)
        if (d!=0):
            self.x=self.x/d
            self.y=self.y/d

    #multiplie par un scalaire
    def mult(self,coef):
        self.x=self.x*coef
        self.y=self.y*coef

    #fait addition de vecteurs
    def add(self,v):
        res=Vecteur()
        res.x=self.x+v.x
        res.y=self.y+v.y
        return(res)

    #construit un vecteur aleatoire norme 1
    def random(self):
        from random import uniform
        self.x=uniform(-1,1)
        self.y=uniform(-1,1)
        self.normalize()
        
        
        
        
############################################
#agent avec un objectif
############################################
class Agent():


    #construit un agent par defaut
    def __init__(self,dx,dy,coef):
        self.x=dx
        self.y=dy
        self.coeffs=coef

        #vitesse max de l'agent
        self.vMax=20
        
        #vitesse initiale aleatoire
        self.v=Vecteur()
        self.v.random()
        self.v.mult(self.vMax)
        
        #pas d'acceleration
        self.ax=0
        self.ay=0
        self.dt=0.1
   
        #acceleration maximale
        self.aMax=20        

    # fait evoluer un agent
    # moteur physique
    def evoluer(self):
        self.v.x=self.v.x+self.ax*self.dt
        self.v.y=self.v.y+self.ay*self.dt
        self.x=self.x+self.v.x*self.dt
        self.y=self.y+self.v.y*self.dt

        if (self.x<0):
            self.x=0
            self.v.x=-self.v.x
        if (self.x>500):
            self.x=500
            self.v.x=-self.v.x


        if (self.y<0):
            self.y=0
            self.v.y=-self.v.y
        if (self.y>400):
            self.y=400
            self.v.y=-self.v.y
            

    # BIZARRE : par inversement proportionnel à la distance
    # regle de separation
    def separation(self,voisins):
        v=Vecteur()
        for agent in voisins:
            res=Vecteur()
            res.x=agent.x-self.x
            res.y=agent.y-self.y
            res.normalize()
            v=v.add(res)
        v.x=-v.x
        v.y=-v.y
        v.normalize()
        return(v)


    # regle de cohesion
    def cohesion(self,voisins):
        v=Vecteur()
        for agent in voisins:
            v.x+=agent.x
            v.y+=agent.y
        v.x /= len(voisins)
        v.y /= len(voisins)
        res=Vecteur()
        res.x=v.x-self.x
        res.y=v.y-self.y
        res.normalize()
        return(res);       

    # regle d'alignement
    def alignement(self,voisins):
        v=Vecteur()
        for agent in voisins:
            v.x+=agent.v.x
            v.y+=agent.v.y
        v.x /= len(voisins)
        v.y /= len(voisins)
        v.normalize()
        return(v);
        

    # fait du flocking
    # clacule la vitesse de l'agent
    def calculerObj(self,agents):
        
        #liste des voisins
        voisins=[]
        seuil=100
        
        #cherche les voisins
        for agent in agents:
            if (agent!=self) and (agent.distance(self)<seuil):
                voisins+=[agent]

        if (len(voisins)==0):
            return
        

        #applique les regles avec leurs coefficients
        separation=self.separation(voisins)
        separation.mult(self.coeffs.coef_sep)

        alignement=self.alignement(voisins)
        alignement.mult(self.coeffs.coef_align)

        cohesion=self.cohesion(voisins)
        cohesion.mult(self.coeffs.coef_cohesion)

        #ajoute a la vitesse courante
        res=separation.add(alignement).add(cohesion)
        self.v=self.v.add(res)

        #ajoute du bruit
        bruit=Vecteur()
        bruit.random()
        bruit.mult(self.coeffs.coef_bruit)
        self.v=self.v.add(bruit)
        
        #normalise la vitesse resultante
        self.v.normalize()
        self.v.mult(self.vMax)

        
    # calcule la distance entre deux objets        
    def distance(self,O):
        dx=(O.x-self.x)
        dy=(O.y-self.y)
        d2=dx*dx+dy*dy
        from math import sqrt
        return sqrt(d2)


############################################
# manipule les coefficients des regles de flocking
############################################
class Coeff():

    # construit des coefficients par defaut
    def __init__(self):
        self.coef_sep=1
        self.coef_align=1
        self.coef_cohesion=1
        self.coef_bruit=0.5
        self.max=4


        #dessin
        self.vsize=20
        self.debut=410

        self.debutX=400
        self.finX=600
        
    #dessiner un coefficient
    def dessiner_un_coeff(self,val,num,nom,couleur):
        BLACK = (0x00, 0x00, 0x00)
        vsize=self.vsize
        debut=self.debut
        debutX=self.debutX
        ceoffX=(self.finX-self.debutX)/self.max
        font = pygame.font.Font(None, 20)

        pygame.draw.rect(screen,couleur,(0,debut+num*vsize,700,vsize))
        texte=nom+": "+str(val)
        screen.blit(font.render(texte, True, (0,0,0)), (100, debut+num*vsize+5))
        #dessin de la ligne
        pygame.draw.line(screen,BLACK,[debutX,debut+num*vsize+vsize/2],[debutX+val*ceoffX,debut+num*vsize+vsize/2],5)
      

    # dessine les coefficients
    def dessinerCoeffs(self):
        RED = (0xFF, 0x00, 0x00)
        GREEN= (0x00, 0xFF, 0x00)
        PURPLE= (0xFF, 0x00, 0xFF)
        BLUE = (0x00, 0x00, 0xFF)
        
        self.dessiner_un_coeff(self.coef_sep,0,"coef_sep",RED)
        self.dessiner_un_coeff(self.coef_align,1,"coef_align",GREEN)
        self.dessiner_un_coeff(self.coef_cohesion,2,"coef_cohesion",BLUE)
        self.dessiner_un_coeff(self.coef_bruit,3,"coef_bruit",PURPLE)


    #controler un coefficient
    def get_valeur_coeff(self):
        #localisation en x
        valX=pygame.mouse.get_pos()[0]
        valX=self.max*(valX-self.debutX)/(self.finX-self.debutX)

        if (valX>=0) and (valX<=self.max):
            coeff=valX
        else :
            if (valX<0):
                coeff=0
            else:
                coeff=self.max
        return(coeff)
    

    #controle coefficients
    def controler_coeffs(self):
        RED = (0xFF, 0x00, 0x00)
        GREEN= (0x00, 0xFF, 0x00)
        PURPLE= (0xFF, 0x00, 0xFF)
        BLUE = (0x00, 0x00, 0xFF)
        
        #localisation du bandeau
        sourisY = pygame.mouse.get_pos()[1]
        num_bandeau = (sourisY - self.debut)/self.vsize

        if (num_bandeau>4) or (num_bandeau<0):
            return
        
        #recupere valeur
        val=self.get_valeur_coeff()
        nom=""


        if (num_bandeau<=1):
            self.coef_sep=val
            nom="coeff_sep"
        else:
            if (num_bandeau<=2):
                self.coef_align=val
                nom="coeff_align"
            else:
                if (num_bandeau<=3):
                    self.coef_cohesion=val
                    nom="coeff_cohesion"
                else:
                    self.coef_bruit=val
                    nom="coeff_bruit"

        
        
        #affiche
        font = pygame.font.Font(None, 36)
        texte=nom+": "+str(val)
        screen.blit(font.render(texte, True, (255,0,0)), (200, 100))
    



############################################      
# systeme c'est plusieurs agents et un coefficient
############################################
class Systeme():

    #initialise le systeme avec les coordonnees de l'agent
    def __init__(self,num):
        from random import uniform
        self.num=num
        self.agents=[]    
        self.coeffs=Coeff()
    
       
        for i in range(num):
            x=uniform(0,500)
            y=uniform(0,700)
            ag=Agent(x,y,self.coeffs)
            ag.groupe=1
            self.agents+=[ag]
      

    #evoluer le jeu
    def evoluer(self):
        for agent in self.agents:
            agent.calculerObj(self.agents)
            agent.evoluer()
        

    
        
    #dessine les agents
    def dessiner(self):
        WHITE = (0xFF, 0xFF, 0xFF)
        RED = (0xFF, 0x00, 0x00)
        GREEN= (0x00, 0xFF, 0x00)
        PURPLE= (0xFF, 0x00, 0xFF)
        BLUE = (0x00, 0x00, 0xFF)
        BLACK = (0x00, 0x00, 0x00)
        
        screen.fill(WHITE)
        
        #dessine agents
        for agent in self.agents:
            pygame.draw.ellipse(screen,RED,(agent.x-5,agent.y-5,10,10))         
            pygame.draw.line(screen,BLACK,[agent.x,agent.y],[agent.x+agent.v.x,agent.y+agent.v.y])

        #dessin interface graphique des coeffs
        self.coeffs.dessinerCoeffs()
        

############################################
# main tres simple
############################################

print("Flocking simple")



           
import pygame
pygame.init()
size = (700, 500)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Moteur")


# boucle tant que non fini
done = False
mouse=False

#construit un jeu avec 50 agents
jeu=Systeme(20)

# -------- boucle jeu
clock = pygame.time.Clock()
while not done:

    # --- gestion des evenements
    for event in pygame.event.get(): 
        # si on arrete
        if event.type == pygame.QUIT: 
            done = True

        # gere le click de souris
        # passe en mode modifie coefficients
        if event.type == pygame.MOUSEBUTTONDOWN  :
            mouse = True
            ancienY=pygame.mouse.get_pos()[1]

        if event.type == pygame.MOUSEBUTTONUP  :
            mouse = False
            
    # --- mise a jour du jeu
    jeu.evoluer()

    # --- mise a jour dessins
    jeu.dessiner()
    
    # --- si on modifie les coefficients
    if mouse:
        jeu.coeffs.controler_coeffs()
    
  
        
    # --- graphique
    pygame.display.flip()
    
    # --- attente
    clock.tick(60)

pygame.quit()
   

