pyglet / pyglet

pyglet is a cross-platform windowing and multimedia library for Python, for developing games and other visually rich applications.
http://pyglet.org
BSD 3-Clause "New" or "Revised" License
1.78k stars 294 forks source link

Lines seem to be rectangles, and not lines. #1071

Closed glanois closed 2 months ago

glanois commented 2 months ago

I'm on version 2.0.14.

Problem 1 - When I render Lines (width = 1), and increase the view scale, the lines get longer (as expected) but also get wider (not expected).

I was expecting Lines to work more like GL_LINES, where the line width is the same regardless of the view scale.

Problem 2 - I was expecting these two lines to be drawn on top of one another.

It almost seems as if Lines are implemented as rectangles.

Can Pyglet render lines more line the classic GL_LINES?

Here is the Pyglet 2.0.14 program:

import sys
import pyglet

class LineWindow(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        super(LineWindow, self).__init__(*args, **kwargs)
        self._zoom = 1.0
        self._frame = 0
        pyglet.clock.schedule_interval(self.update, 1.0/60.0)

    def on_resize(self, width, height):
        # This projection matrix is orthographic, centered in the window.
        # See:
        #     https://pyglet.readthedocs.io/en/latest/programming_guide/migration.html#window-projection-and-cameras
        #     https://pyglet.readthedocs.io/en/latest/programming_guide/math.html#helper-methods
        self.viewport = (0, 0, width, height)
        self.projection = pyglet.math.Mat4.orthogonal_projection(
            -width//2,
            width//2,
            -height//2,
            height//2,
            z_near=-10000,
            z_far=10000)

    def on_draw(self):
        self.clear()

        batch_hud = pyglet.graphics.Batch()            
        label = pyglet.text.Label(
            'lines.py - Pyglet Line demo.  Pyglet version {}'.format(pyglet.version),
            x=-self._width//2 + 20,
            y=self._height//2 - 20,
            color=(255, 255, 255, 255),
            batch=batch_hud)
        batch_hud.draw()

        self.view = self.view.scale((self._zoom, self._zoom, 1))

        batch_lines = pyglet.graphics.Batch()
        lines = []
        lines.append(pyglet.shapes.Line(0, 0, -10, 0, color=(255, 0, 0), width=1, batch=batch_lines))
        lines.append(pyglet.shapes.Line(-10, 0, 0, 0, color=(0, 0, 255), width=1, batch=batch_lines))
        batch_lines.draw()

        self.view = self.view.scale((1.0/self._zoom, 1.0/self._zoom, 1))

        return pyglet.event.EVENT_HANDLED

    def update(self, dt):
        self._frame += 1
        if self._frame <= 150:
            self._zoom = self._zoom * 1.02
        elif self._frame > 150 and self._frame <= 300:
            pass
        elif self._frame > 300 and self._frame <= 450:
            self._zoom = self._zoom / 1.02
        elif self._frame > 450:
            self._frame = 0

def main():
    window = LineWindow(800, 400, resizable=True)
    pyglet.app.run()
    return(0)

if __name__ == '__main__':
    sys.exit(main())

Here is a Pyglet 1.5 program. Notice how the lines stay the same width regardless of the zoom:

# Pyglet 1.5
import sys
from pyglet.gl import *
import math

class OpenGL2DWindow(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        super(OpenGL2DWindow, self).__init__(*args, **kwargs)
        self._width, self._height = self.get_size()
        self._zoom = 1.0
        self._frame = 0
        pyglet.clock.schedule_interval(self.update, 1.0/60.0)

    def on_resize(self, width, height):
        self._width  = width
        self._height = height
        return pyglet.event.EVENT_HANDLED

    def on_draw(self):
        w = self._width
        h = self._height
        glViewport(0, 0, w, h)

        # Clear the window.
        glClearColor(0.1, 0.1, 0.1, 0.0)
        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
        glDrawBuffer(GL_BACK)

        # We are going to do some 2-D orthographic drawing.
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()

        size = (w if (w >= h) else h) / 2.0
        aspect = None
        if (w <= h):
            aspect = h/float(w)
            glOrtho(-size, size, -size*aspect, size*aspect, -1000000.0, 1000000.0)
        else:
            aspect = w/float(h);
            glOrtho(-size*aspect, size*aspect, -size, size, -1000000.0, 1000000.0)

        # Make the world and window coordinates coincide so that 1.0 in
        # model space equals one pixel in window space.
        glScaled(aspect, aspect, 1.0)

        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()

        glPushMatrix()
        glScalef(self._zoom, self._zoom, 1.0)
        # Crosshairs.
        glColor3f(1.0, 1.0, 1.0)
        glBegin(GL_LINES)
        glVertex2f(-10.0, 0.0)
        glVertex2f(10.0, 0.0)
        glVertex2f(0.0, -10.0)
        glVertex2f(0.0, 10.0)
        glEnd()
        glPopMatrix()

        return pyglet.event.EVENT_HANDLED

    def update(self, dt):
        self._frame += 1
        if self._frame <= 150:
            self._zoom = self._zoom * 1.02
        elif self._frame > 150 and self._frame <= 300:
            pass
        elif self._frame > 300 and self._frame <= 450:
            self._zoom = self._zoom / 1.02
        elif self._frame > 450:
            self._frame = 0

def main():
    window = OpenGL2DWindow(800, 400, resizable=True)

    pyglet.app.run()
    return 0

if __name__ == '__main__':
    sys.exit(main())
benmoran56 commented 2 months ago

Hi,

Yes the Lines are implemented as a rectangle (two GL_TRIANGLES). This is because glLineWidth does not work reliably on some platforms (macOS).

For single width lines, you can create a VertexList directly. The shapes shader can be reused. For example:

        lines = []
        lines.append(pyglet.shapes.Line(0, 0, -10, 0, color=(255, 0, 0), width=1, batch=batch_lines))
        # lines.append(pyglet.shapes.Line(-10, 0, 0, 0, color=(0, 0, 255), width=1, batch=batch_lines))

        shader = pyglet.shapes.get_default_shader()
        vlist = shader.vertex_list(2, pyglet.gl.GL_LINES,
                                   colors=('Bn', (0, 0, 255, 255) * 2),
                                   position=('f', (-10, 0, 0, 0)), batch=batch_lines)
glanois commented 2 months ago

@benmoran56 - THANKS - that works.

import sys
import pyglet

class LineWindow(pyglet.window.Window):
    def __init__(self, *args, **kwargs):
        super(LineWindow, self).__init__(*args, **kwargs)
        self._zoom = 1.0
        self._frame = 0
        pyglet.clock.schedule_interval(self.update, 1.0/60.0)

    def on_resize(self, width, height):
        # This projection matrix is orthographic, centered in the window.
        # See:
        #     https://pyglet.readthedocs.io/en/latest/programming_guide/migration.html#window-projection-and-cameras
        #     https://pyglet.readthedocs.io/en/latest/programming_guide/math.html#helper-methods
        self.viewport = (0, 0, width, height)
        self.projection = pyglet.math.Mat4.orthogonal_projection(
            -width//2,
            width//2,
            -height//2,
            height//2,
            z_near=-10000,
            z_far=10000)

    def on_draw(self):
        self.clear()

        batch_hud = pyglet.graphics.Batch()            
        label = pyglet.text.Label(
            'lines.py - Pyglet Line demo.  Pyglet version {}'.format(pyglet.version),
            x=-self._width//2 + 20,
            y=self._height//2 - 20,
            color=(255, 255, 255, 255),
            batch=batch_hud)
        batch_hud.draw()

        self.view = self.view.scale((self._zoom, self._zoom, 1))

        batch_lines = pyglet.graphics.Batch()
        lines = []

        #lines.append(pyglet.shapes.Line(0, 0, -10, 0, color=(255, 0, 0), width=1, batch=batch_lines))
        #lines.append(pyglet.shapes.Line(-10, 0, 0, 0, color=(0, 0, 255), width=1, batch=batch_lines))

        shader = pyglet.shapes.get_default_shader()

        # https://pyglet.readthedocs.io/en/latest/modules/graphics/shader.html#pyglet.graphics.shader.ShaderProgram.vertex_list
        vlist = shader.vertex_list(2, pyglet.gl.GL_LINES,
                                   colors=('Bn', (255, 0, 0, 255) * 2),
                                   position=('f', (0, 5, -10, 5)), batch=batch_lines)
        vlist = shader.vertex_list(2, pyglet.gl.GL_LINES,
                                   colors=('Bn', (0, 0, 255, 255) * 2),
                                   position=('f', (-10, 0, 0, 0)), batch=batch_lines)
        batch_lines.draw()

        self.view = self.view.scale((1.0/self._zoom, 1.0/self._zoom, 1))

        return pyglet.event.EVENT_HANDLED

    def update(self, dt):
        self._frame += 1
        if self._frame <= 150:
            self._zoom = self._zoom * 1.02
        elif self._frame > 150 and self._frame <= 300:
            pass
        elif self._frame > 300 and self._frame <= 450:
            self._zoom = self._zoom / 1.02
        elif self._frame > 450:
            self._frame = 0

def main():
    window = LineWindow(800, 400, resizable=True)
    pyglet.app.run()
    return(0)

if __name__ == '__main__':
    sys.exit(main())
glanois commented 2 months ago

Thanks again! Works as expected now.