PDA

Voir la version complète : Plug-In et Ressorts.



Jean-Laurent
03/01/2007, 21h43
Bonjour à tous, :odile:
De nombreuses questions sur le forum (faire flotter un objet sur une mer, animer un bras mécanique, chute libre d'objets etc...) m'ont donné envie de lier mes deux loisirs, la 3D et la physique.

Pour cela, je vais tenter de créer un Plug-In contenant différents tags (très simples) en rapport avec les lois de la physique.
Tout cela existe déjà et se fait certainement facilement avec de nombreux Plug-In spécialisées, ou évidemment le module Dynamic, mon but n'est donc absolument pas de les concurrencer (pas le temps, pas le niveau, pas les moyens). :oops:
C'est juste pour le plaisir. J'espère que ça me permettra de progresser en programmation avec l'aide des personnes du forum ou que ça permettra à d'autres d'utiliser plus facilement le langage COFFEE. Les exemples ne sont jamais trop nombreux.

- Le premier tag concernera donc LES RESSORTS.

Il s'agit juste de pouvoir lier entre eux plusieurs objets avec des ressorts (visibles ou non) aux caractéristiques réglables.


Commençons tout en douceur avec un ressort à une dimension et une extrémité fixe.
Avant de développer l'interface (ExpressionPlugIn) , mettons en place l'expression coffee.

Les objets seront symbolisés par des sphères (Sphere1 et Sphere2) placées sur l'axe z et le ressort par une primitive "Helix" suivant le même axe.
L'élongation du ressort se fera donc suivant l'axe z.

http://cinema4d.chez-alice.fr/r2.JPG

On placera l'expression coffee sur un Null provisoirement.

Pour que le ressort ai toujours la même longueur que la distance entre les sphères auxquelles il est attaché:


main(doc,op)
{

var helix = doc->FindObject("Helix"); // On repère les objets dans le document.
var Sphere2 = doc->FindObject("Sphere2");
var Sphere1 = doc->FindObject("Sphere1");

var pos2= Sphere2->GetPosition(); // On récupère dans des variables la position des objets.
var pos1= Sphere1->GetPosition();
var posh =helix->GetPosition();

var distance= vlen(pos2-pos1); // On mesure la distance entre les deux sphères.

var chelix= helix->GetContainer();
// On récupère les données (taille etc...) de la primitive "ressort"

chelix->SetData(PRIM_HELIX_HEIGHT,distance);
helix->SetContainer(chelix);
// On règle la longueur du ressort, celle-ci est identique à la distance entre les sphères.

helix->SetPosition(pos2);

// On fixe l'extrémité du ressort sur la sphère2

}



On a maintenant créé un ressort qui s'étire ou se compresse entre les 2 sphères lorsqu'on les déplace suivant l'axe z.
Pour plus d'esthétisme, on peut réduire le rayon à 50m et augmenter le nombre de spires (

http://cinema4d.chez-alice.fr/r3.JPG

Il reste maintenant à rendre le ressort dynamique pour qu'il agisse sur les sphères.

Petit rappel:
http://cinema4d.chez-alice.fr/r1.JPG

Les caractéristiques les plus importantes d'un ressort sont l0: sa longueur au repos et K: Sa raideur.

Lorsqu'on l'étire ou le compresse d'une longueur x, la force qu'il exerce alors vaut:
F= -K*x.

Un objet de masse m soumis à cette force a une accélération a = F/m.
(On négligera la masse des sphères dans un tout premier temps).

L'accélération est liée à la vitesse par la formule: a=dv/dt (variation de vitesse et de temps, très courtes).
Et la variation de position de l'objet: dx = v * dt.

En coffee:



var v=0; // Ces paramètres seront déclarées avant main (op,doc)
var k =5; // On peut les changer manuellement pour l'instant.
var l0 =800;
var dt =0.1; // Temps en seconde entre chaque frame.

var force = -k*(distance-l0); // On définit la force.

v+=force*dt; // On définit la vitesse.
pos2.z+=v*dt; // On change la position suivant l'axe z de la sphère.

Sphere2->SetPosition(pos2);



La sphère 2 oscille maintenant comme si elle était reliée à un ressort.
Attention, si on tend trop le ressort pour l'instant (d > 2*l0) il casse.

Si on souhaite que la sphère 1 oscille également (pas d'attache fixe) il suffit de rajouter:



pos1.z-=v*dt;
Sphere1->SetPosition(pos1);


Puisque les deux forces sont égales et opposées.

Toutes remarques, questions etc... sont les bienvenues. :poucehaut:

La suite: Un ressort dans toutes les directions de l'espace et prévision des limites. :odile:

phanault
03/01/2007, 21h48
Ouahh ! Un wip scientifique avec Jean-Laurent !!!! :bounce:

Ça promet déjà.

Je te suis, tu peux en être assuré.

GaazMaster
03/01/2007, 22h29
Animer un bras mécanique ... chute libre d'objets .... Hum ...
Toi je vais être obligé de te sponsoriser !* *:P

Bon alors déjà j'ai tout compris la théorie, ce qui est déjà une bonne chose ... pour ce qui est du côté plus pratique ( en l'occurence le coffee ) la ... bon ... euh ...* :mrgreen:

Donc première question .... tu as utilisé une spline spirale, est ce que l'on peut adapter le principe avec une spline formule ?

Par exemple : avec laquelle je simule le ressort d'un amortos
X(t) : 6*sin(t*pi)
Y(t) : 16*tanh(0.12*t)
Z(t) : 6*cos(t*pi)
Tmin : -10
Tmax : 10
Echantillon : 500

Voila ... super initiative .... forcément !* :mrgreen:

kald01
03/01/2007, 22h47
EXELLENTE initiative Jean-Laurent, je met dans un coin et je prend des notes :prie:

Pyerow
03/01/2007, 22h50
Bon alors je vais regarder tout ca, par curiosité parce que j'y connais rien en prog' COFFEE (c'est quoi comme language déja? :nono:)

ET j'oublie pas mon tube d'aspirine, j'ai déja vu Jean-Laurent sur des résolutions de problèmes.. :coup:


Je suis, donc :odile:

Jean-Laurent
04/01/2007, 10h44
Wahou, toutes ces réponses me font chaud au coeur. :D

phanault: scientifique est un grand mot, les maths ne dépasseront pas le niveau lycée, promis. :wink:

Gaaz: Oui, oui on peut l'adapter pour l'instant à une spline formule (je te mettrai un exemple) mais le principe est alors un poil différent.

Ta spline formule est une "simple" spline paramétrique. Elle fait déjà tout ce que tu veux et se comporte en apparence comme un ressort. Tout simplement parceque tu as rentré la formule de l'équation résolue d'un ressort.

J'aurais pu faire la même chose (en moins de lignes de code) du coup en rentrant directement la formule:
Z= A*sin (wt + phi) qui décrit le déplacement d'un ressort dans une dimension.
Dont ta "spline formule" est une simplification.

Ou même en allant plus loin, si tu veux un ressort plus réaliste avec un amortissement:
Z= A*exp(-landa*t)*sin (wt + phi)

Le problème c'est que ça ne marchera qu'avec 1 ressort (ou évidemment plusieurs mais indépendants les uns des autres).

Or je souhaite pouvoir connecter plusieurs ressorts entre eux et dans différentes positions.
(En réalité c'est déjà fait, mais il faut mettre un peu ça au propre.)

Dans ce cas là, la résolution des équations est extrémement compliquée (modes propres etc...), voir impossible dans l'espace à 3 dimensions.

D'où la nécessité d'une "simulation".

En résumé: Si la spline formule est plus pratique que la primitive (il faudrait que tu me dises pourquoi) on peut évidemment l'utiliser à la place de celle-ci mais pas avec les formules que tu as entré.
Pour pouvoir créer un systéme plus complexe, je n'utilise volontairement pas de formules toutes faites de ressorts en mouvement mais essaye de partir de la notion de "force" pour réaliser une "simulation" du mouvement.

Je sais pas si j'ai été clair. :oops:

Kald: Pourqoui se contenter de prendre des notes? :bounce:

Pyerow: Comme pour les autres, ce qui serait dommage c'est que le côté COFFEE vous échappe, d'autant plus que c'est un peu le but de ce post. :cry2:

Petit résumé de COFFEE:

main (doc,op) { }
C'est la fonction principale, on place entre les accolades le code qui sera exécuté à chaque frame ou à chaque action dans le logiciel (copier-coller,création d'une primitive etc...).

var xyz = 12;
Ligne de code (toujours finir par un point virgule) qui définit une variable dont le nom est xyz et la valeur 12.
A chaque fois qu'on écrira xyz dans le programme, le logiciel le remplacera par 12.
EX: println(10*xyz); Ecrira dans la console 120.

var helix=doc->FindObject("helix");

La fonction doc->FindObject() permet de chercher un objet dans le gestionnaire d'objet.
Ici, on cherche donc l'objet qui se nomme "helix", les " " signifient que c'est un texte et pas une variable.
Si cette objet existe alors la fonction renvoie sont "ID" (un numéro qui désigne l'objet).

Au final, on a créé la variable helix, et chaque fois qu'on tapera helix comme code, le logiciel comprendra qu'il s'agit de l'objet helix.

var posh =helix->GetPosition();

On crée une nouvelle variable qui a pour valeur la position du ressort.
Puisque le logiciel sait maintenant que helix c'est le ressort.
helix->GetPosition(); renvoi un vecteur avec les 3 position [x,y,z]
Donc posh est un vecteur.

var distance= vlen(pos2-pos1);

la fonction vlen () (vector lenght) mesure la longeur d'un vecteur.
Donc là, la variable distance a pour valeur la distance entre les deux sphères.

var chelix= helix->GetContainer();


La fonction GetContainer() est très importante, elle permet de récupérer toutes les données d'un objet (sa taille,
la distance focale si c'est une caméra, etc...)
Donc là, la variable chelix contient maintenant toutes les données du ressort (nombre de spire, longueur etc...)

chelix->SetData(PRIM_HELIX_HEIGHT,distance);

SetData() permet de modifier l'une des données précédentes. (contenues dans chelix)
on modifie "PRIM_HELIX_HEIGHT" la hauteur de l'hélice (donc du ressort) et on la met à la longueur "distance", notre variable précédente.
Remarque: Inutile de retenir les codes comme "PRIM_HELIX_HEIGHT", il suffit de faire un glisser-déposer du gestionnaire d'attribut vers l'expression coffee.

helix->SetContainer(chelix);

On a modifier le container précédemment, mais ça ne modifie pas l'objet immédiatement à l'image.
Il faut pour obtenir les modification, renvoyer le nouveau container à l'objet.
La fonction SetContainer() s'en occupe. On renvoi le chelix modifié.

helix->SetPosition(pos2);

La fonction SetPosition() place un objet à la position voulue. Il faut rentrer dedans un vecteur.
Ex: objet->SetPosition(vector(0,0,0)); Placerait l'objet en 0,0,0.
Je rappel que plus haut pos2 est la variable qui contient le vecteur position de l'objet 2.
Donc on fixe le ressort à l'objet2.

Dernière explication:

v+=force*dt;
C'est un raccourci qui signifie la même chose que:
v=v+force*dt;

v,force et dt sont 3 variables définies précédemment.
On dit ainsi que la nouvelle valeur de v, c'est la précédente + la valeur de force*dt.

Je ne détaillerai plus chaque ligne je vous rassure mais je pense qu'il est important de bien comprendre le code.

Aurety
04/01/2007, 11h46
Jean-Laurent, je t'aime ! Quelle merveille à lire :prie: je vais essayer de suivre même si les dernières formules m'ont fait reculer :nono:

johnc
04/01/2007, 11h56
bravo pour l'initiative... :efface: :efface: :efface: :prie: :prie:

je te suis aussi avec grand intéret...

Jean-Laurent
04/01/2007, 11h58
Jean-Laurent, je t'aime ! Quelle merveille à lire :prie: je vais essayer de suivre même si les dernières formules m'ont fait reculer :nono:


:oops: :love:

Il va falloir que je développe un peu plus alors. :mrgreen:

Blague à part n'hésitez pas à questionner ou à me corriger.
Là, ça fait un peu cours magistral, alors que je suis bien moins avancé que beaucoup sur le forum en programmation. :oops:

Merci à toi johnc. :P

kald01
04/01/2007, 12h01
C'est super Jean Laurent, le coffee expliqué pas à pas comme ça :love:



Kald: Pourqoui se contenter de prendre des notes?
Parce que j'ai un niveau collège en math moi, après j'ai une formation exclusiment littéraire, alors j'ai quelques soucis avec les langages de programmation :oops:
J'apprécie à ce titre ta merveilleuse pédagogie :prie:

ZOZO
04/01/2007, 12h45
Très belle initiative, je suis et je décortique !

moebius
04/01/2007, 12h55
:shock:
Voilà un sujet comme on aimerait en voir plus!
Merci jean-laurent :love:


Je suis avec grand intérêt. je n'ai pas eut le temps de tout tout lire et tout tout décortiquer, mais je me réserve un créneau prochainement pour examiner la question.
En attandant :arrow: Abonné :)

++

phanault
04/01/2007, 16h10
Et en boni, on a droit à une doc plus détaillée du langage COFFEE !

:poucehaut:

C'est super comme truc.* :bounce:


Un petit truc de programmation pour les intéressés : Étant donné que le langage COFFEE n'est pas typé (c'est à dire qu'il n'est pas nécessaire de déterminer le type de donnée assigné à une variable), il est d'usage d'utiliser la méthode dite "Ungarian" pour nommer les variables. Cette méthode propose de placer un préfix donnant le type du contenu de la variable. Par exemple, si une variable doit contenir un objet, on débute par la lette "o". Pour un entier, on débute par "i" (Integer en anglais). Des exemples :

oDoc : Objet document
iLoop: Compteur de boucle / itération
vVec: Un vecteur
cChaine: Une chaine de caractere

etc..

Voilà pour ma très petite contribution.

phanault
04/01/2007, 16h54
Jean-Laurent,

J'ai codé le COFFEE mais je n'arrive pas à obtenir le résultat souhaîté au niveau de l'hélix. Celui-ci demeure toujours de la même longueur, même si le code est explicite à ce niveau. Je bosse sur la R10. Y aurait-il des changements à ce niveau ?


chelix->SetData(PRIM_HELIX_HEIGHT,distance);
helix->SetContainer(chelix);

EDIT : Oublie ça, j'ai tenté de changer manuellement la valeur Height de l'hélix dans le gestionnaire d'objets puis Boom ! Tout s'est placé comme par enchantement. Probablement un bug ou une fonctionnalité mal implantée !!!

Pyerow
04/01/2007, 21h36
D'après ce que j'ai compris donc, le COFFEE (le language de programmation utilisé sur C4D apparement) ressemble assez au C/C++ j'ai l'impression..
POO == :puke: ( :mrgreen:)


Bon j'vais suivre quand même, ca à l'air utile. Merci d'apporter ton savoir.

:efface: :odile:

phanault
04/01/2007, 23h19
Oui, le langage ressemble au C/C++ au niveau de la structure sans toutefois être aussi complexe. Puis la programmation OO est tellement plus simple. Y'a qu'à regarder comment on peut accéder aux propriétés des objets de C4D :

oTest = doc->FindObject("XYZ");

qui se traduit ainsi : Utilise la fonction nommée FindObject de l'objet document, pour trouver et retourner l'objet dénommé "XYZ". Met cet objet dans la variable oTest pour utilisation future. termine la ligne avec un ;

Voilà, tout simple !

Jean-Laurent
04/01/2007, 23h31
Et là c'est que le début. :mrgreen:

C'est vrai que de toute manière tous les langages de programmation se ressemblent.

La création de l'interface et des fichiers .cof ressemble encore plus à du C++, avec tout un tas de classes etc...

Je sens qu'on va apprendre plein de trucs avec phanault. :poucehaut:
Il faudra certainement me reprendre sur beaucoup de noms de variables. :wink:

Un peu à la bourre aujourd'hui, je me suis rendu compte que sur les grandes vitesses le ressort ne suit pas exactement l'objet, puisque la position de l'objet est calculée après la taille du ressort.
Quelques modif et demain je poste la suite.

Pyerow
05/01/2007, 00h01
Quand je parlais de POO, c'est surtout que je préfère la programmation Web :mrgreen:
La puissance du PHP combiné avec un peu d'xAjax et Perl :love: J'ai jamais aimé le C/C++, j'préfère Python et Ruby :mrgreen:

J'vais me pencher sur COFFEE alors, vous m'avez donné envi avec vos bétises :bounce:


:odile:

phanault
05/01/2007, 02h38
Pyerow,

c'est vrai que la programmation web est beaucoup plus linéaire que le C++ ou COFFEE et aussi plus amusante. Personnellement, j'aime bien le PHP, même si je n'ai pas souvent l'occasion de l'utiliser. Mon langage de prédilection est présentement Delphi.

phanault
05/01/2007, 02h41
Je sens qu'on va apprendre plein de trucs avec phanault. :poucehaut:


Je ne suis pas certain que j'aurais des choses à t'apprendre. Ce serait plutôt le contraire ! :wink:

En ce qui concerne les noms de variables, il n'y a pas de lois ni d'obligations. Le truc sert uniquement à détecter rapidement le type de donnée contenu dans une variable simplement en regardant le préfix de celle-ci. Je n'ai rien inventé, c'est courrement utilisé en Clipper et d'autres langages.

Machaon
05/01/2007, 20h20
Alors là je campe ... enfin un cours de programmation COFFEE et avec qui !!!* :bounce:
The best one* : Jean-Laurent and the best two : Phanault* :odile:

J'peux pas résister : un looping (de papillon) pour Jean-Laurent !

phanault
05/01/2007, 20h38
The best one* : Jean-Laurent and the best two : Phanault* :odile:


Alors là, faut pas exagérer. Je suis programmeur de métier, mais j'y connaît pas grand chose en COFFEE. Le langage en lui-même ça va, mais il me faut apprendre la hiérarchie des classes au complet, ainsi que les fonctions. La documentation fournie par Maxon est bien, sans plus.

Mais avec Jean-Laurent dans les parrages... c'est certain que nous allons tous en apprendre.

Jean-Laurent
05/01/2007, 21h09
Allez, la suite en douceur: Les limitations. :wink:

Pour l'instant si on sépare les sphères d'une distance exactement égale à l0 c'est à dire 800 m (valeur arbitraire), le ressort ne bouge pas. (logique).

Si on les sépare d'une distance de 1000m, il oscille entre 1000 et 600m. Comme dans la réalité en l'absence de frottement. (Dont on s'occupera par la suite).

Mais si on les sépare d'une distance supérieure à 2*l0 (2000m par exemple) il revient trop fort, dépasse la sphère1 et s'en va ensuite à l'infini car la force exercée n'est plus dans le bon sens (la distance restant positive).

Cela correspond au cas réel où tirant trop fort sur le ressort, l'objet frappe violemment l'autre extrémité. (Et on a mal aux doigts).

Dans la réalité, le choc de l'objet sur un mur solide et en l'absence de frottement et de déformation aurait pour effet d'inverser le sens de la vitesse.
(Les chocs solides-solides élastiques seront traitée par la suite aussi).

On se contentera dans un premier temps d'arrêter la sphère2 lorsqu'elle touche la sphère1.
Il suffit pour cela de mettre la vitesse à 0.
Toujours pour simplifier un maximum au départ, on considérera que le rayon des sphères étant de 100m le ressort est comprimé au maximum si sa longueur est de 200m.

Le code:


if (fDistance<250) {v=0;}


30 lignes de commentaires pour une ligne de code. :mrgreen:

250 au lieu de 200, car on prend une petite marge de sécurité (Plus tard calculée à partir de la vitesse).

Profitons en pour corriger un bug.
On réglait la taille du ressort, puis après la nouvelle position de la sphère.
D'où un léger retard d'une frame entre le ressort et la sphère, visible sur les grandes élongations.

Pour régler ça, je recalcule la nouvelle distance et ajuste la taille du ressort.



oSphere2->SetPosition(vPos2);

fDistance= vlen(vPos2-vPos1);
oHelix->SetPosition(vPos2);
oChelix->SetData(PRIM_HELIX_HEIGHT,fDistance);
oHelix->SetContainer(oChelix);

Je trouve ça un peu lourd.
J'aurais préféré que ce code ne soit exécuté qu'une fois mais je ne vois pas comment faire.
Il faut également qu'il soit exécuter en début de programme.
Peut-être avec un "if (fDistance)" en début de programme qui n'effectue le calcul qu'en début de la deuxième frame.

Une autre limitation maintenant (facultative), l'élongation.
Passé une certaine longueur, le ressort se déforme et ne fonctionne plus.
Il suffit de rajouter une condition:



var fLimite=3000;

if (fDistance<fLimite)
{v+=force*dt;
vPos2.z+=v*dt;
}


Petite astuce: Pour déplacer facilement les sphères sans être gêné par le code, on peut en désactiver des parties en tapant devant "//" qui transforme le code en commentaires.

Ex:


oSphere2->SetPosition(vPos2);
//oSphere1->SetPosition(vPos1);


Seule la sphère2 est soumise au ressort, la sphère1 reste fixe.

Profitons en pour rajouter un petit détail, utile par la suite lorqu'il y aura plusieurs ressorts différents.
Changeons le nombre de spires en fonction de la raideur du ressort.
(La raideur d'un ressort dépendant de sa forme géométrique et de la matière qui le compose.
Des amortisseurs de voiture ont une forte raideur contrairement à un ressort de stylo bille)

Plus le ressort est raide, plus il oscille rapidemment.



oChelix->SetData(PRIM_HELIX_END,10*k);


Attention l'angle est affiché en degrés dans le gestionnaire, mais est entré en radian dans le code.

Le code pour l'instant. Par la suite je n'afficherai que les modifications et fournirai un fichier.



var v=0;
var k =5;
var l0 =800;
var dt =0.1;
var fLimite=3000;

main(doc,op)
{

var oHelix = doc->FindObject("Helix");
var oSphere2 = doc->FindObject("Sphere2");
var oSphere1 = doc->FindObject("Sphere1");

var vPos2= oSphere2->GetPosition();
var vPos1= oSphere1->GetPosition();

var oChelix= oHelix->GetContainer();

var fDistance= vlen(vPos2-vPos1);
oHelix->SetPosition(vPos2);
oChelix->SetData(PRIM_HELIX_HEIGHT,fDistance);
oChelix->SetData(PRIM_HELIX_END,10*k);


oHelix->SetContainer(oChelix);

var force = -k*(fDistance-l0);

if (fDistance<200) {v=0;}

if (fDistance<fLimite)
{v+=force*dt;
vPos2.z+=v*dt;
//vPos1.z-=v*dt;
}

oSphere2->SetPosition(vPos2);
//oSphere1->SetPosition(vPos1);

fDistance= vlen(vPos2-vPos1);
oHelix->SetPosition(vPos2);
oChelix->SetData(PRIM_HELIX_HEIGHT,fDistance);
oHelix->SetContainer(oChelix);

}


Demain si tout va bien, on s'occupe d'un déplacement dans toutes les directions de l'espace.





The best one : Jean-Laurent and the best two : Phanault :odile:

Mais avec Jean-Laurent dans les parrages... c'est certain que nous allons tous en apprendre.


Heu. :oops:
Moi par contre je ne suis pas du tout programmeur. Je suis une bille en algorythmique donc tout n'est peut-être pas à sa place dans le code.
Sans parler des lourdeurs.
Ne pas hésiter à intervenir. :odile:

Jean-Laurent
06/01/2007, 22h55
Je suis mon bonhomme de chemin, tranquillement à mon rythme.

Pour étendre le mouvement du ressort à toutes les directions on pourrait évidemment le mettre dans un null et faire tourner celui-ci. La solution serait bonne pour un ressort mais pas pour plusieurs ressorts couplés donc je la laisse tomber.

J'ai pensé ensuite à calculer l'angle entre les deux objets et ça marche mais ça entraîne des calculs supplémentaires inutiles.
La meilleure solution à mon avis est donc d'utiliser une "expression target" pour alléger le code.
Après quelques réglages que j'expliquerai dans le post suivant ça marche.
Par contre je n'ai pas encore réussi à placer un tag "target" à partir de la fonction coffee "allocatetag()" (un truc comme ça), si quelqu'un sait exactement comment elle fonctionne ça me fera gagner un temps précieux. :poucehaut:

En attendant ces joyeusetés encore une petite modif:

L'amortissement.

Les ressorts n'oscillent pas éternellement puisqu'ils sont soumis aux frottements.
Frottement de l'air par exemple. Ces frottements sont généralement proportionnels à la vitesse et la force de frottement qui s'oppose au mouvement vaut: F = -h*v ou h est un coefficient de frottement qui dépend de la géométrie de l'objet et du milieu traversé.

On remplace donc le code:


v+=force*dt;

par :


var h=0.3;
v+=(force-h*v)*dt;


Le ressort a maintenant un comportement beaucoup plus naturel.
Pour essayer, lancer l'animation et agiter suivant l'axe z la sphère 1, puis regarder.
http://cinema4d.chez-alice.fr/ressort2.c4d

Le ressort a maintenant un mouvement amorti de la forme:
Z= A*exp(-l*t) sin(w*t+p)
Mais qu'on a obtenu d'une façon dynamique et non pas par un calcul savant.

J'espère que cette démarche basée sur les forces me permettra de coupler facilement les ressorts entre eux et d'ajouter d'autres forces par la suite comme par exemple la pesanteur. :odile:

Jean-Laurent
07/01/2007, 15h03
Ressort dans toutes les directions.

La vitesse et la force étaient jusqu'à présent de simples variables de type "float" puisqu'il n'y avait qu'une direction.
Si on veut que le ressort se déplace dans toutes les directions il faut les transformer en vecteurs de la forme: V= (vx,vy,vz) et F= (fx,fy,fz).

La valeur de l'intensité de la force reste la même ("fNorme") , mais il faut projeter celle-ci dans toutes les directions de l'espace.

Ex: Dans 2 dimensions.

http://cinema4d.chez-alice.fr/r5.JPG

Dans les 3 dimensions:



var vRoth= oHelix->GetRotation();

var fNorme = -k*(fDistance-l0);

var vForce = vector(fNorme*cos(vRoth.y)*sin(vRoth.x),-fNorme*sin(vRoth.y),
-fNorme*cos(vRoth.y)*cos(vRoth.x));


On relève donc la rotation du ressort et en fonction de cet angle on projette la force suivant les différents axes.

Au passage on pourra noter que pour définir un vecteur on utilise la fonction vector(x,y,z).

La force est maintenant un vecteur, on fait de même pour la vitesse:




if (fDistance<200) {vx=0;vy=0;vz=0;}

if (fDistance<fLimite)
{vx+=vForce.x*dt;
vy+=vForce.y*dt;
vz+=vForce.z*dt;

vPos2.x+=vx*dt;
vPos2.y+=vy*dt;
vPos2.z+=vz*dt;

}


Il ne faut pas oublier en début de code de déclarer les nouvelles variables vy, vy et vz et de les fixer à 0.

J'aurais préféré déclaré la vitesse sous forme d'un vecteur, comme la force :
var vVitesse= vector(vx,vy,vz)
mais il n'est visiblement pas possible d'utiliser "vector" avant la fonction main().
Si quelqu'un a une idée.


Autrement je pourrais utiliser un variable "first" associé à une condition qui ne serait exécutée qu'en début de programme.

Genre: var first=1;
var vVitesse;

if (first) {vVitesse=vector(0,0,0);}
first=0;

Pour que le ressort soit à chaque instant entre les deux sphères il suffit de placer dessus une expression cible "target".

http://cinema4d.chez-alice.fr/r4.JPG

Je n'ai pas encore cherché à l'intégrer au code. :oops:

Comme l'expression cible fait pointer l'axe z vers l'objet cible, il faut modifier le code en conséquence et simplement remplacer:


oChelix->SetData(PRIM_HELIX_HEIGHT,fDistance);

par:

oChelix->SetData(PRIM_HELIX_HEIGHT,-fDistance);


On a maintenant un ressort qui fait se mouvoir un objet dans n'importe quelle direction de l'espace. L'approximation dans la valeur des sinus et cosinus entraîne néanmoins une petite dérive au bout de quelques minutes d'animation. Je corrigerai ça par la suite.

Remarques: La mise en place d'un code entraîne de nombreuses étourderies, erreurs etc... (particulièrement avec moi). :wink:
Une fonction très utile est la fonction println(); qui affiche une donnée dans la console (window->console). Console qui affiche également les erreurs de code.

Ex: println("salut"); affichera salut dans la console.
println(vRoth); affichera [0.000000,2.12123213,0.0000000]

Pratique pour voir évoluer une variable. :poucehaut:



Je fais une petite pause pour le côté physique à proprement parler et je vais tenter de rendre l'ensemble un peu plus sympa en débutant une ébauche d'interface.

Pour les allergiques à la physique et aux ressorts, vous pouvez oublier les pages précédentes.
Ce qui suit sera presque indépendant et concernera la mise en place de la partie Plug-In.
(Comme Steph et Majoul l'ont fait un peu plus bas) :prie:.

Vous pourrez donc le transposer à vos propres passions. Une Pin-Up à la poitrine paramétrable par exemple. :wink:

N'hésitez pas à questionner, modifier, décrier. :odile:

Majoul
07/01/2007, 16h17
Pour attribuer un nouveau tag "target" à un objet ( Obj )


var tag = AllocTag(Ttargetexpression) ;
Obj->InsertTag(tag, NULL) ;


- Sinon petite remarque importante, lorsque tu pointe vers des objets comme dans ton code avec la fonction* doc->FindObject(nom de l’objet), il faut toujours s’assurer que les objets existent .


var oHelix = doc->FindObject("Helix");
var oSphere2 = doc->FindObject("Sphere2");
var oSphere1 = doc->FindObject("Sphere1");

if(!oHelix || !oSphere2 || !oSphere1) return ;

Si l’un de ces objets n’existe pas alors on stoppe le programme.


- Autre remarque de commodité, depuis la version 9 tu peut utiliser les opérateurs "objet#Id" pour lire et modifier les paramètres du container d’un objet.


var oChelix= oHelix->GetContainer();
oChelix->SetData(PRIM_HELIX_HEIGHT,fDistance);*
oHelix->SetContainer(oChelix);


c’est la même chose que :


oHelix#PRIM_HELIX_HEIGHT =* fDistance ;

tout simplement par un drag & drop du paramètre dans la fenêtre de l’éditeur

Pour attribuer une valeur* V :*


oHelix#PRIM_HELIX_HEIGHT =* V;


Pour lire*une valeur* V :


V = oHelix #PRIM_HELIX_HEIGHT* ;


Plus pratique et rapide à mettre en place

Bonne continuation.

Jean-Laurent
07/01/2007, 17h04
Magnifique. :love:
Je corrige de suite tout ça.
C'est donc à ça que sert le # lorsqu'on fait un glisser-déplacer. :prie:

tarlack
07/01/2007, 23h27
j'applaudis des 3 mains et des 4 pieds :mrgreen: :prie:




Un petit truc de programmation pour les intéressés : Étant donné que le langage COFFEE n'est pas typé (c'est à dire qu'il n'est pas nécessaire de déterminer le type de donnée assigné à une variable), il est d'usage d'utiliser la méthode dite "Ungarian" pour nommer les variables. Cette méthode propose de placer un préfix donnant le type du contenu de la variable. Par exemple, si une variable doit contenir un objet, on débute par la lette "o". Pour un entier, on débute par "i" (Integer en anglais).


Je ne peux que te remercier de dire ceci, c'est grace à ce genre de (très) bonnes pratiques qu'un code est maintenable decemment. par contre, je trouve que c'est aussi une bonne idée de le faire même si le langage est typé. On peut pousser l'expressivité des noms de variables encore plus loin en les prefixant ou suffixant pour indiquer leur contexte de declaration. Par exemple, mettre un "m_" pour indiquer que la variable est membre de la classe, suffixer par un "_" pour dire que la variable est un parametre de fonction...
ca permet par exemple de savoir que dans l'expression (dont on ne sait a priori rien, ni son origine, ni quoique ce soit d'autre)

ma_var = m_ma_var + ma_var_;

"ma_var" est une variable locale, "m_ma_var" un membre de la classe dans laquelle on est et "ma_var_" un parametre de la fonction dans laquelle on est. ca évite d'avoir à chercher la declaration de chaque variable, et ca peut faire gagner un temps précieux. Par contre, c'est une pratique que j'ai, mais je ne sais pas s'il y a un equivalent "consacré", je ne fais que proposer un petit "truc" en plus.

bonne continuation jean-laurent !

slobade
07/01/2007, 23h58
alors la c'est du lourd :shock:

Comme il ne reste plus de place je reste debout au fond mais j'observe quand même. :love:

Jean-Laurent
09/01/2007, 21h08
Je suis preneur de tout ce qui peut améliorer la lisibilité du code. :poucehaut:
Par contre il faudra que tu m'aides un peu. J'ai un peu de mal avec ton exemple. :oops:
Paramètre de fonction? Si une variable est définie dans une classe, on ne peut pas la retrouver ailleurs, non? Elle appartient donc obligatoirement à la classe? Il faudra que tu me montres tout ça sur un exemple de code un peu plus long.

Toujours tout en douceur la suite: Création du tag.

Je rappel que je ne suis pas programmeur, donc que les vrais spécialistes (tarlack, phanault, majoul pour ne citer que ceux qui sont déjà intervenus sur ce post) n"hésitent pas à me corriger ou à apporter des précisions. (Comme ils l'ont déjà fait :prie:).

Il ne s'agit pour l'instant que de l'ébauche d'une interface. Par la suite, l'interface et le code seront développés conjointement. Ce n'est pas pour me disperser, mais une fois une petite interface en place, il est souvent plus facile de faire varier rapidement plusieurs paramètres du code. (Un peu comme les DU en Xpresso).

Pour créer un Plug-In, il suffit de créer un fichier texte avec le code et que ce fichier termine par l'extension .cof , on peut donc utiliser le bloc-note.
Ce n'est certainement pas l'idéal et il me semble qu'il existe des éditeurs de code qui permettent de "coloré" le code automatiquement (pour repérer les variables, les fonctions etc...) et d'afficher les numéros de ligne. (Pratique lorsque la console annonce une erreur à la ligne 408).

Toujours par soucis de simplifier au maximum la démarche initiale, je partirai d'un seul fichier .cof , mais par la suite on pourra hiérarchiser un peu mieux le Plug-In en utilisant plusieurs fichiers. (Un dossier ressource, langue etc...)

Il existe plusieurs sorte de Plug-In. Pour l'instant je partirai sur la base d'un "Expression Plugin Tag" qui est ce qu'il y a de plus proche pour l'instant du code qu'on a tapé.

Pour créer un tel Plug-In on utilisera 2 classes pour l'instant.

La classe "ExpressionPluginTag" et la classe "GeBaseDialog".

Pour ceux qui ne sauraient pas ce qu'est une classe (ce qui n'est pas loin d'être mon cas), il y a une très bonne explication dans le SDK ("code primer").
En résumé, il existe des classes toutes faites qui contiennent des fonctions et des variables prêtes à l'utilisation mais on peut aussi créer ses propres classes.

EX: Quand on utilise la classe "BaseObject" on dispose de fonctions comme GetPosition() qui permettent d'obtenir la position d'un objet.

A partir des classes existantes on peut créer des classes dérivées (et on pourra utiliser les mêmes fonctions) de la façon suivante:

class Ma_classe :GeBaseDialog
(Ma classe sera une classe dérivée de la classe "GeBaseDialog")
Puis on cite les fonctions et les variables que l'on souhaite utiliser.

{ public:
var ma_variable;
Ma_classe();
Ma_fonction();
}

Et pour définir une fonction appartenant à une classe:

Ma_classe:: Ma_fonction() { };


On peut y aller.

Pour commencer, on se contentera d'un code qui affiche le nom du tag et place une icône du tag sur un objet.




var iPLUGIN_ID = 1234567 ;
var sPLUGIN_NAME = "Ressort" ;
var sPLUGIN_HELP = "Gestion de ressorts" ;

var oTagIcon;



On définit quelques variables qu'on utilisera par la suite dans le code.



class Ressort_Tag : ExpressionPluginTag
{

public:

Ressort_Tag();

GetID();

MultipleAllowed();
DisplayAllowed();

GetIcon();
GetHelpText();

UseMenu();
GetName();

Edit();

Execute(doc, op);
}



On définit une classe "Ressort-Tag" (très personnelle) qui est une classe dérivée de la classe "ExpressionPluginTag" (toute faite avec tout un tas de fonctions pratiques.)

On cite ensuite dans "public:" toutes les fonctions de la classe "ExpressionPluginTag" que l'on va utiliser (pour l'instant).

Puis on utilise ces fonctions.



Ressort_Tag::Ressort_Tag() { super();}


super() est une fonction qui appelle le constructeur de la classe parent.
(Pour moi c'est du chinois :oops:, je sais juste qu'il faut le faire).



Ressort_Tag::GetID() { return iPLUGIN_ID ;}


On donne le numéro du Plug-In (on utilise la variable définie plus haut).
Ce numéro doit être unique, si deux Plug-In ont le même numéro aucun ne fonctionnera.
Pour être certain d'avoir un numéro unique, il faut en demander un gratuit.
Il me semble également qu'il existe des numéros tests??
Attention, j'ai mis ici un nombre arbitraire. :!:



Ressort_Tag::MultipleAllowed() { return False ;}

On précise qu'un seul tag de ce type pourra être associer à l'objet. Pour pouvoir mettre plusieurs tag on aurait mis TRUE.



Ressort_Tag::DisplayAllowed() { return TRUE;}

On précise que le tag sera visible. (False, si on le souhaite invisible).



Ressort_Tag::GetIcon() { return oTagIcon ;}

Ca va afficher l'icone du tag.


Ressort_Tag::GetHelpText() { return sPLUGIN_HELP ;}

C'est le message à afficher quand on demandera de l'aide sur le tag.


Ressort_Tag::UseMenu() { return TRUE ;}

Le nom du tag sera mis dans le menu.


Ressort_Tag::GetName() {return sPLUGIN_NAME ;}

C'est le nom du tag qui sera affiché. (sPLUGIN_NAME renvoit à "Ressort")


Ressort_Tag::Edit() { }

On verra cette fonction par la suite, elle ouvrira l'interface.


Ressort_Tag::Execute(doc, op) { }

Idem, c'est là qu'on mettra le code qui anime le ressort.


main()

{

var fn = GeGetRootFilename();
fn->RemoveLast();
fn->AddLast("ressort.tif");

oTagIcon = new(BaseBitmap,1,1);
oTagIcon->Load(fn);

Register(Ressort_Tag);
}



Ces dernières lignes vont chercher le fichier nommé "ressort.tif" qui contiendra l'image du tag. Au final cette image sera "contenue" dans la variable "oTagIcon" que l'on affiche un peu plus haut. (Ressort_Tag::GetIcon() { return oTagIcon ;})

Je présume qu'il y a aussi une précaution à prendre si le fichier n'existe pas? Je ferrai une petite recherche.

Résumé:


var PLUGIN_ID = 1234567 ;
var PLUGIN_NAME = "Ressort" ;
var PLUGIN_HELP = "Gestion de ressorts" ;

var oTagIcon;

class Ressort_Tag : ExpressionPluginTag
{

public:

Ressort_Tag();

GetID();

MultipleAllowed();
DisplayAllowed();

GetIcon();
GetHelpText();

UseMenu();
GetName();

Edit();

Execute(doc, op);

}

Ressort_Tag::Ressort_Tag() { super();}

Ressort_Tag::GetID() { return iPLUGIN_ID ;}

Ressort_Tag::MultipleAllowed() { return False ;}
Ressort_Tag::DisplayAllowed() { return TRUE;}

Ressort_Tag::GetIcon() { return oTagIcon ;}
Ressort_Tag::GetHelpText() { return sPLUGIN_HELP ;}

Ressort_Tag::UseMenu() { return TRUE ;}
Ressort_Tag::GetName() {return sPLUGIN_NAME ;}

Ressort_Tag::Edit() { }

Ressort_Tag::Execute(doc, op) { }


main()

{

var fn = GeGetRootFilename();
fn->RemoveLast();
fn->AddLast("ressort.tif");

oTagIcon = new(BaseBitmap,1,1);
oTagIcon->Load(fn);

Register(Ressort_Tag);
}

Pour l'instant notre tag se contente de placer une icône sur un objet.
http://cinema4d.chez-alice.fr/r6.JPG

Demain on crée l'interface. :odile:

tarlack
09/01/2007, 22h34
un exemple un peu mieux, il est vrai que celui que j'ai mis était un peu....non, très mauvais :mrgreen:

imaginons qu'on ait une classe qui ayant comme variables membre (sans appliquer la notation définie plus haut) :
a, b, f, h

dans cette classe, il y a une fonction, dont le code est (à la syntaxe COFFEE près) :
public MaClasse::operation1 (c, e, g) {
var d, i;
d = b * c + e / a;
i = h * d - (g * a + d / 2);
b = (a * b + c * d) * i;
}

question : peux tu me dire en un clin d'oeil qui est quoi, où est déclaré qui, quelles variables seront aussi disponibles dans d'autres méthodes ? (au passage, le 1er qui me file un code avec des variables aussi mal nommées à relire, je lui fait tout réécrire, je force le trait pour l'exemple ici :D )
Cette question n'est pas qu'une question en l'air. Quand on code, il se peut que l'implémentation de la fonction que l'on est en train de coder soit longue, et/ou loin (si dans le meme fichier, ce qui n'est pas forcément le cas) de la déclaration des membres de la classe. Et les allers retours incessants, je peux te garantir que ca augmente le risque d'erreur et ca fait perdre du temps. En plus, ca ne permet pas de voir immédiatement que cette fonction modifie la valeur d'un des membres de la classe.

Maintenant, la meme chose avec la notation (on suppose que les variables membres ont été renommées) :

public MaClasse::operation1 (c_, e_, g_) {
var d, i;
d = m_b * c_ + e_ / m_a;
i = m_h * d - (g_ * m_a + d / 2);
m_b = (m_a * m_b + c_ * d) * i;
}

On ne sait certes toujours pas à quoi correspond chaque variable (est-ce un coeff, une constante d'une formule, le parametre d'une simulation, etc), mais ce n'est pas le role de la methode, c'est le role du nommage des variables. Mais là, en un clin d'oeil, on sait que la fonction modifie le membre de classe nommé "b", et que parmi les variables utilisées, seules m_b, m_a et m_h sont accessible depuis une autre methode de la meme classe. On sait aussi que "d" et "i" sont des variables intermediaires, qui n'ont pas d'existence en dehors de la fonction, et que ce sera à l'appelant de décider des valeurs de "c", "e" et "g". Ca peut paraitre inutile de pouvoir dire ca en un clin d'oeil alors qu'on peut le voir en lisant le code du dessus et en voyant où chaque variable est déclarée, mais quand les fonctions sont longues ca permet de s'eviter quelques maux de tete à relire très attentivement du code.

Jean-Laurent
10/01/2007, 13h05
Merci beaucoup tarlack d'avoir pris le temps de préciser. :poucehaut:
C'est maintenant beaucoup plus claire pour moi, et je comprends surtout bien mieux l'utilité.
Même si je ne suis pas encore sûr de pouvoir tout nommer correctement.
Tu restes dans le coin, hein? :)
Pour les fonctions des classes existantes en coffee, je ne suis pas encore certain qu'on puisse changer l'appellation des paramètres de fonction. (Il faut que je teste ça.)
Une dernière chose me turlupine. (Liée à mon faible niveau :oops:)
Comment distingues-tu dans ta notation les variables globales? (déclarées en début de document et en dehors de toute classe.) Elles ne sont pas déclarées dans la classe donc n'ont pas le droit au m_ et ne sont pas locales.


La suite: L'interface.

Après avoir créé le tag (enfin, pour l'instant il se contente d'exister) , on va créer l'interface avec une classe dérivée de la classe "GeModalDialog".

Il y a les dialogues modaux (je sais pas si c'est la bonne traduction) et les non-modaux (plus difficiles à mettre en place.) Un dialogue est dit modal, si il faut fermer l'interface avant de pouvoir réutiliser le logiciel. C'est le cas de beaucoup de Plug-In. On ouvre l'interface, on fait les changements nécessaires et on la referme pour repasser à C4D.



class Dialog : GeModalDialog
{

public:

Dialog();

CreateLayout();
}


Notre classe se nomme donc "Dialog" et dérive de la classe " GeModalDialog" déjà existante avec tout un tas de fonctions pratiques que l'on va utiliser.

Pour l'instant on n'utilisera que les deux fonctions nécessaires à l'affichage de l'interface, la fonction Dialog() qui comme souvent pour chaque classe "se contente" d'appeler le constructeur parent" (Toujours aussi opaque pour moi)



Dialog::Dialog() {super();}


Et la fonction CreateLayout(); dans laquelle on va ajouter toutes les fonctions qui permettent d'obtenir une interface esthétique. Des sliders, des boutons, des champs de texte, des preview, des images de sa femme etc...)



Dialog::CreateLayout()
{
SetTitle(sPLUGIN_NAME);

AddStaticText(500,BFH_SCALEFIT,0,0,"Raideur:",0);
AddEditSlider(4000,0,50,0);

AddStaticText(501,BFH_SCALEFIT,0,0,"Longueur à vide:",0);
AddEditNumberArrows( 4001,0, 30, 0 );

AddStaticText(502,BFH_SCALEFIT,0,0,"Longueur maximale:",0);
AddEditNumberArrows( 4002,0, 30, 0 );

}


La fonction SetTitle() permet bien évidemment de mettre le titre de la fenêtre qui s'ouvre quand on utilise le plug-in ou qu'on double-clique sur le tag.
Ici ce sera "Ressort" ( car la variable sPLUGIN_NAME fait référence au texte "Ressort", voir plus haut).

La fonction AddStaticText() permet d'afficher un texte dans l'interface.

Le premier nombre (ici 500) est le numéro ID du texte. Très important, c'est le numéro qui "fait référence" à cet élément. Si plus loin dans le code, on parlera de l'élément 500, c'est de ce texte là dont il s'agit. La fonction COLORER_EN_ROUGE (500); agira sur ce texte.

Pour l'instant je tape directement les numéro ID, mais par la suite on pourra simplifier en les remplacant par des variables (AddStaticText(iText_ID,BFH_SCALEFIT,0,0,"Raideur:",0);)
Qui seront toutes déclarées dans une fonction "enum" qui évite de taper 5001,5002,5003,5004,5005 etc....

Le deuxième paramètre (ici " BFH_SCALEFIT" ) permet de placer le texte comme on le souhaite, à gauche, à droite, centré etc... Une liste exhaustive des possibilités se trouve dans le SDK.

Les deux suivants (ici, 0 et 0) sont la largeur et la hauteur du texte. Le texte prenant de toute manière la place minimum dont il a besoin lorsque c'est possible.

Le paramètre suivant est le texte du texte.

Enfin le dernier correspond à la bordure (ici 0 donc pas de bordure) dont il existe plusieurs styles. (Liste complète dans le SDK).

La fonction AddEditSlider () ajoute un slider.

Le premier paramètre est encore l'ID qui fait référence au slider, le suivant le "flag" (centré, à droite, etc...) les deux derniers la taille.
NB: Les valeurs du slider, les limites etc... ne se définissent pas dans la classe dialogue qui se contente de l'affichage de l'interface.

La fonction AddEditNumberArrows() fonctionne comme les précédentes, sauf qu'il s'agit d'une case avec un nombre.

Il reste juste quelques lignes de code à ajouter dans la classe précédente. Celle qui gère le plug-in.

Dans la fonction Edit() que l'on avait laissé vierge et qui décrit ce qui se passe quand on ouvre le plug-In.



Ressort_Tag::Edit()
{ var d = new(Dialog) ;
d->Open(-1,-1) ; }


Maintenant, quand on clique sur le tag, ça ouvre le dialogue créé précédemment.
Open(-1,-1) est un code qui signifie que la fenêtre s'ouvre à l'endroit où se trouve la souris.
Si on entre des valeurs différentes comme par exemple: Open(200,300), la fenêtre du tag s'ouvrira à 200 pixels du bord gauche de l'écran et à 300 pixels du haut de l'écran.

http://cinema4d.chez-alice.fr/r7.JPG
http://cinema4d.chez-alice.fr.Ressort.cof
http://cinema4d.chez-alice.fr.ressort.tif

Pour l'instant on a créé une interface vide, sans aucune interaction avec notre ressort. C'est le prochain thème à venir. :odile:



Une nouvelle mise en garde pour ceux qui débarqueraient. Il ne s'agit en aucun cas d'un cours, mais d'une tentative de progression commune. Les erreurs grossières sont certainement nombreuses. Merci donc par avance à tous ceux qui auraient la gentillesse de me corriger. :prie:
N'hésiter pas non plus, à intervenir si vous ne comprenez pas certains points. :wink:

ZOZO
10/01/2007, 13h27
:bounce: roh lolo mais comment je kiffe ! :nono:

La manière dont tu décrit la création de ton plug est très instructive.

Je n'ai aucune critique très constructive à apporte étant donné que je suis une sous fiotte en prog.

Je te soutient néamoins entièrement et te suis attentivement !

Rudy
10/01/2007, 14h07
Je vais suivre très attentivement :poucehaut:

xs_yann
10/01/2007, 15h45
Très interessant. :poucehaut:
Les étapes de création du plug sont très instructives, j'ai hate de voir l'interaction interface-ressort :bounce:
Les calculs sont un peu compliqués :o
Merci beaucoup. :prie:

Pyerow
10/01/2007, 20h16
*mal de crane* :coup:

j'ai âssé une bonne partie de l'après midi à lire et à relire pour etre sur d'avoir compris, je pense y etre arrivé à peu près :mrgreen:




merci :prie: :odile:

tarlack
10/01/2007, 20h49
ah, oui, les variables globales...j'ai l'habitude de ne jamais en utiliser sauf impossibilité de faire autrement...mais dans ces cas là, tu peux très bien mettre un "g_" par exemple, qui fera très bien l'affaire :)
pour les parametres des fonctions, tu veux parler de quand tu es dans un noeud Xpresso, où les parametres sont pré-déclarés ?
si c'est ca, tu peux toujours inverser la notation ("_" après les vars locales), ou mettre un prefixe en "l_" par exemple..mais encore une fois, la notation que j'ai présentée n'a rien d'un standard, ce n'est qu'une idée comme autre, tu peux très bien inventer la tienne si tu la trouves plus claire que la mienne ! ;)

Jean-Laurent
11/01/2007, 12h10
Je te soutient néamoins entièrement et te suis attentivement !



Je vais suivre très attentivement :poucehaut:



j'ai âssé une bonne partie de l'après midi à lire et à relire pour etre sur d'avoir compris, je pense y etre arrivé à peu près :mrgreen:


Merci à tous, alors interro à la fin du mois. :mrgreen:



Les étapes de création du plug sont très instructives, j'ai hate de voir l'interaction interface-ressort :bounce:
Les calculs sont un peu compliqués :o


C'est rassurant que tu les trouves compliqués, autrement tu serais un prodige mathématique. :wink:
Je te rassure c'est des calculs de classes scientifiques, qu'on oubli souvent par la suite.
C'est surtout le principe et la création de l'interface que je voudrais le plus clair possible.
L'interaction, c'est pour demain normalement. :odile:



ah, oui, les variables globales...j'ai l'habitude de ne jamais en utiliser sauf impossibilité de faire autrement...mais dans ces cas là, tu peux très bien mettre un "g_" par exemple, qui fera très bien l'affaire :)
pour les parametres des fonctions, tu veux parler de quand tu es dans un noeud Xpresso, où les parametres sont pré-déclarés ?


Il faudra que quelqu'un m'explique alors comment les éviter. :oops:
Dans la suite justement, je suis obligé d'utiliser des variables globales.

Je rappel que la classe "Dialog" sert à créer l'interface. Et la classe "ExpressionPlugIn" à faire tourner le plug-in.
Quand je touche un slider ça modifie une variable de la classe dialog, et je voudrais que ce changement se répercute dans la classe expressionPlugIn pour que mon ressort bouge en conséquence.

Donc pour l'instant j'utilise une variable globale qui est modifiée dès que je touche à l'interface dans les deux classes en même temps. Je ne sais pas si c'est la bonne méthode.

J'ai épluché tous les exemples donnés dans le SDK mais tous sont très ciblés et aucun ne correspond exactement à ce que je souhaite faire. Par contre il existe évidemment pleins de plug-in qui fonctionnent sur ce principe mais on a des fichier .cob (b pour binary, ils sont compilés) le plus souvent et pas .cof ce qui ne permet pas d'en étudier le code. On n'accède souvent qu'à l'interface. (Les dialogues étant dans un fichier à part).

Pour les paramètres de fonction je pensais à main(doc,op) par exemple, command (id, msg) etc...
Mais ça marche effectivement avec main(doc_,op_), il comprend sans problème que doc_ est le document.
C'est logique mais j'avais un doute. :oops:

Demain la suite, avec un peu de mouvement. :odile:

Jean-Laurent
11/01/2007, 15h31
C'est déjà demain. :mrgreen:

Interaction entre l'interface et le ressort.

On va rajouter à la classe Dialog créée précédemment 2 nouvelles fonctions:
La fonction Init() et la fonction Command(id, msg).

La première est une fonction qui détermine ce qui se passe à l'ouverture du dialogue (donc de l'interface) et la deuxième "enregistre" les changements de l'interface (comme le déplacement d'un slider ou un bouton cliqué etc...).



class Dialog : GeModalDialog
{
public:

Dialog();

CreateLayout();
Init();
Command(id, msg);
}


Dans la fonction init() on va initialiser la valeur des sliders et des boites à nombre (?) définis avant.



Dialog::Init()
{SetInt( 4000, 5, 0, 100, 1 );
SetInt( 4001, 400, 0, 2000, 10 );
SetInt( 4002, 3000, 0, 4000, 100);

k=5;
l0=400;
iLimite=3000;

vx=0;vy=0;vz=0;
}


La fonction SetInt() permet de rentrer une valeur entière (integer), il existe bien entendu aussi une fonction SetFloat()

SetInt( 4000, 5, 0, 100, 1 );

Le premier nombre (ici 4000) fait référence au numéro ID. (Voir post précédent). 4000 renvoi donc à notre premier slider. (Rappel: AddSlider(4000, etc...))

Le deuxièmenombre (ici 5) est la valeur du slider à l'ouverture de l'interface.

Les nombres suivants (0 et 100) sont les valeurs min et max du slider.
Le dernier nombre (ici 1) est le pas.

On précise donc à l'ouverture de l'interface que le premier slideur ("raideur du ressort") prend des valeurs entières de 0 à 100 et peut varier de 1 en 1.

SetInt( 4001, 400, 0, 2000, 10 );
Initialise donc la longueur à vide du ressort en lui donnant la valeur par défaut 400.

SetInt( 4002, 3000, 0, 4000, 100);
Initialise la valeur maximale d'allongement du ressort. (3000).

k=5;
l0=400;
iLimite=3000;

On a initialisé les sliders et les boites, mais ceci n'a affecté que l'interface.
Pour que les variables concernées soient également initialisées. On fixe également leurs valeurs par défaut.
Il ne faut pas oublier de déclarer ces variables en tout début de code.


var iPLUGIN_ID = 1234567 ;
var sPLUGIN_NAME = "Ressort" ;
var sPLUGIN_HELP = "Gestion de ressorts" ;

var oTagIcon;

var k,l0,iLimite;
var vx,vy,vz;


vx=0;vy=0;vz=0;

Ligne qui n'est pas obligatoire, mais pratique pour l'instant.
Chaque fois qu'on ouvre le tag (ce qui signifie généralement qu'on veut apporter une modification, on annule la vitesse précédente du ressort). Dans le cas contraire, le nouveau ressort partirait avec une vitesse initiale. Il peut même tourner en rond, en plus d'osciller, selon sa direction.

Remarque: Pour l'instant, à chaque fois qu'on ouvre l'interface, c'est les valeurs par défaut qui se réinstallent. On verra par la suite comment conserver les valeurs précédentes.


Dans la fonction command() on change les valeurs des variables quand on touche aux sliders etc...



Dialog::Command(id, msg)
{
switch (id)
{
case 4000:k = GetInt(4000);break;
case 4001:l0 = GetInt(4001);break;
case 4002:iLimite = GetInt(4002);break;
}
}


la fonction switch (id) réagit selon le numéro ID de l'objet qu'on touche.
Si on touche l'objet dont le numéro ID est 4000 (ici, c'est le slider) alors c'est la ligne de code suivant "case 4000:" qui est lue.

Donc quand on touche à notre slider:
k = GetInt(4000);break; On change la valeur de notre variable k (raideur) et on la remplace par la valeur affichée par le slider grace à la fonction GetInt().
break permet de passer à la suite.

Il ne reste plus qu'à placer notre code (voir les premiers posts), avec les changements de majoul dans la seule fonction encore vierge de la classe "ExpressionPlugIn" la fonction Execute(doc,op). En retirant les déclarations de variables déjà faites.

Cette fonction Execute(doc,op) remplace la fonction de l'expression main(doc,op) et joue le même rôle. Elle est exécutée par le plug-In à chaque frame ou à chaque action dans le logiciel.


Pour l'instant. On a donc créé un Plug-In franchement moyen, qui se contente de remplacer notre tag coffee précédent, mais qui présente déjà l'avantage de pouvoir modifier rapidement les 3 paramètres suivants, la raideur du ressort, sa longueur à vide et sa longueur maximale.

Par la suite, on va se débrouiller pour que le tag prenne en compte l'objet sur lequel on le place, qu'il faille choisir un deuxième objet par glisser-déposer et que soit automatiquement créé tout le reste. :odile:

Une question bête au passage. A chaque fois qu'on modifie le code, est-on obligé de relancer C4D ou y a-t-il un moyen plus simple pour recharger le Plug-In?

jcBug
11/01/2007, 16h44
:o
Jean-Laurent, merci, c'est le bonheur à l'état brut,* :love:
ça me rappelle le basic GFA sur Atari en 1987...
Je ne sais pas comment je vais faire,
mais il faudra absolument que je fourre
un ressort dans mes prochaines créations !* :mrgreen:

Je ne peux pas répondre directement à ta question,
mais Majoul n'est pas loin...

tabou
11/01/2007, 18h08
Félicitations Jean-Laurent pour tes explications très détaillées, c'est une mine d'or pour ceux qui veulent s'initier aux arcanes de la programmation* :poucehaut:
Pour recharger les plugs il y a la commande Recharger les modules C.O.F.F.E.E. dans le menu modules externes de la console, mais ça ne marche pas à tous les coups, il faut parfois redémarrer le soft pour que certains plugs se chargent.

paspas
12/01/2007, 14h27
salut jean laurent

merci pour ces explication :bounce:


De nombreuses questions sur le forum (faire flotter un objet sur une mer, animer un bras mécanique, chute libre d'objets etc...)

j ai eu la meme idee mais plutot du cote purement mecanique : automatiser des mouvement simples un bras (2 ou 3 section )
levier , rotation pour en fair une chaine

je posterait les premier pas dans un autre post bientot avec explication

mais grace a tes explication j' espere en fair un plug ! encore merciiii !!!!!

paspas

Jean-Laurent
12/01/2007, 17h34
Merci à tous pour ces beaux compliments. :love:

JCBug: L'idée peut-être étendue à tout mouvement oscillant. C'est une interaction de type ressort, pas forcément un ressort. Ca peut très bien être la poitrine d'une Pin-Up courant sur une plage par exemple. :mrgreen:

Merci Tabou, je vais gagner un temps précieux grâce à toi. :prie:
La question me taraudait depuis un moment mais je n'osais pas la poser, cherchant l'évidence. Mais quand c'est trop simple on ne trouve pas. :wink:

Je vais suivre ça avec attention alors paspas, les couples, les moments, que du bonheur. 8)

La suite. Pas encore de révolution, une lente progression.

Amélioration de l'interface.

Si vous avez tout suivi attentivement (au moins les derniers posts en diagonal :wink:) vous comprenez donc qu'on ne touchera aujourd'hui qu'à la classe "Dialog".

En l'absence de code, le logiciel place automatiquement les éléments de l'interface à la verticale, les uns à la suite des autres. Ce qui n'est pas très esthétique.
Pour y remédier, nous allons utiliser les fonctions: AddGroupBeginV()
et AddGroupBeginH() qui permettent de créer des "groupes d'éléments ", un peu comme des tableaux pour une page web.

Nous allons placer nos éléments ainsi:
http://cinema4d.chez-alice.fr/r8.JPG



Dialog::CreateLayout()
{
SetTitle(sPLUGIN_NAME);

AddGroupBeginV(5000,BFH_SCALEFIT,2,"Fenêtre",0);
{

AddGroupBeginH(5001,BFH_SCALEFIT,4,"Caractéristiques",0);
{
AddGroupBorder(BORDER_GROUP_IN);
AddGroupSpace( 10, 10 );

AddStaticText(500,0,0,0,"Raideur:",0);
AddEditSlider(4000,0,50,0);

AddGroupBeginV(5002,BFH_SCALEFIT,2,"Longueurv",0);
{AddGroupSpace( 10, 10 );
AddStaticText(501,BFH_SCALEFIT,0,0,"Longueur à vide:",0);
AddEditNumberArrows( 4001,0, 50, 0 );} AddGroupEnd();

AddGroupBeginV(5003,BFH_SCALEFIT,2,"Longueurm",0);
{AddGroupSpace( 10, 10 );
AddStaticText(502,BFH_SCALEFIT,0,0,"Longueur maximale:",0);
AddEditNumberArrows( 4002,0, 50, 0 );} AddGroupEnd();

} AddGroupEnd();

AddGroupBeginH(5004,BFH_SCALEFIT,4,"propriétés",0);
{ AddGroupBorder(BORDER_GROUP_IN);
AddGroupSpace(9,9 );
AddGroupBeginV(5005,BFH_SCALEFIT,2,"ag",0);
{AddGroupSpace( 10, 10 );
AddStaticText(503,BFH_SCALEFIT,0,0,"Amortissement:",0);
AddEditNumberArrows( 4003,0, 0, 0 );} AddGroupEnd();

AddCheckbox( 4004, BFH_SCALEFIT, 0, 0, "Extremité fixe" );

AddGroupBeginV(5006,BFH_SCALEFIT,2,"ag",0);
{AddGroupSpace( 10, 10 );
AddStaticText(504,BFH_SCALEFIT,0,0,"Objet1:",0);
AddEditText( 4005, BFH_SCALEFIT, 200, 0 );} AddGroupEnd();

AddGroupBeginV(5007,BFH_SCALEFIT,2,"ag",0);
{AddGroupSpace( 10, 10 );
AddStaticText(505,BFH_SCALEFIT,0,0,"Objet2:",0);
AddEditText( 4006, BFH_SCALEFIT, 200, 0 );} AddGroupEnd();


}AddGroupEnd();
} AddGroupEnd();
}


Il ne faut pas prendre peur, c'est toujours la même chose. :)

La fonction AddGroupBeginV(5000,BFH_SCALEFIT,2,"Fenêtre",0);

Elle permet de créer notre premier groupe. Chaque groupe doit avoir un numéro ID distinct qui y fait référence (ici 5000), puis c'est un "flag" habituel (qui permet de centrer, de décentrer etc... la liste de tous les flags possibles est dans le SDK).

Le chiffre 2 est très important, il désigne le nombre d'éléments du groupe.
Ici c'est le groupe1 du schéma précédent avec 2 éléments (eux même des groupes, le 5001 et le 5004 dans le code, les groupes 2 et 3 sur le schéma).

Ensuite c'est le nom du groupe (qui n'apparaîtra pas ici visuellement), et le dernier chiffre (ici 0) c'est les "Groupflags" , pas essayé, mais je pense que ça doit être une autre possibilité pour faire des bordures etc...
Ex:
Use one of the following types: BORDER_GROUP_IN Standard border BORDER_NONE No border BORDER_THIN_IN Thin border inward BORDER_THIN_OUT Thin border outward BORDER_IN Normal border inward BORDER_OUT Normal border outward BORDER_GROUP_OUT Group border outside BORDER_OUT2 Outward border 2

Evidemment le V après "AddGroupBegin" signifie vertical et le H horizontal.
Attention, dans les groupes H, les éléments se placent à la verticale et dans les groupes V à l'horizontal. (Il y a une meilleure explication que ça dans le SDK pour les plus curieux.)

AddGroupBorder(BORDER_GROUP_IN);

Ajoute une bordure à un groupe.

AddGroupSpace(9,9 );

Détermine l'espace horizontal et vertical entre les éléments du groupe.

AddCheckbox( 4004, BFH_SCALEFIT, 0, 0, "Extremité fixe" );

Ajoute une "boite à cocher" dont le nom qui sera affiché à droite sera "Extrémité fixe".
(Pour le reste tout est déjà expliqué précédemment, n°ID etc...)

AddEditNumberArrows( 4002,0, 50, 0 );

Celle là on l'a déjà vu, mais je fais juste remarquer que le chiffre affiché étant 3000 il se sentait à l'étroit dans sa petite boîte. Du coup la valeur fixée de 50 pixel de large lui donne plus d'espace.

Le résultat:

http://cinema4d.chez-alice.fr/r9.JPG

Je vous avais mis en garde, je ne suis pas programmeur.
Je réitère, je ne suis pas non plus graphiste. :puke:

Il ne reste plus qu'à se débrouiller maintenant pour que tout ça agisse.
C'est pour bientôt. :odile:

Jean-Laurent
16/01/2007, 22h03
Toujours sans violence. :wink:

La mise en accord de l'expression et de l'interface.

Une petite remarque préliminaire:

Suite au conseil judicieux de majoul j'ai remplacé les lignes de code obsolètes: (Cf p2)

var chelix=oHelix->GetContainer();
chelix->SetData(PRIM_HELIX_HEIGHT,-fDistance);
oHelix->SetContainer(chelix);

par: oHelix#PRIM_HELIX_END=-fDistance; ce qui revient au même, mais en plus facile.

Et bien hélas non. :cry2:
Je n'avais pas fais attention mais la hauteur de la primitive hélice ne peut pas être négative. Mettre une valeur négative était l'astuce trouvée pour placer le ressort dans le bon sens.
Or, avec la fonction SetData(), ça marche. :o
En attendant de faire mieux (deux angles et une rotation et ça marchera) et pour pouvoir utiliser le tag cible, je laisse le vieux code.

Par contre je n'oublie pas la ligne supplémentaire:

if (!oHelix||!oSphere1||!oSphere2) {return;}

if (!oHelix) {} La fonction if() exécute ce qui suit (le code entre accolade) si la condition (entre crochet) est vrai. Mais ça marche aussi avec 0, NILL (Rien) ou une valeur.
Toutes les valeurs (12, 256, PI, "hello", un_objet etc...) seront considérée comme TRUE.
Seules le 0 ou le NILL sont considérés comme False.

Ainsi, if(126) {println ("hello";} affichera "hello" dans la console,
tandis que if(0) {println ("hello";} n'affichera rien.

Le "!" signie le "contraire de" (lire "NON"). Ex: a = = b (a est équivalent à b) a!=b (a est différent de b)

if (!oHelix) signifie donc "Si l'objet oHelix n'existe pas". Puisque s'il n'existe pas oHelix sera équivalent à False et donc !oHelix à TRUE.

Le "||" signifie OU, il suffit qu'une seule des trois conditions soit vérifiée pour exécuter le code qui suit.



Revenons à notre interface:
On avait défini la fonction Init(); de la classe Dialog qui fonctionne à chaque ouverture du dialogue.

Dialog::Init()
{SetInt( 4000, 5, 0, 100, 1 );
SetInt( 4001, 400, 0, 2000, 10 );
SetInt( 4002, 3000, 0, 4000, 100);

k=5;
l0=400;
iLimite=3000;
}

Le problème c'est qu'à chaque fois qu'on double-clique sur le tag, il réinitialise ces valeurs et efface les précédentes.

Pour y remédier:
On va initialiser les variables globales dans la fonction main() du Plug-In.



main()

{
k=5;
l0=400;
iLimite=3000;

var fn = GeGetRootFilename();
fn->RemoveLast();
fn->AddLast("ressort.tif");

oTagIcon = new(BaseBitmap,1,1);
oTagIcon->Load(fn);

Register(Ressort_Tag);
}


Et ne laisser que le code:


Dialog::Init()
{
SetInt( 4000, k, 0, 100, 1 );
SetInt( 4001, l0, 0, 2000, 10 );
SetInt( 4002, iLimite, 0, 4000, 100);
}


La fenêtre s'ouvrira donc à chaque fois avec les bonnes valeurs.

Faisons également autre chose qui me tient à coeur et que je ne pouvais faire précédemment.
Remplaçons les variables vx,vy,vz, par un unique vecteur vitesse:
var vVitesse= vector(vx,vy,vz);

Ainsi pour annuler la vitesse: vVitesse= vector(0,0,0);

Le code devient donc:


if (fDistance<iLimite)
{vVitesse.x+=vForce.x*dt;
vVitesse.y+=vForce.y*dt;
vVitesse.z+=vForce.z*dt;

vPos2.x+=vVitesse.x*dt;
vPos2.y+=vVitesse.y*dt;
vPos2.z+=vVitesse.z*dt;


La notation vVitesse.x permet évidemment d'accéder à la coordonnée x du vecteur vitesse.

Ajoutons maintenant l'amortissement.

J'en ai déjà parlé précédemment (Cf p2), il suffit de le généraliser à toutes les dimensions.

On déclare la variable globale avec les autres:



var k,l0,iLimite,fFrot;
var vVitesse;


On pense à initialiser sa valeur dans l'interface (classe Dialog) et dans la fonction main() et à la récupérer quand on la modifie dans la fonction command() de la classe Dialog ;



SetFloat( 4003, fFrot, 0, 9, 1);



fFrot=0;



case 4003:fFrot = GetFloat(4003);break;
On pourra choisir un chiffre entre 0 et 9, initialement il n'y aura pas d'amortissement.

On rentre la formule physique:



var vAmortissement=(0.1*fFrot)*vVitesse;

vVitesse.x+=(vForce.x-vAmortissement.x)*dt;
vVitesse.y+=(vForce.y-vAmortissement.y)*dt;
vVitesse.z+=(vForce.z-vAmortissement.z)*dt;


L'amortissement (cf p2) est proportionnel à la vitesse. Puis le ressort va vite et plus il freine.
Comme la résistance de l'air en voiture qui augmente avec la vitesse.

Il s'oppose en permanence à la vitesse (pas forcément à la force contrairement à l'impression donnée par l'équation). La force est parfois opposée à la vitesse.

Demain, la suite avec la création automatique de le l'hélice représentant le ressort, le placement automatique du tag sur celle-ci, ainsi que le choix possible des objets à relier entre eux et la fonctionnalité "extrémité fixe". :odile:

phanault
16/01/2007, 23h32
Oulà-là !!!

Voilà que je reviens après une semaine d'absence et que vois-je ? :shock: :arg:

Quel travail accompli par notre ami Jean-Laurent ! :poucehaut:

Je n'y comprend absolument rien et je suis encore sur le "luc". :efface:

Il me faudra des jours pour saisir tout le travail qu'il y a là dedans.

Bravo à notre géni, Bravo Jean-Laurent !!! :efface:

Jean-Laurent
17/01/2007, 20h06
Merci beaucoup Phanault, mais ça tient plus de la copie de l'étudiant attendant la correction des maîtres que de l'article de génie. :oops:

On se rapproche calmement de la fin de la première étape.

Création automatique de l'objet ressort, et possibilité de désigner les objets interagissant.

Dans la fonction "execute" de la classe "ExpressionPlugIn" on récupère le nom de l'objet qui porte le tag. Cet objet est alors désigné d'office comme faisant partie de l'interaction (et sera éventuellement l'extrémité fixe).



sObjet=op->GetName();


op désignant l'objet portant le tag, la fonction GetName() récupérant son nom dans une variable "sObet" que l'on n'oubliera pas de déclarer globale en début de code.

Dans la classe "Dialog" on récupère cette variable pour l'afficher dans la case de l'interface "objet 1":


AddStaticText(4005,BFH_SCALEFIT,0,0,sObjet,0);

Le changement de nom, ne sera pas immédiat mais visible à la prochaine ouverture de l'interface. (On peut sans doute faire mieux, je verrai par la suite.)

On récupère de la même manière le nom de l'objet 2, qui sera entré manuellement dans l'interface avec la fonction GetString().
Dans la classe "Dialog":


case 4006:sObjet2=GetString(4006);break;


GetString(4006); Contrairement à la première impression cette fonction ne signifie pas "satisfaire madame", mais récupérer une chaîne de caractère dans l'objet désigné par le numéro ID 4006, c'est à dire l'Edit Text créé précédemment.

On remplace partout dans le code précédent les anciennes notations par les nouvelles:
Ex:

var oObjet2 = doc->FindObject(sObjet2);
var oObjet = doc->FindObject(sObjet);


Création automatique du ressort dans la fonction "execute" de la classe "ExpressionPlugIn":


if (!oRessort) {
var oRessort=AllocObject(Osplinehelix);
doc->InsertObject(oRessort,NULL,NULL);
oRessort->SetName( "Ressort" );
oRessort#PRIM_HELIX_RADIUS1=20;
oRessort#PRIM_HELIX_RADIUS2=20;
var oCible=AllocTag(Ttargetexpression);
oRessort->InsertTag(oCible,NULL);
oCible#TARGETEXPRESSIONTAG_LINK=op;}


if (!oRessort) {} Comme expliqué précédemment, on ne crée le ressort que si il n'existe pas déjà. Ceci afin de ne pas avoir un nouveau ressort à chaque image.
Il serait également possible de ne le créer qu'à l'appel du tag, pour laisser la possibilité à l'utilisateur de le supprimer. Là, tant que le tag existe le ressort est indestructible.

var oRessort=AllocObject(Osplinehelix);

La fonction AllocObjet() permet de créer (je pense un peu à la manière de New()) n'importe quel objet (primitive, lumière, déformateur etc...). La liste complète est dans le SDK. Ici Osplinehelix créera une primitive hélice.

L'objet créé n'est pas inséré dans le document, c'est le rôle de la fonction:

doc->InsertObject(oRessort,NULL,NULL);

Les deux derniers termes "NULL" désignent éventuellement les parents et enfants de l'objet.

oRessort->SetName( "Ressort" );

On donne un nom explicite à notre ressort, afin de l'identifier en début de code.
Si l'objet Ressort existe déjà, il n'en est pas créer de nouveau.

oRessort#PRIM_HELIX_RADIUS1=20;
oRessort#PRIM_HELIX_RADIUS2=20;

Pour une question d'esthétisme, on règle le rayon du ressort avec la formulation la plus simple (Voir le post de Majoul).

var oCible=AllocTag(Ttargetexpression);
oRessort->InsertTag(oCible,NULL);
oCible#TARGETEXPRESSIONTAG_LINK=op;

Selon les mêmes principes qu'au-dessus, on attribue une expression cible à notre ressort qui pointera toujours sur l'objet portant le tag.


Voilà, il ne reste plus que le bouton "extrémité fixe" ce qui ne devrait pas poser trop de problème.

Les fichiers: http://cinema4d.chez-alice.fr/Ressort2.cof
http://cinema4d.chez-alice.fr/ressort.tif


Pour l'instant, on a créé un tag de peu d'intérêt qui permet de relier deux objets quelconques entre eux par une interaction de type ressort.
La même chose peut se faire en quelques clicks avec une formule du type A exp(-h*t) sin (w*t) où A est l'amplitude du mouvement, h l'amortissement et w la pulsation ("vitesse d'oscillation"). Où encore avec quelques lignes temporelles de la Time-Line.
L'intérêt de cette méthode est qu'il ne s'agit pas d'un résultat tout fait correspondant à un cas simple mais d'une simulation basée sur la notion de force.

L'étape suivante est maintenant de coupler plusieurs ressorts entre eux.
Ce qui peut se faire de façon très simple par simple addition des forces.
F = F1+F2+F3 etc...

On peut donc imaginer par la suite de nouvelles forces, comme la gravitation, frottement solide,etc...

Et là, impossible de sortir des formules simples toutes faites ou encore de prévoir ce que sera le mouvement.

Reste à savoir comment faire. :wip:
Si vous avez des pistes sur les questions suivantes:
Est-il possible de passer des variables d'un tag à un autre?
(Question déjà posée par paspas...)
Si on place plusieurs expressions tags identiques dans une scène, si on change les variables de l'un, ça change les variables de l'autre.
Y'a t'il un code pour désactiver cela?

:odile:

moebius
17/01/2007, 20h12
vraiment très bon ton travail JM! :prie:

En plus c'est plein d'humour :lol:

félicitation et merci de partager tout ça!
j'avoue que je n'ai pas tout lu mais le jour ou je veux me plonger dans la programmation, je sais où me documenter :poucehaut:

ZOZO
19/02/2007, 20h57
i' va pô nous faire toutes les dynamics en "simplifiées" façon clothidle kan mémeuh :shock: :o

Travail impressionnant !!! :prie:

PP
20/02/2007, 23h53
J'aime bien ces sujets où je ne pige que dalle. Ça fait pro ! :mrgreen:

Bravo les gars. Je n'ai pas fait de progrès en programmation depuis mon MasterMind en basic sur Amstrad464... :D

kiteman
13/03/2007, 13h14
waouw JeanLaurent , j'avais pas vu ce topic de malades ! :mrgreen:

tout ça me donne envie de m'y mettre j'avoue .. je pense que pour le rigging ça pourrait m'aider :D

merci à toi pour cette mise en bouche superbe :odile:

kiteman
13/03/2007, 16h23
arf ha j'arrive pas à faire marcher ton plug :oops: .. j'arrive pas à avoir la main pour faire glisser l'objet 2 dans le lien de ton interface

Jean-Laurent
13/03/2007, 16h48
Oui, le suivi laisse à désirer. :oops:

J'ai laissé reposer un peu le code pour faire un peu d'image, ce qui ne devrait pas me faire de mal.
Mais je n'ai pas du tout abandonné mon idée, même si je bute toujours sur les dernières questions. :coup:



tout ça me donne envie de m'y mettre j'avoue .. je pense que pour le rigging ça pourrait m'aider :D


Alors n'hésite pas. L'interface, c'est le plus facile à mettre en place malgré les apparences. Dans les mains d'un spécialiste comme toi de l'animation, ça pourrait donner de superbes choses.
Sur un autre plug que j'avais commencé, j'ai installé une petite fenêtre dans laquelle on voit une prévisualisation de quelque chose qui bouge et ça marche très bien. C'est une fonctionnalité intéressante entre autre.



arf ha j'arrive pas à faire marcher ton plug :oops: .. j'arrive pas à avoir la main pour faire glisser l'objet 2 dans le lien de ton interface


Moi non plus. :lol:
Je n'ai pas encore trouvé la manière de permettre un glisser-déposer des objets.
Il faut rentrer le nom de l'objet à la main dans le champ. :poucebas:

Mais ce qui m'inquiète un peu, c'est que certains plugs (Bolt classic par exemple) fonctionnent sur le même principe. Du coup je me demande si c'est possible avec le SDK traditionnel?

Attention, je ne l'ai toujours pas finalisé. :oops:

kiteman
13/03/2007, 17h09
ha oki :D

j'avais pas pensé à tapper manuellement le nom de l'objet ^^ .. moi ça se drag&drop pas .. :mrgreen:

combien de temps t'as mis Jean-Laurent , pour apprendre les bases et en arriver là ? :oops:

pour faire ton interface , il faut dessiner ça dans un autre logiciel en fait ? comment qu'on fait-y ça :calim: ?

Jean-Laurent
13/03/2007, 17h32
combien de temps t'as mis Jean-Laurent , pour apprendre les bases et en arriver là ? :oops:


Je veux bien répondre mais ça ne correspond pas à grand chose. Tout dépend de ton vécu.
Si tu as fais un peu de programmation et que tu sais ce que sont des variables ou pas etc... (Ce qui était mon cas, je connaissais à peine le C++ et un peu mieux l'action script.)
Deux jours pour lire les tutoriaux du SDK, ensuite j'ai essayé la simulation d'une mer animée (une semaine) en réponse au problème d'Aurety, qui n'en était plus un. Puis le rideau de paspas, quelques jours aussi. En gros je dirais un mois. (A 1 heure par jour maxi car j'ai un travail à côté :wink:).

Les tutos du SDK sont cependant super Hard si tu n'as jamais programmé et je te les déconseille. Par contre les exemples sont très simples et beaucoup plus faciles à étudier.
Mon conseil:
Lire l'introduction du SDK qui est très claire. Lire les tutoriaux mais sans se prendre la tête. (Y revenir par la suite).
Les expressions coffee sont à lire en premier, souvent elles sont bien légendées et parlent toutes seules, puis ensuite les plug.



pour faire ton interface , il faut dessiner ça dans un autre logiciel en fait ? comment qu'on fait-y ça :calim: ?


Aucun autre logiciel. (Même si ça existe mais je ne me suis pas penché sur la question).
Un traitement de texte et tu tapes du code.
Tu peux passer les premier posts où je décris le coffee pour faire bouger le ressort et ensuite il y a un post où je commence à expliquer comment créer l'interface.
Les boutons radios, sliders etc... sont très faciles à créer.
C'est du code genre "AddSlider() " et ça t'ajoute un slider.
De toute manière si tu te lances, tout un tas de monde sera là pour t'épauler. :poucehaut:

Tu peux commencer facilement par créer l'interface de tes rêves, puis implémenter les fonctions ensuite.

kiteman
13/03/2007, 18h38
oki merci :odile:

dès que jai un peu de temps je m'y plonge , et viendrai me ridiculiser en public :boss:

moebius
14/03/2007, 02h49
tu veux dire que tu viendras ridiculiser le public ? :cry2:
avec ta manie de tout masteuriser ce que tu touches....

kiteman
14/03/2007, 04h10
tu veux dire que tu viendras ridiculiser le public ? :cry2:
avec ta manie de tout masteuriser ce que tu touches....


;) .. merci mais nan là je pense sincèrement que je vais en baver un moment :mrgreen:

je serais tout content quand j'aurais fais un premier code de 850 lignes pour créer un cube 8)

johnc
15/03/2007, 02h53
alors Kiteman ... une petite interface de poulpe à l'horizon... hein? :nono: