Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/plot3d/shapes.pyx
4036 views
1
"""
2
Basic objects such as Sphere, Box, Cone, etc.
3
4
AUTHORS:
5
-- Robert Bradshaw 2007-02: initial version
6
-- Robert Bradshaw 2007-08: obj/tachon rendering, much updating
7
-- Robert Bradshaw 2007-08: cythonization
8
9
EXAMPLES:
10
sage: from sage.plot.plot3d.shapes import *
11
sage: S = Sphere(.5, color='yellow')
12
sage: S += Cone(.5, .5, color='red').translate(0,0,.3)
13
sage: S += Sphere(.1, color='white').translate(.45,-.1,.15) + Sphere(.05, color='black').translate(.51,-.1,.17)
14
sage: S += Sphere(.1, color='white').translate(.45, .1,.15) + Sphere(.05, color='black').translate(.51, .1,.17)
15
sage: S += Sphere(.1, color='yellow').translate(.5, 0, -.2)
16
sage: S.show()
17
sage: S.scale(1,1,2).show()
18
19
sage: from sage.plot.plot3d.shapes import *
20
sage: Torus(.7, .2, color=(0,.3,0)).show()
21
"""
22
23
24
#*****************************************************************************
25
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
26
#
27
# Distributed under the terms of the GNU General Public License (GPL)
28
#
29
# This code is distributed in the hope that it will be useful,
30
# but WITHOUT ANY WARRANTY; without even the implied warranty of
31
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32
# General Public License for more details.
33
#
34
# The full text of the GPL is available at:
35
#
36
# http://www.gnu.org/licenses/
37
#*****************************************************************************
38
39
40
cdef extern from "math.h":
41
double sqrt(double)
42
double sin(double)
43
double cos(double)
44
double tan(double)
45
double asin(double)
46
double acos(double)
47
double atan(double)
48
double M_PI
49
50
from sage.rings.real_double import RDF
51
from sage.modules.free_module_element import vector
52
53
from sage.misc.all import srange
54
from sage.plot.misc import rename_keyword
55
56
from base import Graphics3dGroup, Graphics3d
57
58
# Helper function to check that Box input is right
59
def validate_frame_size(size):
60
"""
61
Checks that the input is an iterable of length 3 with all
62
elements nonnegative and coercible to floats.
63
64
EXAMPLES::
65
66
sage: from sage.plot.plot3d.shapes import validate_frame_size
67
sage: validate_frame_size([3,2,1])
68
[3.0, 2.0, 1.0]
69
70
TESTS::
71
72
sage: from sage.plot.plot3d.shapes import validate_frame_size
73
sage: validate_frame_size([3,2,-1])
74
Traceback (most recent call last):
75
...
76
ValueError: each box dimension must be nonnegative
77
sage: validate_frame_size([sqrt(-1),3,2])
78
Traceback (most recent call last):
79
...
80
TypeError: each box dimension must coerce to a float
81
"""
82
if not isinstance(size, (list, tuple)):
83
raise TypeError("size must be a list or tuple")
84
if len(size) != 3:
85
raise TypeError("size must be of length 3")
86
try:
87
size = [float(x) for x in size]
88
except TypeError:
89
raise TypeError("each box dimension must coerce to a float")
90
for x in size:
91
if x < 0:
92
raise ValueError("each box dimension must be nonnegative")
93
return size
94
95
96
class Box(IndexFaceSet):
97
"""
98
EXAMPLES:
99
sage: from sage.plot.plot3d.shapes import Box
100
101
A square black box:
102
sage: show(Box([1,1,1]))
103
104
A red rectangular box.
105
sage: show(Box([2,3,4], color="red"))
106
107
A stack of boxes:
108
sage: show(sum([Box([2,3,1], color="red").translate((0,0,6*i)) for i in [0..3]]))
109
110
A sinusoidal stack of multicolored boxes:
111
sage: B = sum([Box([2,4,1/4], color=(i/4,i/5,1)).translate((sin(i),0,5-i)) for i in [0..20]])
112
sage: show(B, figsize=6)
113
"""
114
def __init__(self, *size, **kwds):
115
"""
116
EXAMPLES:
117
sage: from sage.plot.plot3d.shapes import Box
118
sage: Box(10, 1, 1) + Box(1, 10, 1) + Box(1, 1, 10)
119
"""
120
if isinstance(size[0], (tuple, list)):
121
size = validate_frame_size(size[0])
122
self.size = size
123
x, y, z = self.size
124
faces = [[(x, y, z), (-x, y, z), (-x,-y, z), ( x,-y, z)],
125
[(x, y, z), ( x, y,-z), (-x, y,-z), (-x, y, z)],
126
[(x, y, z), ( x,-y, z), ( x,-y,-z), ( x, y,-z)] ]
127
faces += [list(reversed([(-x,-y,-z) for x,y,z in face])) for face in faces]
128
IndexFaceSet.__init__(self, faces, enclosed=True, **kwds)
129
130
def bounding_box(self):
131
"""
132
EXAMPLES:
133
sage: from sage.plot.plot3d.shapes import Box
134
sage: Box([1,2,3]).bounding_box()
135
((-1.0, -2.0, -3.0), (1.0, 2.0, 3.0))
136
"""
137
return tuple([-a for a in self.size]), tuple(self.size)
138
139
def x3d_geometry(self):
140
"""
141
EXAMPLES:
142
sage: from sage.plot.plot3d.shapes import Box
143
sage: Box([1,2,1/4]).x3d_geometry()
144
"<Box size='1.0 2.0 0.25'/>"
145
"""
146
return "<Box size='%s %s %s'/>" % tuple(self.size)
147
148
def ColorCube(size, colors, opacity=1, **kwds):
149
"""
150
Return a cube with given size and sides with given colors.
151
152
INPUT:
153
size -- 3-tuple of sizes (same as for box and frame)
154
colors -- a list of either 3 or 6 colors
155
opacity -- (default: 1) opacity of cube sides
156
**kwds -- passed to the face constructor
157
158
OUTPUT:
159
-- a 3d graphics object
160
161
EXAMPLES:
162
A color cube with translucent sides:
163
sage: from sage.plot.plot3d.shapes import ColorCube
164
sage: c = ColorCube([1,2,3], ['red', 'blue', 'green', 'black', 'white', 'orange'], opacity=0.5)
165
sage: c.show()
166
sage: list(c.texture_set())[0].opacity
167
0.500000000000000
168
169
If you omit the last 3 colors then the first three are repeated (with
170
repeated colors on opposing faces):
171
sage: c = ColorCube([0.5,0.5,0.5], ['red', 'blue', 'green'])
172
"""
173
if not isinstance(size, (tuple, list)):
174
size = (size, size, size)
175
box = Box(size)
176
faces = box.face_list()
177
if len(colors) == 3:
178
colors = colors * 2
179
all = []
180
181
from texture import Texture
182
for k in range(6):
183
all.append(IndexFaceSet([faces[k]], enclosed=True,
184
texture=Texture(colors[k], opacity=opacity),
185
**kwds))
186
return Graphics3dGroup(all)
187
188
cdef class Cone(ParametricSurface):
189
"""
190
A cone, with base in the xy-plane pointing up the z-axis.
191
192
INPUT:
193
194
- ``radius`` - positive real number
195
196
- ``height`` - positive real number
197
198
- ``closed`` - whether or not to include the base (default True)
199
200
- ``**kwds`` -- passed to the ParametricSurface constructor
201
202
EXAMPLES::
203
204
sage: from sage.plot.plot3d.shapes import Cone
205
sage: c = Cone(3/2, 1, color='red') + Cone(1, 2, color='yellow').translate(3, 0, 0)
206
sage: c.show(aspect_ratio=1)
207
208
We may omit the base::
209
210
sage: Cone(1, 1, closed=False)
211
212
A spiky plot of the sine function::
213
214
sage: sum(Cone(.1, sin(n), color='yellow').translate(n, sin(n), 0) for n in [0..10, step=.1])
215
216
A Christmas tree::
217
218
sage: T = sum(Cone(exp(-n/5), 4/3*exp(-n/5), color=(0, .5, 0)).translate(0, 0, -3*exp(-n/5)) for n in [1..7])
219
sage: T += Cone(1/8, 1, color='brown').translate(0, 0, -3)
220
sage: T.show(aspect_ratio=1, frame=False)
221
"""
222
def __init__(self, radius, height, closed=True, **kwds):
223
"""
224
TESTS:
225
sage: from sage.plot.plot3d.shapes import Cone
226
sage: c = Cone(1/2, 1, opacity=.5)
227
"""
228
ParametricSurface.__init__(self, **kwds)
229
self.radius = radius
230
self.height = height
231
self.closed = closed
232
233
def x3d_geometry(self):
234
"""
235
EXAMPLES::
236
237
sage: from sage.plot.plot3d.shapes import Cone
238
sage: Cone(1, 3).x3d_geometry()
239
"<Cone bottomRadius='1.0' height='3.0'/>"
240
241
"""
242
return "<Cone bottomRadius='%s' height='%s'/>"%(self.radius, self.height)
243
244
def get_grid(self, ds):
245
"""
246
Returns the grid on which to evaluate this parametric surface.
247
248
EXAMPLES::
249
250
sage: from sage.plot.plot3d.shapes import Cone
251
sage: Cone(1, 3, closed=True).get_grid(100)
252
([1, 0, -1], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
253
sage: Cone(1, 3, closed=False).get_grid(100)
254
([1, 0], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
255
sage: len(Cone(1, 3).get_grid(.001)[1])
256
38
257
"""
258
cdef int k, t_res = min(max(int(2*M_PI*self.radius/ds), 5), 37)
259
if self.closed:
260
urange = [1,0,-1]
261
else:
262
urange = [1,0]
263
vrange = [2*M_PI*k/t_res for k from 0 <= k < t_res] + [0.0]
264
return urange, vrange
265
266
cdef int eval_c(self, point_c *res, double u, double v) except -1:
267
if u == -1:
268
res.x, res.y, res.z = 0, 0, 0
269
elif u == 0:
270
res.x = self.radius*sin(v)
271
res.y = self.radius*cos(v)
272
res.z = 0
273
else: # u == 1:
274
res.x, res.y, res.z = 0, 0, self.height
275
276
277
cdef class Cylinder(ParametricSurface):
278
"""
279
A cone, with base in the xy-plane pointing up the z-axis.
280
281
INPUT:
282
283
- ``radius`` - positive real number
284
285
- ``height`` - positive real number
286
287
- ``closed`` - whether or not to include the ends (default True)
288
289
- ``**kwds`` -- passed to the ParametricSurface constructor
290
291
EXAMPLES::
292
293
sage: from sage.plot.plot3d.shapes import Cylinder
294
sage: c = Cylinder(3/2, 1, color='red') + Cylinder(1, 2, color='yellow').translate(3, 0, 0)
295
sage: c.show(aspect_ratio=1)
296
297
We may omit the base::
298
299
sage: Cylinder(1, 1, closed=False)
300
301
Some gears::
302
303
sage: G = Cylinder(1, .5) + Cylinder(.25, 3).translate(0, 0, -3)
304
sage: G += sum(Cylinder(.2, 1).translate(cos(2*pi*n/9), sin(2*pi*n/9), 0) for n in [1..9])
305
sage: G += G.translate(2.3, 0, -.5)
306
sage: G += G.translate(3.5, 2, -1)
307
sage: G.show(aspect_ratio=1, frame=False)
308
"""
309
def __init__(self, radius, height, closed=True, **kwds):
310
"""
311
TESTS:
312
sage: from sage.plot.plot3d.shapes import Cylinder
313
sage: Cylinder(1, 1, color='red')
314
"""
315
ParametricSurface.__init__(self, **kwds)
316
self.radius = radius
317
self.height = height
318
self.closed = closed
319
320
def bounding_box(self):
321
"""
322
EXAMPLES::
323
324
sage: from sage.plot.plot3d.shapes import Cylinder
325
sage: Cylinder(1, 2).bounding_box()
326
((-1.0, -1.0, 0), (1.0, 1.0, 2.0))
327
328
"""
329
return (-self.radius, -self.radius, 0), (self.radius, self.radius, self.height)
330
331
def x3d_geometry(self):
332
"""
333
EXAMPLES::
334
335
sage: from sage.plot.plot3d.shapes import Cylinder
336
sage: Cylinder(1, 2).x3d_geometry()
337
"<Cylinder radius='1.0' height='2.0'/>"
338
339
"""
340
return "<Cylinder radius='%s' height='%s'/>"%(self.radius, self.height)
341
342
def tachyon_repr(self, render_params):
343
"""
344
EXAMPLES::
345
346
sage: from sage.plot.plot3d.shapes import Cylinder
347
sage: C = Cylinder(1/2, 4, closed=False)
348
sage: C.tachyon_repr(C.default_render_params())
349
'FCylinder \n Base 0 0 0\n Apex 0 0 4.0\n Rad 0.5\n texture... '
350
sage: C = Cylinder(1, 2)
351
sage: C.tachyon_repr(C.default_render_params())
352
['Ring Center 0 0 0 Normal 0 0 1 Inner 0 Outer 1.0 texture...',
353
'FCylinder \n Base 0 0 0\n Apex 0 0 2.0\n Rad 1.0\n texture... ',
354
'Ring Center 0 0 2.0 Normal 0 0 1 Inner 0 Outer 1.0 texture...']
355
"""
356
transform = render_params.transform
357
if not (transform is None or transform.is_uniform_on([(1,0,0),(0,1,0)])):
358
# Tachyon can't do squashed
359
return ParametricSurface.tachyon_repr(self, render_params)
360
361
base, top = self.get_endpoints(transform)
362
rad = self.get_radius(transform)
363
cyl = """FCylinder
364
Base %s %s %s
365
Apex %s %s %s
366
Rad %s
367
%s """%(base[0], base[1], base[2], top[0], top[1], top[2], rad, self.texture.id)
368
if self.closed:
369
normal = (0,0,1)
370
if transform is not None:
371
normal = transform.transform_vector(normal)
372
base_cap = """Ring Center %s %s %s Normal %s %s %s Inner 0 Outer %s %s""" \
373
% (base[0], base[1], base[2], normal[0], normal[1], normal[2], rad, self.texture.id)
374
top_cap = """Ring Center %s %s %s Normal %s %s %s Inner 0 Outer %s %s""" \
375
% ( top[0], top[1], top[2], normal[0], normal[1], normal[2], rad, self.texture.id)
376
return [base_cap, cyl, top_cap]
377
else:
378
return cyl
379
380
def jmol_repr(self, render_params):
381
"""
382
EXAMPLES::
383
384
sage: from sage.plot.plot3d.shapes import Cylinder
385
386
For thin cylinders, lines are used::
387
sage: C = Cylinder(.1, 4)
388
sage: C.jmol_repr(C.default_render_params())
389
['\ndraw line_1 width 0.1 {0 0 0} {0 0 4.0}\ncolor $line_1 [102,102,255]\n']
390
391
For anything larger, we use a pmesh::
392
sage: C = Cylinder(3, 1, closed=False)
393
sage: C.jmol_repr(C.testing_render_params())
394
['pmesh obj_1 "obj_1.pmesh"\ncolor pmesh [102,102,255]']
395
"""
396
transform = render_params.transform
397
base, top = self.get_endpoints(transform)
398
rad = self.get_radius(transform)
399
400
cdef double ratio = sqrt(rad*rad / ((base[0]-top[0])**2 + (base[1]-top[1])**2 + (base[2]-top[2])**2))
401
402
if ratio > .02:
403
if not (transform is None or transform.is_uniform_on([(1,0,0),(0,1,0)])) or ratio > .05:
404
# Jmol can't do squashed
405
return ParametricSurface.jmol_repr(self, render_params)
406
407
name = render_params.unique_name('line')
408
return ["""
409
draw %s width %s {%s %s %s} {%s %s %s}\n%s
410
""" % (name,
411
rad,
412
base[0], base[1], base[2],
413
top [0], top [1], top [2],
414
self.texture.jmol_str("$" + name)) ]
415
416
def get_endpoints(self, transform=None):
417
"""
418
EXAMPLES::
419
420
sage: from sage.plot.plot3d.shapes import Cylinder
421
sage: from sage.plot.plot3d.transform import Transformation
422
sage: Cylinder(1, 5).get_endpoints()
423
((0, 0, 0), (0, 0, 5.0))
424
sage: Cylinder(1, 5).get_endpoints(Transformation(trans=(1,2,3), scale=(2,2,2)))
425
((1.0, 2.0, 3.0), (1.0, 2.0, 13.0))
426
"""
427
if transform is None:
428
return (0,0,0), (0,0,self.height)
429
else:
430
return transform.transform_point((0,0,0)), transform.transform_point((0,0,self.height))
431
432
def get_radius(self, transform=None):
433
"""
434
EXAMPLES::
435
436
sage: from sage.plot.plot3d.shapes import Cylinder
437
sage: from sage.plot.plot3d.transform import Transformation
438
sage: Cylinder(3, 1).get_radius()
439
3.0
440
sage: Cylinder(3, 1).get_radius(Transformation(trans=(1,2,3), scale=(2,2,2)))
441
6.0
442
"""
443
if transform is None:
444
return self.radius
445
else:
446
radv = transform.transform_vector((self.radius,0,0))
447
return sqrt(sum([x*x for x in radv]))
448
449
def get_grid(self, ds):
450
"""
451
Returns the grid on which to evaluate this parametric surface.
452
453
EXAMPLES::
454
455
sage: from sage.plot.plot3d.shapes import Cylinder
456
sage: Cylinder(1, 3, closed=True).get_grid(100)
457
([2, 1, -1, -2], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
458
sage: Cylinder(1, 3, closed=False).get_grid(100)
459
([1, -1], [0.0, 1.2566..., 2.5132..., 3.7699..., 5.0265..., 0.0])
460
sage: len(Cylinder(1, 3).get_grid(.001)[1])
461
38
462
"""
463
cdef int k, v_res = min(max(int(2*M_PI*self.radius/ds), 5), 37)
464
if self.closed:
465
urange = [2,1,-1,-2]
466
else:
467
urange = [1,-1]
468
vrange = [2*M_PI*k/v_res for k from 0 <= k < v_res] + [0.0]
469
return urange, vrange
470
471
cdef int eval_c(self, point_c *res, double u, double v) except -1:
472
if u == -2:
473
res.x, res.y, res.z = 0, 0, 0
474
elif u == -1:
475
res.x = self.radius*sin(v)
476
res.y = self.radius*cos(v)
477
res.z = 0
478
elif u == 1:
479
res.x = self.radius*sin(v)
480
res.y = self.radius*cos(v)
481
res.z = self.height
482
else: # u == 2:
483
res.x, res.y, res.z = 0, 0, self.height
484
485
486
def LineSegment(start, end, thickness=1, radius=None, **kwds):
487
"""
488
Create a line segment, which is drawn as a cylinder from start to
489
end with radius radius.
490
491
EXAMPLES:
492
sage: from sage.plot.plot3d.shapes import LineSegment, Sphere
493
sage: P = (0,0,0.1)
494
sage: Q = (0.5,0.6,0.7)
495
sage: S = Sphere(.2, color='red').translate(P) + \
496
Sphere(.2, color='blue').translate(Q) + \
497
LineSegment(P, Q, .05, color='black')
498
sage: S.show()
499
sage: S = Sphere(.1, color='red').translate(P) + \
500
Sphere(.1, color='blue').translate(Q) + \
501
LineSegment(P, Q, .15, color='black')
502
sage: S.show()
503
504
AUTHOR:
505
-- Robert Bradshaw
506
"""
507
if radius is None:
508
radius = thickness/50.0
509
start = vector(RDF, start, sparse=False)
510
end = vector(RDF, end, sparse=False)
511
zaxis = vector(RDF, (0,0,1), sparse=False)
512
diff = end - start
513
height= sqrt(diff.dot_product(diff))
514
cyl = Cylinder(radius, height, **kwds)
515
axis = zaxis.cross_product(diff)
516
if axis == 0:
517
if diff[2] < 0:
518
return cyl.translate(end)
519
else:
520
return cyl.translate(start)
521
else:
522
theta = -acos(diff[2]/height)
523
return cyl.rotate(axis, theta).translate(start)
524
525
@rename_keyword(deprecated='Sage 4.6', deprecated_option='thickness', thickness='width')
526
def arrow3d(start, end, width=1, radius=None, head_radius=None, head_len=None, **kwds):
527
"""
528
Create a 3d arrow.
529
530
INPUT:
531
start -- (x,y,z) point; the starting point of the arrow
532
end -- (x,y,z) point; the end point
533
width -- (default: 1); how wide the arrow is
534
radius -- (default: width/50.0) the radius of the arrow
535
head_radius -- (default: 3*radius); radius of arrow head
536
head_len -- (default: 3*head_radius); len of arrow head
537
538
EXAMPLES:
539
The default arrow:
540
sage: arrow3d((0,0,0), (1,1,1), 1)
541
542
A fat arrow:
543
sage: arrow3d((0,0,0), (1,1,1), radius=0.1)
544
545
A green arrow:
546
sage: arrow3d((0,0,0), (1,1,1), color='green')
547
548
A fat arrow head:
549
sage: arrow3d((2,1,0), (1,1,1), color='green', head_radius=0.3, aspect_ratio=[1,1,1])
550
551
Many arrow arranged in a circle (flying spears?):
552
sage: sum([arrow3d((cos(t),sin(t),0),(cos(t),sin(t),1)) for t in [0,0.3,..,2*pi]])
553
554
Change the width of the arrow. (Note: for an arrow that scales with zoom, please consider
555
the 'line3d' function with the option 'arrow_head=True'):
556
sage: arrow3d((0,0,0), (1,1,1), width=1)
557
558
TESTS:
559
If the arrow is too long, the shaft and part of the head is cut off.
560
sage: a = arrow3d((0,0,0), (0,0,0.5), head_len=1)
561
sage: len(a.all)
562
1
563
sage: type(a.all[0])
564
<type 'sage.plot.plot3d.shapes.Cone'>
565
566
Arrows are always constructed pointing up in the z direction from
567
the origin, and then rotated/translated into place. This works for
568
every arrow direction except the -z direction. We take care of the
569
anomaly by testing to see if the arrow should point in the -z
570
direction, and if it should, just scaling the constructed arrow by
571
-1 (i.e., every point is sent to its negative). The scaled arrow
572
then points downwards. The doctest just tests that the scale of -1
573
is applied to the arrow.
574
575
sage: a = arrow3d((0,0,0), (0,0,-1))
576
sage: a.all[0].get_transformation().transform_point((0,0,1))
577
(0.0, 0.0, -1.0)
578
579
The thickness option is now deprecated. It has been replaced by the width option.
580
581
sage: arrow3d((0,0,0), (1,1,1), thickness=1)
582
doctest:...: DeprecationWarning: (Since Sage 4.6) use the option 'width' instead of 'thickness'
583
<BLANKLINE>
584
"""
585
if radius is None:
586
radius = width/50.0
587
if head_radius == None:
588
head_radius = 3*radius
589
if head_len == None:
590
head_len = 3*head_radius
591
start = vector(RDF, start, sparse=False)
592
end = vector(RDF, end, sparse=False)
593
zaxis = vector(RDF, (0,0,1), sparse=False)
594
diff = end - start
595
length = sqrt(diff.dot_product(diff))
596
if length <= head_len:
597
arrow = Cone(head_radius*length/head_len, length, **kwds)
598
else:
599
arrow = Cylinder(radius, length-head_len, **kwds) \
600
+ Cone(head_radius, head_len, **kwds).translate(0, 0, length-head_len)
601
axis = zaxis.cross_product(diff)
602
if axis == 0:
603
if diff[2] >= 0:
604
return arrow.translate(start)
605
else:
606
return arrow.scale(-1).translate(start)
607
else:
608
theta = -acos(diff[2]/length)
609
return arrow.rotate(axis, theta).translate(start)
610
611
612
613
cdef class Sphere(ParametricSurface):
614
"""
615
This class represents a sphere centered at the origin.
616
617
EXAMPLES::
618
619
sage: from sage.plot.plot3d.shapes import Sphere
620
sage: Sphere(3)
621
622
Plot with aspect_ratio=1 to see it unsquashed::
623
624
sage: S = Sphere(3, color='blue') + Sphere(2, color='red').translate(0,3,0)
625
sage: S.show(aspect_ratio=1)
626
627
Scale to get an ellipsoid::
628
sage: S = Sphere(1).scale(1,2,1/2)
629
sage: S.show(aspect_ratio=1)
630
631
"""
632
def __init__(self, radius, **kwds):
633
"""
634
TESTS:
635
sage: from sage.plot.plot3d.shapes import Sphere
636
sage: Sphere(3)
637
"""
638
ParametricSurface.__init__(self, **kwds)
639
self.radius = radius
640
641
def bounding_box(self):
642
"""
643
Return the bounding box that contains this sphere.
644
645
EXAMPLES::
646
647
sage: from sage.plot.plot3d.shapes import Sphere
648
sage: Sphere(3).bounding_box()
649
((-3.0, -3.0, -3.0), (3.0, 3.0, 3.0))
650
"""
651
return ((-self.radius, -self.radius, -self.radius),
652
(self.radius, self.radius, self.radius))
653
654
def x3d_geometry(self):
655
"""
656
EXAMPLES::
657
658
sage: from sage.plot.plot3d.shapes import Sphere
659
sage: Sphere(12).x3d_geometry()
660
"<Sphere radius='12.0'/>"
661
"""
662
return "<Sphere radius='%s'/>"%(self.radius)
663
664
def tachyon_repr(self, render_params):
665
"""
666
Tachyon can natively handle spheres. Ellipsoids rendering is done
667
as a parametric surface.
668
669
EXAMPLES::
670
671
sage: from sage.plot.plot3d.shapes import Sphere
672
sage: S = Sphere(2)
673
sage: S.tachyon_repr(S.default_render_params())
674
'Sphere center 0 0 0 Rad 2.0 texture...'
675
sage: S.translate(1, 2, 3).scale(3).tachyon_repr(S.default_render_params())
676
[['Sphere center 3.0 6.0 9.0 Rad 6.0 texture...']]
677
sage: S.scale(1,1/2,1/4).tachyon_repr(S.default_render_params())
678
[['TRI V0 0 0 -0.5 V1 0.308116 0.0271646 -0.493844 V2 0.312869 0 -0.493844',
679
'texture...',
680
...
681
'TRI V0 0.308116 -0.0271646 0.493844 V1 0.312869 0 0.493844 V2 0 0 0.5',
682
'texture...']]
683
"""
684
transform = render_params.transform
685
if not (transform is None or transform.is_uniform()):
686
return ParametricSurface.tachyon_repr(self, render_params)
687
688
if transform is None:
689
cen = (0,0,0)
690
rad = self.radius
691
else:
692
cen = transform.transform_point((0,0,0))
693
radv = transform.transform_vector((self.radius,0,0))
694
rad = sqrt(sum([x*x for x in radv]))
695
return "Sphere center %s %s %s Rad %s %s" % (cen[0], cen[1], cen[2], rad, self.texture.id)
696
697
def jmol_repr(self, render_params):
698
"""
699
EXAMPLES::
700
701
sage: from sage.plot.plot3d.shapes import Sphere
702
703
Jmol has native code for handling spheres::
704
705
sage: S = Sphere(2)
706
sage: S.jmol_repr(S.default_render_params())
707
['isosurface sphere_1 center {0 0 0} sphere 2.0\ncolor isosurface [102,102,255]']
708
sage: S.translate(10, 100, 1000).jmol_repr(S.default_render_params())
709
[['isosurface sphere_1 center {10.0 100.0 1000.0} sphere 2.0\ncolor isosurface [102,102,255]']]
710
711
It can't natively handle ellipsoids::
712
713
sage: Sphere(1).scale(2, 3, 4).jmol_repr(S.testing_render_params())
714
[['pmesh obj_2 "obj_2.pmesh"\ncolor pmesh [102,102,255]']]
715
716
Small spheres need extra hints to render well::
717
718
sage: Sphere(.01).jmol_repr(S.default_render_params())
719
['isosurface sphere_1 resolution 100 center {0 0 0} sphere 0.01\ncolor isosurface [102,102,255]']
720
"""
721
name = render_params.unique_name('sphere')
722
transform = render_params.transform
723
if not (transform is None or transform.is_uniform()):
724
return ParametricSurface.jmol_repr(self, render_params)
725
726
if transform is None:
727
cen = (0,0,0)
728
rad = self.radius
729
else:
730
cen = transform.transform_point((0,0,0))
731
radv = transform.transform_vector((self.radius,0,0))
732
rad = sqrt(sum([x*x for x in radv]))
733
if rad < 0.5:
734
res = "resolution %s" % min(int(7/rad), 100)
735
else:
736
res = ""
737
return ["isosurface %s %s center {%s %s %s} sphere %s\n%s" % (name, res, cen[0], cen[1], cen[2], rad, self.texture.jmol_str("isosurface"))]
738
739
def get_grid(self, double ds):
740
"""
741
Returns the the range of variables to be evaluated on to render as a
742
parametric surface.
743
744
EXAMPLES::
745
746
sage: from sage.plot.plot3d.shapes import Sphere
747
sage: Sphere(1).get_grid(100)
748
([-10.0, ..., 0.0, ..., 10.0],
749
[0.0, ..., 3.141592653589793, ..., 0.0])
750
"""
751
cdef int K, u_res, v_res
752
u_res = min(max(int(M_PI*self.radius/ds), 6), 20)
753
v_res = min(max(int(2*M_PI * self.radius/ds), 6), 36)
754
urange = [-10.0] + [M_PI * k/u_res - M_PI/2 for k in range(1, u_res)] + [10.0]
755
vrange = [2*M_PI * k/v_res for k in range(v_res)] + [0.0]
756
return urange, vrange
757
758
cdef int eval_c(self, point_c *res, double u, double v) except -1:
759
if u == -10:
760
res.x, res.y, res.z = 0, 0, -self.radius
761
elif u == 10:
762
res.x, res.y, res.z = 0, 0, self.radius
763
else:
764
res.x = self.radius*cos(v) * cos(u)
765
res.y = self.radius*sin(v) * cos(u)
766
res.z = self.radius * sin(u)
767
768
769
cdef class Torus(ParametricSurface):
770
# e.g show(sum([Torus(1,.03,20,20, color=[1, float(t/30), 0]).rotate((1,1,1),t) for t in range(30)], Sphere(.3)))
771
"""
772
INPUT:
773
R -- (default: 1) outer radius
774
r -- (default: .3) inner radius
775
776
OUTPUT:
777
a 3d torus
778
779
EXAMPLES::
780
781
sage: from sage.plot.plot3d.shapes import Torus
782
sage: Torus(1, .2).show(aspect_ratio=1)
783
sage: Torus(1, .7, color='red').show(aspect_ratio=1)
784
785
A rubberband ball::
786
787
sage: show(sum([Torus(1, .03, color=(1, t/30.0, 0)).rotate((1,1,1),t) for t in range(30)]))
788
789
Mmm... doughnuts::
790
791
sage: D = Torus(1, .4, color=(.5, .3, .2)) + Torus(1, .3, color='yellow').translate(0, 0, .15)
792
sage: G = sum(D.translate(RDF.random_element(-.2, .2), RDF.random_element(-.2, .2), .8*t) for t in range(10))
793
sage: G.show(aspect_ratio=1, frame=False)
794
"""
795
def __init__(self, R=1, r=.3, **kwds):
796
"""
797
TESTS:
798
sage: from sage.plot.plot3d.shapes import Torus
799
sage: T = Torus(1, .5)
800
"""
801
ParametricSurface.__init__(self, None, **kwds)
802
self.R = R
803
self.r = r
804
805
def get_grid(self, ds):
806
"""
807
Returns the the range of variables to be evaluated on to render as a
808
parametric surface.
809
810
EXAMPLES::
811
812
sage: from sage.plot.plot3d.shapes import Torus
813
sage: Torus(2, 1).get_grid(100)
814
([0.0, -1.047..., -3.141592653589793, ..., 0.0],
815
[0.0, 1.047..., 3.141592653589793, ..., 0.0])
816
"""
817
cdef int k, u_divs, v_divs
818
u_divs = min(max(int(4*M_PI * self.R/ds), 6), 37)
819
v_divs = min(max(int(4*M_PI * self.r/ds), 6), 37)
820
urange = [0.0] + [-2*M_PI * k/u_divs for k in range(1, u_divs)] + [0.0]
821
vrange = [ 2*M_PI * k/v_divs for k in range(v_divs)] + [0.0]
822
return urange, vrange
823
824
cdef int eval_c(self, point_c *res, double u, double v) except -1:
825
res.x = (self.R+self.r*sin(v))*sin(u)
826
res.y = (self.R+self.r*sin(v))*cos(u)
827
res.z = self.r*cos(v)
828
829
830
class Text(PrimitiveObject):
831
"""
832
A text label attached to a point in 3d space. It always starts at the
833
origin, translate it to move it elsewhere.
834
835
EXAMPLES::
836
837
sage: from sage.plot.plot3d.shapes import Text
838
sage: Text("Just a lonely label.")
839
sage: pts = [(RealField(10)^3).random_element() for k in range(20)]
840
sage: sum(Text(str(P)).translate(P) for P in pts)
841
842
"""
843
def __init__(self, string, **kwds):
844
"""
845
TEST:
846
sage: from sage.plot.plot3d.shapes import Text
847
sage: T = Text("Hi")
848
"""
849
PrimitiveObject.__init__(self, **kwds)
850
self.string = string
851
852
def x3d_geometry(self):
853
"""
854
EXAMPLES::
855
856
sage: from sage.plot.plot3d.shapes import Text
857
sage: Text("Hi").x3d_geometry()
858
"<Text string='Hi' solid='true'/>"
859
"""
860
return "<Text string='%s' solid='true'/>"%self.string
861
862
def obj_repr(self, render_params):
863
"""
864
The obj file format doesn't support text strings::
865
866
sage: from sage.plot.plot3d.shapes import Text
867
sage: Text("Hi").obj_repr(None)
868
''
869
"""
870
return ''
871
872
def tachyon_repr(self, render_params):
873
"""
874
Strings are not yet supported in Tachyon, so we ignore them for now::
875
876
sage: from sage.plot.plot3d.shapes import Text
877
sage: Text("Hi").tachyon_repr(None)
878
''
879
"""
880
return ''
881
# Text in Tachyon not implemented yet.
882
# I have no idea what the code below is supposed to do.
883
## transform = render_params.transform
884
## if not (transform is None or transform.is_uniform()):
885
## return ParametricSurface.tachyon_repr(self, render_params)
886
887
## if transform is None:
888
## cen = (0,0,0)
889
## rad = self.radius
890
## else:
891
## cen = transform.transform_point((0,0,0))
892
## radv = transform.transform_vector((self.radius,0,0))
893
## rad = sqrt(sum([x*x for x in radv]))
894
## return "Sphere center %s %s %s Rad %s %s" % (cen[0], cen[1], cen[2], rad, self.texture.id)
895
896
def jmol_repr(self, render_params):
897
"""
898
Labels in jmol must be attached to atoms.
899
900
EXAMPLES::
901
902
sage: from sage.plot.plot3d.shapes import Text
903
sage: T = Text("Hi")
904
sage: T.jmol_repr(T.testing_render_params())
905
['select atomno = 1', 'color atom [102,102,255]', 'label "Hi"']
906
sage: T = Text("Hi").translate(-1, 0, 0) + Text("Bye").translate(1, 0, 0)
907
sage: T.jmol_repr(T.testing_render_params())
908
[[['select atomno = 1', 'color atom [102,102,255]', 'label "Hi"']],
909
[['select atomno = 2', 'color atom [102,102,255]', 'label "Bye"']]]
910
"""
911
cen = (0,0,0)
912
if render_params.transform is not None:
913
cen = render_params.transform.transform_point(cen)
914
render_params.atom_list.append(cen)
915
atom_no = len(render_params.atom_list)
916
return ['select atomno = %s' % atom_no,
917
self.get_texture().jmol_str("atom"),
918
'label "%s"' % self.string] #.replace('\n', '|')]
919
920
def bounding_box(self):
921
"""
922
Text labels have no extent::
923
924
sage: from sage.plot.plot3d.shapes import Text
925
sage: Text("Hi").bounding_box()
926
((0, 0, 0), (0, 0, 0))
927
"""
928
return (0,0,0), (0,0,0)
929
930
931