pyxen.Mesh

Mesh는 정점 하나하나를 직접 정의해 만드는 삼각형 또는 쿼드(사각형)로 이루어진 그리기 컴포넌트입니다. 각 정점은 위치, 이미지에서 샘플링할 지점, 색을 가지므로 Mesh는 단색 다각형, 텍스처가 입혀진 스트립, 그라데이션, 휘거나 물결치는 표면이 될 수 있습니다.

Sprite, Text, 도형과 같은 컴포넌트입니다. 스폰할 때 엔티티에 하나를 주면 엔티티가 매 프레임 그립니다.

Mesh는 2D입니다. 정점에는 x와 y만 있고 깊이는 없습니다. z도, 깊이 버퍼도, 원근도 없습니다. 모든 것이 CPU에서 변환되어 다른 컴포넌트처럼 레이어 순서대로 평면으로 그려집니다. 스프라이트와 도형으로는 그릴 수 없는 모양을 그리는 방법이지, 3D 엔진이 아닙니다.

from pyxen import *

def start():
    world.spawn(x=160, y=60,
                mesh=Mesh(points=(-40, 40, 0, -40, 40, 40), fill=(0.3, 0.9, 0.5, 1.0)))

생성자

Mesh(
    *,
    points=None,
    vertices=None,
    indices=None,
    fill=None,
    image=None,
    mode=TRIANGLES
)

모든 매개변수는 키워드 전용입니다. points 또는 vertices를 반드시 지정해야 하며, 둘 다 없으면 ValueError: Mesh needs points= or vertices=가 발생합니다.


매개변수

매개변수타입설명
points튜플, 리스트 또는 FloatBuffer평평한 위치. 정점당 float 2개: (x1, y1, x2, y2, …). 모든 정점이 fill의 하나의 색을 가집니다
vertices튜플, 리스트 또는 FloatBuffer전체 정점. 정점당 float 8개: x, y, u, v, r, g, b, a
indices튜플, 리스트 또는 IntBuffer어떤 정점이 각 삼각형이나 쿼드를 이루는지. 생략하면 정점을 순서대로 사용합니다
fill색points용 색. (r, g, b, a) 튜플 또는 Color. 기본값은 불투명한 흰색
imagestr메시에 텍스처를 입힐 이미지 에셋의 이름
modeTRIANGLES 또는 QUADS각 프리미티브의 종류. 기본값 TRIANGLES

points와 vertices를 모두 전달하면 vertices가 우선합니다.


정점

정점은 여덟 개의 숫자입니다.

float의미
x, y픽셀 단위 위치, 엔티티 기준 상대값. 변환 참고
u, v이 정점이 보여 줄 이미지의 지점. 이미지 전체를 0.0에서 1.0으로 나타냅니다. (0, 0)이 왼쪽 위, (1, 1)이 오른쪽 아래입니다. image가 없으면 무시됩니다
r, g, b, a정점 색. 각각 0.0에서 1.0

색은 정점 사이에서 보간되므로, 쿼드의 모서리마다 다른 색을 주면 그라데이션이 그려집니다. image가 있으면 색은 이미지에 곱해집니다. 흰색은 이미지를 그대로 두고, 빨간색은 빨간 성분만 남깁니다.

짧은 형식인 points는 텍스처가 없고 한 가지 색인 지오메트리용입니다. u = v = 0이고 모든 색이 fill인 vertices와 같습니다.


인덱스

정점은 한 번만 나열합니다. 인덱스는 어떤 정점이 각 프리미티브를 이루는지 알려 주므로, 변을 공유하는 두 삼각형은 그 두 정점을 반복하지 않고 공유할 수 있습니다.

  • mode=TRIANGLES에서는 3개의 인덱스마다 삼각형 하나가 만들어집니다.
  • mode=QUADS에서는 4개의 인덱스마다 쿼드 하나가 만들어집니다. 모서리는 쿼드를 따라 순서대로(예: 왼쪽 위, 오른쪽 위, 오른쪽 아래, 왼쪽 아래) 나열하세요.

인덱스는 float가 아니라 정점을 셉니다. 정점 형식과 상관없이 2는 세 번째 정점을 뜻합니다.

# 0-2 변을 공유하는 두 삼각형으로 만든 정사각형
Mesh(vertices=square, indices=(0, 1, 2, 0, 2, 3))

# 같은 정사각형을 쿼드 하나로
Mesh(vertices=square, indices=(0, 1, 2, 3), mode=QUADS)

indices를 생략하면 정점이 순서대로 사용됩니다. 처음 세 개가 삼각형 하나(또는 처음 네 개가 쿼드 하나)가 되고, 이후도 마찬가지입니다. 정점을 하나씩 쌓아 만든 스트립에 적합합니다.

두 가지는 오류를 내지 않고 허용됩니다. 마지막 정점을 넘어서는 인덱스는 그 삼각형이나 쿼드만 버려지고 나머지는 그려집니다. 끝에 남은 3개(또는 4개) 미만의 인덱스 묶음은 무시됩니다. 예전 인덱스 목록이 남아 있는 상태에서 정점을 다시 만드는 게임은 멈추지 않고 한 프레임 동안 삼각형 하나를 잃을 뿐입니다.


텍스처

world.spawn(x=40, y=40, mesh=Mesh(
    vertices=(0,   0,  0, 0, 1, 1, 1, 1,
              64,  0,  1, 0, 1, 1, 1, 1,
              64, 64,  1, 1, 1, 1, 1, 1,
              0,  64,  0, 1, 1, 1, 1, 1),
    image="tiles",
    mode=QUADS,
))

image는 프로젝트의 이미지 에셋 이름(확장자 제외)이며, Sprite와 같은 방식으로 찾습니다. 각 정점의 u와 v가 보여 줄 이미지의 지점을 고르고, 이미지는 그 사이에서 프리미티브에 늘어나 입혀집니다.

u와 v는 픽셀이 아니라 이미지 전체에 대한 비율입니다. 0.0–1.0 밖의 값은 타일처럼 반복되지 않고, 이미지 가장자리 픽셀을 반복합니다.


변환

메시의 정점은 엔티티에 대해 로컬입니다. 엔티티의 x, y, rotation, scale(부모가 있으면 부모의 것도)이 그릴 때 모든 정점에 적용됩니다. 지오메트리를 (0, 0) 주위에 만들고, 다른 엔티티처럼 이동하고 회전하고 크기를 조절하세요.

def start():
    world.spawn(name="diamond", x=160, y=90,
                mesh=Mesh(points=(-20, 0, 0, -30, 20, 0, 0, 30),
                          indices=(0, 1, 2, 3), mode=QUADS,
                          fill=(1.0, 0.6, 0.2, 1.0)))

def update():
    world.get(name="diamond").rotation += 1

Mesh 첨부

스폰 시:

e = world.spawn(x=100, y=60, mesh=Mesh(points=(0, 0, 50, 0, 25, 40)))

스폰은 지오메트리를 엔티티에 복사합니다. 그 뒤에는 만든 Mesh 객체와 엔티티의 연결이 끊어집니다. 살아 있는 지오메트리는 아래에서 설명하는 entity.mesh를 통해 바꾸세요. 같은 Mesh 객체를 여러 번 스폰해 독립적인 사본을 만들 수도 있습니다.


컴포넌트 속성

entity.mesh로 접근합니다.

멤버R/W설명
points = bufW평평한 (x, y, …) 시퀀스로 모든 정점의 위치를 교체합니다. 색과 u, v는 그대로 유지됩니다
vertices = bufW평평한 시퀀스(정점당 float 8개)로 모든 정점을 교체합니다. 정점 수가 바뀔 수 있습니다
indices = bufW인덱스 목록을 교체합니다
visible = flagW지오메트리를 잃지 않고 메시를 그리거나 건너뜁니다
modeRTRIANGLES 또는 QUADS
set_vertex(i, x, y, [u, v, [r, g, b, a]])정점 하나를 변경합니다. u, v와 그다음 r, g, b, a는 선택 사항이지만, 각 묶음은 모두 지정해야 합니다
count()정점의 수

mode는 메시를 만들 때 정해집니다.

e.mesh.points = (0, 0, 60, 0, 30, 50)    # 세 정점을 이동
e.mesh.set_vertex(1, 70, 5)               # 그다음 하나만 살짝 이동
e.mesh.visible = False                    # 숨기기

존재하지 않는 정점 번호로 set_vertex를 호출하면 오류이며, 메시가 없는 엔티티에서 entity.mesh를 사용하는 것도 오류입니다.

메시보다 짧은 points는 앞쪽 정점만 움직이고, 더 긴 points는 마지막 정점에서 잘립니다.


메시 애니메이션

매 프레임 움직이는 것에는 points를 사용하세요. 한 번의 대입으로 모든 위치가 교체되고 정점을 도는 루프는 엔진 안에서 실행됩니다. 같은 갱신을 set_vertex로 하면 정점마다 Python 호출이 한 번씩 필요합니다.

저렴해지는 것은 업로드이지 계산이 아닙니다. 새 위치는 여전히 매 프레임 직접 코드에서 계산해야 하고, 정점이 많은 메시에서는 시간이 거기에 쓰입니다. 한 번 만들어 엔티티와 함께 이동, 회전, 크기 조절만 하는 메시는 프레임당 비용이 거의 없고, 매 프레임 모양이 바뀌는 메시는 그 지오메트리를 계산하는 만큼의 비용이 듭니다.

이 물결치는 리본은 지오메트리를 한 번만 만든 다음 entity.mesh.points로 정점을 움직입니다. 에셋이 필요 없습니다.

from pyxen import *
import math

COLUMNS = 20
STEP = 14

def ribbon(t):
    points = []
    for i in range(COLUMNS + 1):
        y = math.sin(t * 0.08 + i * 0.5) * 14
        points.append(i * STEP)
        points.append(y - 12)     # 위쪽 가장자리
        points.append(i * STEP)
        points.append(y + 12)     # 아래쪽 가장자리
    return points

def start():
    vertices = []
    indices = []
    for i in range(COLUMNS + 1):
        r = i / COLUMNS
        # 이 열의 위쪽과 아래쪽 정점: x, y, u, v, r, g, b, a
        vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
        vertices.extend((0, 0, 0, 0, 1.0 - r, 0.4, r, 1.0))
    for i in range(COLUMNS):
        a = i * 2
        # 왼쪽 위, 왼쪽 아래, 오른쪽 아래, 오른쪽 위
        indices.extend((a, a + 1, a + 3, a + 2))
    world.spawn(name="ribbon", x=20, y=90,
                mesh=Mesh(vertices=vertices, indices=indices, mode=QUADS))

def update():
    world.get(name="ribbon").mesh.points = ribbon(time.frame)

FloatBuffer와 IntBuffer

메시는 튜플과 리스트로 만들 수 있지만, 지오메트리가 크거나 자주 다시 만들어야 할 때는 두 가지 버퍼 타입을 쓰면 매번 새 튜플을 만드는 비용을 피할 수 있습니다.

positions = FloatBuffer(64)     # 용량
positions.append(0.0)
positions.append(0.0)
positions[0] = 5.0              # 인덱스로 읽고 쓰기

indices = IntBuffer(64)
indices.append(0)
FloatBuffer(capacity)IntBuffer(capacity)
담는 것float: 위치, 정점정수: 인덱스
append(value)끝에 추가끝에 추가
clear()비움비움
buf[i]읽기와 대입읽기와 대입

capacity는 시작 크기이며, 그 이상 추가하면 버퍼가 늘어납니다. Mesh의 points=, vertices=, indices=로 전달하거나 entity.mesh의 해당 속성에 대입하세요.


Mesh와 도형

직사각형, 원, 삼각형, 선에는 도형이, 매 프레임 다시 그리는 그림에는 draw가 알맞습니다. 이들이 할 수 없는 일이 필요할 때 Mesh를 사용하세요. 직사각형이 아닌 텍스처 다각형, 모서리마다 다른 색, 깃발·도로·리본·수면처럼 정점이 각각 움직이는 표면 등입니다.


일반적인 에러

지오메트리 누락

Mesh(fill=(1, 0, 0, 1))
ValueError: Mesh needs points= or vertices=