{
  "author": "Claude pour Studio Mocap",
  "authorIds": [],
  "category": "Movement",
  "dependencies": [],
  "description": "Rejoue dans GDevelop les animations enregistrées avec **Studio Mocap** (mode « Objet tenu ») : tu bouges le cube à marqueurs devant ta webcam, et l'objet 3D ou la caméra refait exactement le même mouvement.\n\n- Comportement « Animation enregistrée » pour n'importe quel objet 3D.\n- Actions pour la caméra 3D de n'importe quel calque.\n- L'animation part de la position et de la rotation actuelles de l'objet ou de la caméra.\n- Boucle, aller-retour (l'animation se rejoue à l'envers pour revenir au point de départ), vitesse (même à l'envers), échelle, pause, reprise, aller à un instant.",
  "dimension": "3D",
  "extensionNamespace": "",
  "fullName": "Mocap Player (animations Studio Mocap)",
  "gdevelopVersion": ">=5.5.222",
  "helpPath": "https://matteoels.github.io/studio-mocap/",
  "iconUrl": "",
  "name": "MocapPlayer",
  "origin": {
    "identifier": "MocapPlayer",
    "name": "local"
  },
  "previewIconUrl": "",
  "shortDescription": "Rejoue sur un objet 3D ou la caméra les mouvements enregistrés avec Studio Mocap.",
  "tags": [
    "3d",
    "animation",
    "mocap",
    "caméra",
    "webcam"
  ],
  "version": "1.2.0",
  "globalVariables": [],
  "sceneVariables": [],
  "eventsFunctions": [
    {
      "fullName": "",
      "functionType": "Action",
      "name": "onFirstSceneLoaded",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::Standard",
          "conditions": [],
          "actions": [
            {
              "type": {
                "value": "MocapPlayer::DefineHelperClasses"
              },
              "parameters": [
                "",
                ""
              ]
            }
          ]
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "fullName": "",
      "functionType": "Action",
      "name": "onScenePreEvents",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "if (!ext || !runtimeScene.__mocapCameras) return;",
            "const dt = runtimeScene.getTimeManager().getElapsedTime() / 1000;",
            "for (const [layer, player] of runtimeScene.__mocapCameras) {",
            "    if (runtimeScene.hasLayer && !runtimeScene.hasLayer(layer)) continue;",
            "    if (player.advance(dt)) ext.Core.applyToCamera(runtimeScene, layer, player);",
            "}"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Définit le code JavaScript du lecteur.",
      "fullName": "Définir les classes du lecteur",
      "functionType": "Action",
      "name": "DefineHelperClasses",
      "private": true,
      "sentence": "Définir les classes du lecteur",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "if (gdjs.__mocapPlayerExtension) {",
            "    return;",
            "}",
            "",
            "// =====================================================================",
            "// Mocap Player — rejoue dans GDevelop une animation enregistrée avec Studio Mocap",
            "// Texte : SMOCAP1|images par seconde|nombre d'images|x,y,z,qx,qy,qz,qw;...",
            "// Positions en cm et rotations (quaternion), relatives au début de la prise,",
            "// dans le repère : X à droite, Y en s'éloignant de la webcam, Z vers le haut.",
            "// =====================================================================",
            "/* global THREE */",
            "const MocapCore = (() => {",
            "  const cache = new Map();",
            "",
            "  const parse = (text) => {",
            "    text = String(text || '');",
            "    if (cache.has(text)) return cache.get(text);",
            "    let anim = null;",
            "    const clean = text.replace(/\\s+/g, '');",
            "    const parts = clean.split('|');",
            "    if (parts.length >= 4 && /^SMOCAP\\d+$/.test(parts[0])) {",
            "      const fps = Math.max(1, Number(parts[1]) || 30);",
            "      const frames = parts.slice(3).join('|').split(';').filter(Boolean);",
            "      const n = frames.length;",
            "      if (n >= 1) {",
            "        const pos = new Float32Array(n * 3);",
            "        const quat = new Float32Array(n * 4);",
            "        let ok = true;",
            "        for (let i = 0; i < n; i++) {",
            "          const v = frames[i].split(',').map(Number);",
            "          if (v.length < 7 || v.some((x) => !isFinite(x))) { ok = false; break; }",
            "          pos[i * 3] = v[0]; pos[i * 3 + 1] = v[1]; pos[i * 3 + 2] = v[2];",
            "          // même signe d'un quaternion à l'autre (interpolation sans détour)",
            "          let qx = v[3], qy = v[4], qz = v[5], qw = v[6];",
            "          if (i > 0 && qx * quat[i * 4 - 4] + qy * quat[i * 4 - 3] + qz * quat[i * 4 - 2] + qw * quat[i * 4 - 1] < 0) {",
            "            qx = -qx; qy = -qy; qz = -qz; qw = -qw;",
            "          }",
            "          const l = Math.hypot(qx, qy, qz, qw) || 1;",
            "          quat[i * 4] = qx / l; quat[i * 4 + 1] = qy / l; quat[i * 4 + 2] = qz / l; quat[i * 4 + 3] = qw / l;",
            "        }",
            "        if (ok) anim = { fps, n, pos, quat, duration: (n - 1) / fps };",
            "      }",
            "    }",
            "    if (!anim) console.error('[MocapPlayer] Texte d\\'animation invalide : il doit commencer par \"SMOCAP1|\" (copie-le depuis Studio Mocap).');",
            "    if (cache.size > 50) cache.clear();",
            "    cache.set(text, anim);",
            "    return anim;",
            "  };",
            "",
            "  const qa = new THREE.Quaternion();",
            "  const qb = new THREE.Quaternion();",
            "  // Position (cm) et rotation à l'instant t (secondes)",
            "  const sample = (anim, t, outPos, outQuat) => {",
            "    const f = Math.min(anim.n - 1, Math.max(0, t * anim.fps));",
            "    const i = Math.floor(f), j = Math.min(anim.n - 1, i + 1), k = f - i;",
            "    for (let c = 0; c < 3; c++) outPos[c] = anim.pos[i * 3 + c] + (anim.pos[j * 3 + c] - anim.pos[i * 3 + c]) * k;",
            "    qa.fromArray(anim.quat, i * 4);",
            "    qb.fromArray(anim.quat, j * 4);",
            "    outQuat.copy(qa).slerp(qb, k);",
            "  };",
            "",
            "  // Lecteur commun aux objets et à la caméra",
            "  class Player {",
            "    constructor() {",
            "      this.anim = null;",
            "      this.time = 0;",
            "      this.playing = false;",
            "      this.finished = false;",
            "      this.loop = false;",
            "      this.speed = 1;",
            "      this.scale = 1;",
            "      this.applyPosition = true;",
            "      this.applyRotation = true;",
            "      this.relativeToAngle = false;",
            "      this.pingPong = false; // aller-retour : rejoue l'animation à l'envers après la fin",
            "      this.start = null; // état au lancement",
            "      this._p = [0, 0, 0];",
            "      this._q = new THREE.Quaternion();",
            "    }",
            "    play(text, opts, start) {",
            "      const anim = parse(text);",
            "      if (!anim) return false;",
            "      Object.assign(this, opts);",
            "      this.anim = anim;",
            "      this.time = this.speed < 0 ? this.total() : 0; // vitesse négative : part de la fin",
            "      this.playing = true;",
            "      this.finished = false;",
            "      this.start = start;",
            "      return true;",
            "    }",
            "    // Durée totale d'un passage (aller + retour en mode aller-retour)",
            "    total() {",
            "      return this.anim ? this.anim.duration * (this.pingPong ? 2 : 1) : 0;",
            "    }",
            "    // Instant de l'enregistrement montré : à l'aller, puis en remontant le temps au retour",
            "    sampleTime() {",
            "      const d = this.anim.duration;",
            "      return this.pingPong && this.time > d ? 2 * d - this.time : this.time;",
            "    }",
            "    // Avance le temps ; renvoie false s'il n'y a rien à appliquer",
            "    advance(dt) {",
            "      if (!this.anim || !this.playing) return false;",
            "      this.time += dt * this.speed;",
            "      const d = this.total();",
            "      if (d <= 0) { this.time = 0; }",
            "      else if (this.loop) { this.time = ((this.time % d) + d) % d; }",
            "      else if (this.time >= d || this.time < 0) {",
            "        this.time = Math.min(d, Math.max(0, this.time));",
            "        this.playing = false;",
            "        this.finished = true;",
            "      }",
            "      return true;",
            "    }",
            "    // Décalage de position en pixels GDevelop et rotation (monde affiché) à l'instant courant.",
            "    // yaw : en mode relatif, angle (radians, sens GDevelop) dont on tourne tout le mouvement",
            "    // autour de l'axe vertical, pour que « s'éloigner de la webcam » = « avancer devant ».",
            "    current(yaw) {",
            "      sample(this.anim, this.sampleTime(), this._p, this._q);",
            "      // monde affiché (Three.js) : X droite, Y loin de la webcam, Z haut ; GDevelop : Y inversé",
            "      let dx = this._p[0] * this.scale, dy = -this._p[1] * this.scale;",
            "      const dz = this._p[2] * this.scale;",
            "      if (this.relativeToAngle && yaw) {",
            "        const c = Math.cos(yaw), s = Math.sin(yaw);",
            "        const rx = dx * c - dy * s, ry = dx * s + dy * c;",
            "        dx = rx; dy = ry;",
            "      }",
            "      return { dx, dy, dz, q: this._q };",
            "    }",
            "  }",
            "",
            "  const euler = new THREE.Euler(0, 0, 0, 'ZYX');",
            "  const q0 = new THREE.Quaternion();",
            "  const qr = new THREE.Quaternion();",
            "  const qy = new THREE.Quaternion();",
            "  const qyi = new THREE.Quaternion();",
            "  const AXIS_Z = new THREE.Vector3(0, 0, 1);",
            "",
            "  // ---- Objet 3D ----",
            "  const objectStart = (object) => ({",
            "    x: object.getX(), y: object.getY(), z: object.getZ(),",
            "    rotationX: object.getRotationX ? object.getRotationX() : 0,",
            "    rotationY: object.getRotationY ? object.getRotationY() : 0,",
            "    angle: object.getAngle(),",
            "  });",
            "  const applyToObject = (object, player) => {",
            "    const st = player.start;",
            "    // un objet d'angle 0 regarde vers +X : « loin de la webcam » devient sa direction",
            "    const yaw = player.relativeToAngle ? gdjs.toRad(st.angle + 90) : 0;",
            "    const c = player.current(yaw);",
            "    if (player.applyPosition) {",
            "      object.setX(st.x + c.dx);",
            "      object.setY(st.y + c.dy);",
            "      if (object.setZ) object.setZ(st.z + c.dz);",
            "    }",
            "    if (player.applyRotation) {",
            "      // rotation GDevelop = miroir (Y) de la rotation affichée, appliquée à la rotation de départ",
            "      euler.set(gdjs.toRad(st.rotationX), gdjs.toRad(st.rotationY), gdjs.toRad(st.angle), 'ZYX');",
            "      q0.setFromEuler(euler);",
            "      qr.set(-c.q.x, c.q.y, -c.q.z, c.q.w);",
            "      if (yaw) {",
            "        // mode relatif : les axes des rotations tournent avec l'objet (pencher en avant = vers son avant)",
            "        qy.setFromAxisAngle(AXIS_Z, yaw); qyi.copy(qy).invert();",
            "        qr.premultiply(qy).multiply(qyi);",
            "      }",
            "      qr.multiply(q0);",
            "      euler.setFromQuaternion(qr, 'ZYX');",
            "      if (object.setRotationX) object.setRotationX(gdjs.toDegrees(euler.x));",
            "      if (object.setRotationY) object.setRotationY(gdjs.toDegrees(euler.y));",
            "      object.setAngle(gdjs.toDegrees(euler.z));",
            "    }",
            "  };",
            "",
            "  // ---- Caméra 3D ----",
            "  const cam = () => gdjs.evtTools.camera;",
            "  const cam3d = () => gdjs.scene3d && gdjs.scene3d.camera;",
            "  const cameraStart = (scene, layer) => ({",
            "    x: cam().getCameraX(scene, layer, 0),",
            "    y: cam().getCameraY(scene, layer, 0),",
            "    z: cam3d() ? cam3d().getCameraZ(scene, layer, 0) : 0,",
            "    rotationX: cam3d() ? cam3d().getCameraRotationX(scene, layer, 0) : 0,",
            "    rotationY: cam3d() ? cam3d().getCameraRotationY(scene, layer, 0) : 0,",
            "    angle: cam().getCameraRotation(scene, layer, 0),",
            "  });",
            "  const applyToCamera = (scene, layer, player) => {",
            "    const st = player.start;",
            "    // caméra d'angle 0 (inclinée de 90° en X) : elle regarde vers −Y, comme « loin de la webcam »",
            "    const yaw = player.relativeToAngle ? gdjs.toRad(st.angle) : 0;",
            "    const c = player.current(yaw);",
            "    if (player.applyPosition) {",
            "      cam().setCameraX(scene, st.x + c.dx, layer, 0);",
            "      cam().setCameraY(scene, st.y + c.dy, layer, 0);",
            "      if (cam3d()) cam3d().setCameraZ(scene, st.z + c.dz, layer, 0);",
            "    }",
            "    if (player.applyRotation && cam3d()) {",
            "      // la caméra Three.js est hors de la scène inversée : rotation (X, Y, -angle) dans l'ordre ZYX",
            "      euler.set(gdjs.toRad(st.rotationX), gdjs.toRad(st.rotationY), -gdjs.toRad(st.angle), 'ZYX');",
            "      q0.setFromEuler(euler);",
            "      qr.copy(c.q);",
            "      if (yaw) {",
            "        // repère de la caméra Three.js : Y non inversé, donc l'angle GDevelop compte à l'envers",
            "        qy.setFromAxisAngle(AXIS_Z, -yaw); qyi.copy(qy).invert();",
            "        qr.premultiply(qy).multiply(qyi);",
            "      }",
            "      qr.multiply(q0);",
            "      euler.setFromQuaternion(qr, 'ZYX');",
            "      cam3d().setCameraRotationX(scene, gdjs.toDegrees(euler.x), layer, 0);",
            "      cam3d().setCameraRotationY(scene, gdjs.toDegrees(euler.y), layer, 0);",
            "      cam().setCameraRotation(scene, -gdjs.toDegrees(euler.z), layer, 0);",
            "    }",
            "  };",
            "",
            "  return { parse, Player, objectStart, applyToObject, cameraStart, applyToCamera };",
            "})();",
            "",
            "",
            "// =====================================================================",
            "// Mocap Player — liaison avec GDevelop",
            "// =====================================================================",
            "gdjs.__mocapPlayerExtension = {",
            "  Core: MocapCore,",
            "  player(behavior) {",
            "    if (!behavior.__mocapPlayer) behavior.__mocapPlayer = new MocapCore.Player();",
            "    return behavior.__mocapPlayer;",
            "  },",
            "  cameras(runtimeScene) {",
            "    if (!runtimeScene.__mocapCameras) runtimeScene.__mocapCameras = new Map();",
            "    return runtimeScene.__mocapCameras;",
            "  },",
            "  cameraPlayer(runtimeScene, layer) {",
            "    const map = this.cameras(runtimeScene);",
            "    if (!map.has(layer)) map.set(layer, new MocapCore.Player());",
            "    return map.get(layer);",
            "  },",
            "  options(ctx) {",
            "    const a = (n, d) => { const v = ctx.getArgument(n); return v === undefined || v === '' ? d : v; };",
            "    return {",
            "      loop: !!a('Loop', false),",
            "      speed: Number(a('Speed', 1)),",
            "      scale: Number(a('Scale', 1)),",
            "      applyPosition: !!a('ApplyPosition', true),",
            "      applyRotation: !!a('ApplyRotation', true),",
            "      relativeToAngle: !!a('RelativeToAngle', false),",
            "      pingPong: !!a('PingPong', false),",
            "    };",
            "  },",
            "  restore(target, player, isCamera, runtimeScene, layer) {",
            "    if (!player.anim || !player.start) return;",
            "    player.time = 0;",
            "    if (isCamera) MocapCore.applyToCamera(runtimeScene, layer, player);",
            "    else MocapCore.applyToObject(target, player);",
            "  },",
            "};"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [],
      "objectGroups": []
    },
    {
      "description": "Joue une animation enregistrée avec Studio Mocap sur la caméra d'un calque, à partir de sa position et de son orientation actuelles.",
      "fullName": "Jouer l'animation sur la caméra",
      "functionType": "Action",
      "group": "Caméra",
      "name": "PlayOnCamera",
      "sentence": "Jouer l'animation enregistrée sur la caméra du calque _PARAM2_ (boucle : _PARAM3_, aller-retour : _PARAM9_, vitesse ×_PARAM4_, échelle _PARAM5_ px/cm)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "const player = ext.cameraPlayer(runtimeScene, layer);",
            "const opts = ext.options(eventsFunctionContext);",
            "if (player.play(eventsFunctionContext.getArgument(\"Data\"), opts, ext.Core.cameraStart(runtimeScene, layer))) ext.Core.applyToCamera(runtimeScene, layer, player);"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Texte de l'animation (copié depuis Studio Mocap)",
          "name": "Data",
          "type": "string",
          "longDescription": "Par exemple une variable texte : VariableString(MonAnimation), ou le texte d'un objet Texte : MonTexte.Text()."
        },
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        },
        {
          "description": "Jouer en boucle",
          "name": "Loop",
          "type": "yesorno",
          "defaultValue": "no",
          "optional": true
        },
        {
          "description": "Vitesse (1 = normale, 2 = deux fois plus vite, -1 = à l'envers)",
          "name": "Speed",
          "type": "expression",
          "defaultValue": "1",
          "optional": true
        },
        {
          "description": "Pixels par cm réel",
          "name": "Scale",
          "type": "expression",
          "defaultValue": "1",
          "optional": true,
          "longDescription": "1 = 1 cm de mouvement du cube donne 1 pixel. Avec la Physique 3D par défaut (100 px = 1 m), 1 est l'échelle réelle."
        },
        {
          "description": "Appliquer les déplacements",
          "name": "ApplyPosition",
          "type": "yesorno",
          "defaultValue": "yes",
          "optional": true
        },
        {
          "description": "Appliquer les rotations",
          "name": "ApplyRotation",
          "type": "yesorno",
          "defaultValue": "yes",
          "optional": true
        },
        {
          "description": "Relatif à l'orientation de départ",
          "name": "RelativeToAngle",
          "type": "yesorno",
          "defaultValue": "yes",
          "optional": true,
          "longDescription": "Oui : le mouvement tourne avec la caméra. « S'éloigner de la webcam » devient « avancer dans la direction où regarde la caméra », et pencher le cube en avant fait baisser la caméra, quel que soit son angle."
        },
        {
          "description": "Aller-retour (rejouer à l'envers après la fin)",
          "name": "PingPong",
          "type": "yesorno",
          "defaultValue": "no",
          "optional": true,
          "longDescription": "Oui : à la fin, l'animation se rejoue à l'envers jusqu'au début, donc l'objet ou la caméra revient exactement à sa position et à sa rotation de départ. Avec la boucle, l'aller-retour se répète sans à-coup."
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Arrête l'animation de la caméra. Elle reste où elle est, ou revient à son départ.",
      "fullName": "Arrêter l'animation de la caméra",
      "functionType": "Action",
      "group": "Caméra",
      "name": "StopCamera",
      "sentence": "Arrêter l'animation de la caméra du calque _PARAM1_ (revenir au départ : _PARAM2_)",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "const player = ext.cameraPlayer(runtimeScene, layer);",
            "player.playing = false;",
            "if (eventsFunctionContext.getArgument(\"Reset\")) ext.restore(null, player, true, runtimeScene, layer);"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        },
        {
          "description": "Revenir à la position et à l'orientation de départ",
          "name": "Reset",
          "type": "yesorno",
          "defaultValue": "no",
          "optional": true
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Met en pause l'animation de la caméra.",
      "fullName": "Mettre en pause la caméra",
      "functionType": "Action",
      "group": "Caméra",
      "name": "PauseCamera",
      "sentence": "Mettre en pause l'animation de la caméra du calque _PARAM1_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "ext.cameraPlayer(runtimeScene, layer).playing = false;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Reprend l'animation de la caméra.",
      "fullName": "Reprendre la caméra",
      "functionType": "Action",
      "group": "Caméra",
      "name": "ResumeCamera",
      "sentence": "Reprendre l'animation de la caméra du calque _PARAM1_",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "const player = ext.cameraPlayer(runtimeScene, layer);",
            "if (player.anim && !player.finished) player.playing = true;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vérifie si la caméra joue une animation.",
      "fullName": "Animation de la caméra en cours",
      "functionType": "Condition",
      "group": "Caméra",
      "name": "CameraIsPlaying",
      "sentence": "La caméra du calque _PARAM1_ joue une animation",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "eventsFunctionContext.returnValue = ext.cameraPlayer(runtimeScene, layer).playing;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Vérifie si l'animation de la caméra est arrivée à la fin.",
      "fullName": "Animation de la caméra terminée",
      "functionType": "Condition",
      "group": "Caméra",
      "name": "CameraIsFinished",
      "sentence": "L'animation de la caméra du calque _PARAM1_ est terminée",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "eventsFunctionContext.returnValue = ext.cameraPlayer(runtimeScene, layer).finished;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        }
      ],
      "objectGroups": []
    },
    {
      "description": "Temps actuel de l'animation de la caméra (secondes).",
      "fullName": "Temps de l'animation de la caméra",
      "functionType": "Expression",
      "group": "Caméra",
      "name": "CameraTime",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "eventsFunctionContext.returnValue = ext.cameraPlayer(runtimeScene, layer).time;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Calque (vide = calque de base)",
          "name": "Layer",
          "type": "layer",
          "defaultValue": "\"\"",
          "optional": true
        }
      ],
      "objectGroups": [],
      "expressionType": {
        "type": "expression"
      }
    },
    {
      "description": "Durée (secondes) d'un texte d'animation de Studio Mocap.",
      "fullName": "Durée d'une animation",
      "functionType": "Expression",
      "group": "Animation enregistrée",
      "name": "AnimationDuration",
      "sentence": "",
      "events": [
        {
          "type": "BuiltinCommonInstructions::JsCode",
          "inlineCode": [
            "const ext = gdjs.__mocapPlayerExtension;",
            "const layer = eventsFunctionContext.getArgument(\"Layer\") || \"\";",
            "const anim = ext.Core.parse(eventsFunctionContext.getArgument(\"Data\"));",
            "eventsFunctionContext.returnValue = anim ? anim.duration : 0;"
          ],
          "parameterObjects": "",
          "useStrict": true,
          "eventsSheetExpanded": false
        }
      ],
      "parameters": [
        {
          "description": "Texte de l'animation (copié depuis Studio Mocap)",
          "name": "Data",
          "type": "string",
          "longDescription": "Par exemple une variable texte : VariableString(MonAnimation), ou le texte d'un objet Texte : MonTexte.Text()."
        }
      ],
      "objectGroups": [],
      "expressionType": {
        "type": "expression"
      }
    }
  ],
  "eventsFunctionsFolderStructure": {
    "folderName": "__ROOT",
    "children": [
      {
        "functionName": "onFirstSceneLoaded"
      },
      {
        "functionName": "onScenePreEvents"
      },
      {
        "functionName": "DefineHelperClasses"
      },
      {
        "functionName": "PlayOnCamera"
      },
      {
        "functionName": "StopCamera"
      },
      {
        "functionName": "PauseCamera"
      },
      {
        "functionName": "ResumeCamera"
      },
      {
        "functionName": "CameraIsPlaying"
      },
      {
        "functionName": "CameraIsFinished"
      },
      {
        "functionName": "CameraTime"
      },
      {
        "functionName": "AnimationDuration"
      }
    ]
  },
  "eventsBasedBehaviors": [
    {
      "description": "Rejoue sur un objet 3D une animation (déplacements et rotations) enregistrée avec Studio Mocap.",
      "fullName": "Animation enregistrée (Studio Mocap)",
      "helpPath": "",
      "iconUrl": "",
      "name": "MocapAnimation",
      "objectType": "",
      "previewIconUrl": "",
      "eventsFunctions": [
        {
          "fullName": "",
          "functionType": "Action",
          "name": "onCreated",
          "sentence": "",
          "events": [
            {
              "type": "BuiltinCommonInstructions::Standard",
              "conditions": [],
              "actions": [
                {
                  "type": {
                    "value": "MocapPlayer::DefineHelperClasses"
                  },
                  "parameters": [
                    "",
                    ""
                  ]
                }
              ]
            },
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "// Rien d'autre : le lecteur est créé au premier lancement."
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "fullName": "",
          "functionType": "Action",
          "name": "doStepPreEvents",
          "sentence": "",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "const dt = (object.getElapsedTime ? object.getElapsedTime() : runtimeScene.getTimeManager().getElapsedTime()) / 1000;",
                "if (player.advance(dt)) ext.Core.applyToObject(object, player);"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Joue sur l'objet une animation enregistrée avec Studio Mocap, à partir de sa position et de sa rotation actuelles.",
          "fullName": "Jouer l'animation",
          "functionType": "Action",
          "group": "Animation enregistrée",
          "name": "Play",
          "sentence": "Jouer l'animation enregistrée sur _PARAM0_ (boucle : _PARAM3_, aller-retour : _PARAM9_, vitesse ×_PARAM4_, échelle _PARAM5_ px/cm)",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "if (player.play(eventsFunctionContext.getArgument(\"Data\"), ext.options(eventsFunctionContext), ext.Core.objectStart(object))) ext.Core.applyToObject(object, player);"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            },
            {
              "description": "Texte de l'animation (copié depuis Studio Mocap)",
              "name": "Data",
              "type": "string",
              "longDescription": "Par exemple une variable texte : VariableString(MonAnimation), ou le texte d'un objet Texte : MonTexte.Text()."
            },
            {
              "description": "Jouer en boucle",
              "name": "Loop",
              "type": "yesorno",
              "defaultValue": "no",
              "optional": true
            },
            {
              "description": "Vitesse (1 = normale, 2 = deux fois plus vite, -1 = à l'envers)",
              "name": "Speed",
              "type": "expression",
              "defaultValue": "1",
              "optional": true
            },
            {
              "description": "Pixels par cm réel",
              "name": "Scale",
              "type": "expression",
              "defaultValue": "1",
              "optional": true,
              "longDescription": "1 = 1 cm de mouvement du cube donne 1 pixel. Avec la Physique 3D par défaut (100 px = 1 m), 1 est l'échelle réelle."
            },
            {
              "description": "Appliquer les déplacements",
              "name": "ApplyPosition",
              "type": "yesorno",
              "defaultValue": "yes",
              "optional": true
            },
            {
              "description": "Appliquer les rotations",
              "name": "ApplyRotation",
              "type": "yesorno",
              "defaultValue": "yes",
              "optional": true
            },
            {
              "description": "Relatif à l'angle de départ",
              "name": "RelativeToAngle",
              "type": "yesorno",
              "defaultValue": "no",
              "optional": true,
              "longDescription": "Oui : le mouvement tourne avec l'objet. « S'éloigner de la webcam » devient « avancer devant l'objet » (un objet d'angle 0 regarde vers +X), et pencher le cube en avant penche l'objet vers son avant, quel que soit son angle."
            },
            {
              "description": "Aller-retour (rejouer à l'envers après la fin)",
              "name": "PingPong",
              "type": "yesorno",
              "defaultValue": "no",
              "optional": true,
              "longDescription": "Oui : à la fin, l'animation se rejoue à l'envers jusqu'au début, donc l'objet ou la caméra revient exactement à sa position et à sa rotation de départ. Avec la boucle, l'aller-retour se répète sans à-coup."
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Arrête l'animation. L'objet reste où il est, ou revient à sa position de départ.",
          "fullName": "Arrêter l'animation",
          "functionType": "Action",
          "group": "Animation enregistrée",
          "name": "Stop",
          "sentence": "Arrêter l'animation de _PARAM0_ (revenir au départ : _PARAM2_)",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "player.playing = false;",
                "if (eventsFunctionContext.getArgument(\"Reset\")) ext.restore(object, player, false);"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            },
            {
              "description": "Revenir à la position et à la rotation de départ",
              "name": "Reset",
              "type": "yesorno",
              "defaultValue": "no",
              "optional": true
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Met l'animation en pause.",
          "fullName": "Mettre en pause",
          "functionType": "Action",
          "group": "Animation enregistrée",
          "name": "Pause",
          "sentence": "Mettre en pause l'animation de _PARAM0_",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "player.playing = false;"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Reprend l'animation mise en pause.",
          "fullName": "Reprendre",
          "functionType": "Action",
          "group": "Animation enregistrée",
          "name": "Resume",
          "sentence": "Reprendre l'animation de _PARAM0_",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "if (player.anim && !player.finished) player.playing = true;"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Place l'animation à un instant précis (en secondes).",
          "fullName": "Aller à un instant",
          "functionType": "Action",
          "group": "Animation enregistrée",
          "name": "SetTime",
          "sentence": "Placer l'animation de _PARAM0_ à _PARAM2_ s",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "if (!player.anim) return;",
                "player.time = Math.max(0, Math.min(player.total(), eventsFunctionContext.getArgument(\"Time\")));",
                "player.finished = false;",
                "ext.Core.applyToObject(object, player);"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            },
            {
              "description": "Temps (secondes)",
              "name": "Time",
              "type": "expression"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Vérifie si l'animation est en train de jouer.",
          "fullName": "Animation en cours",
          "functionType": "Condition",
          "group": "Animation enregistrée",
          "name": "IsPlaying",
          "sentence": "L'animation de _PARAM0_ est en cours",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "eventsFunctionContext.returnValue = player.playing;"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Vérifie si l'animation est arrivée à la fin (pas en boucle).",
          "fullName": "Animation terminée",
          "functionType": "Condition",
          "group": "Animation enregistrée",
          "name": "IsFinished",
          "sentence": "L'animation de _PARAM0_ est terminée",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "eventsFunctionContext.returnValue = player.finished;"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": []
        },
        {
          "description": "Temps écoulé depuis le début du passage en cours (secondes, aller + retour en mode aller-retour).",
          "fullName": "Temps de l'animation",
          "functionType": "Expression",
          "group": "Animation enregistrée",
          "name": "Time",
          "sentence": "",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "eventsFunctionContext.returnValue = player.time;"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": [],
          "expressionType": {
            "type": "expression"
          }
        },
        {
          "description": "Durée d'un passage de l'animation en cours (secondes) : deux fois la durée enregistrée en mode aller-retour.",
          "fullName": "Durée de l'animation",
          "functionType": "Expression",
          "group": "Animation enregistrée",
          "name": "Duration",
          "sentence": "",
          "events": [
            {
              "type": "BuiltinCommonInstructions::JsCode",
              "inlineCode": [
                "const object = objects[0];",
                "const behavior = object.getBehavior(eventsFunctionContext.getBehaviorName(\"Behavior\"));",
                "const ext = gdjs.__mocapPlayerExtension;",
                "const player = ext.player(behavior);",
                "eventsFunctionContext.returnValue = player.total();"
              ],
              "parameterObjects": "Object",
              "useStrict": true,
              "eventsSheetExpanded": false
            }
          ],
          "parameters": [
            {
              "description": "Objet",
              "name": "Object",
              "type": "object"
            },
            {
              "description": "Comportement",
              "name": "Behavior",
              "supplementaryInformation": "MocapPlayer::MocapAnimation",
              "type": "behavior"
            }
          ],
          "objectGroups": [],
          "expressionType": {
            "type": "expression"
          }
        }
      ],
      "eventsFunctionsFolderStructure": {
        "folderName": "__ROOT",
        "children": [
          {
            "functionName": "onCreated"
          },
          {
            "functionName": "doStepPreEvents"
          },
          {
            "functionName": "Play"
          },
          {
            "functionName": "Stop"
          },
          {
            "functionName": "Pause"
          },
          {
            "functionName": "Resume"
          },
          {
            "functionName": "SetTime"
          },
          {
            "functionName": "IsPlaying"
          },
          {
            "functionName": "IsFinished"
          },
          {
            "functionName": "Time"
          },
          {
            "functionName": "Duration"
          }
        ]
      },
      "propertyDescriptors": [],
      "propertiesFolderStructure": {
        "folderName": "__ROOT",
        "children": []
      },
      "sharedPropertyDescriptors": []
    }
  ],
  "eventsBasedObjects": []
}