ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • KMS14 : Python을 이용한 simple rotatable 3D 구현 프로그램
    Program_Light 2021. 11. 6. 13:00

    이번에는 지난 포스팅을 발전시켜 구체를 형성하는 프로그램을 만들었습니다. 이 모델링 프로젝트의 최종 목표는 txt파일을 통해 원하는 도형을 직접 만들 수 있게 하는 것이지만, 일단은 프로젝트의 중간 프로젝트 느낌으로 구체 형상의 도형에 지구본 이미지를 입힐 수 있게 만들어보았습니다. 이후에 지구본에 특정 위치를 mapping 할 수 있는 기능을 추가할 생각입니다.


    consideration

    참고로, 이 프로그램은 점을 A도 회전시킬 때 sinA + cosAi를 점에 곱하여 회전된 점의 좌표를 계산하는 방식을 취하고 있습니다. 또한, 모든 sin, cos함수는 degree각도를 사용합니다. (간지나고 표준적으로다가 원주각을 사용할려고 했는데, 그냥 degree로 했어요;;)

     

    sphere에 사용할 이미지가 소스코드가 있는 파일과 동일한 폴더에 있어야 합니다.
    지도 이미지는 등 장방형 도법(Equirectangular projection)을 사용하여야 합니다.


    source

    source.py

    import tkinter
    import math
    import time
    import random
    
    from PIL import Image
    import numpy as np
    
    # X coor, Y coor, Z coor
    #nodeL = [[200, -150, 200], [-200, -150, 200], [-200, -150, -200], [200, -150, -200], [0, 250, 0]]
    #edgeL = [(0, 1), (1, 2), (2, 3), (3, 0), (0, 4), (1, 4), (2, 4), (3, 4)]
    #faceL = []
    nodeL = [];
    edgeL = [];
    faceL = [];
    
    # coor indi
    coorL = [[20, 0, 0], [0, 20, 0], [0, 0, -20]]
    coorC = ['#FF0000', '#00FF00', '#0000FF']
    
    #Object size
    Obj_C = '#000000' #object color
    Nod_R = 5 # node radius
    Edg_W = 5 # Edge width
    
    scale = 1
    Scl_L = 20 #scale max limit
    
    Cen_X = 300 # center X
    Cen_Y = 300 # center y
    
    MOS_S = 2#0.3 # mouse sensitivity
    
    Dx = 0 # delta x angle spin
    Dy = 0 # delta y angle spin
    
    def sin(angle):
        if(angle % 90):
            return math.sin(angle * math.pi / 180)
        else:
            if(angle % 180):
                return -1 if (angle % 360 - 90) else 1
            else:
                return 0;
    
    def cos(angle):
        if(angle % 90):
            return math.cos(angle * math.pi / 180)
        else:
            if(angle % 180):
                return 0
            else:
                return -1 if (angle % 360) else 1
    
    window = tkinter.Tk()
    win = tkinter.Canvas(window, width = Cen_X * 2, height = Cen_Y * 2)
    
    # process mouse event
    Bx = By = -1
    
    ########## defines ##########
    #mouse move while mouse was in clicked state
    def FNC1(event):
        global Bx, By, Dx, Dy
        if not ((Bx == -1) and (By == -1)):
            Ang_X = event.x - Bx
            Ang_Y = event.y - By
            Dx = (Ang_X + 360) % 360
            Dy = (360 - Ang_Y) % 360
        Bx = event.x
        By = event.y
    
    #mouses click
    def FNC2(event):
        global Bx, By
        Bx = By = -1
    
    #wheel mouse
    def FNC3(event):
        global scale, Scl_L
        if event.delta == 120:
            if scale < max(Cen_X, Cen_Y) / Scl_L:
                scale += 0.15
        elif event.delta == -120:
            if scale > 0.15: 
                scale -= 0.15
    
    def Rotate_A(R, Z, A):
        return (R * cos(A)) - (Z * sin(A)), (R * sin(A)) + (Z * cos(A))
    
    for i in range(1, 4, 1):
        win.bind("<Button-" + str(i) + ">", FNC1)
        win.bind("<B" + str(i) + "-Motion>", FNC1)
        win.bind("<ButtonRelease-" + str(i) + ">", FNC2)
        win.bind("<MouseWheel>", FNC3);
    win.pack()
    
    def rgb_to_hex(arr):
        r, g, b = int(arr[0]), int(arr[1]), int(arr[2])
        return '#' + hex(r)[2:].zfill(2) + hex(g)[2:].zfill(2) + hex(b)[2:].zfill(2)
    
    ########## create sphere ##########
    img_name = ('map.jpg')
    im = Image.open(img_name)
    
    pix = np.array(im)
    print(im.size)
    print(str(im.size[0] - 1) + "  " + str(im.size[1] - 1))
    print(pix[im.size[1] - 1][im.size[0] - 1])
    
    #form const numbers
    RAD = 200       #radian
    XL = 360 // 3   #number of point in 360 degree column
    YL = 180 // 3   #number of point in 180 degree row
    
    SemVal = 2      #number of null layer in polar. this value mush be even
    
    DAXL = 360 // XL
    DAYL = 180 // YL
    YL -= SemVal
    
    NYL = 90 - (DAYL * (SemVal // 2))
    
    #form base structure
    Ny = sin(NYL) * RAD
    Nr = cos(NYL) * RAD
    for i in range(0, 360, DAXL):
        nodeL.append([cos(i) * Nr, Ny, sin(i) * Nr])
        edgeL.append((i // DAXL, (i // DAXL + 1) % XL))
    NYL = (NYL - DAYL + 360) % 360
    for j in range(0, YL, 1):
        Ny = sin(NYL) * RAD
        Nr = cos(NYL) * RAD
        for i in range(0, 360, DAXL):
            nodeL.append([cos(i) * Nr, Ny, sin(i) * Nr])
            edgeL.append(((XL * (j + 1)) + (i // DAXL), (XL * (j + 1)) + ((i // DAXL + 1) % XL)))
            edgeL.append(((XL * j) + (i // DAXL), (XL * (j + 1)) + (i // DAXL)))
        NYL = (NYL - DAYL + 360) % 360
    
    #form sphere
    for j in range(0, YL, 1):
        for i in range(0, XL, 1):
            agh = [(im.size[1] - 1) * (j + (SemVal // 2)) // (YL + SemVal), (im.size[1] - 1) * (j + (SemVal // 2 + 1)) // (YL + SemVal) - 1]
            agj = [(im.size[0] - 1) * i // XL, (im.size[0] - 1) * (i + 1) // XL - 1]
    
            #this loop isn't optimized
            arp = [0, 0, 0]
            for n in range(0, 4, 1):
                for m in range(0, 3, 1):
                    arp[m] += pix[agh[n & 1]][agj[n >> 1]][m]
            for m in range(0, 3):
                arp[m] //= 4
            
            CR = rgb_to_hex(arp)
            faceL.append(((XL * j) + i, (XL * j) + ((i + 1) % XL), (XL * (j + 1)) + ((i + 1) % XL), (XL * (j + 1)) + i, CR))
    
    ########## window initalization ##########
    win.create_text(Cen_X, 20, text= "move cursor to spin",fill="black",font=('Arial 15 bold'))
    Dx = Dy = 0
    
    ########## rotating ##########
    while 1:
        win.delete('figure')
        win.delete('coor')
        
        #Rotate node
        for node in nodeL:
            node[0], node[2] = Rotate_A(node[0], node[2], Dx)
            node[2], node[1] = Rotate_A(node[2], node[1], Dy)
        #Rotate coor
        for coor in coorL:
            coor[0], coor[2] = Rotate_A(coor[0], coor[2], Dx)
            coor[2], coor[1] = Rotate_A(coor[2], coor[1], Dy)
    
        Dx = Dy = 0;
    
        #Draw coor
        #for i in range (0, len(coorL), 1):
            #win.create_line(Cen_X, Cen_Y, Cen_X - coorL[i][0], Cen_Y - coorL[i][1], fill = coorC[i], width = 3, tags = ('edge', 'coor'))
        #Draw edge
        #for edge in edgeL:
            #if (nodeL[edge[0]][2] >= 0) and (nodeL[edge[1]][2] >= 0):
                #win.create_line(Cen_X - nodeL[edge[0]][0], Cen_Y - nodeL[edge[0]][1], Cen_X - nodeL[edge[1]][0], Cen_Y - nodeL[edge[1]][1], fill = Obj_C, width = Edg_W, tags=('edge', 'figure'))
        #Draw face
        for face in faceL:
            if (nodeL[face[0]][2] >= 0) and (nodeL[face[1]][2] >= 0) and (nodeL[face[2]][2] >= 0) and (nodeL[face[3]][2] >= 0):
                win.create_polygon(Cen_X - nodeL[face[0]][0], Cen_Y - nodeL[face[0]][1], Cen_X - nodeL[face[1]][0], Cen_Y - nodeL[face[1]][1], Cen_X - nodeL[face[2]][0], Cen_Y - nodeL[face[2]][1], Cen_X - nodeL[face[3]][0], Cen_Y - nodeL[face[3]][1], fill = face[4], tags=('face', 'figure'))
        win.update()
        time.sleep(0.01)
    
    window.mainloop()

    excute

    사용 방법은 간단합니다.
    실행시킬 시, 프로그램은 자동으로 랜덤하게 다양한 색깔을 가지는 구체를 만들어냅니다.
    클릭 후 드래그로 회전시킬 수 있고, 마우스 휠로 확대 및 축소할 수 있습니다.

    (축소.확대 기능은 이 프로그램에서는 빠짐)

    original example
    excuted window

     

하면된다 學業報國