이전 글에서는 간단하게 점과 폴리곤을 그리고, 평행/회전이동, matplotlib을 이용한 드로잉 모듈을 만들었음

이번에는 3d 차원을 다뤄보자

 

3d 공간 띄우기

draw 3d 모듈 가져와서 실행하면 이런식으로 matplotlib에서 3d로 만든 공간이 나옴

 

from draw3d import *
draw3d()

 

점과 선분 그리기

여기서는 3차원 선분을 Segment3D로 제공

draw3d(
    Points3D((2, 2, 2), (1, -2, -2)),
    Segment3D((2, 2, 2), (1, -2, -2))
)

 

3차원 박스 그리기

box 3d를 쓰면 원점을 한 모서리로하는 3차원 박스 그릴수있음

draw3d(
    Points3D((2, 2, 2), (1, -2, -2)),
    Segment3D((2, 2, 2), (1, -2, -2)),
    Box3D(2, 2, 2),
    Box3D(1, -2, -2)
)

 

 

점과 선분으로 3d 사각형 그리기

이번엔 3차원 점과 3차원 선분으로 그림

pm1 = [1, -1]
vertices = [(x, y, z) for x in pm1 for y in pm1 for z in pm1]
print(vertices)

edges = [((-1,y,z),(1,y,z)) for y in pm1 for z in pm1] +\
                [((x,-1,z),(x,1,z)) for x in pm1 for z in pm1] +\
                [((x,y,-1),(x,y,1)) for x in pm1 for y in pm1]
print(edges)

draw3d(
    Points3D(*vertices),
    *[Segment3D(*edge) for edge in edges]
)

 

세 백터의 합 구하기, 3차원에서 길이 계산

zip은 리스트의 같은 번째 요소들끼리 묶음

(1, 1, 3), (2, 4, -4), (4, 2, -2) 0번 끼리, 1번끼리 2번끼리 묶어 (1, 2, 4), (1, 4,2). 식으로 됨.

길이야 2d 때처럼 유클리디안 거리로 구현

 

def add(*vecs):
    by_coord = zip(*vecs)
    coord_sum = [sum(coords) for coords in by_coord]
    return tuple(coord_sum)

print(list(zip(*[(1, 1, 3), (2, 4, -4), (4, 2, -2)])))
print([sum(coords) for coords in [(1, 2, 4), (1, 4, 2), (3, -4, -2)]])


def add(*vecs):
    return tuple(map(sum, zip(*vecs)))
add((1, 1, 3), (2, 4, -4), (4, 2, -2))



from math import sqrt
def length(v):
    return sqrt(sum([coord **2 for coord in v]))
length((3, 4, 5))

 

3차원 벡터 합 드로잉 하기

draw3d에 arrow3d로 드로잉

(4, 0, 3)과 (-1, 0, 1)을 합하여 (3, 0, 4) 가 나옴

draw3d(
    Arrow3D((4, 0, 3), color=red),
    Arrow3D((-1, 0, 1), color=blue),
    Arrow3D((3, 0, 4), color=purple)
)

 

 

삼각함수로 3차원 회전상승? 그리기

pi/6 만큼 상 이동하는 (sin, cos, 1/3) 벡터 리스트 vs 준비

벡터 합 연산을 누적 시켜 arrow 3d 준비

draw arrows로 회전상승하는 화살표 그리기

from math import sin, cos, pi
vs = [(sin(pi*t/6), cos(pi*t/6), 1.0/3) for t in range(0, 24)]

running_sum = (0, 0, 0)
arrows = []
for v in vs:
    next_sum = add(running_sum, v)
    arrows.append(Arrow3D(next_sum, running_sum))
    running_sum = next_sum

print(running_sum)
draw3d(*arrows)

 

3d 벡터 스칼라 곱 구현, 길이가 1로 조정하기

스칼라 곱으로 스케일 함수 구현

-1, -1, 2 유클리디안 거리 구하기

1/vec_len으로 몇을 곱하면 1이 되는지 찾기

(-1, -1, 2) 벡터에 0.408 스칼라곱하여 길이가 1이되도록 조정

def scale(scalar, v):
    return tuple(scalar * coord for coord in v)

vec_len = length((-1, -1, 2))
s = 1/vec_len
vec_scaled = scale(s, (-1, -1, 2))

print(vec_len)
print(s)
print(vec_scaled)

 

 

내적

- 벡터간 곱셈 연산 -> 스칼라 반환, 점곱/스칼라곱 이라고도함

- 두 벡터끼리 얼마나 같은 방향인지 나타내는 정도

- 내적 = u dot v = |u| x |v| x cos(theta)

 * 두 벡터 사이 각이 90인 경우 0 이 됨 (cos 90 = 0이므로)

def dot(u, v):
    return sum([coord1 * coord2 for coord1, coord2 in zip(u, v)])

print(dot((1, 0), (0, 2)))
print(dot((0, 3, 0), (0, 0, -5)))
print(dot((3, 4), (2, 3)))

 

두 벡터 사이간 각도

- 내적 공식 u dot v = |u| |v| cos theta 을 정리하면

 cos theta = u dot v / (|u| |v|)

theta = acos(u dot v / (|u| |v|) ) 로 정리 가능

 

 

벡터 (3, 4)와 (2, 3)의 내적 결과 18

두 벡터 사이 각도 0.0554 rad

|(3, 4)| x |(2, 3)| x cos(0.0554 rad) = 18

 

from math import cos, pi, acos

def angle_between(v1, v2):
    return acos(dot(v1, v2) / (length(v1) * length(v2)))

ang = angle_between((3,4), (2, 3))
print(ang)

print(dot((3, 4), (2, 3)))
dot_product = length((3, 4)) * length((2, 3)) * cos(ang)
print(dot_product)

 

 

 

외적

- 벡터 x 벡터 -> 벡터, 벡터곱이라고 부르며 벡터를 반환해서 크기와 방향을 가짐

- 방향은 두 벡터에 수직( 오른손 법칙)

- 크기는 두 벡터가 얼마나 포개지는지 다룬 내적과는 달리 얼마나 90도에 가까운지에 달림

=>  u x v = u cross v = |u| |v| sin theta

                 =  (uy * vz - uz * vy, uz*vx - ux*vz, ux*vy - uy*vx)

def cross(u, v):
    ux, uy, uz = u
    vx, vy, vz = v
    return (uy * vz - uz * vy, uz*vx - ux*vz, ux*vy - uy*vx)

print(cross((1, 0, 0), (0, 1, 0)))
print(cross((1, 0, 0), (0, 2, 0)))

 

 

외적 계산 예시보기

 외적은 두 벡터에 수직인 방향을 가지며, 크기는 90도일수록 큰 값을 가짐

(1, 0, 0)과 (0, 0, 1)의 외적 결과 (0, 0, 1)은 z축에 나란한 형태가 나옴

 

(1, 1, 1)과 (-1, 1, 1)의 외적결과도 두 벡터간 수직 방향 향함

 

두 벡터 다 크기가 같으나

두 벡터사이 각이 좁을떄는 작고

90도 되니 좁을때보다 커짐

   

 

3차원 벡터의 2차원 투영

3차원 벡터를 x, y 축 단위 벡터랑 점곱 연산해서 구함

z축 값이 0이 된다.

 

 

# (1, 0, 0)과 dot 연산
# (0, 1, 0)과 dot 연산
def component(v, direction):
    return (dot(v, direction) / length(direction))

# 3차원 백터를 xy 평면(2차원)에 투영하는 함수
def vector_to_2d(v):
    return (component(v, (1, 0, 0)), component(v, (0, 1, 0)))

u = (3, 2, 5)
projected_vec_2d = vector_to_2d(u)
projected_ved_3d = (projected_vec_2d[0], projected_vec_2d[1], 0)
print(u)
print(projected_ved_3d)
draw3d(
    Arrow3D(u),
    Arrow3D(projected_ved_3d, color="blue")
)

 

3차원 8면체 그리기

단순히 8면체 꼭지점 준비해서 연결만 하면 끝

 

top = (0, 0, 1)
bottom = (0, 0, -1)
xy_plane = [(1, 0, 0,), (0, 1, 0), (-1 , 0, 0), (0, -1, 0)]
edges = [Segment3D(top, p) for p in xy_plane] +\
    [Segment3D(bottom, p) for p in xy_plane] +\
    [Segment3D(xy_plane[i], xy_plane[(i + 1) % 4]) for i in range(0, 4)]
draw3d(*edges)

 

 

 

8면체 2차원 투영 준비하기

8면체의 면 face들은 세 점으로 구성

8면체 변수는 8개의 면 리스트

normal로 면에 수직 벡터 취득 후 unit으로 단위벡터화

unit_normal[2] > 0는 면의 수직 벡터가 위를 향함을 의미. 위에서 볼것이므로 윗 면만 나와야 함

face_to_2d로 3차원 면을 2차원 폴리곤으로 변환 후 전체 드로잉

 

 

 

from vectors import *
from draw2d import *

# 면에서 정점 가져옴
def vertices(faces):
    return list(set([vertex for face in faces for vertex in face]))

# 면을 2차원으로 투영
def face_to_2d(face):
    return [vector_to_2d(vertex) for vertex in face]

# 길이가 1인 단위벡터화
def unit(v):
    return scale(1./length(v), v)

# 두벡터간 차연산
def subtract(v1,v2):
    return tuple(v1-v2 for (v1,v2) in zip(v1,v2))

def normal(face):
    return cross(subtract(face[1], face[0]), subtract(face[2], face[0]))

blues = matplotlib.cm.get_cmap("Blues")
def render(faces, light = (1, 2, 3), color_map=blues, lines=None):
    polygons = []
    for face in faces:
        unit_normal = unit(normal(face))
        if unit_normal[2] > 0:
            c = color_map(1 - dot(unit(normal(face)), unit(light)))
            p = Polygon2D(*face_to_2d(face), fill=c, color=lines)
            polygons.append(p)
    draw2d(*polygons, axes=False, origin=False, grid=None)

octahedron = [
    [(1,0,0), (0,1,0), (0,0,1)],
    [(1,0,0), (0,0,-1), (0,1,0)],
    [(1,0,0), (0,0,1), (0,-1,0)],
    [(1,0,0), (0,-1,0), (0,0,-1)],
    [(-1,0,0), (0,0,1), (0,1,0)],
    [(-1,0,0), (0,1,0), (0,0,-1)],
    [(-1,0,0), (0,-1,0), (0,0,1)],
    [(-1,0,0), (0,0,-1), (0,-1,0)],
]

 

8면체 2차원에 투영하기

 

 

render(octahedron, color_map=matplotlib.cm.get_cmap('Blues'), lines=black)

 

 

 

 

 

 

 

 

 

+ Recent posts