Path: blob/master/src/sage/plot/plot3d/index_face_set.pyx
8815 views
"""1Graphics3D object that consists of a list of polygons, also used for2triangulations of other objects.34AUTHORS:5-- Robert Bradshaw (2007-08-26): initial version6-- Robert Bradshaw (2007-08-28): significant optimizations78TODO:9-- Smooth triangles10"""111213#*****************************************************************************14# Copyright (C) 2007 Robert Bradshaw <[email protected]>15#16# Distributed under the terms of the GNU General Public License (GPL)17#18# This code is distributed in the hope that it will be useful,19# but WITHOUT ANY WARRANTY; without even the implied warranty of20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU21# General Public License for more details.22#23# The full text of the GPL is available at:24#25# http://www.gnu.org/licenses/26#*****************************************************************************27282930include "sage/ext/stdsage.pxi"31include "sage/ext/interrupt.pxi"3233cdef extern from *:34void memset(void *, int, Py_ssize_t)35void memcpy(void * dest, void * src, Py_ssize_t n)36int sprintf_3d "sprintf" (char*, char*, double, double, double)37int sprintf_3i "sprintf" (char*, char*, int, int, int)38int sprintf_4i "sprintf" (char*, char*, int, int, int, int)39int sprintf_5i "sprintf" (char*, char*, int, int, int, int, int)40int sprintf_6i "sprintf" (char*, char*, int, int, int, int, int, int)41int sprintf_9d "sprintf" (char*, char*, double, double, double, double, double, double, double, double, double)4243# import the double infinity constant44cdef extern from "math.h":45enum: INFINITY4647from cpython.list cimport *48from cpython.string cimport *4950include "point_c.pxi"515253from math import sin, cos, sqrt54from random import randint5556from sage.rings.real_double import RDF5758from sage.matrix.constructor import matrix59from sage.modules.free_module_element import vector6061from sage.plot.plot3d.base import Graphics3dGroup6263from transform cimport Transformation64656667# --------------------------------------------------------------------68# Fast routines for generating string representations of the polygons.69# --------------------------------------------------------------------7071cdef inline format_tachyon_triangle(point_c P, point_c Q, point_c R):72cdef char ss[250]73# PyString_FromFormat doesn't do floats?74cdef Py_ssize_t r = sprintf_9d(ss,75"TRI V0 %g %g %g V1 %g %g %g V2 %g %g %g",76P.x, P.y, P.z,77Q.x, Q.y, Q.z,78R.x, R.y, R.z )79return PyString_FromStringAndSize(ss, r)8081cdef inline format_json_vertex(point_c P):82cdef char ss[100]83cdef Py_ssize_t r = sprintf_3d(ss, "{x:%g,y:%g,z:%g}", P.x, P.y, P.z)84return PyString_FromStringAndSize(ss, r)8586cdef inline format_json_face(face_c face):87return "[%s]" % ",".join([str(face.vertices[i]) for i from 0 <= i < face.n])8889cdef inline format_obj_vertex(point_c P):90cdef char ss[100]91# PyString_FromFormat doesn't do floats?92cdef Py_ssize_t r = sprintf_3d(ss, "v %g %g %g", P.x, P.y, P.z)93return PyString_FromStringAndSize(ss, r)9495cdef inline format_obj_face(face_c face, int off):96cdef char ss[100]97cdef Py_ssize_t r, i98if face.n == 3:99r = sprintf_3i(ss, "f %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off)100elif face.n == 4:101r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[0] + off, face.vertices[1] + off, face.vertices[2] + off, face.vertices[3] + off)102else:103return "f " + " ".join([str(face.vertices[i] + off) for i from 0 <= i < face.n])104# PyString_FromFormat is almost twice as slow105return PyString_FromStringAndSize(ss, r)106107cdef inline format_obj_face_back(face_c face, int off):108cdef char ss[100]109cdef Py_ssize_t r, i110if face.n == 3:111r = sprintf_3i(ss, "f %d %d %d", face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)112elif face.n == 4:113r = sprintf_4i(ss, "f %d %d %d %d", face.vertices[3] + off, face.vertices[2] + off, face.vertices[1] + off, face.vertices[0] + off)114else:115return "f " + " ".join([str(face.vertices[i] + off) for i from face.n > i >= 0])116return PyString_FromStringAndSize(ss, r)117118119cdef inline format_pmesh_vertex(point_c P):120cdef char ss[100]121# PyString_FromFormat doesn't do floats?122cdef Py_ssize_t r = sprintf_3d(ss, "%g %g %g", P.x, P.y, P.z)123return PyString_FromStringAndSize(ss, r)124125cdef inline format_pmesh_face(face_c face):126cdef char ss[100]127cdef Py_ssize_t r, i128if face.n == 3:129r = sprintf_5i(ss, "%d\n%d\n%d\n%d\n%d", face.n+1,130face.vertices[0],131face.vertices[1],132face.vertices[2],133face.vertices[0])134elif face.n == 4:135r = sprintf_6i(ss, "%d\n%d\n%d\n%d\n%d\n%d", face.n+1,136face.vertices[0],137face.vertices[1],138face.vertices[2],139face.vertices[3],140face.vertices[0])141else:142# Naive triangulation143all = []144for i from 1 <= i < face.n-1:145r = sprintf_4i(ss, "4\n%d\n%d\n%d\n%d", face.vertices[0],146face.vertices[i],147face.vertices[i+1],148face.vertices[0])149PyList_Append(all, PyString_FromStringAndSize(ss, r))150return "\n".join(all)151# PyString_FromFormat is almost twice as slow152return PyString_FromStringAndSize(ss, r)153154155156157cdef class IndexFaceSet(PrimitiveObject):158159"""160Graphics3D object that consists of a list of polygons, also used for161triangulations of other objects.162163Polygons (mostly triangles and quadrilaterals) are stored in the164c struct \code{face_c} (see transform.pyx). Rather than storing165the points directly for each polygon, each face consists a list166of pointers into a common list of points which are basically triples167of doubles in a \code{point_c}.168169Usually these objects are not created directly by users.170171EXAMPLES:172sage: from sage.plot.plot3d.index_face_set import IndexFaceSet173sage: S = IndexFaceSet([[(1,0,0),(0,1,0),(0,0,1)],[(1,0,0),(0,1,0),(0,0,0)]])174sage: S.face_list()175[[(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)]]176sage: S.vertex_list()177[(1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0)]178179sage: def make_face(n): return [(0,0,n),(0,1,n),(1,1,n),(1,0,n)]180sage: S = IndexFaceSet([make_face(n) for n in range(10)])181sage: S.show()182183sage: point_list = [(1,0,0),(0,1,0)] + [(0,0,n) for n in range(10)]184sage: face_list = [[0,1,n] for n in range(2,10)]185sage: S = IndexFaceSet(face_list, point_list, color='red')186sage: S.face_list()187[[(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)]]188sage: S.show()189"""190191def __cinit__(self):192self.vs = <point_c *>NULL193self.face_indices = <int *>NULL194self._faces = <face_c *>NULL195196197def __init__(self, faces, point_list=None, enclosed=False, **kwds):198PrimitiveObject.__init__(self, **kwds)199200self.enclosed = enclosed201202if point_list is None:203face_list = faces204faces = []205point_list = []206point_index = {}207for face in face_list:208iface = []209for p in face:210try:211ix = point_index[p]212except KeyError:213ix = len(point_list)214point_index[p] = ix215point_list.append(p)216iface.append(ix)217faces.append(iface)218219cdef Py_ssize_t i220cdef Py_ssize_t index_len = 0221for i from 0 <= i < len(faces):222index_len += len(faces[i])223224self.vcount = len(point_list)225self.fcount = len(faces)226self.icount = index_len227228self.realloc(self.vcount, self.fcount, index_len)229230for i from 0 <= i < self.vcount:231self.vs[i].x, self.vs[i].y, self.vs[i].z = point_list[i]232233cdef int cur_pt = 0234for i from 0 <= i < self.fcount:235self._faces[i].n = len(faces[i])236self._faces[i].vertices = &self.face_indices[cur_pt]237for ix in faces[i]:238self.face_indices[cur_pt] = ix239cur_pt += 1240241cdef realloc(self, vcount, fcount, icount):242r"""243Allocates memory for vertices, faces, and face indices. Can244only be called from Cython, so the doctests must be indirect.245246EXAMPLES::247248sage: var('x,y,z')249(x, y, z)250sage: G = implicit_plot3d(x^2+y^2+z^2 - 1, (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)251sage: G.triangulate() # indirect doctest252sage: len(G.face_list())25344254sage: len(G.vertex_list())255132256sage: G = implicit_plot3d(x^2+y^2+z^2 - 100, (x, -2, 2), (y, -2, 2), (z, -2, 2), plot_points=6)257sage: G.triangulate() # indirect doctest258sage: len(G.face_list())2590260sage: len(G.vertex_list())2610262"""263if self.vs == NULL:264self.vs = <point_c *> sage_malloc(sizeof(point_c) * vcount)265else:266self.vs = <point_c *> sage_realloc(self.vs, sizeof(point_c) * vcount)267if self._faces == NULL:268self._faces = <face_c *> sage_malloc(sizeof(face_c) * fcount)269else:270self._faces = <face_c *> sage_realloc(self._faces, sizeof(face_c) * fcount)271if self.face_indices == NULL:272self.face_indices = <int *> sage_malloc(sizeof(int) * icount)273else:274self.face_indices = <int *> sage_realloc(self.face_indices, sizeof(int) * icount)275if (self.vs == NULL and vcount > 0) or (self.face_indices == NULL and icount > 0) or (self._faces == NULL and fcount > 0):276raise MemoryError, "Out of memory allocating triangulation for %s" % type(self)277278def _clean_point_list(self):279# TODO: There is still wasted space where quadrilaterals were converted to triangles...280# but it's so little it's probably not worth bothering with281cdef int* point_map = <int *>sage_malloc(sizeof(int) * self.vcount)282if point_map == NULL:283raise MemoryError, "Out of memory cleaning up for %s" % type(self)284memset(point_map, 0, sizeof(int) * self.vcount) # TODO: sage_calloc285cdef Py_ssize_t i, j286cdef face_c *face287for i from 0 <= i < self.fcount:288face = &self._faces[i]289for j from 0 <= j < face.n:290point_map[face.vertices[j]] += 1291ix = 0292for i from 0 <= i < self.vcount:293if point_map[i] > 0:294point_map[i] = ix295self.vs[ix] = self.vs[i]296ix += 1297if ix != self.vcount:298for i from 0 <= i < self.fcount:299face = &self._faces[i]300for j from 0 <= j < face.n:301face.vertices[j] = point_map[face.vertices[j]]302self.realloc(ix, self.fcount, self.icount)303self.vcount = ix304sage_free(point_map)305306def _seperate_creases(self, threshold):307"""308Some rendering engines Gouraud shading, which is great for smooth309surfaces but looks bad if one actually has a polyhedron.310311INPUT:312threshold -- the minimum cosine of the angle between adjacent faces313a higher threshold separates more, all faces if >= 1,314no faces if <= -1315"""316cdef Py_ssize_t i, j, k317cdef face_c *face318cdef int v, count, total = 0319cdef int* point_counts = <int *>sage_malloc(sizeof(int) * (self.vcount * 2 + 1))320# For each vertex, get number of faces321if point_counts == NULL:322raise MemoryError, "Out of memory in _seperate_creases for %s" % type(self)323cdef int* running_point_counts = &point_counts[self.vcount]324memset(point_counts, 0, sizeof(int) * self.vcount)325for i from 0 <= i < self.fcount:326face = &self._faces[i]327total += face.n328for j from 0 <= j < face.n:329point_counts[face.vertices[j]] += 1330# Running used as index into face list331cdef int running = 0332cdef int max = 0333for i from 0 <= i < self.vcount:334running_point_counts[i] = running335running += point_counts[i]336if point_counts[i] > max:337max = point_counts[i]338running_point_counts[self.vcount] = running339# Create an array, indexed by running_point_counts[v], to the list of faces containing that vertex.340cdef face_c** point_faces = <face_c **>sage_malloc(sizeof(face_c*) * total)341if point_faces == NULL:342sage_free(point_counts)343raise MemoryError, "Out of memory in _seperate_creases for %s" % type(self)344sig_on()345memset(point_counts, 0, sizeof(int) * self.vcount)346for i from 0 <= i < self.fcount:347face = &self._faces[i]348for j from 0 <= j < face.n:349v = face.vertices[j]350point_faces[running_point_counts[v]+point_counts[v]] = face351point_counts[v] += 1352# Now, for each vertex, see if all faces are close enough,353# or if it is a crease.354cdef face_c** faces355cdef int start = 0356cdef bint any357# We compare against face 0, and if it's not flat enough we push it to the end.358# Then we come around again to compare everything that was put at the end, possibly359# pushing stuff to the end again (until no further changes are needed).360while start < self.vcount:361ix = self.vcount362# Find creases363for i from 0 <= i < self.vcount - start:364faces = &point_faces[running_point_counts[i]]365any = 0366for j from point_counts[i] > j >= 1:367if cos_face_angle(faces[0][0], faces[j][0], self.vs) < threshold:368any = 1369face = faces[j]370point_counts[i] -= 1371if j != point_counts[i]:372faces[j] = faces[point_counts[i]] # swap373faces[point_counts[i]] = face374if any:375ix += 1376# Reallocate room for vertices at end377if ix > self.vcount:378self.vs = <point_c *>sage_realloc(self.vs, sizeof(point_c) * ix)379if self.vs == NULL:380sage_free(point_counts)381sage_free(point_faces)382self.vcount = self.fcount = self.icount = 0 # so we don't get segfaults on bad points383sig_off()384raise MemoryError, "Out of memory in _seperate_creases for %s, CORRUPTED" % type(self)385ix = self.vcount386running = 0387for i from 0 <= i < self.vcount - start:388if point_counts[i] != running_point_counts[i+1] - running_point_counts[i]:389# We have a new vertex390self.vs[ix] = self.vs[i+start]391# Update the point_counts and point_faces arrays for the next time around.392count = running_point_counts[i+1] - running_point_counts[i] - point_counts[i]393faces = &point_faces[running]394for j from 0 <= j < count:395faces[j] = point_faces[running_point_counts[i] + point_counts[i] + j]396face = faces[j]397for k from 0 <= k < face.n:398if face.vertices[k] == i + start:399face.vertices[k] = ix400point_counts[ix-self.vcount] = count401running_point_counts[ix-self.vcount] = running402running += count403ix += 1404running_point_counts[ix-self.vcount] = running405start = self.vcount406self.vcount = ix407408sage_free(point_counts)409sage_free(point_faces)410sig_off()411412413414def _mem_stats(self):415return self.vcount, self.fcount, self.icount416417def __dealloc__(self):418if self.vs != NULL:419sage_free(self.vs)420if self._faces != NULL:421sage_free(self._faces)422if self.face_indices != NULL:423sage_free(self.face_indices)424425def is_enclosed(self):426"""427Whether or not it is necessary to render the back sides of the polygons428(assuming, of course, that they have the correct orientation).429430This is may be passed in on construction. It is also calculated431in ParametricSurface by verifying the opposite edges of the rendered432domain either line up or are pinched together.433434EXAMPLES:435sage: from sage.plot.plot3d.index_face_set import IndexFaceSet436sage: IndexFaceSet([[(0,0,1),(0,1,0),(1,0,0)]]).is_enclosed()437False438"""439return self.enclosed440441def index_faces(self):442cdef Py_ssize_t i, j443return [[self._faces[i].vertices[j] for j from 0 <= j < self._faces[i].n] for i from 0 <= i < self.fcount]444445def faces(self):446"""447An iterator over the faces.448449EXAMPLES:450sage: from sage.plot.plot3d.shapes import *451sage: S = Box(1,2,3)452sage: list(S.faces()) == S.face_list()453True454"""455return FaceIter(self)456457def face_list(self):458points = self.vertex_list()459cdef Py_ssize_t i, j460return [[points[self._faces[i].vertices[j]] for j from 0 <= j < self._faces[i].n] for i from 0 <= i < self.fcount]461462def edges(self):463return EdgeIter(self)464465def edge_list(self):466# For consistancy467return list(self.edges())468469def vertices(self):470"""471An iterator over the vertices.472473EXAMPLES:474sage: from sage.plot.plot3d.shapes import *475sage: S = Cone(1,1)476sage: list(S.vertices()) == S.vertex_list()477True478"""479return VertexIter(self)480481def vertex_list(self):482cdef Py_ssize_t i483return [(self.vs[i].x, self.vs[i].y, self.vs[i].z) for i from 0 <= i < self.vcount]484485def x3d_geometry(self):486cdef Py_ssize_t i487points = ",".join(["%s %s %s"%(self.vs[i].x, self.vs[i].y, self.vs[i].z) for i from 0 <= i < self.vcount])488coordIndex = ",-1,".join([",".join([str(self._faces[i].vertices[j])489for j from 0 <= j < self._faces[i].n])490for i from 0 <= i < self.fcount])491return """492<IndexedFaceSet coordIndex='%s,-1'>493<Coordinate point='%s'/>494</IndexedFaceSet>495"""%(coordIndex, points)496497def bounding_box(self):498r"""499Calculate the bounding box for the vertices in this object500(ignoring infinite or NaN coordinates).501502OUTPUT:503504a tuple ( (low_x, low_y, low_z), (high_x, high_y, high_z)),505which gives the coordinates of opposite corners of the506bounding box.507508EXAMPLE::509510sage: x,y=var('x,y')511sage: p=plot3d(sqrt(sin(x)*sin(y)), (x,0,2*pi),(y,0,2*pi))512sage: p.bounding_box()513((0.0, 0.0, -0.0), (6.283185307179586, 6.283185307179586, 0.9991889981715697))514"""515if self.vcount == 0:516return ((0,0,0),(0,0,0))517518cdef Py_ssize_t i519cdef point_c low520cdef point_c high521522low.x, low.y, low.z = INFINITY, INFINITY, INFINITY523high.x, high.y, high.z = -INFINITY, -INFINITY, -INFINITY524525for i in range(0,self.vcount):526point_c_update_finite_lower_bound(&low, self.vs[i])527point_c_update_finite_upper_bound(&high, self.vs[i])528return ((low.x, low.y, low.z), (high.x, high.y, high.z))529530def partition(self, f):531"""532Partition the faces of self based on a map $f: \RR^3 \leftarrow \ZZ$533applied to the center of each face.534"""535cdef Py_ssize_t i, j, ix, face_ix536cdef int part537cdef point_c P538cdef face_c *face, *new_face539cdef IndexFaceSet face_set540541cdef int *partition = <int *>sage_malloc(sizeof(int) * self.fcount)542543if partition == NULL:544raise MemoryError545part_counts = {}546for i from 0 <= i < self.fcount:547face = &self._faces[i]548P = self.vs[face.vertices[0]]549for j from 1 <= j < face.n:550point_c_add(&P, P, self.vs[face.vertices[j]])551point_c_mul(&P, P, 1.0/face.n)552partition[i] = part = f(P.x, P.y, P.z)553try:554count = part_counts[part]555except KeyError:556part_counts[part] = count = [0,0]557count[0] += 1558count[1] += face.n559all = {}560for part, count in part_counts.iteritems():561face_set = IndexFaceSet([])562face_set.realloc(self.vcount, count[0], count[1])563face_set.vcount = self.vcount564face_set.fcount = count[0]565face_set.icount = count[1]566memcpy(face_set.vs, self.vs, sizeof(point_c) * self.vcount)567face_ix = 0568ix = 0569for i from 0 <= i < self.fcount:570if partition[i] == part:571face = &self._faces[i]572new_face = &face_set._faces[face_ix]573new_face.n = face.n574new_face.vertices = &face_set.face_indices[ix]575for j from 0 <= j < face.n:576new_face.vertices[j] = face.vertices[j]577face_ix += 1578ix += face.n579face_set._clean_point_list()580all[part] = face_set581sage_free(partition)582return all583584def tachyon_repr(self, render_params):585"""586TESTS:587sage: from sage.plot.plot3d.shapes import *588sage: S = Cone(1,1)589sage: s = S.tachyon_repr(S.default_render_params())590"""591cdef Transformation transform = render_params.transform592lines = []593cdef point_c P, Q, R594cdef face_c face595cdef Py_ssize_t i, k596sig_on()597for i from 0 <= i < self.fcount:598face = self._faces[i]599if transform is not None:600transform.transform_point_c(&P, self.vs[face.vertices[0]])601transform.transform_point_c(&Q, self.vs[face.vertices[1]])602transform.transform_point_c(&R, self.vs[face.vertices[2]])603else:604P = self.vs[face.vertices[0]]605Q = self.vs[face.vertices[1]]606R = self.vs[face.vertices[2]]607PyList_Append(lines, format_tachyon_triangle(P, Q, R))608PyList_Append(lines, self.texture.id)609if face.n > 3:610for k from 3 <= k < face.n:611Q = R612if transform is not None:613transform.transform_point_c(&R, self.vs[face.vertices[k]])614else:615R = self.vs[face.vertices[k]]616PyList_Append(lines, format_tachyon_triangle(P, Q, R))617PyList_Append(lines, self.texture.id)618sig_off()619620return lines621622def json_repr(self, render_params):623"""624TESTS::625626sage: G = polygon([(0,0,1), (1,1,1), (2,0,1)])627sage: G.json_repr(G.default_render_params())628["{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'}"]629"""630631cdef Transformation transform = render_params.transform632cdef point_c res633634if transform is None:635vertices_str = "[%s]" % ",".join([format_json_vertex(self.vs[i])636for i from 0 <= i < self.vcount])637else:638vertices_str = "["639for i from 0 <= i < self.vcount:640transform.transform_point_c(&res, self.vs[i])641if i > 0:642vertices_str += ","643vertices_str += format_json_vertex(res)644vertices_str += "]"645faces_str = "[%s]" % ",".join([format_json_face(self._faces[i])646for i from 0 <= i < self.fcount])647color_str = "'%s'" % self.texture.hex_rgb()648return ["{vertices:%s,faces:%s,color:%s}" %649(vertices_str, faces_str, color_str)]650651def obj_repr(self, render_params):652"""653TESTS:654sage: from sage.plot.plot3d.shapes import *655sage: S = Cylinder(1,1)656sage: s = S.obj_repr(S.default_render_params())657"""658cdef Transformation transform = render_params.transform659cdef int off = render_params.obj_vertex_offset660cdef Py_ssize_t i661cdef point_c res662663sig_on()664if transform is None:665points = [format_obj_vertex(self.vs[i]) for i from 0 <= i < self.vcount]666else:667points = []668for i from 0 <= i < self.vcount:669transform.transform_point_c(&res, self.vs[i])670PyList_Append(points, format_obj_vertex(res))671672faces = [format_obj_face(self._faces[i], off) for i from 0 <= i < self.fcount]673if not self.enclosed:674back_faces = [format_obj_face_back(self._faces[i], off) for i from 0 <= i < self.fcount]675else:676back_faces = []677678render_params.obj_vertex_offset += self.vcount679sig_off()680681return ["g " + render_params.unique_name('obj'),682"usemtl " + self.texture.id,683points,684faces,685back_faces]686687def jmol_repr(self, render_params):688"""689TESTS:690sage: from sage.plot.plot3d.shapes import *691sage: S = Cylinder(1,1)692sage: S.show()693"""694cdef Transformation transform = render_params.transform695cdef Py_ssize_t i696cdef point_c res697698self._seperate_creases(render_params.crease_threshold)699700sig_on()701if transform is None:702points = [format_pmesh_vertex(self.vs[i]) for i from 0 <= i < self.vcount]703else:704points = []705for i from 0 <= i < self.vcount:706transform.transform_point_c(&res, self.vs[i])707PyList_Append(points, format_pmesh_vertex(res))708709faces = [format_pmesh_face(self._faces[i]) for i from 0 <= i < self.fcount]710711# If a face has more than 4 vertices, it gets chopped up in format_pmesh_face712cdef Py_ssize_t extra_faces = 0713for i from 0 <= i < self.fcount:714if self._faces[i].n >= 5:715extra_faces += self._faces[i].n-3716717sig_off()718719all = [str(self.vcount),720points,721str(self.fcount + extra_faces),722faces]723724from base import flatten_list725name = render_params.unique_name('obj')726all = flatten_list(all)727if render_params.output_archive:728filename = "%s.pmesh" % (name)729render_params.output_archive.writestr(filename, '\n'.join(all))730else:731filename = "%s-%s.pmesh" % (render_params.output_file, name)732f = open(filename, 'w')733for line in all:734f.write(line)735f.write('\n')736f.close()737738s = 'pmesh %s "%s"\n%s' % (name, filename, self.texture.jmol_str("pmesh"))739740# Turn on display of the mesh lines or dots?741if render_params.mesh:742s += '\npmesh %s mesh\n'%name743if render_params.dots:744s += '\npmesh %s dots\n'%name745return [s]746747def dual(self, **kwds):748749cdef point_c P750cdef face_c *face751cdef Py_ssize_t i, j, ix, ff752cdef IndexFaceSet dual = IndexFaceSet([], **kwds)753cdef int incoming, outgoing754755dual.realloc(self.fcount, self.vcount, self.icount)756757sig_on()758# is using dicts overly-heavy?759dual_faces = [{} for i from 0 <= i < self.vcount]760761for i from 0 <= i < self.fcount:762# Let the vertex be centered on the face according to a simple average763face = &self._faces[i]764dual.vs[i] = self.vs[face.vertices[0]]765for j from 1 <= j < face.n:766point_c_add(&dual.vs[i], dual.vs[i], self.vs[face.vertices[j]])767point_c_mul(&dual.vs[i], dual.vs[i], 1.0/face.n)768769# Now compute the new face770for j from 0 <= j < face.n:771if j == 0:772incoming = face.vertices[face.n-1]773else:774incoming = face.vertices[j-1]775if j == face.n-1:776outgoing = face.vertices[0]777else:778outgoing = face.vertices[j+1]779dd = dual_faces[face.vertices[j]]780dd[incoming] = i, outgoing781782i = 0783ix = 0784for dd in dual_faces:785face = &dual._faces[i]786face.n = len(dd)787if face.n == 0: # skip unused vertices788continue789face.vertices = &dual.face_indices[ix]790ff, next = dd.itervalues().next()791face.vertices[0] = ff792for j from 1 <= j < face.n:793ff, next = dd[next]794face.vertices[j] = ff795i += 1796ix += face.n797798dual.vcount = self.fcount799dual.fcount = i800dual.icount = ix801sig_off()802803return dual804805806def stickers(self, colors, width, hover):807"""808Returns a group of IndexFaceSets809810INPUT:811colors - list of colors/textures to use (in cyclic order)812width - offset perpendicular into the edge (to create a border)813may also be negative814hover - offset normal to the face (usually have to float above815the original surface so it shows, typically this value816is very small compared to the actual object817818OUTPUT:819Graphics3dGroup of stickers820821EXAMPLE:822sage: from sage.plot.plot3d.shapes import Box823sage: B = Box(.5,.4,.3, color='black')824sage: S = B.stickers(['red','yellow','blue'], 0.1, 0.05)825sage: S.show()826sage: (S+B).show()827"""828all = []829n = self.fcount; ct = len(colors)830for k in range(len(colors)):831if colors[k]:832all.append(self.sticker(range(k,n,ct), width, hover, texture=colors[k]))833return Graphics3dGroup(all)834835def sticker(self, face_list, width, hover, **kwds):836if not isinstance(face_list, (list, tuple)):837face_list = (face_list,)838faces = self.face_list()839all = []840for k in face_list:841all.append(sticker(faces[k], width, hover))842return IndexFaceSet(all, **kwds)843844845cdef class FaceIter:846def __init__(self, face_set):847self.set = face_set848self.i = 0849def __iter__(self):850return self851def __next__(self):852cdef point_c P853if self.i >= self.set.fcount:854raise StopIteration855else:856face = []857for j from 0 <= j < self.set._faces[self.i].n:858P = self.set.vs[self.set._faces[self.i].vertices[j]]859PyList_Append(face, (P.x, P.y, P.z))860self.i += 1861return face862863cdef class EdgeIter:864def __init__(self, face_set):865self.set = face_set866if not self.set.enclosed:867raise TypeError, "Must be closed to use the simple iterator."868self.i = 0869self.j = 0870self.seen = {}871def __iter__(self):872return self873def __next__(self):874cdef point_c P, Q875cdef face_c face = self.set._faces[self.i]876while self.i < self.set.fcount:877if self.j == face.n:878self.i += 1879self.j = 0880if self.i < self.set.fcount:881face = self.set._faces[self.i]882else:883if self.j == 0:884P = self.set.vs[face.vertices[face.n-1]]885else:886P = self.set.vs[face.vertices[self.j-1]]887Q = self.set.vs[face.vertices[self.j]]888self.j += 1889if self.set.enclosed: # Every edge appears exactly twice, once in each orientation.890if point_c_cmp(P, Q) < 0:891return ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))892else:893if point_c_cmp(P, Q) > 0:894P,Q = Q,P895edge = ((P.x, P.y, P.z), (Q.x, Q.y, Q.z))896if not edge in self.seen:897self.seen[edge] = edge898return edge899raise StopIteration900901902cdef class VertexIter:903def __init__(self, face_set):904self.set = face_set905self.i = 0906def __iter__(self):907return self908def __next__(self):909if self.i >= self.set.vcount:910raise StopIteration911else:912self.i += 1913return (self.set.vs[self.i-1].x, self.set.vs[self.i-1].y, self.set.vs[self.i-1].z)914915def len3d(v):916return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])917918def sticker(face, width, hover):919n = len(face)920edges = []921for i from 0 <= i < n:922edges.append(vector(RDF, [face[i-1][0] - face[i][0],923face[i-1][1] - face[i][1],924face[i-1][2] - face[i][2]]))925sticker = []926for i in range(n):927v = -edges[i]928w = edges[i-1]929N = v.cross_product(w)930lenN = len3d(N)931dv = v*(width*len3d(w)/lenN)932dw = w*(width*len3d(v)/lenN)933sticker.append(tuple(vector(RDF, face[i-1]) + dv + dw + N*(hover/lenN)))934return sticker935936937938