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