Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/plot3d/index_face_set.pyx
4036 views
1
"""
2
Graphics3D object that consists of a list of polygons, also used for
3
triangulations of other objects.
4
5
AUTHORS:
6
-- Robert Bradshaw (2007-08-26): initial version
7
-- Robert Bradshaw (2007-08-28): significant optimizations
8
9
TODO:
10
-- Smooth triangles
11
"""
12
13
14
#*****************************************************************************
15
# Copyright (C) 2007 Robert Bradshaw <[email protected]>
16
#
17
# Distributed under the terms of the GNU General Public License (GPL)
18
#
19
# This code is distributed in the hope that it will be useful,
20
# but WITHOUT ANY WARRANTY; without even the implied warranty of
21
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22
# General Public License for more details.
23
#
24
# The full text of the GPL is available at:
25
#
26
# http://www.gnu.org/licenses/
27
#*****************************************************************************
28
29
30
31
include "../../ext/stdsage.pxi"
32
include "../../ext/interrupt.pxi"
33
34
cdef extern from *:
35
void memset(void *, int, Py_ssize_t)
36
void memcpy(void * dest, void * src, Py_ssize_t n)
37
int sprintf_3d "sprintf" (char*, char*, double, double, double)
38
int sprintf_3i "sprintf" (char*, char*, int, int, int)
39
int sprintf_4i "sprintf" (char*, char*, int, int, int, int)
40
int sprintf_5i "sprintf" (char*, char*, int, int, int, int, int)
41
int sprintf_6i "sprintf" (char*, char*, int, int, int, int, int, int)
42
int sprintf_9d "sprintf" (char*, char*, double, double, double, double, double, double, double, double, double)
43
44
# import the double infinity constant
45
cdef extern from "math.h":
46
enum: INFINITY
47
48
include "../../ext/python_list.pxi"
49
include "../../ext/python_string.pxi"
50
51
include "point_c.pxi"
52
53
54
from math import sin, cos, sqrt
55
from random import randint
56
57
from sage.rings.real_double import RDF
58
59
from sage.matrix.constructor import matrix
60
from sage.modules.free_module_element import vector
61
62
from sage.plot.plot3d.base import Graphics3dGroup
63
64
from transform cimport Transformation
65
66
67
68
# --------------------------------------------------------------------
69
# Fast routines for generating string representations of the polygons.
70
# --------------------------------------------------------------------
71
72
cdef inline format_tachyon_triangle(point_c P, point_c Q, point_c R):
73
cdef char ss[250]
74
# PyString_FromFormat doesn't do floats?
75
cdef Py_ssize_t r = sprintf_9d(ss,
76
"TRI V0 %g %g %g V1 %g %g %g V2 %g %g %g",
77
P.x, P.y, P.z,
78
Q.x, Q.y, Q.z,
79
R.x, R.y, R.z )
80
return PyString_FromStringAndSize(ss, r)
81
82
cdef inline format_json_vertex(point_c P):
83
cdef char ss[100]
84
cdef Py_ssize_t r = sprintf_3d(ss, "{x:%g,y:%g,z:%g}", P.x, P.y, P.z)
85
return PyString_FromStringAndSize(ss, r)
86
87
cdef inline format_json_face(face_c face):
88
return "[%s]" % ",".join([str(face.vertices[i]) for i from 0 <= i < face.n])
89
90
cdef inline format_obj_vertex(point_c P):
91
cdef char ss[100]
92
# PyString_FromFormat doesn't do floats?
93
cdef Py_ssize_t r = sprintf_3d(ss, "v %g %g %g", P.x, P.y, P.z)
94
return PyString_FromStringAndSize(ss, r)
95
96
cdef inline format_obj_face(face_c face, int off):
97
cdef char ss[100]
98
cdef Py_ssize_t r, i
99
if face.n == 3:
100
r = sprintf_3i(ss, "f %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off)
101
elif face.n == 4:
102
r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off, face.vertices[3] + off)
103
else:
104
return "f " + " ".join([str(face.vertices[i] + off) for i from 0 <= i < face.n])
105
# PyString_FromFormat is almost twice as slow
106
return PyString_FromStringAndSize(ss, r)
107
108
cdef inline format_obj_face_back(face_c face, int off):
109
cdef char ss[100]
110
cdef Py_ssize_t r, i
111
if face.n == 3:
112
r = sprintf_3i(ss, "f %d %d %d", face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)
113
elif face.n == 4:
114
r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[3] + off, face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)
115
else:
116
return "f " + " ".join([str(face.vertices[i] + off) for i from face.n > i >= 0])
117
return PyString_FromStringAndSize(ss, r)
118
119
120
cdef inline format_pmesh_vertex(point_c P):
121
cdef char ss[100]
122
# PyString_FromFormat doesn't do floats?
123
cdef Py_ssize_t r = sprintf_3d(ss, "%g %g %g", P.x, P.y, P.z)
124
return PyString_FromStringAndSize(ss, r)
125
126
cdef inline format_pmesh_face(face_c face):
127
cdef char ss[100]
128
cdef Py_ssize_t r, i
129
if face.n == 3:
130
r = sprintf_5i(ss, "%d\n%d\n%d\n%d\n%d", face.n+1,
131
face.vertices[0],
132
face.vertices[1],
133
face.vertices[2],
134
face.vertices[0])
135
elif face.n == 4:
136
r = sprintf_6i(ss, "%d\n%d\n%d\n%d\n%d\n%d", face.n+1,
137
face.vertices[0],
138
face.vertices[1],
139
face.vertices[2],
140
face.vertices[3],
141
face.vertices[0])
142
else:
143
# Naive triangulation
144
all = []
145
for i from 1 <= i < face.n-1:
146
r = sprintf_4i(ss, "4\n%d\n%d\n%d\n%d", face.vertices[0],
147
face.vertices[i],
148
face.vertices[i+1],
149
face.vertices[0])
150
PyList_Append(all, PyString_FromStringAndSize(ss, r))
151
return "\n".join(all)
152
# PyString_FromFormat is almost twice as slow
153
return PyString_FromStringAndSize(ss, r)
154
155
156
157
158
cdef class IndexFaceSet(PrimitiveObject):
159
160
"""
161
Graphics3D object that consists of a list of polygons, also used for
162
triangulations of other objects.
163
164
Polygons (mostly triangles and quadrilaterals) are stored in the
165
c struct \code{face_c} (see transform.pyx). Rather than storing
166
the points directly for each polygon, each face consists a list
167
of pointers into a common list of points which are basically triples
168
of doubles in a \code{point_c}.
169
170
Usually these objects are not created directly by users.
171
172
EXAMPLES:
173
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
174
sage: S = IndexFaceSet([[(1,0,0),(0,1,0),(0,0,1)],[(1,0,0),(0,1,0),(0,0,0)]])
175
sage: S.face_list()
176
[[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)]]
177
sage: S.vertex_list()
178
[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0)]
179
180
sage: def make_face(n): return [(0,0,n),(0,1,n),(1,1,n),(1,0,n)]
181
sage: S = IndexFaceSet([make_face(n) for n in range(10)])
182
sage: S.show()
183
184
sage: point_list = [(1,0,0),(0,1,0)] + [(0,0,n) for n in range(10)]
185
sage: face_list = [[0,1,n] for n in range(2,10)]
186
sage: S = IndexFaceSet(face_list, point_list, color='red')
187
sage: S.face_list()
188
[[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 0.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 2.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 3.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 4.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 5.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 6.0)], [(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 7.0)]]
189
sage: S.show()
190
"""
191
192
def __cinit__(self):
193
self.vs = <point_c *>NULL
194
self.face_indices = <int *>NULL
195
self._faces = <face_c *>NULL
196
197
198
def __init__(self, faces, point_list=None, enclosed=False, **kwds):
199
PrimitiveObject.__init__(self, **kwds)
200
201
self.enclosed = enclosed
202
203
if point_list is None:
204
face_list = faces
205
faces = []
206
point_list = []
207
point_index = {}
208
for face in face_list:
209
iface = []
210
for p in face:
211
try:
212
ix = point_index[p]
213
except KeyError:
214
ix = len(point_list)
215
point_index[p] = ix
216
point_list.append(p)
217
iface.append(ix)
218
faces.append(iface)
219
220
cdef Py_ssize_t i
221
cdef Py_ssize_t index_len = 0
222
for i from 0 <= i < len(faces):
223
index_len += len(faces[i])
224
225
self.vcount = len(point_list)
226
self.fcount = len(faces)
227
self.icount = index_len
228
229
self.realloc(self.vcount, self.fcount, index_len)
230
231
for i from 0 <= i < self.vcount:
232
self.vs[i].x, self.vs[i].y, self.vs[i].z = point_list[i]
233
234
cdef int cur_pt = 0
235
for i from 0 <= i < self.fcount:
236
self._faces[i].n = len(faces[i])
237
self._faces[i].vertices = &self.face_indices[cur_pt]
238
for ix in faces[i]:
239
self.face_indices[cur_pt] = ix
240
cur_pt += 1
241
242
cdef realloc(self, vcount, fcount, icount):
243
r"""
244
Allocates memory for vertices, faces, and face indices. Can
245
only be called from Cython, so the doctests must be indirect.
246
247
EXAMPLES::
248
249
sage: var('x,y,z')
250
(x, y, z)
251
sage: G = implicit_plot3d(x^2+y^2+z^2 - 1, (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)
252
sage: G.triangulate() # indirect doctest
253
sage: len(G.face_list())
254
44
255
sage: len(G.vertex_list())
256
132
257
sage: G = implicit_plot3d(x^2+y^2+z^2 - 100, (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)
258
sage: G.triangulate() # indirect doctest
259
sage: len(G.face_list())
260
0
261
sage: len(G.vertex_list())
262
0
263
"""
264
if self.vs == NULL:
265
self.vs = <point_c *> sage_malloc(sizeof(point_c) * vcount)
266
else:
267
self.vs = <point_c *> sage_realloc(self.vs, sizeof(point_c) * vcount)
268
if self._faces == NULL:
269
self._faces = <face_c *> sage_malloc(sizeof(face_c) * fcount)
270
else:
271
self._faces = <face_c *> sage_realloc(self._faces, sizeof(face_c) * fcount)
272
if self.face_indices == NULL:
273
self.face_indices = <int *> sage_malloc(sizeof(int) * icount)
274
else:
275
self.face_indices = <int *> sage_realloc(self.face_indices, sizeof(int) * icount)
276
if (self.vs == NULL and vcount > 0) or (self.face_indices == NULL and icount > 0) or (self._faces == NULL and fcount > 0):
277
raise MemoryError, "Out of memory allocating triangulation for %s" % type(self)
278
279
def _clean_point_list(self):
280
# TODO: There is still wasted space where quadrilaterals were converted to triangles...
281
# but it's so little it's probably not worth bothering with
282
cdef int* point_map = <int *>sage_malloc(sizeof(int) * self.vcount)
283
if point_map == NULL:
284
raise MemoryError, "Out of memory cleaning up for %s" % type(self)
285
memset(point_map, 0, sizeof(int) * self.vcount) # TODO: sage_calloc
286
cdef Py_ssize_t i, j
287
cdef face_c *face
288
for i from 0 <= i < self.fcount:
289
face = &self._faces[i]
290
for j from 0 <= j < face.n:
291
point_map[face.vertices[j]] += 1
292
ix = 0
293
for i from 0 <= i < self.vcount:
294
if point_map[i] > 0:
295
point_map[i] = ix
296
self.vs[ix] = self.vs[i]
297
ix += 1
298
if ix != self.vcount:
299
for i from 0 <= i < self.fcount:
300
face = &self._faces[i]
301
for j from 0 <= j < face.n:
302
face.vertices[j] = point_map[face.vertices[j]]
303
self.realloc(ix, self.fcount, self.icount)
304
self.vcount = ix
305
sage_free(point_map)
306
307
def _seperate_creases(self, threshold):
308
"""
309
Some rendering engines Gouraud shading, which is great for smooth
310
surfaces but looks bad if one actually has a polyhedron.
311
312
INPUT:
313
threshold -- the minimum cosine of the angle between adjacent faces
314
a higher threshold separates more, all faces if >= 1,
315
no faces if <= -1
316
"""
317
cdef Py_ssize_t i, j, k
318
cdef face_c *face
319
cdef int v, count, total = 0
320
cdef int* point_counts = <int *>sage_malloc(sizeof(int) * (self.vcount * 2 + 1))
321
# For each vertex, get number of faces
322
if point_counts == NULL:
323
raise MemoryError, "Out of memory in _seperate_creases for %s" % type(self)
324
cdef int* running_point_counts = &point_counts[self.vcount]
325
memset(point_counts, 0, sizeof(int) * self.vcount)
326
for i from 0 <= i < self.fcount:
327
face = &self._faces[i]
328
total += face.n
329
for j from 0 <= j < face.n:
330
point_counts[face.vertices[j]] += 1
331
# Running used as index into face list
332
cdef int running = 0
333
cdef int max = 0
334
for i from 0 <= i < self.vcount:
335
running_point_counts[i] = running
336
running += point_counts[i]
337
if point_counts[i] > max:
338
max = point_counts[i]
339
running_point_counts[self.vcount] = running
340
# Create an array, indexed by running_point_counts[v], to the list of faces containing that vertex.
341
cdef face_c** point_faces = <face_c **>sage_malloc(sizeof(face_c*) * total)
342
if point_faces == NULL:
343
sage_free(point_counts)
344
raise MemoryError, "Out of memory in _seperate_creases for %s" % type(self)
345
sig_on()
346
memset(point_counts, 0, sizeof(int) * self.vcount)
347
for i from 0 <= i < self.fcount:
348
face = &self._faces[i]
349
for j from 0 <= j < face.n:
350
v = face.vertices[j]
351
point_faces[running_point_counts[v]+point_counts[v]] = face
352
point_counts[v] += 1
353
# Now, for each vertex, see if all faces are close enough,
354
# or if it is a crease.
355
cdef face_c** faces
356
cdef int start = 0
357
cdef bint any
358
# We compare against face 0, and if it's not flat enough we push it to the end.
359
# Then we come around again to compare everything that was put at the end, possibly
360
# pushing stuff to the end again (until no further changes are needed).
361
while start < self.vcount:
362
ix = self.vcount
363
# Find creases
364
for i from 0 <= i < self.vcount - start:
365
faces = &point_faces[running_point_counts[i]]
366
any = 0
367
for j from point_counts[i] > j >= 1:
368
if cos_face_angle(faces[0][0], faces[j][0], self.vs) < threshold:
369
any = 1
370
face = faces[j]
371
point_counts[i] -= 1
372
if j != point_counts[i]:
373
faces[j] = faces[point_counts[i]] # swap
374
faces[point_counts[i]] = face
375
if any:
376
ix += 1
377
# Reallocate room for vertices at end
378
if ix > self.vcount:
379
self.vs = <point_c *>sage_realloc(self.vs, sizeof(point_c) * ix)
380
if self.vs == NULL:
381
sage_free(point_counts)
382
sage_free(point_faces)
383
self.vcount = self.fcount = self.icount = 0 # so we don't get segfaults on bad points
384
sig_off()
385
raise MemoryError, "Out of memory in _seperate_creases for %s, CORRUPTED" % type(self)
386
ix = self.vcount
387
running = 0
388
for i from 0 <= i < self.vcount - start:
389
if point_counts[i] != running_point_counts[i+1] - running_point_counts[i]:
390
# We have a new vertex
391
self.vs[ix] = self.vs[i+start]
392
# Update the point_counts and point_faces arrays for the next time around.
393
count = running_point_counts[i+1] - running_point_counts[i] - point_counts[i]
394
faces = &point_faces[running]
395
for j from 0 <= j < count:
396
faces[j] = point_faces[running_point_counts[i] + point_counts[i] + j]
397
face = faces[j]
398
for k from 0 <= k < face.n:
399
if face.vertices[k] == i + start:
400
face.vertices[k] = ix
401
point_counts[ix-self.vcount] = count
402
running_point_counts[ix-self.vcount] = running
403
running += count
404
ix += 1
405
running_point_counts[ix-self.vcount] = running
406
start = self.vcount
407
self.vcount = ix
408
409
sage_free(point_counts)
410
sage_free(point_faces)
411
sig_off()
412
413
414
415
def _mem_stats(self):
416
return self.vcount, self.fcount, self.icount
417
418
def __dealloc__(self):
419
if self.vs != NULL:
420
sage_free(self.vs)
421
if self._faces != NULL:
422
sage_free(self._faces)
423
if self.face_indices != NULL:
424
sage_free(self.face_indices)
425
426
def is_enclosed(self):
427
"""
428
Whether or not it is necessary to render the back sides of the polygons
429
(assuming, of course, that they have the correct orientation).
430
431
This is may be passed in on construction. It is also calculated
432
in ParametricSurface by verifying the opposite edges of the rendered
433
domain either line up or are pinched together.
434
435
EXAMPLES:
436
sage: from sage.plot.plot3d.index_face_set import IndexFaceSet
437
sage: IndexFaceSet([[(0,0,1),(0,1,0),(1,0,0)]]).is_enclosed()
438
False
439
"""
440
return self.enclosed
441
442
def index_faces(self):
443
cdef Py_ssize_t i, j
444
return [[self._faces[i].vertices[j] for j from 0 <= j < self._faces[i].n] for i from 0 <= i < self.fcount]
445
446
def faces(self):
447
"""
448
An iterator over the faces.
449
450
EXAMPLES:
451
sage: from sage.plot.plot3d.shapes import *
452
sage: S = Box(1,2,3)
453
sage: list(S.faces()) == S.face_list()
454
True
455
"""
456
return FaceIter(self)
457
458
def face_list(self):
459
points = self.vertex_list()
460
cdef Py_ssize_t i, j
461
return [[points[self._faces[i].vertices[j]] for j from 0 <= j < self._faces[i].n] for i from 0 <= i < self.fcount]
462
463
def edges(self):
464
return EdgeIter(self)
465
466
def edge_list(self):
467
# For consistancy
468
return list(self.edges())
469
470
def vertices(self):
471
"""
472
An iterator over the vertices.
473
474
EXAMPLES:
475
sage: from sage.plot.plot3d.shapes import *
476
sage: S = Cone(1,1)
477
sage: list(S.vertices()) == S.vertex_list()
478
True
479
"""
480
return VertexIter(self)
481
482
def vertex_list(self):
483
cdef Py_ssize_t i
484
return [(self.vs[i].x, self.vs[i].y, self.vs[i].z) for i from 0 <= i < self.vcount]
485
486
def x3d_geometry(self):
487
cdef Py_ssize_t i
488
points = ",".join(["%s %s %s"%(self.vs[i].x, self.vs[i].y, self.vs[i].z) for i from 0 <= i < self.vcount])
489
coordIndex = ",-1,".join([",".join([str(self._faces[i].vertices[j])
490
for j from 0 <= j < self._faces[i].n])
491
for i from 0 <= i < self.fcount])
492
return """
493
<IndexedFaceSet coordIndex='%s,-1'>
494
<Coordinate point='%s'/>
495
</IndexedFaceSet>
496
"""%(coordIndex, points)
497
498
def bounding_box(self):
499
r"""
500
Calculate the bounding box for the vertices in this object
501
(ignoring infinite or NaN coordinates).
502
503
OUTPUT:
504
505
a tuple ( (low_x, low_y, low_z), (high_x, high_y, high_z)),
506
which gives the coordinates of opposite corners of the
507
bounding box.
508
509
EXAMPLE::
510
511
sage: x,y=var('x,y')
512
sage: p=plot3d(sqrt(sin(x)*sin(y)), (x,0,2*pi),(y,0,2*pi))
513
sage: p.bounding_box()
514
((0.0, 0.0, -0.0), (6.283185307179586, 6.283185307179586, 0.9991889981715697))
515
"""
516
if self.vcount == 0:
517
return ((0,0,0),(0,0,0))
518
519
cdef Py_ssize_t i
520
cdef point_c low
521
cdef point_c high
522
523
low.x, low.y, low.z = INFINITY, INFINITY, INFINITY
524
high.x, high.y, high.z = -INFINITY, -INFINITY, -INFINITY
525
526
for i in range(0,self.vcount):
527
point_c_update_finite_lower_bound(&low, self.vs[i])
528
point_c_update_finite_upper_bound(&high, self.vs[i])
529
return ((low.x, low.y, low.z), (high.x, high.y, high.z))
530
531
def partition(self, f):
532
"""
533
Partition the faces of self based on a map $f: \RR^3 \leftarrow \ZZ$
534
applied to the center of each face.
535
"""
536
cdef Py_ssize_t i, j, ix, face_ix
537
cdef int part
538
cdef point_c P
539
cdef face_c *face, *new_face
540
cdef IndexFaceSet face_set
541
542
cdef int *partition = <int *>sage_malloc(sizeof(int) * self.fcount)
543
544
if partition == NULL:
545
raise MemoryError
546
part_counts = {}
547
for i from 0 <= i < self.fcount:
548
face = &self._faces[i]
549
P = self.vs[face.vertices[0]]
550
for j from 1 <= j < face.n:
551
point_c_add(&P, P, self.vs[face.vertices[j]])
552
point_c_mul(&P, P, 1.0/face.n)
553
partition[i] = part = f(P.x, P.y, P.z)
554
try:
555
count = part_counts[part]
556
except KeyError:
557
part_counts[part] = count = [0,0]
558
count[0] += 1
559
count[1] += face.n
560
all = {}
561
for part, count in part_counts.iteritems():
562
face_set = IndexFaceSet([])
563
face_set.realloc(self.vcount, count[0], count[1])
564
face_set.vcount = self.vcount
565
face_set.fcount = count[0]
566
face_set.icount = count[1]
567
memcpy(face_set.vs, self.vs, sizeof(point_c) * self.vcount)
568
face_ix = 0
569
ix = 0
570
for i from 0 <= i < self.fcount:
571
if partition[i] == part:
572
face = &self._faces[i]
573
new_face = &face_set._faces[face_ix]
574
new_face.n = face.n
575
new_face.vertices = &face_set.face_indices[ix]
576
for j from 0 <= j < face.n:
577
new_face.vertices[j] = face.vertices[j]
578
face_ix += 1
579
ix += face.n
580
face_set._clean_point_list()
581
all[part] = face_set
582
sage_free(partition)
583
return all
584
585
def tachyon_repr(self, render_params):
586
"""
587
TESTS:
588
sage: from sage.plot.plot3d.shapes import *
589
sage: S = Cone(1,1)
590
sage: s = S.tachyon_repr(S.default_render_params())
591
"""
592
cdef Transformation transform = render_params.transform
593
lines = []
594
cdef point_c P, Q, R
595
cdef face_c face
596
cdef Py_ssize_t i, k
597
sig_on()
598
for i from 0 <= i < self.fcount:
599
face = self._faces[i]
600
if transform is not None:
601
transform.transform_point_c(&P, self.vs[face.vertices[0]])
602
transform.transform_point_c(&Q, self.vs[face.vertices[1]])
603
transform.transform_point_c(&R, self.vs[face.vertices[2]])
604
else:
605
P = self.vs[face.vertices[0]]
606
Q = self.vs[face.vertices[1]]
607
R = self.vs[face.vertices[2]]
608
PyList_Append(lines, format_tachyon_triangle(P, Q, R))
609
PyList_Append(lines, self.texture.id)
610
if face.n > 3:
611
for k from 3 <= k < face.n:
612
Q = R
613
if transform is not None:
614
transform.transform_point_c(&R, self.vs[face.vertices[k]])
615
else:
616
R = self.vs[face.vertices[k]]
617
PyList_Append(lines, format_tachyon_triangle(P, Q, R))
618
PyList_Append(lines, self.texture.id)
619
sig_off()
620
621
return lines
622
623
def json_repr(self, render_params):
624
"""
625
TESTS::
626
627
sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])
628
sage: G.json_repr(G.default_render_params())
629
["{vertices:[{x:0,y:0,z:1},{x:1,y:1,z:1},{x:2,y:0,z:1}],faces:[[0,1,2]],color:'0000ff'}"]
630
"""
631
632
cdef Transformation transform = render_params.transform
633
cdef point_c res
634
635
if transform is None:
636
vertices_str = "[%s]" % ",".join([format_json_vertex(self.vs[i])
637
for i from 0 <= i < self.vcount])
638
else:
639
vertices_str = "["
640
for i from 0 <= i < self.vcount:
641
transform.transform_point_c(&res, self.vs[i])
642
if i > 0:
643
vertices_str += ","
644
vertices_str += format_json_vertex(res)
645
vertices_str += "]"
646
faces_str = "[%s]" % ",".join([format_json_face(self._faces[i])
647
for i from 0 <= i < self.fcount])
648
color_str = "'%s'" % self.texture.hex_rgb()
649
return ["{vertices:%s,faces:%s,color:%s}" %
650
(vertices_str, faces_str, color_str)]
651
652
def obj_repr(self, render_params):
653
"""
654
TESTS:
655
sage: from sage.plot.plot3d.shapes import *
656
sage: S = Cylinder(1,1)
657
sage: s = S.obj_repr(S.default_render_params())
658
"""
659
cdef Transformation transform = render_params.transform
660
cdef int off = render_params.obj_vertex_offset
661
cdef Py_ssize_t i
662
cdef point_c res
663
664
sig_on()
665
if transform is None:
666
points = [format_obj_vertex(self.vs[i]) for i from 0 <= i < self.vcount]
667
else:
668
points = []
669
for i from 0 <= i < self.vcount:
670
transform.transform_point_c(&res, self.vs[i])
671
PyList_Append(points, format_obj_vertex(res))
672
673
faces = [format_obj_face(self._faces[i], off) for i from 0 <= i < self.fcount]
674
if not self.enclosed:
675
back_faces = [format_obj_face_back(self._faces[i], off) for i from 0 <= i < self.fcount]
676
else:
677
back_faces = []
678
679
render_params.obj_vertex_offset += self.vcount
680
sig_off()
681
682
return ["g " + render_params.unique_name('obj'),
683
"usemtl " + self.texture.id,
684
points,
685
faces,
686
back_faces]
687
688
def jmol_repr(self, render_params):
689
"""
690
TESTS:
691
sage: from sage.plot.plot3d.shapes import *
692
sage: S = Cylinder(1,1)
693
sage: S.show()
694
"""
695
cdef Transformation transform = render_params.transform
696
cdef Py_ssize_t i
697
cdef point_c res
698
699
self._seperate_creases(render_params.crease_threshold)
700
701
sig_on()
702
if transform is None:
703
points = [format_pmesh_vertex(self.vs[i]) for i from 0 <= i < self.vcount]
704
else:
705
points = []
706
for i from 0 <= i < self.vcount:
707
transform.transform_point_c(&res, self.vs[i])
708
PyList_Append(points, format_pmesh_vertex(res))
709
710
faces = [format_pmesh_face(self._faces[i]) for i from 0 <= i < self.fcount]
711
712
# If a face has more than 4 vertices, it gets chopped up in format_pmesh_face
713
cdef Py_ssize_t extra_faces = 0
714
for i from 0 <= i < self.fcount:
715
if self._faces[i].n >= 5:
716
extra_faces += self._faces[i].n-3
717
718
sig_off()
719
720
all = [str(self.vcount),
721
points,
722
str(self.fcount + extra_faces),
723
faces]
724
725
from base import flatten_list
726
name = render_params.unique_name('obj')
727
all = flatten_list(all)
728
if render_params.output_archive:
729
filename = "%s.pmesh" % (name)
730
render_params.output_archive.writestr(filename, '\n'.join(all))
731
else:
732
filename = "%s-%s.pmesh" % (render_params.output_file, name)
733
f = open(filename, 'w')
734
for line in all:
735
f.write(line)
736
f.write('\n')
737
f.close()
738
739
s = 'pmesh %s "%s"\n%s' % (name, filename, self.texture.jmol_str("pmesh"))
740
741
# Turn on display of the mesh lines or dots?
742
if render_params.mesh:
743
s += '\npmesh %s mesh\n'%name
744
if render_params.dots:
745
s += '\npmesh %s dots\n'%name
746
return [s]
747
748
def dual(self, **kwds):
749
750
cdef point_c P
751
cdef face_c *face
752
cdef Py_ssize_t i, j, ix, ff
753
cdef IndexFaceSet dual = IndexFaceSet([], **kwds)
754
cdef int incoming, outgoing
755
756
dual.realloc(self.fcount, self.vcount, self.icount)
757
758
sig_on()
759
# is using dicts overly-heavy?
760
dual_faces = [{} for i from 0 <= i < self.vcount]
761
762
for i from 0 <= i < self.fcount:
763
# Let the vertex be centered on the face according to a simple average
764
face = &self._faces[i]
765
dual.vs[i] = self.vs[face.vertices[0]]
766
for j from 1 <= j < face.n:
767
point_c_add(&dual.vs[i], dual.vs[i], self.vs[face.vertices[j]])
768
point_c_mul(&dual.vs[i], dual.vs[i], 1.0/face.n)
769
770
# Now compute the new face
771
for j from 0 <= j < face.n:
772
if j == 0:
773
incoming = face.vertices[face.n-1]
774
else:
775
incoming = face.vertices[j-1]
776
if j == face.n-1:
777
outgoing = face.vertices[0]
778
else:
779
outgoing = face.vertices[j+1]
780
dd = dual_faces[face.vertices[j]]
781
dd[incoming] = i, outgoing
782
783
i = 0
784
ix = 0
785
for dd in dual_faces:
786
face = &dual._faces[i]
787
face.n = len(dd)
788
if face.n == 0: # skip unused vertices
789
continue
790
face.vertices = &dual.face_indices[ix]
791
ff, next = dd.itervalues().next()
792
face.vertices[0] = ff
793
for j from 1 <= j < face.n:
794
ff, next = dd[next]
795
face.vertices[j] = ff
796
i += 1
797
ix += face.n
798
799
dual.vcount = self.fcount
800
dual.fcount = i
801
dual.icount = ix
802
sig_off()
803
804
return dual
805
806
807
def stickers(self, colors, width, hover):
808
"""
809
Returns a group of IndexFaceSets
810
811
INPUT:
812
colors - list of colors/textures to use (in cyclic order)
813
width - offset perpendicular into the edge (to create a border)
814
may also be negative
815
hover - offset normal to the face (usually have to float above
816
the original surface so it shows, typically this value
817
is very small compared to the actual object
818
819
OUTPUT:
820
Graphics3dGroup of stickers
821
822
EXAMPLE:
823
sage: from sage.plot.plot3d.shapes import Box
824
sage: B = Box(.5,.4,.3, color='black')
825
sage: S = B.stickers(['red','yellow','blue'], 0.1, 0.05)
826
sage: S.show()
827
sage: (S+B).show()
828
"""
829
all = []
830
n = self.fcount; ct = len(colors)
831
for k in range(len(colors)):
832
if colors[k]:
833
all.append(self.sticker(range(k,n,ct), width, hover, texture=colors[k]))
834
return Graphics3dGroup(all)
835
836
def sticker(self, face_list, width, hover, **kwds):
837
if not isinstance(face_list, (list, tuple)):
838
face_list = (face_list,)
839
faces = self.face_list()
840
all = []
841
for k in face_list:
842
all.append(sticker(faces[k], width, hover))
843
return IndexFaceSet(all, **kwds)
844
845
846
cdef class FaceIter:
847
def __init__(self, face_set):
848
self.set = face_set
849
self.i = 0
850
def __iter__(self):
851
return self
852
def __next__(self):
853
cdef point_c P
854
if self.i >= self.set.fcount:
855
raise StopIteration
856
else:
857
face = []
858
for j from 0 <= j < self.set._faces[self.i].n:
859
P = self.set.vs[self.set._faces[self.i].vertices[j]]
860
PyList_Append(face, (P.x, P.y, P.z))
861
self.i += 1
862
return face
863
864
cdef class EdgeIter:
865
def __init__(self, face_set):
866
self.set = face_set
867
if not self.set.enclosed:
868
raise TypeError, "Must be closed to use the simple iterator."
869
self.i = 0
870
self.j = 0
871
self.seen = {}
872
def __iter__(self):
873
return self
874
def __next__(self):
875
cdef point_c P, Q
876
cdef face_c face = self.set._faces[self.i]
877
while self.i < self.set.fcount:
878
if self.j == face.n:
879
self.i += 1
880
self.j = 0
881
if self.i < self.set.fcount:
882
face = self.set._faces[self.i]
883
else:
884
if self.j == 0:
885
P = self.set.vs[face.vertices[face.n-1]]
886
else:
887
P = self.set.vs[face.vertices[self.j-1]]
888
Q = self.set.vs[face.vertices[self.j]]
889
self.j += 1
890
if self.set.enclosed: # Every edge appears exactly twice, once in each orientation.
891
if point_c_cmp(P, Q) < 0:
892
return ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))
893
else:
894
if point_c_cmp(P, Q) > 0:
895
P,Q = Q,P
896
edge = ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))
897
if not edge in self.seen:
898
self.seen[edge] = edge
899
return edge
900
raise StopIteration
901
902
903
cdef class VertexIter:
904
def __init__(self, face_set):
905
self.set = face_set
906
self.i = 0
907
def __iter__(self):
908
return self
909
def __next__(self):
910
if self.i >= self.set.vcount:
911
raise StopIteration
912
else:
913
self.i += 1
914
return (self.set.vs[self.i-1].x, self.set.vs[self.i-1].y, self.set.vs[self.i-1].z)
915
916
def len3d(v):
917
return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
918
919
def sticker(face, width, hover):
920
n = len(face)
921
edges = []
922
for i from 0 <= i < n:
923
edges.append(vector(RDF, [face[i-1][0] - face[i][0],
924
face[i-1][1] - face[i][1],
925
face[i-1][2] - face[i][2]]))
926
sticker = []
927
for i in range(n):
928
v = -edges[i]
929
w = edges[i-1]
930
N = v.cross_product(w)
931
lenN = len3d(N)
932
dv = v*(width*len3d(w)/lenN)
933
dw = w*(width*len3d(v)/lenN)
934
sticker.append(tuple(vector(RDF, face[i-1]) + dv + dw + N*(hover/lenN)))
935
return sticker
936
937
938