Parametric surface¶
Graphics 3D object for triangulating surfaces, and a base class for many other objects that can be represented by a 2D parametrization.
It takes great care to turn degenerate quadrilaterals into triangles and to propagate identified points to all attached polygons. This is not so much to save space as it is to assist the raytracers/other rendering systems to better understand the surface (and especially calculate correct surface normals).
AUTHORS:
Robert Bradshaw (2007-08-26): initial version
EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface, MoebiusStrip
sage: def f(x, y): return x+y, sin(x)*sin(y), x*y
sage: P = ParametricSurface(f, (srange(0,10,0.1), srange(-5,5.0,0.1)))
sage: show(P)
>>> from sage.all import *
>>> from sage.plot.plot3d.parametric_surface import ParametricSurface, MoebiusStrip
>>> def f(x, y): return x+y, sin(x)*sin(y), x*y
>>> P = ParametricSurface(f, (srange(Integer(0),Integer(10),RealNumber('0.1')), srange(-Integer(5),RealNumber('5.0'),RealNumber('0.1'))))
>>> show(P)
sage: S = MoebiusStrip(1,.2)
sage: S.is_enclosed()
False
sage: show(S)
>>> from sage.all import *
>>> S = MoebiusStrip(Integer(1),RealNumber('.2'))
>>> S.is_enclosed()
False
>>> show(S)
By default, the surface is colored with one single color.
sage: P = ParametricSurface(f, (srange(0,10,0.1), srange(-5,5.0,0.1)),
....: color='red')
sage: P.show()
>>> from sage.all import *
>>> P = ParametricSurface(f, (srange(Integer(0),Integer(10),RealNumber('0.1')), srange(-Integer(5),RealNumber('5.0'),RealNumber('0.1'))),
... color='red')
>>> P.show()
One can instead provide a coloring function and a colormap:
sage: def f(x, y): return x+y, x-y, x*y
sage: def c(x, y): return sin((x+y)/2)**2
sage: cm = colormaps.RdYlGn
sage: P = ParametricSurface(f, (srange(-5,5,0.1), srange(-5,5.0,0.1)), color=(c,cm))
sage: P.show(viewer='tachyon')
>>> from sage.all import *
>>> def f(x, y): return x+y, x-y, x*y
>>> def c(x, y): return sin((x+y)/Integer(2))**Integer(2)
>>> cm = colormaps.RdYlGn
>>> P = ParametricSurface(f, (srange(-Integer(5),Integer(5),RealNumber('0.1')), srange(-Integer(5),RealNumber('5.0'),RealNumber('0.1'))), color=(c,cm))
>>> P.show(viewer='tachyon')
Note that the coloring function should rather have values between 0 and 1. This value is passed to the chosen colormap.
Another colored example:
sage: colm = colormaps.autumn
sage: def g(x, y): return x, y, x**2 + y**2
sage: P = ParametricSurface(g, (srange(-10,10,0.1), srange(-5,5.0,0.1)), color=(c,colm))
sage: P.show(viewer='tachyon')
>>> from sage.all import *
>>> colm = colormaps.autumn
>>> def g(x, y): return x, y, x**Integer(2) + y**Integer(2)
>>> P = ParametricSurface(g, (srange(-Integer(10),Integer(10),RealNumber('0.1')), srange(-Integer(5),RealNumber('5.0'),RealNumber('0.1'))), color=(c,colm))
>>> P.show(viewer='tachyon')
Note
One may override eval()
or eval_c()
in a subclass
rather than passing in a function for greater speed.
One also would want to override get_grid.
Todo
actually remove unused points, fix the below code:
S = ParametricSurface(f=lambda xy: (xy[0],xy[1],0), domain=(range(10),range(10)))
- class sage.plot.plot3d.parametric_surface.MoebiusStrip(r, width, twists=1, **kwds)[source]¶
Bases:
ParametricSurface
Base class for the
MoebiusStrip
graphics type. This sets the basic parameters of the object.INPUT:
r
– a number which can be coerced to a float, serving roughly as the radius of the objectwidth
– a number which can be coerced to a float, which gives the width of the objecttwists
– (default: 1) an integer, giving the number of twists in the object (where one twist is the ‘traditional’ Möbius strip)
EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: M = MoebiusStrip(3,3) sage: M.show()
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> M = MoebiusStrip(Integer(3),Integer(3)) >>> M.show()
- eval(u, v)[source]¶
Return a tuple for \(x,y,z\) coordinates for the given
u
andv
for this MoebiusStrip instance.EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: N = MoebiusStrip(7,3,2) # two twists sage: N.eval(-1,0) (4.0, 0.0, -0.0)
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> N = MoebiusStrip(Integer(7),Integer(3),Integer(2)) # two twists >>> N.eval(-Integer(1),Integer(0)) (4.0, 0.0, -0.0)
- get_grid(ds)[source]¶
Return appropriate \(u\) and \(v\) ranges for this MoebiusStrip instance.
This is intended for internal use in creating an actual plot.
INPUT:
ds
– a number, typically coming from a RenderParams object, which helps determine the increment for the \(v\) range for the MoebiusStrip object
EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: N = MoebiusStrip(7,3,2) # two twists sage: N.get_grid(N.default_render_params().ds) ([-1, 1], [0.0, 0.12566370614359174, 0.25132741228718347, 0.37699111843077515, ...])
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> N = MoebiusStrip(Integer(7),Integer(3),Integer(2)) # two twists >>> N.get_grid(N.default_render_params().ds) ([-1, 1], [0.0, 0.12566370614359174, 0.25132741228718347, 0.37699111843077515, ...])
- class sage.plot.plot3d.parametric_surface.ParametricSurface[source]¶
Bases:
IndexFaceSet
Base class that initializes the ParametricSurface graphics type. This sets options, the function to be plotted, and the plotting array as attributes.
INPUT:
f
– (default:None
) the defining function. Either a tuple of three functions, or a single function which returns a tuple, taking two python floats as input. To subclass, passNone
forf
and overrideeval_c
oreval
instead.domain
– (default:None
) a tuple of two lists, defining the grid of \(u,v\) values. IfNone
, this will be calculated automatically.color
– (default:None
) a pair \((h,c)\) where \(h\) is a function with values in \([0,1]\) and \(c\) is a colormap. The color of a point \(p\) is then defined as the composition \(c(h(p))\)
EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface sage: def f(x, y): return cos(x)*sin(y), sin(x)*sin(y), cos(y)+log(tan(y/2))+0.2*x sage: S = ParametricSurface(f, (srange(0,12.4,0.1), srange(0.1,2,0.1))) sage: show(S) sage: len(S.face_list()) 2214
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import ParametricSurface >>> def f(x, y): return cos(x)*sin(y), sin(x)*sin(y), cos(y)+log(tan(y/Integer(2)))+RealNumber('0.2')*x >>> S = ParametricSurface(f, (srange(Integer(0),RealNumber('12.4'),RealNumber('0.1')), srange(RealNumber('0.1'),Integer(2),RealNumber('0.1')))) >>> show(S) >>> len(S.face_list()) 2214
The Hessenberg surface:
sage: def f(u, v): ....: a = 1 ....: from math import cos, sin, sinh, cosh ....: x = cos(a)*(cos(u)*sinh(v)-cos(3*u)*sinh(3*v)/3) + sin(a)*( ....: sin(u)*cosh(v)-sin(3*u)*cosh(3*v)/3) ....: y = cos(a)*(sin(u)*sinh(v)+sin(3*u)*sinh(3*v)/3) + sin(a)*( ....: -cos(u)*cosh(v)-cos(3*u)*cosh(3*v)/3) ....: z = cos(a)*cos(2*u)*cosh(2*v)+sin(a)*sin(2*u)*sinh(2*v) ....: return (x,y,z) sage: v = srange(float(0),float((3/2)*pi),float(0.1)) sage: S = ParametricSurface(f, (srange(float(0),float(pi),float(0.1)), ....: srange(float(-1),float(1),float(0.1))), color='blue') sage: show(S)
>>> from sage.all import * >>> def f(u, v): ... a = Integer(1) ... from math import cos, sin, sinh, cosh ... x = cos(a)*(cos(u)*sinh(v)-cos(Integer(3)*u)*sinh(Integer(3)*v)/Integer(3)) + sin(a)*( ... sin(u)*cosh(v)-sin(Integer(3)*u)*cosh(Integer(3)*v)/Integer(3)) ... y = cos(a)*(sin(u)*sinh(v)+sin(Integer(3)*u)*sinh(Integer(3)*v)/Integer(3)) + sin(a)*( ... -cos(u)*cosh(v)-cos(Integer(3)*u)*cosh(Integer(3)*v)/Integer(3)) ... z = cos(a)*cos(Integer(2)*u)*cosh(Integer(2)*v)+sin(a)*sin(Integer(2)*u)*sinh(Integer(2)*v) ... return (x,y,z) >>> v = srange(float(Integer(0)),float((Integer(3)/Integer(2))*pi),float(RealNumber('0.1'))) >>> S = ParametricSurface(f, (srange(float(Integer(0)),float(pi),float(RealNumber('0.1'))), ... srange(float(-Integer(1)),float(Integer(1)),float(RealNumber('0.1')))), color='blue') >>> show(S)
A colored example using the
color
keyword:sage: def g(x, y): return x, y, - x**2 + y**2 sage: def c(x, y): return sin((x-y/2)*y/4)**2 sage: cm = colormaps.gist_rainbow sage: P = ParametricSurface(g, (srange(-10,10,0.1), ....: srange(-5,5.0,0.1)),color=(c,cm)) sage: P.show(viewer='tachyon')
>>> from sage.all import * >>> def g(x, y): return x, y, - x**Integer(2) + y**Integer(2) >>> def c(x, y): return sin((x-y/Integer(2))*y/Integer(4))**Integer(2) >>> cm = colormaps.gist_rainbow >>> P = ParametricSurface(g, (srange(-Integer(10),Integer(10),RealNumber('0.1')), ... srange(-Integer(5),RealNumber('5.0'),RealNumber('0.1'))),color=(c,cm)) >>> P.show(viewer='tachyon')
- bounding_box()[source]¶
Return the lower and upper corners of a 3D bounding box for
self
.This is used for rendering and
self
should fit entirely within this box.Specifically, the first point returned should have x, y, and z coordinates should be the respective infimum over all points in
self
, and the second point is the supremum.EXAMPLES:
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: M = MoebiusStrip(7,3,2) sage: M.bounding_box() ((-10.0, -7.53907349250478..., -2.9940801852848145), (10.0, 7.53907349250478..., 2.9940801852848145))
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> M = MoebiusStrip(Integer(7),Integer(3),Integer(2)) >>> M.bounding_box() ((-10.0, -7.53907349250478..., -2.9940801852848145), (10.0, 7.53907349250478..., 2.9940801852848145))
- default_render_params()[source]¶
Return an instance of RenderParams suitable for plotting this object.
- dual()[source]¶
Return an
IndexFaceSet
which is the dual of theParametricSurface
object as a triangulated surface.EXAMPLES:
As one might expect, this gives an icosahedron:
sage: D = dodecahedron() sage: D.dual() Graphics3d Object
>>> from sage.all import * >>> D = dodecahedron() >>> D.dual() Graphics3d Object
But any enclosed surface should work:
sage: from sage.plot.plot3d.shapes import Torus sage: T = Torus(1, .2) sage: T.dual() Graphics3d Object sage: T.is_enclosed() True
>>> from sage.all import * >>> from sage.plot.plot3d.shapes import Torus >>> T = Torus(Integer(1), RealNumber('.2')) >>> T.dual() Graphics3d Object >>> T.is_enclosed() True
Surfaces which are not enclosed, though, should raise an exception:
sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: M = MoebiusStrip(3,1) sage: M.is_enclosed() False sage: M.dual() Traceback (most recent call last): ... NotImplementedError: this is only implemented for enclosed surfaces
>>> from sage.all import * >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> M = MoebiusStrip(Integer(3),Integer(1)) >>> M.is_enclosed() False >>> M.dual() Traceback (most recent call last): ... NotImplementedError: this is only implemented for enclosed surfaces
- is_enclosed()[source]¶
Return a boolean telling whether or not it is necessary to render the back sides of the polygons (assuming, of course, that they have the correct orientation).
This is calculated in by verifying the opposite edges of the rendered domain either line up or are pinched together.
EXAMPLES:
sage: from sage.plot.plot3d.shapes import Sphere sage: Sphere(1).is_enclosed() True sage: from sage.plot.plot3d.parametric_surface import MoebiusStrip sage: MoebiusStrip(1,0.2).is_enclosed() False
>>> from sage.all import * >>> from sage.plot.plot3d.shapes import Sphere >>> Sphere(Integer(1)).is_enclosed() True >>> from sage.plot.plot3d.parametric_surface import MoebiusStrip >>> MoebiusStrip(Integer(1),RealNumber('0.2')).is_enclosed() False
- jmol_repr(render_params)[source]¶
Return a representation of the object suitable for plotting using Jmol.
- json_repr(render_params)[source]¶
Return a representation of the object in JSON format as a list with one element, which is a string of a dictionary listing vertices, faces and colors.
- obj_repr(render_params)[source]¶
Return a complete representation of object with name, texture, and lists of vertices, faces, and back-faces.
- plot()[source]¶
Draw a 3D plot of this graphics object, which just returns this object since this is already a 3D graphics object. Needed to support PLOT in doctrings, see Issue #17498
EXAMPLES:
sage: S = parametric_plot3d( (sin, cos, lambda u: u/10), (0, 20)) sage: S.plot() is S True
>>> from sage.all import * >>> S = parametric_plot3d( (sin, cos, lambda u: u/Integer(10)), (Integer(0), Integer(20))) >>> S.plot() is S True
- tachyon_repr(render_params)[source]¶
Return representation of the object suitable for plotting using Tachyon ray tracer.
- threejs_repr(render_params)[source]¶
Return a representation of the surface suitable for plotting with three.js.
EXAMPLES:
sage: _ = var('x,y') sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2)) sage: P.threejs_repr(P.default_render_params()) [('surface', {'color': '#6666ff', 'faces': [[0, 1, 2, 3], ... 'opacity': 1.0, 'vertices': [{'x': -2.0, 'y': -2.0, 'z': 0.0}, ... {'x': 2.0, 'y': 2.0, 'z': 0.0}]})]
>>> from sage.all import * >>> _ = var('x,y') >>> P = plot3d(x**Integer(2)-y**Integer(2), (x, -Integer(2), Integer(2)), (y, -Integer(2), Integer(2))) >>> P.threejs_repr(P.default_render_params()) [('surface', {'color': '#6666ff', 'faces': [[0, 1, 2, 3], ... 'opacity': 1.0, 'vertices': [{'x': -2.0, 'y': -2.0, 'z': 0.0}, ... {'x': 2.0, 'y': 2.0, 'z': 0.0}]})]
- triangulate(render_params=None)[source]¶
Call self.eval_grid() for all \((u,v)\) in \(\text{urange} \times \text{vrange}\) to construct this surface.
The most complicated part of this code is identifying shared vertices and shrinking trivial edges. This is not done so much to save memory, rather it is needed so normals of the triangles can be calculated correctly.