
AViD is an Application for Visualizing Dendrimers. In a nutshell, it is designed to render a "Ball and Stick" model for dendrimers (highly branched molecules with regular repeating patterns). The intention behind it is to fill the need of displaying and communicating simplified models of these structures to others.
As a matter of fact, I'm not a chemist. By chance, my wife who is now nearing the end of her doctorate degree in Chemistry needed some help visualizing her molecules. This is where the software developer - me - came into the picture.
So, I set about trying to come up with an application that would suit her needs and, while I was at it, learn something new along the way.

It contains the following capabilities:
Since this was my first time to use DirectX, I dealt with several conceptual hurdles that for me were not directly obvious. But before I begin, some definitions:
Now, let's begin on some of the sticking points when working with DirectX. As a note, the examples I'm providing are purely for understanding the concepts. Many of the objects being used should be cached if possible, as they are expensive to produce.
Initially, I believed that the difference between object space and world space was a formality to allow you to conceptually refer to something as either relative to the world's origin or the object's origin. In fact, it was far more important because of the structure of how objects are rendered in the DirectX world.
The primitive object types (points, lines, triangles) all inherently contain position information. So, when they are rendered, the world is expected to be centered back at the origin. This is done through the following:
// The following resets the world transform back to the origin.
device.Transform.World = Matrix.Identity;
After this, the primitive object can be rendered properly in the DirectX world. The Transform property of the device is how we are able to move where an object is rendered in our 3D world.
For more complex objects (those that use meshes), a mesh does not contain any positional information. When you render these objects, they require you to use the world transform to shift the object to its proper location.
// The following moves (or translates) the origin of the world to align
// with the origin of the object (i.e. its object space).
device.Transform.World = Matrix.Translation(new Vector3(12f, 14f, 8f));
Unfortunately, this doesn't solve all our issues. We will need the ability to rotate an object in space, and the very same world transform allows us to do so simply.
// Now, we take care of rotating the object prior to moving.
device.Transform.World = Matrix.RotationYawPitchRoll(0.1f, 0.5f, 0f)
* Matrix.Translation(new Vector3(12f, 14f, 8f));
Even the above, though, presents yet another problem, the rotation shown here will rotate about the object's origin. For many cases, the object's origin would not present the best point from which rotation would need to occur. Take for example, a cylinder. Rotating about the end is far different from rotating along a point in the middle of the length of the cylinder. So, for these cases, you must first translate the point of rotation, rotate, and then translate the rotated object to the correct position in world space.
// Rotate to move the point of rotation, rotate, and then translate the rotated object.
device.Transform.World = Matrix.Translation(new Vector3(0f, 0f, 5f)
* Matrix.RotationYawPitchRoll(0.1f, 0.5f, 0f)
* Matrix.Translation(new Vector3(12f, 14f, 8f));
As I was learning to develop in DirectX, I came across meshes as the defacto way of generating shapes. Unfortunately, because of my lack of knowledge within DirectX, I was unaware of the amount of overhead each mesh carries with it.
Because meshes carry only the blue-print of an object's shape, they are built to be reused. And should be. In a way, the separation of the various qualities of a 3D object (shape, location, material, etc.) actually allows for reuse because these components are not tied to each other. In the cases where it makes sense, they can quickly provide a performance boost to an application.
Pick rays are important if you want to do any sort of interaction with your 3D world. In my case, I wanted the users to be able to select an object via a right mouse click. Capturing the mouse's click event, I could obtain the position of the mouse, but I still needed to translate that to my 3D world space. To do so, I used something similar to the following code:
int intMouseX, intMouseY;
Vector3 vecFar, vecPickRayPosition, vecPickRayDirection;
// Make sure our position is within the proper range.
intMouseX = Math.Max(0, Math.Min(mouseLocation.X, this.m_device.Viewport.Width));
intMouseY = Math.Max(0, Math.Min(mouseLocation.Y, this.m_device.Viewport.Height));
// Create two vectors for finding the thing at which the user had clicked.
vecNear = new Vector3(intMouseX, intMouseY, 0);
vecFar = new Vector3(intMouseX, intMouseY, 1);
// Transform the vectors to world space.
vecNear.Unproject(this.m_device.Viewport, this.m_device.Transform.Projection,
this.m_device.Transform.View, Matrix.Identity);
vecFar.Unproject(this.m_device.Viewport, this.m_device.Transform.Projection,
this.m_device.Transform.View, Matrix.Identity);
vecPickRayPosition = vecNear;
vecPickRayDirection = Vector3.Subtract(vecFar, vecNear);
vecPickRayDirection.Normalize();
The near vector represents the origin of the pick ray, while the far vector provides the direction. You may have noticed that the Z component of the near and far vectors are 0 and 1, respectively. The reason for this is that this forces the near vector to the origin of the click (i.e., the camera position), and the far vector is forced to the farthest point in the world space upon unprojecting the two vectors.
While meshes do contain a method called Intersect which does most of the heavy lifting, it does not take into account how the mesh was rendered into the world. So, to overcome this, you must take the matrix used to translate the object into the world and invert it (i.e., Matrix.Invert(matrix)). With the matrix inverted, you apply it to the pick ray to orientate it correctly with the untransformed mesh.
// vecPosition = The position of the pick ray
// vecDirection = The direction of the pick ray
// m_mtxInverseWorldProjection = The inverted world transform matrix for the object.
// Calculate the position and direction.
vecPosition.TransformCoordinate(this.m_mtxInverseWorldProjection);
vecDirection.TransformNormal(this.m_mtxInverseWorldProjection);
vecDirection.Normalize();
bool blnIntersected = this.m_Mesh.Intersect(vecPosition, vecDirection);
In the above, TransformCoordinate is used when you need to maintain the length of the resulting vector. For the direction where I don't care about the length, I used TransformNormal, which maintains only the direction. After doing this, the mesh can now properly tell me whether or not the transformed pick ray did indeed intersect it.
Now that we are able to find if an object intersected with the pick ray, we have the issue of what if there are multiple objects. Because we are dealing with a 3D space, we certainly can have a situation where more than one object could fall within the pick ray. To overcome this, we must take into account the position of the pick ray and the object. Simply put, we find the closest object to the pick ray's origin:
// Find the distance from the pick ray position for the figure.
fltTempFigureDistance = Vector3.Subtract(vecPosition, tempFigure.Position).Length();
if ((figure == null) || (fltTempFigureDistance < fltFigureDistance))
{
figure = tempFigure;
fltFigureDistance = fltTempFigureDistance;
}
I went through several iterations of algorithms to determine all of the figures within an area.
Attempt #1 - My first idea was to iterate through every pixel in the area selected and perform a pick ray. While it was easy to code and correct, it was horribly inefficient. For anything over a 40x40 pixel box, it would hang up the UI for more than a few seconds.
Attempt #2 - My next idea was to use the concept of collision detection to generate a 3D box of the region and find which figures intersected with this box. After toiling over whether or not I was going to use axis-aligned bounding boxes[^] or an oriented bounding box, I realized I still had a major hurtle looming over me. How do I pick the objects closest to the camera? Indeed, even if I were to find which figures were in the area, I could not easily tell which were visible.
The Solution - So, after some time, it struck me. I don't need to reinvent trying to figure out who's visible or not. The graphics engine already has done that for me. I just needed some way of determining which figures were in the area based on an image. It was here that my childhood days of paint by numbers struck me as a viable solution. Each figure would receive a unique color. Keeping one color in reserve for the background, I would be able to map all colors from 0xFF000001 to 0xFFFFFFFF (16,777,214 colors in all) to a specific figure. It should be noted that the colors will be so close that you can't determine their uniqueness based on the human eye. Below is an example of this technique:
Though, I did have to overcome one final hurdle. To improve the quality of the rendering, I have multisampling turned on in my settings. Unfortunately, this causes the rendering to 'blur' the edges with background causing the unique colors assumption to be invalidated. To disable this, I had to use the following code to on the fly force the rendering to not use multisampling.
// Disable multisampling to make the edges hard.
this.m_device.RenderState.MultiSampleAntiAlias = false;
| You must Sign In to use this message board. | ||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||