Hi all,

I've been interested in using numpy arrays with the pyglet.graphics
api. One of my motivations to use numpy arrays over the ctypes c
arrays is the nice item access that comes with numpy arrays.

The unobtrusive approach is just to just use a numpy array that
references the same memory of the c arrays returned by vertex list
properties (I found some useful discussion related to this at
http://projects.scipy.org/pipermail/numpy-discussion/2006-July/009438.html
that uses the buffer interface to share the memory). This works just
fine, although then you'll need to do some extra work to shape the
array to reflex the vertex/attribute counts, and then update it as the
properties of the vertex list change (resizing, allocation, etc).

I've attached some code (and example usage) to use numpy arrays with
pyglet.graphics. Essentially the 'as_numpy_array' function is all you
need to manipulate pyglets vertex data as numpy arrays. But based on
Alex's suggestion
(http://groups.google.com/group/pyglet-users/msg/92a658460c2547d8),
this sketch implements versions of the relevant classes in
pyglet.graphics. I implemented simple subclasses of the classes in
pyglet.graphics.vertexbuffer to return numpy array views of the
allocated data, as well as a subclass of
pyglet.graphics.vertexdomain.VertexList whose vertexattribute property
descriptors reshape and transpose their numpy arrays to reflect the
vertex/attribute counts, i.e for 4 vertices with the format 'v3f':

((x1, x2, x3, x4),
 (y1, y2, y3, y4),
 (z1, z2, z3, z4))

Using these classes with pyglet.graphics in the normal way (i.e
pyglet.graphics.vertex_list, pyglet.graphics.Batch.add/add_indexed)
involves hacking the module by replacing some of its classes and
functions (see the 'install' function), although hooks in
pyglet.graphics some time in the future would be nice :)

-tamas

--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"pyglet-users" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/pyglet-users?hl=en
-~----------~----~----~----~------~----~------~--~---

"""
Numpy arrays with pyglet's vertex lists.
"""

import numpy
import ctypes
from pyglet import gl
from pyglet.graphics import vertexbuffer, vertexdomain


_installed = False
_VertexList = vertexdomain.VertexList

ctypes.pythonapi.PyBuffer_FromReadWriteMemory.restype = ctypes.py_object


def install():
    """
    Globally hooks in numpy arrays for pyglet.graphics.
    """
    global _installed
    if _installed:
       return 

    vertexdomain.VertexList = VertexList
    vertexbuffer.create_buffer = _with_numpy(
        vertexbuffer.create_buffer)
    vertexbuffer.create_mappable_buffer = _with_numpy(
        vertexbuffer.create_mappable_buffer)

        
def as_numpy_array(c_array):
    """
    Return a view of the c array as a numpy array, with the
    underlying memory shared.
    """
    return numpy.frombuffer(
        ctypes.pythonapi.PyBuffer_FromReadWriteMemory(
                c_array, ctypes.sizeof(c_array._type_) * len(c_array)),
        dtype=c_array._type_)


def _with_numpy(func):
    """
    Decorates the create_buffer and create_mappable_buffer in
    pyglet.grpahics.vertexbuffer that reinstantiates their returned
    vertex buffers with their equivalents in this module.
    """
    def new_func(
        size,
        target=gl.GL_ARRAY_BUFFER,
        usage=gl.GL_DYNAMIC_DRAW,
        vbo=True):
        a = func(size, target, usage, vbo)
        if isinstance(a, vertexbuffer.VertexBufferObject):
            return NumpyVertexBufferObject(size, target, usage)
        elif isinstance(a, vertexbuffer.MappableVertexBufferObject):
            return NumpyMappableVertexBufferObject(size, target, usage)
        else:
            return NumpyVertexArray(size)

    new_func.__name__ = func.__name__
    new_func.__dict__ = func.__dict__
    new_func.__doc__ = func.__doc__
    new_func._decorated_func = func

    return new_func


class NumpyVertexArray(vertexbuffer.VertexArray):
    def get_region(self, start, size, ptr_type):
        region = super(NumpyVertexArray, self).get_region(
            start, size, ptr_type)
        region.array = as_numpy_array(region.array)
        return region


class NumpyVertexBufferObject(vertexbuffer.VertexBufferObject):
    def get_region(self, start, size, ptr_type):
        region = super(NumpyVertexBufferObject, self).get_region(
            start, size, ptr_type)
        region.array = as_numpy_array(region.array)
        return region


class NumpyMappableVertexBufferObject(vertexbuffer.VertexBufferObject):
    def get_region(self, start, size, ptr_type):
        region = super(NumpyMappableVertexBufferObject, self).get_region(
            start, size, ptr_type)
        region.array = as_numpy_array(region.array)
        return region


class VertexList(_VertexList):
    def _get_shaped_array(self, attribute_name):
        """
        Shapes and transposes the arrays retrieved by the
        vertexattribute property descriptors to reflect the
        vertex/attribute counts.
        """
        array = getattr(super(VertexList, self), '_get_%s' % attribute_name)()
        count = self.domain.attribute_names[attribute_name].count
        if len(array.shape) == 1:
            array.shape = (len(array) / count, count)
            array = array.transpose()
        getattr(self, '_%s_cache' % attribute_name).array = array
        return array

    def _get_colors(self):
        return self._get_shaped_array('colors')

    def _get_fog_coords(self):
        return self._get_shaped_array('fog_coords')

    def _get_edge_flags(self):
        return self._get_shaped_array('edge_flags')

    def _get_normals(self):
        return self._get_shaped_array('normals')

    def _get_secondary_colors(self):
        return self._get_shaped_array('secondary_colors')

    def _get_tex_coords(self):
        return self._get_shaped_array('tex_coords')

    def _get_vertices(self):
        return self._get_shaped_array('vertices')

    colors = property(
        _get_colors, _VertexList._set_colors,
        doc=_VertexList.colors.__doc__)

    fog_coords = property(
        _get_fog_coords, _VertexList._set_fog_coords,
        doc=_VertexList.fog_coords.__doc__)

    edge_flags = property(
        _get_edge_flags, _VertexList._set_edge_flags,
        doc=_VertexList.edge_flags.__doc__)

    normals = property(
        _get_normals, _VertexList._set_normals,
        doc=_VertexList.normals.__doc__)

    tex_coords = property(
        _get_tex_coords, _VertexList._set_tex_coords,
        doc=_VertexList.tex_coords.__doc__)

    vertices = property(
        _get_vertices, _VertexList._set_vertices,
        doc=_VertexList.vertices.__doc__)


import sys
import random
import numpy
from numpy import sin, cos
import pyglet
from pyglet import gl
import nverts


# first, an example without hacking pyglet.graphics
vlist = pyglet.graphics.vertex_list(4, 'v2i')
nv = nverts.as_numpy_array(vlist.vertices)
nv.shape = (4, 2)
nv = nv.transpose()
# nv is now in the form:
# ((x1, x2, x3, x4),
#  (y1, y2, x3, x4))
# so using 'v4f' or similar attributes will allow you
# to apply the dot product of matrix transformations
# to the vertices
vlist.delete()
del nv


# integrating numpy arrays into python.graphics

nverts.install()


window = pyglet.window.Window(width=256, height=256)


# geometry

batch = pyglet.graphics.Batch()

quad = numpy.array(
    ((-1, -1, 1,  1),
     (-1,  1, 1, -1),
     ( 0,  0, 0,  0),
     ( 1,  1, 1,  1)),
    dtype=gl.GLfloat)

scale = numpy.array(
    ((10, 0 , 0 , 0),
     (0 , 10, 0 , 0),
     (0 , 0 , 10, 0),
     (0 , 0 , 0 , 1)),
    dtype=gl.GLfloat)

quad = numpy.dot(scale, quad)

color = numpy.array(
    ((255, 255, 255, 255),
     (255, 255, 255, 255),
     (255, 255, 255, 255),
     (255, 255, 255, 255)),
    dtype='uint8')

quad = batch.add(
    4, gl.GL_LINE_LOOP, None,
    ('v4f', quad.transpose().flat),
    ('c4B', color.transpose().flat))

@window.event
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
    rotate_x = numpy.array(
       ((1., 0.          , 0.           , 0.),
        (0., cos(-dy*.01), -sin(-dy*.01), 0.),
        (0., sin(-dy*.01), cos(-dy*.01) , 0.),
        (0., 0.          , 0.           , 1.)),
       dtype=gl.GLfloat)
    rotate_y = numpy.array(
        ((cos(dx*.01) , 0., sin(dx*.01), 0.),
         (0.      , 1., 0.             , 0.),
         (-sin(dx*.01), 0., cos(dx*.01), 0.),
         (0.      , 0., 0.             , 1.)),
        dtype=gl.GLfloat)
    transform = numpy.dot(rotate_x, rotate_y)
    quad.vertices[:] = numpy.dot(transform, quad.vertices)


# colors

target_color = (255, 255, 255)

def pick_new_color(dt):
    global target_color
    target_color = (
        random.choice(range(256)),
        random.choice(range(256)),
        random.choice(range(256)))

def adjust_color(dt):
    global target_color
    actual_r = quad.colors[0, 0]
    actual_g = quad.colors[1, 0]
    actual_b = quad.colors[2, 0]
    quad.colors[0] = int(actual_r - (actual_r - target_color[0]) / 10.)
    quad.colors[1] = int(actual_g - (actual_g - target_color[1]) / 10.)
    quad.colors[2] = int(actual_b - (actual_b - target_color[2]) / 10.)

pyglet.clock.schedule_interval(pick_new_color, 1)
pyglet.clock.schedule_interval(adjust_color, 1. / 10.)


# main app

@window.event
def on_draw():
    gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
    
    gl.glMatrixMode(gl.GL_PROJECTION)
    gl.glLoadIdentity()
    gl.gluPerspective(60., float(window.width) / window.height, .1, 8192)
    gl.glTranslatef(0., 0., -50.)
    gl.glMatrixMode(gl.GL_MODELVIEW)

    gl.glLineWidth(3.)
    batch.draw()

@window.event
def on_key_press(symbol, modifiers):
    if symbol == pyglet.window.key.ESCAPE:
        sys.exit()

try:
    pyglet.app.run()
except KeyboardInterrupt:
    sys.exit()

Reply via email to