#author("2021-12-05T13:24:18+09:00","default:Real2Virtual202111","Real2Virtual202111") #author("2021-12-06T13:19:16+09:00","default:Real2Virtual202111","Real2Virtual202111") [[Real2Virtual202111]] * real2virtual_ex02.py [#h05e60ed] - このライブラリを使わせていただいています。感謝します。 -- https://sites.google.com/site/3dprogramminginpython/ #code(Python){{ import math from tkinter import * import tkinter.font as tkFont import matrix import copy import threading import re import socket import time import readchar from Cube import Cube class Space: ''' Coordiante-system: Right-handed, Matrices are column-major ones ''' WIDTH = 1280.0 HEIGHT = 960.0 SPEED = 0.05 EPSILON = 1.0e-8 ZERO = EPSILON def __init__(self): print("start Space.__init__") self.root = Tk() self.root.resizable(False, False) self.root.title('3D') #self.chat_client=chat_client left = (self.root.winfo_screenwidth() - Space.WIDTH) / 2 top = (self.root.winfo_screenheight() - Space.HEIGHT) / 2 self.root.geometry('%dx%d+%d+%d' % (Space.WIDTH, Space.HEIGHT, left, top)) self.graph = Canvas(self.root, width=Space.WIDTH, height=Space.HEIGHT, background='black') self.graph.pack() self.walls = [matrix.Vector3D(7.0, 0.0, 0.0), matrix.Vector3D(-7.0, 0.0, 0.0), matrix.Vector3D(0.0, 7.0, 0.0), matrix.Vector3D(0.0, -7.0, 0.0), matrix.Vector3D(0.0, 0.0, 7.0), matrix.Vector3D(0.0, 0.0, -7.0)] self.wallsnormals = [matrix.Vector3D(-1.0, 0.0, 0.0), matrix.Vector3D(1.0, 0.0, 0.0), matrix.Vector3D(0.0, -1.0, 0.0), matrix.Vector3D(0.0, 1.0, 0.0), matrix.Vector3D(0.0, 0.0, -1.0), matrix.Vector3D(0.0, 0.0, 1.0)] ################# # self.cubenormals = [] # for i in range(len(self.cubefaces)): # poly = [] # for j in range(len(self.cubefaces[i])): # poly.append(self.cube[self.cubefaces[i][j]]) # self.cubenormals.append(self.calcNormalVec(poly)) ################# self.ang = [0.0, 0.0, 0.0] # phi(x), theta(y), psi(z) of the camera self.trans = [0.0, 0.0, 2.0] # translation (x, y, z) of the camera #The matrices (Scale, Shear, Rotate, Translate) apply to the View/Camera #The Scale Matrix self.Scale = matrix.Matrix(4, 4) Scalex = 1.0 Scaley = 1.0 Scalez = 1.0 self.Scale[(0,0)] = Scalex self.Scale[(1,1)] = Scaley self.Scale[(2,2)] = Scalez #The Shear Matrix self.Shearxy = matrix.Matrix(4, 4) self.Shearxy[(0,2)] = 0.0 self.Shearxy[(1,2)] = 0.0 self.Shearxz = matrix.Matrix(4, 4) self.Shearxz[(0,1)] = 0.0 self.Shearxz[(2,1)] = 0.0 self.Shearyz = matrix.Matrix(4, 4) self.Shearyz[(1,0)] = 0.0 self.Shearyz[(2,0)] = 0.0 self.Shear = self.Shearxy*self.Shearxz*self.Shearyz #The Rotation Matrices self.Rotx = matrix.Matrix(4,4) self.Roty = matrix.Matrix(4,4) self.Rotz = matrix.Matrix(4,4) #The Translation Matrix (will contain xoffset, yoffset, zoffset) self.Tr = matrix.Matrix(4, 4) self.Tr[(0,3)] = self.trans[0] self.Tr[(1,3)] = self.trans[1] self.Tr[(2,3)] = self.trans[2] #The Projection Matrix self.Proj = matrix.Matrix(4, 4) #foc controls how much of the screen is viewed fov = 60.0 #between 30 and 90 ? zfar = 100.0 znear = 0.1 S = 1/(math.tan(math.radians(fov/2))) A = Space.WIDTH/Space.HEIGHT self.Proj[(0,0)] = S/A self.Proj[(1,1)] = S self.Proj[(2,2)] = (zfar+znear)/(znear-zfar) self.Proj[(3,2)] = -1.0 self.Proj[(2,3)] = 2*(zfar*znear)/(znear-zfar) # self.Proj[(3,3)] = 0.0 #ToScreen Matrix self.toSC = matrix.Matrix(4, 4) self.toSC[(0,0)] = Space.WIDTH/2 self.toSC[(1,1)] = -Space.HEIGHT/2 self.toSC[(0,3)] = Space.WIDTH/2 self.toSC[(1,3)] = Space.HEIGHT/2 # self.root.bind("<B1-Motion>", self.dragcallback) # self.root.bind("<ButtonRelease-1>", self.releasecallback) self.root.bind("<Key>", self.keycallback) # self.root.bind("<KeyRelease>", self.keyreleasecallback) self.fnt = tkFont.Font(family='Helvetica', size=12, weight='bold') cube1=Cube(self) cube1.set_name("s*") self.cube1=cube1 self.polygons_3D=[cube1] self.update(self.polygons_3D) #self.client_start() #self.mainloop() def start_3d(self): self.root.mainloop() def lookUpCube(self,name): for i in range(len(self.polygons_3D)): cx=self.polygons_3D[i] if name==cx.name : return cx return None def update(self,polygons_3D): # Main simulation loop. self.graph.delete(ALL) self.Rotx[(1,1)] = math.cos(math.radians(360.0-self.ang[0])) self.Rotx[(1,2)] = -math.sin(math.radians(360.0-self.ang[0])) self.Rotx[(2,1)] = math.sin(math.radians(360.0-self.ang[0])) self.Rotx[(2,2)] = math.cos(math.radians(360.0-self.ang[0])) self.Roty[(0,0)] = math.cos(math.radians(360.0-self.ang[1])) self.Roty[(0,2)] = math.sin(math.radians(360.0-self.ang[1])) self.Roty[(2,0)] = -math.sin(math.radians(360.0-self.ang[1])) self.Roty[(2,2)] = math.cos(math.radians(360.0-self.ang[1])) self.Rotz[(0,0)] = math.cos(math.radians(360.0-self.ang[2])) self.Rotz[(0,1)] = -math.sin(math.radians(360.0-self.ang[2])) self.Rotz[(1,0)] = math.sin(math.radians(360.0-self.ang[2])) self.Rotz[(1,1)] = math.cos(math.radians(360.0-self.ang[2])) #The Rotation matrix self.Rot = self.Rotx*self.Roty*self.Rotz #Translation (just copying) self.Tr[(0,3)] = -self.trans[0] self.Tr[(1,3)] = -self.trans[1] self.Tr[(2,3)] = -self.trans[2] #The Transformation matrix self.Tsf = self.Scale*self.Shear*self.Rot*self.Tr #Computing the normals in __init__ and only rotating them here is the same. # print '+++++++++++++++' # for i in range(len(self.cubenormals)): # print self.Rot*self.cubenormals[i] # print '+++++++++++++++' # print '************' #Cube for ic in range(len(polygons_3D)): cubex=polygons_3D[ic] cubex.update() cubefaces=cubex.cubefaces cls=cubex.cls for i in range(len(cubefaces)): poly = [] #transformed polygon for j in range(len(cubefaces[0])): cube=cubex.cube v = cube[cubefaces[i][j]] #Scale, Shear, Rotate the vertex around X axis, then around Y axis, and finally around Z axis and Translate. r=self.Tsf*v poly.append(r) n=self.calcNormalVec(poly) #print n if not self.isPolygonFrontFace(poly[0], n): #Backface culling continue poly2d = [] inviewingvolume = False for j in range(len(poly)): #put the point from 3D to 2D ps =self.Proj*poly[j] # Puut the screenpoint in the list of transformed vertices p=self.toSC*ps x=int(p.x) y = int(p.y) poly2d.append((x, y)) #if only one point is in the screen (x[-1,1], y[-1,1], z[-1,1]) then draw the whole polygon if (-1.0 <= ps.x <= 1.0) and (-1.0 <= ps.y <= 1.0) and (-1.0 <= ps.z <= 1.0): inviewingvolume = True if inviewingvolume: self.graph.create_polygon(*poly2d, fill=cls[i]) cfx=cubex.cfaces xface=cfx[i] xface_led=xface[0] for k in range(len(xface_led)): #LED v=xface_led[k] r=self.Tsf*v ps=self.Proj*r p = self.toSC*ps x, y = int(p.x), int(p.y) self.graph.create_rectangle(x, y, x+3, y+3, fill="magenta") v=xface[1] #photo tr r=self.Tsf*v ps=self.Proj*r p=self.toSC*ps x,y=int(p.x), int(p.y) self.graph.create_rectangle(x,y, x+3, y+3, fill="cyan") # cfx=cubex.cfaces # for i in range(len(cfx)): # xface=cfx[i] # xface_led=xface[0] # for j in range(len(xface_led)): # v=xface_led[j] # r=self.Tsf*v # ps=self.Proj*r # p = self.toSC*ps # x, y = int(p.x), int(p.y) # self.graph.create_rectangle(x, y, x+3, y+3, fill="magenta") # v=xface[1] # r=self.Tsf*v # ps=self.Proj*r # p=self.toSC*ps # x,y=int(p.x), int(p.y) # self.graph.create_rectangle(x,y, x+3, y+3, fill="cyan") #Texts (these are in camera-CS) txt1 = 'xpos: '+str(self.trans[0])+' ypos: '+str(self.trans[1])+' zpos: '+str(self.trans[2]) txt2 = 'xrot: '+str(self.ang[0])+' yrot: '+str(self.ang[1])+' zrot: '+str(self.ang[2]) self.idTxt1 = self.graph.create_text(30,30, text=txt1, fill='white', anchor=SW, font=self.fnt) self.idTxt2 = self.graph.create_text(30,60, text=txt2, fill='white', anchor=SW, font=self.fnt) vFwd = self.getForwardVec2(self.ang[1]) txt3 = 'Fwd: '+str(vFwd) self.idTxt3 = self.graph.create_text(30,90, text=txt3, fill='white', anchor=SW, font=self.fnt) def isPolygonFrontFace(self, v, n):#Clockwise? '''v is a vertex of the polygon, n is the normal-vector of the polygon.''' #The camera should be at (0.0, 0.0, 0.0) but that doesn't work well. #It seems that the camera is at (0.0, 0.0, 1.0) c = matrix.Vector3D(0.0, 0.0, 1.0) vv = v-c r = vv.dot(n) return r < 0.0 def calcNormalVec(self, p): '''p is an array of vertices of the polygon/face''' v1 = p[0]-p[1] v2 = p[0]-p[3] v = v1.cross(v2) v.normalize() return v def getForwardVec1(self): #Where the camera is facing. Inverting a matrix is slow m = self.Rot.invert() f1 = m[(0,2)] f2 = m[(1,2)] f3 = m[(2,2)] v = matrix.Vector3D(f1, f2, f3) v.normalize() #Forward vector is really backward one, so need to be negated return -v def getForwardVec2(self, yrot): #Where the camera is facing. f1 = -math.sin(math.radians(yrot)) f2 = 0.0 f3 = -math.cos(math.radians(yrot)) v = matrix.Vector3D(f1, f2, f3) return v def isCollisionPlanes(self, campos, camdir): num = len(self.walls) for i in range(num): iscol, dist = self.isCollisionPlane(self.walls[i], self.wallsnormals[i], campos, camdir) if iscol and dist < 0.1: #print( i+1, dist) return True return False def isCollisionPlane(self, p, n, campos, camdir): '''instead of pointonplane (p) and normalofplane (n) we could pass a polygon and calculate its normal vector here. campos is the position of the camera and camdir is the forward vector of the camera''' dist = 0.0 #Dot Product Between Plane Normal And Ray Direction dotprod = camdir.dot(n) #Determine If Ray Parallel To Plane if ((dotprod < Space.ZERO) and (dotprod > -Space.ZERO)): return False, dist #Find Distance To Collision Point dist = (n.dot(p-campos))/dotprod #Test If Collision Behind Start if (dist < -Space.ZERO): return False, dist return True, dist def isCollisionCube(self, campos,polygons_3D): '''Checks if the camera is inside the cube (this is the bounding-box-technique (bbt) but we have a cube so we do not need to calculate a bb) ''' for i in range(len(polygons_3D)): cube=(polygons_3D[i]).cube cubefaces=(polygons_3D[i]).cubefaces if (campos.z >= cube[cubefaces[0][0]].z and campos.z <= cube[cubefaces[2][0]].z) and (campos.y >= cube[cubefaces[5][0]].y and campos.y <= cube[cubefaces[4][0]].y) and (campos.x >= cube[cubefaces[3][0]].x and campos.x <= cube[cubefaces[1][0]].x): return True return False def keycallback(self, event): # print event.char # print event.keycode # print event.keysym #Foward if event.keysym == 'Up': vFwd = self.getForwardVec2(self.ang[1]) #Is there a collison? pos = matrix.Vector3D(self.trans[0]+vFwd.x*Space.SPEED, self.trans[1], self.trans[2]+vFwd.z*Space.SPEED) if self.isCollisionPlanes(pos, vFwd): return if self.isCollisionCube(pos,self.polygons_3D): return self.trans[0] += vFwd.x*Space.SPEED self.trans[2] += vFwd.z*Space.SPEED #Backward elif event.keysym == 'Down': vFwd = self.getForwardVec2(self.ang[1]) vBck = -vFwd #Is there a collison? pos = matrix.Vector3D(self.trans[0]-vFwd.x*Space.SPEED, self.trans[1], self.trans[2]-vFwd.z*Space.SPEED) if self.isCollisionPlanes(pos, vBck): return if self.isCollisionCube(pos,self.polygons_3D): return self.trans[0] -= vFwd.x*Space.SPEED self.trans[2] -= vFwd.z*Space.SPEED #Turn right elif event.keysym == 'Right': self.ang[1] -= 1.5 #Turn left elif event.keysym == 'Left': self.ang[1] += 1.5 #Upwards elif event.keysym == 'u': #Is there a collison? pos = matrix.Vector3D(self.trans[0], self.trans[1]+Space.SPEED, self.trans[2]) vUp = matrix.Vector3D(0.0, 1.0, 0.0) if self.isCollisionPlanes(pos, vUp): return if self.isCollisionCube(pos,self.polygons_3D): return self.trans[1] += Space.SPEED #Downwards elif event.keysym == 'd': #Is there a collison? pos = matrix.Vector3D(self.trans[0], self.trans[1]-Space.SPEED, self.trans[2]) vDwn = matrix.Vector3D(0.0, -1.0, 0.0) if self.isCollisionPlanes(pos, vDwn): return if self.isCollisionCube(pos,self.polygons_3D): return self.trans[1] -= Space.SPEED #Quit elif event.keysym == 'Escape': self.quit() if self.ang[1] >= 360.0: self.ang[1] -= 360.0 if self.ang[1] < 0.0: self.ang[1] += 360.0 self.update(self.polygons_3D) def quit(self): self.root.quit() def client_start(self): """ start client """ print("start client_start") handle_thread = threading.Thread(target=self.handler, daemon=True) self.parse("send up \"str (this s* f2d1 next s*4 f4d1)\".") self.parse("send up \"str (this s*4 f2d1 next s*44 f4d1)\".") self.update(self.polygons_3D) handle_thread = threading.Thread(target=self.handler, daemon=True) handle_thread.start() def handler(self): """ receive commands from terminal """ while True: print("input...") line=input() self.parse(line) self.update(self.polygons_3D) def clear(self): self.polygons_3D=[self.cube1] self.update(self.polygons_3D) def parse(self,line): #self.python2fwb(">"+line) print(line) p=line.find("send up ") if(p<0): return xl=line[(p+len("send up ")):] #print("xl="+xl) px=parser(xl) px.rb() if not (px.key("\"")): return if not px.key("str "): return px.rb() if not px.key("("): return px.rb() if not px.key("this "): return px.rb() xn=[""] if not px.d_name(xn): return px.rb() dn1=xn[0] cn=[(0,0)] if not px.connect(cn): return cn1=cn[0] px.rb() if not px.key("next "): return px.rb() xn=[""] if not px.d_name(xn): return dn2=xn[0] px.rb() if not px.connect(cn): return cn2=cn[0] px.rb() if not px.key(")"): return px.rb() if not px.key("\""): return px.rb() if not px.key("."): return print("dn1="+dn1) cube1=self.lookUpCube(dn1) if cube1 == None: print("new "+dn1) cube1=Cube(self) cube1.set_name(dn1) self.polygons_3D.append(cube1) print("dn2="+dn2) cube2=self.lookUpCube(dn2) if cube2 == None: print("new "+dn2) cube2=Cube(self) cube2.set_name(dn2) self.polygons_3D.append(cube2) print(cn1) print(cn2) if1=cn1[0] if2=cn2[0] id1=cn1[1] id2=cn2[1] v2=cube1.get_next_place_position(if1) cube2.moveTo(v2.x,v2.y,v2.z) cube1.rotate_nextdoor_cube_until_match_the_face(if1, cube2, if2) cube1.rotate_nextdoor_cube_until_match_the_direction(if1,cube2,if2,id2) self.update(self.polygons_3D) class parser: # # srs. # ok. # fid=2. received=ack1 face 4 dir 0 uDir 0. # this face(2):dir(0)<-> next face(4):dir(0) # send up "str (this s* f2d0 next s*4 f4d0)". # fid=2, received=send up "str (this s*4 f2d0 next s*44 f4d0)". # str (this s*4 f2d0 next s*44 f4d0) # ok # fid=2, received=send up "str (this s*44 f2d0 next s*444 f4d0)". # str (this s*44 f2d0 next s*444 f4d0)". # ok # def __init__(self,line): self.line=line print("parser init "+line) def alpha(self,p2a): #print("alpha line="+self.line) c=self.line[0] p2a[0]=c if(c.isalpha()): self.line=self.line[1:] return True return False def digit(self,p2i): #print("digit line="+self.line) c=self.line[0] if(c.isdigit()): p2i[0]=int(c) self.line=self.line[1:] return True return False def d_name(self,xn): #print("d_name line="+self.line) cl=[''] if not self.alpha(cl): return False xn[0]=xn[0]+cl[0] if not self.key("*"): return False xn[0]=xn[0]+"*" dx=[0] while self.digit(dx): xn[0]=xn[0]+str(dx[0]) return True def connect(self,tx): #print("connect line="+self.line) if not self.key("f"): return False fx=[0] if not self.digit(fx): return False if not self.key("d"): return False dx=[0] if not self.digit(dx): return False tx[0]=(fx[0],dx[0]) return True def key(self,key): #print("key line="+self.line) #print("key="+key) tf=self.line.startswith(key) print(tf) if(self.line.startswith(key)): lk=len(key) self.line=self.line[lk:] return True return False def rb(self): while self.key(' '): continue # # +-----------------------------------------------------+ # | TESLA | # | +-----------+ +--------------+ DICE | # | | mbed | |m5atom | | # | | |<--UART-->|tcp_server_ex1| | # | +-----------+ +--------------+ | # | ^ | # +--------------------------- | -----------------------+ # | # wifi.... SSID YAMA-M5ATOM-EX01 # | PASS 12345678 # | # +--------------------------- | -----------------------+ # | v PC | # | +------------------+ | # | |Chat_Client | # | | | # | v | # | handler | # | | | # | v | # | parser | # | | | # +----|-------------+ # | # | put # v # +------------------+ # |ex04.py | # | | | # | v | # | handler | # +------------------+ # # coding: utf-8 #from PIL import Image, ImageFont, ImageDraw #import time #import sys #import requests #import os #import socket #import threading #from collections import deque #import subprocess #import copy class Chat_Client: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) HOST = '192.168.4.1' PORT = 23 def __init__(self): print("start Chat_Client.__init__") def set_space(self,space): self.space=space def start_input_thread(self): handle_thread2 = threading.Thread(target=self.start_input, args=(self.sock,), daemon=True) handle_thread2.start() def start_input(self,sock): print("sock=") print(sock) print("input...") line="" cx=bytearray(8) while True: try: line=input() if line=="clear.": self.send_message("srr.") self.space.clear() else: for i in range(len(line)): c=line[i] #c=readchar.readkey() #print(c) #cx[0]=c.encode('utf-8') #cx[1]=0x00 sock.sendall(c.encode('ascii',errors='replace')) time.sleep(0.01) except: print("error") def send_message_nl(self,py2x_message): msg=py2x_message+'\n' self.sock.sendall(msg.encode('ascii',errors='replace')) def send_message(self,py2x_message): msg=py2x_message self.sock.sendall(msg.encode('ascii',errors='replace')) def parse(self,line): vf.parse(line) def client_start(self): """start client""" self.sock.connect((self.HOST, self.PORT)) handle_thread = threading.Thread(target=self.handler, args=(self.sock,), daemon=True) handle_thread.start() def handler(self,sock): """receive the message from the server and print it.""" while True: lx="" while True: data = sock.recv(1024) dl=len(data) try: rd=data.decode('utf-8') except: rd='?'*dl for i in range(dl): c=rd[i] lx=lx+c if c=="." or c=="\n": print("[receive] "+lx) self.parseLines(lx) lx="" #print(c) if c=="." or c=="\n": break print("[receive]-"+lx) if self.space!=None: print("parse-"+lx) self.parseLines(lx) def parseLines(self,ls): lsx=ls.splitlines() for i in range(len(lsx)): self.space.parse(lsx[i]) if __name__ == "__main__": client=Chat_Client() space=Space() client.set_space(space) client.client_start() client.start_input_thread() space.start_3d() }} ---- #counter