Open prim animator

Répondre
Partager Rechercher
Bon je poursuis ma vielle idée https://forums.jeuxonline.info/showt...php?t=1082237& et j'ai trouvé ce script:

Code:
// Script Name: Open_Prim_Animator.lsl
// Author: Todd Borst
// Summary: This is a simple prim animation script.  Just add this script
//// to your object and a dialog will automatically pop up for you to use.
// 
//// Features:
//// -Single script "Prim Puppeteer" like animation tool
//// -Playback controllable through external scripts
//// -Animation is scalable and resizeable
//// -On-touch trigger built-in
//// -Completely free and open sourced 
//

// Downloaded from : http://www.free-lsl-scripts.com/freescripts.plx?ID=1580

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

integer COMMAND_CHANNEL = 32;
 
integer primCount = 0;
integer commandListenerHandle = -1;
 
list posList     = [];
list rotList     = [];
list scaleList   = [];
integer currentSnapshot   = 0;
integer recordedSnapshots = 0;
 
vector rootScale   = ZERO_VECTOR;
vector scaleChange = <1,1,1>;
 
// For tracking memory usage.  The amount of snapshots you can record is based
// on the number of prims and available memory.  Less prims = more snapshots
integer maxMemory  = 0;
integer freeMemory = 0;
 
integer playAnimationStyle = 0;
// The values for playAnimationStyle means
// 0 = no animation playing
// 1 = play animation once
// 2 = play animation looping
 
// This function is used to display a recorded snapshot
showSnapshot(integer snapNumber)
{
    if(snapNumber > 0 && snapNumber <= recordedSnapshots )
    {
        integer i = 0;
        vector   pos;
        rotation rot;
        vector   scale;
        integer numpriminlist;
 
        vector rootPos = llGetPos();
 
        // Might want to move llGetRot() into the loop for fast rotating objects.
        // Rotation during the animation playback may cause errors.
        rotation rootRot = llGetRot();
 
        //2 is the first linked prim number.
        for( i = 2; i <= primCount; i++)
        {
            numpriminlist = ((snapNumber-1)*(primCount-1))+(i-2);
            pos     = llList2Vector(posList, numpriminlist);
            rot     = llList2Rot(rotList, numpriminlist);
            scale   = llList2Vector(scaleList, numpriminlist);
 
            //Adjust for scale changes
            if( rootScale.x != 1.0 || rootScale.y != 1.0 || rootScale.z != 1.0 )
            {
                pos.x *= scaleChange.x;
                pos.y *= scaleChange.y;
                pos.z *= scaleChange.z;
                scale.x *= scaleChange.x;
                scale.y *= scaleChange.y;
                scale.z *= scaleChange.z;
            }
 
            llSetLinkPrimitiveParamsFast( i, [ PRIM_POSITION, pos, PRIM_ROTATION, rot/rootRot, PRIM_SIZE, scale ] );
        }
    }
}
 
// This function is used to start a sequential animation playback.
// If the delay speed is set too low, the script might not be able to keep up.
playAnimation(float delay, integer loop)
{
    if(delay < 0.1) delay = 1.0;
 
    if( loop == FALSE)
        playAnimationStyle = 1;
    else
        playAnimationStyle = 2;
 
    if (recordedSnapshots >= 1)
        llSetTimerEvent(delay);
}
 
// This shows the edit menu
showMenuDialog()
{
    string temp = (string)((float)freeMemory/(float)maxMemory * 100.0);
    string menuText = "Available Memory: " + (string)freeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" +
    "\nCurrent Snapshot: " + (string)currentSnapshot +"\tSnapshots Recorded: " + (string)recordedSnapshots +
    "\n\n[ Record ] - Record a snapshot of prim positions\n[ Play ] - Play back all the recorded snapshots\n[ Publish ] - Finish the recording process\n[ Show Next ] - Show the next snapshot\n[ Show Prev ] - Show the previous snapshot";
 
    llDialog(llGetOwner(), menuText, ["Record","Play","Publish","Show Prev","Show Next"], COMMAND_CHANNEL);
}

recordSnapshot(){
    integer i = 0;
    //2 is the first linked prim number.
    vector rootPos = llGetPos();
    for( i = 2; i <= primCount; i++)
    {
        vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
        //need to convert into local position
        pos.x -= rootPos.x;
        pos.z -= rootPos.z;
        pos.y -= rootPos.y;
        pos = pos / llGetRot();
        posList += pos;

        rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
        //Converting into local rot
        rot = rot / llGetRot();
        rotList += rot;

        scaleList += llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
    }
    recordedSnapshots++;

    llOwnerSay("Total number of snapshots recorded: " + (string)recordedSnapshots);
    freeMemory = llGetFreeMemory();
}
 
default
{
    state_entry()
    {
        maxMemory = llGetFreeMemory();
        freeMemory = llGetFreeMemory();
 
        primCount = llGetNumberOfPrims();
        commandListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), "");
        showMenuDialog();
 
        //setting initial root scale.  this allows the animation to scale if the root size is changed afterwards.
        rootScale = llGetScale();
    }
 
    //Feel free to remove this on-touch trigger if you are using your own script to control playback
    touch_start(integer num_detected)
    {
        //only activate after publish.
        if (commandListenerHandle == -1)
        {
            //if animation not playing start it, else stop it.
            if( playAnimationStyle == 0)
                playAnimation(1.0,TRUE);
            else
            {
                playAnimationStyle = 0;
                llSetTimerEvent(0);
            }
        }
    }
 
    changed(integer change)
    {
        //this is needed to detect scale changes and record the differences in order to adjust the animation accordingly.
        if (change & CHANGED_SCALE)
        {
            if (rootScale != ZERO_VECTOR)
            {
                vector newScale = llGetScale();
                //since change_scale is triggered even with linked prim changes,
                //this is to filter out non-root changes.
                if( ( newScale.x / rootScale.x) != scaleChange.x ||
                    ( newScale.y / rootScale.y) != scaleChange.y ||
                    ( newScale.z / rootScale.z) != scaleChange.z )
                {
                    scaleChange.x = newScale.x / rootScale.x;
                    scaleChange.y = newScale.y / rootScale.y;
                    scaleChange.z = newScale.z / rootScale.z;
                }
            }
        }
        // if new prims are added or removed from this object then the script resets
        // because the animations are now broken.
        else if (change & CHANGED_LINK)
        {
            if( primCount != llGetNumberOfPrims() )
            {
                llOwnerSay("Link change detected, reseting script.");
                llResetScript();
            }
        }
    }
 
    //The message link function is to allow other scripts to control the snapshot playback
    //This command will display snapshot #2:
    //      llMessageLinked(LINK_ROOT, 2, "XDshow", NULL_KEY);  llSleep(1.0);
    //
    //This command will play through all the recorded snapshots in ascending order.  The number "1.0" is the delay speed and can be changed.
    //      llMessageLinked(LINK_ROOT, 0, "XDplay", "1.0");
    //
    //This command will loop through all the recorded snapshots in ascending order.  The number "1.0" is the delay speed and can be changed.
    //      llMessageLinked(LINK_ROOT, 0, "XDplayLoop", "1.0");
    //
    //To stop any playing animation use
    //      llMessageLinked(LINK_ROOT, 0, "XDstop", NULL_KEY);
    link_message(integer sender_num, integer num, string str, key id)
    {
        if ("XDshow" == str && num >= 1 && num <= recordedSnapshots)
            showSnapshot(num);
        else if ("XDplay" == str)
        {
            currentSnapshot = 1;
            float delay = (float)((string)id);
            playAnimation(delay,FALSE);
        }
        else if ("XDplayLoop" == str)
        {
            float delay = (float)((string)id);
            playAnimation(delay,TRUE);
        }
        else if ("XDstop" == str)
        {
            playAnimationStyle = 0;
            llSetTimerEvent(0);
        }
    }
 
    //This event handler takes care of all the editing commands.
    //Available commands are: record, play, publish, show next, show prev, show #
    listen(integer channel, string name, key id, string message)
    {
        list parsedMessage = llParseString2List(message, [" "], []);
        string firstWord = llToLower(llList2String(parsedMessage,0));
        string secondWord = llToLower(llList2String(parsedMessage,1));
 
        //display a snapshot
        if("show" == firstWord && recordedSnapshots > 0)
        {
            //stop any currently playing animation.
            llSetTimerEvent(0);
 
            if(secondWord == "next")
            {
                currentSnapshot++;
                if(currentSnapshot > recordedSnapshots)
                    currentSnapshot = 1;
 
                showSnapshot(currentSnapshot);
            }
            else if(secondWord == "prev")
            {
                currentSnapshot--;
                if(currentSnapshot < 1)
                    currentSnapshot = recordedSnapshots;
 
                showSnapshot(currentSnapshot);
            }
            else
            {
                // when the conversion fails, snapshotNumber = 0
                currentSnapshot = (integer)secondWord;
                if(currentSnapshot > 0 && currentSnapshot <= recordedSnapshots )
                {
                    showSnapshot(currentSnapshot);
                    llOwnerSay("Showing snapshot: "+(string)currentSnapshot);
                }
                else
                {
                    llOwnerSay("Invalid snapshot number given: " + (string) currentSnapshot +
                                "\nA valid snapshot number is between 1 and " + (string) recordedSnapshots);
                    currentSnapshot = 1;
                }
            }
        }
        //record a snapshot
        else if(firstWord == "record")
        {
            recordSnapshot();
        }
        //play the animation from beginning to end once without looping.
        else if (firstWord == "play")
        {
            float delay = (float)secondWord;
            currentSnapshot = 1;
            //play the animation once without loop
            playAnimation(delay, FALSE);
        }
        //publish disables the recording features and enables the on-touch trigger
        else if("publish" == firstWord)
        {
            //stop any currently playing animation.
            llSetTimerEvent(0);
            playAnimationStyle = 0;
            currentSnapshot = 1;
 
            //remove listeners to disable recording
            llListenRemove(commandListenerHandle);
            commandListenerHandle = -1; //indicating that it's been published
 
            llOwnerSay("Recording disabled. Publish complete.\nClick me to toggle animation on/off.");
        }
 
        //if not published, show menu
        if(commandListenerHandle != -1)
            showMenuDialog();
    }
 
    //Timer event is used to handle the animation playback.
    timer()
    {
        showSnapshot(currentSnapshot);
 
        //if not at the end of the animation, increment the counter
        if(currentSnapshot < recordedSnapshots)
            currentSnapshot++;
        else
        {
            // if animation is looping, set the counter back to 1
            if( playAnimationStyle == 2)
                currentSnapshot = 1;
            // if animation isn't looping, stop the animation
            else
            {
                llSetTimerEvent(0);
                //if not published, show dialog menu
                if(commandListenerHandle != -1)
                    showMenuDialog();
            }
        }
    }
}
Il a le mérite d'être simple et de bien fonctionner. J'ai des tas d'idée pour en améliorer les fonctionnalités. Et il permet de bien voir les limitations de cette approche.

1) L'occupation mémoire: ben c'est pas si mal que ça: sur un linkset de 21 prims, un snapshot prend moins de 5% de la mémoire, alors que l'on enregistre sans discernement la position, rotation et size de chaque prim. On doit pouvoir faire au moins 20 frames, ce qui me parait suffisant.

2) La vitesse de restitution: là c'est nettement moins bien: elle est limité volontairement dans le script à 1 fps. Là aussi il n'y a pas de discernement: on parcourt les listes et on fait un llSetLinkPrimitiveParamsFast sur chaque prim.

Ma petite idée pour améliorer ces 2 points c'est d'utiliser une 4éme liste d'integer qui enregistrera pour chaque prim et chaque frame s'il y a eu changement. Je compte utiliser un masque binaire qui renseignera sur la nature du changement, ce qui permettra ultérieurement l'animation de texture et de particle par scripts externes.

Pour le gain en mémoire, on enregistrerait dans les listes posList, rotList et sizeList un integer au lieu de vector et de rotation si la prim n'a pas bougé (gain de 28 octects pour 20 dépensés dans la nouvelle list, soit un gain net de 8 octect multiplié par le nombre de prim inchangés). Et bien sur on peut grapiller aussi d'autres octects si il n'y a qu'une ou deux données qui ont changées.

Et pour le gain en vitesse on regarderait dans cette nouvelle liste si la prim a bougé et on n'extrairait des autres listes et repositionnerait que si oui.

Evidemment, le cas défavorable: tout a bougé, cas assez fréquent dans l'animation de puppet que l'on déplace en laissant un root invisible fixe.

Aussi je pense que ce cas là, il va falloir le traiter autrement en recommandant de faire carrément un déplacement du root (et en traitant ça bien sur) plutôt que de déplacer toutes les prims en laissant le root fixe. Cela pose le problème de retour à la position d'origine qu'il faudra mémoriser. Mais je verrais ça plus tard.

Vous en pensez quoi ?
Mettre le setup et le mover dans le même script n'est pas très optimal. Tout garder en mémoire n'est pas aussi très sûr. Le script est intéressant mais on peut effectivement bien l'optimiser.

Je suis actuellement dans une réflexion sur le sujet avec Sandrine pour développer son produit et je me rends compte de la délicate question de la mémoire. Encore ça peut passer pour les vecteurs mais le changement de textures devient vite problématique. La solution que je vois est de vraiment séparer la partie setup avec plusieurs scripts à la demande selon ce qu'on veut modifier. Ensuite le passage incontournable pas la notecard avec optimisation des informations (suppression des redondances...). Et un script de mise en œuvre le plus léger possible, mais sans doute plusieurs en cas de montée en charge qui reste difficile à automatiser.
J'ai mis en oeuvre mon idée.
Tests avec une poupée de 21 prims + root
Référence avec le script originel: 27 frames enregistrées et app. 0,05 à 0,06 secondes/frame (moyenne sur 10).

Mon moteur "économique" :
Si on ne bouge que 6 prims/21: très satisfaisant en temps: app. 0,01 à 0,02 s/frame
Très décevant en mémoire : seulement 25 frames enregistrées (soit 2 de moins) alors que j'en escomptais 10 ou 20 de plus.
Si on bouge les 21 prims à chaque frame: même performance en temps que la référence et 20 frames enregistrées (je m'attendais à pire), ce qui n'est pas catastrophique.

Autrement dit objectif atteint en temps mais pas en mémoire. J'ai donc remis mon métier sur l'ouvrage: au lieu de bourrer les listes avec des integer inutiles, je n'enregistre donc que les changement et je gére ça en enregistrant dans ma liste des changements le numéro de frame en partie entiére et le numéro de prim concerné en partie décimale. Pour extraire facilement des listes je maintiens de plus un index.

Résultat des courses :
Sur 6 prims: mémoire très satisfaisante: après le 1er shot qui prend 10% de la mémoire (normal on doit tout enregistrer en double), on ne prend après que 1% de la mémoire. J'ai arrêté mes tests à 20 frames et il me restait encore 60%. Temps d'éxécution : j'ai doublé par rapport au test précédent : 0,02 à 0,04 secondes par frame. La faute au calcul et au cast pour extraire le numéro de prim de la liste.
Les 21 prims: mémoire inchangée (20 frames: normal un float ne coute pas plus cher qu'un integer). Par contre en temps c'est la cata : 0,1 seconde / frame.

Conclusion: il faut aller plus loin: pour éviter le calcul si gourmand en temps, je pense que je vais gérer avec 2 listes d'integer: une qui enregistrera en couple (strided list) le numéro de frame et le nombre de prim concernées, l'autre qui enregistrera les numéros de prims affectées. La premiére permettant de situer ainsi le début de frame dans la seconde. Je pense que le gain en mémoire constaté permet de rajouter cette liste somme toute peu gourmande (2 integer par frame). On peut diminuer sa taille aussi de 50% en réutilsant le principe du float: le calcul n'aura lieu qu'une fois par frame, et pas à chaque prim.

Mais à terme je pense qu'il faudra proposer 2 moteurs: l'originel si on bouge toutes les prims, ou l'optimisé si on n'en bouges allez disons moins de 70%. Cela amène donc tout naturellement à ce que suggére fort judicieusement Best: sortir le dialogue utilisateur et les calculs d'enregistrement du moteur. Et cela fera gagner un peu de mémoire je pense.

Et puis urgent aussi la sauvegarde par notecard: je passe plus de temps à bouger les prims pour les tests qu'à coder. Bien sur la sauvegarde devra être compatible avec les 2 moteurs afin de changer simplement de moteur en rechargeant.

Je vais donc me consacrer à ça avec la version actuelle que je publie ici, des fois que vous vouliez faire des tests ou qui sait me donner un coup de main.
Code:
// Script Name: Open_Prim_Animator V1.3
devenu obsoléte
Bon ça se bouscule pas pour participer ; m'en fiche je le ferais toute seule na !!

Donc voici la version 1.6: 2 scripts, le setup qui sert pour les réglages et l'engine qui est le moteur d'exécution, auquel il faut ajouter un trigger (déclencheur) pour l'exécution.

Cette version supporte le vidage de la mémoire dans le chat, et le rechargement à partir de notecard: il suffit de copier telles quelles les lignes du chat dans une notecard dont le nom devra obligatoirement commencer par : OPRIAN PRS

Oui c'est officiel: le nom du projet est OPRIAN pour Open PRIm ANimator.

Bon j'ai aussi optimisé le moteur tant que j'ai pu, implémenté une surveillance de la mémoire: si on sature, ça ne plante plus le script et on sauve ainsi l'animation déjà enregistrée. Et puis une fonction sympa qui existait dans le moteur d'origine et que j'ai implémenté dans la notecard: vous pouvez redimensionner tout le build, l'animation s'adaptera. J'estime cette version suffisamment aboutie et sure pour être utilisable. Elle supporte aussi la dépublication: il suffit de remettre le setup dans l'objet et vous pourrez rajouter des snapshots à l'animation existante.

le script engine 2 (l'engine 1, moteur originel, viendra plus tard):
Code:
// Script Name: OPRIAN engine 2 : OPen_PRIm_ANimator V1.6
devenu obsoléte
Le script setup : (attention amis scripteurs: si vous le modifier dans un objet pensez à commenter la dernière ligne avant publication)
Code:
// Script Name: OPRIAN setup : OPen_PRIm_ANimator V1.6
devenu obsoléte
Enfin un trigger, qu'il faut mettre dans l'objet AVANT de publier avec le setup
Code:
// Script Name: OPRIAN click trigger : OPen_PRIm_ANimator V1.6

// trigger script for use in set scripts OPRIAN author Elenia Boucher

//must be in objet BEFORE publishing

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////


integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;

integer C_PUBLISHED = 60;
integer C_UNPUBLISHED = 61;

stop(){
    llMessageLinked(LINK_THIS, C_LOOP_OFF, "", "");
    llMessageLinked(LINK_THIS, C_STOP_ANIM, "", "");//comment to stop at the end of loop
}

default{
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PUBLISHED) state OFF;
    }
}

state OFF{
    touch_start(integer total_number){
        llMessageLinked(LINK_THIS, C_LOOP_ON, "", "");//comment to one shot animation
        state ON;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED) state default;
    }
    
}

state ON{
    state_entry(){
        llMessageLinked(LINK_THIS, C_PLAY_ANIM, "", "");
    }
    
    touch_start(integer total_number){
        stop();
        state OFF;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED){
            stop();
            state default;
        }
    }
    
}
Pour les développements à venir:
v1.7 : temps individuels entre chaque frame, et plus courts que 1 seconde
v1.8 : mode reverse et symetrie pour le playback
v1.9 : suppression, insertion et édition de snapshots dans l'animation

la v2.0 devrait voir l'ajout de l'engine 1 avec toutes ces fonctionnalités et sera distribuée sur le marketplace.
Voilà la version que vous attendiez tous : la 1.7, qui supporte des temps individuels pour chaque snapshot, temps qui peuvent descendre jusqu'à 0.1 seconde.
Alors cela se règle par chat: commande '/32 delay' avec 2 versions :
'/32 delay x.x' , avec x.x le temps pour le prochain snapshot que vous enregistrerez.
'/32 delay snapshot y x.x' : change le temps pour le snapshot y déjà enregistré pour x.x
J'ai aussi corrigé un gros bug: si on enregistrait un snapshot après avoir redimmensionné le build c'était décalé; c'est réglé maintenant.

l'engine 2:
Code:
// Script Name: OPRIAN engine 2 : OPen_PRIm_ANimator V1.7
devenu obsoléte
le setup
Code:
// Script Name: OPRIAN setup OPen_PRIm_ANimator V1.7
devenu obsoléte
et en bonus, un loader qui permet de recharger la notecard en cas de reset des scripts dans le build; à utiliser sans le setup dans l'inventaire du build sinon je répond de rien.
Code:
// Script Name: OPRIAN loader OPen_PRIm_ANimator V1.7

// Loader script for use in set scripts OPRIAN: Elenia Boucher
// Don't use with setup script : remove from inventory prim
// use only after publishing to reload notecard in case reset

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

string sTypEngine;
 
integer iPrimCount = 0;
 
integer iCurrentSnapshot   = 1;
integer iRecordedSnapshots = 999;
 
integer iFreeMemory = 0;

list lPrevSnap;

vector vScaleChange;

integer iLoad;
integer iFrameLoad;
string sPrimLoad;
integer iEOF;

integer iPrimCounter;

string sNotecardPRS;
key kHandlePRS; 
integer iLinePRS;

//dialog constants - naming: R request, A answer, C command - direction setup/triggers -> engine

integer C_PLAY_FRAME = 1;

integer R_FREE_MEMORY= 22;
integer A_FREE_MEMORY= 23;

integer R_ACTUAL_FRAME = 26;
integer A_ACTUAL_FRAME = 27;
integer A_PLAYING_FRAME = 28;

integer R_SCALE = 30;
integer A_SCALE = 31;

integer C_RECORD_FRAME = 40;
integer A_RECORD_FRAME = 41;
integer C_RECORD_PRIM = 42;
integer A_RECORD_PRIM = 43;
integer C_END_FRAME = 44;

integer R_TYP_ENGINE = 47;
integer A_TYP_ENGINE = 48;

integer C_RESET = 50;
integer A_OUT_MEMORY = 51;

//use only for trigger
integer C_PUBLISHED = 60;

errorLine(string note){
    llDialog(llGetOwner(), "error at line " + (string)iLinePRS + "\nin notecard\n" + note + "\n loading stop here", 
            ["Ok"], - 32768);
    iLoad = FALSE;
    llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
    llOwnerSay("End reading notecard on error\nTotal number of snapshots loaded: " + (string)iRecordedSnapshots);
}

recordPrevSnap(){//record list lPrevSnap after each frame (for engine 1)
    lPrevSnap = [];
    vector pos;
    rotation rot;
    vector scale;
    integer i;
    vector rootPos = llGetPos();
    
    for(i = 2; i <= iPrimCount; i++){
        vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
        //need to convert into local position
        pos.x -= rootPos.x;
        pos.z -= rootPos.z;
        pos.y -= rootPos.y;
        pos = pos / llGetRot();
        
        rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
            //Converting into local rot
            rot = rot / llGetRot();
        
        vector scale = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
        lPrevSnap += [pos, rot, scale];
    }
}

default{
    state_entry(){
        
        //search notecard PRS
        sNotecardPRS = "";
        integer nb = llGetInventoryNumber(INVENTORY_NOTECARD);
        integer i;
        for(i=0; i<nb; i++){
            string name = llGetInventoryName(INVENTORY_NOTECARD, i);
            if(~llSubStringIndex(name, "OPRIAN PRS")) sNotecardPRS = name;
        }
        if(sNotecardPRS == ""){//no found
            llDialog(llGetOwner(), "Not notecard 'OPRIAN PRS'", ["Ok"], -32768);
            state published;
        }
        llSleep(1.0);//to let the engine start
        //erase current animation
        llMessageLinked(LINK_THIS, C_RESET, "", "");
        llSleep(1.0);//to let the engine start
        llMessageLinked(LINK_THIS, R_TYP_ENGINE, "", "");
        iRecordedSnapshots = 0;
        iCurrentSnapshot = 0;
        lPrevSnap = [];
    }
    
    dataserver(key query_id, string data){
        if (query_id == kHandlePRS) {
            if (data != EOF){
                if(data == ""){//empty line
                    kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);
                    return;
                }
                integer index = llSubStringIndex(data, ": ");
                if(index == -1){ errorLine(sNotecardPRS); return;}
                data = llDeleteSubString(data, 0, index + 1);//delete time stamp and ': '
                iFrameLoad = (integer)llGetSubString(data, 0, index -1);
                if(iFrameLoad == 0 || index == -1) { errorLine(sNotecardPRS); return;}
                index = llSubStringIndex(data, ";");
                sPrimLoad = llDeleteSubString(data, 0, index);//delete the frame number
                if(iFrameLoad == iCurrentSnapshot + 1){//new frame
                    if(iFrameLoad > 1){//end previous frame
                    llOwnerSay("Snapshot " + (string)(iFrameLoad - 1) + " loaded");
                        llMessageLinked(LINK_THIS, C_END_FRAME, "", "");//triggers an answer free memory
                    }else{ //first frame
                        iCurrentSnapshot = 1;
                        llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");
                        llOwnerSay("begin reading notecard");
                    }
                }else{
                    llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
                }
            }else{//end notecard
                iEOF = TRUE;
                llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
                llOwnerSay("Snapshot " + (string)(iRecordedSnapshots + 1) + " loaded");
                llOwnerSay("End reading notecard");
            }
        }
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == A_TYP_ENGINE){
            sTypEngine = str;
            kHandlePRS = llGetNotecardLine(sNotecardPRS, iLinePRS = 0);// request first line
        }else if(num == A_FREE_MEMORY){//memory answer after C_END_FRAME
            iFreeMemory = (integer)str;
                iRecordedSnapshots++;
                llMessageLinked(LINK_THIS, C_PLAY_FRAME, (string)iCurrentSnapshot, "");
                iCurrentSnapshot++;
        }else if(num == A_ACTUAL_FRAME){//answer after playing frame
                recordPrevSnap();
                if(iEOF){
                    llMessageLinked(LINK_THIS, C_PLAY_FRAME, "1", "");
                    state published;
                }else if(iCurrentSnapshot == 2)
                    llMessageLinked(LINK_THIS, R_SCALE, "", "");//request scale after loading frame 1
                else
                    llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");//next frame
        }else if(num == A_SCALE){//need scale for engine 1
            vScaleChange = (vector)str;
            llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");//next frame
        }else if(num == A_RECORD_FRAME){//answer after C_RECORD_FRAME
            // new frame first prim
                iPrimCounter = (integer)llGetSubString(sPrimLoad, 0, llSubStringIndex(sPrimLoad, ";") -1);
                llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
        }else if(num == A_RECORD_PRIM){
            kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);//read the next notecard line
        }else if(num == A_OUT_MEMORY){
            llDialog(llGetOwner(),"No enough memory", ["Ok"], -32768);
            state published;
        }
    }
}

state published{
    state_entry(){
        //enable trigger
        llMessageLinked(LINK_THIS, C_PUBLISHED, "", "");
    }
}
Voilà je m'attaque à la 1.8
Bon voici donc la 1.8, qui permet donc de jouer les animations en inverse ou en symétrie (aller et retour), bouclées ou non. Seul l'engine a évolué.
Code:
// Script Name: OPRIAN engine 2 : OPen_PRIm_ANimator engine only change
// Version 1.8 : reverse and symmetry playback mode implemented
devenu obsoléte
2 triggers pour tester
Code:
// Script Name: OPRIAN click trigger reverse: OPen_PRIm_ANimator trigger on click after publishing - reverse mode
// Version V1.8

// trigger script for use in set scripts OPRIAN - Author : Elenia Boucher

//must be in objet BEFORE publishing

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////


integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_REVERSE_ON = 6;

integer C_PUBLISHED = 60;
integer C_UNPUBLISHED = 61;

stop(){
    llMessageLinked(LINK_THIS, C_LOOP_OFF, "", "");
    llMessageLinked(LINK_THIS, C_STOP_ANIM, "", "");//comment to stop at the end of loop
}

default{
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PUBLISHED) state OFF;
    }
}

state OFF{
    touch_start(integer total_number){
        llMessageLinked(LINK_THIS, C_REVERSE_ON, "", "");
        llMessageLinked(LINK_THIS, C_LOOP_ON, "", "");//comment to one shot animation
        state ON;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED) state default;
    }
    
}

state ON{
    state_entry(){
        llMessageLinked(LINK_THIS, C_PLAY_ANIM, "", "");
    }
    
    touch_start(integer total_number){
        stop();
        state OFF;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED){
            stop();
            state default;
        }
    }
    
}
Code:
// Script Name: OPRIAN click trigger : OPen_PRIm_ANimator trigger on click after publishing
// Playing in symmetry mode
// Version V1.8

// trigger script for use in set scripts OPRIAN author Elenia Boucher

//must be in objet BEFORE publishing

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////


integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_SYMMETRY_ON = 8;

integer C_PUBLISHED = 60;
integer C_UNPUBLISHED = 61;

stop(){
    llMessageLinked(LINK_THIS, C_LOOP_OFF, "", "");
    llMessageLinked(LINK_THIS, C_STOP_ANIM, "", "");//comment to stop at the end of loop
}

default{
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PUBLISHED) state OFF;
    }
}

state OFF{
    touch_start(integer total_number){
        llMessageLinked(LINK_THIS, C_SYMMETRY_ON, "", "");
        llMessageLinked(LINK_THIS, C_LOOP_ON, "", "");//comment to one shot animation
        state ON;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED) state default;
    }
    
}

state ON{
    state_entry(){
        llMessageLinked(LINK_THIS, C_PLAY_ANIM, "", "");
    }
    
    touch_start(integer total_number){
        stop();
        state OFF;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED){
            stop();
            state default;
        }
    }
    
}
J'attire votre attention que vous pouvez avoir des résultats étranges si vous ne bougez pas toujours les mêmes prims à chaque snapshot: il peut être nécessaire de bouger légérement un prim pour que sa position soit enregistré et avoir ainsi un résultat cohérent quand c'est joué à l'inverse.
Oui je me dit qu'il faudra tôt ou tard faire un mode d'emploi.

Si je ne l'ai pas fait plus tot c'est que ce n'est pas fini, mais aussi que c'est très simple mais sur il faut connaitre ce genre d'outils: les principes des prims animator et autres puppeteers sont tous les mêmes: on bouge les prims d'un build lié avec les outils de build, quand on est satisfait de la position on l'enregistre (une position s'appelle un snapshot). Quand on a plusieurs snapshots, on peut publier le tout et avec un déclencheur (trigger) on peut rejouer sur commande (chat ou click ou radar suivant les triggers) l'enchainement des snapshots.

Alors le OPRIAM, il suffit de mettre les 3 scripts ( engine, setup et un trigger, et dans cet ordre) dans un build. Le menu apparait: record = enregistrer (on ne peut actuellement enregistrer qu'à la fin), play = jouer l'animation, next ou back pour respectivement avancer ou reculer dans l'animation d'un seul snapshot, dump pour vider la mémoire dans le chat pour faire la notecard de sauvegarde, load pour recharger la notecard, publish pour publier (le script setup est effacé, plus de menu, et c'est le trigger qui permet de jouer l'animation). Il y a une aide intégrée au menu (en anglais).

Limitations: pas trop de prims: 20 c'est raisonnable, 30 ça doit encore aller en ne faisant pas trop de snapshot ou en ne bougeant pas trop de prims, 40 j'ai peur que ça lag beaucoup et si on bouge trop de prims on ne pourra pas enregistrer grand chose.
Si ça peut t'aider dans ta tache et te donner des éléments de comparaison en terme de performance mémoire, j'ai moi même aussi réalisé un "prim animator" qui tient en un seul script et mes calculs me donnent quelque chose comme ça ;

NB_FRAMES < (MEMORY / (MEM_VECTOR * NB_COUCHES * NB_OBJETS))


MEMORY = mémoire disponible après l'écriture complète de ton script à vide de frame. (moi c'était pas loin de 56112 je crois).
MEM_VECTOR = mémoire occupée par un vecteur dans une liste (20).
NB_COUCHES = nombre d'infos à stocker (ici 3 car position rotation et taille).
NB_OBJETS = nombre de prims dans ton linkset.
NB_FRAMES = nombre potentiel de frames disponible.

Cette formule te donne la limite en terme de mémoire pour les frames sachant qu'en suite il faut diviser ce nombre par le nombre d'animations qu'on souhaite réaliser. Cela ne donne pas des valeurs super super car il s'agit d'une formule pour un script non optimisé et puis tout tient dans un unique script. J'ai pu ensuite apporter des optimisations qui augmentaient d'un bon pourcentage les capacités mémoire. En terme d'exécution par contre, aucun délai, que du fast et aucun message envoyé, il a donc fallu stocker des temps de pause également ou bien choisir pour l'utilisateur.
La solution pour l'espace mémoire est de multiplier "un petit peu" les scripts. Il faut trouver le bon équilibre. Je n'ai pas trop réagi à ton script parce que je suis en plein dans le code d'un truc du même type avec Sandrine, dans lequel on élargit les modifications des pos, rot et scale aux textures, sculpt map, couleurs... Dans une approche modulaire ça passe plutôt bien, surtout que les fonctions "fast" trouvent rapidement leur limite en cas d'un grand nombre de modifications à apporter.

Il faut aussi se focaliser sur l'aspect "trigger" qui mérite au moins autant d'attention étant donné que les cas d'utilisation sont multiples et que les utilisateurs de ces systèmes sont rarement des scripteurs, il est dommage de s'arrêter en chemin... Je crée une collection de trigger pour les cas les plus généralistes avec suffisamment de paramètres pour couvrir l'essentiel des cas. Elle sera évidemment façonnée pour le produit concerné mais sera sans doute open source...
Citation :
Publié par Ahuri Serenity
Si ça peut t'aider dans ta tache et te donner des éléments de comparaison en terme de performance mémoire, j'ai moi même aussi réalisé un "prim animator" qui tient en un seul script et mes calculs me donnent quelque chose comme ça ;
....
Merci pour ta participation. Mais pour le controle de la mémoire, je le fait à posteriori: chaque fois que l'on enregistre un prim dans le snapshot en cours, on vérifie si on tombe pas en dessous de 300 octect. Si oui, on avorte l'enregistrement en cours et j'efface les prims déjà enregistré.
Avec l'engine 2, c'est impossible de prévoir vu que l'on enregistre que les changements.

Citation :
Publié par bestmomo
La solution pour l'espace mémoire est de multiplier "un petit peu" les scripts. Il faut trouver le bon équilibre. Je n'ai pas trop réagi à ton script parce que je suis en plein dans le code d'un truc du même type avec Sandrine, dans lequel on élargit les modifications des pos, rot et scale aux textures, sculpt map, couleurs... Dans une approche modulaire ça passe plutôt bien, surtout que les fonctions "fast" trouvent rapidement leur limite en cas d'un grand nombre de modifications à apporter.

Il faut aussi se focaliser sur l'aspect "trigger" qui mérite au moins autant d'attention étant donné que les cas d'utilisation sont multiples et que les utilisateurs de ces systèmes sont rarement des scripteurs, il est dommage de s'arrêter en chemin... Je crée une collection de trigger pour les cas les plus généralistes avec suffisamment de paramètres pour couvrir l'essentiel des cas. Elle sera évidemment façonnée pour le produit concerné mais sera sans doute open source...
Je suis entièrement d'accord avec toi. Mon produit évolue de plus en plus vers un produit pour scripteur, avec à termes 3 moteurs dont 1 moteur "classique" à script distribué dans chaque prim: il faudra donc choisir son moteur en fonction de ses objectifs et des contraintes, en étant conscient de la différence, ce qui ne sera pas à la portée de personnes ne connaissant rien en script.

Ainsi là je vais publier ce soir la 1.82, qui permet de jouer l'animation par tranche. Sur que le trigger multi-animation n'est pas simple à écrire et à paramétrer.

Mais j'ai décider de suspendre là: je me suis rendu compte hier que Todd Borst, créateur du script originel d'où je suis partie, est aussi le créateur et le vendeur du Puppeteer. Lui faire de l'ombre avec un opensource basé sur un de ses script, ce n'est pas le remercier d'avoir mis à la disposition de tous son script. Hier, je lui ai envoyé une notecard avec mon projet et j'attends de voir sa réaction pour continuer ou pas.

Alors j'ai un autre opensource, le prim animator lite, qui lui est plus modulaire: un script pour les rotations, un scripts pour les dimensions, un script pour les positions. Cela rejoint ce que tu disais Best. Je vais peut être repartir de là si Todd Borst ne me donne pas son aval.
J'ai eu la réponse de Todd Borst
Citation :
Awesome work! You did what I was too lazy to do haha. By all means, please release it with my full blessings. I've always believed that the more solutions available for the end user the better.
Citation :
Superbe travail! Vous avez fait ce que j'étais trop paresseux pour faire haha. Par tous les moyens, s'il vous plaît diffusez avec mes bénédictions. J'ai toujours cru que plus il y a de solutions disponibles pour l'utilisateur final mieux c'est.
Donc je continue
Sur la chemin de la 1.90
Voici la 1.82; deux nouveautés:

- une limite en prims animables: cela permet d'utiliser l'engine 2 dans un linkset de 100 ou plus prims en n'en animant que 20 par exemple.
Alors cela se règle avec la variable iLimitPrims en tête du script engine; laisser 0 si vous voulez animer tout le linkset.
La précaution à prendre est que les prims animables doivent être sélectionnées en dernier quand on lie.
J'ai eu le cas sur un bateau où il n'y avait à animer que la passerelle de débarquement.

- la possibilité de jouer les snapshots par tranche : par exemple de la frame 1 à la 10, puis séparément de la 11 à la 20. Cela ouvre la porte à plusieurs animations distinctes. Bien sur, on ne peut pas les jouer ensemble.
Bon cela fait des triggers compliqués, vu qu'il faut verrouiller la possibilité de jouer une animation quand on en joue une autre.
Pour reprendre l'exemple du bateau, vous pouvez faire une animation pour la passerelle et une autre pour l'ancre.
Code:
// Script Name: OPRIAN engine 2 : OPen_PRIm_ANimator engine only changes
// Version 1.82; limit prims and play slice
// Original Author: Todd Borst
// Downloaded from : http://www.free-lsl-scripts.com/freescripts.plx?ID=1580

// engine 2 for use in set scripts OPRIAN: Author : Elenia Boucher

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

string sTypEngine = "2";
// 30 is a reasonnable limit for an animable linkset; 
// set to 0 if you want anime all the linkset
// otherwise, only the lastest selected before link will be animable
integer iLimitPrims = 0;//number of animable prims in the linkset - let 0 for all the linkset
integer iPrimCount;
integer iTotalPrims;
list lPos;
list lRot;
list lScale;
list lNumPrims;
list lNbPrimsByFrame;
list lDelay;
integer iNbPrimsInFrame;
integer iCurrentSnapshot   = 1;
integer iRecordedSnapshots = 0;
integer iEndFrame;
integer iStartFrame;
integer iIndexChange;
integer iFrame;
float fDelay = 1.0;
integer iReverse;
 
vector vRootScale   = ZERO_VECTOR;
vector vScaleChange = <1,1,1>;
 
// For tracking memory usage.  The amount of snapshots you can record is based
// on the number of prims and available memory.  Less prims = more snapshots
integer iMaxMemory  = 0;
integer iFreeMemory = 0;
 
integer iPlayAnimationStyle = 0;
// The values for iPlayAnimationStyle means
// 0 = no animation playing
// 1 = play animation once
// 2 = play animation looping
// 4 = play animation reverse mode
// 8 = play animation symmetry mode
// binary values can be combined

//dialog constants - naming: R request, A answer, C command - direction setup/triggers -> engine

integer C_PLAY_FRAME = 1;
integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_REVERSE_ON = 6;
integer C_REVERSE_OFF = 7;
integer C_SYMMETRY_ON = 8;
integer C_SYMMETRY_OFF= 9;
integer C_PLAY_SLICE = 10;

integer R_MAX_MEMORY = 20;
integer A_MAX_MEMORY = 21;
integer R_FREE_MEMORY= 22;
integer A_FREE_MEMORY= 23;
integer R_TOTAL_FRAMES = 24;
integer A_TOTAL_FRAMES = 25;
integer R_ACTUAL_FRAME = 26;
integer A_ACTUAL_FRAME = 27;
integer A_PLAYING_FRAME = 28;
integer A_END_ANIMATION = 29;
integer A_START_ANIMATION = 30;
integer R_SCALE = 31;
integer A_SCALE = 32;
integer R_DELAY_FRAME = 33;
integer A_DELAY_FRAME = 34;
integer R_CHG_DELAY_FRAME = 35;
integer R_PRIM_COUNT = 36;
integer A_PRIM_COUNT = 37;

integer C_RECORD_FRAME = 40;
integer A_RECORD_FRAME = 41;
integer C_RECORD_PRIM = 42;
integer A_RECORD_PRIM = 43;
integer C_END_FRAME = 44;

integer C_DUMP_FRAME = 45;
integer A_DUMP_FRAME = 46;
integer R_TYP_ENGINE = 47;
integer A_TYP_ENGINE = 48;
integer A_DUMP_PRIM = 49;
integer C_RESET = 50;
integer A_RESET = 51;
integer A_OUT_MEMORY = 52;


showSnapshot(integer snapNumber)
{
    if(snapNumber > 0 && snapNumber <= iRecordedSnapshots )
    {
        llMessageLinked(LINK_THIS, A_PLAYING_FRAME, (string)snapNumber, "");
        vector   pos;
        rotation rot;
        vector   scale;
        float delay;
        //passe++;//mesure
 
        vector rootPos = llGetPos();
 
        // Might want to move llGetRot() into the loop for fast rotating objects.
        // Rotation during the animation playback may cause errors.
        rotation rootRot = llGetRot();
        
        integer end = iIndexChange + llList2Integer(lNbPrimsByFrame, snapNumber - 1);

        for(iIndexChange; iIndexChange < end; iIndexChange ++){ 
                    pos     = llList2Vector(lPos, iIndexChange);
                    rot     = llList2Rot(lRot, iIndexChange);
                    scale   = llList2Vector(lScale, iIndexChange);
                    
                //Adjust for scale changes
                if( vRootScale.x != 1.0 || vRootScale.y != 1.0 || vRootScale.z != 1.0 )
                {
                    pos.x *= vScaleChange.x;
                    pos.y *= vScaleChange.y;
                    pos.z *= vScaleChange.z;
                    scale.x *= vScaleChange.x;
                    scale.y *= vScaleChange.y;
                    scale.z *= vScaleChange.z;
                }
            
            llSetLinkPrimitiveParamsFast( llList2Integer(lNumPrims, iIndexChange), 
                                                   [ PRIM_POSITION, pos, 
                                                     PRIM_ROTATION, rot/rootRot, 
                                                     PRIM_SIZE, scale ] );
        }
        if(iPlayAnimationStyle > 0){
            delay = llList2Float(lDelay, snapNumber -1);
            if(delay == .0) delay = llList2Float(lDelay, iRecordedSnapshots - 1);
            llSetTimerEvent(delay);
        }
    }
}

//find the first record of a snapshot in the list
integer findFirstChange(integer snapshot){
    integer i;
    integer s;
    integer end = snapshot - 1;
    for(i=0; i < end; i++)
        s += llList2Integer(lNbPrimsByFrame, i);
    return s;
}

playAnimation(){
    iIndexChange = findFirstChange(iCurrentSnapshot);
    if(iPlayAnimationStyle == 0) iPlayAnimationStyle = 1;//one shot direct play by default
    llSetTimerEvent(0.1);
}

default
{
    state_entry()
    {
        iMaxMemory = llGetFreeMemory();
        iFreeMemory = llGetFreeMemory();
        iTotalPrims = llGetNumberOfPrims();
        if(iTotalPrims > (iLimitPrims + 1) && iLimitPrims > 1) iPrimCount = iLimitPrims +1;
        else iPrimCount = iTotalPrims;
 
        //setting initial root scale.  this allows the animation to scale if the root size is changed afterwards.
        vRootScale = llGetScale();
        llMessageLinked(LINK_THIS, A_RESET, "", "");
    }
 
    changed(integer chg)
    {
        //this is needed to detect scale changes and record the differences in order to adjust the animation accordingly.
        if (chg & CHANGED_SCALE)
        {
            if (vRootScale != ZERO_VECTOR)
            {
                vector newScale = llGetScale();
                //since change_scale is triggered even with linked prim changes,
                //this is to filter out non-root changes.
                if( ( newScale.x / vRootScale.x) != vScaleChange.x ||
                    ( newScale.y / vRootScale.y) != vScaleChange.y ||
                    ( newScale.z / vRootScale.z) != vScaleChange.z )
                {
                    vScaleChange.x = newScale.x / vRootScale.x;
                    vScaleChange.y = newScale.y / vRootScale.y;
                    vScaleChange.z = newScale.z / vRootScale.z;
                    llMessageLinked(LINK_THIS, A_SCALE, (string)vScaleChange, "");
                }
            }
        }
        // if new prims are added or removed from this object then the script resets
        // because the animations are now broken.
        else if (chg & CHANGED_LINK)
        {
            if( iTotalPrims != llGetNumberOfPrims() )
            {
                llOwnerSay("Link change detected, reseting script.");
                llResetScript();
            }
        }
    }
 

    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PLAY_FRAME){
            integer snapshot = (integer)str;
            iIndexChange = findFirstChange(snapshot); 
            iCurrentSnapshot = snapshot;
            showSnapshot(snapshot);
            llMessageLinked(LINK_THIS, A_ACTUAL_FRAME, (string)iCurrentSnapshot, "");
        }else if(num == C_PLAY_ANIM ){
            if(iRecordedSnapshots == 0) return;
            iStartFrame = 1;
            iEndFrame = iRecordedSnapshots;
            if(iPlayAnimationStyle & 4){//reverse mode
                iCurrentSnapshot--;//Start animation at the previous frame
                if(iCurrentSnapshot <= 1) iCurrentSnapshot = iRecordedSnapshots;
            }else{//direct mode
                iCurrentSnapshot++; //Start animation at the next frame
                if(iCurrentSnapshot >= iRecordedSnapshots) iCurrentSnapshot = 1;
            }
            playAnimation();
        }else if(num == C_PLAY_SLICE ){
            iStartFrame = (integer)str;
            iEndFrame = (integer)((string)id);
            if(iStartFrame >= iEndFrame || iStartFrame < 1 || iEndFrame > iRecordedSnapshots) return;
            if(iCurrentSnapshot < iStartFrame || iCurrentSnapshot > iEndFrame){
                if(iPlayAnimationStyle & 4)//reverse mode
                    iCurrentSnapshot = iEndFrame;
                else
                    iCurrentSnapshot = iStartFrame;
            }
            playAnimation();
        }else if(num == C_STOP_ANIM){
            iPlayAnimationStyle = 0;
            llSetTimerEvent(0);
        }else if(num == C_LOOP_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 2;
        }else if(num == C_LOOP_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 0xD;
        }else if(num == C_REVERSE_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 4 & 7;//Reverse excludes symmetry
        }else if(num == C_REVERSE_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 0xB;
        }else if(num == C_SYMMETRY_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 8 & 0xB;//symmetry excludes reverse
        }else if(num == C_SYMMETRY_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 7;
        }else if(num == R_MAX_MEMORY){
            llMessageLinked(LINK_THIS, A_MAX_MEMORY, (string)iMaxMemory, "");
        }else if(num == R_FREE_MEMORY){
            if(iFreeMemory > llGetFreeMemory()) iFreeMemory = llGetFreeMemory();
            else iFreeMemory--;//little cheat because the garbage collector has sometime a strange behavior
            llMessageLinked(LINK_THIS, A_FREE_MEMORY, (string)iFreeMemory, "");
        }else if(num == R_TOTAL_FRAMES){
            llMessageLinked(LINK_THIS, A_TOTAL_FRAMES, (string)iRecordedSnapshots, "");
        }else if(num == R_ACTUAL_FRAME){
            llMessageLinked(LINK_THIS, A_ACTUAL_FRAME, (string)iCurrentSnapshot, "");
        }else if(num == R_PRIM_COUNT){
            llMessageLinked(LINK_THIS, A_PRIM_COUNT, (string)iPrimCount, "");
        }else if(num == R_SCALE){
            llMessageLinked(LINK_THIS, A_SCALE, (string)vScaleChange, "");
        }else if(num == R_DELAY_FRAME){
            integer index = (integer)((string)id) - 1;
            llMessageLinked(LINK_THIS, A_DELAY_FRAME, llList2String(lDelay, index), id);
        }else if(num == R_CHG_DELAY_FRAME){
            integer index = (integer)((string)id) - 1;
            lDelay = llListReplaceList(lDelay, [(float)str], index, index);
        }else if(num == C_RECORD_FRAME){
            iFreeMemory = llGetFreeMemory();
            if(iFreeMemory < 200)
                llMessageLinked(LINK_THIS, A_OUT_MEMORY, (string)iFreeMemory, "");
            else{
                iNbPrimsInFrame = 0;
                iFrame = (integer)str;
                llMessageLinked(LINK_THIS, A_RECORD_FRAME, (string)iFrame, "");
            }
        }else if(num == C_RECORD_PRIM){//inline function more faster and economic
            
            integer index = llSubStringIndex(str, ";");
            integer numprim = (integer)llGetSubString(str, 0, index -1);
            str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
            if(index > -1){
                if(numprim == 1) {//scale root
                    vRootScale = (vector)llGetSubString(str, 0, index -1);
                    vector newScale = llGetScale();
                    if(vRootScale != newScale){
                        vScaleChange.x = newScale.x / vRootScale.x;
                        vScaleChange.y = newScale.y / vRootScale.y;
                        vScaleChange.z = newScale.z / vRootScale.z;
                    }  
                }else{//current prims
                        iNbPrimsInFrame++;
                    lNumPrims += [numprim];
                    
                        lPos += [(vector)llGetSubString(str, 0, index -1)];
                        str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
                    lRot += [(rotation)llGetSubString(str, 0, index -1)];
                    str = llDeleteSubString(str, 0, index);
                        index = llSubStringIndex(str, ";");
                        lScale += [(vector)llGetSubString(str, 0, index -1)];
                        str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
                    fDelay = (float)llGetSubString(str, 0, index -1);
                    str = llDeleteSubString(str, 0, index);
                    if(fDelay < .1) fDelay = 1.0;
                }
            }
            iFreeMemory = llGetFreeMemory();
            if(iFreeMemory < 300){
                lNumPrims = llDeleteSubList(lNumPrims, -iNbPrimsInFrame, -1);
                lPos = llDeleteSubList(lPos, -iNbPrimsInFrame, -1);
                lRot = llDeleteSubList(lRot, -iNbPrimsInFrame, -1);
                lScale = llDeleteSubList(lScale, -iNbPrimsInFrame, -1);
                llMessageLinked(LINK_THIS, A_OUT_MEMORY, (string)iFreeMemory, "");
            }
            else llMessageLinked(LINK_THIS, A_RECORD_PRIM, "", "");
        }else if(num == C_END_FRAME){
            lNbPrimsByFrame += [iNbPrimsInFrame];
            lDelay += [fDelay];    
            iRecordedSnapshots++;
            iCurrentSnapshot++;
            iFreeMemory = llGetFreeMemory();
            llMessageLinked(LINK_THIS, A_FREE_MEMORY, (string)iFreeMemory, "");
        }else if(num == C_DUMP_FRAME){
            integer frame = (integer)str;
            integer index = findFirstChange(frame);
            integer end = index + llList2Integer(lNbPrimsByFrame, frame - 1);
            if(frame == 1 && end > 0) llMessageLinked(LINK_THIS, A_DUMP_PRIM, "1;" + 
                                        (string)vRootScale + ";", (key)((string)frame)); 
            for(index ; index < end; index ++){ 
                llMessageLinked(LINK_THIS, A_DUMP_PRIM, 
                    llList2String(lNumPrims, index) + ";" +
                    llList2String(lPos, index) + ";" + llList2String(lRot, index) + ";" +
                    llList2String(lScale, index) + ";" + llList2String(lDelay, frame-1) + ";", (key)((string)frame));
            }
            llMessageLinked(LINK_THIS, A_DUMP_FRAME, str, "");
        }else if(num == R_TYP_ENGINE){
            llMessageLinked(LINK_THIS, A_TYP_ENGINE, sTypEngine, "");
        }else if(num == C_RESET){
            llResetScript();
        }
    }
 
    //Timer event is used to handle the animation playback.
    timer() {
        showSnapshot(iCurrentSnapshot);
        if(iPlayAnimationStyle & 4){//reverse playback
            //if not at the first frame, decrement the counter
            if(iCurrentSnapshot > iStartFrame){
                iCurrentSnapshot--;
                iIndexChange = iIndexChange - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot)
                            - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot-1);
            }else { 
                if( iPlayAnimationStyle & 2){//if looping?
                    if(iPlayAnimationStyle & 8){//symmetry mode if looping
                        iIndexChange = findFirstChange(iStartFrame);
                        iPlayAnimationStyle = iPlayAnimationStyle & 0xB;//take off the reverse mode next play start frame  
                    }else{// if animation is looping and no symmetry mode, set the counter at the last frame
                        iCurrentSnapshot = iEndFrame;
                        iIndexChange = findFirstChange(iEndFrame);
                    }
                }else{// if animation isn't looping, stop the animation
                    llMessageLinked(LINK_THIS, A_START_ANIMATION, "", "");
                    iIndexChange = 0;
                    llSetTimerEvent(0);
                    iPlayAnimationStyle = 0;
                }
            }
        }else{//direct playback     
            //if not at the end of the animation, increment the counter
            if(iCurrentSnapshot < iEndFrame)
                iCurrentSnapshot++;
            else{
                if(iPlayAnimationStyle & 8){//symmetry mode
                    iPlayAnimationStyle = iPlayAnimationStyle | 4; //set the reverse mode next play iRecordedSnapshots - 1
                    iCurrentSnapshot--;
                    iIndexChange = iIndexChange - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot-1) - 
                                   llList2Integer(lNbPrimsByFrame, iCurrentSnapshot) ;
                // if animation is looping and no symmetry mode , set the counter back to 1
                }else if( iPlayAnimationStyle & 2){
                    iCurrentSnapshot = iStartFrame;
                    iIndexChange = findFirstChange(iStartFrame);
                }else{// if animation isn't looping, stop the animation
                    llMessageLinked(LINK_THIS, A_END_ANIMATION, "", "");
                    llSetTimerEvent(0);
                    iIndexChange = 0;//for the next animation if published
                    iPlayAnimationStyle = 0;
                }
            }
        }
    }
}
Code:
// Script Name: OPRIAN setup : OPen_PRIm_ANimator setup 
// Version 1.82; limit prims and play slice
// Original Author: Todd Borst
// Downloaded from : http://www.free-lsl-scripts.com/freescripts.plx?ID=1580

// Setup script for use in set scripts OPRIAN - Author: Elenia Boucher


// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

string sTypEngine;

integer COMMAND_CHANNEL = 32;
 
integer iPrimCount = 0;
integer iListenerHandle = -1;
 
integer iCurrentSnapshot   = 1;
integer iRecordedSnapshots = 999;
 
integer iMaxMemory  = 0;
integer iFreeMemory = 0;
integer iPrevMemory;

list lPrevSnap;

vector vScaleChange;

integer iFirstInitDone;
integer iInit;

integer iAskDump;
integer iAskPlay;
integer iPlaySnapshot = 999;

integer iLoad;
integer iFrameLoad;
string sPrimLoad;
integer iEOF;
integer iRecordByMenu;
float fDelay;

integer iPrimCounter;

string sNotecardPRS;
key kHandlePRS; 
integer iLinePRS;

//dialog constants - naming: R request, A answer, C command - direction setup/triggers -> engine

integer C_PLAY_FRAME = 1;
integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_REVERSE_ON = 6;
integer C_REVERSE_OFF = 7;
integer C_SYMMETRY_ON = 8;
integer C_SYMMETRY_OFF= 9;

integer R_MAX_MEMORY = 20;
integer A_MAX_MEMORY = 21;
integer R_FREE_MEMORY= 22;
integer A_FREE_MEMORY= 23;
integer R_TOTAL_FRAMES = 24;
integer A_TOTAL_FRAMES = 25;
integer R_ACTUAL_FRAME = 26;
integer A_ACTUAL_FRAME = 27;
integer A_PLAYING_FRAME = 28;
integer A_END_ANIMATION = 29;
integer A_START_ANIMATION = 30;
integer R_SCALE = 31;
integer A_SCALE = 32;
integer R_DELAY_FRAME = 33;
integer A_DELAY_FRAME = 34;
integer R_CHG_DELAY_FRAME = 35;
integer R_PRIM_COUNT = 36;
integer A_PRIM_COUNT = 37;

integer C_RECORD_FRAME = 40;
integer A_RECORD_FRAME = 41;
integer C_RECORD_PRIM = 42;
integer A_RECORD_PRIM = 43;
integer C_END_FRAME = 44;

integer C_DUMP_FRAME = 45;
integer A_DUMP_FRAME = 46;
integer R_TYP_ENGINE = 47;
integer A_TYP_ENGINE = 48;
integer A_DUMP_PRIM = 49;
integer C_RESET = 50;
integer A_RESET = 51;
integer A_OUT_MEMORY = 52;

//use only by trigger
integer C_PUBLISHED = 60;
integer C_UNPUBLISHED = 61;

// This shows the edit menu
showMenuDialog()
{
    string temp = (string)((float)iFreeMemory/(float)iMaxMemory * 100.0);
    string menuText = "Available Memory: " + (string)iFreeMemory + " (" + llGetSubString(temp, 0, 4) +"%)" +
    "\nCurrent Snapshot: " + (string)iCurrentSnapshot +"\tSnapshots Recorded: " + (string)iRecordedSnapshots +
    "\n\t\tDelay : " + llDeleteSubString((string)fDelay, -4, -1)  +" secondes" +
    "\n[ Record ] - Record a snapshot of prim positions\n[ Play ] - Play back all the recorded snapshots" + 
    "\n[ Publish ] - Finish the recording process\n[ Show Next ] - Show the next snapshot" + 
    "\n[ Show Prev ] - Show the previous snapshot\n[ Dump ] - Dump memory\n[ Load ] - Load the notecard";
 
    list buttons = ["Record","Play","Publish","Show Prev","Show Next", "Dump", "Load"];
    
    llDialog(llGetOwner(), menuText, buttons, COMMAND_CHANNEL);
}

recordPrim(){
        integer change = FALSE;
        string str;
        vector rootPos = llGetPos();
        vector pos = llList2Vector(llGetLinkPrimitiveParams(iPrimCounter, [PRIM_POSITION]),0);
        //need to convert into local position
        pos.x -= rootPos.x;
        pos.z -= rootPos.z;
        pos.y -= rootPos.y;
        pos = pos / llGetRot();
        
        rotation rot = llList2Rot(llGetLinkPrimitiveParams(iPrimCounter, [PRIM_ROTATION]),0);
            //Converting into local rot
            rot = rot / llGetRot();
        
        vector scale = llList2Vector(llGetLinkPrimitiveParams(iPrimCounter, [PRIM_SIZE]),0);
        
       
        change = (pos != llList2Vector(lPrevSnap, 0)) || (rot != llList2Rot(lPrevSnap, 1)) ||
                 (scale != llList2Vector(lPrevSnap, 2)) || (iRecordedSnapshots == 0);

        if(change) iRecordByMenu = 2;
        
        if(iRecordedSnapshots > 0) lPrevSnap = llDeleteSubList(lPrevSnap, 0, 2);
        lPrevSnap += [pos, rot, scale];
        
        if( vScaleChange.x != 1.0 || vScaleChange.y != 1.0 || vScaleChange.z != 1.0 )
                {
                    pos.x /= vScaleChange.x;
                    pos.y /= vScaleChange.y;
                    pos.z /= vScaleChange.z;
                    scale.x /= vScaleChange.x;
                    scale.y /= vScaleChange.y;
                    scale.z /= vScaleChange.z;
                }
              
        if(change || sTypEngine == "1"){
            str += (string)iPrimCounter + ";" + (string)pos + ";" + (string)rot + ";" + 
                    (string)scale + ";" + (string)fDelay + ";" ;
        }
        
        llMessageLinked(LINK_THIS, C_RECORD_PRIM, str, (key)((string)iCurrentSnapshot));
}

errorLine(string note){
    llDialog(llGetOwner(), "error at line " + (string)iLinePRS + "\nin notecard\n" + note + "\n loading stop here", 
            ["Ok"], - 32768);
    iLoad = FALSE;
    llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
    llOwnerSay("End reading notecard on error\nTotal number of snapshots loaded: " + (string)iRecordedSnapshots);
}

recordPrevSnap(){//record list lPrevSnap after notecard or analys
    lPrevSnap = [];
    vector pos;
    rotation rot;
    vector scale;
    integer i;
    vector rootPos = llGetPos();
    
    for(i = 2; i <= iPrimCount; i++){
        vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
        //need to convert into local position
        pos.x -= rootPos.x;
        pos.z -= rootPos.z;
        pos.y -= rootPos.y;
        pos = pos / llGetRot();
        
        rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
            //Converting into local rot
            rot = rot / llGetRot();
        
        vector scale = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
        lPrevSnap += [pos, rot, scale];
    }
}

reset(){
    lPrevSnap = [];
    iCurrentSnapshot   = 1;
    iRecordedSnapshots = 999;
    llMessageLinked(LINK_THIS, R_MAX_MEMORY, "", "");
    llMessageLinked(LINK_THIS, R_FREE_MEMORY, "", "");
    llMessageLinked(LINK_THIS, R_PRIM_COUNT, "", "");
    llMessageLinked(LINK_THIS, R_TYP_ENGINE, "", "");
    llMessageLinked(LINK_THIS, R_TOTAL_FRAMES, "", "");
    llMessageLinked(LINK_THIS, R_SCALE, "", "");
}

default{
    state_entry(){
        if(!iFirstInitDone) state wait;
        iListenerHandle = llListen(COMMAND_CHANNEL,"", llGetOwner(), "");
        llMessageLinked(LINK_THIS, R_SCALE, "", "");
        if(!iLoad){
            iInit = TRUE;
            reset();
            llMessageLinked(LINK_THIS, C_UNPUBLISHED, "", "");
        }else{//return in this state after loading
            iLoad = FALSE;
            showMenuDialog();
        }
    }
 
 
    link_message(integer sender_num, integer num, string str, key id){
        iPrevMemory = iFreeMemory;
        if(num == A_MAX_MEMORY){
            iMaxMemory = (integer)str;
        }else if(num == A_FREE_MEMORY){
            iFreeMemory = (integer)str;
                //memory answer after record snapshot
            if(iFreeMemory < iPrevMemory){
                iRecordedSnapshots++;
                iCurrentSnapshot++;
                llOwnerSay("Total number of snapshots recorded: " + (string)iRecordedSnapshots);
                 showMenuDialog();
            }
        }else if(num == A_TOTAL_FRAMES){
            iRecordedSnapshots = (integer)str;
            llMessageLinked(LINK_THIS, R_DELAY_FRAME, "", (key)str);
            if(iAskDump){
                llMessageLinked(LINK_THIS, C_DUMP_FRAME, "1", "");
            }
        }else if(num == A_ACTUAL_FRAME){
            iCurrentSnapshot = (integer)str;
            llMessageLinked(LINK_THIS, R_DELAY_FRAME, "", (key)str);
        }else if(num == A_PRIM_COUNT){
            iPrimCount = (integer)str;
            llOwnerSay((string)(iPrimCount - 1) + " animable prims on " + (string)llGetNumberOfPrims() + " total prims");
        }else if(num == A_SCALE){
            vScaleChange = (vector)str; 
        }else if(num == A_DELAY_FRAME){
            fDelay = (float)str;
            if(fDelay == .0) fDelay = 1.0;
            if(!iInit) showMenuDialog();
        }else if(num == A_RESET){
            llOwnerSay("Reset scripts - Engine is empty");
            reset();
        }else if(num == A_RECORD_FRAME){
            //record by commande first prim
                iPrimCounter = 2;
                recordPrim();
        }else if(num == A_RECORD_PRIM){
            iPrimCounter++;
            if(iPrimCounter > iPrimCount){
                if(iRecordByMenu == 2) //change detected in frame
                    llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
                else showMenuDialog();
                iRecordByMenu = 0; 
            }else recordPrim();
        }else if(num == A_OUT_MEMORY){
            iRecordByMenu = 0;
            iFreeMemory = (integer)str;
            llDialog(llGetOwner(),"No enough memory", ["Ok"], -32768);
        }else if(num == A_DUMP_PRIM){
                string name = llGetObjectName();
                llSetObjectName("");
                llSay(0,(string)id + ";" + str);
                llSetObjectName(name);
        }else if(num == A_DUMP_FRAME){
            iAskDump++;
            if(iAskDump > iRecordedSnapshots){
                llOwnerSay("Dump done !!");
                iAskDump = 0;
                return;
            }
            llMessageLinked(LINK_THIS, C_DUMP_FRAME, (string)iAskDump, "");
        }else if(num == A_TYP_ENGINE){
            sTypEngine = str;
        }else if(num == A_END_ANIMATION){
            iCurrentSnapshot = iRecordedSnapshots;
            recordPrevSnap();
            showMenuDialog();
        }
        //if we have all the init answers to work
        if(iMaxMemory && iFreeMemory && iRecordedSnapshots < 999 && sTypEngine != "" &&
            vScaleChange != ZERO_VECTOR && fDelay > .0 && iPrimCount && iInit && !iAskPlay){
            if(iRecordedSnapshots > 0){
                llOwnerSay((string)iRecordedSnapshots + " registred snapshot(s) found - Playing for analys ...");
                iAskPlay = iRecordedSnapshots;
                llMessageLinked(LINK_THIS, C_PLAY_ANIM, "", "");
            }else{
                iCurrentSnapshot = 1;
                iInit = FALSE;
                showMenuDialog();
            }
        }
        //end init after play animation
        if(iCurrentSnapshot == iAskPlay ){
            iInit = FALSE;
            iAskPlay = 0;
        }
    }
    
    touch_start(integer tot){
        if( !iInit) showMenuDialog();
    }
 
    //This event handler takes care of all the editing commands.
    //Available commands are: record, play, publish, show next, show prev, show #
    listen(integer channel, string name, key id, string message)
    {
        list parsedMessage = llParseString2List(message, [" "], []);
        string firstWord = llToLower(llList2String(parsedMessage,0));
        string secondWord = llToLower(llList2String(parsedMessage,1));
 
        //display a snapshot
        if("show" == firstWord && iRecordedSnapshots > 0){
            if(secondWord == "next"){
                iPlaySnapshot = iCurrentSnapshot + 1;
                if(iPlaySnapshot > iRecordedSnapshots)
                    iPlaySnapshot = 1;
            }else if(secondWord == "prev"){
                iPlaySnapshot = iCurrentSnapshot - 1;
                if(iPlaySnapshot == iRecordedSnapshots) //prev at the end
                    iPlaySnapshot = iRecordedSnapshots - 1;
                if(iPlaySnapshot < 1 ) //prev at the first or only one frame recorded
                    iPlaySnapshot = iRecordedSnapshots;
            }else{
                // when the conversion fails, snapshotNumber = 0
                iPlaySnapshot = (integer)secondWord;
                if(iPlaySnapshot > 0 && iPlaySnapshot <= iRecordedSnapshots ){
                    llOwnerSay("Showing snapshot: "+(string)iPlaySnapshot);
                }else{
                    llOwnerSay("Invalid snapshot number given: " + (string) iPlaySnapshot +
                                "\nA valid snapshot number is between 1 and " + (string)iRecordedSnapshots +
                                "\nAssuming " + (string)iRecordedSnapshots);
                    iPlaySnapshot = iRecordedSnapshots;
                }
            }
            llMessageLinked(LINK_THIS, C_PLAY_FRAME, (string)iPlaySnapshot, "");
        }
        //record a snapshot
        else if(firstWord == "record"){
            //record only at the end
            if(iCurrentSnapshot <= iRecordedSnapshots) iCurrentSnapshot = iRecordedSnapshots + 1;
            iRecordByMenu = 1;
            llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iCurrentSnapshot, "");
        }
        //play the animation from beginning to end once without looping.
        else if (firstWord == "play"){
            //float delay = (float)secondWord;
            //iCurrentSnapshot = 1;
            llMessageLinked(LINK_THIS, C_PLAY_ANIM, "", "");
        }
        //publish disables the recording features and enables the on-touch trigger
        else if("publish" == firstWord){
            state published;
        }else if (firstWord == "dump"){
            iAskDump = 1;
            llMessageLinked(LINK_THIS, R_TOTAL_FRAMES, "", "");
        }else if (firstWord == "load"){
            iLoad = TRUE;
            llDialog(llGetOwner(), "Load notecard?\nThis will erase the current animation\nConfirm?", 
                    ["Yes", "No"], COMMAND_CHANNEL);
        }else if (firstWord == "yes" && iLoad){
            state loading;
        }else if (firstWord == "no"){
            iLoad = FALSE;
        }else if (firstWord == "delay"){
            float delay;
            integer frame;
            if(secondWord == "snapshot"){
                frame = (integer)llList2String(parsedMessage,2);
                if(frame < 1 || frame > iRecordedSnapshots){
                    llOwnerSay("Invalid snapshot number given: " + (string) frame +
                                "\nA valid snapshot number is between 1 and " + (string)iRecordedSnapshots);
                    return;
                }
                delay = (float)llList2String(parsedMessage,3);
            }else delay = (float)secondWord;
            if(delay < 0.1) {
                llOwnerSay("Invalid delay number given: " + (string)delay +
                            "\nA valid delay number is greater than or equal 0.1\n...assuming 0.1 secondes");
                delay = 0.1;    
            }
            if(secondWord == "snapshot"){
                llMessageLinked(LINK_THIS, R_CHG_DELAY_FRAME, (string)delay, (key)((string)frame));
                llOwnerSay("Change delay snapshot " + (string)frame + " for " +
                           llDeleteSubString((string)delay, -4, -1) + " secondes");
                if(frame == iRecordedSnapshots) fDelay = delay;
            }else{
                llOwnerSay("delay for the next taken snapshot : " + 
                            llDeleteSubString((string)delay, -4, -1)+ " secondes");
                fDelay = delay;
            }
        }
    }
}

state loading{
    state_entry(){
        //search notecard PRS
        sNotecardPRS = "";
        integer nb = llGetInventoryNumber(INVENTORY_NOTECARD);
        integer i;
        for(i=0; i<nb; i++){
            string name = llGetInventoryName(INVENTORY_NOTECARD, i);
            if(~llSubStringIndex(name, "OPRIAN PRS")) sNotecardPRS = name;
        }
        if(sNotecardPRS == ""){//no found
            llDialog(llGetOwner(), "Not notecard 'OPRIAN PRS'", ["Ok"], -32768);
            state default;
        }
        //erase current animation
        llMessageLinked(LINK_THIS, C_RESET, "", "");
        iRecordedSnapshots = 0;
        iCurrentSnapshot = 0;
        lPrevSnap = [];
        kHandlePRS = llGetNotecardLine(sNotecardPRS, iLinePRS = 0);// request first line
    }
    
    dataserver(key query_id, string data){
        if (query_id == kHandlePRS) {
            if (data != EOF){
                if(data == ""){//empty line
                    kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);
                    return;
                }
                integer index = llSubStringIndex(data, ": ");
                if(index == -1){ errorLine(sNotecardPRS); return;}
                data = llDeleteSubString(data, 0, index + 1);//delete time stamp and ': '
                iFrameLoad = (integer)llGetSubString(data, 0, index -1);
                if(iFrameLoad == 0 || index == -1) { errorLine(sNotecardPRS); return;}
                index = llSubStringIndex(data, ";");
                sPrimLoad = llDeleteSubString(data, 0, index);//delete the frame number
                if(iFrameLoad == iCurrentSnapshot + 1){//new frame
                    if(iFrameLoad > 1){//end previous frame
                    llOwnerSay("Snapshot " + (string)(iFrameLoad - 1) + " loaded");
                        llMessageLinked(LINK_THIS, C_END_FRAME, "", "");//triggers an answer free memory
                    }else{ //first frame
                        iCurrentSnapshot = 1;
                        llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");
                        llOwnerSay("begin reading notecard");
                    }
                }else{
                    llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
                }
            }else{//end notecard
                iEOF = TRUE;
                llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
                llOwnerSay("Snapshot " + (string)(iRecordedSnapshots + 1) + " loaded");
                llOwnerSay("End reading notecard");
            }
        }
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == A_FREE_MEMORY){//memory answer after C_END_FRAME
            iFreeMemory = (integer)str;
                iRecordedSnapshots++;
                if(iEOF) llMessageLinked(LINK_THIS, R_DELAY_FRAME, "", (key)((string)iCurrentSnapshot));
                llMessageLinked(LINK_THIS, C_PLAY_FRAME, (string)iCurrentSnapshot, "");
                iCurrentSnapshot++;
        }else if(num == A_ACTUAL_FRAME){//answer after playing frame
                recordPrevSnap();
                if(iEOF){
                    iEOF = FALSE;
                    state default;
                }else llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");//next frame
        }else if(num == A_RECORD_FRAME){//answer after C_RECORD_FRAME
            // new frame first prim
                iPrimCounter = (integer)llGetSubString(sPrimLoad, 0, llSubStringIndex(sPrimLoad, ";") -1);
                llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
        }else if(num == A_RECORD_PRIM){
            kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);//read the next notecard line
        }else if(num == A_DELAY_FRAME){
            fDelay = (float)str;
        }else if(num == A_OUT_MEMORY){
            iFreeMemory = (integer)str;
            iEOF = FALSE;
            llDialog(llGetOwner(),"No enough memory", ["Ok"], -32768);
            state default;
        }
    }
}

state wait{//to let the engine start on reset all scripts
    state_entry(){
        iFirstInitDone = TRUE;
        llSleep(2.0);
        state default;
    }
}
state published{
    state_entry(){
        llOwnerSay("Recording disabled. Publish complete.\nClick me to toggle animation on/off.");
        //enable trigger
        llMessageLinked(LINK_THIS, C_PUBLISHED, "", "");
        //llRemoveInventory(llGetScriptName());
    }
}
un trigger tordu comme exemple; joue 1 à 10 en boucle directe sur click, et 11 à 20 sur commande chat 'go2' en symmetry one shot.
Code:
// Script Name: OPRIAN click+chat trigger slice: OPen_PRIm_ANimator trigger on click and chat after publishing
// Playing two slice : once looped in direct mode on click, the other one shot in symmetry mode on chat command 'go2'
// Version V1.8

// trigger script for use in set scripts OPRIAN author Elenia Boucher

//must be in objet BEFORE publishing

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////


integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_REVERSE_ON = 6;
integer C_REVERSE_OFF = 7;
integer C_SYMMETRY_ON = 8;
integer C_SYMMETRY_OFF= 9;
integer C_PLAY_SLICE = 10;

integer A_START_ANIMATION = 30;

integer C_PUBLISHED = 60;
integer C_UNPUBLISHED = 61;

stop(){
    llMessageLinked(LINK_THIS, C_LOOP_OFF, "", "");
    llMessageLinked(LINK_THIS, C_STOP_ANIM, "", "");//comment to stop at the end of loop
}

default{
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PUBLISHED) state wait;
    }
}

state wait{
    state_entry(){
        llListen(0, "", "", "go2");
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED) state default;
    }
    
     touch_start(integer total_number){
        state ON_click;
    }
    
    listen(integer chan, string name, key id, string mess){
        state GO2;
    }
}

state ON_click{
    state_entry(){
        llMessageLinked(LINK_THIS, C_LOOP_ON, "", "");//comment to one shot animation
        llMessageLinked(LINK_THIS, C_PLAY_SLICE, "1", (key)"10");//play animation to snapshot 1 to 10
    }
    
    touch_start(integer total_number){
        stop();
        state wait;
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED){
            stop();
            state default;
        }
    }
}
    
state GO2{
    state_entry(){
        llMessageLinked(LINK_THIS, C_SYMMETRY_ON, "", "");//one shot animation in symmetry mode
        llMessageLinked(LINK_THIS, C_PLAY_SLICE, "11", (key)"20");//play animation to snapshot 11 to 20
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_UNPUBLISHED){
             stop();
             state default;
        }else if(num == A_START_ANIMATION){
            llMessageLinked(LINK_THIS, C_SYMMETRY_OFF, "", "");//dont forget
            state wait;
        }
    }
}
Suite à une mise en oeuvre je me suis rendu compte que je n'avais pas fait suivre le loader, qui permet de recharger la notecard; il marchait plus . Voilà c'est réparé pour les scripts version 1.8.

OPRIAN loader 1.8
Code:
// Script Name: OPRIAN loader OPen_PRIm_ANimator V1.8

// Loader script for use in set scripts OPRIAN: Elenia Boucher
// Don't use with setup script : remove from inventory prim
// use only after publishing to reload notecard in case reset

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

string sTypEngine;
 
integer iPrimCount = 0;
 
integer iCurrentSnapshot   = 1;
integer iRecordedSnapshots = 999;
 
integer iFreeMemory = 0;

list lPrevSnap;

vector vScaleChange;

integer iLoad;
integer iFrameLoad;
string sPrimLoad;
integer iEOF;

integer iPrimCounter;

string sNotecardPRS;
key kHandlePRS; 
integer iLinePRS;

//dialog constants - naming: R request, A answer, C command - direction setup/triggers -> engine

integer C_PLAY_FRAME = 1;

integer R_FREE_MEMORY= 22;
integer A_FREE_MEMORY= 23;

integer R_ACTUAL_FRAME = 26;
integer A_ACTUAL_FRAME = 27;
integer A_PLAYING_FRAME = 28;

integer R_SCALE = 31;
integer A_SCALE = 32;

integer C_RECORD_FRAME = 40;
integer A_RECORD_FRAME = 41;
integer C_RECORD_PRIM = 42;
integer A_RECORD_PRIM = 43;
integer C_END_FRAME = 44;

integer R_TYP_ENGINE = 47;
integer A_TYP_ENGINE = 48;

integer C_RESET = 50;
integer A_RESET = 51;
integer A_OUT_MEMORY = 52;

//use only for trigger
integer C_PUBLISHED = 60;

errorLine(string note){
    llDialog(llGetOwner(), "error at line " + (string)iLinePRS + "\nin notecard\n" + note + "\n loading stop here", 
            ["Ok"], - 32768);
    iLoad = FALSE;
    llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
    llOwnerSay("End reading notecard on error\nTotal number of snapshots loaded: " + (string)iRecordedSnapshots);
}

recordPrevSnap(){//record list lPrevSnap after each frame (for engine 1)
    lPrevSnap = [];
    vector pos;
    rotation rot;
    vector scale;
    integer i;
    vector rootPos = llGetPos();
    
    for(i = 2; i <= iPrimCount; i++){
        vector pos = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_POSITION]),0);
        //need to convert into local position
        pos.x -= rootPos.x;
        pos.z -= rootPos.z;
        pos.y -= rootPos.y;
        pos = pos / llGetRot();
        
        rotation rot = llList2Rot(llGetLinkPrimitiveParams(i, [PRIM_ROTATION]),0);
            //Converting into local rot
            rot = rot / llGetRot();
        
        vector scale = llList2Vector(llGetLinkPrimitiveParams(i, [PRIM_SIZE]),0);
        lPrevSnap += [pos, rot, scale];
    }
}

default{
    state_entry(){
        
        //search notecard PRS
        sNotecardPRS = "";
        integer nb = llGetInventoryNumber(INVENTORY_NOTECARD);
        integer i;
        for(i=0; i<nb; i++){
            string name = llGetInventoryName(INVENTORY_NOTECARD, i);
            if(~llSubStringIndex(name, "OPRIAN PRS")) sNotecardPRS = name;
        }
        if(sNotecardPRS == ""){//no found
            llDialog(llGetOwner(), "Not notecard 'OPRIAN PRS'", ["Ok"], -32768);
            state published;
        }
        llSleep(1.0);//to let the engine start
        //erase current animation
        llMessageLinked(LINK_THIS, C_RESET, "", "");
        llSleep(1.0);//to let the engine start
        llMessageLinked(LINK_THIS, R_TYP_ENGINE, "", "");
        iRecordedSnapshots = 0;
        iCurrentSnapshot = 0;
        lPrevSnap = [];
    }
    
    dataserver(key query_id, string data){
        if (query_id == kHandlePRS) {
            if (data != EOF){
                if(data == ""){//empty line
                    kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);
                    return;
                }
                integer index = llSubStringIndex(data, ": ");
                if(index == -1){ errorLine(sNotecardPRS); return;}
                data = llDeleteSubString(data, 0, index + 1);//delete time stamp and ': '
                iFrameLoad = (integer)llGetSubString(data, 0, index -1);
                if(iFrameLoad == 0 || index == -1) { errorLine(sNotecardPRS); return;}
                index = llSubStringIndex(data, ";");
                sPrimLoad = llDeleteSubString(data, 0, index);//delete the frame number
                if(iFrameLoad == iCurrentSnapshot + 1){//new frame
                    if(iFrameLoad > 1){//end previous frame
                    llOwnerSay("Snapshot " + (string)(iFrameLoad - 1) + " loaded");
                        llMessageLinked(LINK_THIS, C_END_FRAME, "", "");//triggers an answer free memory
                    }else{ //first frame
                        iCurrentSnapshot = 1;
                        llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");
                        llOwnerSay("begin reading notecard");
                    }
                }else{
                    llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
                }
            }else{//end notecard
                iEOF = TRUE;
                llMessageLinked(LINK_THIS, C_END_FRAME, "", "");
                llOwnerSay("Snapshot " + (string)(iRecordedSnapshots + 1) + " loaded");
                llOwnerSay("End reading notecard");
            }
        }
    }
    
    link_message(integer sender_num, integer num, string str, key id){
        if(num == A_TYP_ENGINE){
            sTypEngine = str;
            kHandlePRS = llGetNotecardLine(sNotecardPRS, iLinePRS = 0);// request first line
        }else if(num == A_FREE_MEMORY){//memory answer after C_END_FRAME
            iFreeMemory = (integer)str;
                iRecordedSnapshots++;
                //if(iEOF) llMessageLinked(LINK_THIS, R_DELAY_FRAME, "", (key)((string)iCurrentSnapshot));
                llMessageLinked(LINK_THIS, C_PLAY_FRAME, (string)iCurrentSnapshot, "");
                iCurrentSnapshot++;
        }else if(num == A_ACTUAL_FRAME){//answer after playing frame
                recordPrevSnap();
                if(iEOF){
                    llMessageLinked(LINK_THIS, C_PLAY_FRAME, "1", "");
                    state published;
                }else if(iCurrentSnapshot == 2)
                    llMessageLinked(LINK_THIS, R_SCALE, "", "");//request scale after loading frame 1
                else
                    llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");//next frame
        }else if(num == A_SCALE){//need scale for engine 1
            vScaleChange = (vector)str;
            llMessageLinked(LINK_THIS, C_RECORD_FRAME, (string)iFrameLoad, "");//next frame
        }else if(num == A_RECORD_FRAME){//answer after C_RECORD_FRAME
            // new frame first prim
                iPrimCounter = (integer)llGetSubString(sPrimLoad, 0, llSubStringIndex(sPrimLoad, ";") -1);
                llMessageLinked(LINK_THIS, C_RECORD_PRIM, sPrimLoad, "");
        }else if(num == A_RECORD_PRIM){
            kHandlePRS = llGetNotecardLine(sNotecardPRS, ++iLinePRS);//read the next notecard line
        }else if(num == A_OUT_MEMORY){
            llDialog(llGetOwner(),"No enough memory", ["Ok"], -32768);
            state published;
        }
    }
}

state published{
    state_entry(){
        //enable trigger
        llMessageLinked(LINK_THIS, C_PUBLISHED, "", "");
    }
}
Je crois que je ne l'ai pas précisé par ailleurs: le nom de la notecard doit commencer par 'OPRIAN PRS'.
Ben ma mise en oeuvre c'était dans un attachement et là ça ne marchait pas. J'ai du adapter un peu l'engine donc voici la version 1.83 qui fonctionne aussi bien dans un attachement qu'en dehors.

OPRIAN engine 2 1.83
Code:
// Script Name: OPRIAN engine 2 : OPen_PRIm_ANimator engine only changes
// Version 1.83; works now in attachment
// Original Author: Todd Borst
// Downloaded from : http://www.free-lsl-scripts.com/freescripts.plx?ID=1580

// engine 2 for use in set scripts OPRIAN: Author : Elenia Boucher

// This program is free software; you can redistribute it and/or modify it.
// License information must be included in any script you give out or use.
// This script is licensed under the Creative Commons Attribution-Share Alike 3.0 License
// from http://creativecommons.org/licenses/by-sa/3.0 unless licenses are
// included in the script or comments by the original author,in which case
// the authors license must be followed.

// Please leave any authors credits intact in any script you use or publish.
////////////////////////////////////////////////////////////////////

string sTypEngine = "2";
// 30 is a reasonnable limit for an animable linkset; 
// set to 0 if you want anime all the linkset
// otherwise, only the lastest selected before link will be animable
integer iLimitPrims = 13;//number of animable prims in the linkset - let 0 for all the linkset
integer iPrimCount;
integer iTotalPrims;
list lPos;
list lRot;
list lScale;
list lNumPrims;
list lNbPrimsByFrame;
list lDelay;
integer iNbPrimsInFrame;
integer iCurrentSnapshot   = 1;
integer iRecordedSnapshots = 0;
integer iEndFrame;
integer iStartFrame;
integer iIndexChange;
integer iFrame;
float fDelay = 1.0;
integer iReverse;
integer iAttach;
 
vector vRootScale   = ZERO_VECTOR;
vector vScaleChange = <1,1,1>;
 
// For tracking memory usage.  The amount of snapshots you can record is based
// on the number of prims and available memory.  Less prims = more snapshots
integer iMaxMemory  = 0;
integer iFreeMemory = 0;
 
integer iPlayAnimationStyle = 0;
// The values for iPlayAnimationStyle means
// 0 = no animation playing
// 1 = play animation once
// 2 = play animation looping
// 4 = play animation reverse mode
// 8 = play animation symmetry mode
// binary values can be combined

//dialog constants - naming: R request, A answer, C command - direction setup/triggers -> engine

integer C_PLAY_FRAME = 1;
integer C_PLAY_ANIM = 2;
integer C_STOP_ANIM = 3;
integer C_LOOP_ON = 4;
integer C_LOOP_OFF = 5;
integer C_REVERSE_ON = 6;
integer C_REVERSE_OFF = 7;
integer C_SYMMETRY_ON = 8;
integer C_SYMMETRY_OFF= 9;
integer C_PLAY_SLICE = 10;

integer R_MAX_MEMORY = 20;
integer A_MAX_MEMORY = 21;
integer R_FREE_MEMORY= 22;
integer A_FREE_MEMORY= 23;
integer R_TOTAL_FRAMES = 24;
integer A_TOTAL_FRAMES = 25;
integer R_ACTUAL_FRAME = 26;
integer A_ACTUAL_FRAME = 27;
integer A_PLAYING_FRAME = 28;
integer A_END_ANIMATION = 29;
integer A_START_ANIMATION = 30;
integer R_SCALE = 31;
integer A_SCALE = 32;
integer R_DELAY_FRAME = 33;
integer A_DELAY_FRAME = 34;
integer R_CHG_DELAY_FRAME = 35;
integer R_PRIM_COUNT = 36;
integer A_PRIM_COUNT = 37;

integer C_RECORD_FRAME = 40;
integer A_RECORD_FRAME = 41;
integer C_RECORD_PRIM = 42;
integer A_RECORD_PRIM = 43;
integer C_END_FRAME = 44;

integer C_DUMP_FRAME = 45;
integer A_DUMP_FRAME = 46;
integer R_TYP_ENGINE = 47;
integer A_TYP_ENGINE = 48;
integer A_DUMP_PRIM = 49;
integer C_RESET = 50;
integer A_RESET = 51;
integer A_OUT_MEMORY = 52;


showSnapshot(integer snapNumber)
{
    if(snapNumber > 0 && snapNumber <= iRecordedSnapshots )
    {
        llMessageLinked(LINK_THIS, A_PLAYING_FRAME, (string)snapNumber, "");
        vector   pos;
        rotation rot;
        vector   scale;
        float delay;
        //passe++;//mesure
 
        vector rootPos = llGetPos();
 
        // Might want to move llGetRot() into the loop for fast rotating objects.
        // Rotation during the animation playback may cause errors.
        
        rotation rootRot; 
        if(iAttach)
            rootRot= llGetLocalRot();
        else rootRot = llGetRot();
        
        integer end = iIndexChange + llList2Integer(lNbPrimsByFrame, snapNumber - 1);

        for(iIndexChange; iIndexChange < end; iIndexChange ++){ 
                    pos     = llList2Vector(lPos, iIndexChange);
                    rot     = llList2Rot(lRot, iIndexChange);
                    scale   = llList2Vector(lScale, iIndexChange);
                    
                //Adjust for scale changes
                if( vRootScale.x != 1.0 || vRootScale.y != 1.0 || vRootScale.z != 1.0 )
                {
                    pos.x *= vScaleChange.x;
                    pos.y *= vScaleChange.y;
                    pos.z *= vScaleChange.z;
                    scale.x *= vScaleChange.x;
                    scale.y *= vScaleChange.y;
                    scale.z *= vScaleChange.z;
                }
            
            llSetLinkPrimitiveParamsFast( llList2Integer(lNumPrims, iIndexChange), 
                                                   [ PRIM_POSITION, pos, 
                                                     PRIM_ROTATION, rot/rootRot, 
                                                     PRIM_SIZE, scale ] );
        }
        if(iPlayAnimationStyle > 0){
            delay = llList2Float(lDelay, snapNumber -1);
            if(delay == .0) delay = llList2Float(lDelay, iRecordedSnapshots - 1);
            llSetTimerEvent(delay);
        }
    }
}

//find the first record of a snapshot in the list
integer findFirstChange(integer snapshot){
    integer i;
    integer s;
    integer end = snapshot - 1;
    for(i=0; i < end; i++)
        s += llList2Integer(lNbPrimsByFrame, i);
    return s;
}

playAnimation(){
    iIndexChange = findFirstChange(iCurrentSnapshot);
    if(iPlayAnimationStyle == 0) iPlayAnimationStyle = 1;//one shot direct play by default
    llSetTimerEvent(0.1);
}

default
{
    state_entry()
    {
        iMaxMemory = llGetFreeMemory();
        iFreeMemory = llGetFreeMemory();
        iTotalPrims = llGetNumberOfPrims();
        if(iTotalPrims > (iLimitPrims + 1) && iLimitPrims > 1) iPrimCount = iLimitPrims +1;
        else iPrimCount = iTotalPrims;
 
        //setting initial root scale.  this allows the animation to scale if the root size is changed afterwards.
        vRootScale = llGetScale();
        llMessageLinked(LINK_THIS, A_RESET, "", "");
    }
    
    attach( key _k ) {
        if ( _k == NULL_KEY ){
            iAttach = FALSE;
        }else{ 
            iAttach = TRUE;
        }
    }
 
    changed(integer chg)
    {
        //this is needed to detect scale changes and record the differences in order to adjust the animation accordingly.
        if (chg & CHANGED_SCALE)
        {
            if (vRootScale != ZERO_VECTOR)
            {
                vector newScale = llGetScale();
                //since change_scale is triggered even with linked prim changes,
                //this is to filter out non-root changes.
                if( ( newScale.x / vRootScale.x) != vScaleChange.x ||
                    ( newScale.y / vRootScale.y) != vScaleChange.y ||
                    ( newScale.z / vRootScale.z) != vScaleChange.z )
                {
                    vScaleChange.x = newScale.x / vRootScale.x;
                    vScaleChange.y = newScale.y / vRootScale.y;
                    vScaleChange.z = newScale.z / vRootScale.z;
                    llMessageLinked(LINK_THIS, A_SCALE, (string)vScaleChange, "");
                }
            }
        }
        // if new prims are added or removed from this object then the script resets
        // because the animations are now broken.
        else if (chg & CHANGED_LINK)
        {
            if( iTotalPrims != llGetNumberOfPrims() )
            {
                llOwnerSay("Link change detected, reseting script.");
                llResetScript();
            }
        }
    }
 

    link_message(integer sender_num, integer num, string str, key id){
        if(num == C_PLAY_FRAME){
            integer snapshot = (integer)str;
            iIndexChange = findFirstChange(snapshot); 
            iCurrentSnapshot = snapshot;
            showSnapshot(snapshot);
            llMessageLinked(LINK_THIS, A_ACTUAL_FRAME, (string)iCurrentSnapshot, "");
        }else if(num == C_PLAY_ANIM ){
            if(iRecordedSnapshots == 0) return;
            iStartFrame = 1;
            iEndFrame = iRecordedSnapshots;
            if(iPlayAnimationStyle & 4){//reverse mode
                iCurrentSnapshot--;//Start animation at the previous frame
                if(iCurrentSnapshot <= 1) iCurrentSnapshot = iRecordedSnapshots;
            }else{//direct mode
                iCurrentSnapshot++; //Start animation at the next frame
                if(iCurrentSnapshot >= iRecordedSnapshots) iCurrentSnapshot = 1;
            }
            playAnimation();
        }else if(num == C_PLAY_SLICE ){
            iStartFrame = (integer)str;
            iEndFrame = (integer)((string)id);
            if(iStartFrame >= iEndFrame || iStartFrame < 1 || iEndFrame > iRecordedSnapshots) return;
            if(iCurrentSnapshot < iStartFrame || iCurrentSnapshot > iEndFrame){
                if(iPlayAnimationStyle & 4)//reverse mode
                    iCurrentSnapshot = iEndFrame;
                else
                    iCurrentSnapshot = iStartFrame;
            }
            playAnimation();
        }else if(num == C_STOP_ANIM){
            iPlayAnimationStyle = 0;
            llSetTimerEvent(0);
        }else if(num == C_LOOP_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 2;
        }else if(num == C_LOOP_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 0xD;
        }else if(num == C_REVERSE_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 4 & 7;//Reverse excludes symmetry
        }else if(num == C_REVERSE_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 0xB;
        }else if(num == C_SYMMETRY_ON){
            iPlayAnimationStyle = iPlayAnimationStyle | 8 & 0xB;//symmetry excludes reverse
        }else if(num == C_SYMMETRY_OFF){
            iPlayAnimationStyle = iPlayAnimationStyle & 7;
        }else if(num == R_MAX_MEMORY){
            llMessageLinked(LINK_THIS, A_MAX_MEMORY, (string)iMaxMemory, "");
        }else if(num == R_FREE_MEMORY){
            if(iFreeMemory > llGetFreeMemory()) iFreeMemory = llGetFreeMemory();
            else iFreeMemory--;//little cheat because the garbage collector has sometime a strange behavior
            llMessageLinked(LINK_THIS, A_FREE_MEMORY, (string)iFreeMemory, "");
        }else if(num == R_TOTAL_FRAMES){
            llMessageLinked(LINK_THIS, A_TOTAL_FRAMES, (string)iRecordedSnapshots, "");
        }else if(num == R_ACTUAL_FRAME){
            llMessageLinked(LINK_THIS, A_ACTUAL_FRAME, (string)iCurrentSnapshot, "");
        }else if(num == R_PRIM_COUNT){
            llMessageLinked(LINK_THIS, A_PRIM_COUNT, (string)iPrimCount, "");
        }else if(num == R_SCALE){
            llMessageLinked(LINK_THIS, A_SCALE, (string)vScaleChange, "");
        }else if(num == R_DELAY_FRAME){
            integer index = (integer)((string)id) - 1;
            llMessageLinked(LINK_THIS, A_DELAY_FRAME, llList2String(lDelay, index), id);
        }else if(num == R_CHG_DELAY_FRAME){
            integer index = (integer)((string)id) - 1;
            lDelay = llListReplaceList(lDelay, [(float)str], index, index);
        }else if(num == C_RECORD_FRAME){
            iFreeMemory = llGetFreeMemory();
            if(iFreeMemory < 200)
                llMessageLinked(LINK_THIS, A_OUT_MEMORY, (string)iFreeMemory, "");
            else{
                iNbPrimsInFrame = 0;
                iFrame = (integer)str;
                llMessageLinked(LINK_THIS, A_RECORD_FRAME, (string)iFrame, "");
            }
        }else if(num == C_RECORD_PRIM){//inline function more faster and economic
            
            integer index = llSubStringIndex(str, ";");
            integer numprim = (integer)llGetSubString(str, 0, index -1);
            str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
            if(index > -1){
                if(numprim == 1) {//scale root
                    vRootScale = (vector)llGetSubString(str, 0, index -1);
                    vector newScale = llGetScale();
                    if(vRootScale != newScale){
                        vScaleChange.x = newScale.x / vRootScale.x;
                        vScaleChange.y = newScale.y / vRootScale.y;
                        vScaleChange.z = newScale.z / vRootScale.z;
                    }  
                }else{//current prims
                        iNbPrimsInFrame++;
                    lNumPrims += [numprim];
                    
                        lPos += [(vector)llGetSubString(str, 0, index -1)];
                        str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
                    lRot += [(rotation)llGetSubString(str, 0, index -1)];
                    str = llDeleteSubString(str, 0, index);
                        index = llSubStringIndex(str, ";");
                        lScale += [(vector)llGetSubString(str, 0, index -1)];
                        str = llDeleteSubString(str, 0, index);
                    index = llSubStringIndex(str, ";");
                    fDelay = (float)llGetSubString(str, 0, index -1);
                    str = llDeleteSubString(str, 0, index);
                    if(fDelay < .1) fDelay = 1.0;
                }
            }
            iFreeMemory = llGetFreeMemory();
            if(iFreeMemory < 300){
                lNumPrims = llDeleteSubList(lNumPrims, -iNbPrimsInFrame, -1);
                lPos = llDeleteSubList(lPos, -iNbPrimsInFrame, -1);
                lRot = llDeleteSubList(lRot, -iNbPrimsInFrame, -1);
                lScale = llDeleteSubList(lScale, -iNbPrimsInFrame, -1);
                llMessageLinked(LINK_THIS, A_OUT_MEMORY, (string)iFreeMemory, "");
            }
            else llMessageLinked(LINK_THIS, A_RECORD_PRIM, "", "");
        }else if(num == C_END_FRAME){
            lNbPrimsByFrame += [iNbPrimsInFrame];
            lDelay += [fDelay];    
            iRecordedSnapshots++;
            iCurrentSnapshot++;
            iFreeMemory = llGetFreeMemory();
            llMessageLinked(LINK_THIS, A_FREE_MEMORY, (string)iFreeMemory, "");
        }else if(num == C_DUMP_FRAME){
            integer frame = (integer)str;
            integer index = findFirstChange(frame);
            integer end = index + llList2Integer(lNbPrimsByFrame, frame - 1);
            if(frame == 1 && end > 0) llMessageLinked(LINK_THIS, A_DUMP_PRIM, "1;" + 
                                        (string)vRootScale + ";", (key)((string)frame)); 
            for(index ; index < end; index ++){ 
                llMessageLinked(LINK_THIS, A_DUMP_PRIM, 
                    llList2String(lNumPrims, index) + ";" +
                    llList2String(lPos, index) + ";" + llList2String(lRot, index) + ";" +
                    llList2String(lScale, index) + ";" + llList2String(lDelay, frame-1) + ";", (key)((string)frame));
            }
            llMessageLinked(LINK_THIS, A_DUMP_FRAME, str, "");
        }else if(num == R_TYP_ENGINE){
            llMessageLinked(LINK_THIS, A_TYP_ENGINE, sTypEngine, "");
        }else if(num == C_RESET){
            llResetScript();
        }
    }
 
    //Timer event is used to handle the animation playback.
    timer() {
        showSnapshot(iCurrentSnapshot);
        if(iPlayAnimationStyle & 4){//reverse playback
            //if not at the first frame, decrement the counter
            if(iCurrentSnapshot > iStartFrame){
                iCurrentSnapshot--;
                iIndexChange = iIndexChange - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot)
                            - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot-1);
            }else { 
                if( iPlayAnimationStyle & 2){//if looping?
                    if(iPlayAnimationStyle & 8){//symmetry mode if looping
                        iIndexChange = findFirstChange(iStartFrame);
                        iPlayAnimationStyle = iPlayAnimationStyle & 0xB;//take off the reverse mode next play start frame  
                    }else{// if animation is looping and no symmetry mode, set the counter at the last frame
                        iCurrentSnapshot = iEndFrame;
                        iIndexChange = findFirstChange(iEndFrame);
                    }
                }else{// if animation isn't looping, stop the animation
                    llMessageLinked(LINK_THIS, A_START_ANIMATION, "", "");
                    iIndexChange = 0;
                    llSetTimerEvent(0);
                    iPlayAnimationStyle = 0;
                }
            }
        }else{//direct playback     
            //if not at the end of the animation, increment the counter
            if(iCurrentSnapshot < iEndFrame)
                iCurrentSnapshot++;
            else{
                if(iPlayAnimationStyle & 8){//symmetry mode
                    iPlayAnimationStyle = iPlayAnimationStyle | 4; //set the reverse mode next play iRecordedSnapshots - 1
                    iCurrentSnapshot--;
                    iIndexChange = iIndexChange - llList2Integer(lNbPrimsByFrame, iCurrentSnapshot-1) - 
                                   llList2Integer(lNbPrimsByFrame, iCurrentSnapshot) ;
                // if animation is looping and no symmetry mode , set the counter back to 1
                }else if( iPlayAnimationStyle & 2){
                    iCurrentSnapshot = iStartFrame;
                    iIndexChange = findFirstChange(iStartFrame);
                }else{// if animation isn't looping, stop the animation
                    llMessageLinked(LINK_THIS, A_END_ANIMATION, "", "");
                    llSetTimerEvent(0);
                    iIndexChange = 0;//for the next animation if published
                    iPlayAnimationStyle = 0;
                }
            }
        }
    }
}
Hello tout le monde,

Bravo pour l’idée d'un projet open

Je réagis un peu comme BestMomo, je suis sur un autre gros truc (qui sera open aussi), et donc pas trop impliqué dans votre projet d'animation. Néanmoins, j'ai un peu vu une problématique de mémoire et ayant eut à me battre contre des "stack heap errors", je vous livre ici ce que j'ai pu trouver comme méthodes pour gagner de la place (ça mériterait presque un post à part)

- Comme la très justement indiqué BestMomo, multiplier les scripts. Chaque script ayant son propre espace mémoire. La difficulté étant de pouvoir partager des données globales...

- La taille des scripts : ça me semble limité à 64k (et je les atteins souvent!), donc raccourcir le code, mais au détriment de la lisibilité...
J'ai pu constater que raccourcir le script permettait de gagner de la place pour les variables.

- Les variables globales "constantes". J'ai trouvé une solution étonnante : les remplacer par des fonctions! on gagne en mémoire la place occupée par la variable.
Par exemple, remplacer : integer X=1; par :
integer X() { return 1;}
(j'y croyais pas quand j'ai essayé!)

- les listes. on sait déjà que c'est très lent, mais en plus ça prend de la place!
essayer au maximum de les remplacer par des chaines. bon si on veut stocker des vecteurs avec des float c'est pas évident (mais possible), mais si on a des entiers on peut toujours formater une chaîne et retrouver les éléments avec llGetSubString. Si en plus les entiers ne sont pas trop grands, on peut les remplacer par des caractères, par exemple "a" au lieu de "01;"

- les listes ou les chaines en paramètre de procédure/fonction.
Comme c'est passé par valeur, il y a duplication de la donnée pour le paramètre local. On double l'occupation mémoire, et si le paramètre est déjà volumineux, ça fait mal!. pour ce cas là, il vaut mieux une variable globale dédiée (oui c'est berk!).

Bon évidemment, comme c'est de l'optimisation, ça fait du code pas très joli et plus difficile à faire évoluer/débugguer, mais c'est le prix à payer

voilà, en espérant aider un chouïa sans être trop HS
Merci de ton aide Barbidule

J'ai pu constaté que tout ce que tu dis est exact, en particulier la duplication des strings et listes lors des appels qui fait très mal.
Et puis j'ai été horrifiée de l'occupation mémoire: en mono, un integer prend 15 octects, autant qu'un float ! Alors qu'un string n'en prend que 12+1 par caractére. Sur pour stocker des valeurs booléenne on économise 2 octects en utilisant un string. Et avec les listes c'est une horreur.

Mais je suis satisfaites des performances actuelles de l'OPRIAM: tant que l'on fait peu de frames (et on a tendance a en limiter le nombre parce que c'est laborieux à faire), et qu'elles portent sur peu de prims (il est rare que l'on doivent animer toutes les prims, le seul cas est mannequin ou animal que l'on veut déplacer en bloc) ça va.

Je l'ai mis à l'épreuve pour faire marcher un cheval (marche statique sur place, il est destiné a être porté en attachement): 12 prims à animer parmi 60, 8 frames. Performances très satisfaisantes.

Mais c'est sur que si on veut doubler ou tripler la capacité mémoire, il faut passer par une division des scripts pour stocker position, rotation, taille chacune dans un script dédié. Mais ce sera au détriment de la vitesse de restitution j'en ai peur.
Répondre

Connectés sur ce fil

 
1 connecté (0 membre et 1 invité) Afficher la liste détaillée des connectés