The Color3D is program interfacing the PHANToM, a valuable (and unfortunately expensive) piece of hardware. The program instanciates two threats. One is the OpenGL rendering loop (at approx. 70Hz), the other is the PHANToM servo loop (at 1000Hz). The PHANToM creates the illusion of touching the surfaces of a 3d model by applying force feedback. For extensive models, advanced data structures are necessary to compute and exert the appropriate forces in time. Distributing computation between the two threats and communicating data is challenging. The program runs well for a mesh with 30T vertices and about 60T triangles.
Physics related SourceCode (C++ using openGL & GHOST) | color3dsrc.zip | 6 kB |
Stanford collection of scanned data | repository/ | link |
Trimesh converter (MATLAB 6.5) * | color3d.m | 3 kB |
Beauty is power, a smile is its sword.
Concerning the realisation of the haptics for a static model, much precomputation can be done (for which I used MATLAB, see source color3d.m). This includes matrix inversions and determining the neighborhood around each vertex.
At each openGL frame (in real time), the nearest vertex of the model mesh to the PHANToM pointer is computed. Given the precomputed information at a vertex, exemplarily shown on the left, the PHANToM Serve Loop can check quickly, if the PHANToM pointer is situated close behind any of the triangle faces. In which case, a force in the direction of the triangle normal (with length propotional to the distance away from the surface) would be induced.
Moving around the model, striving over the surface is a big issue. Binding the keyboard or mouse to the movement or rotation has been found uncomfortable. Instead, all the navigation is done by controlling the PHANToM. The following excerpt of the source illustrates what is done. Note the comments and the difference between mot and rot.
gstGetPhantomPosition(ghost,pos); gstGetStylusJointAngles(ghost,sty); force=gstVector(0,0.23,0); //neutralize weight of PHANToM (anti gravity) if (mode&1) { //moving mode force-=pos*.07; //attraction towards the origin master->mot->data[0]=pos.x()*.03;//acceleration in 3 directions is proportional master->mot->data[1]=pos.y()*.03;//...to the excursion from the origin master->mot->data[2]=pos.z()*.03; master->rot->data[0]=(sty.y()-M_PI)*2;//rotational acceleration around the 3 axis master->rot->data[1]=(sty.x()-M_PI)*3;//...is proportional to the stylus angles master->rot->data[2]=(M_PI-sty.z())*.6; }
Different painting tools have been developed to make painting efficient and nice looking.
The brush indicates with a connecting line when the PHANToM pointer is close enough to a vertex on the surface. Since we attach the color vertex-wise, upon pressing the stylus switch, the connected vertex is assigned the color the PHANToM carries currently. This tool can be used to draw sharp lines across the surface, or enhance creases.
The fader operates on a larger environment of the PHANToM pointer. Pressing down the stylus button, resolves in fading the existing color at the indicated vertices towards the color of the PHANToM. This can be used to create shadows, such as in the back of the dragons mouth or on top of the nose. How colors are merging is determined by the distance and spiced with a little normal distribution magic.
The spray effect is ideal to paint a wide area of the surface, where the color is normal distributed with a fixed RGB attribute as a mean. In the previous example pictures, the redness of the tongue is the result of spraying. The bubbles are actually simulated and underlie gravity and collision with the surface. Ejected with a certain velocity, they only decelerate and "die" after 1 1/2 sec, if they didn't come close to the surface. With these assumptions, a list of close vertices of the model (with respect to the spray can) is attached to each bubble. No vertices outside of this subset can be possibly reached by the bubble. Each OpenGL frame, each bubble loops over its attached subset of vertices. If the bubble happens to be really close to a vertex, it manifests its parishing existance by inheriting the color at its place of death. This is a rather simple algorithm which fits the residual data structure well. One more american style reason: It is fast almost everywhere.
Color selection: Although the arrangement of "all colors" is a science itself, color selection is realized in the 3d color cube show at the side. The forces, that attract the PHANToM to the origin are faded out. Eventually the walls of the cube should become transparent such that the model is slightly visible behind it. A color is chosen upon pressing the stylus button. Since at this very moment, there might be a huge excursion from the origin, the forces and movement induction have to be softly faded back in.
User interaction: The user is able to toggle the painting tools by rotating fast the stylus around its own (z-)axis back and forth once. This is a motion that does not occur while changing the viewing position or angle. The stylus z-angle is constantly recorded over a short time period of 1 sec. The signal is convolved with an appropriate filter and if the result is above a certain threshold, the tools are exchanged.
Pressing 's' saves the current paintings such that editing can be continued in the next session, such that upon finishing, the file can imported by other programs. To the left, the colored enterprise is shown scaled down besides a textured car (triangulation of spaceship converted by righthemisphere).
... the bunny. Using color3d.m, initially random color is assigned.
Deer hunting would be fine sport,
if only the deer had guns.
William S. Gilbert
void phantom_haptics(void) {// @ 1000Hz int c1,c2,curr,closest=P_closest; bool hit=false; float world_[16]; float single[15]; float F=10,eps=.001; memcpy(world_,P->world,64); //transform PHANToM position into world coordinates gstVector world_pos=gstVector(world_[ 0],world_[ 1],world_[ 2])*P->pos.x()+ gstVector(world_[ 4],world_[ 5],world_[ 6])*P->pos.y()+ gstVector(world_[ 8],world_[ 9],world_[10])*P->pos.z()+ gstVector(world_[12],world_[13],world_[14]); gstVector rhs,sol; while ((curr=triList->data[closest])>=0&&!hit) { //loop over triangles adjacent to closest vertex memcpy(single,&haptics->data[curr*15],60); rhs=gstVector(world_pos.x()-single[9],world_pos.y()-single[10],world_pos.z()-single[11]); sol=gstVector(single[0],single[1],single[2])*rhs.x()+ gstVector(single[3],single[4],single[5])*rhs.y()+ gstVector(single[6],single[7],single[8])*rhs.z(); if (-eps<=sol.x()&&-eps<=sol.y()&&sol.x()+sol.y()<=1+eps) //these conditions hold if ... if (-.6 lt sol.z()&&sol.z() lt 0) { //...PHANToM is short behind a triangle hit=true; F=F*sol.z(); P->force-=(gstVector(world_[ 0],world_[ 4],world_[ 8])*single[12]+ gstVector(world_[ 1],world_[ 5],world_[ 9])*single[13]+ gstVector(world_[ 2],world_[ 6],world_[10])*single[14])*F; //force in PHANToM coordinates } closest++; } if (P_paint==3&&P->button) { //force induced when spraying P->force+=gstVector(P->transform(2,0),P->transform(2,1),P->transform(2,2))*.3;} };
After graduating the University of Vienna with a law degree,
Schenker devoted himself entirely to music.
Wikipedia