1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# Copyright (c) Vispy Development Team. All Rights Reserved.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
# -----------------------------------------------------------------------------
"""
Tutorial: Creating Visuals
--------------------------
This tutorial is intended to guide developers who are interested in creating
new subclasses of Visual. In most cases, this will not be necessary because
vispy's base library of visuals will be sufficient to create complex scenes as
needed. However, there are cases where a particular visual effect is desired
that is not supported in the base library, or when a custom visual is needed to
optimize performance for a specific use case.
The purpose of a Visual is to encapsulate a single drawable object. This
drawable can be as simple or complex as desired. Some of the simplest visuals
draw points, lines, or triangles, whereas more complex visuals invove multiple
drawing stages or make use of sub-visuals to construct larger objects.
In this example we will create a very simple Visual that draws a rectangle.
Visuals are defined by:
1. Creating a subclass of vispy.visuals.Visual that specifies the GLSL code
and buffer objects to use.
2. Defining a _prepare_transforms() method that will be called whenever the
user (or scenegraph) assigns a new set of transforms to the visual.
"""
from vispy import app, gloo, visuals, scene
import numpy as np
# Define a simple vertex shader. We use $template variables as placeholders for
# code that will be inserted later on. In this example, $position will become
# an attribute, and $transform will become a function. Important: using
# $transform in this way ensures that users of this visual will be able to
# apply arbitrary transformations to it.
vertex_shader = """
void main() {
gl_Position = $transform(vec4($position, 0, 1));
}
"""
# Very simple fragment shader. Again we use a template variable "$color", which
# allows us to decide later how the color should be defined (in this case, we
# will just use a uniform red color).
fragment_shader = """
void main() {
gl_FragColor = $color;
}
"""
# Start the new Visual class.
# By convention, all Visual subclass names end in 'Visual'.
# (Custom visuals may ignore this convention, but for visuals that are built
# in to vispy, this is required to ensure that the VisualNode subclasses are
# generated correctly.)
class MyRectVisual(visuals.Visual):
"""Visual that draws a red rectangle.
Parameters
----------
x : float
x coordinate of rectangle origin
y : float
y coordinate of rectangle origin
w : float
width of rectangle
h : float
height of rectangle
All parameters are specified in the local (arbitrary) coordinate system of
the visual. How this coordinate system translates to the canvas will
depend on the transformation functions used during drawing.
"""
# There are no constraints on the signature of the __init__ method; use
# whatever makes the most sense for your visual.
def __init__(self, x, y, w, h):
# Initialize the visual with a vertex shader and fragment shader
visuals.Visual.__init__(self, vertex_shader, fragment_shader)
# vertices for two triangles forming a rectangle
self.vbo = gloo.VertexBuffer(np.array([
[x, y], [x+w, y], [x+w, y+h],
[x, y], [x+w, y+h], [x, y+h]
], dtype=np.float32))
# Assign values to the $position and $color template variables in
# the shaders. ModularProgram automatically handles generating the
# necessary attribute and uniform declarations with unique variable
# names.
self.shared_program.vert['position'] = self.vbo
self.shared_program.frag['color'] = (1, 0, 0, 1)
self._draw_mode = 'triangles'
def _prepare_transforms(self, view):
# This method is called when the user or the scenegraph has assigned
# new transforms to this visual (ignore the *view* argument for now;
# we'll get to that later). This method is thus responsible for
# connecting the proper transform functions to the shader program.
# The most common approach here is to simply take the complete
# transformation from visual coordinates to render coordinates. Later
# tutorials detail more complex transform handling.
view.view_program.vert['transform'] = view.get_transform()
# At this point the visual is ready to use, but it takes some extra effort to
# set up a Canvas and TransformSystem for drawing (the examples in
# examples/basics/visuals/ all follow this approach).
#
# An easier approach is to make the visual usable in a scenegraph, in which
# case the canvas will take care of drawing the visual and setting up the
# TransformSystem for us.
#
# To be able to use our new Visual in a scenegraph, it needs to be
# a subclass of scene.Node. In vispy we achieve this by creating a parallel
# set of classes that inherit from both Node and each Visual subclass.
# This can be done automatically using scene.visuals.create_visual_node():
MyRect = scene.visuals.create_visual_node(MyRectVisual)
# By convention, these classes have the same name as the Visual they inherit
# from, but without the 'Visual' suffix.
# The auto-generated class MyRect is basically equivalent to::
#
# class MyRect(MyRectVisual, scene.Node):
# def __init__(self, *args, **kwds):
# parent = kwds.pop('parent', None)
# name = kwds.pop('name', None)
# MyRectVisual.__init__(self, *args, **kwds)
# Node.__init__(self, parent=parent, name=name)
#
# Finally we will test the visual by displaying in a scene.
# Create a canvas to display our visual
canvas = scene.SceneCanvas(keys='interactive', show=True)
# Create two instances of MyRect, each using canvas.scene as their parent
rects = [MyRect(100, 100, 200, 300, parent=canvas.scene),
MyRect(500, 100, 200, 300, parent=canvas.scene)]
# To test that the user-specified transforms work correctly, I'll rotate
# one rectangle slightly.
tr = visuals.transforms.MatrixTransform()
tr.rotate(5, (0, 0, 1))
rects[1].transform = tr
# ..and optionally start the event loop
if __name__ == '__main__':
import sys
if sys.flags.interactive != 1:
app.run()
|