Как повернуть квадрат вокруг оси X в 3D-пространстве

Поэтому я пытался узнать, как работает 3D-рендеринг. Я попытался написать скрипт с целью вращения плоского (2D) квадрата в 3D-пространстве. Я начал с определения квадрата в нормализованном пространстве (-1, 1). Обратите внимание, что нормализуются только x и y.

class Vec3:
    #  3D VECTOR
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

s = 1
p1 = Vec3(-s, -s, -s) 
p2 = Vec3(s, -s, -s)
p3 = Vec3(s, s, -s)
p4 = Vec3(-s, s, -s)

Затем перевел точки на экран:

p1.z += 6
p2.z += 6
p3.z += 6
p4.z += 6

Все после этого делается внутри цикла приложения. Я масштабировал точки на экране с применением проекции, используя функцию:

class Transform:
    # IT TRANSFORMS THE X AND Y FROM NORMALISED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED 
    def worldSpaceTransform(self, vec3, w, h):
        if vec3.z == 0:
            vec3.z = 0.001
        zInverse = 1/ vec3.z
        xTransformed = ((vec3.x * zInverse) + 1) * (w/2)
        yTransformed = ((-vec3.y * zInverse) + 1) * (h/2)
        xTransformed = str(xTransformed)[:6]
        yTransformed = str(yTransformed)[:6]
        return Vec2(float(xTransformed), float(yTransformed))

как это:

# TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE
    point1 = transform.worldSpaceTransform(p1, SCREENWIDTH, SCREENHEIGHT)
    point2 = transform.worldSpaceTransform(p2, SCREENWIDTH, SCREENHEIGHT)
    point3 = transform.worldSpaceTransform(p3, SCREENWIDTH, SCREENHEIGHT)
    point4 = transform.worldSpaceTransform(p4, SCREENWIDTH, SCREENHEIGHT)

и поставил точки:

# STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines
    points = ((point1.x, point1.y), (point2.x, point2.y),
              (point2.x, point2.y), (point3.x, point3.y),
              (point3.x, point3.y), (point4.x, point4.y),
              (point4.x, point4.y), (point1.x, point1.y))
    pygame.draw.lines(D, (0, 0, 0), False, points)

Пока все работает (я думаю), потому что он рисует квадрат, как и должно быть.

Теперь ротация. Я попробовал вращение для всех осей, и ни одна из них не работает, но для конкретики я расскажу об оси X. Ниже приведен класс вращения. Я скопировал матрицы вращения из википедии. Я не совсем уверен в том, как они работают, поэтому я также не знаю, совместимо ли это с системой, которую я описал выше.

def multVecMatrix(vec3, mat3):
    # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3  ? 
    x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2]
    y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2]
    z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[2][2]
    return Vec3(x, y, z)

class Rotation:
    def rotateX(self, theta):
        # ROTATION MATRIX IN X AXIS ??
        sinTheta = sin(theta)
        cosTheta = cos(theta)
        m = Mat3()
        m.matrix = [[1, 0,         0],
                    [0, cosTheta,  sinTheta],
                    [0, -sinTheta, cosTheta]]
        return m

    def rotate(self, vec3, theta, axis=None):
        # ROTATES A Vec3 BY GIVEN THETA AND AXIS ??
        if axis == "x":
            return multVecMatrix(vec3, self.rotateX(theta))
        if axis == "y":
            return multVecMatrix(vec3, self.rotateY(theta))
        if axis == "z":
            return multVecMatrix(vec3, self.rotateZ(theta)) 

И вызывается он так после заливки экрана белым и перед масштабированием точек из нормализованного пространства в пространство экрана.

    # screen is filled with white color
    # ROTATING THE POINTS AROUND X AXIS ?????

    p1.x = rotation.rotate(p1, thetax, axis='x').x
    p1.y = rotation.rotate(p1, thetay, axis='x').y
    p1.z = rotation.rotate(p1, thetax, axis='x').z

    p2.x = rotation.rotate(p2, thetax, axis='x').x
    p2.y = rotation.rotate(p2, thetay, axis='x').y
    p2.z = rotation.rotate(p2, thetax, axis='x').z

    p3.x = rotation.rotate(p3, thetax, axis='x').x
    p3.y = rotation.rotate(p3, thetay, axis='x').y
    p3.z = rotation.rotate(p3, thetax, axis='x').z

    p4.x = rotation.rotate(p4, thetax, axis='x').x
    p4.y = rotation.rotate(p4, thetay, axis='x').y
    p4.z = rotation.rotate(p4, thetax, axis='x').z
    
    # then the points are translated into world space

После применения поворота кажется, что он движется и вращается вокруг оси X, но не вращается. Я хочу, чтобы он вращался, оставаясь на месте. Что я делаю не так?

Полный код копирования и вставки для справки:

import pygame
from math import sin, cos, radians
pygame.init()

### PYGAME STUFF ######################################

SCREENWIDTH = 600
SCREENHEIGHT = 600
D = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("PRESS SPACE TO ROTATE AROUND X")

######### MATH FUNCTIONS AND CLASSES ####################

class Mat3:
    # 3X3 MATRIX INITIALIZED WITH ALL 0's
    def __init__(self):
        self.matrix = [[0 for i in range(3)],
                      [0 for i in range(3)],
                      [0 for i in range(3)]]

class Vec2:
    # 2D VECTOR
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Vec3:
    #  3D VECTOR
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

def multVecMatrix(vec3, mat3):
    # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3 
    x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2]
    y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2]
    z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[1][2]
    return Vec3(x, y, z)

class Transform:
    # IT TRANSFORMS THE X AND Y FROM NORMALIZED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED
    def worldSpaceTransform(self, vec3, w, h):
        if vec3.z == 0:
            vec3.z = 0.001
        zInverse = 1/ vec3.z
        xTransformed = ((vec3.x * zInverse) + 1) * (w/2)
        yTransformed = ((-vec3.y * zInverse) + 1) * (h/2)
        xTransformed = str(xTransformed)[:6]
        yTransformed = str(yTransformed)[:6]
        return Vec2(float(xTransformed), float(yTransformed))

class Rotation:
    def rotateX(self, theta):
        # ROTATION MATRIX IN X AXIS
        sinTheta = sin(theta)
        cosTheta = cos(theta)
        m = Mat3()
        m.matrix = [[1, 0,         0],
                    [0, cosTheta,  sinTheta],
                    [0, -sinTheta, cosTheta]]
        return m

    def rotate(self, vec3, theta, axis=None):
        # ROTATES A Vec3 BY GIVEN THETA AND AXIS
        if axis == "x":
            return multVecMatrix(vec3, self.rotateX(theta))
        if axis == "y":
            return multVecMatrix(vec3, self.rotateY(theta))
        if axis == "z":
            return multVecMatrix(vec3, self.rotateZ(theta))

transform = Transform()
rotation = Rotation()


# ASSIGNING 4 Vec3's FOR 4 SIDES OF SQUARE IN NORMALIZED SPACE
s = 1
p1 = Vec3(-s, -s, -s) 
p2 = Vec3(s, -s, -s)
p3 = Vec3(s, s, -s)
p4 = Vec3(-s, s, -s)

# TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
p1.z += 6
p2.z += 6
p3.z += 6
p4.z += 6

# ASSIGNING THE ROTATION ANGLES
thetax = 0

# APPLICATION LOOP
while True:
    pygame.event.get()
    D.fill((255, 255, 255))


    # ROTATING THE POINTS AROUND X AXIS

    p1.x = rotation.rotate(p1, thetax, axis='x').x
    p1.y = rotation.rotate(p1, thetax, axis='x').y
    p1.z = rotation.rotate(p1, thetax, axis='x').z

    p2.x = rotation.rotate(p2, thetax, axis='x').x
    p2.y = rotation.rotate(p2, thetax, axis='x').y
    p2.z = rotation.rotate(p2, thetax, axis='x').z

    p3.x = rotation.rotate(p3, thetax, axis='x').x
    p3.y = rotation.rotate(p3, thetax, axis='x').y
    p3.z = rotation.rotate(p3, thetax, axis='x').z

    p4.x = rotation.rotate(p4, thetax, axis='x').x
    p4.y = rotation.rotate(p4, thetax, axis='x').y
    p4.z = rotation.rotate(p4, thetax, axis='x').z
    

    # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE
    point1 = transform.worldSpaceTransform(p1, SCREENWIDTH, SCREENHEIGHT)
    point2 = transform.worldSpaceTransform(p2, SCREENWIDTH, SCREENHEIGHT)
    point3 = transform.worldSpaceTransform(p3, SCREENWIDTH, SCREENHEIGHT)
    point4 = transform.worldSpaceTransform(p4, SCREENWIDTH, SCREENHEIGHT)

    # STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines
    points = ((point1.x, point1.y), (point2.x, point2.y),
              (point2.x, point2.y), (point3.x, point3.y),
              (point3.x, point3.y), (point4.x, point4.y),
              (point4.x, point4.y), (point1.x, point1.y))

    
    keys = pygame.key.get_pressed()
    # ROTATE X ?
    if keys[pygame.K_SPACE]:
        thetax -= 0.005

    pygame.draw.lines(D, (0, 0, 0), False, points)
    
    pygame.display.flip()

person Community    schedule 29.08.2020    source источник
comment
В multVecMatrix есть ошибка. Последний продукт должен быть vec3.z * mat3.matrix[2][2], но это vec3.z * mat3.matrix[1][2]   -  person Mauricio Cele Lopez Belon    schedule 30.08.2020
comment
Спасибо, что указали на это, но это все еще не работает так, как я хочу. В идеале я хочу, чтобы он оставался на месте и вращался, но теперь он выглядит так, как будто он движется повсюду.   -  person    schedule 30.08.2020


Ответы (1)


Нет необходимости вращать каждый компонент вектора отдельно. Если вы сделаете

p1.x = rotation.rotate(p1, thetax, axis='x').x

тогда компонент x p1 изменился, а p1, который передается следующей инструкции, отличается

p1.y = rotation.rotate(p1, thetay, axis='x').y

Достаточно один раз повернуть все вершины:

p1 = rotation.rotate(p1, thetax, axis='x')  
p2 = rotation.rotate(p2, thetax, axis='x')
p3 = rotation.rotate(p3, thetax, axis='x')
p4 = rotation.rotate(p4, thetax, axis='x')

Когда вы умножаете вектор на матрицу вращения, вектор поворачивается на один раунд (0, 0, 0). Вы должны сделать перевод после поворота.
Добавьте +-оператор в класс Vec3:

class Vec3:
    #  3D VECTOR
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __add__(a, b):
        return Vec3(a.x+b.x, a.y+b.y, a.z+b.z)

Никогда не меняйте исходные координаты вершины p1, p2, p3 и p4. Вычислите вращение, а затем перевод:

# TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
#p1.z += 6 <--- DELETE
#p2.z += 6
#p3.z += 6
#p4.z += 6
transVec = Vec3(0, 0, 6)

# [...]

while run:

    # ROTATING THE POINTS AROUND X AXIS
    point1 = rotation.rotate(p1, thetax, axis='x')  
    # [...]

    # TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
    point1 = point1 + transVec
    # [...]

    # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE
    point1 = transform.worldSpaceTransform(point1, SCREENWIDTH, SCREENHEIGHT)
    # [...]

Я рекомендую организовать координаты вершин в списках:

# ASSIGNING 4 Vec3's FOR 4 SIDES OF SQUARE IN NORMALIZED SPACE
s = 1
modelPoints = [Vec3(-s, -s, -s), Vec3(s, -s, -s), Vec3(s, s, -s), Vec3(-s, s, -s)]

# TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
transVec = Vec3(0, 0, 6)

# ASSIGNING THE ROTATION ANGLES
thetax = 0

# APPLICATION LOOP
run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    D.fill((255, 255, 255))

    # ROTATING THE POINTS AROUND X AXIS
    points = [rotation.rotate(pt, thetax, axis='x') for pt in modelPoints]

    # TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
    points = [pt + transVec for pt in points]

    # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE
    points = [transform.worldSpaceTransform(pt, SCREENWIDTH, SCREENHEIGHT) for pt in points]

    # STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines
    points = [(pt.x, pt.y) for pt in points]

См. полный пример:

import pygame
from math import sin, cos, radians
pygame.init()

### PYGAME STUFF ######################################

SCREENWIDTH = 600
SCREENHEIGHT = 600
D = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("PRESS SPACE TO ROTATE AROUND X")

######### MATH FUNCTIONS AND CLASSES ####################

class Mat3:
    # 3X3 MATRIX INITIALIZED WITH ALL 0's
    def __init__(self):
        self.matrix = [[0 for i in range(3)],
                      [0 for i in range(3)],
                      [0 for i in range(3)]]

class Vec2:
    # 2D VECTOR
    def __init__(self, x, y):
        self.x = x
        self.y = y

class Vec3:
    #  3D VECTOR
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __add__(a, b):
        return Vec3(a.x+b.x, a.y+b.y, a.z+b.z)

def multVecMatrix(vec3, mat3):
    # MULTIPLIES A Vec3 OBJECT WITH Mat3 OBJECT AND RETURNS A NEW Vec3 
    x = vec3.x * mat3.matrix[0][0] + vec3.y * mat3.matrix[0][1] + vec3.z * mat3.matrix[0][2]
    y = vec3.x * mat3.matrix[1][0] + vec3.y * mat3.matrix[1][1] + vec3.z * mat3.matrix[1][2]
    z = vec3.x * mat3.matrix[2][0] + vec3.y * mat3.matrix[2][1] + vec3.z * mat3.matrix[2][2]
    return Vec3(x, y, z)

class Transform:
    # IT TRANSFORMS THE X AND Y FROM NORMALIZED SPACE TO SCREEN SPACE WITH PROJECTION APPLIED
    def worldSpaceTransform(self, vec3, w, h):
        if vec3.z == 0:
            vec3.z = 0.001
        zInverse = 1/ vec3.z
        xTransformed = ((vec3.x * zInverse) + 1) * (w/2)
        yTransformed = ((-vec3.y * zInverse) + 1) * (h/2)
        xTransformed = str(xTransformed)[:6]
        yTransformed = str(yTransformed)[:6]
        return Vec2(float(xTransformed), float(yTransformed))

class Rotation:
    def rotateX(self, theta):
        # ROTATION MATRIX IN X AXIS
        sinTheta = sin(theta)
        cosTheta = cos(theta)
        m = Mat3()
        m.matrix = [[1, 0,         0],
                    [0, cosTheta,  sinTheta],
                    [0, -sinTheta, cosTheta]]
        return m

    def rotate(self, vec3, theta, axis=None):
        # ROTATES A Vec3 BY GIVEN THETA AND AXIS
        if axis == "x":
            return multVecMatrix(vec3, self.rotateX(theta))
        if axis == "y":
            return multVecMatrix(vec3, self.rotateY(theta))
        if axis == "z":
            return multVecMatrix(vec3, self.rotateZ(theta))


transform = Transform()
rotation = Rotation()


# ASSIGNING 4 Vec3's FOR 4 SIDES OF SQUARE IN NORMALIZED SPACE
s = 1
modelPoints = [Vec3(-s, -s, -s), Vec3(s, -s, -s), Vec3(s, s, -s), Vec3(-s, s, -s)]

# TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
transVec = Vec3(0, 0, 6)

# ASSIGNING THE ROTATION ANGLES
thetax = 0

# APPLICATION LOOP
run = True
while run:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False

    D.fill((255, 255, 255))

    # ROTATING THE POINTS AROUND X AXIS
    points = [rotation.rotate(pt, thetax, axis='x') for pt in modelPoints]

    # TRANSLATING THE POINTS OF THE CUBE A LITTLE BIT INTO THE SCREEN
    points = [pt + transVec for pt in points]

    # TRANSLATING THE SQUARE SHEET INTO THE SCREEN SPACE
    points = [transform.worldSpaceTransform(pt, SCREENWIDTH, SCREENHEIGHT) for pt in points]

    # STORING THE POINTS TO A TUPLE SO IT CAN BE DRAWN USING pygame.draw.lines
    points = [(pt.x, pt.y) for pt in points]
    
    keys = pygame.key.get_pressed()
    # ROTATE X ?
    if keys[pygame.K_SPACE]:
        thetax -= 0.005

    pygame.draw.lines(D, (0, 0, 0), True, points)
    
    pygame.display.flip()
person Rabbid76    schedule 30.08.2020