Visualizações Simultâneas
Visualize simultaneamente um modelo em vários sistemas gráficos.
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
leadervermelho) e um controlo por passos (para ofollowerazul). - 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,3JSeX3D. - 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
Tweenjspara fazer interpolações (tweens) e também um controlo por passos.
- 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. - O modelo é atualizado em cada passo da animação através do método
update. - Esta animação tem um controlo por tempo (tweens) para a posição do
leadere também um controlo por passos para ofollower.
Estado Inicial
Pretende-se que o movimento do
leaderpercorra os quatro cantos do espaço e que ofollowero persiga, limitado a uma velocidade máxima.
- As posições, tanto do
leadercomo dofollowersão representadas pelos atributospos: {x:, y:}que usam coordenadas cartesianas. - Também são comuns outras propriedades gráficas, como o tamanho (
size) e cor (color). - O
leadernão precisa de velocidade porque a sua posição é diretamente controlada por tweens. - O
followerprecisa de velocidade porque o seu movimento é constantemente orientado para a posição doleader. 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 bibliotecatween.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
modelno atributomodel.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
updatee segue uma determinada sequência.
- Atualização dos tempos — deste o passo anterior; número de passos, etc.
- Atualização dos tweens para controlo da posição do
leader. - Velocidade e posição do
follower— a nova posição doleaderdetermina um novo rumo para ofollower; 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.
- O tempo decorrido deste o passo anterior (
Atualização dos Tweens
A posição do
leaderé controlada pelo atributoleader.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 doleadermas 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
tweenporque são necessários tweens para o controlo da posição doleader. - 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_modeldevolve o objetomodel; todos os atributos e métodos demodeldefinidos 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,3JSeX3D.
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
modelserve para dimensionar esse<canvas>. - O objeto devolvido,
context, é um CanvasRenderingContext2D a que foram acrescentados os métodosrendererender_char.
Visualização SVG
A visualização usando o sistema
SVGdepende da existência de certos elementosSVGno documentoHTMLe 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
svgtemid="svg:container". - Os elementos
recttêm:id="svg:bg"para o fundo.id="svg:leader"para oleader.id="svg:follower"para ofollower.
Biblioteca para um «Contexto» SVG.
O «contexto»
SVGtem informação sobre quais são os elementos a atualizar (oleadere ofollower) 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_svgtem a estrutura já ilustrada, com osiddos elementos a animar. - O objeto devolvido tem:
- Os elementos
leaderefolloweridentificados. - O método
render, que é executado pela funçãorender_svg.
- Os elementos
- A função
render_svg:- Tem argumento
m, um modelo («compatível»). - Usa as posições do
leadere dofollowerdo modelo (m.leader.posem.follower.pos) para atualizar as posições (os atributos"x"e"y") doleadere dofollowerdo contexto (this).
- Tem argumento
Visualização X3D
A visualização para o sistema
X3Dé muito semelhante à visualização paraSVGdado 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 oleader.id="x3d:follower"para ofollower.
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 oleader.<Box size="0.2 0.2 0.2"></Box>para ofollower.
- 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»
X3Dtem informação sobre quais são os elementos a atualizar (oleadere ofollower) 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_x3dtem a estrutura já ilustrada, com osiddos elementos a animar. - O objeto devolvido tem:
- Os elementos
leaderefolloweridentificados. - O método
render, que é executado pela funçãorender_x3d.
- Os elementos
- A função
render_x3d:- Tem argumento
m, um modelo («compatível»). - Usa as posições do
leadere dofollowerdo modelo (m.leader.posem.follower.pos) para atualizar as posições (os atributos"translation") doleadere dofollowerdo contexto (this).
- Tem argumento
- Nesta atualização as coordenadas «do modelo» são convertidas para coordenadas típicas em sistemas 3D:
- Os valores
xdo modelo variam entre0em.widthe são convertidos para valores entre-2.5e2.5. - Analogamente, os valores
ydo modelo, que variam entre0emodel.height, são convertidos para valores entre-2.5e2.5.
- Os valores
Visualização 3JS
O sistema
3JSé semelhante ao sistemaX3DeSVGporque os objetos gráficos são «pré-construídos» mas, como oC2D, 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_XYZque:- Tem como argumentos a informação necessária inicialização do «contexto», como a identificação dos elementos
domou o próprio modelo. - Devolve um objeto, um «contexto», com um método
renderadequado à visualização do modelo.
- Tem como argumentos a informação necessária inicialização do «contexto», como a identificação dos elementos
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
modelserve 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:
- Uma referência para o elemento
HTMLque vai receber a visualização:container. - Um objeto para construir a visualização da cena:
renderer. - Uma câmara para definir a visualização da cena:
camera. - Um objeto para controlar a câmara:
controls. - Uma cena para conter os objetos gráficos:
scene. - 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çãorenderpara atualizar as posições doleadere dofollowere 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 sistemas3D.
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 elementoshtmlcontrolados 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);
}
terminalé o elementohtmlque vai ser a «consola», onde é mostrada informação textual; A funçãowritepermite escrever para essa «consola».modelé inicializado pela funçãoinit_model.- São criados quatro contextos, um para cada sistema:
context_c2d,context_svg,context_x3d,context_3js. - A função
stepdefine o passo no ciclo de animação: - O modelo é atualizado;
- É mostrada a idade do modelo na consola;
- O modelo é simultaneamente visualizado nos quatro «contextos».
- É colocado um pedido para o browser executar
stepna próxima atualização desta página. - É colocado o primeiro pedido para o browser executar
stepna próxima atualização desta página.
A Parte HTML
A animação existe num documento
HTMLque 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. 3JSe 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
leaderusando 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.
ageelast_ts). - Considere uma variante deste exercício em que, em vez de quarto sistemas gráficos, usa duas visualizações
3JSdo mesmo modelo que diferem apenas ligeiramente na posição da câmara. Isto é, faça uma visualização estereoscópica da animação.
