衝突シミュレーション。

左クリックでボール生成、ボールを右ドラッグアンドドロップで動かせる。
ボールがぶつかるとその時のスピードで動き出す。


…計算式が適当なので真面目に勉強しようと思ったが挫折した。

#!/usr/bin/env python

import Tkinter
from math import *

class boundBall:
    def __init__(self):
        self.width = 800
        self.height = 600
        self.window = Tkinter.Tk()
        self.canvas = Tkinter.Canvas(self.window,bg='white',
                                     width=self.width,height=self.height)
        self.canvas.bind("<ButtonPress-1>",self.press1Canvas)
        self.canvas.bind('<ButtonPress-3>',self.press3Canvas)
        self.canvas.bind('<ButtonRelease-3>',self.releas3Canvas)
        self.canvas.bind('<Button3-Motion>',self.motion3Canvas)
        self.canvas.pack()

        self.circleR = 10
        self.objects = []
        self.dragObject = None
        
        self.window.mainloop()

    def press3Canvas(self,event):
        object = self.findObject(event.x,event.y)
        if object==None:
            return
        self.dragObject = object

    def releas3Canvas(self,event):
        self.dragObject['vector'] = [0,0]
        self.dragObject = None

    def motion3Canvas(self,event):
        if self.dragObject==None:
            return
        x,y = event.x,event.y
        circleR = self.circleR
        self.canvas.coords(self.dragObject['id'],x-circleR,y-circleR,x+circleR,y+circleR)
        self.dragObject['vector'] = [x-self.dragObject['center']['x'],
                                     y-self.dragObject['center']['y']]
        self.dragObject['center']['x'] = x
        self.dragObject['center']['y'] = y
        for obj in self.findConflictObjects(self.dragObject):
            self.conflictObject(obj,self.dragObject)

    def press1Canvas(self,event):
        object = self.findObject(event.x,event.y)
        if object==None:
            circleR = self.circleR
            obj = {'id':self.canvas.create_oval(event.x-circleR,event.y-circleR,
                                                event.x+circleR,event.y+circleR,
                                                fill='red',outline='red'),
                   'center':{'x':event.x,'y':event.y},
                   'vector':[0,0]}
            self.moveObject(obj)
            self.objects.append(obj)
        else:
            return

    def calcInterval(self,x1,y1,x2,y2):
        return pow(pow(x1-x2,2)+pow(y1-y2,2),0.5)

    def calcAngle(self,x1,y1,x2,y2):
        return atan2(y2-y1,x2-x1)

    def findObject(self,x,y):
        for object in self.objects:
            d = self.calcInterval(x,y,object['center']['x'],object['center']['y'])
            if d<=self.circleR:
                return object

    def calcObjectInterval(self,objA,objB):
        return self.calcInterval(objA['center']['x'],objA['center']['y'],
                                 objB['center']['x'],objB['center']['y'])

    def findConflictObjects(self,object):
        return [o for o in self.objects
                if self.calcObjectInterval(o,object) <= self.circleR*2
                and o['id']!=object['id']]

    def conflictObject(self,objA,objB):
        vbx,vby = objB['vector']
        rad1 = atan2(vby,vbx)
        vs = pow(pow(vbx,2)+pow(vby,2),0.5)
        rad2 = self.calcAngle(objB['center']['x'],objB['center']['y'],
                              objA['center']['x'],objA['center']['y'])
        rad3 = rad1-rad2
        vcos = pow(cos(rad3),2)
        vx,vy = cos(rad2)*vcos,sin(rad2)*vcos
        objA['vector'] = self.addVector(objA['vector'],[vx,vy])
        objB['vector'] = self.addVector(objB['vector'],self.multiplyVector([vx,vy],-1))

    def multiplyVector(self,v,n):
        return [i*n for i in v]

    def addVector(self,v1,v2):
        v = []
        for ii in range(len(v1)):
            v.append(v1[ii]+v2[ii])
        return v

    def afterImageEffect(self,id,color):
        if color<=0:
            self.canvas.delete(id)
            return
        c = '#%02x%02x%02x'%(255,255-color,255-color)
        self.canvas.itemconfigure(id,fill=c,outline=c)
        self.window.after(100,self.afterImageEffect,id,color-64)

    def moveObject(self,obj):
        id,x,y,[vx,vy] = obj['id'],obj['center']['x'],obj['center']['y'],obj['vector']
        if self.dragObject!=None and self.dragObject['id']==id:
            return
        circleR = self.circleR
        color = '#%02x%02x%02x'%(255,0,0)
        afterImageId = self.canvas.create_oval(x-circleR,y-circleR,x+circleR,y+circleR,
                                               fill=color,
                                               outline=color)
        self.afterImageEffect(afterImageId,255)
        obj['vector'] = [vx,vy]
        x,y = x+vx,y+vy
        self.canvas.coords(id,x-circleR,y-circleR,x+circleR,y+circleR)
        obj['center']['x'],obj['center']['y'] = x,y

        for o in self.findConflictObjects(obj):
            self.conflictObject(o,obj)
        if x < 0 or x > self.width:
            obj['vector'] = -vx,vy
        if y < 0 or y > self.height:
            obj['vector'] = vx,-vy
        self.window.after(100,self.moveObject,obj)


if __name__ == '__main__':
    boundBall()