Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/plot3d/parametric_surface.pyx
4045 views
1
r"""
2
Parametric Surface
3
4
Graphics 3D object for triangulating surfaces, and a base class for many other
5
objects that can be represented by a 2D parametrization.
6
7
It takes great care to turn degenerate quadrilaterals into triangles and
8
to propagate identified points to all attached polygons. This is not
9
so much to save space as it is to assist the raytracers/other rendering
10
systems to better understand the surface (and especially calculate correct
11
surface normals).
12
13
AUTHORS:
14
15
- Robert Bradshaw (2007-08-26): initial version
16
17
EXAMPLES::
18
19
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface, MobiusStrip
20
sage: def f(x,y): return x+y, sin(x)*sin(y), x*y
21
sage: P = ParametricSurface(f, (srange(0,10,0.1), srange(-5,5.0,0.1)))
22
sage: show(P)
23
sage: S = MobiusStrip(1,.2)
24
sage: S.is_enclosed()
25
False
26
sage: S.show()
27
28
.. NOTE::
29
30
One may override ``eval()`` or ``eval_c()`` in a subclass
31
rather than passing in a function for greater speed.
32
One also would want to override get_grid.
33
34
TODO: actually remove unused points, fix the below code::
35
36
S = ParametricSurface(f=(lambda (x,y):(x,y,0)), domain=(range(10),range(10)))
37
38
"""
39
40
#*****************************************************************************
41
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
42
#
43
# Distributed under the terms of the GNU General Public License (GPL)
44
#
45
# This code is distributed in the hope that it will be useful,
46
# but WITHOUT ANY WARRANTY; without even the implied warranty of
47
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
48
# General Public License for more details.
49
#
50
# The full text of the GPL is available at:
51
#
52
# http://www.gnu.org/licenses/
53
#*****************************************************************************
54
55
56
57
include "../../ext/stdsage.pxi"
58
include "../../ext/interrupt.pxi"
59
60
include "point_c.pxi"
61
62
from math import cos, sin
63
from sage.rings.all import RDF
64
65
from base import RenderParams
66
from sage.ext.fast_eval cimport FastDoubleFunc
67
from sage.ext.interpreters.wrapper_rdf cimport Wrapper_rdf
68
from sage.ext.fast_eval import fast_float
69
70
71
cdef inline bint smash_edge(point_c* vs, face_c* f, int a, int b):
72
if point_c_eq(vs[f.vertices[a]], vs[f.vertices[b]]):
73
f.vertices[b] = f.vertices[a]
74
f.n = 3
75
return 1
76
else:
77
return 0
78
79
cdef class ParametricSurface(IndexFaceSet):
80
"""
81
Base class that initializes the ParametricSurface
82
graphics type. This sets options, the function to be plotted, and the
83
plotting array as attributes.
84
85
INPUT:
86
87
- ``f`` - (default: None) The defining function. Either a tuple of
88
three functions, or a single function which returns a tuple, taking
89
two python floats as input. To subclass, pass None for f and override
90
eval_c or eval instead.
91
92
- ``domain`` - (default: None) A tuple of two lists, defining the
93
grid of `u,v` values. If None, this will be calculate automatically.
94
95
EXAMPLES::
96
97
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface
98
sage: def f(x,y): return cos(x)*sin(y), sin(x)*sin(y), cos(y)+log(tan(y/2))+0.2*x
99
sage: S = ParametricSurface(f, (srange(0,12.4,0.1), srange(0.1,2,0.1)))
100
sage: show(S)
101
102
sage: len(S.face_list())
103
2214
104
105
The Hessenberg surface:
106
107
::
108
109
sage: def f(u,v):
110
... a = 1
111
... from math import cos, sin, sinh, cosh
112
... x = cos(a)*(cos(u)*sinh(v)-cos(3*u)*sinh(3*v)/3) + sin(a)*(
113
... sin(u)*cosh(v)-sin(3*u)*cosh(3*v)/3)
114
... y = cos(a)*(sin(u)*sinh(v)+sin(3*u)*sinh(3*v)/3) + sin(a)*(
115
... -cos(u)*cosh(v)-cos(3*u)*cosh(3*v)/3)
116
... z = cos(a)*cos(2*u)*cosh(2*v)+sin(a)*sin(2*u)*sinh(2*v)
117
... return (x,y,z)
118
sage: v = srange(float(0),float((3/2)*pi),float(0.1))
119
sage: S = ParametricSurface(f, (srange(float(0),float(pi),float(0.1)),
120
... srange(float(-1),float(1),float(0.1))), color="blue")
121
sage: show(S)
122
"""
123
124
def __init__(self, f=None, domain=None, **kwds):
125
"""
126
Create the graphics primitive :class:`ParametricSurface`. See the
127
docstring of this class for full documentation.
128
129
EXAMPLES::
130
131
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface
132
sage: def f(x,y): return x+y, sin(x)*sin(y), x*y
133
sage: S = ParametricSurface(f, (srange(0,12.4,0.1), srange(0.1,2,0.1)))
134
"""
135
if isinstance(f, list):
136
f = tuple(f)
137
self.f = f
138
self.render_grid = domain
139
IndexFaceSet.__init__(self, [], [], **kwds)
140
141
def default_render_params(self):
142
"""
143
Returns an instance of RenderParams suitable for plotting this object.
144
145
TEST::
146
147
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
148
sage: type(MobiusStrip(3,3).default_render_params())
149
<class 'sage.plot.plot3d.base.RenderParams'>
150
"""
151
return RenderParams(ds=.075, crease_threshold=.35)
152
153
def x3d_geometry(self):
154
r"""
155
Returns XML-like representation of the coordinates of all points
156
in a triangulation of the object along with an indexing of those
157
points.
158
159
TESTS::
160
161
sage: _ = var('x,y')
162
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
163
sage: s = P.x3d_str()
164
sage: s[:100]
165
"<Shape>\n<IndexedFaceSet coordIndex='0,1,..."
166
"""
167
self.triangulate(self.default_render_params())
168
return IndexFaceSet.x3d_geometry(self)
169
170
def tachyon_repr(self, render_params):
171
"""
172
Returns representation of the object suitable for plotting
173
using Tachyon ray tracer.
174
175
TESTS::
176
177
sage: _ = var('x,y')
178
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
179
sage: s = P.tachyon_repr(P.default_render_params())
180
sage: s[:2]
181
['TRI V0 -2 -2 0 V1 -2 -1.89744 0.399737 V2 -1.89744 -1.89744 0', 'texture...']
182
"""
183
self.triangulate(render_params)
184
return IndexFaceSet.tachyon_repr(self, render_params)
185
186
def obj_repr(self, render_params):
187
"""
188
Returns complete representation of object with name, texture, and
189
lists of vertices, faces, and back-faces.
190
191
TESTS::
192
193
sage: _ = var('x,y')
194
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
195
sage: s = P.obj_repr(P.default_render_params())
196
sage: s[:2]+s[2][:3]+s[3][:3]
197
['g obj_1', 'usemtl texture...', 'v -2 -2 0', 'v -2 -1.89744 0.399737', 'v -2 -1.79487 0.778435', 'f 1 2 42 41', 'f 2 3 43 42', 'f 3 4 44 43']
198
"""
199
self.triangulate(render_params)
200
return IndexFaceSet.obj_repr(self, render_params)
201
202
def jmol_repr(self, render_params):
203
r"""
204
Returns representation of the object suitable for plotting
205
using Jmol.
206
207
TESTS::
208
209
sage: _ = var('x,y')
210
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
211
sage: s = P.jmol_repr(P.testing_render_params())
212
sage: s[:10]
213
['pmesh obj_1 "obj_1.pmesh"\ncolor pmesh [102,102,255]']
214
"""
215
self.triangulate(render_params)
216
return IndexFaceSet.jmol_repr(self, render_params)
217
218
def json_repr(self, render_params):
219
"""
220
Returns representation of the object in JSON format as
221
a list with one element, which is a string of a dictionary
222
listing vertices and faces.
223
224
TESTS::
225
226
sage: _ = var('x,y')
227
sage: P = plot3d(x^2-y^2, (x, -2, 2), (y, -2, 2))
228
sage: s = P.json_repr(P.default_render_params())
229
sage: s[0][:100]
230
'{vertices:[{x:-2,y:-2,z:0},{x:-2,y:-1.89744,z:0.399737},{x:-2,y:-1.79487,z:0.778435},{x:-2,y:-1.6923'
231
"""
232
self.triangulate(render_params)
233
return IndexFaceSet.json_repr(self, render_params)
234
235
def is_enclosed(self):
236
"""
237
Returns a boolean telling whether or not it is necessary to
238
render the back sides of the polygons (assuming, of course,
239
that they have the correct orientation).
240
241
This is calculated in by verifying the opposite edges
242
of the rendered domain either line up or are pinched together.
243
244
EXAMPLES::
245
246
sage: from sage.plot.plot3d.shapes import Sphere
247
sage: Sphere(1).is_enclosed()
248
True
249
250
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
251
sage: MobiusStrip(1,0.2).is_enclosed()
252
False
253
"""
254
if self.fcount == 0:
255
self.triangulate()
256
return self.enclosed
257
258
def dual(self):
259
"""
260
Returns an ``IndexFaceSet`` which is the dual of the
261
:class:`ParametricSurface` object as a triangulated surface.
262
263
EXAMPLES:
264
265
As one might expect, this gives an icosahedron::
266
267
sage: D = dodecahedron()
268
sage: D.dual()
269
270
But any enclosed surface should work::
271
272
sage: from sage.plot.plot3d.shapes import Torus
273
sage: T = Torus(1, .2)
274
sage: T.dual()
275
sage: T.is_enclosed()
276
True
277
278
Surfaces which are not enclosed, though, should raise an exception::
279
280
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
281
sage: M = MobiusStrip(3,1)
282
sage: M.is_enclosed()
283
False
284
sage: M.dual()
285
Traceback (most recent call last):
286
...
287
NotImplementedError: This is only implemented for enclosed surfaces
288
289
"""
290
# This doesn't completely make sense...
291
if self.fcount == 0:
292
self.triangulate()
293
if not self.is_enclosed():
294
raise NotImplementedError, "This is only implemented for enclosed surfaces"
295
return IndexFaceSet.dual(self)
296
297
def bounding_box(self):
298
"""
299
Returns the lower and upper corners of a 3D bounding box for self.
300
This is used for rendering and self should fit entirely within this
301
box.
302
303
Specifically, the first point returned should have x, y, and z
304
coordinates should be the respective infimum over all points in self,
305
and the second point is the supremum.
306
307
EXAMPLES::
308
309
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
310
sage: M = MobiusStrip(7,3,2)
311
sage: M.bounding_box()
312
((-10.0, -7.53907349250478..., -2.9940801852848145), (10.0, 7.53907349250478..., 2.9940801852848145))
313
"""
314
# We must triangulate before computing the bounding box; otherwise
315
# we'll get an empty bounding box, as the bounding box is computed
316
# using the triangulation, and before triangulating the triangulation
317
# is empty.
318
self.triangulate()
319
return IndexFaceSet.bounding_box(self)
320
321
def triangulate(self, render_params=None):
322
r"""
323
Calls self.eval_grid() for all `(u,v)` in
324
`\text{urange} \times \text{vrange}` to construct this surface.
325
326
The most complicated part of this code is identifying shared
327
vertices and shrinking trivial edges. This is not done so much
328
to save memory, rather it is needed so normals of the triangles
329
can be calculated correctly.
330
331
TESTS::
332
333
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface, MobiusStrip
334
sage: def f(x,y): return x+y, sin(x)*sin(y), x*y # indirect doctests
335
sage: P = ParametricSurface(f, (srange(0,10,0.1), srange(-5,5.0,0.1))) # indirect doctests
336
sage: P.show() # indirect doctests
337
sage: S = MobiusStrip(1,.2) # indirect doctests
338
sage: S.show() # indirect doctests
339
"""
340
cdef double u, v
341
if render_params is None:
342
render_params = self.default_render_params()
343
ds = render_params.ds
344
if render_params.transform is not None:
345
ds /= render_params.transform.max_scale()
346
urange, vrange = self.get_grid(ds)
347
urange = [float(u) for u in urange]
348
vrange = [float(v) for v in vrange]
349
if self.render_grid == (urange, vrange) and self.fcount != 0:
350
# Already triangulated at on this grid.
351
return
352
353
cdef Py_ssize_t i, j
354
cdef Py_ssize_t n = len(urange) - 1
355
cdef Py_ssize_t m = len(vrange) - 1
356
cdef Py_ssize_t ix = 0
357
358
sig_on()
359
try:
360
self.realloc((m+1)*(n+1), m*n, 4*m*n)
361
self.eval_grid(urange, vrange)
362
except: # TODO -- this would catch control-C,etc. -- FIX THIS TO CATCH WHAT IS RAISED!!!!
363
sig_off()
364
self.fcount = self.vcount = 0
365
self.render_grid = None
366
raise
367
368
# face_c.vertices:
369
#
370
# 0 - 1
371
# | |
372
# 3 - 2
373
374
cdef face_c *face, *last_face
375
376
for i from 0 <= i < n:
377
for j from 0 <= j < m:
378
ix = i*m+j
379
face = &self._faces[ix]
380
face.n = 4
381
face.vertices = &self.face_indices[4*ix]
382
383
# Connect to the i-1 row
384
if i == 0:
385
if j == 0:
386
face.vertices[0] = 0
387
else:
388
face.vertices[0] = self._faces[ix-1].vertices[1]
389
face.vertices[1] = j+1
390
smash_edge(self.vs, face, 0, 1)
391
else:
392
face.vertices[0] = self._faces[ix-m].vertices[3]
393
face.vertices[1] = self._faces[ix-m].vertices[2]
394
395
# Connect to the j-1 col
396
if j == 0:
397
face.vertices[3] = (i+1)*(m+1)
398
smash_edge(self.vs, face, 0, 3)
399
else:
400
face.vertices[3] = self._faces[ix-1].vertices[2]
401
402
# This is the newly-seen vertex, identify if it's a triangle
403
face.vertices[2] = (i+1)*(m+1)+j+1
404
smash_edge(self.vs, face, 1, 2) or smash_edge(self.vs, face, 3, 2)
405
406
# Now we see if it wraps around or is otherwise enclosed
407
cdef bint enclosed = 1
408
409
cdef face_c *first, *last
410
for j from 0 <= j < m:
411
first = &self._faces[j]
412
last = &self._faces[(n-1)*m+j]
413
if point_c_eq(self.vs[first.vertices[0]], self.vs[last.vertices[3]]):
414
last.vertices[3] = first.vertices[0]
415
elif first.vertices[0] != first.vertices[1] or last.vertices[3] != last.vertices[2]:
416
enclosed = 0
417
if point_c_eq(self.vs[first.vertices[1]], self.vs[last.vertices[2]]):
418
last.vertices[2] = first.vertices[1]
419
elif first.vertices[0] != first.vertices[1] or last.vertices[3] != last.vertices[2]:
420
enclosed = 0
421
422
for i from 0 <= i < n:
423
first = &self._faces[i*m]
424
last = &self._faces[i*m + m-1]
425
if point_c_eq(self.vs[first.vertices[0]], self.vs[last.vertices[1]]):
426
last.vertices[1] = first.vertices[0]
427
elif first.vertices[0] != first.vertices[3] or last.vertices[1] != last.vertices[2]:
428
enclosed = 0
429
if point_c_eq(self.vs[first.vertices[3]], self.vs[last.vertices[2]]):
430
last.vertices[2] = first.vertices[3]
431
elif first.vertices[0] != first.vertices[3] or last.vertices[1] != last.vertices[2]:
432
enclosed = 0
433
434
self.enclosed = enclosed
435
436
# make sure we deleted the correct point from the triangles
437
for ix from 0 <= ix < n*m:
438
face = &self._faces[ix]
439
if face.n == 3:
440
if face.vertices[3] == face.vertices[2] or face.vertices[3] == face.vertices[0]:
441
pass
442
else:
443
if face.vertices[0] == face.vertices[1]:
444
face.vertices[1] = face.vertices[2]
445
# face.vertices[1] == face.vertices[2]
446
face.vertices[2] = face.vertices[3]
447
448
sig_off()
449
450
self.vcount = (n+1)*(m+1)
451
self.fcount = n*m
452
self.icount = 4*n*m
453
self._clean_point_list()
454
455
self.render_grid = urange, vrange
456
457
458
def get_grid(self, ds):
459
"""
460
TEST::
461
462
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface
463
sage: def f(x,y): return x+y,x-y,x*y
464
sage: P = ParametricSurface(f)
465
sage: P.get_grid(.1)
466
Traceback (most recent call last):
467
...
468
NotImplementedError: You must override the get_grid method.
469
"""
470
if self.render_grid is None:
471
raise NotImplementedError, "You must override the get_grid method."
472
return self.render_grid
473
474
cdef int eval_grid(self, urange, vrange) except -1:
475
r"""
476
This fills in the points ``self.vs`` for all
477
`u \in \text{urange}, v \in \text{vrange}`.
478
We assume enough memory has been allocated.
479
480
We branch outside the loops for efficiency. The options for self.f are:
481
482
None -- call self.eval_c() or self.eval()
483
(One of these is presumably overridden.)
484
tuple -- split into fx, fy, fz and call each separately
485
callable -- call f(u,v)
486
487
In addition, branches are taken for efficient calling of FastDoubleFunc
488
(including whether to iterate over python or c doubles).
489
"""
490
cdef Py_ssize_t i, j, m, n
491
cdef double u, v
492
cdef double uv[2]
493
cdef point_c *res
494
cdef double* ulist
495
cdef double* vlist
496
cdef bint fast_x, fast_y, fast_z
497
498
if self.f is None:
499
500
m, n = len(urange), len(vrange)
501
ulist = to_double_array(urange)
502
vlist = to_double_array(vrange)
503
504
for i from 0 <= i < m:
505
u = ulist[i]
506
for j from 0 <= j < n:
507
v = vlist[j]
508
self.eval_c(&self.vs[i*n+j], u, v)
509
510
sage_free(ulist)
511
sage_free(vlist)
512
513
elif PY_TYPE_CHECK(self.f, tuple):
514
515
fx, fy, fz = self.f
516
fast_x = PY_TYPE_CHECK(fx, FastDoubleFunc) or PY_TYPE_CHECK(fx, Wrapper_rdf)
517
fast_y = PY_TYPE_CHECK(fy, FastDoubleFunc) or PY_TYPE_CHECK(fx, Wrapper_rdf)
518
fast_z = PY_TYPE_CHECK(fz, FastDoubleFunc) or PY_TYPE_CHECK(fx, Wrapper_rdf)
519
520
if fast_x or fast_y or fast_z:
521
522
m, n = len(urange), len(vrange)
523
ulist = to_double_array(urange)
524
vlist = to_double_array(vrange)
525
526
if PY_TYPE_CHECK(fx, FastDoubleFunc):
527
for i from 0 <= i < m:
528
uv[0] = ulist[i]
529
for j from 0 <= j < n:
530
uv[1] = vlist[j]
531
self.vs[i*n+j].x = (<FastDoubleFunc>fx)._call_c(uv)
532
elif fast_x: # must be Wrapper_rdf
533
for i from 0 <= i < m:
534
uv[0] = ulist[i]
535
for j from 0 <= j < n:
536
uv[1] = vlist[j]
537
(<Wrapper_rdf>fx).call_c(uv, &self.vs[i*n+j].x)
538
539
540
if PY_TYPE_CHECK(fy, FastDoubleFunc):
541
for i from 0 <= i < m:
542
uv[0] = ulist[i]
543
for j from 0 <= j < n:
544
uv[1] = vlist[j]
545
self.vs[i*n+j].y = (<FastDoubleFunc>fy)._call_c(uv)
546
elif fast_y: # must be Wrapper_rdf
547
for i from 0 <= i < m:
548
uv[0] = ulist[i]
549
for j from 0 <= j < n:
550
uv[1] = vlist[j]
551
(<Wrapper_rdf>fy).call_c(uv, &self.vs[i*n+j].y)
552
553
if PY_TYPE_CHECK(fz, FastDoubleFunc):
554
for i from 0 <= i < m:
555
uv[0] = ulist[i]
556
for j from 0 <= j < n:
557
uv[1] = vlist[j]
558
self.vs[i*n+j].z = (<FastDoubleFunc>fz)._call_c(uv)
559
elif fast_z: # must be Wrapper_rdf
560
for i from 0 <= i < m:
561
uv[0] = ulist[i]
562
for j from 0 <= j < n:
563
uv[1] = vlist[j]
564
(<Wrapper_rdf>fz).call_c(uv, &self.vs[i*n+j].z)
565
566
567
sage_free(ulist)
568
sage_free(vlist)
569
570
if not (fast_x and fast_y and fast_z):
571
ix = 0
572
for uu in urange:
573
for vv in vrange:
574
res = &self.vs[ix]
575
if not fast_x:
576
res.x = fx(uu, vv)
577
if not fast_y:
578
res.y = fy(uu, vv)
579
if not fast_z:
580
res.z = fz(uu, vv)
581
ix += 1
582
583
else:
584
ix = 0
585
for uu in urange:
586
for vv in vrange:
587
res = &self.vs[ix]
588
res.x, res.y, res.z = self.f(uu, vv)
589
ix += 1
590
591
# One of the following two methods should be overridden in
592
# derived classes.
593
594
cdef int eval_c(self, point_c *res, double u, double v) except -1:
595
# can't do a cpdef because of the point_c* argument
596
res.x, res.y, res.z = self.eval(u, v)
597
598
def eval(self, double u, double v):
599
"""
600
TEST::
601
602
sage: from sage.plot.plot3d.parametric_surface import ParametricSurface
603
sage: def f(x,y): return x+y,x-y,x*y
604
sage: P = ParametricSurface(f,(srange(0,1,0.1),srange(0,1,0.1)))
605
sage: P.eval(0,0)
606
Traceback (most recent call last):
607
...
608
NotImplementedError
609
"""
610
raise NotImplementedError
611
612
613
class MobiusStrip(ParametricSurface):
614
"""
615
Base class for the :class:`MobiusStrip` graphics type. This sets the the
616
basic parameters of the object.
617
618
INPUT:
619
620
- ``r`` - A number which can be coerced to a float, serving roughly
621
as the radius of the object.
622
623
- ``width`` - A number which can be coerced to a float, which gives the
624
width of the object.
625
626
- ``twists`` - (default: 1) An integer, giving the number of twists in the
627
object (where one twist is the 'traditional' Mobius strip).
628
629
EXAMPLES::
630
631
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
632
sage: M = MobiusStrip(3,3)
633
sage: M.show()
634
"""
635
636
def __init__(self, r, width, twists=1, **kwds):
637
"""
638
Create the graphics primitive MobiusStrip. See the docstring of
639
this class for full documentation.
640
641
EXAMPLES:
642
643
::
644
645
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
646
sage: M = MobiusStrip(3,3); M # Same width and radius, roughly
647
sage: N = MobiusStrip(7,3,2); N # two twists, lots of open area in the middle
648
sage: O = MobiusStrip(5,1,plot_points=200,color='red'); O # keywords get passed to plot3d
649
650
"""
651
ParametricSurface.__init__(self, **kwds)
652
self.r = float(r)
653
self.width = float(width)
654
self.twists = int(twists)
655
656
def get_grid(self, ds):
657
"""
658
Returns appropriate `u` and `v` ranges for this MobiusStrip instance.
659
This is intended for internal use in creating an actual plot.
660
661
INPUT:
662
663
- ``ds`` - A number, typically coming from a RenderParams object,
664
which helps determine the increment for the `v` range for the
665
MobiusStrip object.
666
667
EXAMPLE::
668
669
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
670
sage: N = MobiusStrip(7,3,2) # two twists
671
sage: N.get_grid(N.default_render_params().ds)
672
([-1, 1], [0.0, 0.125663706144, 0.251327412287, 0.376991118431, ... 0])
673
674
"""
675
twoPi = RDF.pi()*2
676
# Previous code, which doesn't seem to use any of the parameters
677
# TODO: figure out how to use it properly.
678
# res = max(min(twoPi*(self.r+self.twists*self.width)/ds, 10), 6*self.twists, 50)
679
res = max(6*self.twists, 50)
680
return [-1,1],[twoPi*k/res for k in range(res)] + [0]
681
682
def eval(self, u, v):
683
"""
684
Returns tuple for `x,y,z` coordinates for the given ``u`` and ``v``
685
for this MobiusStrip instance.
686
687
EXAMPLE::
688
689
sage: from sage.plot.plot3d.parametric_surface import MobiusStrip
690
sage: N = MobiusStrip(7,3,2) # two twists
691
sage: N.eval(-1,0)
692
(4.0, 0.0, -0.0)
693
"""
694
return ( (self.r + u*self.width*cos(self.twists*v/2)) * cos(v),
695
(self.r + u*self.width*cos(self.twists*v/2)) * sin(v),
696
u*self.width*sin(self.twists*v/2) )
697
698
699
cdef double* to_double_array(py_list) except NULL:
700
cdef double* c_list = <double *>sage_malloc(sizeof(double) * len(py_list))
701
if c_list == NULL:
702
raise MemoryError
703
cdef Py_ssize_t i = 0
704
cdef double a
705
for a in py_list:
706
c_list[i] = a
707
i += 1
708
return c_list
709
710
711