Bilan du concours « Pédalier

Transcription

Bilan du concours « Pédalier
Bilan du concours « Pédalier »
Nous avons eu deux participants (Christy et Elenia) et un hors concours qui est en même temps un
donateur (Barbidule). Merci pour ces participations ! Dommage qu’on n’ait pas eu de débutant qui
se soit lancé dans l’aventure qui leur était, il me semble, accessible.
Les 3 scripts proposés répondent au cahier des charges et ont résisté à mes mauvais traitements. Ils
sont très bien commentés, merci à leurs auteurs ! Une mention spéciale à Barbidule pour des
commentaires « fleuve ».
Les scripts figurent intégralement en annexe, j’y ai ajouté ma version.
J’ai attribué la première place à Christy dont le script est plus optimisé que celui d’Elenia. J’ai
réparti donc la somme récoltée de la façon suivante :
Christy = 2500 l$
Elenia = 2000 l$
Je vais me contenter de commenter l’aspect rotation du pédalier et des pédales en laissant sous
silence des éléments non demandés au cahier des charges, comme par exemple le réglage de la
vitesse de Christy ou la prise en charge d’un changement de dimension.
Numéro des primitives
Le script a besoin de connaître les numéros de liaison des primitives à mettre en mouvement. Les 3
scripts utilisent le nommage. Barbidule utilise une fonction générale pour cet usage :
integer LinkName2LinkNumber(string pName)
{/
integer i; // variable de boucle
integer nb = llGetNumberOfPrims();
integer result = -1; //variable utilisée pour fournir le résultat
for ( i = 0; i <= nb; ++i) // ++i plus rapide que i++
{
if (llGetLinkName(i) == pName)
{ result = i;
i = nb; //permet de finir la boucle
// plus propre de sortir de la boucle que de faire un return ici
// il vaut mieux centraliser la sortie d'une fonction à un seul endroit
// en général à la fin
}//if == pName
}//for
return(result);
// cette fonction faisant une boucle, il va de soi (!) qu'il ne faut pas
// l'appeler pendant des traitements lourds ou répétitifs sous peine de
// lenteurs
// on ne s'en servira qu'une fois pour stocker les numeros des prims pédales
// et pedalier, dans la phase d'initialisation (state default)
}// LinkName2LinkNumber
Les 2 autres intègrent cette recherche dans une boucle à l’initialisation. A ma décharge j’ai joué la
fainéantise et prévu directement les numéros en paramètres.
Axe de rotation
On a également besoin de connaître l’axe de rotation qui est commun au pédalier et aux pédales. Ici
tous les scripts ont prévu un paramètre global. Par exemple Elenia :
//Configuration du pédalier
vector vAxePedalier = <0,0,1>; //axe de rotation du pédalier; ici z pour un cylindre
Gestion du temps
Christy et Elenia se contentent du timer sans se soucier du lag. Barbidule va plus loin en gérant le
temps de manière plus fine. S’il utilise un timer pour déclencher périodiquement le mouvement
avec évidemment les contraintes que l’on connaît il calcule à chaque fois le temps exact avec cette
fonction :
integer Time()
{ // count milliseconds since the day began
string stamp = llGetTimestamp(); // "YYYY-MM-DDThh:mm:ss.ff..fZ"
return (integer) llGetSubString(stamp, 11, 12) * 3600000 + // hh
(integer) llGetSubString(stamp, 14, 15) * 60000 + // mm
llRound((float)llGetSubString(stamp, 17, -2) * 1000000.0)/1000; // ss.ff..f
}//time
Angle de rotation
Selon la vitesse de rotation désirée correspond un angle de rotation élémentaire dépendant du temps
de base choisi (0.05 pour Christy et Barbidule, 0.04 pour Elenia, il faudra peut-être un jour se
mettre d’accord sur une valeur).
L’angle de rotation est conservé et géré en degrés pour Christy :
fAngle += (float)iVitesse;
if( llRound( fAngle ) >= 360 )
fAngle -= 360.0;
else if( llRound( fAngle ) < 0 )
fAngle += 360.0;
Ou Barbidule :
float angle = ((float)t/1000.0 ) * TWO_PI * gVitesseRotation;
Ou directement en radians avec calcul préliminaire du pas pour Elenia :
angle += pasRd;
if(angle > TWO_PI) angle = .0;
Cet angle est ensuite appliqué à l’axe de rotation. Par exemple Christy :
// Conversion de l'angle en rotation
rNewAngle
= llEuler2Rot( vAxeRot * -fAngle * DEG_TO_RAD );
Ou Barbidule :
// positionnement du pédalier (juste la rotation)
rot = llEuler2Rot( gAxeRotationPedalier * angle );
Ou Elenia directement dans la fonction effectrice :
//on fait tourner le pédalier;
llSetLinkPrimitiveParamsFast(PEDALIER, [PRIM_ROTATION,
llEuler2Rot(vRotLocPedalier + vAxePedalier * aPedalier)/rotRef]);
Positionnement des pédales
Le positionnement des pédales pouvait s’envisager de différentes façons.
Christy a tout groupé et optimisé en intégrant un facteur de dimension du build et l’application d’un
vecteur tournant :
// Calcul des positions des pédales
vPosPedaleDroite = ( llList2Vector( lPos, PEDALIER ) +( llList2Vector
( lPos, PEDALE_DROITE ) - llList2Vector( lPos, PEDALIER ) ) * rNewAngle ) * fFactor;
vPosPedaleGauche = ( llList2Vector( lPos, PEDALIER ) + ( llList2Vector
( lPos, PEDALE_GAUCHE ) - llList2Vector( lPos, PEDALIER ) ) * rNewAngle ) * fFactor;
Elenia a fait quelques gammes trigonométriques en se compliquant un peu les choses :
//les pédales: il n'y a pas de rotation, juste un positionnement suivant calcul trigo
//Mais attention ce positionnement se fait en coordonnées locales (par rapport au root)
llSetLinkPrimitiveParamsFast(PEDAL_G, [PRIM_POSITION, posLocPedalier +
< demiPedalier * llCos(aPedale), //local x par rapport au centre pédalier
demiLargPedal + demiLargPedalier, //local y par rapport au centre pédalier
-demiPedalier * llSin(aPedale)> ]); //local z par rapport au centre pédalier
llSetLinkPrimitiveParamsFast(PEDAL_D, [PRIM_POSITION, posLocPedalier +
< -demiPedalier * llCos(aPedale), //local x par rapport au centre pédalier
-demiLargPedal - demiLargPedalier, //local y par rapport au centre pédalier
demiPedalier * llSin(aPedale)> ]); //locam z par rapport au centre pédalier
Elle a tenu compte de l’écart des pédales mais celui-ci pourrait être intégré directement dans le
vecteur.
Barbidule n’a pas non plus joué avec la simplicité et aime aussi se frotter aux lignes
trigonométriques :
// donc, le vélon vers Z, et voila t'y pas que la pédale est orientée selon -Y
// (hasard du build initial). Il faut corriger la rotation entre les deux:
decalageRot_velo_pedale = llRotBetween( <0.0,0.0,1.0> , <0.0,-1.0,0.0> );
// c'est ca que je trouve pas propre dans mon script !!!
//positionnement local d'une pedale
// déja le centre du pedalier en local
pos = gPosPedalier ;
// on ajoute la trajectoire circulaire
pos += (gRayonPedalier * <0.0,llSin(angle), llCos(angle)>);
// ha ben oui, quand ca tourne, il y a du sinus et du cosinus ^^
// il y a certainement une explication logique et peut-etre simple
// pour expliquer l'ordre des composantes du cercle. J'ai essayé de faire
// propre en suivant le guide sur les rotations, mais j'avais pas les neurones ^^
// résultat, j'ai tatonné pour trouver le bon ordre. il y a forcement une explication
// mathematique, du genre on compose une rotation à une trajectoire circulaire par
// rapport au référentiel du root fois l'age du capitaine...
//llOwnerSay((string)(pos));
pos += < gDecalagePedale, 0.0, 0.0>; //ecartement vers la gauche( ? ou droite :)
rot = decalageRot_velo_pedale / rotVelo ; //rotation en absolu
llSetLinkPrimitiveParamsFast(gNumPedale1, [ PRIM_ROTATION, rot, PRIM_POSITION, pos]
);
//idem pour l'autre pédale, mais avec un angle + 180° (ca change juste le signe des cos/sin)
pos = gPosPedalier; //centre du pedalier
pos += (gRayonPedalier * <0.0,-llSin(angle), -llCos(angle)>) ;
pos -= < gDecalagePedale, 0.0, 0.0>;//ecartement vers la droite( ? ou gauche :)
// la rotation ne change pas
llSetLinkPrimitiveParamsFast(gNumPedale2, [PRIM_POSITION, pos , PRIM_ROTATION, rot]
);
C’est dans ces cas-là qu’on se rend compte que l’utilisation systématique des vecteurs et des
quaternions permet des raccourcis dans le codage. Par exemple :
// Vecteur rotatif pour les pédales (les vecteurs sont opposés)
vector vPedale = vTournant * rRotPedalier;
// Positionnement pédale 1
llSetLinkPrimitiveParamsFast(iNumPedale1, [PRIM_POSITION, vPosPedalier + vPedale]);
// Positionnement pédale 2
llSetLinkPrimitiveParamsFast(iNumPedale2, [PRIM_POSITION, vPosPedalier - vPedale]);
Conclusion
Vous trouverez ci-après les 4 scripts dans leur intégralité. La lecture de ces scripts peut être très
instructive pour les débutants (et les moins débutants d’ailleurs) et leurs auteurs se sont attachés à
commenter abondamment le code. Ce concours était finalement une bonne idée malgré la faible
participation. Est-ce que le problème posé était trop abrupt et a fait un peu peur ?
Christy
integer iBcl;
integer iTmp;
vector vTmp;
// Le vélo a huit vitesses, 4 en avant, 4 en arrière, plus le 0 pour l'arrêt
// Une graduation sur le prim root identifie ces vitesses
// vitesse : -4 -3 -2 -1 0 1 2 3 4
// iGraduation : 8 7 6 5 4 3 2 1 0
integer iGraduation = 100;
integer iVitesse; // Prend la valeur de la graduation sur le prim root, multipliée par 2
vector vPoint;
// Point de touche courant
vector vPrevPoint; // Point de touche précédent
vector vSizeRoot;
// Taille du prim root
float fFactor = 1.0; // Rapport entre taille réelle du build et taille initiale stockée
integer iStart;
// Témoin de fonctionnement
float fAngle;
// Angle de rotation courant du pédalier, en °
vector vAxeRot = <0.0, 1.0, 0.0>; // Détermination de l'axe sur lequel la rotation est appliquée
integer iPedalier;
// Numéro de lien du prim pédalier
integer iPedaleDroite; // Numéro de lien du prim pédale droite
integer iPedaleGauche; // Numéro de lien du prim pédale gauche
rotation rNewAngle; // Conversion de l'angle courant en rotation
rotation rRotPedalier; // Comme son nom l'indique, la rotation du pédalier
vector vPosPedaleDroite; // Position de la pédale droite
vector vPosPedaleGauche; // Position de la pédale gauche
// Sens de rotation des textures des roues
float fSensDroite = -1.0; // Faces droites
float fSensGauche = 1.0; // Faces gauches
// Numéros des faces des roues à animer
integer FACE_ROUE_DROITE = 0;
integer FACE_ROUE_GAUCHE = 3;
// Numéro des prims dans les listes de taille, position, rotation initiales
integer CURSEUR
= 0;
integer ROUE_ARRIERE_DROITE = 1;
integer ROUE_ARRIERE_GAUCHE = 2;
integer ROUE_AVANT_DROITE = 3;
integer ROUE_AVANT_GAUCHE = 4;
integer PEDALIER
= 5;
integer PEDALE_DROITE
= 6;
integer PEDALE_GAUCHE
= 7;
// Noms des prims sur lesquelles le script travaille
list lPrims = [ "Curseur",
// Le prim root contient une graduation de -4 à 4 pour régler la
vitesse du pédalier
"Roue AR DR", "Roue AR GC",
animer la texture de chaque
"Roue AV DR", "Roue AV GC",
"Pedalier",
"Pedale DR", "Pedale GC" ];
// Les 2 roues sont constituées de 2 prim pour pouvoir
// face de roue correctement
// Liste des numéros de liens des prims sur lesquelles le script travaille
list lLinkNums;
// Tailles initiales des prims
list lSizes = [ <0.19135, 0.08200, 0.01000>, // CURSEUR
<0.65000, 0.65000, 0.02500>, // ROUE_ARRIERE DROITE
<0.65000, 0.65000, 0.02500>, // ROUE_ARRIERE GAUCHE
<0.65000, 0.65000, 0.02500>, // ROUE_AVANT DROITE
<0.65000, 0.65000, 0.02500>, // ROUE_AVANT GAUCHE
<0.44250, 0.01900, 0.35290>, // PEDALIER
<0.04000, 0.15000, 0.08500>, // PEDALE_DROITE
<0.04000, 0.15000, 0.08500> ]; // PEDALE_GAUCHE
// Positions relatives (locales) au root des prims
list lPos = [ < 0.0000, 0.38500, 0.03000>, // CURSEUR
< 0.4125, 0.00000, 0.37500>, // ROUE_ARRIERE DROITE
< 0.4125, 0.00000, 0.37500>, // ROUE_ARRIERE GAUCHE
<-0.6625, 0.00000, 0.37500>, // ROUE_AVANT DROITE
<-0.6625, 0.00000, 0.37500>, // ROUE_AVANT GAUCHE
< 0.0000, 0.00875, 0.39525>, // PEDALIER
< 0.1650, 0.18025, 0.39525>, // PEDALE_DROITE
<-0.1650, -0.18025, 0.39525> ]; // PEDALE_GAUCHE
// Rotations locales des prims
list lRot = [ <0.0, 0.0, PI_BY_TWO>,
// CURSEUR
<-PI_BY_TWO, 0.0, 0.0>,
// ROUE_ARRIERE DROITE
<-PI_BY_TWO, 0.0, 0.0>,
// ROUE_ARRIERE GAUCHE
<-PI_BY_TWO, 0.0, 0.0>,
// ROUE_AVANT DROITE
<-PI_BY_TWO, 0.0, 0.0>,
// ROUE_AVANT GAUCHE
<0.0, PI_BY_TWO, -PI_BY_TWO>, // PEDALIER
<0.0, -PI_BY_TWO, 0.0>,
// PEDALE_DROITE
<0.0, -PI_BY_TWO, 0.0> ];
// PEDALE_GAUCHE
// Rotation du pédalier et déplacement des pédales
Action()
{
// Conversion de l'angle en rotation
rNewAngle = llEuler2Rot( vAxeRot * -fAngle * DEG_TO_RAD );
// Calcul de la rotation du pédalier
rRotPedalier = llEuler2Rot( llList2Vector( lRot, PEDALIER ) ) * rNewAngle / llGetRootRotation();
// Calcul des positions des pédales
vPosPedaleDroite = ( llList2Vector( lPos, PEDALIER ) + ( llList2Vector
( lPos, PEDALE_DROITE ) - llList2Vector( lPos, PEDALIER ) ) * rNewAngle ) * fFactor;
vPosPedaleGauche = ( llList2Vector( lPos, PEDALIER ) + ( llList2Vector
( lPos, PEDALE_GAUCHE ) - llList2Vector( lPos, PEDALIER ) ) * rNewAngle ) * fFactor;
// Application du mouvement
llSetLinkPrimitiveParamsFast( iPedalier, [ PRIM_ROTATION, rRotPedalier ] );
llSetLinkPrimitiveParamsFast( iPedaleDroite, [ PRIM_POSITION, vPosPedaleDroite ] );
llSetLinkPrimitiveParamsFast( iPedaleGauche, [ PRIM_POSITION, vPosPedaleGauche ] );
}
// Retour en position neutre du pédalier
ReInit()
{
if( ! iStart && fAngle != 0.0 )
{
// Vitesse 0 et angle courant différent de 0, on repasse en position neutre
if( llRound( fAngle ) <= 180 )
iVitesse = -8;
else
iVitesse = 8;
fAngle = (float)( ( (integer)fAngle / 8 ) * 8 );
llSetTimerEvent( 0.05 );
}
}
// Déplacement du cirseur vert face à la vitesse sélectionnée
PlaceCurseur( integer iPos )
{
if( ( iBcl = llList2Integer( lLinkNums, CURSEUR ) ) )
{
vTmp = llList2Vector( lPos, CURSEUR );
vTmp.x = -0.125 * (float)iPos;
llSetLinkPrimitiveParamsFast( iBcl, [ PRIM_POSITION, vTmp * fFactor ] );
}
}
// Arrêt du fonctionnement
Stop()
{
// Témoin d'arrêt
iStart = FALSE;
// Arrêt du timer
llSetTimerEvent( 0.0 );
// Curseur de vitesse en 0
PlaceCurseur( 0 );
// Arrêt des animations de textures des roues
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_ARRIERE_DROITE ),
FALSE, ALL_SIDES, 0, 0, 0.0, TWO_PI, 0.0 );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_ARRIERE_GAUCHE ),
FALSE, ALL_SIDES, 0, 0, 0.0, TWO_PI, 0.0 );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_AVANT_DROITE ),
FALSE, ALL_SIDES, 0, 0, 0.0, TWO_PI, 0.0 );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_AVANT_GAUCHE ),
FALSE, ALL_SIDES, 0, 0, 0.0, TWO_PI, 0.0 );
}
// Récupération des numéros de liens des prims
// Calcul du rapport de taille effective relativement à la taille initiale stockée
init()
{
llSay( 0, "Initialisation ..." );
// Arrêt si en mouvement
Stop();
vSizeRoot = llGetScale();
fFactor = vSizeRoot.x / 2.5;
lLinkNums = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
for( iBcl = 2; iBcl <= llGetNumberOfPrims(); iBcl++ )
if( ( iTmp = llListFindList( lPrims, [ llGetLinkName( iBcl ) ] ) ) >= 0 )
{
lLinkNums = llListReplaceList( lLinkNums, [ iBcl ], iTmp, iTmp );
llSetLinkPrimitiveParamsFast( iBcl, [ PRIM_SIZE, llList2Vector( lSizes, iTmp ) * fFactor,
PRIM_POSITION, llList2Vector( lPos, iTmp ) * fFactor,
PRIM_ROTATION, llEuler2Rot( llList2Vector( lRot, iTmp ) ) /
llGetRootRotation() ] );
}
// Mémorisation des numéros des prims sur lesquels on agit dans des variables 'simples'
// Objectif : petite optimisation, ne pas adresser un liste mais un integer lors de l'action
iPedalier = llList2Integer( lLinkNums, PEDALIER
);
iPedaleDroite = llList2Integer( lLinkNums, PEDALE_DROITE );
iPedaleGauche = llList2Integer( lLinkNums, PEDALE_GAUCHE );
llSay( 0, "Initialisation terminée." );
}
default
{
state_entry()
{
init();
}
changed( integer iChange )
{
if( iChange & ( CHANGED_SCALE | CHANGED_LINK ) )
init();
}
timer()
{
fAngle += (float)iVitesse;
if( llRound( fAngle ) >= 360 )
fAngle -= 360.0;
else if( llRound( fAngle ) < 0 )
fAngle += 360.0;
Action();
// Après une commande d'arrêt, l'arrêt effectif ne se fait qu'une fois les pédales en position neutre
if( ! iStart && ! llRound( fAngle ) )
{
llSetTimerEvent( 0.0 );
llSay( 0, "Reinitialisation du pédalier." );
}
}
touch_start( integer iNum )
{
if( llDetectedTouchFace( 0 ) )
return;
if( vPoint.y >= 0.82 && vPoint.y <= 0.9850 && // Contrôle de la position du clic sur la
vPoint.x >= 0.06 && vPoint.x <= 0.9425 && // face présentant les graduations de vitesses
iGraduation == 4 && (integer)( ( vPoint.x - 0.06 ) / 0.1 ) == 4 )
// On vient de cliquer sur la graduation 0, mouvement déjà à l'arrêt, on replace le pédalier en position
neutre (angle = 0).
ReInit();
}
touch( integer iNum )
{
if( llDetectedLinkNumber( 0 ) != 1 || llDetectedTouchFace( 0 )
|| vPrevPoint == ( vPoint = llDetectedTouchST( 0 ) ) )
// Seuls les clics sur la face 0 du prim root nous intéresse
return;
if( vPrevPoint == ( vPoint = llDetectedTouchST( 0 ) ) )
// Les déclenchements de l'event touch sont répétitifs, on ne s'intéresse qu'au premier à une position
donnée
// Si la souris reste cliquée, sans déplacement, on ignore les déclenchements suivants
return;
// Mémorisation du point de touche traité
vPrevPoint = vPoint;
if( vPoint.y >= 0.82 && vPoint.y <= 0.9850 &&
vPoint.x >= 0.06 && vPoint.x <= 0.9425 )
{
// Contrôle que l'aire de touche correspond aux graduations de vitesses
if( iGraduation == ( iTmp = (integer)( ( vPoint.x - 0.06 ) / 0.1 ) ) )
{
// On vient de cliquer sur la graduation 0, mouvement déjà à l'arrêt, on replace le pédalier en position
neutre (angle = 0).
ReInit();
return;
}
// Numéro de la graduation cliquée
// vitesse : -4 -3 -2 -1 0 1 2 3 4
// iGraduation : 8 7 6 5 4 3 2 1 0
iGraduation = iTmp;
vPoint.x -= 0.1 * (float)iTmp;
if( vPoint.x < 0.06 || vPoint.x > 0.1425 )
// Point de touche dans une zone grise entre les graduations
return;
if( ! (iVitesse = ( 4 - iTmp ) * 2) )
{
llSay( 0, "Arrêt du vélo" );
Stop();
}
else
{
llSay( 0, "Vitesse " + (string)llAbs( iVitesse / 2 ) + " en marche " +
llList2String( [ "avant.", "arrière." ], (integer)(iVitesse < 0) ) );
PlaceCurseur( iVitesse );
if( ! iStart )
{
// Vélo précédemment à l'arrêt, on démarre le timer
llSetTimerEvent( 0.05 );
iStart = TRUE;
}
// Animation des textures des roues
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_AVANT_GAUCHE ),
ANIM_ON | LOOP | ROTATE | SMOOTH,
FACE_ROUE_GAUCHE, 0, 0, 0.0, TWO_PI,
0.5 * (float)iVitesse * fSensGauche );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_AVANT_DROITE ),
ANIM_ON | LOOP | ROTATE | SMOOTH,
FACE_ROUE_DROITE, 0, 0, 0.0, TWO_PI,
0.5 * (float)iVitesse * fSensDroite );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_ARRIERE_GAUCHE ),
ANIM_ON | LOOP | ROTATE | SMOOTH,
FACE_ROUE_GAUCHE, 0, 0, 0.0, TWO_PI,
0.5 * (float)iVitesse * fSensGauche );
llSetLinkTextureAnim( llList2Integer( lLinkNums, ROUE_ARRIERE_DROITE ),
ANIM_ON | LOOP | ROTATE | SMOOTH,
FACE_ROUE_DROITE, 0, 0, 0.0, TWO_PI,
0.5 * (float)iVitesse * fSensDroite );
}
}
}
}
Elenia
//L'axe de rotation du pedalier doit être aligner sur l'axe y du build
//Les pédales doivent avoir leur épaisseur en z et leur axe de rotation en y
//pas besoin de positionner les pédales au build: elles iront en place grace au script
//----------Constantes pour mémoriser les n° de prim----------integer PEDALIER; //le pédalier doit s'appeller 'pedalier'
integer PEDAL_G; //une pédale doit s'appeller 'pedal_g'
integer PEDAL_D; //l'autre pédale doit s'appeller 'pedal_d'
//----------Autres constantes---------------------------------//Configuration du pédalier
vector vAxePedalier = <0,0,1>; //axe de rotation du pédalier; ici z pour un cylindre
vector vLongPedalier = <1,0,0>; //indiquez ici suivant quel axe est la longueur du pédalier. Ici suivant les
x.
float TEMPO = 0.04; //on fait un mouvement tout les TEMPO- pas la peine de mettre moins ça ne marche
pas
integer pasDeg = 10; //on tourne de 10° à chaque mouvement; moins c'est lent; plus c'est rapide mais
saccadé.
float shiftPedalier = .06; //décalage des pédales par rapport au bout du pédalier
//si 0, les pédales sont à l'extrémité du pédalier
//Jouer sur ces 2 variables si ça tourne pas dans le bon sens
integer sensPedalier = -1;
integer sensPedale = 1;
//----------Constantes calculées------------------------------float pasRd; //le pas en radians, histoire de ne pas le calculer chaque fois
float demiPedalier; //la demi longueur du pédalier, soit le rayon pour les pédales
float demiLargPedalier; // la demi largeur pédalier
vector posLocPedalier; // la position du pédalier par rapport au socle (root)
float demiLargPedal; // la demilargeur des pédales (y)
//----------Variables------------------------------------------float angle; // l'angle de rotation courant
rotation rotRef; //la rotation globale du root
vector vRotLocPedalier;//le rotation locale du pédalier au build
float aPedalier;//l'angle pour le pédalier
float aPedale;//l'angle pour les pédales
//le maximun de calcul sont fait dans le state default
default{
state_entry(){
//vérification des vector
if ( vAxePedalier.x + vAxePedalier.y +vAxePedalier.z > 1 ||
vLongPedalier.x + vLongPedalier.y + vLongPedalier.z > 1 ||
vAxePedalier.x + vAxePedalier.y +vAxePedalier.z < 1 ||
vLongPedalier.x + vLongPedalier.y + vLongPedalier.z < 1) state stop;
//détermination du N° des prims
integer i;
integer tot = llGetNumberOfPrims();
string name;
for(i=0; i<=tot; i++){
name = llGetLinkName(i);
if( name == "pedalier") PEDALIER = i;
else if( name == "pedal_g") PEDAL_G = i;
else if( name == "pedal_d") PEDAL_D = i;
}
pasRd = (float)pasDeg * DEG_TO_RAD;
//détermination des constantes pédalier
vector dimpedalier = llList2Vector(llGetLinkPrimitiveParams(PEDALIER, [PRIM_SIZE]), 0);
float longPedalier = dimpedalier.x*vLongPedalier.x
+ dimpedalier.y*vLongPedalier.y
+ dimpedalier.z*vLongPedalier.z;
demiPedalier = longPedalier/2.0 - shiftPedalier;//position des pédales par rapport au centre du
pédalier
float largPedalier = dimpedalier.x*vAxePedalier.x
+ dimpedalier.y*vAxePedalier.y
+ dimpedalier.z*vAxePedalier.z;
demiLargPedalier = largPedalier/2.0;
//Notez comment on doit retrancher la position du root et annuler sa rotation
posLocPedalier = (llList2Vector(llGetLinkPrimitiveParams(PEDALIER, [PRIM_POSITION]), 0) llGetPos())/llGetRot();
vRotLocPedalier = llRot2Euler(llList2Rot(llGetLinkPrimitiveParams(PEDALIER,
[PRIM_ROTATION]), 0)/llGetRot());
vector axeNonRotP = <1.,1.,1.> - vAxePedalier;
vRotLocPedalier = < vRotLocPedalier.x * axeNonRotP.x ,
vRotLocPedalier.y * axeNonRotP.y ,
vRotLocPedalier.z * axeNonRotP.z >;
vector dimPedal = llList2Vector(llGetLinkPrimitiveParams(PEDAL_G, [PRIM_SIZE]), 0);
demiLargPedal = dimPedal.y/2.0;
}
touch_start(integer tot){
state tourne;
}
}
state tourne{
state_entry(){
llSetTimerEvent(TEMPO);
}
touch_start(integer tot){
state default;
}
timer(){
rotRef = llGetRot();//à chaque incrément on relit la rotation du root dès fois qu'il ai bougé
//on incrémente l'angle et on revient à zéro si on a fait un tour
angle += pasRd;
if(angle > TWO_PI) angle = .0;
aPedalier = angle * sensPedalier;
aPedale = angle * sensPedale;
//on fait tourner le pédalier;
llSetLinkPrimitiveParamsFast(PEDALIER, [PRIM_ROTATION,
llEuler2Rot(vRotLocPedalier + vAxePedalier * aPedalier)/rotRef]);
//les pédales: il n'y a pas de rotation, juste un positionnement suivant calcul trigo
//Mais attention ce positionnement se fait en coordonnées locales (par rapport au root)
llSetLinkPrimitiveParamsFast(PEDAL_G, [PRIM_POSITION, posLocPedalier +
< demiPedalier * llCos(aPedale), //local x par rapport au centre pédalier
demiLargPedal + demiLargPedalier, //local y par rapport au centre pédalier
-demiPedalier * llSin(aPedale)> ]); //local z par rapport au centre pédalier
llSetLinkPrimitiveParamsFast(PEDAL_D, [PRIM_POSITION, posLocPedalier +
< -demiPedalier * llCos(aPedale), //local x par rapport au centre pédalier
-demiLargPedal - demiLargPedalier, //local y par rapport au centre pédalier
demiPedalier * llSin(aPedale)> ]); //locam z par rapport au centre pédalier
}
}
state stop{
state_entry(){
llOwnerSay("erreur dans une constante vector: vérifier vAxePedalier ou vLongPedalier");
}
}
Barbidule
//bon, pour les allergiques aux scripts : les choses modifiables sans trop de danger ici :
// nom des prims utilisés pour les pédales
string gNomPedale1 = "pedale gauche";
string gNomPedale2 = "pedale droite";
string gNomPedalier = "pedalier";
float gFactorRayon = 0.8; //utilisé pour faire varier le rayon, si on veut les pedales
// au bord du pedalier, mettre 1.0, au milieu : 0.0
// valeur zéro a tester ! les pédales devraient paraitre immobiles
float gDecalagePedale = 0.15; //ecartement des pedales par rapport a l'axe du vélo
//une vers la gauche et l'autre vers la droite. en metres.
//rotation initiale du vélo (quand il est droit, debout, normal quoi)
vector gRotationInitiale = <270.0, 0.0, 0.0>; // vecteur en degres
//servira a l'initialisation; c'était pénible à remettre droit à la main ^^
// axe de rotation utilisé pour tourner le pédalier
vector gAxeRotationPedalier = <-1.0, 0.0, 0.0>; // depend du prim utilisé dans le build, ici axe X
float gVitesseRotation = 0.5; // en tours/seconde. negatif si on veut changer le sens
// Pédalier
// version clean, avec deux états : "pedaler" et "stop"
// l'état par défaut sera utilisé pour initialisation.
// A ameliorer:
// 1)gestion du temps pour redemarrer un pedalage
// a partir de la derniere position. la j'avoue j'ai la flemme ^^
// 2)remplacer les llOwnerSay par un appel a une procedure globale, pour changer
// plus facilement la communication avec l'utilisateur
//bon y a quand meme des trucs bien :
// Resite aux deplacement, rotations, et resize/relink/rebuild du pedalier
// (pour la taille ou deplacement du pedalier, ca se recalcule quand ca tourne)
// la seule contrainte est que le pedalier et les pedales ne doivent pas etre
// root prim. L'autre contrainte du build est de respecter les noms des prims
// pour les pedales et le pedalier. Modifiable dans les declarations globales
// un peu plus bas.
//-----------------------------------------------------------------------------------------// Pour ne pas avoir à linker les prims dans un ordre précis, on leur donne un nom
// et la fonction LinkName2LinkNumber permet d'avoir leur numéro de prim dans le link
// (pour utiliser avec llSetLinkPrimitiveParamsFast)
integer gNumPedale1; // pour stocker les n° des prims
integer gNumPedale2; // correspondants aux pedales
// idem pour le pédalier
integer gNumPedalier;
vector gPosPedalier; //position et rayon du pédalier
float gRayonPedalier; //serviront pour calculer les positions des prims pedales
float gDelai = 0.05; //intervalle du timer utilisé pour recalculer les positions des prims
//doit etre assez rapide
//==================== FONCTIONS/PROCEDURES GLOBALES
=======================================
// parcequ'elles sont super utiles ^^
//------------------------------------------------------------------------------------integer LinkName2LinkNumber(string pName)
{ // renvoie le numero du prim enfant (ou root) de nom "pName"
// renvoie -1 si pas trouvé
integer i; // variable de boucle
integer nb = llGetNumberOfPrims();
integer result = -1; //variable utilisée pour fournir le résultat
for ( i = 0; i <= nb; ++i) // ++i plus rapide que i++
{
if (llGetLinkName(i) == pName)
{ result = i;
i = nb; //permet de finir la boucle
// plus propre de sortir de la boucle que de faire un return ici
// il vaut mieux centraliser la sortie d'une fonction à un seul endroit
// en général à la fin
}//if == pName
}//for
return(result);
// cette fonction faisant une boucle, il va de soi (!) qu'il ne faut pas
// l'appeler pendant des traitements lourds ou répétitifs sous peine de
// lenteurs
// on ne s'en servira qu'une fois pour stocker les numeros des prims pédales
// et pedalier, dans la phase d'initialisation (state default)
}// LinkName2LinkNumber
//------------------------------------------------------------------------------------// ma fonction preferee :) merci a Xaviar Czervik et Strife Onizuka
// pompé sur http://wiki.secondlife.com/wiki/LSL_Script_Efficiency/fr
//
// comme on va calculer les positions des prims en fonction du temps, il
// nous faut une fonction qui donne le temps (ha bon??)
// celle ci renvoie un entier correspondant à des millisecondes
// Bien que faisant beaucoups d'opérations (chaines, conversions...) elle
// reste très performante :)
integer Time()
{ // count milliseconds since the day began
string stamp = llGetTimestamp(); // "YYYY-MM-DDThh:mm:ss.ff..fZ"
return (integer) llGetSubString(stamp, 11, 12) * 3600000 + // hh
(integer) llGetSubString(stamp, 14, 15) * 60000 + // mm
llRound((float)llGetSubString(stamp, 17, -2) * 1000000.0)/1000; // ss.ff..f
}//time
//-------------------------------------------------------------------------------------
// petite fonction pour determiner si un avatar a le droit de cliquer sur l'objet
// Pratique si on a besoin de faire le meme test en plusieurs endroits
// et plus simple a modifier ensuite
// renvoie 1 si on a le droit, 0 sinon
integer ALeDroitDeToucher(key pKey)
{
integer result = 0; //centralisation du resultat, bla bla..
if (pKey == llGetOwner()) // pour le proprio seulement
{
result = 1;
}
return(result);
}//ALeDroitDeToucher
//------------------------------------------------------------------------------------integer Init()
{ // initialisation globale du script, sera appelé dans l'état default
integer result; //pour centraliser le resultat de la fonction
// (le pourquoi en faire une fonction expliqué plus bas)
//on va chercher les numéros des prims qu'on veut manipuler
gNumPedale1 = LinkName2LinkNumber(gNomPedale1);
gNumPedale2 = LinkName2LinkNumber(gNomPedale2);
gNumPedalier = LinkName2LinkNumber(gNomPedalier);
result = 1; //resultat de la fonction supposé OK
// bon, pour faire vraiment propre a donf : on envoie un petit
// message au propriétaire si jamais un des prims n'est pas trouvé
// c.a.d qu'il a peut-etre été renommé (ca m'arrive souvent avec le nom
// du root qui porte le nom de l'objet entier)
// C'est pour ca que Init() est une fonction, qui renverra 1 si tout
// se passe bien, ou 0 s'il y a un probleme
if ( (gNumPedale1 == -1)
|| (gNumPedale2 == -1)
// LinkName2LinkNumber renvoie -1
|| (gNumPedalier == -1)
// si le prim n'est pas trouvé
)
{
result = 0;
}// if prims pas bon
// une petite "good practice" : commenter toutes les accolades fermantes
// pour bien voir a quoi elles correspondent.
// et une autre : meme s'il n'y a qu'une seule instruction apres le IF
// quand même ouvrir et fermer des accolades (ce n'est pas obligatoire
// avec une seule instruction), ca simplifie enormément les évolutions futures.
// (et pour les débutants, ca évite de se poser des questions métaphysiques
// avec les points virgules et les clauses ELSE...)
// bon on a initialisé ce qu'on veut, on renvoie le résultat
// A noter : on enverra un message au proprio si l'init s'est mal passée
// on ne le fait pas ici (encore une good practice!) : Une fonction est sensée
// ne rien faire d'autre que renvoyer un résultat (pas d'effets de bords)
// C'est le code "appelant" qui va décider en fonction du résultat, d'envoyer
// un message. La raison est : si jamais cette fonction est utilisée à d'autres
// endroits du code, on n'aura pas forcément envie qu'elle envoie un message, mais
// juste d'utiliser le résultat de la fonction. Ici, l'envoi du message est un
// "effet de bords", c.a.d un effet supplémentaire pas forcément désirable.
// (source à mon avis de la majorité des bugs existants)
// allez la fonction est finie, on renvoie le resultat
// juste avant, on remet le vélo droit
llSetRot( llEuler2Rot( gRotationInitiale * DEG_TO_RAD ));
return(result);
}//Init
//------------------------------------------------------------------------------------AllezPedale(float angle)
{ // procedure globale pour faire tourner les pédales
// sera appelée dans le time. Il vaut mieux lire le reste du script d'abord
// pour mieux comprendre
rotation rot;
vector pos;
rotation rotVelo;
vector posVelo;
rotation decalageRot_velo_pedale;
vector size;
list lstParams;
// bon d'accord, ca fait beaucoups de variables locales, mais c'est pour etre
// plus lisible
// comme il y a des trucs qui tournent, il faut savoir autour de
// quel centre et avec quel rayon
// le centre est la position du pédalier, et le rayon sa taille en Z
// divisée par deux (à changer si on n'utilise pas le cylindre comme
// sur le cachier des charges)
//on stocke en un seul appel ces deux paramètres:
lstParams = llGetLinkPrimitiveParams(gNumPedalier, [PRIM_POSITION, PRIM_SIZE] );
gPosPedalier = (llList2Vector(lstParams, 0) - llGetRootPosition())/llGetRootRotation();
// coordonnees remises en local
size = llList2Vector(lstParams, 1); //grrrr, obligé de passer par un vecteur intermédiaire
gRayonPedalier = size.z / 2.0; //car on ne peut ecrire .z que après une variable
gRayonPedalier *= gFactorRayon ; // plus ou moins grand
//remarque : gPosPedalier et gRayonPedalier sont globales car dans une précédante version
// elles etaient calculées seulement à l'initialisation.
// les recalculer ici permet de resister au resize et/ou deplacements du pédalier :)
// j'ai juste eut la flemme de redeclarer ces variables ici en local
// on a besoin de la rotation globale du vélo pour repositionner
// dans le bon repère
rotVelo = llGetRootRotation();
posVelo = llGetRootPosition();
// l'utilisation permettra de ne pas rappeler inutilement llGetRot
// et llGetPos() si on s'en sert plusieurs fois.
// positionnement du pédalier (juste la rotation)
rot = llEuler2Rot( gAxeRotationPedalier * angle );
// le - en tatonnant un peu, pour faire tourner dans l'autre sens
rot /= rotVelo; //ramené dans le repère du vélo
llSetLinkPrimitiveParamsFast(gNumPedalier, [PRIM_ROTATION, rot] );
//llOwnerSay((string)llRot2Euler(rot)); //commentaire a enlever pour debugage
// pour la rotation de la pédale, ha ben c'est cool, c'est comme le vélo puisqu'on
// doit rester aligné . Ha non, presque ca! le root prim du velo que j'ai buildé
// (très vite fait) est un tore, le vélo avançant suivant son axe Z
// * petite parenthèse : si on prévoit de transformer ca en véhicule il faudrait
// * mieux diriger le root vers l'axe des X il me semble. pô très sur de ce que je dis là...
// * fin de la parenthèse
// donc, le vélon vers Z, et voila t'y pas que la pédale est orientée selon -Y
// (hasard du build initial). Il faut corriger la rotation entre les deux:
decalageRot_velo_pedale = llRotBetween( <0.0,0.0,1.0> , <0.0,-1.0,0.0> );
// c'est ca que je trouve pas propre dans mon script !!!
//positionnement local d'une pedale
// déja le centre du pedalier en local
pos = gPosPedalier ;
// on ajoute la trajectoire circulaire
pos += (gRayonPedalier * <0.0,llSin(angle), llCos(angle)>);
// ha ben oui, quand ca tourne, il y a du sinus et du cosinus ^^
// il y a certainement une explication logique et peut-etre simple
// pour expliquer l'ordre des composantes du cercle. J'ai essayé de faire
// propre en suivant le guide sur les rotations, mais j'avais pas les neurones ^^
// résultat, j'ai tatonné pour trouver le bon ordre. il y a forcement une explication
// mathematique, du genre on compose une rotation à une trajectoire circulaire par
// rapport au référentiel du root fois l'age du capitaine...
//llOwnerSay((string)(pos));
pos += < gDecalagePedale, 0.0, 0.0>; //ecartement vers la gauche( ? ou droite :)
rot = decalageRot_velo_pedale / rotVelo ; //rotation en absolu
llSetLinkPrimitiveParamsFast(gNumPedale1, [ PRIM_ROTATION, rot,
PRIM_POSITION, pos] );
//idem pour l'autre pédale, mais avec un angle + 180° (ca change juste le signe des cos/sin)
pos = gPosPedalier; //centre du pedalier
pos += (gRayonPedalier * <0.0,-llSin(angle), -llCos(angle)>) ;
pos -= < gDecalagePedale, 0.0, 0.0>;//ecartement vers la droite( ? ou gauche :)
// la rotation ne change pas
llSetLinkPrimitiveParamsFast(gNumPedale2, [PRIM_POSITION, pos ,
PRIM_ROTATION, rot] );
//on pourrait optimiser un peu en stockant sinus et cosinus en variable plutot
//que de les recalculer pour la deuxieme pedale.
// mais la, j'en ai marre des variables locales :)
}
//==================================== ETATS
=======================================
// 3 états donc , l'etat par defaut pour faire l'initailisation
// et ensuite deux états : "pedaler" et "stop"
//
// on commence par l'etat defaut qui passera ensuite a l'etat "stop"
// dans "stop" : un click pour passer à "pedaler"
// dans "pedaler" : on fait tourner le bouzin, un click pour passer à "stop"
//
// pour des projets plus complexes, il FAUT faire un graphe des états :
//
// --------------------------------------------// | DEFAULT
|
| STOP
|
// | (initialisations) |---------> | (arrete les pédales) |
// --------------------------------------------//
click |
/|\
//
|
|
//
\|/
| click
//
-----------------------//
| PEDALER
|
//
| (tourne les pédales) |
//
-----------------------default //===============================================================
// etat default uniquement pour initialisations
{
state_entry()
{
//demarrage global du code, on fait l'initialisation
integer resultat = Init();
if (resultat == 0) // si c'est pas bon
{
llOwnerSay("Warning : un des prims n'est pas bien nommé");
// voila, le proprio est averti, on ne va pas dans les autres etats
// qui seraient buggués dans ce cas. On ne fait plus rien d'autre
// jusqu'à correction.
// comme l'etat default ne sert qu'a faire l'init (good practice ^^)
// on n'est pas perturbé par des évenements parasites qui pourraient
// se déclencher (timer, touch...etc)
// vous comprendrez mieux ça, le jour ou vous perdrez un objet qui
// bouge n'importe ou apres une modif de script ^^
}
else // tout va bien on va pouvoir commencer
{
llOwnerSay("Vélo OK, pret a pedaler");
// ca fait pas de mal de le dire aussi quand tout va bien ^^
state stop; // c'est parti, on commence a l'arret
}//if resultat
}//state_entry
//---------------------------------------------------------on_rez(integer start_param)
{
llResetScript();
// pas obligatoire, c'est selon les besoins. Du coup on
// réinitialise tout. Je trouve ça plus propre.
}//on_rez
// a copier dans les autres etats (pour etre sur de rezzer un vélo
// droit et a l'arret)
}//state default
state pedaler //==========================================================
// tient, pourquoi pas d'abord l'etat "stop" ?
// Ce n'est pas obligatoire, mais deux raisons tout de même :
// 1) Je ne saurai quoi stopper qu'apres avoir ecris l'etat "pedaler"
// 2) C'est l'etat pedaler qui sera le plus complexe, je prefere avoir
// moins de code à faire défiler pendant les corrections de bugs
// (fainénant inside ^^)
//
{
state_entry()
{
// Pour pédaler, on va utiliser un timer assez précis dans lequel
// on positionnera les prims avec llSetLinkPrimitiveParamsFast
// bon, ben c'est parti:
llSetTimerEvent(gDelai);
}//state_entry
//---------------------------------------------------------timer()
{
integer t = Time(); // lecture du temps en millisecondes
//vu qu'on va faire tourner des trucs, il va nous falloir un angle
float angle = ((float)t/1000.0 ) * TWO_PI * gVitesseRotation;
//
( de 0 a 1sec exclu ) * 2pi * vitesse
AllezPedale(angle); //
}//timer
// bon ca y est ca marche, ne pas oublier le click pour arreter
//---------------------------------------------------------touch_start(integer total_number)
{
//si on a le droit de toucher le velo
if (ALeDroitDeToucher(llDetectedKey(0)) == 1) // pas la peine de tester tous les avatars
{
llSetTimerEvent(0.0); // normalement inutile car on va changer d'etat
// néanmoins plus propre (a force de bugs on se mefie!)
llOwnerSay("velo arrete");
state stop;
}
}//touch_start
//copie du on_rez
//---------------------------------------------------------on_rez(integer start_param)
{
llResetScript();
// J'ai la flemme de me convaicre qu'on peut effectivement
// passer par le on_rez d'un état pas default.
}//on_rez
}//pedaler
state stop //==========================================================
{
state_entry()
{
//he ben en fait, on n'a rien a faire dans cet etat, a part
//le touch pour relancer le pedalage
// ca fait pas de mal d'avoir un state_entry tout pret si jamais.
llSetTimerEvent(0.0); //inutile car changé d'état, mais bon.
//voila c'est stoppé, c'est sur ^^
}
// Le seul truc qu'on a à faire c'est de relancer le pedalage quand on clique
//---------------------------------------------------------touch_start(integer total_number)
{
//si on a le droit de toucher le velo
if (ALeDroitDeToucher(llDetectedKey(0)) == 1) // pas la peine de tester tous les avatars
{
state pedaler;
}
}//touch_start
//copie du on_rez
//---------------------------------------------------------on_rez(integer start_param)
{
llResetScript();
// bon allez, c'est le meme on_rez copie/collé je vous epargne les commentaires deja lus
}//on_rez
}//stop
Bestmomo
// *****************
// Paramètres
// *****************
// Axe de rotation du pédalier
vector AXE = <1.0, .0, .0>;
// Vitesse de rotation en tours/seconde
float VITESSE = .4;
// Numéro de liaison du pédalier
integer NUMPEDALIER = 5;
// Numéro de liaison pédale 1
integer NUMPEDALE1 = 4;
// Numéro de liaison pédale 2
integer NUMPEDALE2 = 6;
// Variables de travail
integer
iOn;
float
f_elementaire;
float
f_angle;
rotation
rRotPedalier;
vector
vPosPedalier;
vector
vPosPedale;
float
fDelai = .04;
vector
vTournant;
// on-off
// Angle de base
// Angle cumulé
// Rotation locale du pédalier
// Position locale pédalier
// Position locale pédale 1
// Délai de base en secondes
// Vecteur tournant
// Récupération position locale prim enfant
vector GetLinkPos(integer link_num) {
vector v = llList2Vector(llGetLinkPrimitiveParams(link_num, [PRIM_POSITION]), 0);
return (v - llGetRootPosition()) / llGetRootRotation();
}
// Récupération rotation locale prim enfant
rotation GetLinkRot(integer link_num) {
rotation r = llList2Rot(llGetLinkPrimitiveParams(link_num, [PRIM_ROTATION]), 0);
return r / llGetRootRotation();
}
default
{
state_entry()
{
// Position locale du pédalier
vPosPedalier = GetLinkPos(NUMPEDALIER);
// Rotation locale du pédalier
rRotPedalier = GetLinkRot(NUMPEDALIER);
// Angle de base
f_elementaire = VITESSE * 14.4 * DEG_TO_RAD;
// Adaptation de l'axe
AXE *= rRotPedalier;
// Position locale pédale 1
vPosPedale = GetLinkPos(NUMPEDALE1);
// Vecteur tournant
vTournant = (vPosPedale - vPosPedalier) / rRotPedalier;
// Petit message
llOwnerSay("Cliquez sur le vélo pour activer ou désactiver la rotation");
}
touch_start(integer total_number)
{
if(iOn = !iOn) llSetTimerEvent(fDelai);
else llSetTimerEvent(.0);
}
timer()
{
// Calcul nouvel angle
f_angle += f_elementaire;
// Rotation
rotation r_rot = rRotPedalier * llAxisAngle2Rot(AXE, f_angle);
// Application de la rotation au pédalier transposée en global
llSetLinkPrimitiveParamsFast(NUMPEDALIER, [PRIM_ROTATION, r_rot / llGetRootRotation()]);
// Vecteur rotatif pour les pédales (les vecteurs sont opposés)
vector vPedale = vTournant * r_rot;
// Positionnement pédale 1
llSetLinkPrimitiveParamsFast(NUMPEDALE1, [PRIM_POSITION, vPosPedalier + vPedale]);
// Positionnement pédale 2
llSetLinkPrimitiveParamsFast(NUMPEDALE2, [PRIM_POSITION, vPosPedalier - vPedale]);
}
}

Documents pareils