Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Visualizações Simultâneas

Visualize simultaneamente um modelo em vários sistemas gráficos.

TERMINAL

Este exemplo ilustra a animação de um modelo em quatro sistemas gráficos:

const terminal = document.getElementById(target_terminal);
const write = (text) => {
  terminal.innerText = text;
};

const model = init_model();

const context_c2d = new_context_c2d(target_c2d, model);
const context_svg = new_context_svg(objects_svg);
const context_x3d = new_context_x3d(objects_x3d);
const context_3js = new_context_3js(target_3js, model);

const step = function (ts) {
  model.update(ts);
  write(`AGE: ${model.age}`);
  context_c2d.render(model);
  context_svg.render(model);
  context_x3d.render(model);
  context_3js.render(model);

  requestAnimationFrame(step);
};

requestAnimationFrame(step);

Em particular:

  • O modelo usa um controlo por tempo (para o leader vermelho) e um controlo por passos (para o follower azul).
  • O modelo é atualizado em cada passo da animação através do método update.
  • A visualização é feita simultaneamente em quatro sistemas gráficos, C2D, SVG, 3JS e X3D.
  • Adicionalmente, aqui também se ilustra como organizar o código, separando-o por bibliotecas.

O Modelo

Este modelo é um exemplo simples que ilustra um controlo por tempo utilizando a biblioteca Tweenjs para fazer interpolações (tweens) e também um controlo por passos.

  1. O modelo é inicializado na função init_model — esta função define um objeto com os parâmetros necessários para a animação, mais alguns usados na visualização e acrescenta alguns métodos a esse objeto para tornar a programação da animação mais sistemática e ergonómica.
  2. O modelo é atualizado em cada passo da animação através do método update.
  3. Esta animação tem um controlo por tempo (tweens) para a posição do leader e também um controlo por passos para o follower.

Estado Inicial

Pretende-se que o movimento do leader percorra os quatro cantos do espaço e que o follower o persiga, limitado a uma velocidade máxima.

  • As posições, tanto do leader como do follower são representadas pelos atributos pos: {x:, y:} que usam coordenadas cartesianas.
  • Também são comuns outras propriedades gráficas, como o tamanho (size) e cor (color).
  • O leader não precisa de velocidade porque a sua posição é diretamente controlada por tweens.
  • O follower precisa de velocidade porque o seu movimento é constantemente orientado para a posição do leader. Além disso, este movimento deve estar limitado a uma rapidez máxima, max_speed.
  • Também o modelo tem atributos gráficos para definir o tamanho da cena (width, height) e a cor de fundo (background).
  • Finalmente, age é a «idade» do modelo, isto é, o seu número de passos (atualizações).
function init_model() {
  const model = {
    age: 0,
    leader: {
      pos: { x: 0, y: 0 },
      size: 16,
      color: "crimson",
    },
    follower: {
      pos: { x: 246, y: 246 },
      vel: { x: 0, y: 0 },
      max_speed: 1000,
      size: 8,
      color: "steelblue",
    },
    background: "khaki",
    width: 256,
    height: 256,
  };
  
  // Further instructions

  return model;
}

Controlo por Tempo

O movimento do leader é controlado por tempo; para esse efeito usa-se a biblioteca tween.js.

O leader deve deslocar-se pelos quatro cantos da cena, como neste exemplo.

function init_model() {
  const model = { ... };

  const duration_ms = 1000;
  const easing = Easing.Cubic.InOut;
  const right = new Tween(model.leader.pos)
    .to(
      {
        x: model.width - model.leader.size,
        y: 0,
      },
      duration_ms,
    )
    .easing(easing);
  const down = new Tween(model.leader.pos)
    .to(
      {
        x: model.width - model.leader.size,
        y: model.height - model.leader.size,
      },
      duration_ms,
    )
    .easing(easing);
  const left = new Tween(model.leader.pos)
    .to({ x: 0, y: model.height - model.leader.size }, duration_ms)
    .easing(easing);
  const up = new Tween(model.leader.pos)
    .to({ x: 0, y: 0 }, duration_ms)
    .easing(easing);

  right.chain(down);
  down.chain(left);
  left.chain(up);
  up.chain(right);
  right.start();

  const group = new Group();
  group.add(right, down, left, up);
  model.leader.tween = group;

  // Further instructions

  return model;
}
  • O grupo de tweens é adicionado a model no atributo model.leader.tween.
  • Posteriormente, durante a atualização, esse grupo será atualizado de forma a controlar a posição do leader.

Tempos e Atualização

Os restantes atributos do modelo representam (1) momentos «importantes» e (2) o método de atualização.

function init_model() {
  const model = { ...
  };

  // Previous instructions

  model.start = performance.now();
  model.last_ts = model.start;

  model.update = update;

  return model;
}

O atributo model.last_ts é atualizado em cada passo da animação e regista o momento em que esse passo foi executado. Esta informação é importante para determinar o tempo decorrido desde o passo anterior.

Atualização do Modelo

A atualização do modelo é implementada no método update e segue uma determinada sequência.

  1. Atualização dos tempos — deste o passo anterior; número de passos, etc.
  2. Atualização dos tweens para controlo da posição do leader.
  3. Velocidade e posição do follower — a nova posição do leader determina um novo rumo para o follower; esse cálculo é mais simples usando coordenadas polares.

Atualização dos Tempos

function update(ts) {
  this.age += 1;
  const dt = 0.001 * (ts - this.last_ts);
  this.last_ts = ts;

  // Further instructions 
}

O argumento ts é o timestamp (carimbo temporal) e é o instante (em milisegundos) de evocação do método update.

  • As primeiras instruções tratam do progresso do tempo:
  • this.age += 1; incrementa a «idade» do modelo;
  • As restantes duas instruções determinam:
    • O tempo decorrido deste o passo anterior (dt).
    • Atualizam o registo do momento do passo this.last_ts = ts.

Atualização dos Tweens

A posição do leader é controlada pelo atributo leader.tween.

  • Este atributo é um grupo de tweens definido na inicialização do modelo.
  • Um grupo de tweens permite a atualização simultânea de todos os tweens nesse grupo.
function update(ts) {
  // Previous instructions

  this.leader.tween.update(ts);

  // Further instructions
}

Com os tweens bem definidos na inicialização do modelo basta esta instrução para atualizar a posição do leader.

Velocidade e Posição do Seguidor

O follower (seguidor) desloca-se na direção do leader mas tem a velocidade limitada a um valor máximo (model.follower.max_speed).

Este passo é idêntico ao exemplo seguidor interativo.

function update(ts) {
  // Previous instructions

  this.leader.tween.update(ts);

  // FOLLOWER
  //
  // Heading
  const follower_heading_cart = dir(this.follower.pos, this.leader.pos);
  const follower_heading_pol = cart2polar(follower_heading_cart);
  //
  // Max speed
  follower_heading_pol.d = Math.min(
    this.follower.max_speed,
    dist(this.follower.pos, this.leader.pos),
  );
  //
  // Cartesian vel
  this.follower.vel = polar2cart(follower_heading_pol);
  //
  // Position update
  this.follower.pos.x += this.follower.vel.x * dt;
  this.follower.pos.y += this.follower.vel.y * dt;
}

A Biblioteca do Modelo

Toda a funcionalidade que diz respeito ao modelo dve ficar «isolada» numa biblioteca, importando e exportando apenas o que for necessário.

Conteúdo do ficheiro model_grsystems.js

import { Tween, Easing, Group } from "tween";
export { init_model };

function cart2polar(p) { ... }
function polar2cart(p) { ... }
function dir(p, q) { ... }
function dist(p, q) { ... }

function init_model() {
  ... 
  return model;
}

function update(ts) {
  ... 
}
  • A primeira instrução importa a biblioteca tween porque são necessários tweens para o controlo da posição do leader.
  • A instrução export { init_model }; declara que a única função definida aqui que pode ser usada «fora» é init_model.
  • As restantes linhas definem as várias funções e métodos de apoio a init_model.

A função init_model devolve o objeto model; todos os atributos e métodos de model definidos nessa função são acessíveis «fora» da biblioteca.

A Visualização

A visualização do modelo consiste na construção (render) de uma imagem em cada passo da animação, para cada um dos quatro sistemas gráficos C2D, SVG, 3JS e X3D.

Visualização C2D

No exemplo Seguidor Interativo já se definiu suporte para a visualização em C2D, que também funciona para este modelo.

Para esta visualização falta «arrumar» a inicialização numa função (new_context_c2d) e agrupar «tudo» numa biblioteca (render_c2d.js):

export { new_context_c2d };

function render_c2d(m) { ... }
function render_char(c) { ... }

function new_context_c2d(target_c2d, model) {
  const context = document.getElementById(target_c2d).getContext("2d");
  context.render = render_c2d;
  context.render_char = render_char;
  context.canvas.width = model.width;
  context.canvas.height = model.height;

  return context;
}

Esta biblioteca exporta apenas a função new_context_c2d:

  • O argumento target_c2d é o identificador do elemento <canvas> onde esta visualização vai ser construída.
  • O argumento model serve para dimensionar esse <canvas>.
  • O objeto devolvido, context, é um CanvasRenderingContext2D a que foram acrescentados os métodos render e render_char.

Visualização SVG

A visualização usando o sistema SVG depende da existência de certos elementos SVG no documento HTML e agrupa esses elementos com funções para os inicializar e atualizar.

Elementos SVG

<svg id="svg:container" width="256px" height="256px">
    <rect
        id="svg:bg"
        x="0"
        y="0"
        width="256"
        height="256"
        fill="khaki"
    />
    <rect
        id="svg:leader"
        x="60"
        y="60"
        width="15"
        height="15"
        fill="crimson"
    />
    <rect
        id="svg:follower"
        x="200"
        y="200"
        width="10"
        height="10"
        fill="steelblue"
    />
</svg>

No fragmento HTML acima estão identificados:

  • O elemento svg tem id="svg:container".
  • Os elementos rect têm:
    • id="svg:bg" para o fundo.
    • id="svg:leader" para o leader.
    • id="svg:follower" para o follower.

Biblioteca para um «Contexto» SVG.

O «contexto» SVG tem informação sobre quais são os elementos a atualizar (o leader e o follower) e métodos para atualizar os respetivos atributos.

Para esta animação particular as identificações do leader e do follower, agrupados num único objeto, ficam:

const objects_svg = {
  leader: "svg:leader",
  follower: "svg:follower"
}

Este objeto tem a informação necessária para construir e atualizar um «contexto» SVG. Toda essa funcionalidade deve ficar agrupada numa biblioteca (render_svg.js):

export { new_context_svg };

function render_svg(m) {
  this.leader.setAttribute("x", m.leader.pos.x);
  this.leader.setAttribute("y", m.leader.pos.y);
  this.follower.setAttribute("x", m.follower.pos.x);
  this.follower.setAttribute("y", m.follower.pos.y);
}

function new_context_svg(objects_svg) {
  return {
    leader: document.getElementById(objects_svg.leader),
    follower: document.getElementById(objects_svg.follower),
    render: render_svg,
  };
}

Esta biblioteca exporta apenas a função new_context_svg:

  • O argumento objects_svg tem a estrutura já ilustrada, com os id dos elementos a animar.
  • O objeto devolvido tem:
    • Os elementos leader e follower identificados.
    • O método render, que é executado pela função render_svg.
  • A função render_svg:
    • Tem argumento m, um modelo («compatível»).
    • Usa as posições do leader e do follower do modelo (m.leader.pos e m.follower.pos) para atualizar as posições (os atributos "x" e "y") do leader e do follower do contexto (this).

Visualização X3D

A visualização para o sistema X3D é muito semelhante à visualização para SVG dado que estes dois sistemas definem a visualização com elementos integrados no documento (veja Document Object Model (DOM)).

Elementos X3D

Os objetos gráficos no X3D são posicionados (e rodados, dimensionados, etc.) usando elementos Transform. Portanto no X3D os objetos leader e follower são, de facto, Transform:

<X3D width="256px" height="256px">
    <Scene>
        <Background skyColor=".98 .98 .92"></Background>
        <Transform id="x3d:leader" translation="-6 -6 0">
            <Shape>
                <Appearance>
                    <Material id="x3d:leader.material" diffuseColor="crimson"> </Material>
                </Appearance>
                <Box size="0.6 0.6 0.6"></Box>
            </Shape>
        </Transform>
        <Transform id="x3d:follower" translation="0 0 0">
            <Shape>
                <Appearance>
                    <Material diffuseColor="steelblue"> </Material>
                </Appearance>
                <Box size="0.2 0.2 0.2"></Box>
            </Shape>
        </Transform>
    </Scene>
</X3D>

Neste fragmento estão identificados os elementos Transform:

  • id="x3d:leader" para o leader.
  • id="x3d:follower" para o follower.

Limites Geométricos do Modelo e Sistemas 3D

As posições e as dimensões dos objetos do modelo estão em escalas significativamente diferentes das escalas típicas num sistema 3D.

O modelo existe num espaço 2D, com coordenadas x,y e limites 256x256. Num sistema gráfico 3D é necessária mais uma coordenada (z) e tipicamente os limites são menores (por exemplo, 10 x 10 x 10).

Por isso:

  • As dimensões das geometrias acima são reduzidas:
    • <Box size="0.6 0.6 0.6"></Box> para o leader.
    • <Box size="0.2 0.2 0.2"></Box> para o follower.
  • As funções que atualizarem as visualizações 3D terão de fazer um ajuste adequado às posições que o modelo reporta.

Biblioteca para um «Contexto» X3D.

O «contexto» X3D tem informação sobre quais são os elementos a atualizar (o leader e o follower) e métodos para atualizar os respetivos atributos.

Para esta animação particular as identificações do leader e do follower, agrupados num único objeto, ficam:

const objects_x3d = {
  leader: "x3d:leader",
  follower: "x3d:follower"
};

Este objeto tem a informação necessária para construir e atualizar um «contexto» X3D. Toda essa funcionalidade deve ficar agrupada numa biblioteca (render_x3d.js):

export { new_context_x3d };

function render_x3d(m) {
  const leader_x = (m.leader.pos.x / m.width - 0.5) * 5;
  const leader_y = (m.leader.pos.y / m.height - 0.5) * 5;
  this.leader.setAttribute("translation", `${leader_x} ${leader_y} 0.0`);
  const follower_x = (m.follower.pos.x / m.width - 0.5) * 5;
  const follower_y = (m.follower.pos.y / m.height - 0.5) * 5;
  this.follower.setAttribute("translation", `${follower_x} ${follower_y} 0.0`);
}

function new_context_x3d(objects_x3d) {
  return {
    leader: document.getElementById(objects_x3d.leader),
    follower: document.getElementById(objects_x3d.follower),
    render: render_x3d,
  };
}

Esta biblioteca exporta apenas a função new_context_x3d:

  • O argumento objects_x3d tem a estrutura já ilustrada, com os id dos elementos a animar.
  • O objeto devolvido tem:
    • Os elementos leader e follower identificados.
    • O método render, que é executado pela função render_x3d.
  • A função render_x3d:
    • Tem argumento m, um modelo («compatível»).
    • Usa as posições do leader e do follower do modelo (m.leader.pos e m.follower.pos) para atualizar as posições (os atributos "translation") do leader e do follower do contexto (this).
  • Nesta atualização as coordenadas «do modelo» são convertidas para coordenadas típicas em sistemas 3D:
    • Os valores x do modelo variam entre 0 e m.width e são convertidos para valores entre -2.5 e 2.5.
    • Analogamente, os valores y do modelo, que variam entre 0 e model.height, são convertidos para valores entre -2.5 e 2.5.

Visualização 3JS

O sistema 3JS é semelhante ao sistema X3D e SVG porque os objetos gráficos são «pré-construídos» mas, como o C2D, o modelo gráfico é definido por instruções e estruturas de dados.

O esquema usado para os outros sistemas gráficos é o seguinte:

  • Define-se uma biblioteca render_XYZ.js.
  • Essa biblioteca exporta uma função new_context_XYZ que:
    • Tem como argumentos a informação necessária inicialização do «contexto», como a identificação dos elementos dom ou o próprio modelo.
    • Devolve um objeto, um «contexto», com um método render adequado à visualização do modelo.

Com base nos modelos disponíveis, a biblioteca render_3js.js fica:

import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";

export { new_context_3js };

function new_context_3js(container_id, model) {
  // New Context for 3JS 
}

Esta biblioteca exporta apenas a função new_context_3js, detalhada mais abaixo.

  • O argumento container_id é o identificador de um elemento (no documento) onde esta visualização vai ser construída.
  • O argumento model serve para dimensionar esse elemento.

Um «Contexto» para 3JS

A inicialização do «contexto» para o sistema 3JS segue os passos que estão descritos, por exemplo, aqui:

function new_context_3js(container_id, model) {
  const container = document.getElementById(container_id);
  container.width = model.width;
  container.height = model.height;

  const renderer = new THREE.WebGLRenderer({ alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(container.width, container.height);
  renderer.setClearColor("#FAFAEB", 1.0);
  container.appendChild(renderer.domElement);

  const camera = new THREE.PerspectiveCamera(
    35, // abertura
    container.width / container.height, // proporção largura/altura
    0.1, // corte perto
    10000, // corte longe
  );
  camera.position.set(-2.5, 0, 20);

  const controls = new OrbitControls(camera, renderer.domElement);

  const scene = new THREE.Scene();
  camera.lookAt(scene.position);
  
  const light = new THREE.AmbientLight("white");
  light.intensity = 2.0;
  scene.add(light);

  // Further instructions
}

Neste fragmento está definido:

  1. Uma referência para o elemento HTML que vai receber a visualização: container.
  2. Um objeto para construir a visualização da cena: renderer.
  3. Uma câmara para definir a visualização da cena: camera.
  4. Um objeto para controlar a câmara: controls.
  5. Uma cena para conter os objetos gráficos: scene.
  6. Iluminação para iluminar a cena: light.

Falta adicionar objetos gráficos para o leader e para o follower:

function new_context_3js(container_id, model) {
  const container = ...;
  const renderer = ...;
  const camera = ...;
  const controls = ...;
  const scene = ...;
  const light = ...;

  //
  // LEADER
  const leader_geo = new THREE.BoxGeometry(0.6, 0.6, 0.6);
  const leader_mat = new THREE.MeshLambertMaterial({
    color: model.leader.color,
  });
  const leader = new THREE.Mesh(leader_geo, leader_mat);
  scene.add(leader);

  // FOLLOWER
  const follower_geo = new THREE.BoxGeometry(0.2, 0.2, 0.2);
  const follower_mat = new THREE.MeshLambertMaterial({
    color: model.follower.color,
  });
  const follower = new THREE.Mesh(follower_geo, follower_mat);
  scene.add(follower);

  // Further instructions
}

Todos os elementos da visualização estão definidos. Falta definir o método de atualização (render) e devolver o «contexto»:

function new_context_3js(container_id, model) {
  const container = ...;
  const renderer = ...;
  const camera = ...;
  const controls = ...;
  const scene = ...;
  const light = ...;

  //
  // LEADER
  const leader = ...;

  // FOLLOWER
  const follower = ...;

  //
  // RENDER
  function render(m) {
    controls.update(); // Atualizar os controlos
    const leader_x = (m.leader.pos.x / m.width - 0.5) * 5;
    const leader_y = (m.leader.pos.y / m.height - 0.5) * 5;
    leader.position.set(leader_x, leader_y, 0);

    const follower_x = (m.follower.pos.x / m.width - 0.5) * 5;
    const follower_y = (m.follower.pos.y / m.height - 0.5) * 5;
    follower.position.set(follower_x, follower_y, 0);
    renderer.render(scene, camera); // Construir a cena atualizada
  }

  return {
    render: render,
  };
}
  • O objeto devolvido é {render: render} que usa a função render para atualizar as posições do leader e do follower e a visualização da cena.
  • Tal como na visualização do X3D, as coordenadas do modelo são convertidas (da mesma forma) para um referencial mais típico em sistemas 3D.

Este exemplo funciona porque a função function render(m) { ... } «capta» todo o contexto em que é definida.

Isto é, render(m) mantém «vivos» os objetos no contexto em que é definida e que refere diretamente e também os objetos que refere indiretamente. Por exemplo:

  • Referências diretas do contexto: controls, leader, follower, renderer, scene, camera.
  • Referências indiretas: container, light, e os vários objetos auxiliares (e.g. leader_geo).

O Ciclo de Animação

O ciclo de animação inclui a atualização seguida da visualização do modelo nos quatro sistemas gráficos e é definido pela função step. Este nível mais elevado da animação pode ficar na sua própria biblioteca, onde são compostos e arranjados (quase) todos os elementos principais.

  • São importadas as bibliotecas de adaptação aos sistemas gráficos e também a biblioteca do modelo.
  • Os argumentos da função main() identificam os elementos html controlados pela animação.
  • O modelo, e os «contextos» são inicializados.
  • É definido o passo do ciclo de animação.
  • É ativado o ciclo de animação.

Conteúdo do ficheiro grsystems.js:

import { new_context_c2d } from "render_c2d";
import { new_context_svg } from "render_svg";
import { new_context_3js } from "render_3js";
import { new_context_x3d } from "render_x3d";
import { init_model } from "model";

export { main as default };

function main(
  target_c2d,
  target_3js,
  target_terminal,
  objects_svg,
  objects_x3d,
) {
  const terminal = document.getElementById(target_terminal);
  const write = (text) => {
    terminal.innerText = text;
  };

  const model = init_model();

  const context_c2d = new_context_c2d(target_c2d, model);
  const context_svg = new_context_svg(objects_svg);
  const context_x3d = new_context_x3d(objects_x3d);
  const context_3js = new_context_3js(target_3js, model);

  const step = function (ts) {
    model.update(ts);
    write(`AGE: ${model.age}`);
    context_c2d.render(model);
    context_svg.render(model);
    context_x3d.render(model);
    context_3js.render(model);

    requestAnimationFrame(step);
  };

  requestAnimationFrame(step);
}
  1. terminal é o elemento html que vai ser a «consola», onde é mostrada informação textual; A função write permite escrever para essa «consola».
  2. model é inicializado pela função init_model.
  3. São criados quatro contextos, um para cada sistema: context_c2d, context_svg,context_x3d,context_3js.
  4. A função step define o passo no ciclo de animação:
  5. O modelo é atualizado;
  6. É mostrada a idade do modelo na consola;
  7. O modelo é simultaneamente visualizado nos quatro «contextos».
  8. É colocado um pedido para o browser executar step na próxima atualização desta página.
  9. É colocado o primeiro pedido para o browser executar step na próxima atualização desta página.

A Parte HTML

A animação existe num documento HTML que tem de incorporar os vários containers para as visualizações, assim como definir as importações necessárias.

Suporte para o X3D:

<script
    type="application/javascript"
    src="./lib/x3dom/x3dom-full.js"
></script>
<link
    rel="stylesheet"
    type="text/css"
    href="./lib/x3dom/x3dom.css"
/>

Importação de bibliotecas JavaScript. Inclui:

  • tweens com o tween.js.
  • 3JS e respetivos addons (para o controlo orbital da câmara).
  • As bibliotecas descritas aqui: o modelo (model), os «contextos» (render_XYZ) e o controlo da animação (grsystems).
<script type="importmap">
    {
        "imports": {
            "tween": "./lib/tweenjs/tween.esm.js",
            "three": "./lib/threejs/build/three.module.js",
            "three/addons/": "./lib/threejs/addons/jsm/",
            "grsystems": "./grsystems.js",
            "model": "./model_grsystems.js",
            "render_c2d": "./render_c2d.js",
            "render_svg": "./render_svg.js",
            "render_3js": "./render_3js.js",
            "render_x3d": "./render_x3d.js"
        }
    }
</script>

Grelha com as visualizações:

<div style="display:grid;grid-template-columns:256px 256px;margin:0 auto;width: fit-content">
    <div id="terminal" style="font-family: monospace;padding:0.5em;background:black;color:seagreen;grid-column:1/ span 2">TERMINAL</div>
    <canvas id="c2d:canvas"></canvas>
    <div id="3js:container"></div>
    <X3D width="256px" height="256px">
        ...
    </X3D>
    <svg id="svg:container" width="256px" height="256px">
        ...
    </svg>
</div>

Ativação da animação:

<script type="module">
    import main from "grsystems";
    const objects_svg = {
      leader: "svg:leader",
      follower: "svg:follower"
    };
    const objects_x3d = {
      leader: "x3d:leader",
      follower: "x3d:follower"
    };
    main("c2d:canvas", "3js:container", "terminal", objects_svg, objects_x3d);
</script>

O Que Falta (Exercícios)

Uma Imagem Estereoscópica
  • Torne a animação mais sofisticada, seguindo estas sugestões.
  • Esta animação não tem eventos externos. Procure na documentação adequada como controlar o movimento do leader usando o rato.
  • Adicione a possibilidade de pausar a animação: em modo de pausa o modelo apenas atualiza um conjunto reduzido de parâmetros (e.g. age e last_ts).
  • Considere uma variante deste exercício em que, em vez de quarto sistemas gráficos, usa duas visualizações 3JS do mesmo modelo que diferem apenas ligeiramente na posição da câmara. Isto é, faça uma visualização estereoscópica da animação.