Перспективна проекция и ротация в python

Опитах да търся, но никой от другите въпроси не изглежда като моя. Повече или по-малко експериментирам с перспективна проекция и ротация в Python и се натъкнах на проблем. Сигурен съм, че моите проекционни уравнения са точни, както и уравненията ми за въртене; обаче, когато го стартирам, въртенето започва нормално, но започва да се върти навътре, докато векторът е в същата позиция като оста Z (оста, над която се въртя).

''' Imports '''
from tkinter import Tk, Canvas, TclError
from threading import Thread
from math import cos, sin, radians, ceil
from time import sleep

''' Points class '''
class pPoint:
    def __init__(self, fPoint, wWC, wHC):
        self.X = 0
        self.Y = 0
        self.Z = 0
        self.xP = 0
        self.yP = 0
        self.fPoint = fPoint
        self.wWC = wWC
        self.wHC = wHC

    def pProject(self):
        self.xP = (self.fPoint * (self.X + self.wWC)) / (self.fPoint + self.Z)
        self.yP = (self.fPoint * (self.Y + self.wHC)) / (self.fPoint + self.Z)

''' Main class '''
class Main:
    def __init__(self):
        ''' Declarations '''
        self.wWidth = 640
        self.wHeight = 480

        self.fPoint = 256

        ''' Generated declarations '''
        self.wWC = self.wWidth / 2
        self.wHC = self.wHeight / 2

        ''' Misc declarations '''
        self.gWin = Tk()

        self.vPoint = pPoint(self.fPoint, self.wWC, self.wHC)

        self.vPoint.X = 50
        self.vPoint.Y = 60
        self.vPoint.Z = -25
        self.vPoint.pProject()

        self.ang = 0

    def initWindow(self):
        self.gWin.minsize(self.wWidth, self.wHeight)
        self.gWin.maxsize(self.wWidth, self.wHeight)

        ''' Create canvas '''
        self.gCan = Canvas(self.gWin, width = self.wWidth, height = self.wHeight, background = "black")
        self.gCan.pack()

    def setAxis(self):
        ''' Create axis points '''
        self.pXax = pPoint(self.fPoint, self.wWC, self.wHC)
        self.pXbx = pPoint(self.fPoint, self.wWC, self.wHC)
        self.pYax = pPoint(self.fPoint, self.wWC, self.wHC)
        self.pYbx = pPoint(self.fPoint, self.wWC, self.wHC)
        self.pZax = pPoint(self.fPoint, self.wWC, self.wHC)
        self.pZbx = pPoint(self.fPoint, self.wWC, self.wHC)

        ''' Set axis points '''
        self.pXax.X = -(self.wWC)
        self.pXax.Y = 0
        self.pXax.Z = 1
        self.pXbx.X = self.wWC
        self.pXbx.Y = 0
        self.pXbx.Z = 1
        self.pYax.X = 0
        self.pYax.Y = -(self.wHC)
        self.pYax.Z = 1
        self.pYbx.X = 0
        self.pYbx.Y = self.wHC
        self.pYbx.Z = 1
        self.pZax.X = 0
        self.pZax.Y = 0
        self.pZax.Z = -(self.fPoint) / 2
        self.pZbx.X = 0
        self.pZbx.Y = 0
        self.pZbx.Z = (self.fPoint * self.wWC) - self.fPoint

    def projAxis(self):
        ''' Project the axis '''
        self.pXax.pProject()
        self.pXbx.pProject()
        self.pYax.pProject()
        self.pYbx.pProject()
        self.pZax.pProject()
        self.pZbx.pProject()

    def drawAxis(self):
        ''' Draw the axis '''
        self.gCan.create_line(self.pXax.xP, self.pXax.yP, self.pXbx.xP, self.pXbx.yP, fill = "white")
        self.gCan.create_line(self.pYax.xP, self.pYax.yP, self.pYbx.xP, self.pYbx.yP, fill = "white")
        self.gCan.create_line(self.pZax.xP, self.pZax.yP, self.pZbx.xP, self.pZbx.yP, fill = "white")

    def prePaint(self):
        self.vA = self.gCan.create_line(self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP, fill = "red")

    def paintCanvas(self):
        try:
            while True:
                self.ang += 1
                if self.ang >= 361:
                    self.ang = 0

                self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))) - (self.vPoint.Y * sin(radians(self.ang))) 
                self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))) + (self.vPoint.Y * cos(radians(self.ang)))
                self.vPoint.pProject()

                self.gCan.coords(self.vA, self.wWC, self.wHC, self.vPoint.xP, self.vPoint.yP)

                self.gWin.update_idletasks()
                self.gWin.update()

                sleep(0.1)
        except TclError:
            pass

mMain = Main()

mMain.initWindow()
mMain.setAxis()
mMain.projAxis()
mMain.drawAxis()
mMain.prePaint()
mMain.paintCanvas()

Благодаря ви за всякакво участие :)

РЕДАКТИРАНЕ: Съжалявам, току-що осъзнах, че съм забравил да задам въпроса си. Просто искам да знам защо гравитира навътре, а не просто се върти "нормално"?


person user1920386    schedule 21.12.2012    source източник
comment
Виждате ефекта от натрупването на грешки с плаваща запетая, тъй като self.vPoint се извлича от старата си стойност, но използва неточни числа с плаваща запетая. Трябва често да ортогонализирате матрицата, за да предотвратите излизането на тези грешки извън контрол.   -  person Eric    schedule 21.12.2012
comment
ААА добре. Така че трябва, да речем, всеки път, когато тита се върне на 0 градуса, да нулирам X и Y обратно към първоначалните им величини?   -  person user1920386    schedule 21.12.2012
comment
Или изобщо не изчислявайте вектори въз основа на техните стари вектори. Имайте само един или два ъгъла, изчислени от техните стари стойности, където натрупването на грешки с плаваща запетая няма значение. След това преизчислете всички вектори от тези ъгли.   -  person Armin Rigo    schedule 21.12.2012


Отговори (2)


Този раздел е грешен:

self.ang += 1
if self.ang >= 361:
    self.ang = 0

self.vPoint.X = (self.vPoint.X * cos(radians(self.ang))
               - self.vPoint.Y * sin(radians(self.ang))) 
self.vPoint.Y = (self.vPoint.X * sin(radians(self.ang))
               + self.vPoint.Y * cos(radians(self.ang)))
self.vPoint.pProject()

По две причини:

  1. self.ang ще приеме цели числа в отворения диапазон [0 - 360], което означава, че ъгълът 360 (== 0) се повтаря.
  2. In each iteration, you rotate the point from the previous iteration by the angle. As a result, your first frame is at 1 degree, your second at 1+2 = 3, the third at 1 + 2 + 3... You should either be:
    • rotating the point from the previous iteration by a constant angle each time (1°). This suffers from the problem mentioned in my comment
    • завъртане на началната точка с текущия ъгъл на завъртане всеки път
person Eric    schedule 21.12.2012
comment
Вариант 2.1 (въртяща се начална точка) е правилен. Ако вашият обект не се променя, но ъгълът също, тогава извършвайте завъртането винаги върху обекта на позицията на базовата линия, вместо да го завъртате постепенно (натрупва грешки при интегриране). Изчислителната цена е същата. - person heltonbiker; 22.12.2012

Всъщност не е свързано с вашия проблем, но силно ви препоръчвам да използвате Numpy за извършване на геометрични трансформации, особено ако включва 3D точки.

По-долу публикувам примерен фрагмент, надявам се да помогне:

import numpy
from math import radians, cos, sin

## suppose you have a Nx3 cloudpoint (it might even be a single row of x,y,z coordinates)
cloudpoint = give_me_a_cloudpoint()

## this will be a rotation around Y azis:
yrot = radians(some_angle_in_degrees)

## let's create a rotation matrix using a numpy array
yrotmatrix = numpy.array([[cos(yrot), 0, -sin(yrot)],
                          [0,         1,          0],
                          [sin(yrot), 0,  cos(yrot)]], dtype=float)


## apply the rotation via dot multiplication
rotatedcloud = numpy.dot(yrotmatrix, pointcloud.T).T   # .T means transposition
person heltonbiker    schedule 21.12.2012
comment
Как може ефективно да се мащабира това до по-големи облаци от точки? Мислех просто да увелича размерите на yrotmatrix, но не съм сигурен как да мащабирам ъгловите стойности до нулите в средата. - person n1k31t4; 13.11.2017
comment
@DexterMorgan Не съм сигурен, че разбирам съмнението ви. Обикновено увеличаването на мащаба би означавало само, че имате по-голям pointcloud (Nx3 матрица с по-малък N) и човек би очаквал операцията да отнеме повече време, но не вярвам, че ще отнеме толкова повече време, освен за изключително големи облаци от точки (да речем , милиони точки). Също така не разбрах какво имате предвид под мащабиране на ъгловите стойности до нулите в средата. Както и да е, никога няма да имате нужда от по-голямо yrotmatrix, тъй като ТРЯБВА да бъде матрица 3x3 по дефиниция. - person heltonbiker; 13.11.2017
comment
Нямах предвид нищо по отношение на производителността, по-скоро: как би изглеждала ротационната матрица, ако облакът от точки беше да речем 10x10? - person n1k31t4; 13.11.2017
comment
Е, това е интересен въпрос. Моят отговор приема, че облак от точки в 3D пространство е Nx3 матрица, където N е броят на точките, а 3 са размерите (x, y, z). Когато пишем за облак от точки 10x10, това може да означава две неща: или набор от 10 точки с 10 измерения (координати) всяка, което намирам за малко вероятно, или масив от 10x10 3D точки. В последния случай бихте използвали numpy.reshape, за да го направите в масив 100x3. В първия случай ще ви е необходима ротационна матрица 10x10. Но се чудя какво би било практическото значение на 10-измерно точково пространство. - person heltonbiker; 13.11.2017
comment
Интересувам се от завъртане на някои изображения чрез този подход, така че имам напр. (100, 100, 3) RGB масив. Ще има ли смисъл преформатирането, завъртането и обратното оформяне? ... относно последната ви точка: Може би нещо, включващо теорията на струните, може да намери приложение за толкова много измерения :) - person n1k31t4; 13.11.2017
comment
@DexterMorgan погледнете scipy.ndimage.interpolate.rotate функция! - person heltonbiker; 13.11.2017