-
Notifications
You must be signed in to change notification settings - Fork 5
Meshes
Meshes are composed of 3 elements:
- Nodes: a Vec3Matrix of nodes, this is either 1 column of cartesian coordinates, or 3 columns of (coordinates, normals, xi) triples.
- Indices: a series of IndexMatrix objects representing topology indices, these are indexing functions into the node matrix or into field matrices
- Fields: a series of RealMatrix objects representing stored field data, these can have any number of columns but each row is the data element for an element in a topology
Meshes are either the data for scene objects or are the representation data derived from scene object meshes.
Typically mesh data is stored in a PyDataSet
object which handles creating and organizing the three classes of data.
A PyDataSet
has one matrix for nodes but can contain multiple matrices for indices or fields.
The index and field matrices define metadata tags which describe their intended use and relationships to one another.
The method meta()
is used to query and set metadata values, either manually for individual matrices or for whole data sets by the PyDataSet
object when it is constructed.
A PyDataSet
also can have metadata tags which describe properties, eg. whether it was cloned from another object.
The StdProps
enumeration lists the names of metadata tags in use by the framework.
For a scene object data set, the nodes matrix is a single column list of points in Cartesian space. This matrix can be constructed manually, for example to define the three vertices of a triangle:
vecs=Vec3Matrix('vecs',3)
vecs[0]=vec3(0,0,0)
vecs[1]=vec3(1,0,0)
vecs[2]=vec3(0.5,0,1)
An easier way of doing the above is with the helper function listToMatrix()
:
vecs=listToMatrix([vec3(0,0,0),vec3(1,0,0),vec3(0.5,0,1)],'vecs')
Index matrices define the topology of meshes by indexing those values in the nodes or fields matrices that define each element. Each row of an index matrix defines one element, each integer in that row indexes a value in another matrix that gives the data value for that control point of the element.
Indices which define topologies must also have a element type, which is the name of a member of ElemType
.
Each name in ElemType
is keyed to an ElemTypeDef
object which defines the basis function, geometry, and other properties for a geometric object.
This information is necessary to know how to interpolate values within the topology.
See Element-Type-Definition for in-depth discussion of the element type definition.
An index set for a single triangle, whose nodes are defined above, can be defined as follows:
tri=IndexMatrix('tri',ElemType._Tri1NL,1,3)
tri[0]=(0,1,2)
This defines a linear triangle topology with a single element whose indices are (0,1,2). Again more simply with the helper function:
tri=listToMatrix([(0,1,2)],'tri',ElemType._Tri1NL)
The element type for this index is Tri1NL, that is a linear triangle geometry type using the Nodal Lagrange basis function.
Using this type states that each row of tri
represents the indices in nodes
(and other matrices) for the control points for one triangle.
Since this is linear, there must be 3 indices for the 3 vertices of the triangle, therefore tri
must have 3 columns.
Other element types have different numbers of control points and the column count of index matrices using that type must match; eg. Tri2NL is a quadratic triangle type which must have 6 control points. Some element types have an varying number of control points which require extra arguments when the basis function is evaluated, typically index matrices using such types will store these arguments as metadata values.
For tri
to be recognized as a spatial topology its isspatial
metadata tag must be set to True
:
tri.meta(StdProps._isspatial,'True')
This tag can also be set to False
to make it explicit that a matrix is not a spatial topology, if no tag is set meta()
returns an empty string which should be interpreted as an ambiguity or the equivalent to False
depending on context.
The helper function isSpatialIndex()
checks this criteria. Typically this tag is set by the PyDataSet
object when it is constructed.
The ordering of index values for an element in a topology is significant since the ordering of the basis functions in an ElemTypeDef
is fixed.
Eidolon orders elements for all types except lines based on the spatial ordering of xi coordinate values for the indices, with vertices first.
For example, a Tri2NL element will have 6 indices, and a Quad2NL will have 9, arranged topologically as such:
2 2-8-3
|\ | |
4 5 5 6 7
| \ | |
0-3-1 0-4-1
The xi values for these control points are [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (0.5, 0.0), (0.0, 0.5), (0.5, 0.5)] for the Tri2NL and [(0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (0.5, 0.0), (0.0, 0.5), (0.5, 0.5), (1.0, 0.5), (0.5, 1.0)] for the Quad2NL.
Again refer to Element-Type-Definition for further details.
Fields store data values for other objects. Each row of a field matrix stores a data element for a spatial node, an element of a topology, or some other object that makes sense in another context. A simple field defining a single real value for each node in the triangle can be created as such using the helper function:
trif=listToMatrix([1.0,2.0,3.0],'trif')
How a field relates to other data, and thus how it can be rendered, is a complex issue stored as metadata tags.
A field must be associate with an index matrix defined as its spatial topology, naming it in the spatial
metadata value.
The field stores data for the object defined by the matrix, implying the field can be rendered as an augmentation of that topology's representation.
All fields must have this relationship, which by default when added to a PyDataSet
object will be set to the first spatial matrix present if not already set.
There are three different field types as well:
- per-node meaning each row corresponds 1-to-1 to each spatial node in the mesh
- per-element meaning each row corresponds 1-to-1 to each element in the associated topology (and so defines a value for every node of any element simultaneously)
- topology meaning the field has its own topology, each row corresponds 1-to-1 to each node in that topology, and the elements of that topology correspond 1-to-t to those in the associated spatial topology.
In the latter case a topology must be named in the topology
metadata value.
The field topology defines the relationship between field elements and the interpolation scheme in the same way a spatial matrix defines the topology in Cartesian space using nodes.
A Field topology must have a element type which is used for interpolation and must have the same number of elements as the spatial topology; typically the spatial and field topologies are the same matrix.
If a field is per-element then there is no topology since the relationship between values is obvious and implicit.
There is also no interpolation since the values in the field are applied to the whole of each element.
The metadata field elemdata
should be set to True
to indicate this is such a field, otherwise confusion can result when the framework attempts to determine what type of field it is.
If a field is per-node then there is also no topology since there is no relation between values in this case.
A per-node field can be associated with any spatial topology so this doesn't need to be set in metadata fields, although the field nodedata
should be set to True
.
The function isPerNodeField()
checks whether a field fulfils the criteria for this type.
The advantage of a per-node field is that multiple spatial topologies can use the same field simultaneously; the other two types can be used by only topology.
With these two matrices we can construct the actual scene object and add it to the scene:
ds=PyDataSet('TriDS',nodes,[tri],[trif])
obj=MeshSceneObject('Tri',ds)
mgr.addSceneObject(obj)
This constructs the PyDataSet
object with the node and triangle matrices, then constructs the scene object with the data and adds it to the scene. The user can now interact with the object named "Tri".
A PyDataSet
object can also be constructed directly from Python data structures. Internally listToMatrix()
will be used to construct matrices from lists of values or lists of tuples of values. The following is an equivalent way to create ds
:
vecs=[vec3(0,0,0),vec3(1,0,0),vec3(0.5,0,1)]
tri=('tri',ElemType._Tri1NL,[(0,1,2)])
trif=('trif',[(1.0,2.0,3.0)])
ds=PyDataSet('TriDS',vecs,[tri],[trif])
The tri
value is an example of an index set tuple, giving the name, type, and list of tuple values, but omits the optional boolean value stating whether the index is a spatial topology or not. The trif
value is an example of the field tuple, giving the name and data but omitting the names of the field's spatial and topology index sets.
If the metadata tags for the input matrices are not set, by default the constructor for PyDataSet
will attempt to assign sensible values, although this will cause confusion in more complex cases especially when fields are present. The metadata settings can be examined as such:
>>> print ds
Dataset TriDS
Nodes: 3 x 1 (72.00B)
Indices:
tri: 1 x 3 (12.00B)
Fields:
trif: 3 x 1 (24.00B)
Mem Total: 108.00B
>>> for i in ds.enumIndexSets():
... print i,'\n',i.meta()
...
IndexMatrix<tri>
isspatial = True
>>> for f in ds.enumDataFields():
... print f,'\n',f.meta()
...
RealMatrix<trif>
nodedata = True
A simple volume representation of this object can be constructed as such:
rep=obj.createRepr(ReprType._volume)
mgr.addSceneObjectRepr(rep)
The material Rainbow can be used in conjunction with the data field to apply a colouration to this triangle:
rep.applyMaterial('Rainbow',field='trif')
As a result we get on screen:
A mesh may be defined by multiple spatial topologies. This implies multiple index matrices refer to positions in one large node matrix. This implies certain problems for topology fields since they need to be associated with one spatial topology but also might need a separate field topology whose indices start from 0. Consider the following mesh:
trinodes=[vec3(0,0,0),vec3(1,0,0),vec3(0.5,0,1),]
triinds=[(0,1,2)]
trifield=[1,2,3]
quadnodes=[vec3(1.1,0,0),vec3(2.1,0,0),vec3(1.1,0,1),vec3(2.1,0,1)]
quadinds=[(4,5,6,7)]
quadfield=[1,2,3,4]
nodes=trinodes+quadnodes
inds=[('tris',ElemType._Tri1NL,triinds),('quads',ElemType._Quad1NL,quadinds)]
fields=[('trif',trifield,'tris'),('quadf',quadfield,'quads')]
ds=PyDataSet('MeshTest',nodes,inds,fields)
This defines one triangle next to one quad.
Each field is associated with the correct spatial topology, however neither has their own topology so the spatial topology is used instead.
This is fine for trifield
since triinds
indexes values from 0 to 2, however quadinds
indexes values from 4 to 7, which are the correct indices in nodes
but are not correct indices for quadfield
.
To solve the problem, quadfield
must have its own topology with the same shape as quadinds
but indexing from 0:
trinodes=[vec3(0,0,0),vec3(1,0,0),vec3(0.5,0,1),]
triinds=[(0,1,2)]
trifield=[1,2,3]
quadnodes=[vec3(1.1,0,0),vec3(2.1,0,0),vec3(1.1,0,1),vec3(2.1,0,1)]
quadinds=[(4,5,6,7)]
quadfield=[1,2,3,4]
quadfieldtopo=[(0,1,2,3)]
nodes=trinodes+quadnodes
inds=[('tris',ElemType._Tri1NL,triinds),('quads',ElemType._Quad1NL,quadinds),('quadft',ElemType._Quad1NL,quadfieldtopo,False)]
fields=[('trif',trifield,'tris'),('quadf',quadfield,'quads','quadft')]
ds=PyDataSet('MeshTest',nodes,inds,fields)
Note the added index matrix quadft
which is explicitly stated to not be a spatial topology, the field quadf
is defined with quads
as its spatial topology and quadft
as its field topology.