diff --git a/README.md b/README.md index 210e392..a48a14f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PlayWith3D A project about [my](cx@kdxcxs.com) 3D exploring journey -## projects +## Projects ### First Cube @@ -13,6 +13,11 @@ prototype: ![FirstCubePrototype](assets/FirstCubePrototype.jpg) +src: + +1. [python](src/python/First_Cube.py) +2. [js](src/js/First_Cube.html) + ### Cube Without Perspective Simply fill the two faces nearer to the camera. @@ -23,4 +28,24 @@ preview: prototype: -![CubeWithoutPerspectivePrototype](assets/CubeWithoutPerspectivePrototype.jpg) \ No newline at end of file +![CubeWithoutPerspectivePrototype](assets/CubeWithoutPerspectivePrototype.jpg) + +src: + +1. [python](src/python/CubeWithoutPerspective.py) + +### Simple 3D Camera + +To be done. + +I have to prepare for the college entrance examination😥 + +preview for functional parts: + +![Simple3DCameraPreview](assets/Simple3DCamera.gif) + +Check the TODO list in [Simple3DCamera.py](src/python/Simple3DCamera.py) for more information. + +src: + +1. [python](src/python/Simple3DCamera.py) \ No newline at end of file diff --git a/assets/Simple3DCamera.gif b/assets/Simple3DCamera.gif new file mode 100644 index 0000000..ee050b0 Binary files /dev/null and b/assets/Simple3DCamera.gif differ diff --git a/src/python/Simple3DCamera.py b/src/python/Simple3DCamera.py new file mode 100644 index 0000000..9ae00f7 --- /dev/null +++ b/src/python/Simple3DCamera.py @@ -0,0 +1,172 @@ +# coding:utf-8 + +# TODO: +# 0.Debug┑( ̄Д  ̄)┍ +# 1.物体有部分在相对相机X负半轴的情况(TkRender.run中) +# 2.object增加 棱 属性,渲染时作出棱就行了,相应改变render +# 3.搞懂成像原理,缩放后放大多少(Camera.scale) +# 4.实现perspective开关 +# 5.相机的旋转只做了Z的旋转(应该是成功了吧……)我现在的想法是记录旋转的顺时针弧度数 +# 然后计算另外两个轴的新坐标,一个轴一个轴地转.从旋转轴的正半轴朝原点看,另外两个轴的方向 +# 分别向右,向上,以向上轴为0°记录顺时针弧度数 +# 6.Tk canvas的Y轴向下为正方向,尝试转换 + +from tkinter import Tk, Canvas +import math, threading, time + + +class Camera(object): + def __init__(self, position=[0, 0, 0], rotating=[0, 0, 0]): + self.position = position # coordinates in x,y and z + self.focus = 0.8 # 焦距 + self.scale = 200 + self.rotating = rotating # 用于确定相机朝向,x,y和z轴的顺时针旋转角度,参考blender设定 + + def moveTo(self, pst): + self.position = pst + + +class Scen(object): + def __init__(self): + self.objects = [] + + def addObjects(self, objs): + self.objects += objs + + +class TkRender(object): # including render and screen(a canvas) + def __init__(self, cam, scen, dgb=False): + self.cam: Camera = cam # camera + self.pits = [] # points in 2d e.g. [[[1,1],[2,2]],[[3,3],[4,4]]] => [object,object] object => [point,point] + self.perspective = True # 是否透视 + self.size = [800, 600] # screen size + self.offset = [self.size[0] / 2, self.size[1] / 2] # 让画面中心在窗口中心 + self.scen: Scen = scen + self.debug = dgb # debug mode + self.fps = 60 + self.st = 1 / self.fps # sleep time + self.runThread = threading.Thread(target=self.run) + self.tkroot = Tk() + self.cvs = [Canvas(self.tkroot, bg='white', width=800, height=600), # canvas + Canvas(self.tkroot, bg='white', width=800, height=600)] + self.cvs[0].pack() + self.showingCv = 0 + self.hiddenCv = 1 + self.running = False + + def run(self): + angle = 0 + while self.running: + angle += apf + rotateCamera(self, angle) + self.cvs[self.hiddenCv].delete('all') + for ob in self.scen.objects: + pit = [] # points in 2d + for tpt in ob.points: # tpt:3D point + rc = [] # relative coordinate相对坐标 + ra = 0 # relative angle点在旋转平面相对相机正方向顺时针旋转角度 + dis = 0 # 点到相机的距离 + for i in range(3): + rc.append(tpt[i] + ob.center[i] - self.cam.position[i]) + # 先只转动X轴,暂时想不出什么转相机的好方法(就是没想出来┑( ̄Д  ̄)┍) + # for ras, aas in [[0, [1, 2]], # rotating axis,anfected axises + # [1, [0, 2]], # 处理相机旋转,rc更新为相机旋转后轴的相对坐标 + # [2, [0, 1]]]: # 旋转一个轴,影响其他两个轴 + # dis = math.sqrt(rc[aas[0]] ** 2 + rc[aas[1]] ** 2) # 在旋转平面上点与相机间距离 + # ra = math.acos(rc[0] / dis) + self.cam.rotating[ras] + # if ras == 2: # Y轴与X或Z的旋转不同,画个图看看就知道了(大概) + # rc[0] = - dis * math.sin(ra) + # rc[2] = dis * math.cos(ra) + # else: + # rc[aas[0]] = dis * math.sin(ra) + # rc[aas[1]] = dis * math.cos(ra) + # # rc += [0,0,0] # 为了保证计算相对屏幕坐标时旋转前坐标不改变,新坐标用后三位,前三位在计算完成后去除 + # # for era in range(3): # each relative angle一个轴一个轴的转 + # # rc[] + dis = math.sqrt(rc[0] ** 2 + rc[1] ** 2) + if rc[0] > 0 and rc[1] > 0: # 第一象限 + ra = math.asin(rc[0] / dis) + elif rc[0] > 0 and rc[1] < 0: + ra = math.pi - math.asin(rc[0] / dis) + elif rc[1] < 0: + ra = math.asin(-rc[0] / dis) + math.pi + else: + ra = math.pi - math.asin(- rc[0] / dis) + rc[0] = dis * math.sin(ra - self.cam.rotating[2]) + rc[1] = dis * math.cos(ra - self.cam.rotating[2]) + # Z轴转完了 + rate = self.cam.focus / rc[1] # 根据焦距和点离相机垂直距离确定转2d的缩放比例 + pit.append([rc[0] * rate * self.cam.scale, - rc[2] * rate * self.cam.scale]) # *(-1):cvsY轴是倒置的 + for i in range(len(pit)): # 让画面中心在窗口中心 + pit[i] = [pit[i][0] + self.offset[0], pit[i][1] + self.offset[1]] + for dcp, nps in [[1, [0, 3, 5]], # drawing central point + [2, [0, 3, 6]], # 以1,2,4,7四个点为中心,分别连接相邻的三个点即可绘成正方体 + [4, [0, 5, 6]], + [7, [3, 5, 6]]]: + for np in nps: # nearby point + self.cvs[self.hiddenCv].create_line(pit[dcp][0], pit[dcp][1], + pit[np][0], pit[np][1]) + if self.debug: + self.cvs[self.hiddenCv].create_text(pit[dcp][0], pit[dcp][1], + text=str(dcp)) + # text=str(ob.center[0])) + self.cvs[self.hiddenCv].create_text(pit[np][0], pit[np][1], + text=str(np)) + # text=str(ob.center[0])) + if self.debug: + self.cvs[self.hiddenCv].create_text(self.cam.position[0] * 50 + 400, - self.cam.position[1] * 50 + 300, + text='C') + self.cvs[self.hiddenCv].create_text(400, 300, text='O') + self.cvs[self.hiddenCv].create_line(self.cam.position[0] * 50 + 400, -self.cam.position[1] * 50 + 300, + self.cam.position[0] * 50 + 400 + 20 * math.sin( + self.cam.rotating[2]), + -self.cam.position[1] * 50 + 300 - 20 * math.cos( + self.cam.rotating[2])) + # 切换canvas + self.cvs[self.showingCv].pack_forget() + self.cvs[self.hiddenCv].pack() + self.showingCv, self.hiddenCv = self.hiddenCv, self.showingCv + self.cvs[self.hiddenCv].delete('all') + time.sleep(self.st) + + def start(self): + self.running = True + self.runThread.start() + self.tkroot.mainloop() + + +class TDObject(object): # 3D object + def __init__(self, centerpoint=[0, 0, 0], points=None): + self.center = centerpoint # coordinates in three axises e.g. [0,0,0] + self.points = [] # 物体每个点对于center point的相对坐标 + self.setPoints(points) + + def setPoints(self, points): # points e.g. [[1,1,1],[2,2,2]] + self.points += points + + +# 主体结束,下面开始测试 + +def rotateCamera(rd: TkRender, angle): + angle += apf + if angle > 2 * math.pi: + angle -= 2 * math.pi + rd.cam.rotating[2] = -angle + rd.cam.position[0] = 6 * math.sin(angle) + rd.cam.position[1] = -6 * math.cos(angle) + + +if __name__ == '__main__': + ca = Camera(position=[0, 0, 0]) + sc = Scen() + rd = TkRender(ca, sc, True) + # c1 = TDObject([0, 0, 0], + # [[-1, 1, -1], [1, 1, -1], [-1, -1, -1], [1, -1, -1], [-1, 1, 1], [1, 1, 1], [-1, -1, 1], [1, -1, 1]]) + # sc.addObjects([c1]) # cube1,cube2 + c1 = TDObject([2, 2, 2], + [[-1, 1, -1], [1, 1, -1], [-1, -1, -1], [1, -1, -1], [-1, 1, 1], [1, 1, 1], [-1, -1, 1], [1, -1, 1]]) + c2 = TDObject([-2, -2, 0], + [[-1, 1, -1], [1, 1, -1], [-1, -1, -1], [1, -1, -1], [-1, 1, 1], [1, 1, 1], [-1, -1, 1], [1, -1, 1]]) + sc.addObjects([c1, c2]) # cube1,cube2 + apf = math.pi / 60 + rd.start()