Как создать торический узел? - PullRequest
0 голосов
/ 14 января 2019

Позвольте мне начать вопрос с предоставления некоторого стандартного кода, который мы будем использовать для игры:

mcve_framework.py

import time

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

import glm
from glm import unProject
from glm import vec2
from glm import vec3
from glm import vec4


# -------- Camera --------
class BaseCamera():

    def __init__(
        self,
        eye=None, target=None, up=None,
        fov=None, near=0.1, far=100000
    ):
        self.eye = eye or glm.vec3(0, 0, 1)
        self.target = target or glm.vec3(0, 0, 0)
        self.up = up or glm.vec3(0, 1, 0)
        self.original_up = glm.vec3(self.up)
        self.fov = fov or glm.radians(45)
        self.near = near
        self.far = far

    def update(self, aspect):
        self.view = glm.lookAt(
            self.eye, self.target, self.up
        )
        self.projection = glm.perspective(
            self.fov, aspect, self.near, self.far
        )

    # def zoom(self, *args):
    #     delta = -args[1] * 0.1
    #     self.eye = self.target + (self.eye - self.target) * (delta + 1)

    def zoom(self, *args):
        x = args[2]
        y = args[3]
        v = glGetIntegerv(GL_VIEWPORT)
        viewport = vec4(float(v[0]), float(v[1]), float(v[2]), float(v[3]))
        height = viewport.w

        pt_wnd = vec3(x, height - y, 1.0)
        pt_world = unProject(pt_wnd, self.view, self.projection, viewport)
        ray_cursor = glm.normalize(pt_world - self.eye)

        delta = args[1] * 10
        self.eye = self.eye + ray_cursor * delta
        self.target = self.target + ray_cursor * delta

    def load_projection(self):
        width = glutGet(GLUT_WINDOW_WIDTH)
        height = glutGet(GLUT_WINDOW_HEIGHT)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(glm.degrees(self.fov), width / height, self.near, self.far)

    def load_modelview(self):
        e = self.eye
        t = self.target
        u = self.up

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt(e.x, e.y, e.z, t.x, t.y, t.z, u.x, u.y, u.z)


class Camera(BaseCamera):

    def rotate_target(self, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        M = glm.mat4(1)
        M = glm.translate(M, self.eye)
        M = glm.rotate(M, delta.y, right)
        M = glm.rotate(M, delta.x, self.up)
        M = glm.translate(M, -self.eye)
        self.target = glm.vec3(M * glm.vec4(self.target, 1.0))

    def rotate_around_target(self, target, delta):
        right = glm.normalize(glm.cross(self.target - self.eye, self.up))
        ammount = (right * delta.y + self.up * delta.x)
        M = glm.mat4(1)
        M = glm.rotate(M, ammount.z, glm.vec3(0, 0, 1))
        M = glm.rotate(M, ammount.y, glm.vec3(0, 1, 0))
        M = glm.rotate(M, ammount.x, glm.vec3(1, 0, 0))
        self.eye = glm.vec3(M * glm.vec4(self.eye, 1.0))
        self.target = target
        self.up = self.original_up

    def rotate_around_origin(self, delta):
        return self.rotate_around_target(glm.vec3(0), delta)


class GlutController():

    FPS = 0
    ORBIT = 1

    def __init__(self, camera, velocity=100, velocity_wheel=100):
        self.velocity = velocity
        self.velocity_wheel = velocity_wheel
        self.camera = camera

    def glut_mouse(self, button, state, x, y):
        self.mouse_last_pos = vec2(x, y)
        self.mouse_down_pos = vec2(x, y)

        if button == GLUT_LEFT_BUTTON:
            self.mode = self.FPS
        elif button == GLUT_RIGHT_BUTTON:
            self.mode = self.ORBIT

    def glut_motion(self, x, y):
        pos = vec2(x, y)
        move = self.mouse_last_pos - pos
        self.mouse_last_pos = pos

        if self.mode == self.FPS:
            self.camera.rotate_target(move * 0.005)
        elif self.mode == self.ORBIT:
            self.camera.rotate_around_origin(move * 0.005)

    def glut_mouse_wheel(self, *args):
        self.camera.zoom(*args)


# -------- Miscelanea --------
def render_text(x, y, text):
    glColor3f(1, 1, 1)
    glRasterPos2f(x, y)
    glutBitmapString(GLUT_BITMAP_TIMES_ROMAN_24, text.encode("utf-8"))


def line(p0, p1, color=None):
    c = color or glm.vec3(1, 1, 1)
    glColor3f(c.x, c.y, c.z)
    glVertex3f(p0.x, p0.y, p0.z)
    glVertex3f(p1.x, p1.y, p1.z)


def grid(segment_count=10, spacing=1, yup=True):
    size = segment_count * spacing
    right = glm.vec3(1, 0, 0)
    forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
    x_axis = right * size
    z_axis = forward * size

    i = -segment_count

    glBegin(GL_LINES)
    while i <= segment_count:
        p0 = -x_axis + forward * i * spacing
        p1 = x_axis + forward * i * spacing
        line(p0, p1)
        p0 = -z_axis + right * i * spacing
        p1 = z_axis + right * i * spacing
        line(p0, p1)
        i += 1
    glEnd()


def axis(size=1.0, yup=True):
    right = glm.vec3(1, 0, 0)
    forward = glm.vec3(0, 0, 1) if yup else glm.vec3(0, 1, 0)
    x_axis = right * size
    z_axis = forward * size
    y_axis = glm.cross(forward, right) * size
    glBegin(GL_LINES)
    line(x_axis, glm.vec3(0, 0, 0), glm.vec3(1, 0, 0))
    line(y_axis, glm.vec3(0, 0, 0), glm.vec3(0, 1, 0))
    line(z_axis, glm.vec3(0, 0, 0), glm.vec3(0, 0, 1))
    glEnd()


# -------- Mcve --------
class BaseWindow:

    def __init__(self, w, h):
        self.width = w
        self.height = h

        glutInit()
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
        glutInitWindowSize(w, h)
        glutCreateWindow('OpenGL Window')

        self._startup()

        glutReshapeFunc(self.reshape)
        glutDisplayFunc(self._display)
        glutMouseFunc(self.controller.glut_mouse)
        glutMotionFunc(self.controller.glut_motion)
        glutMouseWheelFunc(self.controller.glut_mouse_wheel)
        glutKeyboardFunc(self.keyboard_func)
        glutIdleFunc(self.idle_func)

    def keyboard_func(self, *args):
        try:
            key = args[0].decode("utf8")

            if key == "\x1b":
                glutLeaveMainLoop()

            if key in ['1']:
                if key == '1':
                    self.index_camera = "BPL"

                self.camera = self.cameras[self.index_camera]
                self.controller.camera = self.camera
        except Exception as e:
            import traceback
            traceback.print_exc()

    def display(self):
        pass

    def startup(self):
        pass

    def _startup(self):
        glEnable(GL_DEPTH_TEST)

        params = {
            "eye": glm.vec3(0, 150, 150),
            "target": glm.vec3(0, 0, 0),
            "up": glm.vec3(0, 1, 0)
        }
        self.start_time = time.time()
        self.cameras = {
            "BPL": Camera(**params)
        }
        self.index_camera = "BPL"
        self.yup = True
        self.camera = self.cameras[self.index_camera]
        self.model = glm.mat4(1)
        self.controller = GlutController(self.camera)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

        self.startup()

    def run(self):
        glutMainLoop()

    def idle_func(self):
        glutPostRedisplay()

    def reshape(self, w, h):
        glViewport(0, 0, w, h)
        self.width = w
        self.height = h

    def render_points(self, vertices):
        glColor3f(0.0, 0.0, 0.0)
        glBegin(GL_POINTS)
        for v in vertices:
            glVertex3f(v.x, v.y, v.z)
        glEnd()

    def render_triangles(self, vertices):
        glBegin(GL_TRIANGLES)
        for i in range(0, len(vertices), 3):
            v0 = vertices[i]
            v1 = vertices[i + 1]
            v2 = vertices[i + 2]
            glVertex3f(v0.x, v0.y, v0.z)
            glVertex3f(v1.x, v1.y, v1.z)
            glVertex3f(v2.x, v2.y, v2.z)
        glEnd()

    def render_quads(self, vertices):
        glBegin(GL_QUADS)
        for i in range(0, len(vertices), 4):
            v0 = vertices[i]
            v1 = vertices[i + 1]
            v2 = vertices[i + 2]
            v3 = vertices[i + 3]
            glVertex3f(v0.x, v0.y, v0.z)
            glVertex3f(v1.x, v1.y, v1.z)
            glVertex3f(v2.x, v2.y, v2.z)
            glVertex3f(v3.x, v3.y, v3.z)
        glEnd()

    def render_indexed_triangles(self, indices, vertices):
        glBegin(GL_TRIANGLES)
        for i in indices:
            for j in range(3):
                v = vertices[i[j]]
                glVertex3f(v.x, v.y, v.z)
        glEnd()

    def render_indexed_quads(self, indices, vertices):
        glBegin(GL_QUADS)
        for f1, f2 in zip(indices[::2], indices[1::2]):
            i = [f1[0], f1[1], f1[2], f2[2]]
            for j in range(4):
                v = vertices[i[j]]
                glVertex3f(v.x, v.y, v.z)
        glEnd()

    def _display(self):
        self.camera.update(self.width / self.height)

        glClearColor(0.2, 0.3, 0.3, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        self.camera.load_projection()
        self.camera.load_modelview()

        self.display()

        glLineWidth(5)
        axis(size=70, yup=True)
        glLineWidth(1)
        grid(segment_count=7, spacing=10, yup=True)

        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        glOrtho(-1, 1, -1, 1, -1, 1)
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        info = "\n".join([
            "{}: Camera - {}".format(i, k) for i, k in enumerate(self.cameras.keys())
        ])
        render_text(-1.0, 1.0 - 0.1, info)
        render_text(-1.0, -1.0, "{} camera is active".format(self.index_camera))

        glutSwapBuffers()

Если вы хотите использовать приведенный выше код, вам просто нужно установить pyopengl и pygml. После этого вы можете просто создать свой собственный BaseWindow подкласс, переопределить startup и render, и у вас должно появиться очень простое окно переналадки с простыми функциями, такими как вращение / масштабирование камеры, а также некоторые методы для визуализации точек / треугольников. / quads and indexed_triangles / indexed_quads.


ВОПРОС

А теперь реальный вопрос, рассмотрим этот небольшой фрагмент:

mcve_torusknot.py

from math import cos
from math import pi
from math import sin

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *

from glm import cross
from glm import normalize
from glm import vec3
from mcve_framework import BaseWindow


def sample(theta, p, q, out):
    r = cos(q * theta) + 2.0
    out.x = r * cos(p * theta)
    out.y = r * sin(q * theta)
    out.z = -sin(q * theta)


def gen_torusknot(tess_u, tess_v, p, q):
    vertices = []
    pp = vec3()
    centerpoint = vec3()
    nextpoint = vec3()
    T = vec3()
    B = vec3()
    N = vec3()
    r2 = 5.0

    for u in range(tess_u):
        theta = (u / tess_u) * 2 * pi
        sample(theta, p, q, centerpoint)

        theta = (u + 1) * 2 * pi / tess_u
        sample(theta, p, q, nextpoint)

        T = (nextpoint - centerpoint)
        N = (nextpoint + centerpoint)
        B = normalize(cross(T, N))
        N = normalize(cross(B, T))

        for v in range(tess_v):
            theta = (v / tess_v) * 2 * pi
            pointx = sin(theta) * r2
            pointy = cos(theta) * r2
            pp = N * pointx + B * pointy + centerpoint
            vertices.append(pp * 10)

    return vertices


class McveTorusKnot(BaseWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def startup(self):
        self.torusknot = gen_torusknot(60, 60, 1.000000001, 0.000000001)

    def display(self):
        glPointSize(3)
        glPushMatrix()
        self.render_points(self.torusknot)
        glPopMatrix()


if __name__ == '__main__':
    window = McveTorusKnot(800, 600)
    window.run()

Конечная цель здесь - выяснить, как генерировать и визуализировать торических узлов . Но перед такой амбициозной целью я хотел бы выяснить, почему при использовании параметров p=1 и q=0 я не получаю простой тор, подобный показанному здесь https://www.geeks3d.com/20140516/pq-torus-knot/,, вместо этого я получаю что-то вроде это:

showcase

Так что да, это, в основном, мой вопрос, во-первых, я хотел бы знать, что не так в моем коде выше, поэтому я не получаю простого тора из общей формулы и после этого. .. Я хотел бы знать, как создать сетку (иначе говоря, индексы, независимо от треугольников / квадратов / полос треугольников)?

Примечание: для простоты на данный момент нормальные или текстурные координаты не имеют значения, просто знать, как правильно генерировать вершины / индексы позиции сетки, будет более чем достаточно:)

1 Ответ

0 голосов
/ 14 января 2019

... во-первых, я хотел бы знать, что не так в моем коде выше, чтобы я не получил простой тор ...

Образец функции просто должен сгенерировать точку на окружности с радиусом r:

def sample(theta, p, q, out):
    r = 5.0
    out.x = r * cos(theta)
    out.y = r * sin(theta)
    out.z = 0

Тангенс (T) - это вектор от текущей точки на окружности к следующей точке (приблизительно), но битангенс (B) - это вектор от центра тора до текущей точки на круг:

def gen_torusknot(tess_u, tess_v, p, q):
    vertices = []
    pp = vec3()
    centerpoint = vec3()
    nextpoint = vec3()
    T = vec3()
    B = vec3()
    N = vec3()
    r2 = 2.0

    for u in range(tess_u):
        theta = (u / tess_u) * 2 * pi
        sample(theta, p, q, centerpoint)

        theta = (u + 1) * 2 * pi / tess_u
        sample(theta, p, q, nextpoint)

        T = (nextpoint - centerpoint)
        B = normalize(centerpoint)
        N = normalize(cross(T, B))
        T = normalize(cross(B, N))

        for v in range(tess_v):
            theta = (v / tess_v) * 2 * pi
            pointx = sin(theta) * r2
            pointy = cos(theta) * r2
            pp = N * pointx + B * pointy + centerpoint
            vertices.append(pp * 10)

    return vertices


Первый шаг к узлу Тора - это выборка точек на геометрическом представлении и выборка точек на окружности в центре тора.
Точки на окружности можно рассчитать так же, как и точки на турах, за исключением того, что радиус трубки (r2) равен 0.

def sample(phi, p, q, r1, r2, out):
    out.x = (r1 + r2 * cos(p * phi)) * cos(q * phi)
    out.y = (r1 + r2 * cos(p * phi)) * sin(q * phi)
    out.z = r2 * -sin(p * phi)

def gen_torusknot(tess_u, tess_v, p, q):

    vertices = []
    pt_tk = vec3()
    pt_c = vec3()

    r1, r2 = 5, 2
    p, q = 7, 3

    for u in range(tess_u):
        phi = (u / tess_u) * 2 * pi
        sample(phi, p, q, r1, r2, pt_tk)

        sample(phi, p, q, r1, 0, pt_c)

        vertices.append(pt_tk * 10)
        vertices.append(pt_c * 10)

    return vertices


Наконец, тангенс (T) и битангенс (B) могут быть вычислены аналогично тору.
Тангенс (T) - это вектор от текущей точки на торическом узле к следующей точке (приближенной) на торическом узле. Битангенс (B) - это вектор от точки выборки на окружности до текущей точки выборки на торическом узле:

def sample(phi, p, q, r1, r2, out):
    out.x = (r1 + r2 * cos(p * phi)) * cos(q * phi)
    out.y = (r1 + r2 * cos(p * phi)) * sin(q * phi)
    out.z = r2 * -sin(p * phi)

def gen_torusknot(tess_u, tess_v, p, q):

    vertices = []
    pt_tk = vec3()
    pt_tk_next = vec3()
    pt_c = vec3()

    r1, r2, r3 = 5, 2, 0.5
    p, q = 7, 3

    for u in range(tess_u):
        phi = (u / tess_u) * 2 * pi
        sample(phi, p, q, r1, r2, pt_tk)

        phi = (u + 1) * 2 * pi / tess_u
        sample(phi, p, q, r1, r2, pt_tk_next)

        sample(phi, p, q, r1, 0, pt_c)

        T = (pt_tk_next - pt_tk)
        B = normalize(pt_tk - pt_c)
        N = normalize(cross(T, B))
        T = normalize(cross(B, N))

        for v in range(tess_v):
            theta = (v / tess_v) * 2 * pi
            px = sin(theta) * r3
            py = cos(theta) * r3
            pp = N * px + B * py + pt_tk
            vertices.append(pp * 10)

    return vertices

Установка p = 3 и q = 2 создаст узел трилистника, а p = 0 и q = 1 создаст тор.


Я активировал мультисэмплинг, чтобы генерировать изображения выше.

glutInit()
glutSetOption( GLUT_MULTISAMPLE, 8 )
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE) 

Кроме того, я использовал функцию, которая преобразует значение Оттенок в диапазоне [0, 1] в цвет RGB

def HUEtoRGB(self, H):
    R = abs(H * 6.0 - 3.0) - 1.0
    G = 2.0 - abs(H * 6.0 - 2.0)
    B = 2.0 - abs(H * 6.0 - 4.0)
    return (R, G, B)

и следующая функция для рисования точек:

def render_points(self, vertices):
    glPointSize(5)
    glBegin(GL_POINTS)
    for i in range(len(vertices)):
        v = vertices[i]
        H = i / len(vertices)
        glColor4f(*self.HUEtoRGB(H), 0.0)
        glVertex3f(v.x, v.y, v.z)
    glEnd()

Примитив может генерировать сетку вместо точек GL_TRIANLGL_STRIP. tess_u * полосы отображаются вдоль кривой узла, каждая полоса состоит из tess_v секций (tess_v * 2 треугольника):

def render_strips(self, vertices, tess_v):
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
    no_strips = len(vertices) // tess_v
    for i_strip in range(no_strips):
        glBegin(GL_TRIANGLE_STRIP)
        for i_v in range(tess_v+1):
            for i in [i_strip, (i_strip+1) % no_strips]:
                v, H = vertices[i*tess_v + i_v % tess_v], i / no_strips
                glColor4f(*self.HUEtoRGB(H), 0.0)
                glVertex3f(v.x, v.y, v.z)
        glEnd()
class McveTorusKnot(BaseWindow):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def startup(self):
        self.tess_v = 60
        self.torusknot = gen_torusknot(300, self.tess_v, 1.000000001, 0.000000001)

    def display(self):
        glPushMatrix()
        #self.render_points(self.torusknot)
        self.render_strips(self.torusknot, self.tess_v)
        glPopMatrix()

Следующий узел трилистника был создан с использованием следующих параметров:

r1, r2, r3 = 5, 2, 1
p, q = 3, 2

...