在Pygame中是否有一种方法可以更改导入的.obj文件的位置和大小?

huangapple go评论228阅读模式
英文:

Is there a way to change the location and size of an imported .obj file in Pygame?

问题

我使用Blender创建了一个.obj文件,并使用OBJ文件加载器加载到Pygame中,如sugested by skrx在以下链接中:
https://stackoverflow.com/questions/47660370/error-in-objfileloader

是否有一种简单的方法在导入Pygame后更改导入的.obj文件的位置、高度和宽度?例如,如果您创建了一个tree.obj文件,要能够以不同的大小将相同的树放在不同的位置?

按照下面的代码,可能像这样:

object_obj = OBJ("Tree.obj", swapyz=False)

object_obj.setX = 0

object_obj.setWidth = 3

object_obj.setHeight = 10

或者通过向obj加载器发送额外的参数,改变顶点?

OBJ加载器:

import pygame
from OpenGL.GL import *

def MTL(filename):
    filename = 'OBJ/' + filename
    contents = {}
    mtl = None
    for line in open(filename, "r"):
        if line.startswith('#'): continue
        values = line.split()
        if not values: continue
        if values[0] == 'newmtl':
            mtl = contents[values[1]] = {}
        elif mtl is None:
            raise ValueError("mtl file doesn't start with newmtl stmt")
        elif values[0] == 'map_Kd':
            # load the texture referred to by this declaration
            mtl[values[0]] = values[1]
            surf = pygame.image.load(mtl['map_Kd'])
            image = pygame.image.tostring(surf, 'RGBA', 1)
            ix, iy = surf.get_rect().size
            texid = mtl['texture_Kd'] = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, texid)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                            GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                            GL_LINEAR)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, image)
        else:
            mtl[values[0]] = list(map(float, values[1:]))
    return contents

class OBJ:
    def __init__(self, filename, swapyz=False):
        """Loads a Wavefront OBJ file."""
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        material = None
        for line in open(filename, "r"):
            if line.startswith('#'): continue
            values = line.split()
            if not values: continue
            if values[0] == 'v':
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.vertices.append(v)
            elif values[0] == 'vn':
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.normals.append(v)
            elif values[0] == 'vt':
                self.texcoords.append(list(map(float, values[1:3])))
            elif values[0] in ('usemtl', 'usemat'):
                material = values[1]
            elif values[0] == 'mtllib':
                self.mtl = MTL(values[1])
            elif values[0] == 'f':
                face = []
                texcoords = []
                norms = []
                for v in values[1:]:
                    w = v.split('/')
                    face.append(int(w[0]))
                    if len(w) >= 2 and len(w[1]) > 0:
                        texcoords.append(int(w[1]))
                    else:
                        texcoords.append(0)
                    if len(w) >= 3 and len(w[2]) > 0:
                        norms.append(int(w[2]))
                    else:
                        norms.append(0)
                self.faces.append((face, norms, texcoords, material))

        self.gl_list = glGenLists(1)
        glNewList(self.gl_list, GL_COMPILE)
        glEnable(GL_TEXTURE_2D)
        glFrontFace(GL_CCW)
        for face in self.faces:
            vertices, normals, texture_coords, material = face

            mtl = self.mtl[material]
            if 'texture_Kd' in mtl:
                # use diffuse texmap
                glBindTexture(GL_TEXTURE_2D, mtl['texture_Kd'])
            else:
                # just use diffuse colour
                glColor(*mtl['Kd'])

            glBegin(GL_POLYGON)
            for i in range(len(vertices)):
                if normals[i] > 0:
                    glNormal3fv(self.normals[normals[i] - 1])
                if texture_coords[i] > 0:
                    glTexCoord2fv(self.texcoords[texture_coords[i] - 1])
                glVertex3fv(self.vertices[vertices[i] - 1])
            glEnd()
        glDisable(GL_TEXTURE_2D)
        glEndList()

在Pygame中显示.OBJ文件:

# Basic OBJ file viewer. needs objloader from:
#  http://www.pygame.org/wiki/OBJFileLoader
# LMB + move: rotate
# RMB + move: pan
# Scroll wheel: zoom in/out
import sys, pygame
from pygame.locals import *
from pygame.constants import *
from OpenGL.GL import *
from OpenGL.GLU import *

# IMPORT OBJECT LOADER
from OBJ_Loader import *

pygame.init()
viewport = (800,600)
hx = viewport[0]/2
hy = viewport[1]/2
srf = pygame.display.set_mode(viewport, OPENGL | DOUBLEBUF)

glLightfv(GL_LIGHT0, GL_POSITION,  (-40, 200, 100, 0.0))
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1.0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.5, 0.5, 0.5, 1.0))
glEnable(GL_LIGHT0)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)           # most obj files expect to be smooth-shaded

# LOAD OBJECT AFTER PYGAME INIT
object_obj = OBJ("Tree.obj", swapyz=False)
clock = pygame.time.Clock()

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
width, height = viewport
gluPerspective(90, 1, 0.001, 1000.0)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)

rx, ry = (0,0)
tx, ty = (0,0)
zpos = 1
rotate = move = False
while 1:
    clock.tick(30)
    for e in pygame.event.get():
        if e.type == QUIT:
            sys.exit()
        elif e.type == KEYDOWN and e.key == K_ESCAPE:
            sys.exit()
        elif

<details>
<summary>英文:</summary>

I created an .obj file using blender and loaded into Pygame using the OBJfileloader as sugested by skrx in:
[https://stackoverflow.com/questions/47660370/error-in-objfileloader][1]


  [1]: https://stackoverflow.com/questions/47660370/error-in-objfileloader

Is there an easy way to change the location, height and width of the imported .obj file after importing it into Pygame? As an example, if you create a tree.obj file, to be able to place that same tree in different places with different sizes? 

Following the code below, maybe something like:

```py
object_obj = OBJ(&quot;Tree.obj&quot;, swapyz=False)

object_obj.setX = 0

object_obj.setWidth = 3

object_obj.setHeight =10

Or by sending extra parameters to the obj loader, changing the vertices?

OBJ Loader:

import pygame
from OpenGL.GL import *

def MTL(filename):
    filename = &#39;OBJ/&#39;+filename
    contents = {}
    mtl = None
    for line in open(filename, &quot;r&quot;):
        if line.startswith(&#39;#&#39;): continue
        values = line.split()
        if not values: continue
        if values[0] == &#39;newmtl&#39;:
            mtl = contents[values[1]] = {}
        elif mtl is None:
            raise ValueError(&quot;mtl file doesn&#39;t start with newmtl stmt&quot;)
        elif values[0] == &#39;map_Kd&#39;:
            # load the texture referred to by this declaration
            mtl[values[0]] = values[1]
            surf = pygame.image.load(mtl[&#39;map_Kd&#39;])
            image = pygame.image.tostring(surf, &#39;RGBA&#39;, 1)
            ix, iy = surf.get_rect().size
            texid = mtl[&#39;texture_Kd&#39;] = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, texid)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
                GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
                GL_LINEAR)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ix, iy, 0, GL_RGBA,
                GL_UNSIGNED_BYTE, image)
        else:
            mtl[values[0]] = list(map(float, values[1:]))
    return contents

class OBJ:
    def __init__(self, filename, swapyz=False):
        &quot;&quot;&quot;Loads a Wavefront OBJ file. &quot;&quot;&quot;
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        material = None
        for line in open(filename, &quot;r&quot;):
            if line.startswith(&#39;#&#39;): continue
            values = line.split()
            if not values: continue
            if values[0] == &#39;v&#39;:
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.vertices.append(v)
            elif values[0] == &#39;vn&#39;:
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]
                self.normals.append(v)
            elif values[0] == &#39;vt&#39;:
                self.texcoords.append(list(map(float, values[1:3])))
            elif values[0] in (&#39;usemtl&#39;, &#39;usemat&#39;):
                material = values[1]
            elif values[0] == &#39;mtllib&#39;:
                self.mtl = MTL(values[1])
            elif values[0] == &#39;f&#39;:
                face = []
                texcoords = []
                norms = []
                for v in values[1:]:
                    w = v.split(&#39;/&#39;)
                    face.append(int(w[0]))
                    if len(w) &gt;= 2 and len(w[1]) &gt; 0:
                        texcoords.append(int(w[1]))
                    else:
                        texcoords.append(0)
                    if len(w) &gt;= 3 and len(w[2]) &gt; 0:
                        norms.append(int(w[2]))
                    else:
                        norms.append(0)
                self.faces.append((face, norms, texcoords, material))

        self.gl_list = glGenLists(1)
        glNewList(self.gl_list, GL_COMPILE)
        glEnable(GL_TEXTURE_2D)
        glFrontFace(GL_CCW)
        for face in self.faces:
            vertices, normals, texture_coords, material = face

            mtl = self.mtl[material]
            if &#39;texture_Kd&#39; in mtl:
                # use diffuse texmap
                glBindTexture(GL_TEXTURE_2D, mtl[&#39;texture_Kd&#39;])
            else:
                # just use diffuse colour
                glColor(*mtl[&#39;Kd&#39;])

            glBegin(GL_POLYGON)
            for i in range(len(vertices)):
                if normals[i] &gt; 0:
                    glNormal3fv(self.normals[normals[i] - 1])
                if texture_coords[i] &gt; 0:
                    glTexCoord2fv(self.texcoords[texture_coords[i] - 1])
                glVertex3fv(self.vertices[vertices[i] - 1])
            glEnd()
        glDisable(GL_TEXTURE_2D)
        glEndList()

Displaying .OBJ file in Pygame:

# Basic OBJ file viewer. needs objloader from:
#  http://www.pygame.org/wiki/OBJFileLoader
# LMB + move: rotate
# RMB + move: pan
# Scroll wheel: zoom in/out
import sys, pygame
from pygame.locals import *
from pygame.constants import *
from OpenGL.GL import *
from OpenGL.GLU import *

# IMPORT OBJECT LOADER
from OBJ_Loader import *

pygame.init()
viewport = (800,600)
hx = viewport[0]/2
hy = viewport[1]/2
srf = pygame.display.set_mode(viewport, OPENGL | DOUBLEBUF)

glLightfv(GL_LIGHT0, GL_POSITION,  (-40, 200, 100, 0.0))
glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1.0))
glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.5, 0.5, 0.5, 1.0))
glEnable(GL_LIGHT0)
glEnable(GL_LIGHTING)
glEnable(GL_COLOR_MATERIAL)
glEnable(GL_DEPTH_TEST)
glShadeModel(GL_SMOOTH)           # most obj files expect to be smooth-shaded

# LOAD OBJECT AFTER PYGAME INIT
object_obj = OBJ(&quot;Tree.obj&quot;, swapyz=False)
clock = pygame.time.Clock()

glMatrixMode(GL_PROJECTION)
glLoadIdentity()
width, height = viewport
gluPerspective(90, 1, 0.001, 1000.0)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_MODELVIEW)

rx, ry = (0,0)
tx, ty = (0,0)
zpos = 1
rotate = move = False
while 1:
    clock.tick(30)
    for e in pygame.event.get():
        if e.type == QUIT:
            sys.exit()
        elif e.type == KEYDOWN and e.key == K_ESCAPE:
            sys.exit()
        elif e.type == MOUSEBUTTONDOWN:
            if e.button == 4: zpos = max(1, zpos-1)
            elif e.button == 5: zpos += 1
            elif e.button == 1: rotate = True
            elif e.button == 3: move = True
        elif e.type == MOUSEBUTTONUP:
            if e.button == 1: rotate = False
            elif e.button == 3: move = False
        elif e.type == MOUSEMOTION:
            i, j = e.rel
            if rotate:
                rx += i
                ry += j
            if move:
                tx += i
                ty -= j

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()

    # RENDER OBJECT
    glTranslate(tx/20., ty/20., - zpos)
    glRotate(ry, 1, 0, 0)
    glRotate(rx, 0, 1, 0)
    glCallList(obj.gl_list)


    pygame.display.flip()

答案1

得分: 3

&lt;kbd&gt;[![][1] repl.it/@Rabbid76/pygame-opengl-wavefront-obj](https://replit.com/@Rabbid76/pygame-opengl-wavefront-obj#main.py)&lt;/kbd&gt;

不要更改顶点坐标。使用 [`glTranslate`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml) 和 [`glScale`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glScale.xml) 来定义模型变换。

在读取顶点坐标时计算 [轴对齐边界框](https://en.wikipedia.org/wiki/Bounding_volume):

```py
class OBJ:
    def __init__(self, filename, swapyz=False):
        &quot;&quot;&quot;加载 Wavefront OBJ 文件。&quot;&quot;&quot;
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        self.min_v = [float(&quot;inf&quot;), float(&quot;inf&quot;), float(&quot;inf&quot;)]
        self.max_v = [-float(&quot;inf&quot;), -float(&quot;inf&quot;), -float(&quot;inf&quot;)]

        material = None
        for line in open(filename, &quot;r&quot;):
            if line.startswith(&#39;#&#39;): continue
            values = line.split()
            if not values: continue
            if values[0] == &#39;v&#39;:
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]

                for i in range(3):
                    self.min_v[i] = min(self.min_v[i], v[i])
                    self.max_v[i] = max(self.max_v[i], v[i])

                self.vertices.append(v)
            # [...]
        
        self.size = [self.max_v[i]-self.min_v[i] for i in range(3)]
        # [...]

在绘制对象时进行缩放和平移。使用 glPushMatrix / glPopMatrix 来保存和恢复当前矩阵,在变换前后使用:

# 渲染对象
glTranslate(tx/20., ty/20., - zpos)
glRotate(ry, 1, 0, 0)
glRotate(rx, 0, 1, 0)

pos = [0, 0, -5]
size = [3, 3, 10]
scale = [size[i]/obj.size[i] for i in range(3)]

glPushMatrix()
glTranslate(*pos)
glScale(*scale)
glCallList(obj.gl_list)
glPopMatrix()

或者也可以在显示列表中完成:

self.gl_list = glGenLists(1)
glNewList(self.gl_list, GL_COMPILE)
glEnable(GL_TEXTURE_2D)
glFrontFace(GL_CCW)

glPushMatrix()
glTranslate(*pos)
glScale(*scale)

for face in self.faces:
    vertices, normals, texture_coords, material = face
    # [...]
    
glDisable(GL_TEXTURE_2D)

glPopMatrix()
glEndList()

<details>
<summary>英文:</summary>

&lt;kbd&gt;[![][1] repl.it/@Rabbid76/pygame-opengl-wavefront-obj](https://replit.com/@Rabbid76/pygame-opengl-wavefront-obj#main.py)&lt;/kbd&gt;

Do not change the vertex coordinates. Use [`glTranslate`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glTranslate.xml) and [`glScale`](https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glScale.xml) to define a model transformation.

Compute the [Axis Aligned Bounding Box](https://en.wikipedia.org/wiki/Bounding_volume), when the vertex coordinates are read:

```py
class OBJ:
    def __init__(self, filename, swapyz=False):
        &quot;&quot;&quot;Loads a Wavefront OBJ file. &quot;&quot;&quot;
        self.vertices = []
        self.normals = []
        self.texcoords = []
        self.faces = []

        self.min_v = [float(&quot;inf&quot;), float(&quot;inf&quot;), float(&quot;inf&quot;)]
        self.max_v = [-float(&quot;inf&quot;), -float(&quot;inf&quot;), -float(&quot;inf&quot;)]

        material = None
        for line in open(filename, &quot;r&quot;):
            if line.startswith(&#39;#&#39;): continue
            values = line.split()
            if not values: continue
            if values[0] == &#39;v&#39;:
                v = list(map(float, values[1:4]))
                if swapyz:
                    v = v[0], v[2], v[1]

                for i in range(3):
                    self.min_v[i] = min(self.min_v[i], v[i])
                    self.max_v[i] = max(self.max_v[i], v[i])

                self.vertices.append(v)
            # [...]

        self.size = [self.max_v[i]-self.min_v[i] for i in range(3)]
        # [...]

Scale and translate the object when it is drawn. Use glPushMatrix / glPopMatrix to save and restore the current matrix before and after the transformations:

# RENDER OBJECT
glTranslate(tx/20., ty/20., - zpos)
glRotate(ry, 1, 0, 0)
glRotate(rx, 0, 1, 0)

pos = [0, 0, -5]
size = [3, 3, 10]
scale = [size[i]/obj.size[i] for i in range(3)]

glPushMatrix()
glTranslate(*pos)
glScale(*scale)
glCallList(obj.gl_list)
glPopMatrix()

Alternatively that can be done in the display list, too:

self.gl_list = glGenLists(1)
glNewList(self.gl_list, GL_COMPILE)
glEnable(GL_TEXTURE_2D)
glFrontFace(GL_CCW)

glPushMatrix()
glTranslate(*pos)
glScale(*scale)

for face in self.faces:
    vertices, normals, texture_coords, material = face
    # [...]
    
glDisable(GL_TEXTURE_2D)

glPopMatrix()
glEndList()

huangapple
  • 本文由 发表于 2020年1月6日 17:47:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/59609837.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定