~ubuntu-branches/ubuntu/saucy/python-scipy/saucy

« back to all changes in this revision

Viewing changes to scipy/sandbox/xplt/pl3d.py

  • Committer: Bazaar Package Importer
  • Author(s): Ondrej Certik
  • Date: 2008-06-16 22:58:01 UTC
  • mfrom: (2.1.24 intrepid)
  • Revision ID: james.westby@ubuntu.com-20080616225801-irdhrpcwiocfbcmt
Tags: 0.6.0-12
* The description updated to match the current SciPy (Closes: #489149).
* Standards-Version bumped to 3.8.0 (no action needed)
* Build-Depends: netcdf-dev changed to libnetcdf-dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
## Automatically adapted for scipy Oct 31, 2005 by
 
2
 
 
3
# $Id: pl3d.py 2183 2006-08-29 10:30:44Z oliphant $
 
4
# Copyright (c) 1996, 1997, The Regents of the University of California.
 
5
# All rights reserved.  See Legal.htm for full text and disclaimer.
 
6
 
 
7
from scipy import *
 
8
from shapetest import *
 
9
from yorick import *
 
10
from numpy import *
 
11
from gistfuncs import *
 
12
 
 
13
 
 
14
#  PL3D.PY
 
15
#  Viewing transforms and other aids for 3D plotting.
 
16
#
 
17
#  $Id: pl3d.py 2183 2006-08-29 10:30:44Z oliphant $
 
18
 
 
19
#     Copyright (c) 1997.  The Regents of the University of California.
 
20
#                   All rights reserved.
 
21
 
 
22
"""
 
23
   General overview of module pl3d:
 
24
 
 
25
   (1) Viewing transform machinery.  Arguably the simplest model
 
26
       is the CAD/CAM notion that the object you see is oriented
 
27
       as you see it in the current picture.  You can then move
 
28
       it left, right, up, down, or toward or away from you,
 
29
       or you can rotate it about any of the three axes (horizontal,
 
30
       vertical, or out of the screen).  The xyz coordinates of the
 
31
       object remains unchanged throughout all of this, but this
 
32
       object coordinate system changes relative to the fixed
 
33
       xyz of the viewer, in which x is always to the right, y is
 
34
       up, and z is directed out of the screen.  Initially, the
 
35
       two coordinate systems coincide.
 
36
 
 
37
       rot3 (xangle,yangle,zangle)
 
38
         Rotate the object about viewer's x-axis by xangle, then
 
39
         about viewer's y-axis by yangle, then about viewer's
 
40
         z-axis by zangle
 
41
       mov3 (xchange,ychange,zchange)
 
42
         Move the object by the specified amounts.
 
43
 
 
44
       setz3 (zcamera)
 
45
         The "camera" is located at (0,0,zcamera) in the viewer's
 
46
         coordinate system, looking in the minus-z direction.
 
47
         Initially, zcamera is very large, and the magnification
 
48
         factor is correspondingly large, giving an isometric view.
 
49
         Decreasing zcamera makes the perspective more extreme.
 
50
         If parts of the object are behind the camera, strange things
 
51
         may happen.
 
52
 
 
53
       undo3 ()
 
54
       undo3 (n)
 
55
         Undo the last N (default 1) viewpoint commands (rot3, mov3,
 
56
         or setz3).  Up to 100 viewpoint changes are remembered.
 
57
       viewpoint= save3()
 
58
       ...
 
59
       restore3 (viewpoint)
 
60
         The current viewpoint transformation can be saved and later
 
61
         restored.
 
62
 
 
63
       gnomon (on_off)
 
64
         Toggle the gnomon (a simple display showing the orientation
 
65
         of the xyz axes of the object).
 
66
"""
 
67
 
 
68
#  ------------------------------------------------------------------------
 
69
 
 
70
 
 
71
def set_draw3_ ( n ) :
 
72
 
 
73
    """
 
74
    set_draw3_ ( 0 | 1 ) is used to set the global draw3_,
 
75
    which controls whether the function draw3 actually shows a drawing.
 
76
    """
 
77
 
 
78
    global _draw3
 
79
    _draw3 = n
 
80
 
 
81
def setrot3_ (x) :
 
82
 
 
83
    # ZCM 2/21/97 change reflects the fact that I hadn't realized
 
84
    # that car and cdr, as functions, return the item replaced.
 
85
 
 
86
    global _draw3_list
 
87
    oldx = _draw3_list [0]
 
88
    _draw3_list [0] = x
 
89
    undo3_set_ (setrot3_, oldx)
 
90
 
 
91
def rot3 (xa = 0., ya = 0., za = 0.) :
 
92
 
 
93
    """
 
94
    rot3 (xa, ya, za)
 
95
    rotate the current 3D plot by XA about viewer's x-axis,
 
96
    YA about viewer's y-axis, and ZA about viewer's z-axis.
 
97
    SEE ALSO: orient3, mov3, aim3, setz3, undo3, save3, restore3, light3
 
98
    """
 
99
 
 
100
    x = array ([1.,0.,0.], Float)
 
101
    y = array ([0.,1.,0.], Float)
 
102
    z = array ([0.,0.,1.], Float)
 
103
    [x, y] = rot3_ (za, x, y)
 
104
    [z, x] = rot3_ (ya, z, x)
 
105
    [y, z] = rot3_ (xa, y, z)
 
106
    # n. b. matrixMultiply has the unfortunate effect of destroying
 
107
    # the matrix that calls it.
 
108
    gr3 = array (getrot3_ (), copy = 1)
 
109
    setrot3_ (transpose (dot (transpose (gr3), array ( [x, y, z]))))
 
110
 
 
111
def rot3_ (a, x, y) :
 
112
    ca = cos (a)
 
113
    sa = sin (a)
 
114
    return [multiply (ca, x) + multiply (sa, y), multiply (-sa, x) + multiply (ca, y)]
 
115
 
 
116
def mov3 ( xa = 0., ya = 0., za = 0. ) :
 
117
 
 
118
    """
 
119
    mov3 ( [xa [, ya [, za]]])
 
120
    move the current 3D plot by XA along the viewer's x axis,
 
121
    YA along the viewer's y axis, and ZA along the viewer's z axis.
 
122
    SEE ALSO: rot3, orient3, setz3, undo3, save3, restore3, light3
 
123
    """
 
124
 
 
125
    gr = dot (transpose (gr), transpose (xa))
 
126
    setorg3_ ( getorg3_ () - gr)
 
127
 
 
128
def aim3 ( xa = 0., ya = 0., za = 0. ) :
 
129
 
 
130
    """
 
131
    aim3 ( [xa [, ya [, za]]])
 
132
    move the current 3D plot to put the point (XA, YA, ZA) in object
 
133
    coordinates at the point (0, 0, 0) -- the aim point -- in the
 
134
    viewer's coordinates. If any of the XA, YA, or ZA is nil, it defaults
 
135
    SEE ALSO: mov3, rot3, orient3, setz3, undo3, save3, restore3, light3
 
136
    """
 
137
 
 
138
    setorg3_ (x)
 
139
 
 
140
_ZcError = "ZcError"
 
141
 
 
142
def setz3 ( zc = None ) :
 
143
 
 
144
    """
 
145
    setz3 ( [zc] )
 
146
    Set the camera position to z = ZC (x = y = 0) in the viewer's coordinate
 
147
    system. If zc is None, set the camera to infinity (default).
 
148
    SEE ALSO: rot3, orient3, undo3, save3, restore3, light3
 
149
    """
 
150
 
 
151
    if not is_scalar (zc) :
 
152
        raise _ZcError, "camera position must be scalar."
 
153
 
 
154
    setzc3_ (zc)
 
155
 
 
156
def orient3 ( ** kw ) :
 
157
 
 
158
    """
 
159
    orient3 ( [phi = val1, theta = val2] )
 
160
    Set the orientation of the object to (PHI, THETA). Orientations
 
161
    are a subset of the possible rotation matrices in which the z axis
 
162
    of the object appears vertical on the screen (that is, the object
 
163
    z axis projects onto the viewer y axis). The THETA angle is the
 
164
    angle from the viewer y axis to the object z axis, positive if
 
165
    the object z axis is tilted towards you (toward viewer +z). PHI is
 
166
    zero when the object x axis coincides with the viewer x axis. If
 
167
    neither PHI nor THETA is specified, PHI defaults to - pi / 4 and
 
168
    THETA defaults to pi / 6. If only PHI is specified, THETA remains
 
169
    unchanged, unless the current THETA is near pi / 2, in which case
 
170
    THETA returns to pi / 6, or unless the current orientation does
 
171
    not have a vertical z axis, in which case THETA returns to its
 
172
    default.
 
173
    Unlike rot3, orient3 is not a cumulative operation.
 
174
    """
 
175
    # Notes with regard to global variables: (ZCM 2/21/97)
 
176
    # _orient3_phi, _orient3_theta, the default orientation angles,
 
177
    #    are known and referred to only in this routine. I have started
 
178
    #    them with an underscore, too, to make them inaccessible
 
179
    #    from outside this module.
 
180
    # phi and theta need not be global here since they are recalculated
 
181
    #    each time this routine is called.
 
182
 
 
183
    global _orient3_phi, _orient3_theta
 
184
    try :
 
185
        dummy = _orient3_theta
 
186
    except :
 
187
        _orient3_theta = pi / 6.
 
188
 
 
189
    try :
 
190
        dummy = _orient3_phi
 
191
    except :
 
192
        _orient3_phi = - pi / 4.
 
193
 
 
194
    if kw.has_key ("phi") and kw ["phi"] == None :
 
195
        kw ["phi"] = _orient3_phi
 
196
    if kw.has_key ("theta") and kw ["theta"] == None :
 
197
        kw ["theta"] = _orient3_theta
 
198
    if not kw.has_key ("phi") and not kw.has_key ("theta") :
 
199
        phi = _orient3_phi
 
200
        theta = _orient3_theta
 
201
    elif not kw.has_key ("phi") or not kw.has_key ("theta") :
 
202
        gr3 = array (getrot3_ (), copy = 1)
 
203
        z = dot (transpose (gr3), array ( [0., 0., 1.]))
 
204
        if abs (z [0]) > 1.e-6 :
 
205
            # object z-axis not aligned with viewer y-axis
 
206
            if not kw.has_key ("theta") :
 
207
                theta = _orient3_theta
 
208
                phi = kw ["phi"]
 
209
            else :
 
210
                phi = _orient3_phi
 
211
                theta = kw ["theta"]
 
212
        elif not kw.has_key ("theta") :
 
213
            phi = kw ["phi"]
 
214
            if (abs (z [1]) < 1.e-6) :
 
215
                theta = _orient3_theta
 
216
            else :
 
217
                theta = arctan2 (z [2], z [1])
 
218
        else :
 
219
            theta = kw ["theta"]
 
220
            y = array ( [0., z [2], -z [1]])
 
221
            x = dot (transpose (gr3), array ( [1., 0., 0.]))
 
222
            phi = arctan2 (sum (y * x,axis=0), x [0])
 
223
    else :
 
224
        phi = kw ["phi"]
 
225
        theta = kw ["theta"]
 
226
 
 
227
    x = array ( [1., 0., 0.],  Float)
 
228
    y = array ( [0., 1., 0.],  Float)
 
229
    z = array ( [0., 0., 1.],  Float)
 
230
    [y, z] = rot3_ (theta, y, z)
 
231
    [z, x] = rot3_ (phi, z, x)
 
232
    setrot3_ (array ( [x, -z, y],  Float))
 
233
 
 
234
import copy
 
235
 
 
236
def save3 ( ) :
 
237
 
 
238
    """
 
239
    view = save3 ( )
 
240
      Save the current 3D viewing transformation and lighting.
 
241
      Actually, this doesn't save anything; it returns a copy
 
242
      of the current 3D viewing transformation and lighting, so
 
243
      that the user can put it aside somewhere.
 
244
    SEE ALSO: restore3, rot3, mov3, aim3, light3
 
245
    """
 
246
 
 
247
    return _draw3_list [0:_draw3_n]
 
248
 
 
249
def restore3 ( view = None ) :
 
250
 
 
251
    """
 
252
    restore3 ( view )
 
253
    Restore a previously saved 3D viewing transformation and lighting.
 
254
    If view is missing, rotate object to viewer's coordinate system.
 
255
    SEE ALSO: restore3, rot3, mov3, aim3, light3
 
256
    """
 
257
 
 
258
    global _draw3_list, _draw3_view, _light3_list, _draw3_n
 
259
 
 
260
    if view != None :
 
261
        view = view [0:len (view)] # Copies view
 
262
    else :
 
263
        view = _draw3_view + _light3_list
 
264
    old = _draw3_list [0:_draw3_n]
 
265
    _draw3_list = view [0:_draw3_n] + _draw3_list [_draw3_n:]
 
266
    undo3_set_ (restore3, old)
 
267
 
 
268
_AmbientError = "AmbientError"
 
269
_DiffuseError = "DiffuseError"
 
270
_LightingError = "LightingError"
 
271
 
 
272
def light3 ( * kw, ** kwds ) :
 
273
 
 
274
    """
 
275
    light3 (ambient=a_level,
 
276
                     diffuse=d_level,
 
277
                     specular=s_level,
 
278
                     spower=n,
 
279
                     sdir=xyz)
 
280
      Sets lighting properties for 3D shading effects.
 
281
      A surface will be shaded according to its to its orientation
 
282
      relative to the viewing direction.
 
283
 
 
284
      The ambient level A_LEVEL is a light level (arbitrary units)
 
285
      that is added to every surface independent of its orientation.
 
286
 
 
287
      The diffuse level D_LEVEL is a light level which is proportional
 
288
      to cos(theta), where theta is the angle between the surface
 
289
      normal and the viewing direction, so that surfaces directly
 
290
      facing the viewer are bright, while surfaces viewed edge on are
 
291
      unlit (and surfaces facing away, if drawn, are shaded as if they
 
292
      faced the viewer).
 
293
 
 
294
      The specular level S_LEVEL is a light level proportional to a high
 
295
      power spower=N of 1+cos(alpha), where alpha is the angle between
 
296
      the specular reflection angle and the viewing direction.  The light
 
297
      source for the calculation of alpha lies in the direction XYZ (a
 
298
      3 element vector) in the viewer's coordinate system at infinite
 
299
      distance.  You can have ns light sources by making S_LEVEL, N, and
 
300
      XYZ (or any combination) be vectors of length ns (3-by-ns in the
 
301
      case of XYZ).  (See source code for specular_hook function
 
302
      definition if powers of 1+cos(alpha) aren't good enough for you.)
 
303
 
 
304
      With no arguments, return to the default lighting.
 
305
 
 
306
    EXAMPLES:
 
307
      light3 ( diffuse=.1, specular=1., sdir=[0,0,-1])
 
308
        (dramatic "tail lighting" effect)
 
309
      light3 ( diffuse=.5, specular=1., sdir=[1,.5,1])
 
310
        (classic "over your right shoulder" lighting)
 
311
      light3 ( ambient=.1,diffuse=.1,specular=1.,
 
312
              sdir=[[0,0,-1],[1,.5,1]],spower=[4,2])
 
313
        (two light sources combining previous effects)
 
314
    SEE ALSO: rot3, save3, restore3
 
315
    """
 
316
 
 
317
    global _draw3_list, _draw3_nv
 
318
    if len (kw) > 0 : kwds = kw [0]
 
319
    old = _draw3_list [_draw3_nv:] [0:5]
 
320
    flags = 0
 
321
    if kwds.has_key ("ambient") and kwds ["ambient"] != None :
 
322
        ambient = kwds ["ambient"]
 
323
        if not is_scalar (ambient) :
 
324
            raise _AmbientError, "ambient light level must be scalar."
 
325
        flags = flags | 1
 
326
        _draw3_list [_draw3_nv] = ambient
 
327
    if kwds.has_key ("diffuse") and kwds ["diffuse"] != None :
 
328
        diffuse = kwds ["diffuse"]
 
329
        if not is_scalar (diffuse) :
 
330
            raise _DiffuseError, "diffuse light level must be scalar."
 
331
        flags = flags | 2
 
332
        _draw3_list [_draw3_nv + 1 ] = diffuse
 
333
 
 
334
    if kwds.has_key ("specular") and kwds ["specular"] != None :
 
335
        specular = kwds ["specular"]
 
336
        flags = flags | 4
 
337
    else :
 
338
        specular = _draw3_list [_draw3_nv + 2]
 
339
    if kwds.has_key ("spower") and kwds ["spower"] != None :
 
340
        spower = kwds ["spower"]
 
341
        flags = flags | 8
 
342
    else :
 
343
        spower = _draw3_list [_draw3_nv + 3]
 
344
    if kwds.has_key ("sdir") and kwds ["sdir"] != None :
 
345
        sdir = kwds ["sdir"]
 
346
        dims = shape (sdir)
 
347
        if dims == 0 or len (dims) == 2 and dims [1] != 3 :
 
348
            raise _LightingError, \
 
349
               "lighting direction must be 3 vector or ns-by-3 array."
 
350
        flags = flags | 16
 
351
    else :
 
352
        sdir = _draw3_list [_draw3_nv + 4]
 
353
    if flags & 28 :
 
354
        if flags & 4 : _draw3_list [_draw3_nv + 2] = specular
 
355
        if flags & 8 : _draw3_list [_draw3_nv + 3] = spower
 
356
        if flags & 16 : _draw3_list [_draw3_nv + 4] = sdir
 
357
    if not flags :
 
358
        _draw3_list [_draw3_nv: _draw3_nv + 5] = _light3_list [0:5]
 
359
    undo3_set_ (light3_, old)
 
360
 
 
361
def light3_ (arg) :
 
362
    global _draw3_list, _draw3_nv
 
363
 
 
364
    _draw3_list [_draw3_nv:_draw3_nv + 5] = arg  [0:5]
 
365
 
 
366
def get3_light (xyz, * nxyz) :
 
367
 
 
368
    """
 
369
    get3_light(xyz, nxyz)
 
370
       or get3_light(xyz)
 
371
 
 
372
      return 3D lighting for polygons with vertices XYZ.  If NXYZ is
 
373
      specified, XYZ should be sum(nxyz,axis=0)-by-3, with NXYZ being the
 
374
      list of numbers of vertices for each polygon (as for the plfp
 
375
      function).  If NXYZ is not specified, XYZ should be a quadrilateral
 
376
      mesh, ni-by-nj-by-3 (as for the plf function).  In the first case,
 
377
      the return value is len (NXYZ) long; in the second case, the
 
378
      return value is (ni-1)-by-(nj-1).
 
379
 
 
380
      The parameters of the lighting calculation are set by the
 
381
      light3 function.
 
382
 
 
383
      SEE ALSO: light3, set3_object, get3_normal, get3_centroid
 
384
      """
 
385
 
 
386
    global _draw3_list, _draw3_nv
 
387
    list = _draw3_list [_draw3_nv:]
 
388
    ambient = list [0]
 
389
    diffuse = list [1]
 
390
    specular = list [2]
 
391
    spower = list [3]
 
392
    sdir = list [4]
 
393
 
 
394
    if len (nxyz) == 0 :
 
395
        normal = get3_normal (xyz)
 
396
    else :
 
397
        normal = get3_normal (xyz, nxyz [0])
 
398
 
 
399
    zc = getzc3_ ( )
 
400
    if ( not zc ) :
 
401
        view = array ( [0., 0., 1.],  Float)
 
402
    elif len (nxyz) == 0 :
 
403
        view = array ( [0., 0., zc],  Float) - get3_centroid (xyz)
 
404
    else :
 
405
        view = array ( [0., 0., zc],  Float) - get3_centroid (xyz, nxyz [0])
 
406
        m1 = \
 
407
           sqrt ( sum (view * view,axis=0))
 
408
        if m1 == 0. : m1 = 1.
 
409
        view = view / m1
 
410
 
 
411
    nv = normal [0, ...] * view [0] + normal [1, ...] * view [1] +  \
 
412
       normal [2, ...] * view [2]
 
413
    light = ambient + diffuse * abs (nv)
 
414
    if specular != 0. :
 
415
        sv = transpose (transpose (sdir) / sqrt (sum (transpose (sdir*sdir),axis=0)))
 
416
        sv = dot (sv, view)
 
417
        if len (shape (sdir)) == 1 :
 
418
            sn = sum(array([sdir[0]*normal[0],sdir[1]*normal[1],
 
419
                            sdir[2]*normal[2]]),axis=0)
 
420
            ####### I left out the specular_hook stuff.
 
421
            m1 = maximum (sn * nv -0.5 * sv + 0.5, 1.e-30)
 
422
            m1 = m1 ** spower
 
423
            light = light + (specular * m1)
 
424
        elif len (shape (sdir)) >= 2 :
 
425
            # multiple light sources
 
426
            nsrc = len (shape (sdir))
 
427
            for i in range (nsrc) :
 
428
                sn = sum(array([sdir[i,0]*normal[0],sdir[i,1]*normal[1],
 
429
                            sdir[i,2]*normal[2]]),axis=0)
 
430
                m1 = maximum (sn * nv -0.5 * sv [i] + 0.5, 1.e-30) ** spower [i]
 
431
                light = light + specular * m1
 
432
    return light
 
433
 
 
434
def get3_normal (xyz, *nxyz) :
 
435
 
 
436
    """
 
437
      get3_normal(xyz, nxyz)
 
438
          or get3_normal(xyz)
 
439
 
 
440
      return 3D normals for polygons with vertices XYZ.  If NXYZ is
 
441
      specified, XYZ should be sum(nxyz,axis=0)-by-3, with NXYZ being the
 
442
      list of numbers of vertices for each polygon (as for the plfp
 
443
      function).  If NXYZ is not specified, XYZ should be a quadrilateral
 
444
      mesh, ni-by-nj-by-3 (as for the plf function).  In the first case,
 
445
      the return value is len(NXYZ)-by-3; in the second case, the
 
446
      return value is (ni-1)-by-(nj-1)-by-3.
 
447
 
 
448
      The normals are constructed from the cross product of the lines
 
449
      joining the midpoints of two edges which as nearly quarter the
 
450
      polygon as possible (the medians for a quadrilateral).  No check
 
451
      is made that these not be parallel; the returned "normal" is
 
452
      [0,0,0] in that case.  Also, if the polygon vertices are not
 
453
      coplanar, the "normal" has no precisely definable meaning.
 
454
 
 
455
      SEE ALSO: get3_centroid, get3_light
 
456
      """
 
457
 
 
458
    if len (nxyz) == 0 :
 
459
        # if no polygon list is given, assume xyz is 2D mesh
 
460
        # form normal as cross product of medians
 
461
        m1 = dif_ (zcen_ (xyz, 1), 2)
 
462
        m2 = zcen_ (dif_ (xyz, 1), 2)
 
463
    else :
 
464
        # with polygon list, more elaborate calculation required
 
465
        # (1) frst subscripts the first vertex of each polygon
 
466
        frst = cumsum (nxyz [0],axis=0) - nxyz [0]
 
467
 
 
468
        # form normal by getting two approximate diameters
 
469
        # (reduces to above medians for quads)
 
470
        # (2) compute midpoints of first three sides
 
471
        n2 = (nxyz [0] + 1) / 2
 
472
        c0 = (take(xyz, frst, 0) + take(xyz, frst + 1, 0)) / 2.
 
473
        i = frst + n2 - 1
 
474
        c1 = (take(xyz, i, 0) + take(xyz, i + 1, 0)) / 2.
 
475
        i = n2 / 2
 
476
        c2 = (take(xyz, frst + i, 0) + take(xyz, frst + (i + 1) % nxyz [0], 0)) / 2.
 
477
        i = minimum (i + n2, nxyz [0]) - 1
 
478
        c3 = (take(xyz, frst + i, 0) + take(xyz, frst + (i + 1) % nxyz [0], 0)) / 2.
 
479
        m1 = c1 - c0
 
480
        m2 = c3 - c2
 
481
 
 
482
    # poly normal is cross product of two medians (or diameters)
 
483
    # normal = m1; I had to reverse the sign.
 
484
    if len (shape (xyz)) == 3 :
 
485
        n1 = m1 [2, :] * m2 [1, :] - \
 
486
                               m1 [1, :] * m2 [2, :]
 
487
        n2 = m1 [0, :] * m2 [2, :] - \
 
488
                               m1 [2, :] * m2 [0, :]
 
489
        n3 = m1 [1, :] * m2 [0, :] - \
 
490
                               m1 [0, :] * m2 [1, :]
 
491
    else :
 
492
        n1 = m1 [:, 2] * m2 [:, 1] - \
 
493
                               m1 [:, 1] * m2 [:, 2]
 
494
        n2 = m1 [:, 0] * m2 [:, 2] - \
 
495
                               m1 [:, 2] * m2 [:, 0]
 
496
        n3 = m1 [:, 1] * m2 [:, 0] - \
 
497
                               m1 [:, 0] * m2 [:, 1]
 
498
    m1 = sqrt (n1 ** 2 + n2 **2 + n3 **2)
 
499
    m1 = m1 + equal (m1, 0.0)
 
500
    normal = array([n1 / m1, n2 / m1, n3 / m1])
 
501
 
 
502
    return normal
 
503
 
 
504
def get3_centroid (xyz, * nxyz) :
 
505
 
 
506
    """
 
507
      get3_centroid(xyz, *nxyz)
 
508
          or get3_centroid(xyz)
 
509
 
 
510
      return 3D centroids for polygons with vertices XYZ.  If NXYZ is
 
511
      specified, XYZ should be sum(nxyz,axis=0)-by-3, with NXYZ being the
 
512
      list of numbers of vertices for each polygon (as for the plfp
 
513
      function).  If NXYZ is not specified, XYZ should be a quadrilateral
 
514
      mesh, ni-by-nj-by-3 (as for the plf function).  In the first case,
 
515
      the return value is len(NXYZ) in length; in the second case, the
 
516
      return value is (ni-1)-by-(nj-1)-by-3.
 
517
 
 
518
      The centroids are constructed as the mean value of all vertices
 
519
      of each polygon.
 
520
 
 
521
      SEE ALSO: get3_normal, get3_light
 
522
    """
 
523
 
 
524
    if len (nxyz) == 0 :
 
525
        # if no polygon list is given, assume xyz is 2D mesh
 
526
        centroid = zcen_ (zcen_ (xyz, 1), 0)
 
527
    else :
 
528
        # with polygon list, more elaborate calculation required
 
529
        last = cumsum (nxyz [0],axis=0)
 
530
        list = histogram (1 + last) [0:-1]
 
531
        list = cumsum (list,axis=0)
 
532
        k = len (nxyz [0])
 
533
        l = shape (xyz) [0]
 
534
        centroid = zeros ( (k, 3))
 
535
        centroid [0:k, 0] = histogram (list, xyz [0:l,0])
 
536
        centroid [0:k, 1] = histogram (list, xyz [0:l,1])
 
537
        centroid [0:k, 2] = histogram (list, xyz [0:l,2])
 
538
        fnxyz = array (nxyz [0], Float )
 
539
        centroid = centroid / fnxyz
 
540
    return centroid
 
541
 
 
542
_Get3Error = "Get3Error"
 
543
 
 
544
def get3_xy (xyz, *flg) :
 
545
 
 
546
    """
 
547
      get3_xy (xyz)
 
548
          or get3_xy(xyz, 1)
 
549
 
 
550
      Given anything-by-3 coordinates XYZ, return X and Y in viewer's
 
551
      coordinate system (set by rot3, mov3, orient3, etc.).  If the
 
552
      second argument is present and non-zero, also return Z (for use
 
553
      in sort3d or get3_light, for example).  If the camera position
 
554
      has been set to a finite distance with setz3, the returned
 
555
      coordinates will be tangents of angles for a perspective
 
556
      drawing (and Z will be scaled by 1/zc).
 
557
      Unlike the Yorick version, this function returns a 3-by-anything
 
558
      array of coordinates.
 
559
      Actually, what it returns is a 3-by-anything python array, whose
 
560
      0th element is the x array, whose 1th element is the y array, and
 
561
      whose 2th element is the z array if asked for.
 
562
      I believe that x, y, and z can be either 1d or 2d, so this
 
563
      routine is written in two cases.
 
564
 
 
565
    """
 
566
 
 
567
    # rotate and translate to viewer's coordinate system
 
568
    shp = shape (xyz)
 
569
    if len (shp) == 3:
 
570
        # 2d mesh case is much more complex than in Yorick
 
571
        (k, l) = shp [1:3]
 
572
        go3_ = getorg3_ ()
 
573
        # Unwind xyz
 
574
        xx = ravel (xyz [0])
 
575
        yy = ravel (xyz [1])
 
576
        zz = ravel (xyz [2])
 
577
        tmpxyz = array ( [xx, yy, zz])
 
578
        gr3 = array (getrot3_ (), copy = 1)
 
579
        tmpxyz = dot (transpose (gr3),
 
580
           tmpxyz - array ( [ [go3_ [0]], [go3_ [1]], [go3_ [2]]]))
 
581
##    xx = transpose (reshape (ravel (tmpxyz [0]), (k,l)))
 
582
##    yy = transpose (reshape (ravel (tmpxyz [1]), (k,l)))
 
583
##    zz = transpose (reshape (ravel (tmpxyz [2]), (k,l)))
 
584
        xx = (reshape (ravel (tmpxyz [0]), (k,l)))
 
585
        yy = (reshape (ravel (tmpxyz [1]), (k,l)))
 
586
        zz = (reshape (ravel (tmpxyz [2]), (k,l)))
 
587
        tmpxyz = array ( [xx, yy, zz])
 
588
    elif len (shp) == 2:
 
589
        go3_ = getorg3_ ()
 
590
        lm = array (getrot3_ (), copy = 1)
 
591
        rm = (xyz - array ( [ go3_ [0], go3_ [1], go3_ [2]]))
 
592
        tmpxyz = dot (rm, lm)
 
593
    else:
 
594
        raise _Get3Error, "xyz has a bad shape: " + `shp`
 
595
 
 
596
    # do optional perspective projection
 
597
    zc = getzc3_ ()
 
598
    if zc != None :
 
599
        if len (shp) == 2 :
 
600
            z = tmpxyz [:, 2]
 
601
            zc = maximum (zc - z, 1.e-35)     # protect behind camera, avoid zero divide
 
602
            tmpxyz [:, 0] = tmpxyz [:, 0] / zc
 
603
            tmpxyz [:, 1] = tmpxyz [:, 1] / zc
 
604
            if len (flg) != 0 and flg [0] != 0 :
 
605
                tmpxyz [:, 2] = tmpxyz [:, 2] / zc
 
606
        elif len (shp) == 3 :
 
607
            z = tmpxyz [:,:, 2]
 
608
            zc = maximum (zc - z, 1.e-35)     # protect behind camera, avoid zero divide
 
609
            tmpxyz [:,:, 0] = tmpxyz [:,:, 0] / zc
 
610
            tmpxyz [:,:, 1] = tmpxyz [:,:, 1] / zc
 
611
            if len (flg) != 0 and flg [0] != 0 :
 
612
                tmpxyz [:,:, 2] = tmpxyz [:,:, 2] / zc
 
613
    return tmpxyz
 
614
 
 
615
_UndoError = "UndoError"
 
616
 
 
617
_in_undo3 = 0
 
618
_undo3_list = []
 
619
 
 
620
def undo3 (n = 1) :
 
621
 
 
622
    """
 
623
      undo3 ()
 
624
          or undo3 (n)
 
625
      Undo the effects of the last N (default 1) rot3, orient3, mov3, aim3,
 
626
      setz3, or light3 commands.
 
627
    """
 
628
 
 
629
    global _in_undo3, _undo3_list
 
630
    n = 2 * n
 
631
    if n < 0 or n > len (_undo3_list) :
 
632
        raise _UndoError, "not that many items in undo list"
 
633
    _in_undo3 = 1     # flag to skip undo3_set_
 
634
    # perhaps should save discarded items in a redo list?
 
635
    use_list = undo3_list [-n:]
 
636
    undo3_list = undo3_list [:-n]
 
637
    while n > 0 :
 
638
        fnc = use_list_ [0]
 
639
        del use_list_ [0]
 
640
        arg = use_list_ [0]
 
641
        del use_list_ [0]
 
642
        fnc (arg)
 
643
        n = n - 2
 
644
    _in_undo3 = 0
 
645
    draw3_trigger ( )
 
646
 
 
647
def set3_object (fnc, arg) :
 
648
 
 
649
    """
 
650
      set3_object (drawing_function, [arg1,arg2,...])
 
651
 
 
652
      set up to trigger a call to draw3, adding a call to the
 
653
      3D display list of the form:
 
654
 
 
655
         DRAWING_FUNCTION ( [ARG1, ARG2, ...]))
 
656
 
 
657
      When draw3 calls DRAWING_FUNCTION, the external variable draw3_
 
658
      will be non-zero, so DRAWING_FUNCTION can be written like this:
 
659
 
 
660
      def drawing_function(arg) :
 
661
 
 
662
        if (draw3_) :
 
663
           arg1= arg [0]
 
664
           arg1= arg [1]
 
665
           ...
 
666
           ...<calls to get3_xy, sort3d, get3_light, etc.>...
 
667
           ...<calls to graphics functions plfp, plf, etc.>...
 
668
           return
 
669
 
 
670
        ...<verify args>...
 
671
        ...<do orientation and lighting independent calcs>...
 
672
        set3_object (drawing_function, [arg1,arg2,...])
 
673
 
 
674
    SEE ALSO: get3_xy, get3_light, sort3d
 
675
    """
 
676
 
 
677
    global _draw3_list
 
678
    _draw3_list = _draw3_list + [fnc, arg]
 
679
    draw3_trigger ()
 
680
 
 
681
def setorg3_ ( x ) :
 
682
    # ZCM 2/21/97 change reflects the fact that I hadn't realized
 
683
    # that car and cdr, as functions, return the item replaced.
 
684
    global _draw3_list
 
685
    oldx = _draw3_list [1]
 
686
    _draw3_list [1] = x
 
687
    undo3_set_ ( setorg3_,  oldx)
 
688
 
 
689
def setzc3_ (x) :
 
690
    # ZCM 2/21/97 change reflects the fact that I hadn't realized
 
691
    # that car and cdr, as functions, return the item replaced.
 
692
    global _draw3_list
 
693
    oldx = _draw3_list [2]
 
694
    _draw3_list [2] = x
 
695
    undo3_set_ ( setzc3_,  oldx)
 
696
 
 
697
def getrot3_ () :
 
698
    return _draw3_list [0]
 
699
 
 
700
def getorg3_ () :
 
701
    return _draw3_list [1]
 
702
 
 
703
def getzc3_ () :
 
704
    return _draw3_list [2]
 
705
 
 
706
def undo3_set_ (fnc, arg) :
 
707
    global _undo3_list, _in_undo3, _undo3_limit
 
708
    # arg = copy.deepcopy (arg)
 
709
    if not _in_undo3 :
 
710
        if len (_undo3_list) >= 2 * _undo3_limit :
 
711
            _undo3_list = _undo3_list [0:2 * _undo3_limit - 2]
 
712
        _undo3_list = [fnc, arg] + _undo3_list
 
713
    draw3_trigger ( )
 
714
 
 
715
_in_undo3 = 0         # ??????????????
 
716
_in_undo3 = 100
 
717
 
 
718
def do_nothing ( ) :
 
719
    pass
 
720
    return
 
721
 
 
722
def clear_idler ( ) :
 
723
    _idler = do_nothing ( )
 
724
 
 
725
def set_idler ( fnc ) :
 
726
    global _idler
 
727
    _idler = fnc
 
728
 
 
729
def call_idler ( ) :
 
730
    global _idler
 
731
    _idler ( )
 
732
 
 
733
def _draw3_idler ( ) :
 
734
    # I have added orientation and limits to this because they may not
 
735
    # have been set by a previous command. If the user doesn't like this,
 
736
    # he/she can write his/her own idler. (ZCM 7/1/97)
 
737
    global _default_gnomon
 
738
    orient3 ()
 
739
    if current_window () == -1 :
 
740
        window3 (0)
 
741
    else :
 
742
        window3 (current_window ())
 
743
    gnomon (_default_gnomon)
 
744
    lims = draw3 (1)
 
745
    if lims == None :
 
746
        return
 
747
    else :
 
748
        limits (lims [0], lims [1], lims [2], lims [3])
 
749
 
 
750
def set_default_idler ( ) :
 
751
    set_idler (_draw3_idler)
 
752
 
 
753
set_default_idler ( )
 
754
 
 
755
_draw3_changes = None
 
756
 
 
757
def set_multiple_components ( n = 0 ) :
 
758
    global _multiple_components
 
759
    _multiple_components = n
 
760
 
 
761
set_multiple_components (0)
 
762
 
 
763
def has_multiple_components () :
 
764
    global _multiple_components
 
765
    return _multiple_components
 
766
 
 
767
def draw3_trigger ( ) :
 
768
    "arrange to call draw3 when everything else is finished"
 
769
    global _draw3_changes
 
770
    global _draw3_idler
 
771
    set_idler ( _draw3_idler )
 
772
    _draw3_changes = 1
 
773
 
 
774
def clear3 ( ) :
 
775
    "clear3 ( ) : Clear the current 3D display list."
 
776
    global _draw3_list, _draw3_n
 
777
    _draw3_list [_draw3_n:] = []
 
778
    set_multiple_components (0)
 
779
 
 
780
def window3 ( * n , **kw ) :
 
781
 
 
782
    """
 
783
    window3 ( ) or window3 (n)
 
784
    initialize style="nobox.gs" window for 3D graphics
 
785
    """
 
786
 
 
787
    if kw.has_key ("dump") :
 
788
        dump = kw ["dump"]
 
789
    else :
 
790
        dump = 0
 
791
    if kw.has_key ("hcp") :
 
792
        if len (n) == 0 :
 
793
            window (wait=1, style="nobox.gs", legends=0, hcp=kw ["hcp"],
 
794
               dump = dump)
 
795
            hcpon ()
 
796
        else :
 
797
            window (n [0], wait=1, style="nobox.gs", legends=0, hcp=kw ["hcp"],
 
798
               dump = dump)
 
799
            hcpon ()
 
800
    else :
 
801
        if len (n) == 0 :
 
802
            window (wait=1, style="nobox.gs", legends=0)
 
803
        else :
 
804
            window (n [0], wait=1, style="nobox.gs", legends=0)
 
805
 
 
806
def sort3d (z, npolys) :
 
807
 
 
808
    """
 
809
    sort3d(z, npolys)
 
810
      given Z and NPOLYS, with len(Z)==sum(npolys,axis=0), return
 
811
      a 2-element list [LIST, VLIST] such that Z[VLIST] and NPOLYS[LIST] are
 
812
      sorted from smallest average Z to largest average Z, where
 
813
      the averages are taken over the clusters of length NPOLYS.
 
814
      Within each cluster (polygon), the cyclic order of Z[VLIST]
 
815
      remains unchanged, but the absolute order may change.
 
816
 
 
817
      This sorting order produces correct or nearly correct order
 
818
      for a plfp command to make a plot involving hidden or partially
 
819
      hidden surfaces in three dimensions.  It works best when the
 
820
      polys form a set of disjoint closed, convex surfaces, and when
 
821
      the surface normal changes only very little between neighboring
 
822
      polys.  (If the latter condition holds, then even if sort3d
 
823
      mis-orders two neighboring polys, their colors will be very
 
824
      nearly the same, and the mistake won't be noticeable.)  A truly
 
825
      correct 3D sorting routine is impossible, since there may be no
 
826
      rendering order which produces correct surface hiding (some polys
 
827
      may need to be split into pieces in order to do that).  There
 
828
      are more nearly correct algorithms than this, but they are much
 
829
      slower.
 
830
    SEE ALSO: get3_xy
 
831
    """
 
832
 
 
833
    # first compute z, the z-centroid of every poly
 
834
    # get a list the same length as x, y, or z which is 1 for each
 
835
    # vertex of poly 1, 2 for each vertex of poly2, etc.
 
836
    # the goal is to make nlist with histogram(nlist)==npolys
 
837
    nlist = histogram(cumsum (npolys,axis=0)) [0:-1]
 
838
    nlist = cumsum (nlist,axis=0)
 
839
    # now sum the vertex values and divide by the number of vertices
 
840
    z = histogram (nlist, z) / npolys
 
841
 
 
842
    # sort the polygons from smallest z to largest z
 
843
    list = index_sort (z)
 
844
    # next, find the list which sorts the polygon vertices
 
845
    # first, find a list vlist such that sort(vlist) is above list
 
846
    vlist = zeros (len (list), Int)
 
847
    array_set (vlist, list, arange (len (list), dtype = Int))
 
848
    # then reset the nlist values to that pre-sorted order, so that
 
849
    # sort(nlist) will be the required vertex sorting list
 
850
    nlist = take(vlist, nlist, 0)
 
851
    # the final hitch is to ensure that the vertices within each polygon
 
852
    # remain in their initial order (sort scrambles equal values)
 
853
    # since the vertices of a polygon can be cyclically permuted,
 
854
    # it suffices to add a sawtooth function to a scaled nlist to
 
855
    # produce a list in which each cluster of equal values will retain
 
856
    # the same cyclic order after the sort
 
857
    # (note that the more complicated msort routine would leave the
 
858
    #  clusters without even a cyclic permutation, if that were
 
859
    #  necessary)
 
860
    n1max = max (npolys)    # this must never be so large that
 
861
                            # numberof(npolys)*nmax > 2e9
 
862
    nmax = n1max * ones (len (nlist), Int)
 
863
    vlist = index_sort (nmax * nlist +
 
864
       arange (len (nlist), dtype = Int) % n1max)
 
865
    #         primary sort key ^            secondary key  ^
 
866
    return [list, vlist]
 
867
 
 
868
_square = 1 # Global variable which tells whether to force equal axes
 
869
_xfactor = 1.
 
870
_yfactor = 1. # These globals enable one to scale one or both axes up or down
 
871
 
 
872
def get_factors_ ( ) :
 
873
    return [_xfactor, _yfactor]
 
874
 
 
875
def get_square_ ( ) :
 
876
    global _square
 
877
    return _square
 
878
 
 
879
def limits_ (square = 0, yfactor = 1., xfactor = 1.) :
 
880
    global _square, _xfactor, _yfactor
 
881
    _square = square
 
882
    _xfactor = xfactor
 
883
    _yfactor = yfactor
 
884
 
 
885
def draw3 (called_as_idler = 0, lims = None) :
 
886
 
 
887
    """
 
888
       draw3 (called_as_idler = 0, lims = None):
 
889
    Draw the current 3d display list.
 
890
    Ordinarily triggered automatically when the drawing changes.
 
891
    """
 
892
 
 
893
    global _draw3, _draw3_changes, _draw3_list, _draw3_n, _gnomon
 
894
    if _draw3_changes :
 
895
        if called_as_idler :
 
896
            fma ( )
 
897
 
 
898
        # the first _draw3_n elements of _draw3_list are the viewing
 
899
        # transforms, lighting, etc.
 
900
        # thereafter, elements are (function, argument-list) pairs
 
901
        # the _draw3 flag alerts the functions that these are the draw
 
902
        # calls rather than the interactive setup calls
 
903
        set_draw3_ (1)
 
904
        list = _draw3_list [_draw3_n:]
 
905
        no_lims = lims == None
 
906
        first = 1
 
907
        # ZCM Feb. 1997: Because Gist command 'limits' seems to
 
908
        # misbehave and be timing dependent, I have added the kludge
 
909
        # below, which seems to make things work.
 
910
        while list != [] :
 
911
            fnc = list [0]
 
912
            if no_lims :
 
913
                if (first) :
 
914
                    lims = fnc (list [1])
 
915
                    first = 0
 
916
                else :
 
917
                    fv = fnc (list [1])
 
918
                    if fv != None and lims != None :
 
919
                        lims = [min (fv [0], lims [0]),
 
920
                                max (fv [1], lims [1]),
 
921
                                min (fv [2], lims [2]),
 
922
                                max (fv [3], lims [3])]
 
923
                    elif fv != None :
 
924
                        lims = fv
 
925
            else :
 
926
                fnc (list [1])
 
927
            list = list [2:]
 
928
        if _gnomon :
 
929
            _gnomon_draw ( )
 
930
        _draw3_changes = None
 
931
        set_draw3_ (0)
 
932
        return lims
 
933
#     _draw3 = 0
 
934
 
 
935
try :
 
936
    dummy = _draw3_view
 
937
except :
 
938
    _draw3_view = [array ([[1, 0, 0], [0, 1, 0], [0, 0, 1]]), [0., 0., 0.], None]
 
939
_draw3_nv = len (_draw3_view)
 
940
 
 
941
try :
 
942
    dummy = _draw3
 
943
except :
 
944
    set_draw3_ (0)
 
945
 
 
946
def get_draw3_ ( ) :
 
947
    global _draw3
 
948
    return _draw3
 
949
 
 
950
try :
 
951
    dummy = _light3_ambient
 
952
except :
 
953
    _light3_ambient = 0.2
 
954
 
 
955
try :
 
956
    dummy = _light3_diffuse
 
957
except :
 
958
    _light3_diffuse = 1.0
 
959
 
 
960
try :
 
961
    dummy = _light3_specular
 
962
except :
 
963
    _light3_specular = 0.0
 
964
 
 
965
try :
 
966
    dummy = _light3_spower
 
967
except :
 
968
    _light3_spower = 2
 
969
 
 
970
try :
 
971
    dummy = _light3_sdir
 
972
except :
 
973
    _light3_sdir = array ( [1.0, 0.5, 1.0]) / sqrt(2.25)
 
974
 
 
975
_light3_list = [_light3_ambient, _light3_diffuse, _light3_specular,
 
976
                _light3_spower, _light3_sdir]
 
977
 
 
978
 
 
979
_draw3_list = _draw3_view + _light3_list
 
980
_draw3_n = len (_draw3_list)
 
981
 
 
982
def get_draw3_list_ ( ) :
 
983
    global _draw3_list
 
984
    return _draw3_list
 
985
 
 
986
def get_draw3_n_ ( ) :
 
987
    global _draw3_n
 
988
    return _draw3_n
 
989
 
 
990
try :
 
991
    dummy = _gnomon
 
992
except :
 
993
    _gnomon = 0
 
994
 
 
995
def set_default_gnomon ( * n ) :
 
996
    # The default gnomon value is used when _draw3 is nonzero, i. e.,
 
997
    # when a plot is actually done after every plot call.
 
998
    global _default_gnomon
 
999
    if len (n) > 0 :
 
1000
        _default_gnomon = n
 
1001
    else :
 
1002
        _default_gnomon = 0
 
1003
 
 
1004
set_default_gnomon (0)
 
1005
 
 
1006
def gnomon (* on, ** kw) :
 
1007
 
 
1008
    """
 
1009
    gnomon ()
 
1010
       or gnomon (onoff)
 
1011
      Toggle the gnomon display. If on is present and non-zero,
 
1012
      turn on the gnomon. If zero, turn it off.
 
1013
 
 
1014
      The gnomon shows the X, Y, and Z axis directions in the
 
1015
      object coordinate system. The directions are labeled.
 
1016
      The gnomon is always infinitely far behind the object
 
1017
      (away from the camera).
 
1018
 
 
1019
      There is a mirror-through-the-screen-plane ambiguity in the
 
1020
      display which is resolved in two ways: (1) the (X, Y, Z)
 
1021
      coordinate system is right-handed, and (2) If the tip of an
 
1022
      axis projects into the screen, its label is drawn in opposite
 
1023
      polarity to the other text in the screen.
 
1024
    """
 
1025
 
 
1026
#    (ZCM 4/4/97) Add keyword argument chr to allow specification
 
1027
#    of the axis labels.
 
1028
 
 
1029
    global _gnomon, chr
 
1030
    old = _gnomon
 
1031
    if len (on) == 0 :
 
1032
        _gnomon = 1 - _gnomon
 
1033
    elif (on [0]) :
 
1034
        _gnomon = 1
 
1035
    else :
 
1036
        _gnomon = 0
 
1037
    if old != _gnomon :
 
1038
        draw3_trigger ()
 
1039
    if kw.has_key ("chr") :
 
1040
        chr = kw ["chr"]
 
1041
    else :
 
1042
        chr = ["X", "Y", "Z"]
 
1043
 
 
1044
def _gnomon_draw ( ) :
 
1045
    global chr
 
1046
    o = array ( [0., 0., 0.],  Float)
 
1047
    x1 = array ( [1., 0., 0.],  Float)
 
1048
    y1 = array ( [0., 1., 0.],  Float)
 
1049
    z1 = array ( [0., 0., 1.],  Float)
 
1050
    xyz1 = array (getrot3_ ( ), copy = 1)
 
1051
    xyz2 = array([[o,x1],[o,y1],[o,z1]])
 
1052
    s1 = shape ( xyz1 )
 
1053
    s2 = shape ( xyz2 )
 
1054
    xyz = zeros ( (s2 [1], s2 [0], s1 [1] ), Float)
 
1055
    xyz [0, :, :] = dot (transpose (xyz1), xyz2 [:, 0, :])
 
1056
    xyz [1, :, :] = dot (transpose (xyz1), xyz2 [:, 1, :])
 
1057
    xyz = .0013 * _gnomon_scale * xyz
 
1058
    x1 = xyz [0:2, 0, 0:3]
 
1059
    y1 = xyz [0:2, 1, 0:3]
 
1060
    z1 = xyz [1, 2, 0:3]
 
1061
    x0 = x1 [0]
 
1062
    x1 = x1 [1]
 
1063
    y0 = y1 [0]
 
1064
    y1 = y1 [1]
 
1065
    wid = min (_gnomon_scale / 18., 6.)
 
1066
    if ( wid < 0.5 ) : wid = 0.
 
1067
    plsys (0)
 
1068
    pldj (x0 + _gnomon_x, y0 + _gnomon_y, x1 + _gnomon_x, y1 + _gnomon_y,
 
1069
          width = wid, type = 1, legend = "")
 
1070
    plsys (1)
 
1071
 
 
1072
    # Compute point size of labels (1/3 of axis length)
 
1073
    pts = [8, 10, 12, 14, 18, 24] [digitize (_gnomon_scale / 3.0,
 
1074
          array ([9, 11, 13, 16, 21], Int))]
 
1075
 
 
1076
    if _gnomon_scale < 21.0 :
 
1077
        x1 = x1 * 21. / _gnomon_scale
 
1078
        y1 = y1 * 21. / _gnomon_scale
 
1079
    # label positions: first find shortest axis
 
1080
    xy = sqrt (x1 * x1 + y1 * y1)
 
1081
    xysum = add.reduce (xy)
 
1082
    i = argmin (xy,axis=-1)          # mnx (xy)
 
1083
    jk = [ [1, 2], [2, 0], [0, 1]] [i]
 
1084
    j = jk [0]
 
1085
    k = jk [1]
 
1086
    if xy [i] < 1.e-7 * xysum : # guarantee not exactly zero
 
1087
        x1 [i] = -1.e-6 * (x1 [j] + x1 [k] )
 
1088
        y1 [i] = -1.e-6 * (y1 [j] + y1 [k] )
 
1089
        xy [i] = sqrt (x1 [i] * x1 [i] + y1 [i] * y1 [i])
 
1090
    xyi = xy [i]
 
1091
    # next find axis nearest to shortest
 
1092
    if abs (x1 [j] * y1 [i] - y1 [j] * x1 [i]) * xy [k] > \
 
1093
       abs (x1 [k] * y1 [i] - y1 [k] * x1 [i]) * xy [j] :
 
1094
        jk = j
 
1095
        j = k
 
1096
        k = jk
 
1097
    # furthest axis first--move perpendicular to nearest axis
 
1098
    xk = - y1 [j]
 
1099
    yk = x1 [j]
 
1100
    xy = sqrt (xk * xk + yk * yk)
 
1101
    xk = xk / xy
 
1102
    yk = yk / xy
 
1103
    if (xk * x1 [k] + yk * y1 [k] < 0.0 ) :
 
1104
        xk = - xk
 
1105
        yk = - yk
 
1106
    # nearer axis next--move perpendicular to furthest axis
 
1107
    xj = - y1 [k]
 
1108
    yj = x1 [k]
 
1109
    xy = sqrt (xj * xj + yj * yj)
 
1110
    xj = xj / xy
 
1111
    yj = yj / xy
 
1112
    if (xj * x1[j] + yj * y1 [j] < 0.0 ) :
 
1113
        xj = - xj
 
1114
        yj = - yj
 
1115
    # shortest axis last -- move perpendicular to nearer
 
1116
    xi = - y1 [j]
 
1117
    yi = x1 [j]
 
1118
    xy = sqrt (xi * xi + yi * yi)
 
1119
    xi = xi / xy
 
1120
    yi = yi / xy
 
1121
    if (xi *x1 [i] + yi * y1 [i] < 0.0) :
 
1122
        xi = - xi
 
1123
        yi = - yi
 
1124
 
 
1125
    # shortest axis label may need adjustment
 
1126
    d = 0.0013 * pts
 
1127
    if xyi < d :
 
1128
        # just center it in correct quadrant
 
1129
        jk = sign_ (xi * xj + yi * yj)
 
1130
        yi = sign_ (xi * xk + yi * yk)
 
1131
        xi = jk * xj + yi * xk
 
1132
        yi = jk * yj + yi * yk
 
1133
        jk = sqrt (xi * xi + yi * yi)
 
1134
        xi = xi / jk
 
1135
        yi = yi / jk
 
1136
    x = zeros (3, Float)
 
1137
    y = zeros (3, Float)
 
1138
    x [i] = xi
 
1139
    x [j] = xj
 
1140
    x [k] = xk
 
1141
    y [i] = yi
 
1142
    y [j] = yj
 
1143
    y [k] = yk
 
1144
    x = x * d
 
1145
    y = y * d
 
1146
    x = x + x1 + _gnomon_x
 
1147
    y = y + y1 + _gnomon_y
 
1148
    try :
 
1149
        dum = chr
 
1150
    except :
 
1151
        chr = ["X", "Y", "Z"]
 
1152
    gnomon_text_ (chr [i], x [i], y [i], pts, z1 [i] < 1.e-6)
 
1153
    gnomon_text_ (chr [j], x [j], y [j], pts, z1 [j] < 1.e-6)
 
1154
    gnomon_text_ (chr [k], x [k], y [k], pts, z1 [k] < 1.e-6)
 
1155
 
 
1156
try :
 
1157
    dummy = _gnomon_scale
 
1158
except :
 
1159
    _gnomon_scale = 30.       # axes lengths in points
 
1160
try :
 
1161
    dummy = _gnomon_x
 
1162
except :
 
1163
    _gnomon_x = 0.18          # gnomon origin in system 0 coordinates
 
1164
try :
 
1165
    dummy = _gnomon_y
 
1166
except :
 
1167
    _gnomon_y = 0.42
 
1168
 
 
1169
def gnomon_text_ (chr, x, y, pts, invert) :
 
1170
    # pts = 8, 10, 12, 14, 18, or 24
 
1171
    col = "fg"
 
1172
    if invert :
 
1173
        plsys (0)
 
1174
        plg (array ( [y, y]), array ( [x, x]), type = 1, width = 2.2 * pts,
 
1175
             color = col, marks = 0, legend = "")
 
1176
        plsys (1)
 
1177
        col = "bg"
 
1178
    plt (chr, x, y, justify = "CH", color = col, height = pts,
 
1179
         font = "helvetica", opaque = 0)
 
1180
 
 
1181
from movie import *
 
1182
 
 
1183
g_nframes = 30
 
1184
 
 
1185
def spin3 (nframes = 30, axis = array ([-1, 1, 0],  Float), tlimit = 60.,
 
1186
   dtmin = 0.0, bracket_time = array ([2., 2.],  Float), lims = None,
 
1187
   timing = 0, angle = 2. * pi) :
 
1188
 
 
1189
    """
 
1190
    spin3 ( ) or spin3 (nframes) os spin3 (nframes, axis)
 
1191
      Spin the current 3D display list about AXIS over NFRAMES.  Keywords
 
1192
      tlimit= the total time allowed for the movie in seconds (default 60),
 
1193
      dtmin= the minimum allowed interframe time in seconds (default 0.0),
 
1194
      bracket_time= (as for movie function in movie.i), timing = 1 if
 
1195
      you want timing measured and printed out, 0 if not.
 
1196
 
 
1197
      The default AXIS is [-1,1,0] and the default NFRAMES is 30.
 
1198
    SEE ALSO: rot3
 
1199
    """
 
1200
 
 
1201
    # Note on global variables (ZCM 2/21/97):
 
1202
    # I see no better way of sharing these between spin3 and _spin3
 
1203
    #   than making them global. Otherwise one would have to pass
 
1204
    #   them to movie, which would then send them as arguments to
 
1205
    #   _spin3. But because movie may call other routines, every one
 
1206
    #   of them would have to have these values, necessary or not.
 
1207
    #   So I have started their names with underscores; at least
 
1208
    #   this makes them inaccessible outside this module.
 
1209
    global _phi, _theta, _dtheta
 
1210
    global _g_nframes
 
1211
    _g_nframes = nframes
 
1212
    _dtheta = angle / (nframes - 1)
 
1213
    _theta = arccos (axis [2] / sqrt (axis [0] * axis [0] + axis [1] * axis [1] +
 
1214
                    axis [2] * axis [2]))
 
1215
    inc = axis [0] == axis [1] == 0
 
1216
    _phi = arctan2 (axis [1], axis [0] + inc)
 
1217
    orig = save3 ( )
 
1218
    movie (_spin3, tlimit, dtmin, bracket_time, lims, timing = 0)
 
1219
    restore3 (orig)
 
1220
 
 
1221
def _spin3 (i) :
 
1222
    global _g_nframes
 
1223
    global _phi, _theta, _dtheta
 
1224
    if i >= _g_nframes:
 
1225
        return 0
 
1226
    rot3 (za = -_phi)
 
1227
    rot3 (ya = -_theta, za = _dtheta)
 
1228
    rot3 (ya = _theta, za = _phi)
 
1229
    lims = draw3 ( )
 
1230
    limits (lims [0], lims [1], lims [2], lims [3])
 
1231
    return 1