Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/plot/plot3d/tachyon.py
8815 views
1
r"""
2
The Tachyon 3D Ray Tracer
3
4
Given any 3D graphics object one can compute a raytraced
5
representation by typing ``show(viewer='tachyon')``.
6
For example, we draw two translucent spheres that contain a red
7
tube, and render the result using Tachyon.
8
9
::
10
11
sage: S = sphere(opacity=0.8, aspect_ratio=[1,1,1])
12
sage: L = line3d([(0,0,0),(2,0,0)], thickness=10, color='red')
13
sage: M = S + S.translate((2,0,0)) + L
14
sage: M.show(viewer='tachyon')
15
16
One can also directly control Tachyon, which gives a huge amount of
17
flexibility. For example, here we directly use Tachyon to draw 3
18
spheres on the coordinate axes. Notice that the result is
19
gorgeous::
20
21
sage: t = Tachyon(xres=500,yres=500, camera_center=(2,0,0))
22
sage: t.light((4,3,2), 0.2, (1,1,1))
23
sage: t.texture('t2', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0))
24
sage: t.texture('t3', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0))
25
sage: t.texture('t4', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,0,1))
26
sage: t.sphere((0,0.5,0), 0.2, 't2')
27
sage: t.sphere((0.5,0,0), 0.2, 't3')
28
sage: t.sphere((0,0,0.5), 0.2, 't4')
29
sage: t.show()
30
31
AUTHOR:
32
33
- John E. Stone ([email protected]): wrote tachyon ray tracer
34
35
- William Stein: sage-tachyon interface
36
37
- Joshua Kantor: 3d function plotting
38
39
- Tom Boothby: 3d function plotting n'stuff
40
41
- Leif Hille: key idea for bugfix for texfunc issue (trac #799)
42
43
- Marshall Hampton: improved doctests, rings, axis-aligned boxes.
44
45
TODO:
46
47
- clean up trianglefactory stuff
48
"""
49
50
from tri_plot import Triangle, SmoothTriangle, TriangleFactory, TrianglePlot
51
52
53
from sage.interfaces.tachyon import tachyon_rt
54
55
from sage.structure.sage_object import SageObject
56
57
from sage.misc.misc import SAGE_TMP
58
from sage.misc.temporary_file import tmp_filename, graphics_filename
59
60
#from sage.ext import fast_tachyon_routines
61
62
import os
63
64
from math import sqrt
65
66
class Tachyon(SageObject):
67
r"""
68
Create a scene the can be rendered using the Tachyon ray tracer.
69
70
INPUT:
71
72
- ``xres`` - (default 350)
73
- ``yres`` - (default 350)
74
- ``zoom`` - (default 1.0)
75
- ``antialiasing`` - (default False)
76
- ``aspectratio`` - (default 1.0)
77
- ``raydepth`` - (default 5)
78
- ``camera_center`` - (default (-3, 0, 0))
79
- ``updir`` - (default (0, 0, 1))
80
- ``look_at`` - (default (0,0,0))
81
- ``viewdir`` - (default None)
82
- ``projection`` - (default 'PERSPECTIVE')
83
84
OUTPUT: A Tachyon 3d scene.
85
86
Note that the coordinates are by default such that `z` is
87
up, positive `y` is to the {left} and `x` is toward
88
you. This is not oriented according to the right hand rule.
89
90
EXAMPLES: Spheres along the twisted cubic.
91
92
::
93
94
sage: t = Tachyon(xres=512,yres=512, camera_center=(3,0.3,0))
95
sage: t.light((4,3,2), 0.2, (1,1,1))
96
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0))
97
sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0))
98
sage: t.texture('t2', ambient=0.2,diffuse=0.7, specular=0.5, opacity=0.7, color=(0,0,1.0))
99
sage: k=0
100
sage: for i in srange(-1,1,0.05):
101
....: k += 1
102
....: t.sphere((i,i^2-0.5,i^3), 0.1, 't%s'%(k%3))
103
sage: t.show()
104
105
Another twisted cubic, but with a white background, got by putting
106
infinite planes around the scene.
107
108
::
109
110
sage: t = Tachyon(xres=512,yres=512, camera_center=(3,0.3,0), raydepth=8)
111
sage: t.light((4,3,2), 0.2, (1,1,1))
112
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0))
113
sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0))
114
sage: t.texture('t2', ambient=0.2,diffuse=0.7, specular=0.5, opacity=0.7, color=(0,0,1.0))
115
sage: t.texture('white', color=(1,1,1))
116
sage: t.plane((0,0,-1), (0,0,1), 'white')
117
sage: t.plane((0,-20,0), (0,1,0), 'white')
118
sage: t.plane((-20,0,0), (1,0,0), 'white')
119
120
::
121
122
sage: k=0
123
sage: for i in srange(-1,1,0.05):
124
....: k += 1
125
....: t.sphere((i,i^2 - 0.5,i^3), 0.1, 't%s'%(k%3))
126
....: t.cylinder((0,0,0), (0,0,1), 0.05,'t1')
127
sage: t.show()
128
129
Many random spheres::
130
131
sage: t = Tachyon(xres=512,yres=512, camera_center=(2,0.5,0.5), look_at=(0.5,0.5,0.5), raydepth=4)
132
sage: t.light((4,3,2), 0.2, (1,1,1))
133
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1.0,0,0))
134
sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.3, opacity=1.0, color=(0,1.0,0))
135
sage: t.texture('t2', ambient=0.2, diffuse=0.7, specular=0.5, opacity=0.7, color=(0,0,1.0))
136
sage: k=0
137
sage: for i in range(100):
138
....: k += 1
139
....: t.sphere((random(),random(), random()), random()/10, 't%s'%(k%3))
140
sage: t.show()
141
142
Points on an elliptic curve, their height indicated by their height
143
above the axis::
144
145
sage: t = Tachyon(camera_center=(5,2,2), look_at=(0,1,0))
146
sage: t.light((10,3,2), 0.2, (1,1,1))
147
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0))
148
sage: t.texture('t1', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0))
149
sage: t.texture('t2', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,0,1))
150
sage: E = EllipticCurve('37a')
151
sage: P = E([0,0])
152
sage: Q = P
153
sage: n = 100
154
sage: for i in range(n): # increase 20 for a better plot
155
....: Q = Q + P
156
....: t.sphere((Q[1], Q[0], ZZ(i)/n), 0.1, 't%s'%(i%3))
157
sage: t.show()
158
159
A beautiful picture of rational points on a rank 1 elliptic curve.
160
161
::
162
163
sage: t = Tachyon(xres=1000, yres=800, camera_center=(2,7,4), look_at=(2,0,0), raydepth=4)
164
sage: t.light((10,3,2), 1, (1,1,1))
165
sage: t.light((10,-3,2), 1, (1,1,1))
166
sage: t.texture('black', color=(0,0,0))
167
sage: t.texture('red', color=(1,0,0))
168
sage: t.texture('grey', color=(.9,.9,.9))
169
sage: t.plane((0,0,0),(0,0,1),'grey')
170
sage: t.cylinder((0,0,0),(1,0,0),.01,'black')
171
sage: t.cylinder((0,0,0),(0,1,0),.01,'black')
172
sage: E = EllipticCurve('37a')
173
sage: P = E([0,0])
174
sage: Q = P
175
sage: n = 100
176
sage: for i in range(n):
177
....: Q = Q + P
178
....: c = i/n + .1
179
....: t.texture('r%s'%i,color=(float(i/n),0,0))
180
....: t.sphere((Q[0], -Q[1], .01), .04, 'r%s'%i)
181
sage: t.show() # long time, e.g., 10-20 seconds
182
183
A beautiful spiral.
184
185
::
186
187
sage: t = Tachyon(xres=800,yres=800, camera_center=(2,5,2), look_at=(2.5,0,0))
188
sage: t.light((0,0,100), 1, (1,1,1))
189
sage: t.texture('r', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0))
190
sage: for i in srange(0,50,0.1):
191
....: t.sphere((i/10,sin(i),cos(i)), 0.05, 'r')
192
sage: t.texture('white', color=(1,1,1), opacity=1, specular=1, diffuse=1)
193
sage: t.plane((0,0,-100), (0,0,-100), 'white')
194
sage: t.show()
195
"""
196
def __init__(self,
197
xres=350, yres=350,
198
zoom = 1.0,
199
antialiasing = False,
200
aspectratio = 1.0,
201
raydepth = 8,
202
camera_center = (-3, 0, 0),
203
updir = (0, 0, 1),
204
look_at = (0,0,0),
205
viewdir = None,
206
projection = 'PERSPECTIVE'):
207
r"""
208
Creates an instance of the Tachyon class.
209
210
EXAMPLES::
211
212
sage: t = Tachyon()
213
sage: t._xres
214
350
215
"""
216
self._xres = xres
217
self._yres = yres
218
self._zoom = zoom
219
self._aspectratio = aspectratio
220
self._antialiasing = antialiasing
221
self._raydepth = raydepth
222
self._camera_center = camera_center
223
self._updir = updir
224
self._projection = projection
225
self._objects = []
226
if viewdir is None:
227
self._viewdir = [look_at[i] - camera_center[i] for i in range(3)]
228
else:
229
self._viewdir = viewdir
230
231
232
233
def __repr__(self):
234
r"""
235
Returns the string representation of the Tachyon object,
236
which is just the scene string input to tachyon.
237
238
EXAMPLES::
239
240
sage: q = Tachyon()
241
sage: q.light((1,1,1), 1,(1,1,1))
242
sage: q.texture('s')
243
sage: q.sphere((0,0,0),1,'s')
244
sage: q.__repr__()[-20:]
245
' \n end_scene'
246
"""
247
return self.str()
248
249
def save_image(self, filename=None, *args, **kwds):
250
r"""
251
Save an image representation of self. The image type is
252
determined by the extension of the filename. For example,
253
this could be ``.png``, ``.jpg``, ``.gif``, ``.pdf``,
254
``.svg``. Currently this is implemented by calling the
255
:meth:`save` method of self, passing along all arguments and
256
keywords.
257
258
.. Note::
259
260
Not all image types are necessarily implemented for all
261
graphics types. See :meth:`save` for more details.
262
263
EXAMPLES::
264
265
sage: q = Tachyon()
266
sage: q.light((1,1,11), 1,(1,1,1))
267
sage: q.texture('s')
268
sage: q.sphere((0,-1,1),1,'s')
269
sage: tempname = tmp_filename()
270
sage: q.save_image(tempname)
271
272
TESTS:
273
274
:meth:`save_image` is used for generating animations::
275
276
sage: def tw_cubic(t):
277
....: q = Tachyon()
278
....: q.light((1,1,11), 1,(1,1,1))
279
....: q.texture('s')
280
....: for i in srange(-1,t,0.05):
281
....: q.sphere((i,i^2-0.5,i^3), 0.1, 's')
282
....: return q
283
284
sage: a = animate([tw_cubic(t) for t in srange(-1,1,.3)])
285
sage: a
286
Animation with 7 frames
287
sage: a.show() # optional -- ImageMagick
288
"""
289
self.save(filename, *args, **kwds)
290
291
def save(self, filename='sage.png', verbose=0, block=True, extra_opts=''):
292
r"""
293
INPUT:
294
295
296
- ``filename`` - (default: 'sage.png') output
297
filename; the extension of the filename determines the type.
298
Supported types include:
299
300
- ``tga`` - 24-bit (uncompressed)
301
302
- ``bmp`` - 24-bit Windows BMP (uncompressed)
303
304
- ``ppm`` - 24-bit PPM (uncompressed)
305
306
- ``rgb`` - 24-bit SGI RGB (uncompressed)
307
308
- ``png`` - 24-bit PNG (compressed, lossless)
309
310
- ``verbose`` - integer; (default: 0)
311
312
- ``0`` - silent
313
314
- ``1`` - some output
315
316
- ``2`` - very verbose output
317
318
- ``block`` - bool (default: True); if False, run the
319
rendering command in the background.
320
321
- ``extra_opts`` - passed directly to tachyon command
322
line. Use tachyon_rt.usage() to see some of the possibilities.
323
324
EXAMPLES::
325
326
sage: q = Tachyon()
327
sage: q.light((1,1,11), 1,(1,1,1))
328
sage: q.texture('s')
329
sage: q.sphere((0,0,0),1,'s')
330
sage: tempname = tmp_filename()
331
sage: q.save(tempname)
332
sage: os.system('rm ' + tempname)
333
0
334
"""
335
tachyon_rt(self.str(), filename, verbose, block, extra_opts)
336
337
def show(self, verbose=0, extra_opts=''):
338
r"""
339
Creates a PNG file of the scene.
340
341
EXAMPLES::
342
343
sage: q = Tachyon()
344
sage: q.light((-1,-1,10), 1,(1,1,1))
345
sage: q.texture('s')
346
sage: q.sphere((0,0,0),1,'s')
347
sage: q.show(verbose = False)
348
"""
349
import sage.plot.plot
350
if sage.doctest.DOCTEST_MODE:
351
filename = graphics_filename()
352
self.save(os.path.join(SAGE_TMP, 'test.png'), verbose=verbose, extra_opts=extra_opts)
353
return
354
if sage.plot.plot.EMBEDDED_MODE:
355
filename = graphics_filename()
356
self.save(filename, verbose=verbose, extra_opts=extra_opts)
357
return
358
filename = tmp_filename(ext='.png')
359
self.save(filename, verbose=verbose, extra_opts=extra_opts)
360
os.system('%s %s 2>/dev/null 1>/dev/null &'%(sage.misc.viewer.png_viewer(), filename))
361
362
def _res(self):
363
r"""
364
An internal function that writes the tachyon string for the
365
resolution (x and y size of the image).
366
367
EXAMPLES::
368
369
sage: t = Tachyon(xres = 300, yres = 700)
370
sage: t._res()
371
'\nresolution 300 700\n'
372
"""
373
return '\nresolution %s %s\n'%(self._xres, self._yres)
374
375
def _camera(self):
376
r"""
377
An internal function that writes the tachyon string for the
378
camera and other rendering information (ray depth, antialiasing).
379
380
EXAMPLES::
381
382
sage: t = Tachyon(raydepth = 16, zoom = 2, antialiasing = True)
383
sage: t._camera().split()[3:10]
384
['aspectratio', '1.0', 'antialiasing', '1', 'raydepth', '16', 'center']
385
"""
386
return r"""
387
camera
388
zoom %s
389
aspectratio %s
390
antialiasing %s
391
raydepth %s
392
center %s
393
viewdir %s
394
updir %s
395
end_camera
396
"""%(float(self._zoom), float(self._aspectratio),
397
int(self._antialiasing),
398
int(self._raydepth),
399
tostr(self._camera_center),
400
tostr(self._viewdir),
401
tostr(self._updir))
402
403
def str(self):
404
r"""
405
Returns the complete tachyon scene file as a string.
406
407
EXAMPLES::
408
409
sage: t = Tachyon(xres=500,yres=500, camera_center=(2,0,0))
410
sage: t.light((4,3,2), 0.2, (1,1,1))
411
sage: t.texture('t2', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(1,0,0))
412
sage: t.texture('t3', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,1,0))
413
sage: t.texture('t4', ambient=0.1, diffuse=0.9, specular=0.5, opacity=1.0, color=(0,0,1))
414
sage: t.sphere((0,0.5,0), 0.2, 't2')
415
sage: t.sphere((0.5,0,0), 0.2, 't3')
416
sage: t.sphere((0,0,0.5), 0.2, 't4')
417
sage: t.str().find('PLASTIC')
418
567
419
"""
420
return r"""
421
begin_scene
422
%s
423
%s
424
%s
425
end_scene"""%(
426
self._res(),
427
self._camera(),
428
'\n'.join([x.str() for x in self._objects])
429
)
430
431
def light(self, center, radius, color):
432
r"""
433
Creates a light source of the given center, radius, and color.
434
435
EXAMPLES::
436
437
sage: q = Tachyon()
438
sage: q.light((1,1,1),1.0,(.2,0,.8))
439
sage: q.str().split('\n')[17]
440
' light center 1.0 1.0 1.0 '
441
"""
442
self._objects.append(Light(center, radius, color))
443
444
def texfunc(self, type=0, center=(0,0,0), rotate=(0,0,0), scale=(1,1,1)):
445
r"""
446
INPUT:
447
448
- ``type`` - (default: 0)
449
450
0. No special texture, plain shading
451
1. 3D checkerboard function, like a rubik's cube
452
2. Grit Texture, randomized surface color
453
3. 3D marble texture, uses object's base color
454
4. 3D wood texture, light and dark brown, not very good yet
455
5. 3D gradient noise function (can't remember what it looks
456
like)
457
6. Don't remember
458
7. Cylindrical Image Map, requires ppm filename (don't know
459
how to specify name in sage?!)
460
8. Spherical Image Map, requires ppm filename (don't know
461
how to specify name in sage?!)
462
9. Planar Image Map, requires ppm filename (don't know how
463
to specify name in sage?!)
464
465
- ``center`` - (default: (0,0,0))
466
- ``rotate`` - (default: (0,0,0))
467
- ``scale`` - (default: (1,1,1))
468
469
470
EXAMPLES: We draw an infinite checkboard::
471
472
sage: t = Tachyon(camera_center=(2,7,4), look_at=(2,0,0))
473
sage: t.texture('black', color=(0,0,0), texfunc=1)
474
sage: t.plane((0,0,0),(0,0,1),'black')
475
sage: t.show()
476
"""
477
type = int(type)
478
if type < 0 or type > 9:
479
raise ValueError, "type must be an integer between 0 and 9"
480
return Texfunc(type,center,rotate,scale).str()
481
482
def texture(self, name, ambient=0.2, diffuse=0.8,
483
specular=0.0, opacity=1.0,
484
color=(1.0,0.0, 0.5), texfunc=0, phong=0, phongsize=.5, phongtype="PLASTIC"):
485
r"""
486
INPUT:
487
488
489
- ``name`` - string; the name of the texture (to be
490
used later)
491
492
- ``ambient`` - (default: 0.2)
493
494
- ``diffuse`` - (default: 0.8)
495
496
- ``specular`` - (default: 0.0)
497
498
- ``opacity`` - (default: 1.0)
499
500
- ``color`` - (default: (1.0,0.0,0.5))
501
502
- ``texfunc`` - (default: 0); a texture function; this
503
is either the output of self.texfunc, or a number between 0 and 9,
504
inclusive. See the docs for self.texfunc.
505
506
- ``phong`` - (default: 0)
507
508
- ``phongsize`` - (default: 0.5)
509
510
- ``phongtype`` - (default: "PLASTIC")
511
512
EXAMPLES:
513
514
We draw a scene with 4 spheres that illustrates various uses of
515
the texture command::
516
517
sage: t = Tachyon(camera_center=(2,5,4), look_at=(2,0,0), raydepth=6)
518
sage: t.light((10,3,4), 1, (1,1,1))
519
sage: t.texture('mirror', ambient=0.05, diffuse=0.05, specular=.9, opacity=0.9, color=(.8,.8,.8))
520
sage: t.texture('grey', color=(.8,.8,.8), texfunc=3)
521
sage: t.plane((0,0,0),(0,0,1),'grey')
522
sage: t.sphere((4,-1,1), 1, 'mirror')
523
sage: t.sphere((0,-1,1), 1, 'mirror')
524
sage: t.sphere((2,-1,1), 0.5, 'mirror')
525
sage: t.sphere((2,1,1), 0.5, 'mirror')
526
sage: show(t) # known bug (:trac:`7232`)
527
"""
528
if texfunc and not isinstance(texfunc, Texfunc):
529
texfunc = self.texfunc(int(texfunc))
530
self._objects.append(Texture(name, ambient, diffuse,
531
specular, opacity, color, texfunc,
532
phong,phongsize,phongtype))
533
534
def texture_recolor(self, name, colors):
535
r"""
536
Recolors default textures.
537
538
EXAMPLES::
539
540
sage: t = Tachyon()
541
sage: t.texture('s')
542
sage: q = t.texture_recolor('s',[(0,0,1)])
543
sage: t._objects[1]._color
544
(0, 0, 1)
545
"""
546
base_tex = None
547
names = []
548
ident = "SAGETEX%d"%len(self._objects) #don't collide with other texture names
549
550
for o in self._objects:
551
if isinstance(o, Texture) and o._name == name:
552
base_tex = o
553
break
554
if base_tex is None:
555
base_tex = Texture(name)
556
557
for i in range(len(colors)):
558
n = "%s_%d"%(ident,i)
559
self._objects.append(base_tex.recolor(n, colors[i]))
560
names.append(n)
561
562
return names
563
564
def sphere(self, center, radius, texture):
565
r"""
566
Creates the scene information for a sphere with the given
567
center, radius, and texture.
568
569
EXAMPLES::
570
571
sage: t = Tachyon()
572
sage: t.texture('sphere_texture')
573
sage: t.sphere((1,2,3), .1, 'sphere_texture')
574
sage: t._objects[1].str()
575
'\n sphere center 1.0 2.0 3.0 rad 0.1 sphere_texture\n '
576
"""
577
self._objects.append(Sphere(center, radius, texture))
578
579
def ring(self, center, normal, inner, outer, texture):
580
r"""
581
Creates the scene information for a ring with the given parameters.
582
583
EXAMPLES::
584
585
sage: t = Tachyon()
586
sage: t.ring([0,0,0], [0,0,1], 1.0, 2.0, 's')
587
sage: t._objects[0]._center
588
[0, 0, 0]
589
"""
590
self._objects.append(Ring(center, normal, inner, outer, texture))
591
592
def cylinder(self, center, axis, radius, texture):
593
r"""
594
Creates the scene information for a infinite cylinder with the
595
given center, axis direction, radius, and texture.
596
597
EXAMPLES::
598
599
sage: t = Tachyon()
600
sage: t.texture('c')
601
sage: t.cylinder((0,0,0),(-1,-1,-1),.1,'c')
602
"""
603
self._objects.append(Cylinder(center, axis, radius, texture))
604
605
def plane(self, center, normal, texture):
606
r"""
607
Creates an infinite plane with the given center and normal.
608
609
EXAMPLES::
610
611
sage: t = Tachyon()
612
sage: t.plane((0,0,0),(1,1,1),'s')
613
sage: t.str()[338:380]
614
'plane center 0.0 0.0 0.0 normal 1.0 1.0'
615
"""
616
self._objects.append(Plane(center, normal, texture))
617
618
def axis_aligned_box(self, min_p, max_p, texture):
619
r"""
620
Creates an axis-aligned box with minimal point ``min_p`` and
621
maximum point ``max_p``.
622
623
EXAMPLES::
624
625
sage: t = Tachyon()
626
sage: t.axis_aligned_box((0,0,0),(2,2,2),'s')
627
"""
628
self._objects.append(Axis_aligned_box(min_p, max_p, texture))
629
630
def fcylinder(self, base, apex, radius, texture):
631
r"""
632
Finite cylinders are almost the same as infinite ones, but the
633
center and length of the axis determine the extents of the
634
cylinder. The finite cylinder is also really a shell, it
635
doesn't have any caps. If you need to close off the ends of
636
the cylinder, use two ring objects, with the inner radius set
637
to 0.0 and the normal set to be the axis of the cylinder.
638
Finite cylinders are built this way to enhance speed.
639
640
EXAMPLES::
641
642
sage: t = Tachyon()
643
sage: t.fcylinder((1,1,1),(1,2,3),.01,'s')
644
sage: len(t.str())
645
423
646
"""
647
self._objects.append(FCylinder(base, apex, radius, texture))
648
649
def triangle(self, vertex_1, vertex_2, vertex_3, texture):
650
r"""
651
Creates a triangle with the given vertices and texture.
652
653
EXAMPLES::
654
655
sage: t = Tachyon()
656
sage: t.texture('s')
657
sage: t.triangle([1,2,3],[4,5,6],[7,8,10],'s')
658
sage: t._objects[1]
659
[1, 2, 3] [4, 5, 6] [7, 8, 10] s
660
661
"""
662
self._objects.append(TachyonTriangle(vertex_1,vertex_2,vertex_3,texture))
663
664
def smooth_triangle(self, vertex_1, vertex_2, vertex_3, normal_1, normal_2, normal_3, texture):
665
r"""
666
Creates a triangle along with a normal vector for smoothing.
667
668
EXAMPLES::
669
670
sage: t = Tachyon()
671
sage: t.light((1,1,1),.1,(1,1,1))
672
sage: t.texture('s')
673
sage: t.smooth_triangle([0,0,0],[0,0,1],[0,1,0],[0,1,1],[-1,1,2],[3,0,0],'s')
674
sage: t._objects[2]
675
[0, 0, 0] [0, 0, 1] [0, 1, 0] s [0, 1, 1] [-1, 1, 2] [3, 0, 0]
676
"""
677
self._objects.append(TachyonSmoothTriangle(vertex_1, vertex_2, vertex_3, normal_1, normal_2, normal_3, texture))
678
679
def fractal_landscape(self, res, scale, center, texture):
680
r"""
681
Axis-aligned fractal landscape. Not very useful at the moment.
682
683
EXAMPLES::
684
685
sage: t = Tachyon()
686
sage: t.texture('s')
687
sage: t.fractal_landscape([30,30],[80,80],[0,0,0],'s')
688
sage: len(t._objects)
689
2
690
"""
691
self._objects.append(FractalLandscape(res, scale, center, texture))
692
693
def plot(self,f,(xmin,xmax),(ymin,ymax),texture,grad_f=None,
694
max_bend=.7,max_depth=5,initial_depth=3, num_colors=None):
695
r"""
696
INPUT:
697
698
699
- ``f`` - Function of two variables, which returns a
700
float (or coercible to a float) (xmin,xmax)
701
702
- ``(ymin,ymax)`` - defines the rectangle to plot over
703
texture: Name of texture to be used Optional arguments:
704
705
- ``grad_f`` - gradient function. If specified,
706
smooth triangles will be used.
707
708
- ``max_bend`` - Cosine of the threshold angle
709
between triangles used to determine whether or not to recurse after
710
the minimum depth
711
712
- ``max_depth`` - maximum recursion depth. Maximum
713
triangles plotted = `2^{2*max_depth}`
714
715
- ``initial_depth`` - minimum recursion depth. No
716
error-tolerance checking is performed below this depth. Minimum
717
triangles plotted: `2^{2*min_depth}`
718
719
- ``num_colors`` - Number of rainbow bands to color
720
the plot with. Texture supplied will be cloned (with different
721
colors) using the texture_recolor method of the Tachyon object.
722
723
724
Plots a function by constructing a mesh with nonstandard sampling
725
density without gaps. At very high resolutions (depths 10) it
726
becomes very slow. Cython may help. Complexity is approx.
727
`O(2^{2*maxdepth})`. This algorithm has been optimized for
728
speed, not memory - values from f(x,y) are recycled rather than
729
calling the function multiple times. At high recursion depth, this
730
may cause problems for some machines.
731
732
Flat Triangles::
733
734
sage: t = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4)
735
sage: t.light((4.4,-4.4,4.4), 0.2, (1,1,1))
736
sage: def f(x,y): return float(sin(x*y))
737
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0))
738
sage: t.plot(f,(-4,4),(-4,4),"t0",max_depth=5,initial_depth=3, num_colors=60) # increase min_depth for better picture
739
sage: t.show()
740
741
Plotting with Smooth Triangles (requires explicit gradient
742
function)::
743
744
sage: t = Tachyon(xres=512,yres=512, camera_center=(4,-4,3),viewdir=(-4,4,-3), raydepth=4)
745
sage: t.light((4.4,-4.4,4.4), 0.2, (1,1,1))
746
sage: def f(x,y): return float(sin(x*y))
747
sage: def g(x,y): return ( float(y*cos(x*y)), float(x*cos(x*y)), 1 )
748
sage: t.texture('t0', ambient=0.1, diffuse=0.9, specular=0.1, opacity=1.0, color=(1.0,0,0))
749
sage: t.plot(f,(-4,4),(-4,4),"t0",max_depth=5,initial_depth=3, grad_f = g) # increase min_depth for better picture
750
sage: t.show()
751
752
Preconditions: f is a scalar function of two variables, grad_f is
753
None or a triple-valued function of two variables, min_x !=
754
max_x, min_y != max_y
755
756
::
757
758
sage: f = lambda x,y: x*y
759
sage: t = Tachyon()
760
sage: t.plot(f,(2.,2.),(-2.,2.),'')
761
Traceback (most recent call last):
762
...
763
ValueError: Plot rectangle is really a line. Make sure min_x != max_x and min_y != max_y.
764
"""
765
factory = TachyonTriangleFactory(self,texture)
766
plot = TrianglePlot(factory, f, (xmin, xmax), (ymin, ymax), g = grad_f,
767
min_depth=initial_depth, max_depth=max_depth, max_bend=max_bend, num_colors = num_colors)
768
self._objects.append(plot)
769
770
771
def parametric_plot(self, f, t_0, t_f, tex, r=.1, cylinders = True, min_depth=4, max_depth=8, e_rel = .01, e_abs = .01):
772
r"""
773
Plots a space curve as a series of spheres and finite cylinders.
774
Example (twisted cubic) ::
775
776
sage: f = lambda t: (t,t^2,t^3)
777
sage: t = Tachyon(camera_center=(5,0,4))
778
sage: t.texture('t')
779
sage: t.light((-20,-20,40), 0.2, (1,1,1))
780
sage: t.parametric_plot(f,-5,5,'t',min_depth=6)
781
"""
782
783
self._objects.append(ParametricPlot(f, t_0, t_f, tex, r=r, cylinders=cylinders,min_depth=min_depth,max_depth=max_depth,e_rel=.01,e_abs=.01))
784
785
#Doesn't seem to be used:
786
# def collect(self, objects):
787
# """
788
# Add a set of objects to the scene from a collection.
789
#
790
# EXAMPLES::
791
#
792
# sage: t = Tachyon()
793
# sage: t.texture('s')
794
# sage: for i in range(10): t.sphere((0,0,i),i,'s')
795
# """
796
# self._objects.extend(objects)
797
798
class Light:
799
r"""
800
Represents lighting objects.
801
802
EXAMPLES::
803
804
sage: from sage.plot.plot3d.tachyon import Light
805
sage: q = Light((1,1,1),1,(1,1,1))
806
sage: q._center
807
(1, 1, 1)
808
"""
809
def __init__(self, center, radius, color):
810
r"""
811
Stores the center, radius and color.
812
813
EXAMPLES::
814
815
sage: from sage.plot.plot3d.tachyon import Light
816
sage: q = Light((1,1,1),1,(1,1,1))
817
sage: q._color
818
(1, 1, 1)
819
"""
820
self._center = center
821
self._radius = radius
822
self._color = color
823
824
def str(self):
825
r"""
826
Returns the tachyon string defining the light source.
827
828
EXAMPLES::
829
830
sage: from sage.plot.plot3d.tachyon import Light
831
sage: q = Light((1,1,1),1,(1,1,1))
832
sage: q._radius
833
1
834
"""
835
return r"""
836
light center %s
837
rad %s
838
color %s
839
"""%(tostr(self._center), float(self._radius),
840
tostr(self._color))
841
842
class Texfunc:
843
def __init__(self, type=0,center=(0,0,0), rotate=(0,0,0), scale=(1,1,1)):
844
r"""
845
Creates a texture function.
846
847
EXAMPLES::
848
849
sage: from sage.plot.plot3d.tachyon import Texfunc
850
sage: t = Texfunc()
851
sage: t._type
852
0
853
"""
854
self._type = type
855
self._center = center
856
self._rotate = rotate
857
self._scale = scale
858
859
def str(self):
860
r"""
861
Returns the scene string for this texture function.
862
863
EXAMPLES::
864
865
sage: from sage.plot.plot3d.tachyon import Texfunc
866
sage: t = Texfunc()
867
sage: t.str()
868
'0 center 0.0 0.0 0.0 rotate 0.0 0.0 0.0 scale 1.0 1.0 1.0 '
869
"""
870
if type == 0:
871
return "0"
872
return r"""%d center %s rotate %s scale %s"""%(self._type,
873
tostr(self._center),
874
tostr(self._rotate),
875
tostr(self._scale))
876
877
class Texture:
878
def __init__(self, name, ambient=0.2, diffuse=0.8,
879
specular=0.0, opacity=1.0,
880
color=(1.0,0.0, 0.5), texfunc=0, phong=0, phongsize=0, phongtype="PLASTIC"):
881
r"""
882
Stores texture information.
883
884
EXAMPLES::
885
886
sage: from sage.plot.plot3d.tachyon import Texture
887
sage: t = Texture('w')
888
sage: t.str().split()[2:6]
889
['ambient', '0.2', 'diffuse', '0.8']
890
"""
891
self._name = name
892
self._ambient = ambient
893
self._diffuse = diffuse
894
self._specular = specular
895
self._opacity = opacity
896
self._color = color
897
self._texfunc = texfunc
898
self._phong = phong
899
self._phongsize = phongsize
900
self._phongtype = phongtype
901
902
def recolor(self, name, color):
903
r"""
904
Returns a texture with the new given color.
905
906
EXAMPLES::
907
908
sage: from sage.plot.plot3d.tachyon import Texture
909
sage: t2 = Texture('w')
910
sage: t2w = t2.recolor('w2', (.1,.2,.3))
911
sage: t2ws = t2w.str()
912
sage: color_index = t2ws.find('color')
913
sage: t2ws[color_index:color_index+20]
914
'color 0.1 0.2 0.3 '
915
"""
916
return Texture(name, self._ambient, self._diffuse, self._specular, self._opacity,
917
color, self._texfunc, self._phong, self._phongsize, self._phongtype)
918
919
def str(self):
920
r"""
921
Returns the scene string for this texture.
922
923
EXAMPLES::
924
925
sage: from sage.plot.plot3d.tachyon import Texture
926
sage: t = Texture('w')
927
sage: t.str().split()[2:6]
928
['ambient', '0.2', 'diffuse', '0.8']
929
930
"""
931
return r"""
932
texdef %s ambient %s diffuse %s specular %s opacity %s
933
phong %s %s phong_size %s
934
color %s texfunc %s
935
"""%(self._name,
936
self._ambient,
937
self._diffuse,
938
self._specular,
939
self._opacity,
940
self._phongtype,
941
self._phong,
942
self._phongsize,
943
tostr(self._color),
944
self._texfunc)
945
946
class Sphere:
947
r"""
948
A class for creating spheres in tachyon.
949
"""
950
def __init__(self, center, radius, texture):
951
r"""
952
Stores the center, radius, and texture information in a class.
953
954
EXAMPLES::
955
956
sage: t = Tachyon()
957
sage: from sage.plot.plot3d.tachyon import Sphere
958
sage: t.texture('r', color=(.8,0,0), ambient = .1)
959
sage: s = Sphere((1,1,1),1,'r')
960
sage: s._radius
961
1
962
"""
963
self._center = center
964
self._radius = radius
965
self._texture = texture
966
967
def str(self):
968
r"""
969
Returns the scene string for the sphere.
970
971
EXAMPLES::
972
973
sage: t = Tachyon()
974
sage: from sage.plot.plot3d.tachyon import Sphere
975
sage: t.texture('r', color=(.8,0,0), ambient = .1)
976
sage: s = Sphere((1,1,1),1,'r')
977
sage: s.str()
978
'\n sphere center 1.0 1.0 1.0 rad 1.0 r\n '
979
"""
980
return r"""
981
sphere center %s rad %s %s
982
"""%(tostr(self._center), float(self._radius), self._texture)
983
984
class Ring:
985
r"""
986
An annulus of zero thickness.
987
"""
988
def __init__(self, center, normal, inner, outer, texture):
989
r"""
990
Creates a ring with the given center, normal, inner radius,
991
outer radius, and texture.
992
993
EXAMPLES::
994
995
sage: from sage.plot.plot3d.tachyon import Ring
996
sage: r = Ring((1,1,1), (1,1,0), 1.0, 2.0, 's')
997
sage: r._center
998
(1, 1, 1)
999
"""
1000
self._center = center
1001
self._normal = normal
1002
self._inner = inner
1003
self._outer = outer
1004
self._texture = texture
1005
1006
def str(self):
1007
r"""
1008
Returns the scene string of the ring.
1009
1010
EXAMPLES::
1011
1012
sage: from sage.plot.plot3d.tachyon import Ring
1013
sage: r = Ring((0,0,0), (1,1,0), 1.0, 2.0, 's')
1014
sage: r.str()
1015
'\n ring center 0.0 0.0 0.0 normal 1.0 1.0 0.0 inner 1.0 outer 2.0 s\n '
1016
"""
1017
return r"""
1018
ring center %s normal %s inner %s outer %s %s
1019
"""%(tostr(self._center), tostr(self._normal), float(self._inner), float(self._outer), self._texture)
1020
1021
class FractalLandscape:
1022
r"""
1023
Axis-aligned fractal landscape.
1024
Does not seem very useful at the moment, but perhaps will be improved in the future.
1025
"""
1026
def __init__(self, res, scale, center, texture):
1027
r"""
1028
Creates a fractal landscape in tachyon.
1029
1030
EXAMPLES::
1031
1032
sage: from sage.plot.plot3d.tachyon import FractalLandscape
1033
sage: fl = FractalLandscape([20,20],[30,30],[1,2,3],'s')
1034
sage: fl._center
1035
[1, 2, 3]
1036
"""
1037
self._res = res
1038
self._scale = scale
1039
self._center = center
1040
self._texture = texture
1041
1042
def str(self):
1043
r"""
1044
Returns the scene string of the fractal landscape.
1045
1046
EXAMPLES::
1047
1048
sage: from sage.plot.plot3d.tachyon import FractalLandscape
1049
sage: fl = FractalLandscape([20,20],[30,30],[1,2,3],'s')
1050
sage: fl.str()
1051
'\n scape res 20 20 scale 30 30 center 1.0 2.0 3.0 s\n '
1052
"""
1053
return r"""
1054
scape res %s scale %s center %s %s
1055
"""%(tostr(self._res, 2, int), tostr(self._scale, 2, int), tostr(self._center), self._texture)
1056
1057
class Cylinder:
1058
r"""
1059
An infinite cylinder.
1060
"""
1061
def __init__(self, center, axis, radius, texture):
1062
r"""
1063
Creates a cylinder with the given parameters.
1064
1065
EXAMPLES::
1066
1067
sage: t = Tachyon()
1068
sage: from sage.plot.plot3d.tachyon import Cylinder
1069
sage: c = Cylinder((0,0,0),(1,1,1),.1,'s')
1070
sage: c.str()
1071
'\n cylinder center 0.0 0.0 0.0 axis 1.0 1.0 1.0 rad 0.1 s\n '
1072
"""
1073
self._center = center
1074
self._axis = axis
1075
self._radius = radius
1076
self._texture = texture
1077
1078
def str(self):
1079
r"""
1080
Returns the scene string of the cylinder.
1081
1082
EXAMPLES::
1083
1084
sage: t = Tachyon()
1085
sage: from sage.plot.plot3d.tachyon import Cylinder
1086
sage: c = Cylinder((0,0,0),(1,1,1),.1,'s')
1087
sage: c.str()
1088
'\n cylinder center 0.0 0.0 0.0 axis 1.0 1.0 1.0 rad 0.1 s\n '
1089
"""
1090
return r"""
1091
cylinder center %s axis %s rad %s %s
1092
"""%(tostr(self._center), tostr(self._axis), float(self._radius), self._texture)
1093
1094
class Plane:
1095
r"""
1096
An infinite plane.
1097
"""
1098
def __init__(self, center, normal, texture):
1099
r"""
1100
Creates the plane object.
1101
1102
EXAMPLES::
1103
1104
sage: from sage.plot.plot3d.tachyon import Plane
1105
sage: p = Plane((1,2,3),(1,2,4),'s')
1106
sage: p.str()
1107
'\n plane center 1.0 2.0 3.0 normal 1.0 2.0 4.0 s\n '
1108
"""
1109
self._center = center
1110
self._normal = normal
1111
self._texture = texture
1112
1113
def str(self):
1114
r"""
1115
Returns the scene string of the plane.
1116
1117
EXAMPLES::
1118
1119
sage: from sage.plot.plot3d.tachyon import Plane
1120
sage: p = Plane((1,2,3),(1,2,4),'s')
1121
sage: p.str()
1122
'\n plane center 1.0 2.0 3.0 normal 1.0 2.0 4.0 s\n '
1123
"""
1124
return r"""
1125
plane center %s normal %s %s
1126
"""%(tostr(self._center), tostr(self._normal), self._texture)
1127
1128
class FCylinder:
1129
r"""
1130
A finite cylinder.
1131
"""
1132
def __init__(self, base, apex, radius, texture):
1133
r"""
1134
Creates a finite cylinder object.
1135
1136
EXAMPLES::
1137
1138
sage: from sage.plot.plot3d.tachyon import FCylinder
1139
sage: fc = FCylinder((0,0,0),(1,1,1),.1,'s')
1140
sage: fc.str()
1141
'\n fcylinder base 0.0 0.0 0.0 apex 1.0 1.0 1.0 rad 0.1 s\n '
1142
"""
1143
self._center = base
1144
self._axis = apex
1145
self._radius = radius
1146
self._texture = texture
1147
1148
def str(self):
1149
r"""
1150
Returns the scene string of the finite cylinder.
1151
1152
EXAMPLES::
1153
1154
sage: from sage.plot.plot3d.tachyon import FCylinder
1155
sage: fc = FCylinder((0,0,0),(1,1,1),.1,'s')
1156
sage: fc.str()
1157
'\n fcylinder base 0.0 0.0 0.0 apex 1.0 1.0 1.0 rad 0.1 s\n '
1158
"""
1159
return r"""
1160
fcylinder base %s apex %s rad %s %s
1161
"""%(tostr(self._center), tostr(self._axis), float(self._radius), self._texture)
1162
1163
class Axis_aligned_box():
1164
r"""
1165
Box with axis-aligned edges with the given min and max coordinates.
1166
"""
1167
def __init__(self, min_p, max_p, texture):
1168
r"""
1169
Creates the axis-aligned box object.
1170
1171
EXAMPLES::
1172
1173
sage: from sage.plot.plot3d.tachyon import Axis_aligned_box
1174
sage: aab = Axis_aligned_box((0,0,0),(1,1,1),'s')
1175
sage: aab.str()
1176
'\n box min 0.0 0.0 0.0 max 1.0 1.0 1.0 s\n '
1177
"""
1178
self._min_p = min_p
1179
self._max_p = max_p
1180
self._texture = texture
1181
1182
def str(self):
1183
r"""
1184
Returns the scene string of the axis-aligned box.
1185
1186
EXAMPLES::
1187
1188
sage: from sage.plot.plot3d.tachyon import Axis_aligned_box
1189
sage: aab = Axis_aligned_box((0,0,0),(1,1,1),'s')
1190
sage: aab.str()
1191
'\n box min 0.0 0.0 0.0 max 1.0 1.0 1.0 s\n '
1192
"""
1193
return r"""
1194
box min %s max %s %s
1195
"""%(tostr(self._min_p), tostr(self._max_p), self._texture)
1196
1197
class TachyonTriangle(Triangle):
1198
r"""
1199
Basic triangle class.
1200
"""
1201
def str(self):
1202
r"""
1203
Returns the scene string for a triangle.
1204
1205
EXAMPLES::
1206
1207
sage: from sage.plot.plot3d.tachyon import TachyonTriangle
1208
sage: t = TachyonTriangle([-1,-1,-1],[0,0,0],[1,2,3])
1209
sage: t.str()
1210
'\n TRI V0 -1.0 -1.0 -1.0 V1 0.0 0.0 0.0 V2 1.0 2.0 3.0 \n 0\n '
1211
"""
1212
return r"""
1213
TRI V0 %s V1 %s V2 %s
1214
%s
1215
"""%(tostr(self._a), tostr(self._b),tostr(self._c), self._color)
1216
1217
class TachyonSmoothTriangle(SmoothTriangle):
1218
r"""
1219
A triangle along with a normal vector, which is used for smoothing.
1220
"""
1221
def str(self):
1222
r"""
1223
Returns the scene string for a smoothed triangle.
1224
1225
EXAMPLES::
1226
1227
sage: from sage.plot.plot3d.tachyon import TachyonSmoothTriangle
1228
sage: t = TachyonSmoothTriangle([-1,-1,-1],[0,0,0],[1,2,3],[1,0,0],[0,1,0],[0,0,1])
1229
sage: t.str()
1230
'\n STRI V0 ... 1.0 0.0 0.0 N1 0.0 1.0 0.0 N2 0.0 0.0 1.0 \n 0\n '
1231
"""
1232
return r"""
1233
STRI V0 %s V1 %s V2 %s
1234
N0 %s N1 %s N2 %s
1235
%s
1236
"""%(tostr(self._a), tostr(self._b), tostr(self._c),
1237
tostr(self._da), tostr(self._db), tostr(self._dc), self._color)
1238
1239
1240
1241
class TachyonTriangleFactory(TriangleFactory):
1242
r"""
1243
A class to produce triangles of various rendering types.
1244
"""
1245
def __init__(self, tach, tex):
1246
r"""
1247
Initializes with tachyon instance and texture.
1248
1249
EXAMPLES::
1250
1251
sage: from sage.plot.plot3d.tachyon import TachyonTriangleFactory
1252
sage: t = Tachyon()
1253
sage: t.texture('s')
1254
sage: ttf = TachyonTriangleFactory(t, 's')
1255
sage: ttf._texture
1256
's'
1257
"""
1258
self._tachyon = tach
1259
self._texture = tex
1260
1261
def triangle(self,a,b,c,color=None):
1262
r"""
1263
Creates a TachyonTriangle with vertices a, b, and c.
1264
1265
EXAMPLES::
1266
1267
sage: from sage.plot.plot3d.tachyon import TachyonTriangleFactory
1268
sage: t = Tachyon()
1269
sage: t.texture('s')
1270
sage: ttf = TachyonTriangleFactory(t, 's')
1271
sage: ttft = ttf.triangle([1,2,3],[3,2,1],[0,2,1])
1272
sage: ttft.str()
1273
'\n TRI V0 1.0 2.0 3.0 V1 3.0 2.0 1.0 V2 0.0 2.0 1.0 \n s\n '
1274
"""
1275
if color is None:
1276
return TachyonTriangle(a,b,c,self._texture)
1277
else:
1278
return TachyonTriangle(a,b,c,color)
1279
1280
def smooth_triangle(self,a,b,c,da,db,dc,color=None):
1281
r"""
1282
Creates a TachyonSmoothTriangle.
1283
1284
EXAMPLES::
1285
1286
sage: from sage.plot.plot3d.tachyon import TachyonTriangleFactory
1287
sage: t = Tachyon()
1288
sage: t.texture('s')
1289
sage: ttf = TachyonTriangleFactory(t, 's')
1290
sage: ttfst = ttf.smooth_triangle([0,0,0],[1,0,0],[0,0,1],[1,1,1],[1,2,3],[-1,-1,2])
1291
sage: ttfst.str()
1292
'\n STRI V0 0.0 0.0 0.0 ...'
1293
"""
1294
if color is None:
1295
return TachyonSmoothTriangle(a,b,c,da,db,dc,self._texture)
1296
else:
1297
return TachyonSmoothTriangle(a,b,c,da,db,dc,color)
1298
1299
def get_colors(self, list):
1300
r"""
1301
Returns a list of color labels.
1302
1303
EXAMPLES::
1304
1305
sage: from sage.plot.plot3d.tachyon import TachyonTriangleFactory
1306
sage: t = Tachyon()
1307
sage: t.texture('s')
1308
sage: ttf = TachyonTriangleFactory(t, 's')
1309
sage: ttf.get_colors([1])
1310
['SAGETEX1_0']
1311
"""
1312
return self._tachyon.texture_recolor(self._texture, list)
1313
1314
# following classes TachyonPlot and PlotBlock seems broken and not used anywhere, so commented out. Please write to the sage-devel google-group if you are the author of these classes to comment.
1315
1316
#class TachyonPlot:
1317
#Recursively plots a function of two variables by building squares of 4 triangles, checking at
1318
# every stage whether or not each square should be split into four more squares. This way,
1319
# more planar areas get fewer triangles, and areas with higher curvature get more triangles
1320
1321
# def str(self):
1322
# return "".join([o.str() for o in self._objects])
1323
1324
# def __init__(self, tachyon, f, (min_x, max_x), (min_y, max_y), tex, g = None,
1325
# min_depth=4, max_depth=8, e_rel = .01, e_abs = .01, num_colors = None):
1326
# self._tachyon = tachyon
1327
# self._f = f
1328
# self._g = g
1329
# self._tex = tex
1330
# self._min_depth = min_depth
1331
# self._max_depth = max_depth
1332
# self._e_rel = e_rel
1333
# self._e_abs = e_abs
1334
# self._objects = []
1335
# self._eps = min(max_x - min_x, max_y - min_y)/(2**max_depth)
1336
# if self._eps == 0:
1337
# raise ValueError, 'Plot rectangle is really a line. Make sure min_x != #max_x and min_y != max_y.'
1338
# self._num_colors = num_colors
1339
# if g is None:
1340
# def fcn(x,y):
1341
# return [self._f(x,y)]
1342
# else:
1343
# def fcn(x,y):
1344
# return [self._f(x,y), self._g(x,y)]
1345
1346
# self._fcn = fcn
1347
1348
1349
# # generate the necessary data to kick-start the recursion
1350
# mid_x = (min_x + max_x)/2
1351
# mid_y = (min_y + max_y)/2
1352
# sw_z = fcn(min_x,min_y)
1353
# nw_z = fcn(min_x,max_y)
1354
# se_z = fcn(max_x,min_y)
1355
# ne_z = fcn(max_x,max_y)
1356
# mid_z = fcn(mid_x,mid_y)
1357
1358
# self._min = min(sw_z[0], nw_z[0], se_z[0], ne_z[0], mid_z[0])
1359
# self._max = max(sw_z[0], nw_z[0], se_z[0], ne_z[0], mid_z[0])
1360
1361
# # jump in and start building blocks
1362
# outer = self.plot_block(min_x, mid_x, max_x, min_y, mid_y, max_y, sw_z, nw_z, se_z, ne_z, mid_z, 0)
1363
#
1364
# # build the boundary triangles
1365
# self.triangulate(outer.left, outer.left_c)
1366
# self.triangulate(outer.top, outer.top_c)
1367
# self.triangulate(outer.right, outer.right_c)
1368
# self.triangulate(outer.bottom, outer.bottom_c)
1369
1370
# zrange = self._max - self._min
1371
# if num_colors is not None and zrange != 0:
1372
# colors = tachyon.texture_recolor(tex, [hue(float(i/num_colors)) for i in range(num_colors)])
1373
1374
# for o in self._objects:
1375
# avg_z = (o._vertex_1[2] + o._vertex_2[2] + o._vertex_3[2])/3
1376
# o._texture = colors[int(num_colors * (avg_z - self._min) / zrange)]
1377
1378
# def plot_block(self, min_x, mid_x, max_x, min_y, mid_y, max_y, sw_z, nw_z, se_z, ne_z, mid_z, depth):
1379
1380
# if depth < self._max_depth:
1381
# # recursion is still an option -- step in one last level if we're within tolerance
1382
# # and just keep going if we're not.
1383
# # assumption: it's cheap to build triangles, so we might as well use all the data
1384
# # we calculate
1385
1386
# # big square boundary midpoints
1387
# mid_w_z = self._fcn(min_x, mid_y)
1388
# mid_n_z = self._fcn(mid_x, max_y)
1389
# mid_e_z = self._fcn(max_x, mid_y)
1390
# mid_s_z = self._fcn(mid_x, min_y)
1391
1392
# # midpoints locations of sub_squares
1393
# qtr1_x = (min_x + mid_x)/2
1394
# qtr1_y = (min_y + mid_y)/2
1395
# qtr3_x = (mid_x + max_x)/2
1396
# qtr3_y = (mid_y + max_y)/2
1397
1398
# # function evaluated at these midpoints
1399
# mid_sw_z = self._fcn(qtr1_x,qtr1_y)
1400
# mid_nw_z = self._fcn(qtr1_x,qtr3_y)
1401
# mid_se_z = self._fcn(qtr3_x,qtr1_y)
1402
# mid_ne_z = self._fcn(qtr3_x,qtr3_y)
1403
1404
# # linearization estimates of midpoints
1405
# est_sw_z = (mid_z[0] + sw_z[0])/2
1406
# est_nw_z = (mid_z[0] + nw_z[0])/2
1407
# est_se_z = (mid_z[0] + se_z[0])/2
1408
# est_ne_z = (mid_z[0] + ne_z[0])/2
1409
1410
# self.extrema([mid_w_z[0], mid_n_z[0], mid_e_z[0], mid_s_z[0], mid_sw_z[0], mid_se_z[0], mid_nw_z[0], mid_sw_z[0]])
1411
1412
# tol_check = [(est_sw_z, mid_sw_z[0]), (est_nw_z, mid_nw_z[0]), (est_se_z, mid_se_z[0]), (est_ne_z, mid_ne_z[0])]
1413
1414
# if depth < self._min_depth or not self.tol_list(tol_check):
1415
# next_depth = depth + 1
1416
# else:
1417
# #lie about the depth to halt recursion
1418
# next_depth = self._max_depth
1419
1420
# # recurse into the sub-squares
1421
# sw = self.plot_block(min_x, qtr1_x, mid_x, min_y, qtr1_y, mid_y, sw_z, mid_w_z, mid_s_z, mid_z, mid_sw_z, next_depth)
1422
# nw = self.plot_block(min_x, qtr1_x, mid_x, mid_y, qtr3_y, max_y, mid_w_z, nw_z, mid_z, mid_n_z, mid_nw_z, next_depth)
1423
# se = self.plot_block(mid_x, qtr3_x, max_x, min_y, qtr1_y, mid_y, mid_s_z, mid_z, se_z, mid_e_z, mid_se_z, next_depth)
1424
# ne = self.plot_block(mid_x, qtr3_x, max_x, mid_y, qtr3_y, max_y, mid_z, mid_n_z, mid_e_z, ne_z, mid_ne_z, next_depth)
1425
1426
# # join the sub-squares
1427
# self.interface(1, sw.right, sw.right_c, se.left, se.left_c)
1428
# self.interface(1, nw.right, nw.right_c, ne.left, ne.left_c)
1429
# self.interface(0, sw.top, sw.top_c, nw.bottom, nw.bottom_c)
1430
# self.interface(0, se.top, se.top_c, ne.bottom, ne.bottom_c)
1431
1432
# #get the boundary information about the subsquares
1433
# left = sw.left + nw.left[1:]
1434
# left_c = sw.left_c + nw.left_c
1435
# right = se.right + ne.right[1:]
1436
# right_c = se.right_c + ne.right_c
1437
# top = nw.top + ne.top[1:]
1438
# top_c = nw.top_c + ne.top_c
1439
# bottom = sw.bottom + se.bottom[1:]
1440
# bottom_c = sw.bottom_c + se.bottom_c
1441
1442
# else:
1443
# # just build the square we're in
1444
# if self._g is None:
1445
# sw = [(min_x,min_y,sw_z[0])]
1446
# nw = [(min_x,max_y,nw_z[0])]
1447
# se = [(max_x,min_y,se_z[0])]
1448
# ne = [(max_x,max_y,ne_z[0])]
1449
# c = [[(mid_x,mid_y,mid_z[0])]]
1450
# else:
1451
# sw = [(min_x,min_y,sw_z[0]),sw_z[1]]
1452
# nw = [(min_x,max_y,nw_z[0]),nw_z[1]]
1453
# se = [(max_x,min_y,se_z[0]),se_z[1]]
1454
# ne = [(max_x,max_y,ne_z[0]),ne_z[1]]
1455
# c = [[(mid_x,mid_y,mid_z[0]),mid_z[1]]]
1456
1457
1458
# left = [sw,nw]
1459
# left_c = c
1460
# top = [nw,ne]
1461
# top_c = c
1462
# right = [se,ne]
1463
# right_c = c
1464
# bottom = [sw,se]
1465
# bottom_c = c
1466
1467
# return PlotBlock(left, left_c, top, top_c, right, right_c, bottom, bottom_c)
1468
1469
# def tol(self, (est, val)):
1470
# # Check relative, then absolute tolerance. If both fail, return False
1471
# # This is a zero-safe error checker
1472
1473
# if abs(est - val) < self._e_rel*abs(val):
1474
# return True
1475
# if abs(est - val) < self._e_abs:
1476
# return True
1477
# return False
1478
1479
# def tol_list(self, l):
1480
# # Pass in a list of pairs of numbers, (est, val) to be passed to self.tol
1481
# # returns False if any pair does not fall within tolerance level
1482
1483
# for p in l:
1484
# if not self.tol(p):
1485
# return False
1486
# return True
1487
1488
# def interface(self, n, p, p_c, q, q_c):
1489
# # Takes a pair of lists of points, and compares the (n)th coordinate, and
1490
# # "zips" the lists together into one. The "centers", supplied in p_c and
1491
# # q_c are matched up such that the lists describe triangles whose sides
1492
# # are "perfectly" aligned. This algorithm assumes that p and q start and
1493
# # end at the same point, and are sorted smallest to largest.
1494
1495
# m = [p[0]] # a sorted union of p and q
1496
# mpc = [p_c[0]] # centers from p_c corresponding to m
1497
# mqc = [q_c[0]] # centers from q_c corresponding to m
1498
1499
# i = 1
1500
# j = 1
1501
1502
# while i < len(p_c) or j < len(q_c):
1503
# if abs(p[i][0][n] - q[j][0][n]) < self._eps:
1504
# m.append(p[i])
1505
# mpc.append(p_c[i])
1506
# mqc.append(q_c[j])
1507
# i += 1
1508
# j += 1
1509
# elif p[i][0][n] < q[j][0][n]:
1510
# m.append(p[i])
1511
# mpc.append(p_c[i])
1512
# mqc.append(mqc[-1])
1513
# i += 1
1514
# else:
1515
# m.append(q[j])
1516
# mpc.append(mpc[-1])
1517
# mqc.append(q_c[j])
1518
# j += 1
1519
1520
# m.append(p[-1])
1521
1522
# self.triangulate(m, mpc)
1523
# self.triangulate(m, mqc)
1524
1525
1526
# def triangulate(self, p, c):
1527
# # Pass in a list of edge points (p) and center points (c).
1528
# # Triangles will be rendered between consecutive edge points and the
1529
# # center point with the same index number as the earlier edge point.
1530
1531
# if self._g is None:
1532
# for i in range(0,len(p)-1):
1533
# self._objects.append(Triangle(p[i][0], p[i+1][0], c[i][0], self._tex))
1534
# else:
1535
# for i in range(0,len(p)-1):
1536
# self._objects.append(SmoothTriangle(p[i][0], p[i+1][0], c[i][0],p[i][1], p[i+1][1], c[i][1], self._tex))
1537
1538
1539
# def extrema(self, list):
1540
# if self._num_colors is not None:
1541
# self._min = min(list+[self._min])
1542
# self._max = max(list+[self._max])
1543
1544
1545
#class PlotBlock:
1546
# def __init__(self, left, left_c, top, top_c, right, right_c, bottom, bottom_c):
1547
# self.left = left
1548
# self.left_c = left_c
1549
# self.top = top
1550
# self.top_c = top_c
1551
# self.right = right
1552
# self.right_c = right_c
1553
# self.bottom = bottom
1554
# self.bottom_c = bottom_c
1555
1556
class ParametricPlot:
1557
r"""
1558
Parametric plotting routines.
1559
"""
1560
def str(self):
1561
r"""
1562
Returns the tachyon string representation of the parameterized curve.
1563
1564
EXAMPLES::
1565
1566
sage: from sage.plot.plot3d.tachyon import ParametricPlot
1567
sage: t = var('t')
1568
sage: f = lambda t: (t,t^2,t^3)
1569
sage: q = ParametricPlot(f,0,1,'s')
1570
sage: q.str()[9:69]
1571
'sphere center 0.0 0.0 0.0 rad 0.1 s\n \n fcyli'
1572
"""
1573
return "".join([o.str() for o in self._objects])
1574
1575
def __init__(self, f, t_0, t_f, tex, r=.1, cylinders = True, min_depth=4, max_depth=8, e_rel = .01, e_abs = .01):
1576
r"""
1577
Creates the parametric plotting class.
1578
1579
EXAMPLES::
1580
1581
sage: from sage.plot.plot3d.tachyon import ParametricPlot
1582
sage: t = var('t')
1583
sage: f = lambda t: (t,t^2,t^3)
1584
sage: q = ParametricPlot(f,0,1,'s')
1585
sage: q._e_rel
1586
0.01
1587
"""
1588
self._e_rel = e_rel
1589
self._e_abs = e_abs
1590
self._r = r
1591
self._f = f
1592
self._tex = tex
1593
self._cylinders = cylinders
1594
self._max_depth = max_depth
1595
self._min_depth = min_depth
1596
1597
f_0 = f(t_0)
1598
f_f = f(t_f)
1599
self._objects = [Sphere(f_0, r, texture=tex) ]
1600
1601
self._plot_step(0, t_0, t_f, f_0, f_f)
1602
1603
def _plot_step(self, depth, t_0,t_f,f_0,f_f):
1604
r"""
1605
Recursively subdivides interval, eventually plotting with cylinders and spheres.
1606
1607
EXAMPLES::
1608
1609
sage: from sage.plot.plot3d.tachyon import ParametricPlot
1610
sage: t = var('t')
1611
sage: f = lambda t: (t,t^2,t^3)
1612
sage: q = ParametricPlot(f,0,1,'s')
1613
sage: q._plot_step(8,0,1,[0,0,0],[1,1,1])
1614
sage: len(q._objects)
1615
515
1616
"""
1617
if depth < self._max_depth:
1618
t_mid = (t_f + t_0)/2
1619
f_mid = ((f_f[0] + f_0[0])/2, (f_f[1] + f_0[1])/2, (f_f[2] + f_0[2])/2)
1620
f_val = self._f(t_mid)
1621
if depth < self._min_depth or self.tol(f_mid, f_val):
1622
new_depth = depth + 1
1623
else:
1624
new_depth = self._max_depth
1625
1626
self._plot_step(new_depth, t_0,t_mid, f_0, f_val)
1627
self._plot_step(new_depth, t_mid,t_f, f_val, f_f)
1628
else:
1629
if self._cylinders:
1630
self._objects.append(FCylinder(f_0,f_f,self._r,self._tex))
1631
self._objects.append(Sphere(f_f,self._r,self._tex))
1632
1633
1634
def tol(self, est, val):
1635
r"""
1636
Check relative, then absolute tolerance. If both fail, return False.
1637
This is a zero-safe error checker.
1638
1639
EXAMPLES::
1640
1641
sage: from sage.plot.plot3d.tachyon import ParametricPlot
1642
sage: t = var('t')
1643
sage: f = lambda t: (t,t^2,t^3)
1644
sage: q = ParametricPlot(f,0,1,'s')
1645
sage: q.tol([0,0,0],[1,0,0])
1646
False
1647
sage: q.tol([0,0,0],[.0001,0,0])
1648
True
1649
"""
1650
delta = sqrt((val[0]-est[0])**2 + (val[1]-est[1])**2 + (val[2]-val[2])**2)
1651
if delta < self._e_abs:
1652
return True
1653
1654
r = sqrt(val[0]**2+val[1]**2+val[2]**2)
1655
if delta < self._e_rel*r:
1656
return True
1657
1658
return False
1659
1660
def tostr(s, length = 3, out_type = float):
1661
r"""
1662
Converts vector information to a space-separated string.
1663
1664
EXAMPLES::
1665
1666
sage: from sage.plot.plot3d.tachyon import tostr
1667
sage: tostr((1,1,1))
1668
' 1.0 1.0 1.0 '
1669
sage: tostr('2 3 2')
1670
'2 3 2'
1671
"""
1672
if isinstance(s, str):
1673
return s
1674
output = ' '
1675
for an_item in s:
1676
output = output + str(out_type(an_item)) + ' '
1677
return output
1678
1679
1680