Skip to content

Commit

Permalink
Merge pull request #901 from colour-science/master
Browse files Browse the repository at this point in the history
PR: Support for parametric plane and box geometries.
  • Loading branch information
larsoner committed Jun 1, 2015
2 parents c81cc82 + 483b9eb commit f8a69d0
Show file tree
Hide file tree
Showing 9 changed files with 432 additions and 5 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ doc/_build
/_gh-pages
/_images
/_demo-data
.idea
build
dist
MANIFEST
Expand Down
33 changes: 33 additions & 0 deletions examples/basics/visuals/box.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
Simple demonstration of the box geometry.
"""

import sys

from vispy import scene
from vispy import geometry

canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)

view = canvas.central_widget.add_view()

vertices, faces, outline = geometry.create_box(width=2, height=4, depth=8,
width_segments=4,
height_segments=8,
depth_segments=16)

box = scene.visuals.Box(width=4, height=4, depth=8, width_segments=4,
height_segments=8, depth_segments=16,
vertex_colors=vertices['color'],
edge_color='k',
parent=view.scene)

camera = scene.cameras.TurntableCamera(fov=45, azimuth=60, parent=view.scene)
view.camera = camera

if __name__ == '__main__' and sys.flags.interactive == 0:
canvas.app.run()
33 changes: 33 additions & 0 deletions examples/basics/visuals/plane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2015, Vispy Development Team.
# Distributed under the (new) BSD License. See LICENSE.txt for more info.

"""
Simple demonstration of the plane geometry.
"""

import sys

from vispy import scene
from vispy import geometry

canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)

view = canvas.central_widget.add_view()

vertices, faces, outline = geometry.create_plane(width=2, height=4,
width_segments=4,
height_segments=8,
direction='+y')

plane = scene.visuals.Plane(width=2, height=4, width_segments=4,
height_segments=8, direction='+y',
vertex_colors=vertices['color'],
edge_color='k',
parent=view.scene)

camera = scene.cameras.TurntableCamera(fov=45, azimuth=-45, parent=view.scene)
view.camera = camera

if __name__ == '__main__' and sys.flags.interactive == 0:
canvas.app.run()
9 changes: 5 additions & 4 deletions vispy/geometry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from __future__ import division

__all__ = ['MeshData', 'PolygonData', 'Rect', 'Triangulation', 'triangulate',
'create_arrow', 'create_cone', 'create_cube', 'create_cylinder',
'create_sphere', 'resize']
'create_arrow', 'create_box', 'create_cone', 'create_cube',
'create_cylinder', 'create_plane', 'create_sphere', 'resize']

from .polygon import PolygonData # noqa
from .meshdata import MeshData # noqa
Expand All @@ -19,5 +19,6 @@
from .torusknot import TorusKnot # noqa
from .calculations import (_calculate_normals, _fast_cross_3d, # noqa
resize) # noqa
from .generation import create_arrow, create_cone, create_cube, \
create_cylinder, create_sphere # noqa
from .generation import (create_arrow, create_box, create_cone, # noqa
create_cube, create_cylinder, create_plane, # noqa
create_sphere) # noqa
208 changes: 208 additions & 0 deletions vispy/geometry/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,214 @@ def create_cube():
return vertices, filled, outline


def create_plane(width=1, height=1, width_segments=1, height_segments=1,
direction='+z'):
""" Generate vertices & indices for a filled and outlined plane.
Parameters
----------
width : float
Plane width.
height : float
Plane height.
width_segments : int
Plane segments count along the width.
height_segments : float
Plane segments count along the height.
direction: unicode
``{'-x', '+x', '-y', '+y', '-z', '+z'}``
Direction the plane will be facing.
Returns
-------
vertices : array
Array of vertices suitable for use as a VertexBuffer.
faces : array
Indices to use to produce a filled plane.
outline : array
Indices to use to produce an outline of the plane.
References
----------
.. [1] Cabello, R. (n.d.). PlaneBufferGeometry.js. Retrieved May 12, 2015,
from http://git.io/vU1Fh
"""

x_grid = width_segments
y_grid = height_segments

x_grid1 = x_grid + 1
y_grid1 = y_grid + 1

# Positions, normals and texcoords.
positions = np.zeros(x_grid1 * y_grid1 * 3)
normals = np.zeros(x_grid1 * y_grid1 * 3)
texcoords = np.zeros(x_grid1 * y_grid1 * 2)

y = np.arange(y_grid1) * height / y_grid - height / 2
x = np.arange(x_grid1) * width / x_grid - width / 2

positions[::3] = np.tile(x, y_grid1)
positions[1::3] = -np.repeat(y, x_grid1)

normals[2::3] = 1

texcoords[::2] = np.tile(np.arange(x_grid1) / x_grid, y_grid1)
texcoords[1::2] = np.repeat(1 - np.arange(y_grid1) / y_grid, x_grid1)

# Faces and outline.
faces, outline = [], []
for i_y in range(y_grid):
for i_x in range(x_grid):
a = i_x + x_grid1 * i_y
b = i_x + x_grid1 * (i_y + 1)
c = (i_x + 1) + x_grid1 * (i_y + 1)
d = (i_x + 1) + x_grid1 * i_y

faces.extend(((a, b, d), (b, c, d)))
outline.extend(((a, b), (b, c), (c, d), (d, a)))

positions = np.reshape(positions, (-1, 3))
texcoords = np.reshape(texcoords, (-1, 2))
normals = np.reshape(normals, (-1, 3))

faces = np.reshape(faces, (-1, 3)).astype(np.uint32)
outline = np.reshape(outline, (-1, 2)).astype(np.uint32)

direction = direction.lower()
if direction in ('-x', '+x'):
shift, neutral_axis = 1, 0
elif direction in ('-y', '+y'):
shift, neutral_axis = -1, 1
elif direction in ('-z', '+z'):
shift, neutral_axis = 0, 2

sign = -1 if '-' in direction else 1

positions = np.roll(positions, shift, -1)
normals = np.roll(normals, shift, -1) * sign
colors = np.ravel(positions)
colors = np.hstack((np.reshape(np.interp(colors,
(np.min(colors),
np.max(colors)),
(0, 1)),
positions.shape),
np.ones((positions.shape[0], 1))))
colors[..., neutral_axis] = 0

vertices = np.zeros(positions.shape[0],
[('position', np.float32, 3),
('texcoord', np.float32, 2),
('normal', np.float32, 3),
('color', np.float32, 4)])

vertices['position'] = positions
vertices['texcoord'] = texcoords
vertices['normal'] = normals
vertices['color'] = colors

return vertices, faces, outline


def create_box(width=1, height=1, depth=1, width_segments=1, height_segments=1,
depth_segments=1, planes=None):
""" Generate vertices & indices for a filled and outlined box.
Parameters
----------
width : float
Box width.
height : float
Box height.
depth : float
Box depth.
width_segments : int
Box segments count along the width.
height_segments : float
Box segments count along the height.
depth_segments : float
Box segments count along the depth.
planes: array_like
Any combination of ``{'-x', '+x', '-y', '+y', '-z', '+z'}``
Included planes in the box construction.
Returns
-------
vertices : array
Array of vertices suitable for use as a VertexBuffer.
faces : array
Indices to use to produce a filled box.
outline : array
Indices to use to produce an outline of the box.
"""

planes = (('+x', '-x', '+y', '-y', '+z', '-z')
if planes is None else
[d.lower() for d in planes])

w_s, h_s, d_s = width_segments, height_segments, depth_segments

planes_m = []
if '-z' in planes:
planes_m.append(create_plane(width, depth, w_s, d_s, '-z'))
planes_m[-1][0]['position'][..., 2] -= height / 2
if '+z' in planes:
planes_m.append(create_plane(width, depth, w_s, d_s, '+z'))
planes_m[-1][0]['position'][..., 2] += height / 2

if '-y' in planes:
planes_m.append(create_plane(height, width, h_s, w_s, '-y'))
planes_m[-1][0]['position'][..., 1] -= depth / 2
if '+y' in planes:
planes_m.append(create_plane(height, width, h_s, w_s, '+y'))
planes_m[-1][0]['position'][..., 1] += depth / 2

if '-x' in planes:
planes_m.append(create_plane(depth, height, d_s, h_s, '-x'))
planes_m[-1][0]['position'][..., 0] -= width / 2
if '+x' in planes:
planes_m.append(create_plane(depth, height, d_s, h_s, '+x'))
planes_m[-1][0]['position'][..., 0] += width / 2

positions = np.zeros((0, 3), dtype=np.float32)
texcoords = np.zeros((0, 2), dtype=np.float32)
normals = np.zeros((0, 3), dtype=np.float32)

faces = np.zeros((0, 3), dtype=np.uint32)
outline = np.zeros((0, 2), dtype=np.uint32)

offset = 0
for vertices_p, faces_p, outline_p in planes_m:
positions = np.vstack((positions, vertices_p['position']))
texcoords = np.vstack((texcoords, vertices_p['texcoord']))
normals = np.vstack((normals, vertices_p['normal']))

faces = np.vstack((faces, faces_p + offset))
outline = np.vstack((outline, outline_p + offset))
offset += vertices_p['position'].shape[0]

vertices = np.zeros(positions.shape[0],
[('position', np.float32, 3),
('texcoord', np.float32, 2),
('normal', np.float32, 3),
('color', np.float32, 4)])

colors = np.ravel(positions)
colors = np.hstack((np.reshape(np.interp(colors,
(np.min(colors),
np.max(colors)),
(0, 1)),
positions.shape),
np.ones((positions.shape[0], 1))))

vertices['position'] = positions
vertices['texcoord'] = texcoords
vertices['normal'] = normals
vertices['color'] = colors

return vertices, faces, outline


def create_sphere(rows, cols, radius=1.0, offset=True):
"""Create a sphere
Expand Down
17 changes: 16 additions & 1 deletion vispy/geometry/tests/test_generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
from numpy.testing import assert_array_equal, assert_allclose

from vispy.testing import run_tests_if_main
from vispy.geometry import create_cube, create_cylinder, create_sphere
from vispy.geometry import (create_box, create_cube, create_cylinder,
create_sphere, create_plane)


def test_box():
"""Test box function"""
vertices, filled, outline = create_box()
assert_array_equal(np.arange(len(vertices)), np.unique(filled))
assert_array_equal(np.arange(len(vertices)), np.unique(outline))


def test_cube():
Expand All @@ -29,4 +37,11 @@ def test_cylinder():
assert_allclose(radii, np.ones_like(radii) * 10)


def test_plane():
"""Test plane function"""
vertices, filled, outline = create_plane()
assert_array_equal(np.arange(len(vertices)), np.unique(filled))
assert_array_equal(np.arange(len(vertices)), np.unique(outline))


run_tests_if_main()
2 changes: 2 additions & 0 deletions vispy/visuals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
defined in vispy.scene.
"""

from .box import BoxVisual # noqa
from .cube import CubeVisual # noqa
from .ellipse import EllipseVisual # noqa
from .gridlines import GridLinesVisual # noqa
Expand All @@ -23,6 +24,7 @@
from .line_plot import LinePlotVisual # noqa
from .markers import MarkersVisual, marker_types # noqa
from .mesh import MeshVisual # noqa
from .plane import PlaneVisual # noqa
from .polygon import PolygonVisual # noqa
from .rectangle import RectangleVisual # noqa
from .regular_polygon import RegularPolygonVisual # noqa
Expand Down
Loading

0 comments on commit f8a69d0

Please sign in to comment.