■
やっぱりどうみても衝突時の計算式が少しあやしい気がする。
左クリックでボールの生成、右ドラッグアンドドロップで衝突用のボールを(ドラッグアンドドロップ中だけ)生成。
下のスライダはフレームの軸回転、ビューの遠さの調整。
#!/usr/bin/env python import Tkinter from math import * class boundBall3D: def __init__(self): self.window = Tkinter.Tk() self.width = 800 self.height = 600 self.canvas = Tkinter.Canvas(self.window,bg="white", width=self.width,height=self.height) self.canvas.bind('<ButtonPress-1>',self.canvasPress1) self.canvas.bind('<ButtonPress-3>',self.canvasPress3) self.canvas.bind('<ButtonRelease-3>',self.canvasRelease3) self.canvas.bind('<Button3-Motion>',self.canvasMotion3) self.canvas.bind('<Motion>',self.canvasMotion) self.canvas.pack() self.xAxisRollAngle = Tkinter.Scale(self.window, from_=0, to=360, orient=Tkinter.HORIZONTAL) self.xAxisRollAngle.bind("<ButtonPress-1>",self.angleChanged) self.xAxisRollAngle.bind("<ButtonRelease-1>",self.angleChanged) self.xAxisRollAngle.bind("<B1-Motion>",self.angleChanged) self.xAxisRollAngle.pack(side=Tkinter.LEFT); self.yAxisRollAngle = Tkinter.Scale(self.window, from_=0, to=360, orient=Tkinter.HORIZONTAL) self.yAxisRollAngle.bind("<ButtonPress-1>",self.angleChanged) self.yAxisRollAngle.bind("<ButtonRelease-1>",self.angleChanged) self.yAxisRollAngle.bind("<B1-Motion>",self.angleChanged) self.yAxisRollAngle.pack(side=Tkinter.LEFT); self.zAxisRollAngle = Tkinter.Scale(self.window, from_=0, to=360, orient=Tkinter.HORIZONTAL) self.zAxisRollAngle.bind("<ButtonRelease-1>",self.angleChanged) self.zAxisRollAngle.bind("<ButtonPress-1>",self.angleChanged) self.zAxisRollAngle.bind("<B1-Motion>",self.angleChanged) self.zAxisRollAngle.pack(side=Tkinter.LEFT); self.viewPoint = Tkinter.Scale(self.window, from_=10, to=1000, orient=Tkinter.HORIZONTAL) self.viewPoint.bind("<ButtonRelease-1>",self.angleChanged) self.viewPoint.bind("<ButtonPress-1>",self.angleChanged) self.viewPoint.bind("<B1-Motion>",self.angleChanged) self.viewPoint.set(100) self.viewPoint.pack(side=Tkinter.LEFT); self.objects = [] self.createObject(['line',-200,200,-200,200,200,-200]) self.createObject(['line',200,200,-200,200,-200,-200]) self.createObject(['line',200,-200,-200,-200,-200,-200]) self.createObject(['line',-200,-200,-200,-200,200,-200]) self.createObject(['line',-200,200,-200,-200,200,200,]) self.createObject(['line',200,200,-200,200,200,200,]) self.createObject(['line',200,-200,-200,200,-200,200,]) self.createObject(['line',-200,-200,-200,-200,-200,200,]) self.createObject(['line',-200,200,200,200,200,200]) self.createObject(['line',200,200,200,200,-200,200]) self.createObject(['line',200,-200,200,-200,-200,200]) self.createObject(['line',-200,-200,200,-200,200,200]) self.moving = 0 self.dragObject = None self.window.mainloop() def calcInterval(self,x1,y1,z1,x2,y2,z2): return pow(fabs(pow(x1-x2,3))+fabs(pow(y1-y2,3))+fabs(pow(z1-z2,3)),1/3.0) def conflictSpheres(self,sphere): return [o for o in self.objects if o['itemType']=='sphere' and self.calcInterval(sphere['coords'][0], sphere['coords'][1], sphere['coords'][2], o['coords'][0], o['coords'][1], o['coords'][2], ) <= sphere['coords'][3]+o['coords'][3] and sphere['id']!=o['id']] def calcConflict(self,x1,y1,vx1,vy1,x2,y2,vx2,vy2): v2rad = atan2(vy2,vx2) v2size = pow(pow(x2,2)+pow(y2,2),0.5) rad2to1 = atan2(y1-y2,x1-x2) rad = v2rad - rad2to1 vcos = pow(cos(rad),2) vx,vy = cos(rad2to1)*vcos,sin(rad2to1)*vcos return vx,vy def afterImageEffect(self,obj,color): if color<=0: self.canvas.delete(obj['id']) self.objects.remove(obj) return c = '#%02x%02x%02x'%(255,255-color,255-color) self.canvas.itemconfigure(obj['id'],fill=c,outline=c) self.window.after(100,self.afterImageEffect,obj,color-64) def moveObjects(self): for obj in [o for o in self.objects if 'vector' in o]: if self.dragObject!=None and obj['id']==self.dragObject['id']: continue if obj['itemType']=='sphere': [x,y,z,r],[vx,vy,vz] = obj['coords'],obj['vector'] shadowId = self.createObject(['shadow',x,y,z,r]) self.afterImageEffect(shadowId,255) x,y,z = x+vx,y+vy,z+vz if x < -200+r or x > 200-r: vx = -vx if y < -200+r or y > 200-r: vy = -vy if z < -200+r or z > 200-r: vz = -vz obj['coords'] = [x,y,z,r] obj['vector'] = [vx,vy,vz] for conflict in self.conflictSpheres(obj): [bx,by,bz,br],[vbx,vby,vbz] = conflict['coords'],conflict['vector'] cvx1,cvx2 = self.calcConflict(y,z,vy,vz,by,bz,vby,vbz) cvy1,cvy2 = self.calcConflict(x,z,vx,vz,bx,bz,vbx,vbz) cvz1,cvz2 = self.calcConflict(x,y,vx,vy,bx,by,vbx,vby) obj['vector'] = [vx+(cvy1+cvz1),vy+(cvx1+cvz2),vz+(cvx2+cvy2)] conflict['vector'] = [vbx-(cvy1+cvz1),vby-(cvx1+cvz2),vbz-(cvx2+cvy2)] itemPositions = self.calcObjectPosition(obj) self.canvas.coords(obj['id'],*itemPositions) self.window.after(100,self.moveObjects) def canvasPress1(self,event): if self.moving==0: self.moveObjects() self.moving = 1 (x,y,z) = event.x,event.y,0 (x,y,z) = self.cnvMap(x,y) (x,y,z) = self.roll3DbySettingReverse(x,y,z) r = 10 if -200+r < x < 200-r and -200+r < y < 200-r and -200+r < z < 200-r: obj = self.createObject(['sphere',x,y,z,r]) def canvasPress3(self,event): (x,y,z) = self.roll3DbySettingReverse(*self.cnvMap(event.x,event.y)) self.dragObject = self.createObject(['sphere',x,y,z,10]) def canvasMotion3(self,event): (x,y,z) = self.roll3DbySettingReverse(*self.cnvMap(event.x,event.y)) self.dragObject['coords'][0] = x self.dragObject['coords'][1] = y self.dragObject['coords'][2] = z itemPositions = self.calcObjectPosition(self.dragObject) self.canvas.coords(self.dragObject['id'],*itemPositions) def canvasRelease3(self,event): self.canvas.delete(self.dragObject['id']) self.objects.remove(self.dragObject) self.dragObject = None def canvasMotion(self,event): # (x,y,z) = self.roll3DbySettingReverse(*self.cnvMap(event.x,event.y)) # print x,y,z pass def roll(self,x,y,degree): (orgX,orgY) = (x,y) orgR = sqrt(pow(orgX,2)+pow(orgY,2)) if orgR==0: return (x,y) orgTheta = atan2(orgY/orgR,orgX/orgR) theta = radians(degree)+orgTheta x = cos(theta)*orgR y = sin(theta)*orgR return (x,y) def calcObjectPosition(self,object): if object['itemType']=='line': item,itemType,[sx,sy,sz,ex,ey,ez] = object['id'],object['itemType'],object['coords'] (rsx,rsy,rsz) = self.roll3DbySetting(sx,sy,sz) (rex,rey,rez) = self.roll3DbySetting(ex,ey,ez) (lineSx,lineSy) = self.cnvCanvas(rsx,rsy,rsz) (lineEx,lineEy) = self.cnvCanvas(rex,rey,rez) return (lineSx,lineSy,lineEx,lineEy) if object['itemType']=='sphere' or object['itemType']=='shadow': item,itemType,[x,y,z,r] = object['id'],object['itemType'],object['coords'] (x,y,z) = self.roll3DbySetting(x,y,z) sx,sy =self.cnvCanvas(x,y,z) return (sx-r,sy-r,sx+r,sy+r) def angleChanged(self,event): for ii in range(len(self.objects)): itemPositions = self.calcObjectPosition(self.objects[ii]) self.canvas.coords(self.objects[ii]['id'],*itemPositions) def roll3DbySetting(self,x,y,z): x,y = self.roll(x,y,self.zAxisRollAngle.get()) y,z = self.roll(y,z,self.xAxisRollAngle.get()) x,z = self.roll(x,z,self.yAxisRollAngle.get()) return (x,y,z) def roll3DbySettingReverse(self,x,y,z): x,z = self.roll(x,z,-self.yAxisRollAngle.get()) y,z = self.roll(y,z,-self.xAxisRollAngle.get()) x,y = self.roll(x,y,-self.zAxisRollAngle.get()) return (x,y,z) def cnvMap(self,x,y): return (x-self.width/2,(y-self.height/2)*-1,0) def cnvCanvas(self,x,y,z): interval = self.viewPoint.get()*100 / (self.viewPoint.get()*100 + float(z)) x = x*interval y = y*interval return (x+self.width/2,(y-self.height/2)*-1) def createObject(self,item): if item[0]=='line': (itemType,sx,sy,sz,ex,ey,ez) = (item[0], int(item[1]),int(item[2]),int(item[3]), int(item[4]),int(item[5]),int(item[6])) (lineSx,lineSy,lineEx,lineEy) = self.calcObjectPosition( dict(id=-1,itemType=itemType,coords=[sx,sy,sz,ex,ey,ez])) line = self.canvas.create_line(lineSx,lineSy, lineEx,lineEy, fill="red",width=3) obj = dict(id=line,itemType=itemType,coords=[sx,sy,sz,ex,ey,ez],vector=[0,0,0]) self.objects.append(obj) return obj elif item[0]=='sphere' or item[0]=='shadow': (itemType,x,y,z,r) = (item[0],int(item[1]),int(item[2]),int(item[3]),int(item[4])) (x1,y1,x2,y2) = self.calcObjectPosition( dict(id=-1,itemType=itemType,coords=[x,y,z,r])) sphere = self.canvas.create_oval(x1,y1,x2,y2,fill='red',outline='red') obj = dict(id=sphere,itemType=itemType,coords=[x,y,z,r],vector=[0,0,0]) self.objects.append(obj) return obj else: pass pass if __name__ == '__main__': boundBall3D()