iMSTK
Interactive Medical Simulation Toolkit
Docs/Geometry.md
1 # Geometry
2 
3 ## Base Geometry
4 
5 Geometry base class implements pre and post states of geometry as well as two types of transforms.
6 
7 - `ApplyToData`: One transform directly applies to both pre and post geometry immediately.
8 - `ConcatenateToTransform`: The other transform applies to pre geometry in order to produce post geometry.
9 
10 First see how to get both post and pre vertices of a PointSet. A similar convention is used among the other geometries (even post and pre values of primitive shapes)
11 
12 ```cpp
13 std::shared_ptr<PointSet> myPoints = MeshIO::read<PointSet>("file path here");
14 auto preVerticesPtr = myPoints->getVertexPositions(Geometry::DataType::PostTransform);
15 auto postVerticesPtr = myPoints->getVertexPositions(Geometry::DataType::PreTransform);
16 ```
17 
18 With any geometry, apply transforms to either pre and/or post.
19 
20 ```cpp
21 std::shared_ptr<SurfaceMesh> mySurfaceMesh = MeshIO::read<SurfaceMesh>("file path here");
22 
23 // Apply immediately to data, transforming both post and pre vertices
24 mySurfaceMesh->translate(Vec3d(1.0, 0.0, 0.0), Geometry::TransformType::ApplyToData);
25 mySurfaceMesh->rotate(Vec3d(0.0, 1.0, 0.0), 1.57, Geometry::TransformType::ApplyToData);
26 // Also available are Quatd (quaternion) and Mat3d(3x3 matrix) rotations
27 mySurfaceMesh->scale(Vec3d(2.0, 2.0, 2.0), Geometry::TransformType::ApplyToData);
28 mySurfaceMesh->transform(myTransform, Geometry::TransformType::ApplyToData);
29 
30 // Concat the transform, when get is later called it will update (lazy update)
31 mySurfaceMesh->translate(Vec3d(1.0, 0.0, 0.0), Geometry::TransformType::ConcatenateToTransform);
32 mySurfaceMesh->rotate(Vec3d(0.0, 1.0, 0.0), 1.57, Geometry::TransformType::ConcatenateToTransform);
33 mySurfaceMesh->scale(Vec3d(2.0, 2.0, 2.0), Geometry::TransformType::ConcatenateToTransform);
34 mySurfaceMesh->transform(myTransform, Geometry::TransformType::ConcatenateToTransform);
35 ```
36 
37 The advantage of the `ConcatenateToTransform` is transforms are not applied four times and the initial vertices are preserved.
38 
39 ## Analytical & Implicit
40 
41 Analytical geometries are basic primitives such as sphere, box, capsule, plane, etc. They are given by parameters. All analytical geometries are also implicit. Implicit geometries provide a function value for all points in space (usually signed or unsigned distance).
42 
43 Similarly they have post and pre geometry as well as a transform, but some transforms may not apply fully. For instance, a sphere cannot anisotropically scale or shear.
44 
45 ```cpp
46 auto sphere = std::make_shared<Sphere>(Vec3d(0.0, 3.0, 0.0), 2.0); // center, radius
47 sphere->translate(Vec3d(0.0, 1.0, 0.0)); // Default is ConcatenateToTransform
48 Vec3d newPos = sphere->getPosition(Geometry::DataType::PostTransform);
49 // newPos is (0.0, 4.0, 0.0)
50 
51 double signedDistance = sphere->getFunctionValue(Vec3d(0.0, 0.0, 0.0));
52 // signedDistance is 2.0
53 signedDistance = sphere->getFunctionValue(Vec3d(0.0, 2.0, 0.0));
54 // signedDistance is 0.0
55 signedDistance = sphere->getFunctionValue(Vec3d(0.0, 4.0, 0.0));
56 // signedDistance is -2.0
57 ```
58 
59 ## PointSet
60 
61 PointSets represent a collection of points. A set of vertices. They can be created like so:
62 
63 ```cpp
64 auto pointSet = std::make_shared<PointSet>();
65 
66 auto verticesPtr = std::make_shared<VecDataArray<double, 3>>(2);
67 VecDataArray<double, 3>& vertices = *verticesPtr;
68 vertices[0] = Vec3d(0.0, 1.0, 0.0);
69 vertices[1] = Vec3d(0.0, 2.0, 2.0);
70 
71 pointSet->initialize(verticesPtr);
72 ```
73 
74 PointSet can also come with named per vertex "attributes". These are abstract arrays. The abstract arrays could be single per vertex scalars, 3d vectors (normals, displacements, velocities), matrices, 2d texture coordinates, etc. There are then a set of designated/labeled/active attributes for normals, texture coordinates, tangents, and scalar. This gives context so other codes can just ask the PointSet what the active normals, texture coordinates, etc are.
75 
76 Add and access an attribute to the same geometry:
77 
78 ```cpp
79 auto scalarsPtr = std::make_shared<DataArray<double>>(2);
80 DataArray<double>& scalars = *scalarsPtr;
81 scalars[0] = 5.0;
82 scalars[1] = 14.0;
83 
84 pointSet->setVertexAttribute("myScalars", scalarsPtr);
85 
86 // Can later access by name, or designate the active scalars to provide context
87 pointSet->setVertexScalars("myScalars");
88 ```
89 
90 ## LineMesh, SurfaceMesh, TetrahedralMesh, & HexahedralMesh
91 
92 Subclassing PointSet there are `LineMesh`, `SurfaceMesh`, `TetrahedralMesh`, & `HexahedralMesh`. These are all mostly the same stemming off a AbstractCellMesh base class giving indices/order of the vertices per cell. For instance, a triangle has 3 integer indices that specify the location of the 3 vertices involved in the triangle (from the vertex array). Similarly a LineMesh has 2, TetrahedralMesh has 4, HexahedralMesh has 8.
93 
94 ```cpp
95 auto verticesPtr = std::make_shared<VecDataArray<double, 3>>(3);
96 VecDataArray<double, 3>& vertices = *verticesPtr;
97 vertices[0] = Vec3d(0.0, 1.0, 0.0);
98 vertices[1] = Vec3d(-1.0, 0.0, 0.0);
99 vertices[2] = Vec3d(0.0, 0.0, 1.0);
100 
101 auto cellsPtr = std::make_shared<VecDataArray<int, 3>>(1);
102 VecDataArray<int, 3>& cells = *cellsPtr;
103 cells[0] = Vec3i(0, 1, 2);
104 
105 // Create SurfaceMesh with given buffers
106 SurfaceMesh surfMesh;
107 surfMesh.initialize(verticesPtr, cellsPtr);
108 ```
109 
110 Similar to vertex data you may have per cell attributes. That is a normal, scalar, matrix, etc specified per cell given a name and labeled/designated as active.
111 
112 Other notable functions:
113 
114 - SurfaceMesh::computeVertexNormals
115 - SurfaceMesh::computeTriangleNormals
116 - SurfaceMesh::computeVertexTangents
117 - SurfaceMesh::computeTriangleTangents
118 - TetrahedralMesh::extractSurfaceMesh
119 
120 <p align="center">
121  <img src="media/Heart.png" alt="Heart Tetrahedral Mesh Cut in Half" width="600"/>
122 </p>
123 
124 ### Shared Buffers
125 
126 It is often useful to share the vertex buffer or other large buffers between meshes.
127 
128 ```cpp
129 auto verticesPtr = std::make_shared<VecDataArray<double, 3>>(3);
130 VecDataArray<double, 3>& vertices = *verticesPtr;
131 vertices[0] = Vec3d(0.0, 1.0, 0.0);
132 vertices[1] = Vec3d(-1.0, 0.0, 0.0);
133 vertices[2] = Vec3d(0.0, 0.0, 1.0);
134 vertices[3] = Vec3d(0.0, 0.0, -1.0);
135 
136 auto cellsPtr1 = std::make_shared<VecDataArray<int, 3>>(1);
137 VecDataArray<int, 3>& cells1 = *cellsPtr1;
138 cells1[0] = Vec3i(0, 1, 2);
139 
140 auto cellsPtr2 = std::make_shared<VecDataArray<int, 4>>(1);
141 VecDataArray<int, 4>& cells2 = *cellsPtr2;
142 cells2[0] = Vec4i(3, 1, 2, 0);
143 
144 // Create two meshes with differing index buffers
145 SurfaceMesh surfMesh;
146 surfMesh.initialize(verticesPtr, cellsPtr1);
147 
148 TetrahedralMesh tetMesh;
149 tetMesh.initialize(verticesPtr, cellsPtr2);
150 ```
151 
152 ## ImageData
153 
154 An ImageData has points that are not explicitly computed (unless asked for), given implicity via origin, spacing, and dimensions of an image. It supports 3d and 2d images.
155 
156 ```cpp
157 auto imageData = std::make_shared<ImageData>();
158 myImage->allocate(
159  IMSTK_DOUBLE, // Scalar Type
160  1, // Number of Components
161  Vec3i(10, 10, 10), // XYZ Dimensions
162  Vec3d(1.0, 1.0, 1.0), // XYZ Spacing
163  Vec3d(0.0, 0.0, 0.0)); // XYZ origin
164 
165 // Another type of allocation, 3 tuple, 2d
166 myImage->allocate(
167  IMSTK_DOUBLE, // Scalar Type
168  3, // Number of Components
169  Vec3i(10, 10, 1), // XYZ Dimensions
170  Vec3d(1.0, 1.0, 1.0), // XYZ Spacing
171  Vec3d(0.0, 0.0, 0.0)); // XYZ origin
172 ```
173 
174 How to access:
175 
176 ```cpp
177 std::shared_ptr<AbstractDataArray> myAbstractScalars = myImage->getScalars();
178 auto myScalarArray = std::dynamic_pointer_cast<DataArray<double>>(imageData->getScalars());
179 
180 std::shared_ptr<AbstractDataArray> myAbstractScalars = myImage->getScalars();
181 auto myScalarArray = std::dynamic_pointer_cast<VecDataArray<double, 3>>(imageData->getScalars());
182 ```
183 
184 <p align="center">
185  <img src="media/vhp.png" alt="Femur Signed Distance Function" width="600"/>
186 </p>
187 
188 ImageData does not support transformations due to sampling performance purposes. You may change the origin for translational offsets and change the spacing for scaling.
189 
190 Other notable functions:
191 
192 - ImageData::computePoints
193  - Computes the points of the underlying PointSet for the regular grid
194 - ImageData::cast
195  - Returns same image of differing/specified type
196 - ImageData::getScalarIndex
197  - Index into the 1d array for 3d coordinates.
198 
199 ## SignedDistanceField
200 
201 SignedDistanceField's are types of ImplicitGeometries. They provide signed distances via trilinear interpolation of a field of values. That is, given a uniform (or non-uniform) field of values it finds the the voxel (8 nearest neighbors) and linearly interpolates the value to give you the value at the point.
202 
203 <p align="center">
204  <img src="media/FemurSDF.png" alt="Femur Signed Distance Function" width="600"/>
205 </p>
206 
207 <p align="center">
208  <img src="media/sdf.png" alt="Signed Distance Function" width="600"/>
209 </p>
210 
211 ```cpp
212 // This shows how to use a filter to compute the SDF of a SurfaceMesh (slow)
213 auto myWatertightMesh = MeshIO::read<SurfaceMesh>("file path here");
214 
215 SurfaceMeshDistanceTransform computeSdf;
216 computeSdf.setInputMesh(myWatertightMesh);
217 computeSdf.setDimensions(100, 100, 100);
218 computeSdf.update();
219 
220 // Construct SDFs with images
221 auto sdf = std::make_shared<SignedDistanceField>(computeSdf.getOutputImage());
222 
223 // Access with
224 double signedDist = sdf->getFunctionValue(position);
225 ```
226 
227 You may set a scale to the SDF as well which is multiplied with the return value. One should be careful not to anistropically scale an SDF.
228 
229 The pro to SDFs is that they give some of the fastest collision available just by sampling the 3d image. (normalized gradient = normal, value = penetration depth). Additionally one can inverse transform into them for dynamic objects. The downside is memory consumption and topology changes.
230 
231 ## CompositeImplicitGeometry
232 
233 CompositeImplicitGeometries allow you to do boolean operations between implicit geometries dynamically. That is, it does not precompute anything but when asked what the signed distance is given a point, it does min & max operations to compute unions, intersections, or differences on the fly.
234 
235 For example, you can punch and move a hole or grow a capsule in a signed distance field in real time, without any added cost. You are not updating the field. Simply doing some mins/maxes when getting the SDF value. You can also rasterize this back into a field.
236 
237 ```cpp
238 // Subtract the sphere from the plane to make a crater
239 auto planeGeom = std::make_shared<Plane>();
240 planeGeom->setWidth(40.0);
241 auto sphere = std::make_shared<Sphere>();
242 sphereGeom->setRadius(25.0);
243 sphereGeom->setPosition(0.0, 10.0, 0.0);
244 
245 auto compGeom = std::make_shared<CompositeImplicitGeometry>();
246 compGeom->addImplicitGeometry(planeGeom, CompositeImplicitGeometry::GeometryBoolType::Union);
247 compGeom->addImplicitGeometry(sphereGeom, CompositeImplicitGeometry::GeometryBoolType::Difference);
248 ```
249 
250 <p align="center">
251  <img src="media/compositeGeo.png" alt="Composite Implicit Geometry" width="600"/>
252 </p>