Rotation incrémentielle

Ce didacticiel suppose que vous connaissiez la définitions des différents noeuds, events et spécifications de base du VRML97.
Vous pourrez trouver toutes ces infos (en français) sur le site de 3Dnet.
N'oubliez pas que tous les ROUTE doivent se trouver tout à la fin du fichier.

But :

Dans cet exercice, nous allons créer un objet qui pivotera de 90° lorsque l'on cliquera dessus. La difficulté étant de faire en sorte que à chaque clic supplémentaire, nous rajoutions 90° à l'orientation courante de l'objet.

En pratique :

Notre objet est une boite surmonter d'un cône pour bien voir son orientation. Ils sont groupés dans un noeud Transform que nous nommons "aiguille".

DEF	aiguille Transform {
	children [
		Shape {
			geometry Box {
			}
			appearance Appearance {
				material Material {
					diffuseColor .15 .15 .15
					specularColor .5 .5 .5
					emissiveColor .14 .14 .14
					ambientIntensity 0
				}
			}
		}
		Transform {
			translation	0 2 0
			children Shape {
				geometry Cone {
				}
				appearance Appearance {
					material Material {
						diffuseColor .8 0 0
						specularColor .5 .5 .5
						emissiveColor .15 0 0
						ambientIntensity 0
					}
				}
			}
		}
	]
}

Nous associons un TouchSensor nommé "clique" à cette "aiguille".

DEF	clique TouchSensor {}

Voir la scène Voir le source

Nous créons un OrientationInterpolator nommé "rotator" qui tourne de 90° durant son cycle.

DEF	rotator OrientationInterpolator	{
	key	[0 1]
	keyValue [0 0 1 0, 0 0 1 1.57079632]
}

Cette rotation s'appliquera à notre groupe "aiguille".

ROUTE rotator.value_changed	TO aiguille.rotation

Il nous faut aussi un TimeSensor nommé "timer" qui commandera notre "rotator"

DEF	timer TimeSensor {}

Nous routons la fraction de temp de "timer" vers l'eventIn set_fraction de "rotator".

ROUTE timer.fraction_changed TO	rotator.set_fraction

Le clic sur "aiguille" déclenchera le "timer" qui commandera le "rotator".

ROUTE clique.touchTime TO timer.startTime

Voir la scène Voir le source

A ce stade, un clic fait pivoter notre objet de 90°. Mais si on recliques dessus, l' "aiguille" est ramené à son orientation initiale(tel qu'elle était à l'ouverture de la scène), puis pivoté de 90°. Or nous voudrions qu'elle tourne de 90° à partir de son orientation actuelle.


Ce qu'il nous faudrait, c'est une fonction qui redéfinisse les valeurs des clés de "rotator" une fois la première rotation effectuée. Nous utilisons pour cela un noeud script nommé "redefine".

DEF	redefine Script	{
		}

Ce script utilisera un champ de type SFNode qui sera lié au "rotator" de notre scène. Ainsi, le script pourra accéder facilement à chacune des propriétés (key, key_value...) de l'OrientationInterpolator.

field SFNode rotator USE rotator

Il utilisera un autre field de même type qui sera lié au Transform "aiguille". De cette façon, le script aura accès à une propriété très importante de "aiguille" : sla rotation courante!

 field SFNode aiguille USE aiguille

Maintenant, il faut définir la fonction qui redéfinie les key_value de "rotator". Cette fonction sera exécutée après chacune des interpolations de rotation. "rotator" étant commander par "timer", c'est à la fin du cycle de timer que nous l'exécuterons. Nous utilisons donc l'eventOut cycleTime de "timer" que nous routons vers un eventIn de type SFTime du script "redefine".

DEF	redefine Script	{
	eventIn	SFTime cycle
	field SFNode rotator USE rotator
	field SFNode aiguille USE aiguille
	url "javascript: function cycle(){
	}
	"
}

ROUTE timer.cycleTime TO redefine.cycle

La propriété Key_value de "rotator" est de type MFRotation. La valeur de la première clé de "rotator" peut s'écrire Key_value[0]. Key_value[0] est quant à elle du type SFRotation.

Avant le premier clic sur "aiguille", la valeur de la première clé de "rotator", Key_value[0], est égale à l'orientation initiale de "aiguille", soit 0 0 1 0.
La valeur de la 2ème clé, Key_value[1], indique la valeur de rotation de "aiguille" après interpolation, soit 0 0 1 1.57079632 .
Après l'interpolation, Key_value[0] doit être réinitialiséeà 0 0 1 1.57079632 qui est l'orientation courante de "aiguille". Notons déjà cela dans notre fonction :

	url "javascript: function cycle(){
		rotator.keyValue[0] = aiguille.rotation ;
	}
	"

La 2ème clé de "rotator" est égale à l'orientation courante de "aiguille" mais incrémentée de PI/2 radians. Nous pourrions utiliser une opération du type :

Key_value[1][3] = aiguille.rotation[3] + Math.PI/2

Mais nous serions très vite handicapés par deux choses :

  • la nécessité de remettre à 0 la valeur de l'angle si celle-ci est égale à PI, et ce pour prévenir les débordement . (la valeur de l'angle pouvant monter à l'infinis, nous pourrions obtenir une valeur si grande qu'elle accaparerait une quantité phénoménale de ressources du système)
  • la trop grande complexité du calcul de la valeur de l'angle dans le cas de rotation sur plusieurs axes.

Pour ces raisons, nous utiliserons un outil existant et beaucoup plus puissant, une fonction javascript standart, la fonction multiply().

Cette fonction utilise une syntaxe un peu spéciale. Voici la syntaxe générique :

SFRotation = SFRotation.multiply(SFRotation);

rotator.keyValue[1] = aiguille.rotation.multiply(new SFRotation(0, 0, 1, Math.PI /2));

rot + rot = rot * rot !?!

Aussi bizarre que celà puisse paraître, l'ajout de rotation se fait par une multiplication !! Ceci est possible car les rotations en VRML sont codées sous formes de "quaternion".

Pour plus d'infos sur les quaternions, je vous recommande le site de michael wagner, dont on dis qu'il est le "Docteur Quaternion". (et c'est vrai !)

Les rotations sous formes de quaternions sont très souvent utilisées par les logiciels de 3D car elles ont l'avantage d'éviter les phénomènes de basculement, problème récurrent d'autre méthode (Rotation de Euler notamment ).

Voir la scène Voir le source


Pour aller plus loin :

Il existe une fonction javascript qui gère automatiquement les interpolations de rotation. C'est la fonction slerp(), qui est définie par :

SFRotation = SFRotation.slerp(SFRotation, numeric) Pour mieux la comprendre, écrivons la comme suit :

rotation finale = rotation de départ.slerp( rotation de destination, temp);

Nous pouvons utiliser cette fonction dans notre exemple, mais il nous faut modifier plusieurs choses :

Nous n'auront plus besoin de "rotator". Vous pouvez donc l'effacer ainsi que les ROUTE qui le concerne.
De même, le script n'a plus besoin du field rotator.
Le contenu de la fonction cycle() est à refaire, vous pouvez l'effacer.

Voir le source

Le script contiendra une nouvelle fonction, qui s'exécutera pendant toute la durée de "timer". Pour activer cette fonction, il faut un eventIn de type SFFloat que nous nommons "duree".

DEF	redefine Script	{
	eventIn	SFTime cycle
	eventIn	SFFloat	duree
	field SFNode aiguille USE aiguille
	url "javascript: 
	function cycle(){
	}
	function duree(){
	}
	"
}

Nous avons toujours besoin de la fonction cycle(). Elle servira à définir les rotations de départ et d'arrivée qui seront utilisées par l'instruction slerp(). Ces deux rotations (départ et arrivé ), sont stockées chacune dans un field de type SFRotation et se nomment rotdep et rotend. Ces champs SFRotation ont par défaut la valeur 0 0 1 0.

	field SFRotation rotdep 0 0 1 0
	field SFRotation rotend 0 0 1 0

Comme précedemment, la rotation d'arrivé est égale à la rotation courante incrémenter de PI/2. Nous utilisons toujours la fonction multiply() pour celà :

function cycle(){
		rotdep = aiguille.rotation ;
		rotend = aiguille.rotation.multiply(new SFRotation(0, 0, 1, Math.PI /2));
	}

La fonction duree() va elle calculer à chaque fraction de temp t l'interpolation linéaire entre rotdep et rotend via la fonction slerp(). Il faut que nous récupérions la valeur de cette fraction t :

function duree(t){
	}

La valeur obtenu par la fonction slerp() est affectée en temps réel à l'orientation de "aiguille".

function duree(t){
		aiguille.rotation = rotdep.slerp (rotend, t) ;
	}

Pour que cela marche, il faut bien sûr router la fraction_changed de "timer" vers l'eventIn duree du script, qui exécutera la function duree().

ROUTE timer.fraction_changed TO redefine.duree

Voir la scène Voir le source

Valid XHTML 1.0 Strict Valid CSS!