<html><head><style type="text/css"><!-- DIV {margin:0px;} --></style></head><body><div style="font-family:arial,helvetica,sans-serif;font-size:10pt">Hi to all,<br><br>I have the time to address the 3 ancients bugs that affect the mapper vtkOpenGLGPUVolumeRayCastMapper, discovered and signaled on mantis bugtracer by me on February 2009 (bugs never solved). These bugs make the mapper quite unusable and this is a serious issue because that mapper is the best compromise between quality and speed for volume rendering.<br>OK, we have 3 different issue. Let me talk about the three.<br><br>1) Plane clipping bugs (to better figure out the problem, here a video of the problem: <a href="http://www.youtube.com/watch?v=NjUTTA86yTM&feature=related">http://www.youtube.com/watch?v=NjUTTA86yTM&feature=related</a>). I have solved this bug. The problem is in the method: vtkOpenGLGPUVolumeRayCastMapper::RenderClippedBoundingBox. That method check the correct winding
order of the polygon and fails its job. Actually the algorithm check the relationship between the polygon plane and the SPATIAL center of the volume. Note that the volume is always convex. The problem is that you haven't to use spatial center, but center of mass. If you use spatial center (center of bounding box) you wrong because it not always lie inside the convex polyhedra: it could lie on a polygon boundary plane. Let me show this in 2D:<br><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> __________</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;">| /</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /<br>| /<br>| x<br></span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /<br></span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">|/<br><br></span>If you have a triangle, the spatial center lie on an edge and due to floating point inaccuracy this point could lie
in front or behind the plane. When point lie behind the plane the algorithm flip the vertex order resulting in back-faced polygon. The solution is to compute center of mass (average of vertices):<br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br></span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> __________</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;">
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;">
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;">
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /<br>
| /<br>
| x /<br>
</span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /<br>
</span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br>
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br>
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">| /</span><br>
<span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">|/<br>
</span><br>The center of mass always lie inside the convex polyhedra and this solve the problem.<br><br>Here my fix (in vtkOpenGLGPUVolumeRayCastMapper::RenderClippedBoundingBox):<br><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double center[3] = {0,0,0};</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double min[3] = {VTK_DOUBLE_MAX, VTK_DOUBLE_MAX, VTK_DOUBLE_MAX};</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double max[3] = {VTK_DOUBLE_MIN, VTK_DOUBLE_MIN, VTK_DOUBLE_MIN};</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"> // First compute center point</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> npts = points->GetNumberOfPoints();</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> for ( i = 0; i < npts; i++ )</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> {</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double pt[3];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"> points->GetPoint( i, pt );</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> for ( j = 0; j < 3; j++ )</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> {</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // AGPX MODIFIED</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">//
min[j] = (pt[j]<min[j])?(pt[j]):(min[j]);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">// max[j] = (pt[j]>max[j])?(pt[j]):(max[j]);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> <span style="color: rgb(0, 0, 0);">center[j] += pt[j];</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // AGPX MODIFIED</span><br style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> }</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> }</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);"> // AGPX MODIFIED</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> //center[0] = 0.5*(min[0]+max[0]);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96,
191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> //center[1] = 0.5*(min[1]+max[1]);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> //center[2] = 0.5*(min[2]+max[2]);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> center[0] /= ((double)npts);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> center[1] /= ((double)npts);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> center[2] /=
((double)npts);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);"> // AGPX MODIFIED</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br>This completely solve this issue. Now go to the next bug.<br><br>2) "Volume disappearing". Suddenly, if you fly-through inside a volume, it can disappear. The problem is related to the near clipping plane. Actually the method vtkOpenGLGPUVolumeRayCastMapper::ClipBoundingBox, clip the volume box with the near clipping plane (and eventually also with custom clip planes) in order to avoid that the volume results opened. The problem is that due to floating point inaccuracy the resulting clipped convex polyhedra could be still opened! The problem could be solved pushing the near clipping plane slightly ahead. That is:<br><br
style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">if(this->NearPlane==0)</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> {</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> this->NearPlane= vtkPlane::New();</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> }</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);">// AGPX ADDED</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> const double offset = 0.01;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> camNearPoint[0] += camPlaneNormal[0] * offset;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> camNearPoint[1] += camPlaneNormal[1] * offset;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> camNearPoint[2] += camPlaneNormal[2] * offset;</span><br style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);">// AGPX </span></span><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">ADDED</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> this->NearPlane->SetOrigin( camNearPoint );</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> this->NearPlane->SetNormal( camPlaneNormal );</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">
this->Planes->AddItem(this->NearPlane);</span><br><br>The problem here is to establish how much you have to push plane ahead. 0.01 is working for me. Ok, following me to the last (hard) bug.<br><br>3) Volume Rendering Deformation. This bug show a non correct rendering of the volume when you perform a fly-through inside a volume (here <a href="http://www.youtube.com/watch?gl=IT&hl=it&v=C_GA6UHbUGo">http://www.youtube.com/watch?gl=IT&hl=it&v=C_GA6UHbUGo</a> you can see a video of the problem). Actually this bug is unsolved and here I need some help from authors. Look like a bad entry-ray and/or ray-direction calculation. The problem is showed only when the near clipping plane clips the volume box. The entry point is given by the texture coordinate of the point being rasterized (a 3D texture coordinate is applied to the vertices of the rendered volume box). This look like correct. The ray direction is given by the difference
between the entry point and the camera position in texture space. This should be always ok. The method vtkOpenGLGPUVolumeRayCastMapper::LoadProjectionParameters computed the camera position in texture space and looks correct (notice that I'm always talking about a perspective camera):<br><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">double *bounds=this->CurrentScalar->GetLoadedBounds();</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double dx=bounds[1]-bounds[0];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double
dy=bounds[3]-bounds[2];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double dz=bounds[5]-bounds[4];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // Perspective projection</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // Compute camera position in texture coordinates</span><br
style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // Position of the center of the camera in world frame</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double cameraPosWorld[4];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);">// Position of the center of the camera in the dataset frame</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191,
0);"> // (the transform of the volume is taken into account)</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> double cameraPosDataset[4];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);"> // Position of the center of the camera in the texture frame</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"> // the coordinates are translated and rescaled</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"> double cameraPosTexture[4];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> ren->GetActiveCamera()->GetPosition(cameraPosWorld);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> cameraPosWorld[3]=1.0; <span style="color: rgb(96, 191, 0);">// we use homogeneous coordinates.</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">
datasetToWorld->MultiplyPoint(cameraPosWorld,cameraPosDataset);</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);"> // From homogeneous to cartesian coordinates.</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> if(cameraPosDataset[3]!=1.0)</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> {</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier
New,courier,monaco,monospace,sans-serif;"> double ratio=1/cameraPosDataset[3];</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> cameraPosDataset[0]*=ratio;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> cameraPosDataset[1]*=ratio;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> cameraPosDataset[2]*=ratio;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> }</span><br
style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="font-weight: bold;">cameraPosTexture[0] = (cameraPosDataset[0]-bounds[0])/dx;</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; font-weight: bold;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; font-weight: bold;"> cameraPosTexture[1] = (cameraPosDataset[1]-bounds[2])/dy;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif; font-weight: bold;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; font-weight: bold;"> cameraPosTexture[2] = (cameraPosDataset[2]-bounds[4])/dz;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><br>Ray
direction is computed by the shader (vtkGPUVolumeRayCastMapper_PerspectiveProjectionFS.glsl):<br><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">// Entry position (global scope)</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">vec3 pos;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">// Incremental vector in texture space (global scope)</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">vec3 rayDir;</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color:
rgb(96, 191, 0);">// Camera position in texture space</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">uniform vec3 cameraPosition;</span><br><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif; color: rgb(96, 191, 0);">// Defined in the right projection method.</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">void incrementalRayDirection()</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">{</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> <span style="color: rgb(96, 191, 0);">// Direction of the ray
in texture space, not normalized.</span></span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> rayDir=pos-cameraPosition;</span><br><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;"> ...</span><br style="font-family: Courier New,courier,monaco,monospace,sans-serif;"><span style="font-family: Courier New,courier,monaco,monospace,sans-serif;">}<br><br></span>and looks correct.<br><br>The entry point seems correct, because if I use a totally opaque volume (in order to avoid ray traversal, that is the algorithm stop immediately on the first hitted voxel) and clipped with a plane to enter inside it, the rendering is always ok. That is entry point looks correct. When the volume isn't fully opaque the result is wrong. So, I suspect that the ray direction is wrong. Note that the problem disappear (or at least is not much
noticeable) IF I try push the near clipping plane ahead of a greater quantity (>> 0.01). If I push it ahead of 16.0 instead of 0.01 (as stated in the previous bug), the deformation is not very noticeable. Note that 16.0 is an empirically determinated value for my tested datasets and reasonably it's not a valid choice for every dataset (a way to determine a good value?). It's not clear why the near clipping plane cause this problem. The texture coordinate seems to be interpolated correctly and apparently there are no reason why the method should not works. Here I'm stuck and I need some help from some CG Guru! Please support!<br><br>Thanks in advance for your attention,<br><br>- Gianluca Arcidiacono (a.k.a. AGPX)<br>
</div><br>
</body></html>