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]); } }