LeGreg | Qu'est-ce qu'un blob?
C'est le nom commun d'une surface d'isopotentiel.
Le potentiel c'est un champ de valeurs scalaires dans l'espace tel qu'à chaque point de l'espace (x,y,z) corresponde une unique valeur f(x,y,z).
La surface isopotentiel est constitué des points tels que
f(x,y,z) soit égal à une constante.
En général pour représenter des blobs on utilise des champs types électriques ou gravitationnels. Si on dispose d'une source de champ ponctuelle en P0, la valeur du champ au point P est égale à E0/(P-P0)^2.
De plus la valeur du champ créé par deux sources en un point est égal à la somme des deux champs créés par chacune de ces sources. (c'est LA propriété primordiale que l'on utilise par la suite).
On définit donc un blob comme la surface d'isopotentiel = 1 dans un champ défini par n sources ponctuelles tel que donc:
f(P) = Somme sur n (Taillen^2 / (P-Pn)^2 )
Note: Grâce a notre définition, si n = 1, notre blob est simplement une sphere de rayon = taille.
Resolution rayon/blob
Bon c'est pas tout d'avoir cette definition implicite. On aimerait que ça donne quelque chose à l'écran.
Le problème de cette définition du potentiel c'est qu'il est possible d'obtenir une solution exacte au problème d'intersection mais que pour cela il faut résoudre des équations polynomiales de degrés de plus en plus importants à chaque nouvelle source ajoutée. C'est sympa de savoir le faire mais ça dépasse le cadre de mon article donc je vais trouver un autre moyen.
L'idée c'est d'approximer la fonction 1/dist^2 sous forme de fonctions linéaires par morceaux de dist^2.
En gros pour chaque source on construit une série de sphères de plus en plus resserrées et entre deux de ces sphères c'est f(P) = gamma * (P-Pn)*(P-Pn) + beta + delta(P);
Avec delta(P) que l'on néglige.
Ca ne pose pas de problème tant que dist n'est pas nul et l'on a une bonne approximation si l'on découpe en suffisamment de zones.
Maintenant que se passe t'il quand on a n spheres ? Le probleme c'est que les zones d'influences s'interpénètrent de manière non déterministe et que notre rayon, passe allègrement d'une sphère d'influence à l'autre sans ordre bien déterminé. Par contre le gros avantage de cette méthode c'est que la valeur du champ dans chaque sous zone est égal à une combinaison des gamma * dist ^2 + beta. Dit sous cette forme ce n'est pas très intéressant mais si l'on remplace dist par la valeur paramétrée par t du rayon on obtient une equation du second degré facile à résoudre et la somme d'équations du second degré faciles à résoudre est encore une équation du second degré facile à résoudre (pratique n'est-ce pas ?).
Le code source
Code :
- // fonction polynomiale du second degré définie par ses trois coéfficients a * x^2 + b * x + c
- struct poly
- {
- float a, b, c;
- };
- const int zoneNumber = 10;
- // une zone est définie par un rapport du carré de la distance à la sphère sur le carré de son rayon
- // puis par les gamma et beta incrémentaux qui approchent la courbe 1/ dist^2
- // ces deux coefficients ne sont calculées qu'une seule fois par exécution dans l'initZones.
- struct
- {
- float fCoef, fGamma, fBeta;
- } zoneTab[zoneNumber] =
- {
- {10.0f, 0, 0},
- {5.0f, 0, 0},
- {3.33333f, 0, 0},
- {2.5f, 0, 0},
- {2.0f, 0, 0},
- {1.66667f, 0, 0},
- {1.42857f, 0, 0},
- {1.25f, 0, 0},
- {1.1111f, 0, 0},
- {1.0f, 0, 0}
- };
- static void initZones()
- {
- float fLastGamma = 0.0f, fLastBeta = 0.0f;
- float fLastInvRSquare = 0.0f;
- for (int i = 0; i < zoneNumber - 1; i++)
- {
- float fInvRSquare = 1.0f / zoneTab[i + 1].fCoef;
- // fGamma est la pente entre le point d'entrée et le point de sortie
- // on ne stocke que la valeur incrémentale par rapport à la zone précédente
- zoneTab[i].fGamma = (fLastInvRSquare - fInvRSquare) / (zoneTab[i].fCoef - zoneTab[i + 1].fCoef) - fLastGamma;
- fLastGamma = zoneTab[i].fGamma + fLastGamma;
- // en faisant - fLastGamma et + fLastGamma à la ligne suivante
- // on s'économise l'utilisation d'une temporaire
- // je sais c'est débile..
- // fBeta est la valeur de la droite approchant la courbe pour dist = 0
- // on ne stocke que la valeur incrémentale par rapport à la zone précédente
- zoneTab[i].fBeta = fInvRSquare - fLastGamma * zoneTab[i+1].fCoef - fLastBeta;
- fLastBeta = zoneTab[i].fBeta + fLastBeta;
- fLastInvRSquare = fInvRSquare;
- };
- // la derniere zone d'influence agit comme un signal que l'on traite comme un cas particulier
- zoneTab[zoneNumber - 1].fGamma = 0.0f;
- zoneTab[zoneNumber - 1].fBeta = 1.0f;
- }
- bool isBlobIntersected(const ray &r, const blob& b, float &t)
- {
- map<float, poly> polynomMap;
- static bool bInit = false;
- if (!bInit)
- {
- // on initialise les coéfficients gamma et beta une seule fois par execution
- initZones();
- bInit = true;
- }
-
- for (unsigned int i= 0; i< b.sphereList.size(); i++)
- {
- float A, B, C, rSquare, rInvSquare;
- float fDelta, t0, t1;
- sphere ¤tSphere = *b.sphereList[i];
- rSquare = currentSphere.size * currentSphere.size;
- // Cela ne sert à rien d'avoir des spheres de taille zero
- // mais soyons de bons paranoiaques tout de meme
- if (rSquare == 0.0f)
- continue;
- rInvSquare = 1.0f / rSquare;
- vecteur vDist = currentSphere.pos - r.start;
- A = 1.0f;
- B = - 2.0f * r.dir * vDist;
- C = vDist * vDist;
- // on parcourt la liste des zones d'influences de la sphère courante
- // on calcule la nouvelle version du polynome qui a cours dans
- // cette zone d'influence et on le stocke de manière incrémentale
- // ce qui importe c'est la différence avec la zone précédente, ce qui permet
- // de bien gérer le cas de sphères imbriquées les unes dans les autres
- for (int j=0; j < zoneNumber; j++)
- {
- // On calcule le Delta de l'équation s'il est négatif
- // il n'y a pas de solution donc pas de point d'intersection
- fDelta = B*B - 4.0f * (C - zoneTab[j].fCoef * rSquare);
- if (fDelta < 0.0f)
- break;
- t0 = 0.5f * ( - B - sqrtf(fDelta));
- // cool on ne s'occupe pas de l'ordre il est ici explicite t0 < t1
- t1 = 0.5f * ( - B + sqrtf(fDelta));
- poly poly0 = {zoneTab[j].fGamma * A * rInvSquare ,zoneTab[j].fGamma * B * rInvSquare , zoneTab[j].fGamma * C * rInvSquare + zoneTab[j].fBeta };
- poly poly1 = {- poly0.a, - poly0.b, - poly0.c};
-
- // les variations du polynome sont trièes par leur position sur le rayon
- // au fur et à mesure qu'elles sont insérées. C'est la map qui nous garantit cela.
- // ce serait peut-etre plus optimal de placer dans un vector sans se soucier de l'ordre
- // et trier ensuite mais je me fiche de l'optimisation en fait.
- polynomMap.insert(pair<float, poly>(t0, poly0));
- polynomMap.insert(pair<float, poly>(t1, poly1));
- };
- }
- poly currentPolynom = {0.0f,0.0f,0.0f};
- // la variable inside permet d'ignorer les équations
- // dont on sait qu'elles n'auront pas d'influence
- // sur notre affichage puisqu'elles se trouvent
- // à l'intérieur d'une sphère incluse dans le
- // blob.
- int inside = 0;
- for (map<float, poly>::const_iterator it = polynomMap.begin(); it != polynomMap.end(); ++it )
- {
- if (((*it).second.a == 0) &&((*it).second.b == 0) &&((*it).second.c ==1.0f))
- {
- if (inside == 0)
- {
- if (( (*it).first < t ) && ((*it).first > 0.01f))
- {
- t = (*it).first;
- return true;
- }
- }
- inside = inside + 1;
- }
- else if (((*it).second.a == 0) &&((*it).second.b == 0) &&((*it).second.c == -1.0f))
- {
- inside = inside -1;
- }
- else
- {
- // comme on a stocké les polynomes de manière incrémentale on
- // reconstruit le polynome de la zone d'influence courante
- currentPolynom.a += (*it).second.a;
- currentPolynom.b += (*it).second.b;
- currentPolynom.c += (*it).second.c;
- }
- map<float, poly>::const_iterator itNext = it;
- ++itNext;
- // ça ne sert à rien de résoudre l'équation si la zone d'influence est avant le point de départ
- // ou après le point d'arrivée sur le rayon
- if ((inside == 0) && (t > (*it).first ) && ((*itNext).first) > 0.01f)
- {
- // on peut se permettre de résoudre la dernière équation de manière exacte
- // après toutes les approximations que l'on a fait
- // avec un nombre suffisant de découpage,
- // il devrait être difficile de faire la distinction
- // entre le blob et son découpage.
- // on remarque que l'on a un blob qui est constitué de morceaux
- // de sphères imbriquées.
- float fDelta = currentPolynom.b * currentPolynom.b - 4.0f * currentPolynom.a * (currentPolynom.c - 1.0f) ;
- if (fDelta < 0.0f)
- continue;
- float t0 = (0.5f / currentPolynom.a) * (- currentPolynom.b - sqrtf(fDelta));
- float t1 = (0.5f / currentPolynom.a) * (- currentPolynom.b + sqrtf(fDelta));
- bool retValue = false;
- if ((t0 > 0.01f ) && (t0 >= (*it).first ) && (t0 < (*itNext).first) && (t0 <= t ))
- {
- t = t0;
- retValue = true;
- }
-
- if ((t1 > 0.01f ) && (t1 >= (*it).first ) && (t1 < (*itNext).first) && (t1 <= t ))
- {
- t = t1;
- retValue = true;
- }
- if (retValue == true)
- return true;
- }
- }
- return false;
- }
|
Je suis d'accord que ce n'est franchement pas clair donc si vous avez des questions n'hésitez pas.
Les normales
J'ai lu quelque part que pour calculer les normales on pouvait se contenter de faire une moyenne arithmétique de la normale de chaque sphère rapportée à la distance. C'est pas tout à fait faux mais on peut faire mieux.
Une surface isopotentielle a une normale en un point de la surface dirigée par le gradient du champ de potentiel en ce point.
Comme le champ est quelque chose comme:
Somme (taille * taille/dist * dist)
que le gradient de dist*dist = {2 * (x - xn), 2 * (y - yn), 2 * (z-zn)} (c'est un vecteur);
on en déduit que le gradient de notre champ c'est quelque chose comme :
- 2 * { Somme (taille * taille * (x-xn) / dist ^4), Somme(taille * taille * (y-yn)/ dist ^ 4, Somme(taille * taille * (z-zn) / dist ^4)}; (c'est aussi un vecteur)
Donc on peut calculer directement notre vecteur normal en ce point avec la formule ci dessous (en normalisant et en prenant l'opposé puisque l'extérieur est du coté des potentiels decroissants).
Code :
- void blobInterpolation(point pos, const blob& b, vecteur &vOut)
- {
- vecteur gradient = {0.0f,0.0f,0.0f};
- float fSomme = 0.0f;
- for (unsigned int i= 0; i< b.sphereList.size(); i++)
- {
- vecteur normal = pos - b.sphereList[i]->pos;
- float fDistSquare = normal * normal;
- if (fDistSquare <= 0.001f)
- continue;
- float fDistFour = fDistSquare * fDistSquare;
- float fRSquare = b.sphereList[i]->size * b.sphereList[i]->size;
- normal = (fRSquare/fDistFour) * normal;
- gradient = gradient + normal;
- }
- vOut = gradient;
- }
|
Voilà c'est tout pour cette nuit.
A plus.
LeGreg Message édité par LeGreg le 24-08-2003 à 09:39:32
|