Introdução à programação
com Python em um contexto visual


Como desenhar em 3D

Nessa página vamos ver como desenhar objetos tridimensionais, ou ainda objetos 2D no espaço

Primeiros passos

Para começar, dentro do setup() é preciso usar o argumento P3D em size(), como neste exemplo:

def setup():
    size(500, 500, P3D)

Passa então a ser possivel modificar o sistema de coordenadas com translate(x, y, z) (agora com z) e rotate_x(), rotate_y() e rotate_z() (que giram em torno dos eixos).

Desta maneira é possível desenhar os mesmos elementos 2D que utilizamos até agora, porém deslocados e girados no espaço. Mas além dessa estratégia, também acontecem as seguintes mudanças:

Alguns objetos desenhados em 3D

imagem exemplo 3D


def setup():
    size(500, 500, P3D)
    rect_mode(CENTER)

def draw():
    background(200)
    lights()
    translate(width / 2, height / 2)
    rotate_x(radians(frame_count))
    stroke(255)
    fill(0, 200, 0)
    caixa(50, 50, 0, 20, 100, 50)
    fill(255, 0, 0)
    caixa(20, 20, -20, 20)
    fill(0, 0, 200)
    caixa(0, 20, 20, 20)
    stroke(0)
    line(0, 0, 0, 50, 50, 100)

    begin_shape()
    vertex(0, 0, 0)
    vertex(100, 100, 100)
    vertex(100, -100, 50)
    end_shape(CLOSE)

    # rotateY(QUARTER_PI + radians(frameCount))
    translate(0, 0, 100)
    fill(255)
    rect(0, 0, 100, 100)

def caixa(x, y, z, *tam):
    push_matrix()
    translate(x, y, z)
    box(*tam)
    pop_matrix()

Desenhando uma caixa entre dois pontos

Usando um volume box() girado em 3D.

def setup():
    size(500, 500, P3D)
    hint(DISABLE_DEPTH_MASK)  # https://processing.org/reference/hint_.html

def draw():
    background(200)
    lights()
    fill(255, 0, 0, 100)
    bar_line(50, 50, 0, mouse_x, mouse_y, 250, 30)
    bar_line(450, 450, 0, 250, 250, 250, 30)

def bar_line(x1, y1, z1, x2, y2, z2, weight=10):
    """Draw a box rotated in 3D like a bar_line/edge."""
    p1, p2=(x1, y1, z1), (x2, y2, z2)
    dist_p1_p2=dist(x1, y1, z1, x2, y2, z2)
    v1=(x2 - x1, y2 - y1, z2 - z1)
    phi, the=acos(v1[2] / dist_p1_p2), atan2(v1[1], v1[0])
    push_matrix()
    translate(x1 + v1[0] / 2.0, y1 + v1[1] / 2.0, z1 + v1[2] / 2.0)
    rotate_z(the)
    rotate_y(phi)
    box(weight, weight, dist_p1_p2)
    pop_matrix()

Desenhando um prisma girado em 3D

def prism_line(x1, y1, z1, x2, y2, z2, radius=10, num_points=6):
    """
    Desenha um prisma girado em 3D como uma barra.
    Requer as funções auxiliares draw_face e z_circle.
    """
    p1, p2=(x1, y1, z1), (x2, y2, z2)
    dist_p1_p2=dist(x1, y1, z1, x2, y2, z2)
    v1=(x2 - x1, y2 - y1, z2 - z1)
    phi, the=acos(v1[2] / dist_p1_p2), atan2(v1[1], v1[0])
    push_matrix()
    translate(x1 + v1[0] / 2.0, y1 + v1[1] / 2.0, z1 + v1[2] / 2.0)
    rotate_z(the)
    rotate_y(phi)
    # box(radius, radius, dist_p1_p2) # antiga barra
    rotate_x(HALF_PI)  # deixa circlulos perpendiculares a direção da barra
    base=z_circle(0, -dist_p1_p2 / 2.0, radius, num_points)
    draw_face(base)  # fechamento base
    top=z_circle(0, dist_p1_p2 / 2.0, radius, num_points)
    draw_face(top)  # fechamento topo
    pairs=zip(top, base)
    for i, (point_a, point_b) in enumerate(pairs):
        point_c, point_d=pairs[i - 1]
        face=(point_a, point_b, point_d, point_c)
        draw_face(face)  # faces laterais
    pop_matrix()

def draw_face(points):
    begin_shape()
    for x, y, z in points:
        vertex(x, y, z)
    end_shape(CLOSE)

def z_circle(x, y, radius, num_points=16):
    a=TWO_PI / num_points
    return [(x + cos(a * i) * radius, y, sin(a * i) * radius)
             for i in range(num_points)]

Carregando arquivos OBJ externos

Usando size() com P3D é possível carregar arquivos OBJ com a função load_shape('arquivo.obj') e mostrá-los com shape(s, x, y). Procure o exemplo que vem no IDE na janela de exemplos: Basic > Shape > LoadDisplayOBJ.

Explorando ajustes avançados com a função hint()

É possível controlar alguns aspectos do cálculo da apresentação 3D, meio que um ajuste fino, que tem impacto na performance (velocidade de execução) e aparência.

O exemplo abaixo permite testar algos dos ajustes, conhecidos como hints, do motor de apresentação (render engine) P3D, mostrando a diferença entre as opções documentadas em py5coding.org/reference/sketch_hint.html.

# Inspirado na conversa em:
# https://discourse.processing.org/t/program-to-test-hint-with-transparency/4361

# ["name", hint_enabled, hint_disable_constant, hint_enable_constant]
hints=(["DEPTH_TEST", False,
          DISABLE_DEPTH_TEST, ENABLE_DEPTH_TEST],
         ["DEPTH_SORT", False,
          DISABLE_DEPTH_SORT, ENABLE_DEPTH_SORT],
         ["DEPTH_MASK", False,
          DISABLE_DEPTH_MASK, ENABLE_DEPTH_MASK],
         ["OPTIMIZED_STROKE", True,
          DISABLE_OPTIMIZED_STROKE, ENABLE_OPTIMIZED_STROKE],
         ["STROKE_PERSPECTIVE", False,
          DISABLE_STROKE_PERSPECTIVE, ENABLE_STROKE_PERSPECTIVE],
         )
use_sphere=False

def setup():
    size(800, 600, P3D)
    sphere_detail(12)
    apply_hints()

def draw():
    window_title('FPS: {}'.format(round(get_frame_rate())))
    background(255)

    fill(0)
    for i, (name, status, _, _) in enumerate(hints):
        text("{} {}".format(name, str(status)), 20, 20 + i * 20)
    text("<- use the mouse to toggle settings", 200, 40)
    text("click here to toggle shape", 300, 580)

    fill(255, 40, 20, 100)
    translate(width / 2, height / 2)
    rotate_y(frame_count * 0.005)
    for x in range(-200, 201, 200):
        for y in range(-200, 201, 200):
            push_matrix()
            translate(x, 0, y)
            if use_sphere:
                sphere(90)
            else:
                box(180)
            pop_matrix()

def mouse_pressed():
    global use_sphere
    if mouse_y > height - 100:
        use_sphere=not use_sphere

    id = mouse_y // 20
    if id < len(hints):
        hints[id][1] = not hints[id][1]

    apply_hints()

def apply_hints():
    for _, status, disable_const, enable_const in hints:
        hint(enable_const if status else disable_const)

Assuntos relacionados