이전 글에서는 간단하게 점과 폴리곤을 그리고, 평행/회전이동, 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)
'컴퓨터과학 > 응용수학' 카테고리의 다른 글
파이썬수학 - 6. 변화율기초 (0) | 2023.09.29 |
---|---|
파이썬수학 - 5.게임과 연립일차식 풀기 (0) | 2023.09.25 |
파이썬수학 - 4. 벡터 깊게 다루기 (0) | 2023.09.25 |
파이썬수학 - 3. 선형변환 (0) | 2023.09.24 |
파이썬수학 - 1. 2차원 드로잉 (0) | 2023.09.23 |